Home > Back-end >  How to run thermal camera mlx90640 concurrently with countdown timers
How to run thermal camera mlx90640 concurrently with countdown timers

Time:01-23

The enclosed Python code for Raspberry Pi 4 runs separately each function without any problem countdown() and RunThermalCam(), but when running both functions concurrently the timers stop and camera image freeze. My understanding is camera is heavily processor usage so I used multiprocessing, but it gives the following error which I couldn't figure out. The code should first runs GUI then once "Start" button is hit, modules (PWM) runs with countdown timers for each module along with thermal camera.

XIO:  fatal IO error 25 (Inappropriate ioctl for device) on X server ":0"
      after 1754 requests (1754 known processed) with 38 events remaining.
[xcb] Unknown sequence number while processing queue
[xcb] Most likely this is a multi-threaded client and XInitThreads has not been called
[xcb] Aborting, sorry about that.
python3: ../../src/xcb_io.c:269: poll_for_event: Assertion `!xcb_xlib_threads_sequence_lost' failed.

Process ended with exit code -6.

import RPi.GPIO as GPIO
from adafruit_blinka import Enum, Lockable, agnostic
import csv, datetime
from tkinter import * 
from tkinter.filedialog import asksaveasfile
import time,board,busio
import numpy as np
import adafruit_mlx90640
import matplotlib.pyplot as plt
import multiprocessing 

def first():  
   print("first new time is up \n") 
   #stop module (1)
def second():  
   print("second new time is up \n")  
   #stop module (2)
def third():  
   print("third new time is up \n")  
   #stop module (3)
   
#Create interface#
root = Tk()
root.geometry("1024x600")
root.title("Countdown Timer")

def modules():
    if (clockTime[0] == 0 or clockTime[0] == -1):
        first()
    if(clockTime[1] == 0 or clockTime[1] == -1):
        second()
    if(clockTime[2] == 0 or clockTime[2] == -1):
        third()
        
#initialize timers lists
timers_number = 3
hrString=[0]*timers_number
minString=[0]*timers_number
secString=[0]*timers_number
totalSeconds  = [0]*timers_number
totalMinutes = [0]*timers_number
totalHours = [0]*timers_number

for i in range(timers_number):
    hrString[i] = StringVar()
    hrString[i].set("00")

for i in range(timers_number):
    minString[i] = StringVar()
    minString[i].set("00")
    
for i in range(timers_number):
    secString[i] = StringVar()
    secString[i].set("00")
 
#Get User Input
hourTextBox1 = Entry(root, width=3, font=("Calibri", 20, ""),textvariable=hrString[0]).place(x=170, y=100) 
minuteTextBox1 = Entry(root, width=3, font=("Calibri", 20, ""),textvariable=minString[0]).place(x=220, y=100)  
secondTextBox1 = Entry(root, width=3, font=("Calibri", 20, ""),textvariable=secString[0]).place(x=270, y=100) 

hourTextBox2 = Entry(root, width=3, font=("Calibri", 20, ""), textvariable=hrString[1]).place(x=170, y=180) 
minuteTextBox2 = Entry(root, width=3, font=("Calibri", 20, ""), textvariable=minString[1]).place(x=220, y=180)  
secondTextBox2 = Entry(root, width=3, font=("Calibri", 20, ""), textvariable=secString[1]).place(x=270, y=180) 

hourTextBox3 = Entry(root, width=3, font=("Calibri", 20, ""), textvariable=hrString[2]).place(x=170, y=260) 
minuteTextBox3 = Entry(root, width=3, font=("Calibri", 20, ""), textvariable=minString[2]).place(x=220, y=260)  
secondTextBox3 = Entry(root, width=3, font=("Calibri", 20, ""), textvariable=secString[2]).place(x=270, y=260) 

def RunThermalCam():
    thermal_mapfile = str(datetime.datetime.now().date())   '_'   str(datetime.datetime.now().time()).replace(':', '.')
    thermal_mapfile = thermal_mapfile[:16]  #limit thermal file name to 16 characters
    i2c = busio.I2C(board.SCL, board.SDA, frequency=800000) # setup I2C
    mlx = adafruit_mlx90640.MLX90640(i2c) # begin MLX90640 with I2C comm
    mlx.refresh_rate = adafruit_mlx90640.RefreshRate.REFRESH_2_HZ # set refresh rate 2Hz
    mlx_shape = (24,32)
    print("Initialized")
    # setup the figure for plotting
    plt.ion() # enables interactive plotting
    fig,ax = plt.subplots(figsize=(12,7))
    therm1 = ax.imshow(np.zeros(mlx_shape),vmin=0,vmax=60) #start plot with zeros
    cbar = fig.colorbar(therm1) # setup colorbar for temps
    cbar.set_label('Temperature [$^{\circ}$C]',fontsize=14) # colorbar label
    
    #frame = np.zeros((24*32,)) # setup array for storing all 768 temperatures
    
    t_array = []
    frame = [0] * 768
    while True:
        t1 = time.monotonic()
        try:
            mlx.getFrame(frame) # read MLX temperatures into frame var
            data_array = (np.reshape(frame,mlx_shape)) # reshape to 24x32
            therm1.set_data(np.fliplr(data_array)) # flip left to right
            therm1.set_clim(vmin=np.min(data_array),vmax=np.max(data_array)) # set bounds
            cbar.update_normal(therm1) # update colorbar range
            plt.title(f"Max Temp: {np.max(data_array):.1f}C")
            plt.pause(0.001) # required
            t_array.append(time.monotonic()-t1)
            print('Sample Rate: {0:2.1f}fps'.format(len(t_array)/np.sum(t_array)))
                #except AttributeError:
                #    continue 
        except ValueError:
            continue # if error, just read again                   
        for h in range(24):
            for w in range(32):
                t = frame[h*32   w]
        frame = list(np.around(np.array(frame),1))  #round array elements to one decimal point     
        with open("/home/pi/MOC/Thermal_Camera/" thermal_mapfile ".csv","a") as thermalfile:
            writer = csv.writer(thermalfile,delimiter=" ")
            writer.writerow([time.time(),frame])    
            
def countdown():
    for i in range (timers_number):
  
        if(clockTime[i] > -1):
            totalMinutes[i], totalSeconds[i] = divmod(clockTime[i], 60)
            if(totalMinutes[i]>60):
                totalHours[i], totalMinutes[i] = divmod(totalMinutes[i], 60)
            
            hrString[i].set("{0:2d}".format(totalHours[i]))
            minString[i].set("{0:2d}".format(totalMinutes[i]))
            secString[i].set("{0:2d}".format(totalSeconds[i]))
           
        if(clockTime[i] == 0):       #time is up
            hrString[i].set("00")
            minString[i].set("00")
            secString[i].set("00")
            modules()            
        
        if(clockTime[i] != -1):    #timer is paused
            clockTime[i] -= 1     
    
    if(clockTime[i] != -1):
        root.after(1000, countdown)   

p1 = multiprocessing.Process(target = RunThermalCam)
p2 = multiprocessing.Process(target = countdown)
                
def starttimer():
    #Start_modules()
    global clockTime
    clockTime = [0]*timers_number
    try:
        #global clockTime
        for i in range (timers_number):
            clockTime[i] = int(hrString[i].get())*3600   int(minString[i].get())*60   int(secString[i].get()) 
    except:
        print("Incorrect values")
    countdown()
    RunThermalCam()
    #p1.start()
    #p2.start()
    #p1.join()
    #p2.join() 
    
def stop():
    for i in range (timers_number):
        clockTime[i] = 0        
def pause():
    for i in range (timers_number):
        clockTime[i] = -1
    modules()
def GUI():       
    setTimeButton = Button(root, text='START', bd='5', command=starttimer).place(x=200, y=500)       
    setTimeButton = Button(root, text='STOP', bd='5', command=stop).place(x=350, y=500)   
    setTimeButton = Button(root, text='PAUSE', bd='5', command=pause).place(x=500, y=500)   
    root.mainloop() 

if __name__ == '__main__':
    GUI()
    
    

CodePudding user response:

Your code isn't really using multiprocessing.

When you click the start button, tkinter processes the event, and calls the starttimer callback. This in turn calls RunThermalCam in the current process. This is a problem, because RunThermalCam has an infinite loop inside it.

So basically, since RunThermalCam runs forever, starttimer will never return. That means that tkinter's event processing grinds to a halt.

CodePudding user response:

I found it is more efficient to use PyQt library instead of tkinter. Timers and other peripherals are running smoothly in parallel with the Thermal Camera Moreover, no need to multiprocessing or multithreading.

import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QApplication
import RPi.GPIO as GPIO
import time,board,busio
import numpy as np
import adafruit_mlx90640
import matplotlib.pyplot as plt
from adafruit_blinka import Enum, Lockable, agnostic
import csv 
import datetime
font_1 = "color: blue; font: bold 12px;"
font_2 = "color: blue; font:14px;"
line0 = 0
line1 = 20
line2 = 70
line3 = 120
line4 = 170
line12 = 500

def run_thermal_cam():
    i2c = busio.I2C(board.SCL, board.SDA, frequency=800000) # setup I2C for thermal camera 
    thermal_mapfile = str(datetime.datetime.now().date())   '_'   str(datetime.datetime.now().time()).replace(':', '.')
    thermal_mapfile = thermal_mapfile[:16]  #limit thermal file name to 16 characters
    print("Thermal cam is ON")
    mlx = adafruit_mlx90640.MLX90640(i2c) # begin MLX90640 with I2C comm
    mlx.refresh_rate = adafruit_mlx90640.RefreshRate.REFRESH_2_HZ # set refresh rate 2Hz
    mlx_shape = (24,32)
    print("Initialized")
    # setup the figure for plotting
    plt.ion() # enables interactive plotting
    fig,ax = plt.subplots(figsize=(12,7))
    therm1 = ax.imshow(np.zeros(mlx_shape),vmin=0,vmax=60) #start plot with zeros
    cbar = fig.colorbar(therm1) # setup colorbar for temps
    cbar.set_label('Temperature [$^{\circ}$C]',fontsize=14) # colorbar label
    t_array = []
    frame = [0] * 768
    t1 = time.monotonic()       
    while True:
        try:
            mlx.getFrame(frame) # read MLX temperatures into frame var
            data_array = (np.reshape(frame,mlx_shape)) # reshape to 24x32
            therm1.set_data(np.fliplr(data_array)) # flip left to right
            therm1.set_clim(vmin=np.min(data_array),vmax=np.max(data_array)) # set bounds
            cbar.update_normal(therm1) # update colorbar range
            plt.title(f"Max Temp: {np.max(data_array):.1f}C")
            plt.pause(0.001) # required
            t_array.append(time.monotonic()-t1)
            
        except ValueError:
            continue # if error, just read again
                        
        for h in range(24):
            for w in range(32):
                t = frame[h*32   w]
                
        frame = list(np.around(np.array(frame),1))  #round array elements to one decimal point 
        with open("/home/pi/Thermal_Camera/" thermal_mapfile ".csv","a") as thermalfile:
            writer = csv.writer(thermalfile,delimiter=" ")
            unix_time = time.time()
            formatted_time = datetime.datetime.fromtimestamp(unix_time).strftime('%H:%M:%S')
            writer.writerow([formatted_time,frame]) 


class TimerApp(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()

        # Create layout
        self.setGeometry(0, 0, 1024, 600)
        self.setWindowTitle("Thermal Camera")
        self.setStyleSheet(font_2)  #control timers color & font
     
        # Create countdown timers
        self.timer1 = QtWidgets.QTimeEdit(self)
        self.timer1.setDisplayFormat("HH:mm:ss")
        self.timer1.setGeometry(QtCore.QRect(920, line2-10, 85, 25))
        self.timer1.setTime(QtCore.QTime(0, 0, 0))

        self.timer2 = QtWidgets.QTimeEdit(self)
        self.timer2.setDisplayFormat("HH:mm:ss")
        self.timer2.setGeometry(QtCore.QRect(920, line3-10, 85, 25))
        self.timer2.setTime(QtCore.QTime(0, 0, 0))

        self.timer3 = QtWidgets.QTimeEdit(self)
        self.timer3.setDisplayFormat("HH:mm:ss")
        self.timer3.setGeometry(QtCore.QRect(920, line4-10, 85, 25))
        self.timer3.setTime(QtCore.QTime(0, 0, 0))
        
        # Create start button
        self.start_button = QtWidgets.QPushButton("Start", self)
        self.start_button.setGeometry(250, line12, 55, 55)
        self.start_button.clicked.connect(self.start)
        # Connect start button to start function
        self.start_button.clicked.connect(self.start)
        
        # Create timer
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.countdown)
        
    def start(self):
        self.timer.start(1000)   # start all timers #
        run_thermal_cam()
    
    def countdown(self):
        if self.timer1.time() > QtCore.QTime(0, 0, 0):
            new_time = self.timer1.time().addSecs(-1)
            self.timer1.setTime(new_time)
        if self.timer2.time() > QtCore.QTime(0, 0, 0):
            new_time = self.timer2.time().addSecs(-1)
            self.timer2.setTime(new_time)
        if self.timer3.time() > QtCore.QTime(0, 0, 0):
            new_time = self.timer3.time().addSecs(-1)
            self.timer3.setTime(new_time)
            
if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    timer_app = TimerApp()
    timer_app.show()
    sys.exit(app.exec_())
  • Related