pythonasynchronousopc-uamodbus-tcppymodbus

Python OPCUA, modbus communication code gets a RuntimeError after 3 hours of running


I've got this python program that handles communication between a PLC using OPCUA, some mass flow controllers using Modbus TCP and an Arduino using Serial. The program is supposed to run continuously and it does so for 3 hours with no trouble as my PLC is controlling another system but after this I get this error:

  File "C:\Users\labuser\Documents\Automation\Lib\site-packages\asyncua\client\ua_client.py", line 230, in open_secure_channel
    raise RuntimeError('Two Open Secure Channel requests can not happen too close to each other. ' 'The response must be processed and returned before the next request can be sent.')
RuntimeError: Two Open Secure Channel requests can not happen too close to each other. The response must be processed and returned before the next request can be sent.
ERROR:asyncua.client.client:Error in watchdog loop
Traceback (most recent call last):
  File "C:\Users\labuser\Documents\Automation\Lib\site-packages\asyncua\client\client.py", line 551, in _monitor_server_loop
    _ = await self.nodes.server_state.read_value()
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\labuser\Documents\Automation\Lib\site-packages\asyncua\common\node.py", line 207, in read_value   
    result = await self.read_data_value()
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\labuser\Documents\Automation\Lib\site-packages\asyncua\common\node.py", line 220, in read_data_value
    return await self.read_attribute(ua.AttributeIds.Value, None, raise_on_bad_status)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\labuser\Documents\Automation\Lib\site-packages\asyncua\common\node.py", line 342, in read_attribute
    result = await self.session.read(params)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\labuser\Documents\Automation\Lib\site-packages\asyncua\client\ua_client.py", line 404, in read    
    data = await self.protocol.send_request(request)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\labuser\Documents\Automation\Lib\site-packages\asyncua\client\ua_client.py", line 165, in send_request
    await self.pre_request_hook()
  File "C:\Users\labuser\Documents\Automation\Lib\site-packages\asyncua\client\client.py", line 534, in check_connection
    await self._renew_channel_task
RuntimeError: Two Open Secure Channel requests can not happen too close to each other. The response must be processed and returned before the next request can be sent.
ERROR:asyncua.client.client:Error in watchdog loop
Traceback (most recent call last):
  File "C:\Users\labuser\Documents\Automation\Lib\site-packages\asyncua\client\client.py", line 551, in _monitor_server_loop
    _ = await self.nodes.server_state.read_value()
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\labuser\Documents\Automation\Lib\site-packages\asyncua\common\node.py", line 207, in read_value   
    result = await self.read_data_value()
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\labuser\Documents\Automation\Lib\site-packages\asyncua\common\node.py", line 220, in read_data_value
    return await self.read_attribute(ua.AttributeIds.Value, None, raise_on_bad_status)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\labuser\Documents\Automation\Lib\site-packages\asyncua\common\node.py", line 342, in read_attribute
    result = await self.session.read(params)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\labuser\Documents\Automation\Lib\site-packages\asyncua\client\ua_client.py", line 404, in read    
    data = await self.protocol.send_request(request)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\labuser\Documents\Automation\Lib\site-packages\asyncua\client\ua_client.py", line 165, in send_request
    await self.pre_request_hook()
  File "C:\Users\labuser\Documents\Automation\Lib\site-packages\asyncua\client\client.py", line 534, in check_connection
    await self._renew_channel_task
RuntimeError: Two Open Secure Channel requests can not happen too close to each other. The response must be processed and returned before the next request can be sent.
ERROR:asyncio:Task exception was never retrieved
future: <Task finished name='Task-3' coro=<Client._monitor_server_loop() done, defined at C:\Users\labuser\Documents\Automation\Lib\site-packages\asyncua\client\client.py:542> exception=RuntimeError('Two Open Secure Channel requests can not happen too close to each other. The response must be processed and returned before the next request can be sent.')>
Traceback (most recent call last):
  File "C:\Users\labuser\Documents\Automation\Lib\site-packages\asyncua\client\client.py", line 551, in _monitor_server_loop
    _ = await self.nodes.server_state.read_value()
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\labuser\Documents\Automation\Lib\site-packages\asyncua\common\node.py", line 207, in read_value   
    result = await self.read_data_value()
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\labuser\Documents\Automation\Lib\site-packages\asyncua\common\node.py", line 220, in read_data_value
    return await self.read_attribute(ua.AttributeIds.Value, None, raise_on_bad_status)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\labuser\Documents\Automation\Lib\site-packages\asyncua\common\node.py", line 342, in read_attribute
    result = await self.session.read(params)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\labuser\Documents\Automation\Lib\site-packages\asyncua\client\ua_client.py", line 404, in read    
    data = await self.protocol.send_request(request)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\labuser\Documents\Automation\Lib\site-packages\asyncua\client\ua_client.py", line 165, in send_request
    await self.pre_request_hook()
  File "C:\Users\labuser\Documents\Automation\Lib\site-packages\asyncua\client\client.py", line 534, in check_connection
    await self._renew_channel_task
RuntimeError: Two Open Secure Channel requests can not happen too close to each other. The response must be processed and returned before the next request can be sent.
INFO:asyncua.client.client:disconnect
INFO:asyncua.client.ua_client.UaClient:close_session
INFO:asyncua.client.ua_client.UASocketProtocol:close_secure_channel
INFO:asyncua.client.ua_client.UASocketProtocol:Request to close socket received
INFO:asyncua.client.ua_client.UASocketProtocol:Socket has closed connection
Traceback (most recent call last):
  File "c:\Users\labuser\Documents\Automation\Automation_test.py", line 339, in <module>
    asyncio.run(main())
  File "C:\Users\labuser\AppData\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 194, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "C:\Users\labuser\AppData\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\labuser\AppData\Local\Programs\Python\Python312\Lib\asyncio\base_events.py", line 685, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "C:\Users\labuser\Documents\Automation\Lib\site-packages\asyncua\client\client.py", line 700, in close_session
    await self._renew_channel_task
RuntimeError: Two Open Secure Channel requests can not happen too close to each other. The response must be processed and returned before the next request can be sent.

Here is the code:

import asyncio
import logging
import serial
from asyncua import Client, ua
from pymodbus.client import AsyncModbusTcpClient
from struct import pack, unpack
import pandas as pd
# Initialize DataFrame to store recorded variables
df = pd.DataFrame(columns=['Timestamp', 'Variable', 'Value'])
# Configure logging
logging.basicConfig(level=logging.INFO)

# Define the serial port and baud rate
arduino = serial.Serial('COM8', 9600) 
asyncio.sleep(2)
 
async def record_variables(variable, value):
    dosing_time = pd.Timestamp.now()
    global df
    new_row = {'Timestamp': dosing_time, 'Variable': variable, 'Value': value}
    print("New Row:", new_row)  # Check the new row before appending
    df = pd.concat([df, pd.DataFrame([new_row])], ignore_index=True)
    df.to_csv(r"C:/Users/labuser/df.csv", index=False) 
def set_valve_position(channel, position):
    if position == 0:
        #arduino.write(bytes(str(channel) + '0', 'utf-8'))  # Send 'channel1' to Arduino to activate solenoid valve
        #print(f"Solenoid valve on channel {channel} activated")
        arduino.write(f"{channel} {position}\n".encode())
    elif position == 1:
        arduino.write(f"{channel} {position}\n".encode())  # Send 'channel0' to Arduino to deactivate solenoid valve
        #print(f"Solenoid valve on channel {channel} deactivated")
    else:
        print("invalid input")
# Function to read data from input registers (Function Code 4)
async def read_input_registers(modbusclient, start_address, num_elements, slave, datatype):
    response = await modbusclient.read_input_registers(start_address, num_elements, unit=slave)
    if response.isError():
        logging.error(f"Read Input Registers Error for {datatype}: {response}")
    else:
        if datatype == 'B':
            data = response.registers[0]  # Assuming you expect only one register for Unsigned8 datatype
        else:
            data_bytes = b''.join(pack('>H', register) for register in response.registers)
            data = unpack(datatype, data_bytes)
        print(f"{datatype} Data:", data)
    return data
# Function to read holding registers (Function Code 3)
async def read_holding_registers(modbusclient, start_address, num_registers, slave, datatype):
    response = await modbusclient.read_holding_registers(start_address, num_registers, unit=slave)
    if response.isError():
        print("Read Holding Registers Error:", response)
    else:
        if datatype == 'B':
            data = response.registers[0]  # Assuming you expect only one register for Unsigned8 datatype
        else:
            data_bytes = b''.join(pack('>H', register) for register in response.registers)
            data = unpack(datatype, data_bytes)
        print(f"{datatype} Data:", data)
        return data
# Function to write data to holding registers
async def write_holding_registers(modbusclient, start_address, data, slave):
    response = await modbusclient.write_holding_registers(start_address, data, unit=slave)
    if response.isError():
        print("Write Holding Registers Error:", response)
    else:
        print("Write Successful")
# Function to write register
async def write_register(modbusclient, address, value, slave):
    await modbusclient.connect()
    response = await modbusclient.write_register(address, value, unit=slave)
    if response.isError():
        print("Write Single Register Error:", response)
    else:
        print("Write Successful")
# Function to write single holding register (Function Code 6)
async def write_single_register(modbusclient, address, value, slave):
    # Convert the float value to bytes
    byte_value = pack('!f', value)
    # Write the bytes to the specified address
    response = await modbusclient.write_registers(address, [int.from_bytes(byte_value[i:i+2], byteorder='big') for i in range(0, len(byte_value), 2)], unit=slave)
    if response.isError():
        print("Write Single Register Error:", response)
    else:
        print("Write Successful")
async def write_value_bool(client, node_id, value):
    client_node = client.get_node(node_id)
    await client_node.set_data_value(value=value, varianttype=ua.VariantType.Boolean)  # Pass value directly
    #await client_node.set_value(client_node_dv)
    print("Value of : " + str(client_node) + ' : ' + str(value))
async def read_input_value(client, node_id):
   client_node = client.get_node(node_id)
   client_node_value = await client_node.read_value()
   logging.info(f"Value of {client_node}: {client_node_value}")
   return client_node_value
async def write_value_float(client, node_id, value):
    client_node = client.get_node(node_id)
    client_node_dv = await client_node.write_value(ua.Variant(float(value), ua.VariantType.Float))  # Pass value directly
    await client_node.set_value(client_node_dv)
    print("Value of : " + str(client_node) + ' : ' + str(value))
async def keep_connection_alive(modbusclient, client, stop_event):
    """
    Periodically read a register to keep the Modbus connection alive.
    """
    loop = asyncio.get_running_loop()
    while not stop_event.is_set():
        try:
            # Perform a non-blocking read operation to keep the connection alive
            await read_input_registers(modbusclient, 0, 1, 1, 'B')
            await asyncio.sleep(30)  # Send a ping every minute or as needed
            await read_input_value (client, "ns=4;s=|var|C6 S14 PRO.Application.GVL.PBPumpOnOff")
            await asyncio.sleep(30)
        except Exception as e:
            # Handle exceptions if the connection is lost or any other errors occur
            print(f"Error during connection keep-alive: {e}")
# async def record_dosing_quantity(client, MFC_01DosedQuantity_g, MFC_02DosedQuantity_g):
#     while True:
#         try:
#             await write_value_float(client,"ns=4;s=|var|C6 S14 PRO.Application.GVL.MFC01_DosedQuantity", MFC_01DosedQuantity_g)
#         except Exception as e:
#             print(f"Error during write dosed quantity: {e}")
#         try:
#             await write_value_float(client,"ns=4;s=|var|C6 S14 PRO.Application.GVL.MFC02_DosedQuantity", MFC_01DosedQuantity_g)
#         except Exception as e:
#             print(f"Error during write dosed quantity: {e}")
 
#Setup one for coupling ()
async def PhosDCI_operations(modbusclient, MFC01_PhosphoramiditeQuantity_g,
                             MFC02_DCIQuantity_g,
                             Phosphoramiditie_VP,
                             DCI_VP,
                             MFC02_CyanethanolQuantity_g,
                             Cyanethanol_VP,
                             MFC02_POSCSOQuantity_g,
                             POSCSO_VP,
                             client
                             ):
   await modbusclient.connect()
   stop_event = asyncio.Event()
   try:
       # Since the Modbus operations are synchronous, run them in executor to avoid blocking the event loop
       loop = asyncio.get_running_loop()
       keep_alive_task = asyncio.create_task(keep_connection_alive(modbusclient, client, stop_event))
       ## Phosphoramidite + DCI premixing
       # write the code to take Phosphoramidite_VP abd DCI_VP
       await loop.run_in_executor(None, set_valve_position, Phosphoramiditie_VP, 1)
       #await loop.run_in_executor(None, set_valve_position, Phosphoramiditie_VP, 1) #DCI is without manifold for now
       await asyncio.sleep(2)
       # Write dosing setpoint to MFC_01&02
       await loop.run_in_executor(None, write_single_register, modbusclient, 1711, MFC01_PhosphoramiditeQuantity_g/1000, 1) #MFC01_SetpointDosingQuantity
       await asyncio.sleep(0.1)
       await write_single_register(modbusclient,  2711, MFC02_DCIQuantity_g/1000, 1) #MFC02_SetpointDosingQuantity
       await asyncio.sleep(0.1)
       # Write MFC01&02_StartBatch
       await write_register(modbusclient, 0, 256, 1) #Write MFC01_StartBatch - 256is dose and 0is stop
       await asyncio.sleep(0.1)
       await write_register(modbusclient,  1, 256, 1) #Write MFC02_StartBatch - 256is dose and 0is stop
       await asyncio.sleep(0.1)
       # Read MFC01&02_StartBatch
       await write_register(modbusclient,  0, 0, 1) #Write MFC01_StartBatch - 256is dose and 0is stop
       await asyncio.sleep(0.1)
       await write_register(modbusclient,  1, 0, 1) #Write MFC02_StartBatch - 256is dose and 0is stop
       #Finishing the phosphoramidite + DCI premixing
       await asyncio.sleep(12) #20s as test.
       await loop.run_in_executor(None, set_valve_position, Phosphoramiditie_VP, 0)
       MFC_01DosedQuantity_g = await read_input_registers(modbusclient, 8, 2, 1, '>f' ) #Float datatype (big-endian)
       await record_variables('MFC01_DosedQuantity_g', MFC_01DosedQuantity_g)
       MFC_02DosedQuantity_g = await read_input_registers(modbusclient, 18, 2, 1, '>f') #Float datatype (big-endian)
       await record_variables('MFC02_DosedQuantity_g', MFC_02DosedQuantity_g)
       #await record_variables()
       #await asyncio.sleep(2)
       #await record_dosing_quantity(client, MFC_01DosedQuantity_g, MFC_02DosedQuantity_g )
       await asyncio.sleep(3600)
        ## Cyanethanol
       # write the code to take Cyanethanol_VP
       await loop.run_in_executor(None, set_valve_position, Cyanethanol_VP, 1)
       await asyncio.sleep(2)
       # Write dosing setpoint to MFC_02
       await write_single_register(modbusclient, 1711, MFC02_CyanethanolQuantity_g/1000, 1) #MFC01_SetpointDosingQuantity
       await asyncio.sleep(0.1)
       # Write MFC01&02_StartBatch
       await write_register(modbusclient, 0, 256, 1) #Write MFC01_StartBatch - 256is dose and 0is stop
       await asyncio.sleep(0.1)
       await write_register(modbusclient, 0, 0, 1) #Write MFC01_StartBatch - 256is dose and 0is stop
        #Finishing the phosphoramidite + DCI premixing
       await asyncio.sleep(12) #20s as test. Change this to 2mins for actual coupling reaction
       await loop.run_in_executor(None, set_valve_position, Cyanethanol_VP, 0)
       MFC_02DosedCyethanolQuantity_g = await read_input_registers(modbusclient, 8, 2, 1, '>f') #Float datatype (big-endian)
       await record_variables('MFC02_CyanethanolQuantity_g', MFC_02DosedCyethanolQuantity_g)
       #await record_variables()
       await asyncio.sleep(60)
 
       ## POS_CSO
       # write the code to take PSOCSO_VP
       await loop.run_in_executor(None, set_valve_position, POSCSO_VP, 1)
       await asyncio.sleep(2)
        # Write dosing setpoint to MFC_02
       await write_single_register( modbusclient, 1711, MFC02_POSCSOQuantity_g/1000, 1) #MFC02_SetpointDosingQuantity
       await asyncio.sleep(0.1)
       # Write MFC01&02_StartBatch
       await write_register( modbusclient, 0, 256, 1) #Write MFC01_StartBatch - 256is dose and 0is stop
       await asyncio.sleep(0.1)
       await write_register( modbusclient, 0, 0, 1) #Write MFC01_StartBatch - 256is dose and 0is stop
        #Finishing the phosphoramidite + DCI premixing
       await asyncio.sleep(12) #20s as test. Change this to 2mins for actual coupling reaction
       await loop.run_in_executor(None, set_valve_position, POSCSO_VP, 0)
       MFC_02DosedCSOQuantity_g = await read_input_registers( modbusclient, 8, 2, 1, '>f') #Float datatype (big-endian)
       await record_variables('MFC02_POSCSOQuantity_g', MFC_02DosedCSOQuantity_g)
       #await record_variables()
       await asyncio.sleep(3600)
   finally:
       stop_event.set()
       await asyncio.sleep(15) #Disconnect - estimate how long the dosing will take
       await keep_alive_task
       await modbusclient.close()
#Setup one for detrit()
async def Detrit_operations(modbusclient, MFC02_TFAQuantity_g,
                            MFC02_TEMESQuantity_g,
                            TFA_VP,
                            TEMES_VP,
                            client
                            ):
   await modbusclient.connect()
   stop_event = asyncio.Event()
   try:
       # Since the Modbus operations are synchronous, run them in executor to avoid blocking the event loop
       loop = asyncio.get_running_loop()
       keep_alive_task = asyncio.create_task(keep_connection_alive(modbusclient,client, stop_event))
       ## Acid detrit
       # write the code to take Phosphoramidite_VP abd DCI_VP
       await loop.run_in_executor(None, set_valve_position, TEMES_VP, 1)                        
       await asyncio.sleep(2)
       # Write dosing setpoint to MFC_02
       await write_single_register(modbusclient, 1711, MFC02_TEMESQuantity_g/1000, 1) #MFC01_SetpointDosingQuantity
       await asyncio.sleep(0.1)
       # Write MFC02_StartBatch
       await write_register(modbusclient, 0, 256, 1) #Write MFC01_StartBatch - 256is dose and 0is stop
       await asyncio.sleep(0.1)
       # Read MFC02_StartBatch
       await write_register(modbusclient, 0, 0, 1) #Write MFC01_StartBatch - 256is dose and 0is stop
       #Finishing the phosphoramidite + DCI premixing
       await asyncio.sleep(120) #20s as test. Change this to 10mins for actual coupling reaction
       loop.run_in_executor(None, set_valve_position, TEMES_VP, 0)
       MFC_02DosedTEMESQuantity_g = await read_input_registers(modbusclient, 8, 2, 1, '>f') #Float datatype (big-endian)
       await record_variables('MFC02_TEMESQuantity_g', MFC_02DosedTEMESQuantity_g)
       await asyncio.sleep(1200)
       ## TEMES
       # write the code to take Cyanethanol_VP
       await loop.run_in_executor(None, set_valve_position, TFA_VP, 1)    
       await asyncio.sleep(2)
       # Write dosing setpoint to MFC_02
       await write_single_register(modbusclient, 1711, MFC02_TFAQuantity_g/1000, 1) #MFC02_SetpointDosingQuantity
       await asyncio.sleep(0.1)
       # Write MFC01&02_StartBatch
       await write_register(modbusclient,  0, 256, 1) #Write MFC02_StartBatch - 256is dose and 0is stop
       #
       await asyncio.sleep(0.1)
       await write_register(modbusclient,  0, 0, 1) #Write MFC02_StartBatch - 256is dose and 0is stop
       # Read MFC01&02_StartBatch
       #Finishing the phosphoramidite + DCI premixing
       await asyncio.sleep(12) #20s as test. Change this to 2mins for actual coupling reaction
       await loop.run_in_executor(None, set_valve_position, TFA_VP, 0)
       MFC_02DosedTFAQuantity_g = read_input_registers(modbusclient,  8, 2, 1, '>f') #Float datatype (big-endian)
       await record_variables('MFC02_TFAQuantity_g', MFC_02DosedTFAQuantity_g)
       await asyncio.sleep(120)
   finally:
       stop_event.set()
       await asyncio.sleep(15) #Disconnect - estimate how long the dosing will take
       await keep_alive_task
       await modbusclient.close()
#Running the dosing sequence
async def main():
   url = "address"
   logging.info(f"Connecting to {url} ...")
   # Modbus TCP client initialization
   modbusclient = AsyncModbusTcpClient('modbusaddress')
   async with Client(url=url) as client:
       await client.connect()
       root = client.get_root_node()
       logging.info(f"Objects root node is: {root}")
       coupling_param = {
                          1.5: (5, 5, 1, 5, 5, 2, 5, 3),
                          2.5: (5, 5, 5, 5, 5, 5, 5, 5),
                          3.5: (5, 5, 1, 5, 5, 2, 5, 3)} #PhosDCI_operations(MFC01_PhosphoramiditeQuantity_g, MFC02_DCIQuantity_g, Phosphoramiditie_VP, DCI_VP, MFC02_CyanethanolQuantity_g, Cyanethanol_VP, MFC02_POSCSOQuantity_g, POSCSO_VP)
       detrit_param = {
                         1: (5, 5, 5, 5),
                         2: (5, 5, 5, 5),
                         3: (5, 5, 5, 5),
                         4: (5, 5, 5, 5)}#Detrit_operations(MFC02_TFAQuantity_g, MFC02_TEMESQuantity_g, TFA_VP, TEMES_VP, client):
                         #5: (27.5, 10, 1, 3)}
       while True:
            ## Should replace the node_value woth a 'handshake' value from PLC
            ## One node_value for coupling (potentially sub divided into phos-DCI, quench, PO/PS0)
            ## Onde node_value for detrit (potentially sub divided into traps and then acid)
            node_value = await read_input_value(client, "ns=4;s=|var|C6 S14 PRO.Application.GVL.Dosing")
            process_stage = await read_input_value(client, "ns=4;s=|var|C6 S14 PRO.Application.GVL.ProcessStage") ## Assign Process_stage to GVL.ProcessStage
            if node_value:
                logging.info(f"Node value: {node_value}")
                if process_stage == int(process_stage):
                    #await write_value_bool(client, "ns=4;s=|var|C6 S14 PRO.Application.GVL.Dosing", True)
                    try:
                        await write_value_bool(client, "ns=4;s=|var|C6 S14 PRO.Application.GVL.Detritylation", True)  # Writing an integer value
                    except Exception as e:
                        logging.error("error")
                    try:
                        await Detrit_operations(modbusclient, *detrit_param[process_stage], client)
                    except Exception as e:
                        logging.error(f"Error during modbus operations: {e}")    
                    try:
                        await write_value_bool(client, "ns=4;s=|var|C6 S14 PRO.Application.GVL.Dosing", False)  # Writing an integer value
                    except Exception as e:
                        logging.error(f"Error during write_value_bool dosing 309 operations: {e}")
                    try:
                        await write_value_bool(client, "ns=4;s=|var|C6 S14 PRO.Application.GVL.PBDFMode", True)  # Writing an integer value
                    except Exception as e:
                        logging.error(f"Error during write_value_bool PBDFMode 313 operations: {e}")
                else:
                    try:
                        await write_value_bool(client, "ns=4;s=|var|C6 S14 PRO.Application.GVL.Coupling", True)  # Writing an integer value
                    except Exception as e:
                        logging.error("error")
                    try:
                        await PhosDCI_operations(modbusclient, *coupling_param[process_stage], client)
                    except Exception as e:
                        logging.error(f"Error during modbus operations: {e}")    
                    try:    
                        await write_value_bool(client, "ns=4;s=|var|C6 S14 PRO.Application.GVL.Dosing", False)  # Writing an integer value
                    except Exception as e:
                        logging.error(f"Error during write_value_bool Dosing 236 operations: {e}")
                    try:
                        await write_value_bool(client, "ns=4;s=|var|C6 S14 PRO.Application.GVL.PBDFMode", True)  # Writing an integer value
                    except Exception as e:
                        logging.error(f"Error during write_value_bool PBDFMode 330 operations: {e}")
                await asyncio.sleep(5)
            else:
                logging.info("Node value is not True, skipping Modbus operations")
            await asyncio.sleep(5)  # Adjust sleep duration as needed
if __name__ == "__main__":
   asyncio.run(main())

Previously the code ran modbus I/O synchronously, had the same issue after 3 hours of operation where I would get a RuntimeError. I changed the modbus I/O to be async but same issue. I'm not super sure what to try next.


Solution

  • You connect multiple times, remove the client.connect line. Because with async with Client(url=url) as client you already connect to the server.

     async with Client(url=url) as client:
           await client.connect()   <--- remove this line