Adaulith
Posts: 17
Joined: Thu Oct 19, 2017 8:47 pm

Multithreading guidiance

Fri Jan 19, 2018 3:58 pm

Just wanted to pick at all your smart peoples brains to see if I'm on the right track here. After some delay I'm back working on a project. Basically it's a batch reactor with some sensor inputs.

The sensors inputs are

- Dissolved oxygen (DO) https://www.atlas-scientific.com/dissolved-oxygen.html
- Temperature https://www.atlas-scientific.com/temperature.html
- pH https://www.atlas-scientific.com/ph.html
- Water level
- Color 520nm

There are also various pumps to control - these are based on pH, and other factors that aren't important for this discussion.
I am fairly new to Python, but am coding it at the request of the end user. I have to say I am enjoying learning it though. Otherwise I probably would have used C++, which I am more familiar with, but by no means any expert. I am a chemist, not a programmer.

I have a gui that I developed using PyQt5, it's simple but it works great for what I need.

The water level and color sensors are only used a few times each day - so they can be excluded. They aren't an issue.

The problem I am having is with the DO, temperature and pH sensors. I am using sensors from Atlas Scientific in I2C mode. There is a 1.5 second delay that 'hangs' the user interface between when the request is sent to the sensor and the data is received from it. Not so bad, but I am planning on gathering pH, temp and DO every 10 seconds. That would make for a very clunky user interface.

I am have little to no experience with programing multithreading. So I just am hoping someone can tell me if I'm on the right track here.
I saw the guide at
http://www.tutorialspoint.com/python/py ... eading.htm
and it seems I am, but I have a lot to learn!

But here Is what I am thinking - Having 3 threads in my program:

Thread 1: User Interface, Would also control all the time, data recording, etc....
Thread 2: Sensors, collect information from the sensors
Thread 3: Pumps, whilst thread 3 is running it would suspend thread 2. The sensors of water level and color would be in here.

Would it makes sense to do it this way? Or combine threads 2 and 3?

For the record I do have a working version using an Arduino Mega and a little touch screen, but the client wants a more modern user friendly interface.

Thanks for any suggestions! If I need to provide any more info please let me know!

User avatar
davef21370
Posts: 897
Joined: Fri Sep 21, 2012 4:13 pm
Location: Earth But Not Grounded

Re: Multithreading guidiance

Fri Jan 19, 2018 4:07 pm

I wouldn't bother using threading for the UI, data recording, etc. but certainly for the data aquisition if the sensors are laggy.

Dave.
Apple say... Monkey do !!

Adaulith
Posts: 17
Joined: Thu Oct 19, 2017 8:47 pm

Re: Multithreading guidiance

Fri Jan 19, 2018 4:46 pm

Thanks,

I guess I mean by the UI thread I mean just the "thread" that is created when I create the script - maybe not the correct terminology?

User avatar
bensimmo
Posts: 3468
Joined: Sun Dec 28, 2014 3:02 pm
Location: East Yorkshire

Re: Multithreading guidiance

Fri Jan 19, 2018 7:20 pm

You could thread each sensor if it takes time to get a result and just leave them running in the background. Just ask for it's value and get whatever was the last variable update for it was.
I cannot see a need to stop them when using the pumps, just don't call them for their value if it would mess data up.

I have used threading for file writes and sensor collecting, that was made easy as RPI had pretty much done it for me. I have used the same principles for other sensors too.

You can have a read over the old SenseHAT learning resource and the code is there as well
https://github.com/raspberrypilearning/ ... ata-logger
(the new revision of it in the Education section isn't half as useful :-( they could do with a part2 with all the old interesting parts added back in )

anyway
I've not found an easy way to stop a thread (mainly as there is a .start & .run but no .stop :roll: ) so it's easy to leave them running in the background when testing code.)
I know as I was trying to thread DS180B20 sensors which need 750ms to get it's result and a few of them one after the other it becomes a pain as they get them one after another in a normal loop.
I have managed to have them all threaded so I can get result all at once.
I think closing them down was my downfall. Must get back to that at some point.

Have fun and do post back anything you come up with.

I use Python3 as it's actively developed.

Adaulith
Posts: 17
Joined: Thu Oct 19, 2017 8:47 pm

Re: Multithreading guidiance

Fri Jan 19, 2018 8:07 pm

bensimmo wrote:
Fri Jan 19, 2018 7:20 pm
You could thread each sensor if it takes time to get a result and just leave them running in the background. Just ask for it's value and get whatever was the last variable update for it was.
I cannot see a need to stop them when using the pumps, just don't call them for their value if it would mess data up.
Sometimes the answer is that simple :D . No need to "stop" the thread. Just ignore the data for a while, when the pumps are doing their thing. A simple Boolean expression could take care of that :D . I think you saved me a future headache. Many thanks!

User avatar
OutoftheBOTS
Posts: 676
Joined: Tue Aug 01, 2017 10:06 am

Re: Multithreading guidiance

Fri Jan 19, 2018 8:17 pm

I am far from a expert on threading in fact I tend to take experts opinion and try avoid using threading because of all the potintial problems it opens up.

First I would ask why is it taking so long to read the sensors if your using i2C the RPi is the master device it sets the clock speed for coms usually 400hz (400 bits transferred every second). I2C has a little overheads like every transmission sends the slave address, register address then the data to read or write so this means and extra 16bits with each transmission. I assume when you read data from the sensor you are maybe reading 2 x 8 bit registers (16 bits) plus the 16 bits of the overheads is a total of 32 bits at 400 bit/sec shouls mean that you should read the sensor in 0.08 of a second.

This time taken to communicate from the sensor isn't controlled by the sensor but by the master device the RPi.

I personally would write a simple program like this to read sensors without using threads as it will be less problematic and require less resources from the RPi

Adaulith
Posts: 17
Joined: Thu Oct 19, 2017 8:47 pm

Re: Multithreading guidiance

Fri Jan 19, 2018 9:07 pm

OutoftheBOTS wrote:
Fri Jan 19, 2018 8:17 pm
I am far from a expert on threading in fact I tend to take experts opinion and try avoid using threading because of all the potintial problems it opens up.

First I would ask why is it taking so long to read the sensors if your using i2C the RPi is the master device it sets the clock speed for coms usually 400hz (400 bits transferred every second). I2C has a little overheads like every transmission sends the slave address, register address then the data to read or write so this means and extra 16bits with each transmission. I assume when you read data from the sensor you are maybe reading 2 x 8 bit registers (16 bits) plus the 16 bits of the overheads is a total of 32 bits at 400 bit/sec shouls mean that you should read the sensor in 0.08 of a second.

This time taken to communicate from the sensor isn't controlled by the sensor but by the master device the RPi.

I personally would write a simple program like this to read sensors without using threads as it will be less problematic and require less resources from the RPi
It's the nature of the Atlas sensor circuit itself.
You have to send a command to it for example 'R' for read, wait 1.5 seconds, then read it.
The Atlas sensor circuits return true values for pH, DO, Not voltage readings. There delay is from the processing time on the sensor chip.
Last edited by Adaulith on Sat Jan 20, 2018 11:49 pm, edited 1 time in total.

Adaulith
Posts: 17
Joined: Thu Oct 19, 2017 8:47 pm

Re: Multithreading guidiance

Sat Jan 20, 2018 4:04 pm

Well I started to put together some code.
I took just a skeleton portion of it , to try and get it working from the rest of my program. The code I post runs, but it doesn't do what I want it to do.
The GUI still 'hangs' for the 1.5 seconds that it sleeps (the long_timeout variable) in the temp_probe class.

Code: Select all

import sys
import fcntl
import io
import time
import threading 
from time import sleep
from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication, QLineEdit, QPushButton, QWidget, QDialog, QTextEdit, QLabel, QMenu, QVBoxLayout, QSizePolicy
from PyQt5.QtGui import *
from PyQt5.QtCore import Qt, QTime, QTimer, QRect
from PyQt5 import QtCore


versionNumber = 'V0.1'

inTemp = 0.00
temp_average = 0.00
temp_counter = 0

pH = 0.00
pH_average = 0.00
pH_counter = 0

do = 0.00
do_average = 0.00
do_counter = 0

class mainMenu(QMainWindow):
	def __init__(self):
		super().__init__()
		self.topL = 20
		self.top = 40
		self.width = 800
		self.height = 400
		self.title = ('WIST Batch Reactor ' + versionNumber)
		self.initUI()
		
	def initUI(self):
		
		# initilize main window
		self.setWindowTitle(self.title)
		self.setGeometry(self.topL, self.top, self.width, self.height)
		
		#initlize menu and status bars
		menubar = self.menuBar()
		self.statusBar()
				
		#define file menu and and add options to it.
		exitAct = QAction('&Exit', self)
		exitAct.setShortcut('Ctrl-Q')
		exitAct.setStatusTip('Exit Application')
		exitAct.triggered.connect(qApp.quit)
		
		fileMenu = menubar.addMenu('&File')
		fileMenu.addAction(exitAct)		
		
		# Reactor conditions title
		self.reactor_title = QLabel('<b><u>Reactor Conditions</u></b>', self)		
		font = self.reactor_title.font()	
		font.setPointSize(20)
		self.reactor_title.setFont(font)	
		self.reactor_title.setGeometry(QRect(10,20,250,100))
		
		# Temperature title
		self.temperature_label = QLabel('<b>Temperature: </b>', self)
		font = self.temperature_label.font()
		font.setPointSize(18)
		self.temperature_label.setFont(font)
		self.temperature_label.setGeometry(QRect(10,50,200,100))
		
		# pH Title
		self.pH_label = QLabel('<b>pH: </b>', self)
		font = self.pH_label.font()
		font.setPointSize(18)
		self.pH_label.setFont(font)
		self.pH_label.setGeometry(QRect(10,75,200,100))
		
		# Dissolved oxygen title
		self.do_label = QLabel('<b>Dissolved Oxygen: </b>', self)
		font = self.do_label.font()
		font.setPointSize(18)
		self.do_label.setFont(font)
		self.do_label.setGeometry(QRect(10,100,210,100))				
		
		# Temperature label
		displayInTemp = '{0:0.2f}'.format(inTemp)
		self.currentTempIn = QLabel(str(displayInTemp) + '\u2103' , self)
		font = self.currentTempIn.font()
		font.setPointSize(18)
		self.currentTempIn.setFont(font)
		self.currentTempIn.setGeometry(QRect(175,50,200,100))
		
		# current pH value
		pH_display = '{0:0.2f}'.format(float(pH))
		self.pH_current = QLabel(str(pH_display), self)
		font = self.pH_current.font()
		font.setPointSize(18)
		self.pH_current.setFont(font)
		self.pH_current.setGeometry(QRect(175,75,200,100))
		
		# Current dissolved oxygen value
		do_display = '{0:0.2f}'.format(float(do))
		self.do_current = QLabel(str(do_display) + ' mg/L', self)
		font = self.do_current.font()
		font.setPointSize(18)
		self.do_current.setFont(font)
		self.do_current.setGeometry(QRect(225,100,200,100))				
		
	def update_temperature(self):						
		displayInTemp = '{0:0.2f}'.format(float(inTemp))		
		self.currentTempIn.setText(str(displayInTemp) + '\u2103')
		average_temperature()  
		return
		
	def update_pH(self):
		pH_display = '{0:0.2f}'.format(float(pH))
		self.pH_current.setText(str(pH_display))
		average_pH()
		return
		
	def update_do(self):
		do_display = '{0:0.2f}'.format(float(do))
		self.do_current.setText(str(do_display) + ' mg/L')
		average_do()
		return


class temperature_i2c(threading.Thread):
	long_timeout = 1.5
	def __init__(self, address , bus):
		threading.Thread.__init__(self)
		self.file_read = io.open("/dev/i2c-" + str(bus), "rb", buffering=0)
		self.file_write = io.open("/dev/i2c-" + str(bus), "wb", buffering=0)
		self.set_i2c_address(address)

	def set_i2c_address(self, addr):
		I2C_SLAVE = 0x703
		fcntl.ioctl(self.file_read, I2C_SLAVE, addr)
		fcntl.ioctl(self.file_write, I2C_SLAVE, addr)

	def write(self, string):
        # appends the null character and sends the string over I2C
		string += "\00"
		self.file_write.write(bytes(string, 'UTF-8'))

	def read(self, num_of_bytes = 31):
        # reads a specified number of bytes from I2C,
        # then parses and displays the result
		res = self.file_read.read(num_of_bytes)  # read from the board
        # remove the null characters to get the response
		response = [x for x in res if x != '\x00']
		if response[0] == 1:  # if the response isn't an error
            # change MSB to 0 for all received characters except the first
            # and get a list of characters
			char_list = [chr(x & ~0x80) for x in list(response[1:])]
            # NOTE: having to change the MSB to 0 is a glitch in the
            # raspberry pi, and you shouldn't have to do this!
            # convert the char list to a string and returns it
			return ''.join(char_list[0:5])
		else:
			return "Error " + str(response[0])

	def query(self):		
        # write a command to the board, wait the correct timeout,
        # and read the response
		global inTemp
		global temp_counter
		string = "R"
		self.write(string)
        # the read and calibration commands require a longer timeout
		if((string.upper().startswith("R")) or
           (string.upper().startswith("CAL"))):
			time.sleep(self.long_timeout)
		elif((string.upper().startswith("SLEEP"))):
			return "sleep mode"
		else:
			time.sleep(self.short_timeout)        
		inTemp = self.read()
		print ('Temperature reading: ' + str(inTemp))
		temp_counter = temp_counter + 1                 
		return        

	def close(self):
		self.file_read.close()
		self.file_write.close()
        
def average_temperature():
	global temp_average
	temp_average = (temp_average + float(inTemp))
	current_average = temp_average / temp_counter
	print ('Average temperature: ' + str(current_average))
	return


def main():		
	app = QApplication(sys.argv)
	temp_probe = temperature_i2c(102,1)
	temp_probe.start()	
	temp_probe.join()
	ex = mainMenu()		
	timer = QTimer()
	timer.start(5000)	
	timer.timeout.connect(temp_probe.query)	
	timer.timeout.connect(ex.update_temperature)
	ex.show()	
	sys.exit(app.exec_())
			
if __name__ == '__main__':
	main()
Any tips?

User avatar
paddyg
Posts: 2243
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: Multithreading guidiance

Mon Jan 22, 2018 12:30 am

It looks quite complicated code so I might have missed this in your code but I would expect to see a thread function that has an infinite loop which keeps reading from your sensor and writing the result into a global variable (or, better an object that you passed to the function when you started it, or class property if you do it with classes). This then read asynchronously and you can 'just' read the value at your leisure. Kind of like

Code: Select all

def my_func():
  global sens_val
  while True:
     sens_val = ...code to read sensor
     time.sleep(0.5)

t = threading.Thread(target=my_func)
t.start()
t.daemon = True # as I haven't added a mechanism to kill off the thread

# main loop
while True:
  # do gui stuff
  # etc
  output_sens_val(sens_val)
PS in your code, now I look at it a bit more, you should probably have a method that overrides the temperature_i2c.run() inherited method and which contains the body of your query() inside a while True: loop (as above) but writes the result into self.inTemp and self.temp_counter. temperature_i2c.query() would then simply return (self.inTemp, self.temp_counter) and you would call that from your main loop and from average_temperature()
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

Adaulith
Posts: 17
Joined: Thu Oct 19, 2017 8:47 pm

Re: Multithreading guidiance

Mon Jan 22, 2018 4:36 pm

Thanks for the response.
I have added what you have suggested, and am still getting some errors.
Sorry for the previous code. I cleaned it up a bit this time. Hopefully it's a bit better.

Code: Select all

import sys, fcntl, io, time, threading
from time import sleep
from PyQt5.QtWidgets import QMainWindow, QAction, qApp, QApplication, QLineEdit, QPushButton, QWidget, QDialog, QTextEdit, QLabel, QMenu, QVBoxLayout, QSizePolicy
from PyQt5.QtGui import *
from PyQt5.QtCore import Qt, QTime, QTimer, QRect
from PyQt5 import QtCore

versionNumber = 'V0.1'

inTemp = 0.00
temp_average = 0.00
temp_counter = 0


class mainMenu(QMainWindow):
	def __init__(self):
		super().__init__()
		self.topL = 20
		self.top = 40
		self.width = 800
		self.height = 400
		self.title = ('WIST Batch Reactor ' + versionNumber)
		self.initUI()
		
	def initUI(self):
		
		# initilize main window
		self.setWindowTitle(self.title)
		self.setGeometry(self.topL, self.top, self.width, self.height)
		
		#initlize menu and status bars
		menubar = self.menuBar()
		self.statusBar()
				
		#define file menu and and add options to it.
		exitAct = QAction('&Exit', self)
		exitAct.setShortcut('Ctrl-Q')
		exitAct.setStatusTip('Exit Application')
		exitAct.triggered.connect(qApp.quit)
		
		fileMenu = menubar.addMenu('&File')
		fileMenu.addAction(exitAct)		
		
		# Reactor conditions title
		self.reactor_title = QLabel('<b><u>Reactor Conditions</u></b>', self)		
		font = self.reactor_title.font()	
		font.setPointSize(20)
		self.reactor_title.setFont(font)	
		self.reactor_title.setGeometry(QRect(10,20,250,100))
		
		# Temperature title
		self.temperature_label = QLabel('<b>Temperature: </b>', self)
		font = self.temperature_label.font()
		font.setPointSize(18)
		self.temperature_label.setFont(font)
		self.temperature_label.setGeometry(QRect(10,50,200,100))					
		
		# Temperature label
		displayInTemp = '{0:0.2f}'.format(inTemp)
		self.currentTempIn = QLabel(str(displayInTemp) + '\u2103' , self)
		font = self.currentTempIn.font()
		font.setPointSize(18)
		self.currentTempIn.setFont(font)
		self.currentTempIn.setGeometry(QRect(175,50,200,100))					
		
	def update_temperature(self):
		global update						
		displayInTemp = '{0:0.2f}'.format(float(inTemp))		
		self.currentTempIn.setText(str(displayInTemp) + '\u2103')						 
		return

class temperature_i2c(threading.Thread):
	long_timeout = 3.0	
	def __init__(self, address = 102 , bus = 1):
		threading.Thread.__init__(self)
		self.file_read = io.open("/dev/i2c-" + str(bus), "rb", buffering=0)
		self.file_write = io.open("/dev/i2c-" + str(bus), "wb", buffering=0)
		self.set_i2c_address(address)
		
	def set_i2c_address(self, addr):
		I2C_SLAVE = 0x703
		fcntl.ioctl(self.file_read, I2C_SLAVE, addr)
		fcntl.ioctl(self.file_write, I2C_SLAVE, addr)

	def write(self, string):
        # appends the null character and sends the string over I2C
		string += "\00"
		self.file_write.write(bytes(string, 'UTF-8'))

	def read(self, num_of_bytes = 31):
        # reads a specified number of bytes from I2C,
        # then parses and displays the result
		res = self.file_read.read(num_of_bytes)  # read from the board
        # remove the null characters to get the response
		response = [x for x in res if x != '\x00']
		if response[0] == 1:  # if the response isn't an error
            # change MSB to 0 for all received characters except the first
            # and get a list of characters
			char_list = [chr(x & ~0x80) for x in list(response[1:])]
            # NOTE: having to change the MSB to 0 is a glitch in the
            # raspberry pi, and you shouldn't have to do this!
            # convert the char list to a string and returns it
			return ''.join(char_list[0:5])
		else:
			return "Error " + str(response[0])

	def run(self):		
        # write a command to the board, wait the correct timeout,
        # and read the response
		global inTemp	
		global update	
		string = "R"
		self.write(string)
        # the read and calibration commands require a longer timeout
		if((string.upper().startswith("R")) or
           (string.upper().startswith("CAL"))):
			time.sleep(self.long_timeout)
		elif((string.upper().startswith("SLEEP"))):
			return "sleep mode"
		else:
			time.sleep(self.short_timeout)        
		inTemp = self.read()
		print ('\n**********Temperature**********')
		print ('Temperature reading: ' + str(inTemp))			              
		return        

	def close(self):
		self.file_read.close()
		self.file_write.close()
		
def main():		
	app = QApplication(sys.argv)
	ex = mainMenu()
	ex.show()
	t = threading.Thread(target=temperature_i2c)		
	t.start()
	t.daemon = True
	
	while True:
		t.run		
		#ex.update_temperature		
	sys.exit(app.exec_())
			
if __name__ == '__main__':
	main()

Here is the error that I get:
Traceback (most recent call last):
File "mt.py", line 149, in <module>
main()
File "mt.py", line 140, in main
t.daemon = True
File "/usr/lib/python3.5/threading.py", line 1139, in daemon
raise RuntimeError("cannot set daemon status of active thread")
RuntimeError: cannot set daemon status of active thread

I have tried running it like this with out the daemon just to see what happens, nothing happens. the program executes but I have to ctrl+c to stop it.
I changed the main loop to the following

Code: Select all

def main():		
	app = QApplication(sys.argv)
	ex = mainMenu()
	ex.show()
	t = threading.Thread(target=temperature_i2c)		
	t.start()
	#t.daemon = True	
	
	while True:
		t.run		
		#ex.update_temperature		
	sys.exit(app.exec_())
			
if __name__ == '__main__':
	main()
^CTraceback (most recent call last):
File "mt.py", line 147, in <module>
main()
File "mt.py", line 141, in main
t.run
KeyboardInterrupt

User avatar
paddyg
Posts: 2243
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: Multithreading guidiance

Mon Jan 22, 2018 5:20 pm

Ah yes there are a couple of things. It's a bit trickier because Qt has a 'hidden' loop system designed to make it hard to figure out what's going on.

You can do threading two ways. First as you did originally by inheriting Thread and overriding start() and or run(). That's quite a tidy way that avoids globals. Second you can do the way I suggested where you pass a function as an argument to threading.Thread(). You can't really mix them (or, you probably can but not a good idea).

The hidden loop system of Qt means that calling UI update functions is done the way you tried with the QTimer.timeout.connect() function but to use this 'properly' without gobals you would have to pass a reference to your Thread instance to your UI instance. Which is starting to feel a bit messy. So I would stick with globals to get it working and improve it later. My idea would be something like this:

Code: Select all

class temperature_i2c(threading.Thread):
  long_timeout = 3.0  
  def __init__(self, address = 102 , bus = 1):
    threading.Thread.__init__(self)
    self.file_read = io.open("/dev/i2c-" + str(bus), "rb", buffering=0)
    self.file_write = io.open("/dev/i2c-" + str(bus), "wb", buffering=0)
    self.set_i2c_address(address)
    self.keep_running = True # avoids messing around with setting daemon to True
...
  def run(self):    
    global inTemp, temp_counter
    string = "R"
    while self.keep_running:
      self.write(string)
      if((string.upper().startswith("R")) or
             (string.upper().startswith("CAL"))):
        time.sleep(self.long_timeout)
      elif((string.upper().startswith("SLEEP"))):
        return "sleep mode"
      else:
        time.sleep(self.short_timeout)        
      inTemp = self.read()
      temp_counter += 1
      print ('\n**********Temperature**********')
      print ('Temperature reading: ' + str(inTemp))
      time.sleep(2.0) # or whatever frequeny you need

  def close(self):
    self.keep_running = False # to stop the run loop
    self.file_read.close()
    self.file_write.close()
    
def main():
  app = QApplication(sys.argv)
  temp_probe = temperature_i2c(102,1)
  temp_probe.start()
  temp_probe.join()
  ex = mainMenu()
  timer = QTimer()
  timer.start(5000)
  timer.timeout.connect(ex.update_temperature)
  ex.show()
  sys.exit(app.exec_())
      
if __name__ == '__main__':
  main()
PS in python t.run is an actual thing (a function) that can get passed around and assigned to variable but to run it you need to put () after it.
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

Adaulith
Posts: 17
Joined: Thu Oct 19, 2017 8:47 pm

Re: Multithreading guidiance

Mon Jan 22, 2018 5:43 pm

This works!
Thank you very much for your help!

It has also given me a better understanding of what is going on. You've helped me solve a major problem with this project that I am designing :D

Adaulith
Posts: 17
Joined: Thu Oct 19, 2017 8:47 pm

Re: Multithreading guidiance

Tue Jan 23, 2018 4:18 pm

So, I was able to implement it into my actual program and got it working. I did have to modify some of the code however and have a question as to as why. Trying to get a grasp as to what is going on.

Code: Select all

def main():
  app = QApplication(sys.argv)
  temp_probe = temperature_i2c(102,1)
  temp_probe.start()
  temp_probe.join()
  ex = mainMenu()
  timer = QTimer()
  timer.start(5000)
  timer.timeout.connect(ex.update_temperature)
  ex.show()
  sys.exit(app.exec_())   
     
if __name__ == '__main__':
  main()

I added two additional sensors. I had to remove the xxx_probe.join() for the program to work with multiple sensors

Code: Select all

def main():
  app = QApplication(sys.argv)
  
  temp_probe = temperature_i2c
  pH_probe = pH_i2c
  do_probe = do_i2c
  
  temp_probe.start()
  pH_probe.start()
  do_probe.start()
    
  ex = mainMenu()
  ex.show()
  
  timer = QTimer()
  timer.start(5000)
  timer.timeout.connect(ex.update_temperature)
  timer.timeout.connect(ex.update_pH)
  timer.timeout.connect(ex.update_do)  
  sys.exit(app.exec_())     
     
if __name__ == '__main__':
  main()

Basically I just had to remove the .join() command. The program did run with the .join(), but the GUI portion never executed. It just kept taking three readings from the sensors, and outputting it to the terminal. I thought I could have had an error in one of my sensor classes, but it didn't matter if changed the order or removed one of them, the same thing happened.

FWIW I will be switching to some nestled while loops in my main function, because it will be more suitable for what I am trying to achieve. But for now - this works just fine.

Return to “Python”