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_())