1

Description and code:

I'm using the Synchronous ModbusTcpServer with pymodbus library to create a Modbus Slave/Server, that here's the code:

from pymodbus.server.sync import StartTcpServer, ModbusTcpServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from twisted.internet.task import LoopingCall
from twisted.internet import reactor
import threading
import logging

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

def run_server():
    block1 = ModbusSequentialDataBlock(0x00, [717] * 0x0F)
    block2 = ModbusSequentialDataBlock(0x10, [323] * 0x1F)
    store2 = ModbusSlaveContext(hr=block1, ir=block2)

    slaves = {
        0x01: store2,
    }

    context = ModbusServerContext(slaves=slaves, single=False)

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

    interval = 2
    server = ModbusTcpServer(context,
                             identity=identity,
                             address=('0.0.0.0', 5021))  # Problem cause.
    thread_ = threading.Thread(target=server.serve_forever, daemon=True)
    thread_.start()
    loop = LoopingCall(f=update_values, a=server)
    loop.start(interval, now=True)
    reactor.run()


def update_values(a):
    print("-----------START-----------")
    rfuncode = 3
    wfuncode = 16
    slave_id = 0x01
    address = 0x00
    context_ = a.context[slave_id]
    values = context_.getValues(rfuncode, address, count=32)
    print(values)
    values = [val+1 for val in values]
    context_.setValues(wfuncode, address, values)
    print("------------END------------")


if __name__ == "__main__":
    run_server()

When a client app connects to this server and when I close this code (with Ctrl+C) and run again encountered with this error:

OSError: [Errno 98] Address already in use I know that in socket creation we can use socket.SO_REUSEADDR for overcome on this issue.

Also, I can .close() connection in client side to resolve this issue but I want have a stable server.


Question:

Is there a built-in way to overcome this? I find out this argument (socket.SO_REUSEADDR) in Asynchronous ModbusTcpServer (in async.py) but there isn't in Synchronous ModbusTcpServer (sync.py).


[NOTE]:

Versions

  • Python: 3.6.5
  • OS: Ubuntu 16.04
  • Pymodbus: 1.5.2
  • Modbus Hardware (if used): No

Pymodbus Specific

  • Server: tcp - sync
  • Client: tcp - sync
Benyamin Jafari
  • 27,880
  • 26
  • 135
  • 150

1 Answers1

1

ModbusTcpServer is derived from socketserver.ThreadingTCPServer. To reuse the address you will have to explicitly override the class variable allow_resuse_address .

class ReusableModbusTcpServer(ModbusTcpServer):

    def __init__(self, context, framer=None, identity=None,
                 address=None, handler=None, **kwargs):
        self.allow_reuse_address = True
        ModbusTcpServer.__init__(self, context, framer, identity, address, handler, **kwargs)

For more information refer to the source code of socketserver here


[UPDATE]:

You are mixing threads and reactor. And twisted has its own signal handlers which could be the reason why the server is not quitting as expected. BTW, have you checked the updating_server.py example? That looks similar to what you are doing, except it uses Async server. It comes with reusing address by default and handles graceful terminations.

But in case you still want to go with your code. Here is an ugly hack to deal with the blocking program. Note, in some case, you will have to presee Ctrl+C` twice and you will see some ugly tracebacks from the threading module.

from pymodbus.server.sync import StartTcpServer, ModbusTcpServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from twisted.internet.task import LoopingCall
from twisted.internet import reactor
import threading
import logging
import signal
import time

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

SERVER = None
THREADED_SERVER = None
LOOP = None

class ReusableModbusTcpServer(ModbusTcpServer):
    def __init__(self, context, framer=None, identity=None,
                 address=None, handler=None, **kwargs):
        self.allow_reuse_address = True
        ModbusTcpServer.__init__(self, context, framer, identity, address, handler, **kwargs)

class ThreadedModbusServer(threading.Thread):
    def __init__(self, server):
        super(ThreadedModbusServer, self).__init__(name="ModbusServerThread")
        self._server = server
        self.daemon = True

    def run(self):
        self._server.serve_forever()

    def stop(self):
        if isinstance(self._server, ModbusTcpServer):
            self._server.shutdown()
        else:
            if self._server.socket:
                self._server.server_close()


def update_values(a):
    print("-----------START-----------")
    rfuncode = 3
    wfuncode = 16
    slave_id = 0x01
    address = 0x00
    context_ = a.context[slave_id]
    values = context_.getValues(rfuncode, address, count=32)
    print(values)
    values = [val+1 for val in values]
    context_.setValues(wfuncode, address, values)
    print("------------END------------")
    time.sleep(0.1)

def run_server():
    global SERVER, THREADED_SERVER, LOOP
    block1 = ModbusSequentialDataBlock(0x00, [717] * 0x0F)
    block2 = ModbusSequentialDataBlock(0x10, [323] * 0x1F)
    store2 = ModbusSlaveContext(hr=block1, ir=block2)

    slaves = {
        0x01: store2,
    }

    context = ModbusServerContext(slaves=slaves, single=False)

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

    interval = 2
    SERVER = ReusableModbusTcpServer(context,
                             identity=identity,
                             address=('0.0.0.0', 5021))  # Problem cause.
    THREADED_SERVER = ThreadedModbusServer(SERVER)
    THREADED_SERVER.start()
    LOOP = LoopingCall(f=update_values, a=SERVER)
    LOOP.start(interval, now=True)
    reactor.run()

def signal_handler(signal, frame):
    global THREADED_SERVER, LOOP
    log.warning("You pressed Ctrl+C! ."
              "If the program does not quit, Try pressing the CTRL+C again!!!")
    if THREADED_SERVER:
        THREADED_SERVER.stop()
        THREADED_SERVER = None
    if LOOP:
        LOOP.stop()
        LOOP = None
    if reactor.running:
        reactor.stop()
    else:
        exit(1)

if __name__ == "__main__":
    signal.signal(signal.SIGINT, signal_handler)
    run_server()
Nihal
  • 5,262
  • 7
  • 23
  • 41
Sanju
  • 1,974
  • 1
  • 18
  • 33
  • Thank you, +1, but yet I have a problem with close the ModbusTcpServer when a client connected to it. – Benyamin Jafari Aug 30 '18 at 16:34
  • Is using the async server recommended for creating a `ModbusTcpServer` instead of a sync server? what are the pros and cons? – Benyamin Jafari Aug 31 '18 at 08:19
  • 1
    There is such pro's or con's using async over sync server. Depending on your need you can choose either of them. For simple stand alone usage synchronous server is easy to use as you don't have to install additional packages like `twisted` which is required if you want to use async server. Ofcourse with async server you can leverage on the many features of twisted package . If your main program is twisted based go with async version. – Sanju Aug 31 '18 at 08:54