2

I want to stop a pymodbus async ModbusTcpServer then start a new server. Therefore, I've tried with the following simplified code snippet, but I got an error:

from pymodbus.server.async import StartTcpServer, StopServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from time import sleep

import logging
logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.DEBUG)

def main(name='Pymodbus'):
    store = ModbusSlaveContext(hr=ModbusSequentialDataBlock(0, [17]*100))
    context = ModbusServerContext(slaves=store, single=True)

    identity = ModbusDeviceIdentification()
    identity.VendorName = name
    identity.ProductCode = 'PM'
    identity.VendorUrl = 'http://github.com/bashwork/pymodbus/'
    identity.ProductName = 'Pymodbus Server'
    identity.ModelName = 'Pymodbus Server'
    identity.MajorMinorRevision = '1.0'

    StartTcpServer(
        context,
        identity=identity,
        address=("localhost", 5020),
        defer_reactor_run=True
    )
    sleep(3)
    name += 'stuff'

    StopServer()
    sleep(3)
    main(name)  # Recursive

main()

Out:

INFO:pymodbus.server.async:Starting Modbus TCP Server on localhost:5020
DEBUG:pymodbus.server.async:Running in Main thread
Traceback (most recent call last):
  File "stack.py", line 42, in <module>
    main()
  File "stack.py", line 38, in main
    StopServer()
  File "/usr/local/lib/python3.6/dist-packages/pymodbus/server/async.py", line 328, in StopServer
    reactor.stop()
  File "/usr/local/lib/python3.6/dist-packages/twisted/internet/base.py", line 630, in stop
    "Can't stop reactor that isn't running.")
twisted.internet.error.ReactorNotRunning: Can't stop reactor that isn't running.

[UPDATE]

Also, I've tried with another thread to stop the ModbusTcpServer with the defer_reactor_run=False argument (as default) in ModbusTcpServer, but despite that, the behavior remains the same:

import threading
import logging
from pymodbus.server.async import StartTcpServer, StopServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext

logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.DEBUG)

def stop():
    StopServer()

def main(name='Pymodbus'):
    store = ModbusSlaveContext(hr=ModbusSequentialDataBlock(0, [17]*100))
    context = ModbusServerContext(slaves=store, single=True)

    identity = ModbusDeviceIdentification()
    identity.VendorName = name
    identity.ProductCode = 'PM'
    identity.VendorUrl = 'http://github.com/bashwork/pymodbus/'
    identity.ProductName = 'Pymodbus Server'
    identity.ModelName = 'Pymodbus Server'
    identity.MajorMinorRevision = '1.0'

    t = threading.Timer(5, stop)
    t.daemon = True
    t.start()

    StartTcpServer(
        context,
        identity=identity,
        address=("localhost", 5020),
        defer_reactor_run=False
    )
    name += 'stuff'

    main(name)  # Recursive

main()

Out:

INFO:pymodbus.server.async:Starting Modbus TCP Server on localhost:5020
DEBUG:pymodbus.server.async:Running in Main thread
DEBUG:pymodbus.server.async:Running in spawned thread
DEBUG:pymodbus.server.async:Stopping Server from another thread
INFO:pymodbus.server.async:Starting Modbus TCP Server on localhost:5020
DEBUG:pymodbus.server.async:Running in Main thread
Traceback (most recent call last):
  File "stack.py", line 41, in <module>
    main()
  File "stack.py", line 39, in main
    main()  # Recursive
  File "stack.py", line 35, in main
    defer_reactor_run=False
  File "/usr/local/lib/python3.6/dist-packages/pymodbus/server/async.py", line 257, in StartTcpServer
    reactor.run(installSignalHandlers=_is_main_thread())
  File "/usr/local/lib/python3.6/dist-packages/twisted/internet/base.py", line 1260, in run
    self.startRunning(installSignalHandlers=installSignalHandlers)
  File "/usr/local/lib/python3.6/dist-packages/twisted/internet/base.py", line 1240, in startRunning
    ReactorBase.startRunning(self)
  File "/usr/local/lib/python3.6/dist-packages/twisted/internet/base.py", line 748, in startRunning
    raise error.ReactorNotRestartable()
twisted.internet.error.ReactorNotRestartable
Benyamin Jafari
  • 15,536
  • 14
  • 81
  • 116

3 Answers3

1

I found an alternative solution to stop and start an Async ModbusTcpServer by another Python code because apparently, we cannot restart a reactor event-loop.


This is the runner.py code:

import subprocess

python_version = '3'
path_to_run = './'
py_name = 'async_server.py'

def run():
    args = [f"python{python_version}", f"{path_to_run}{py_name}"]
    sub_process = subprocess.Popen(args, stdout=subprocess.PIPE)
    output, error_ = sub_process.communicate()

    if not error_:
        print(output)
    else:
        print(error_)

    run()  # Recursively.

if __name__ == '__main__':
    run()

This is the async_server.py code snippet:

from pymodbus.server.async import StartTcpServer, StopServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext

import threading
import sys
import logging

FORMAT = ('%(asctime)-15s %(threadName)-15s'
          ' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s')
logging.basicConfig(format=FORMAT)
log = logging.getLogger()
log.setLevel(logging.DEBUG)

def stop():
    print('Process will be down.')
    StopServer()  # Stop server.
    sys.exit(0)  # Kill the server code.

def run_async_server():
    store = ModbusSlaveContext(hr=ModbusSequentialDataBlock(0, [17] * 100))
    slaves = {
        0x01: store,
        0x02: store,
        0x03: store,
    }
    context = ModbusServerContext(slaves=slaves, single=False)

    identity = ModbusDeviceIdentification()
    identity.VendorName = 'Pymodbus'
    identity.ProductCode = 'PM'
    identity.VendorUrl = 'http://github.com/bashwork/pymodbus/'
    identity.ProductName = 'Pymodbus Server'
    identity.ModelName = 'Pymodbus Server'
    identity.MajorMinorRevision = '1.5'

    from twisted.internet import reactor
    StartTcpServer(context, identity=identity, address=("localhost", 5020),
                   defer_reactor_run=True)
    print('Start an async server.')
    t = threading.Timer(5, stop)
    t.daemon = True
    t.start()
    reactor.run()
    print('Server was stopped.')

if __name__ == "__main__":
    run_async_server()

Out:

$ python3 runner.py 

2019-01-24 12:45:05,126 MainThread      INFO     async          :254      Starting Modbus TCP Server on localhost:5020
2019-01-24 12:45:10,129 Thread-1        DEBUG    async          :222      Running in spawned thread
2019-01-24 12:45:10,129 Thread-1        DEBUG    async          :332      Stopping Server from another thread
b'Start an async server.\nProcess will be down.\nServer was stopped.\n'
2019-01-24 12:45:13,389 MainThread      INFO     async          :254      Starting Modbus TCP Server on localhost:5020
2019-01-24 12:45:18,392 Thread-1        DEBUG    async          :222      Running in spawned thread
2019-01-24 12:45:18,392 Thread-1        DEBUG    async          :332      Stopping Server from another thread
b'Start an async server.\nProcess will be down.\nServer was stopped.\n'
2019-01-24 12:45:21,653 MainThread      INFO     async          :254      Starting Modbus TCP Server on localhost:5020
2019-01-24 12:45:26,656 Thread-1        DEBUG    async          :222      Running in spawned thread
2019-01-24 12:45:26,657 Thread-1        DEBUG    async          :332      Stopping Server from another thread
b'Start an async server.\nProcess will be down.\nServer was stopped.\n'
.
.
.
Benyamin Jafari
  • 15,536
  • 14
  • 81
  • 116
0

In the first example, the TCP server is not run since you setted param defer_reactor_run to True, but you have not explicitely run it . Thus , when you try stopping it , it fails since it has not been started.

In the second example, you run it, but are calling it a next time recursively with the main(name) call ! so, it fails with error since it is already started ! the next code should work :

from pymodbus.server.async import StartTcpServer, StopServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from time import sleep

import logging
logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.DEBUG)

def main(name='Pymodbus'):
    store = ModbusSlaveContext(hr=ModbusSequentialDataBlock(0, [17]*100))
    context = ModbusServerContext(slaves=store, single=True)

    identity = ModbusDeviceIdentification()
    identity.VendorName = name
    identity.ProductCode = 'PM'
    identity.VendorUrl = 'http://github.com/bashwork/pymodbus/'
    identity.ProductName = 'Pymodbus Server'
    identity.ModelName = 'Pymodbus Server'
    identity.MajorMinorRevision = '1.0'

    StartTcpServer(
        context,
        identity=identity,
        address=("localhost", 5020),
        defer_reactor_run=False
    )

    sleep(3)  # for the fun ?
    # and do your stuff 

    StopServer()

main()

If you want to defer run, you have to call :

from twisted.internet import reactor
StartTcpServer(context, identity=identity, address=("localhost", 5020),
            defer_reactor_run=True)
reactor.run()
sancelot
  • 1,329
  • 8
  • 24
  • In the second example, I stop the reactor by another thread after 5sec with the built-in `StopServer()` in pymodbus – Benyamin Jafari Jan 23 '19 at 08:59
  • maybe, but meantime you try to restart the already started server, before the timer wakes up . – sancelot Jan 23 '19 at 09:11
  • Did try this snippet code? This code stuck in `StartTcpServe()` because `defer_reactor_run` is false. However, I want to stop and restart the server – Benyamin Jafari Jan 23 '19 at 09:39
  • It is not stuck , it runs a server .... if you want to stop it, kill the task .I updated how to run it in defered mode – sancelot Jan 23 '19 at 14:43
  • My question is obvious, this is not my answer, and thanks for the response. – Benyamin Jafari Jan 24 '19 at 08:40
  • I haven't any problem to run the server, I want to run a server then stop it then start the server with new content for some reason (my question code is simplified of code to simulate that want I want). If I not found any answer to restart a `reactor` loop I do it by another code – Benyamin Jafari Jan 24 '19 at 09:02
0

You can use the example included in library folders: asynchronous-server.py

in python3.7 async was included as a keyword, but in pymodbus 2.3.0 pymodbus.server.async was changed to pymodbus.server.asynchronous

i share you a example that works fine for me:

If you have any more questions, write me at e-mail:Danielsalazr@hotmail.com

#Este codigo funciona ingresando a la direccion esclavo 16 en decimal
#la Ip debe ser la misma que esta asignada en el dispositivo

#los datos se actualizan a patir del registro #15
#Aparentemente la direcion del esclavo no tiene importancia, debido a que
#al realizar la conexion desde el software QModMaster, el numero ingresado
#em el campo SlaveAddr, no afecta la posibilidad de conexion

'''
Pymodbus Server With Updating Thread
--------------------------------------------------------------------------

This is an example of having a background thread updating the
context while the server is operating. This can also be done with
a python thread::

    from threading import Thread

    thread = Thread(target=updating_writer, args=(context,))
    thread.start()
'''
#---------------------------------------------------------------------------# 
# import the modbus libraries we need
#---------------------------------------------------------------------------# 
from pymodbus.server.asynchronous import StartTcpServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer

#---------------------------------------------------------------------------# 
# import the twisted libraries we need
#---------------------------------------------------------------------------# 
from twisted.internet.task import LoopingCall

#---------------------------------------------------------------------------# 
# configure the service logging
#---------------------------------------------------------------------------# 
import logging
logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.DEBUG)

#---------------------------------------------------------------------------# 
# define your callback process
#---------------------------------------------------------------------------# 
def updating_writer(a):
    ''' A worker process that runs every so often and
    updates live values of the context. It should be noted
    that there is a race condition for the update.

    :param arguments: The input arguments to the call
    '''
    log.debug("updating the context")
    context  = a[0]
    register = 3
    slave_id = 0x01
    address  = 0x10
    values   = context[slave_id].getValues(register, address, count=5)
    #el valor count = 5 modifica los datos de los siguientes 5 registros
    #continuos al registro especificado en la variable address = 0x10, 16 en decimal
    values   = [v + 1 for v in values]
    log.debug("new values: " + str(values))
    context[slave_id].setValues(register, address, values)

#---------------------------------------------------------------------------# 
# initialize your data store
#---------------------------------------------------------------------------# 
store = ModbusSlaveContext(
    di = ModbusSequentialDataBlock(0, [17]*100),
    co = ModbusSequentialDataBlock(0, [17]*100),
    hr = ModbusSequentialDataBlock(0, [17]*100),
    ir = ModbusSequentialDataBlock(0, [17]*100))
context = ModbusServerContext(slaves=store, single=True)

#ModbusSlaveContext.setValues("lol",hr,3,25)
#context = ModbusServerContext(slaves=store, single=True)
#---------------------------------------------------------------------------# 
# initialize the server information
#---------------------------------------------------------------------------# 
identity = ModbusDeviceIdentification()
identity.VendorName  = 'pymodbus'
identity.ProductCode = 'PM'
identity.VendorUrl   = 'http://github.com/bashwork/pymodbus/'
identity.ProductName = 'pymodbus Server'
identity.ModelName   = 'pymodbus Server'
identity.MajorMinorRevision = '1.0'

#---------------------------------------------------------------------------# 
# run the server you want
#---------------------------------------------------------------------------# 
time = 1 # 5 seconds delay
loop = LoopingCall(f=updating_writer, a=(context,))
loop.start(time, now=False) # initially delay by time
StartTcpServer(context, identity= identity, address=("192.168.0.40", 502)) #here you need 
#change the ip for the ip address of your device 

Greetings from Colombia

  • Hello Daniel, Since that pymodbus library has been new updated, `pymodbus.server.async` has been transformed into the `pymodbus.server.asynchronous` in the newer version and I'm aware of it (my question was asked one-year ago with the earlier version of pymodbus). However, this is not my problem. As I mentioned in the question, the problem is `StopServer` not `StartTcpServer` – Benyamin Jafari Mar 19 '20 at 09:09