SimDeveloper
SimDeveloper

Reputation: 1

Problem with the continuous call of the "read_register" function of the minimalmodbus library in a thread loop

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

Answers (0)

Related Questions