Reputation: 1
I'm developing a Windows interface through QT creator that interacts with a Modbus device. With several buttons, I'm able to communicate with the device without any problems (i.e. read and write registers). I added what I called "live log" functionality: if a button is clicked, a thread is started aiming to read a specific register every X ms (both register number and time interval are passed through two text boxes, default value register 0x2000
, time interval 0.5 s) and show the result in another text box.
The problem is the following: seemingly randomly, after several loops iterations, the loop is blocked when try to newly read the register and get its value. I don't understand why it doesn't go in the "expect" code block if an error in the reading operation occurs.
I show the main parts of my code here:
import serial
import threading
from datetime import datetime
import serial.tools.list_ports
import minimalmodbus as modbus
from PyQt5 import QtCore, QtGui, QtWidgets, uic
from PyQt5.QtCore import QMutex, QObject, QThread, pyqtSignal, QCoreApplication
from PyQt5.QtWidgets import QMessageBox, QApplication
import queue
class MyClass:
def __init__(self):
self.stop_event = threading.Event()
self.thread = None
def live_log(self):
while not self.stop_event.is_set():
lucchetto = threading.Lock()
# Live log
lucchetto.acquire()
window.process_queue()
if not value_queue.empty():
values = value_queue.get()
timeInterval, regAddr = values
regAddr = '0x' + regAddr
try:
timestamp = datetime.now().now()
timestamp = timestamp.strftime("%H:%M:%S")
print("Reading register (hex): " + regAddr)
regAddr_int = int(regAddr, 16)
registerValue = None
registerValue = window.device.read_register(regAddr_int, 0, 3, False)
if registerValue != None:
print("Value read (int): " + str(registerValue))
message_queue.put((timestamp, registerValue))
lucchetto.release()
except Exception as e: # Specific handling of exceptions
time.sleep(0.1)
print("Exception generated in the reading of the parameter/illustration to video: ", str(e))
time.sleep(timeInterval)
if self.stop_event.is_set():
break
def start_log_thread(self):
self.thread = threading.Thread(target=self.live_log)
self.thread.start()
return self.thread
def stop_log_thread(self):
if self.thread is not None:
self.stop_event.set()
self.thread.join()
def restart_log_thread(self):
self.stop_log_thread()
self.stop_event.clear()
self.thread = self.start_log_thread()
return self.thread
class Ui(QtWidgets.QMainWindow):
# Thread creation for live log management
my_object = MyClass()
# ........
# ........
# ........
def process_queue(self):
self.lucchetto.acquire()
if not message_queue.empty():
timestamp, value = message_queue.get()
self.tb_LiveLog.append(timestamp + " -- " + str(value) + " (int)")
self.tb_LiveLog.ensureCursorVisible()
timeInterval = float(self.tb_LiveLogTime_s.toPlainText()) # [s]
regAddr = self.tb_LiveLogReg_hex.toPlainText()
self.lucchetto.release()
value_queue.put((timeInterval, regAddr))
def btn_Leggi_Callback(self):
print("Single parameter reading button pressed")
if self.serialConnection:
retry = 0
send = 0
while retry < MAX_RETRY and send == 0:
try:
if self.rbtn_ExpertMode.isChecked():
regAddr = '0x' + self.tb_RegAddr_hex.toPlainText()
else:
regAddr = parametersList[self.cb_Param.currentIndex()+1][1]
self.tb_RegAddr_hex.setText(regAddr)
print("Reading from the register (hex): " + regAddr)
regAddr_int = int(regAddr, 16)
print("Reading from the register (int): " + str(regAddr))
registerValue = self.device.read_register(regAddr_int, 0, 3, False) # The register must always be passed as int!
print("value read (int): "+str(registerValue))
print("Success [", retry + 1, "/", MAX_RETRY, "] ", sys.exc_info()[0], " : ", sys.exc_info()[1])
send = 1
except: # except IOError:
print("Fail [", retry + 1, "/", MAX_RETRY, "]: ", sys.exc_info()[0], " : ", sys.exc_info()[1])
retry += 1
time.sleep(0.1)
if send == 1:
print(" ** Read from device OK **")
self.tb_Log.append("READ OK --> Parameter reading: "+regAddr+", "+str(regAddr_int)+" (int), valore: "+str(registerValue)+" (int)")
self.tb_Valore_dec.setText(str(registerValue))
else:
print(" ** Read from device FAIL **")
self.tb_Log.append("READ FAIL")
else:
self.showPopup("Attention", "Please open serial port.")
def btn_Start_Stop_Callback(self):
if self.btn_Start_Stop.isChecked():
print("Button for start live log pressed")
if (len(self.tb_LiveLogReg_hex.toPlainText()) != 0 and len(self.tb_LiveLogTime_s.toPlainText()) != 0):
if self.firstCapture:
# I start the thread for the live log
thread = self.my_object.start_log_thread()
self.firstCapture = False
else:
# To restart the thread, call the restart_log_thread() method
thread = self.my_object.restart_log_thread()
self.btn_Start_Stop.setText('STOP')
else:
self.showPopup("attention", "Check the log to be monitored and/or the time interval.")
else:
print("Button for live log stop pressed")
self.btn_Start_Stop.setText("START")
# To stop the thread, call the stop_log_thread() method
self.my_object.stop_log_thread()
I used queue to pass the values from thread to interface and vice versa. I used lock method to synchronize the accesses to the controls.
When I read or write some register once (i.e only one time outside the thread, using for example btn_Leggi_Callback
function) I don't have problems. It seems to be clear that is related at the read_register
function in the thread loop (only) since the latest string showed in my console is Reading register (hex): 0x2000
. The interface simply freezes after several seconds and readings (i.e. several thread iterations) and I'm no more able to click buttons and then the process finishes with exit code -1073741819 (0xC0000005)
. If I go in debug I'm not able to encounter the error.
Upvotes: 0
Views: 142