Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a2d79e1
#357 Support registration of custom requests
dhoomakethu Dec 3, 2018
f07dcee
#368 Fixes write to broadcast address
mdmuhlbaier Jan 10, 2019
2e169b6
Bump version to 2.2.0
dhoomakethu Jan 14, 2019
c410f5b
Merge branch '#357-Custom-Function' into pymodbus-2.2.0
dhoomakethu Jan 14, 2019
826240b
Fix #371 pymodbus repl on python3
dhoomakethu Jan 14, 2019
6e72e44
1. Fix tornado async serial client `TypeError` while processing incom…
dhoomakethu Jan 15, 2019
18fe036
[fix v3] poprawa sprawdzania timeout
MarekLew Jan 6, 2019
964a565
Release candidate for pymodbus 2.2.0
dhoomakethu Jan 16, 2019
6960d9c
Fix #377 when invalid port is supplied and minor updates in logging
dhoomakethu Jan 26, 2019
249ad8f
Merge remote-tracking branch 'upstream/dev' into dev
muhlbaier Jan 31, 2019
60aca50
#368 adds broadcast support for sync client and server
muhlbaier Jan 31, 2019
e07e01e
#368 Fixes minor bug in broadcast support code
muhlbaier Jan 31, 2019
5030514
Fixed erronous CRC handling
JStrbg Jan 16, 2019
1a24c1d
Merge branch 'pull/372' into pymodbus-2.2.0
dhoomakethu Feb 11, 2019
e5c2615
Update Changelog
dhoomakethu Feb 11, 2019
f66f464
Fix test coverage
dhoomakethu Feb 11, 2019
7650421
Fix #387 Transactions failing on 2.2.0rc2.
dhoomakethu Feb 16, 2019
6233706
Task Cancellation and CRC Errors
pazzarpj Dec 12, 2018
89d3909
Cherry pick commit from PR #367 , Update changelog , bump version to …
dhoomakethu Mar 6, 2019
e4f202c
#389 Support passing all serial port parameters to asynchronous server
dhoomakethu Mar 23, 2019
3f48b90
Fix BinaryPayloadDecoder and Builder wrt to coils
dhoomakethu Apr 18, 2019
c919fe4
Misc updates, bump version to 2.2.0
dhoomakethu Apr 18, 2019
3d34820
ReportSlaveIdResponse now tries to get slave id based on server ident…
dhoomakethu Apr 18, 2019
2d8d467
Update missing bcrypt requirement for testing
dhoomakethu Apr 18, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
#357 Support registration of custom requests
  • Loading branch information
dhoomakethu committed Dec 3, 2018
commit a2d79e16a56f42f01b8b072eadbc4a54cce9e9a4
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Version 2.1.1
-----------------------------------------------------------
* Provide an option to disable inter char timeouts with Modbus RTU.
* Add support to register custom requests in clients and server instances.

Version 2.1.0
-----------------------------------------------------------
Expand Down
6 changes: 5 additions & 1 deletion examples/common/asynchronous_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from pymodbus.transaction import (ModbusRtuFramer,
ModbusAsciiFramer,
ModbusBinaryFramer)
from custom_message import CustomModbusRequest

# --------------------------------------------------------------------------- #
# configure the service logging
Expand Down Expand Up @@ -92,6 +93,8 @@ def run_async_server():
co=ModbusSequentialDataBlock(0, [17]*100),
hr=ModbusSequentialDataBlock(0, [17]*100),
ir=ModbusSequentialDataBlock(0, [17]*100))
store.register(CustomModbusRequest.function_code, 'cm',
ModbusSequentialDataBlock(0, [17] * 100))
context = ModbusServerContext(slaves=store, single=True)

# ----------------------------------------------------------------------- #
Expand All @@ -113,7 +116,8 @@ def run_async_server():

# TCP Server

StartTcpServer(context, identity=identity, address=("localhost", 5020))
StartTcpServer(context, identity=identity, address=("localhost", 5020),
custom_functions=[CustomModbusRequest])

# TCP Server with deferred reactor run

Expand Down
46 changes: 37 additions & 9 deletions examples/common/custom_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from pymodbus.pdu import ModbusRequest, ModbusResponse, ModbusExceptions
from pymodbus.client.sync import ModbusTcpClient as ModbusClient
from pymodbus.bit_read_message import ReadCoilsRequest
from pymodbus.compat import int2byte, byte2int
# --------------------------------------------------------------------------- #
# configure the client logging
# --------------------------------------------------------------------------- #
Expand All @@ -40,15 +41,41 @@


class CustomModbusResponse(ModbusResponse):
pass
function_code = 55
_rtu_byte_count_pos = 2

def __init__(self, values=None, **kwargs):
ModbusResponse.__init__(self, **kwargs)
self.values = values or []

def encode(self):
""" Encodes response pdu

:returns: The encoded packet message
"""
result = int2byte(len(self.values) * 2)
for register in self.values:
result += struct.pack('>H', register)
return result

def decode(self, data):
""" Decodes response pdu

:param data: The packet data to decode
"""
byte_count = byte2int(data[0])
self.values = []
for i in range(1, byte_count + 1, 2):
self.values.append(struct.unpack('>H', data[i:i + 2])[0])


class CustomModbusRequest(ModbusRequest):

function_code = 1
function_code = 55
_rtu_frame_size = 8

def __init__(self, address):
ModbusRequest.__init__(self)
def __init__(self, address=None, **kwargs):
ModbusRequest.__init__(self, **kwargs)
self.address = address
self.count = 16

Expand All @@ -74,12 +101,12 @@ def execute(self, context):

class Read16CoilsRequest(ReadCoilsRequest):

def __init__(self, address):
def __init__(self, address, **kwargs):
""" Initializes a new instance

:param address: The address to start reading from
"""
ReadCoilsRequest.__init__(self, address, 16)
ReadCoilsRequest.__init__(self, address, 16, **kwargs)

# --------------------------------------------------------------------------- #
# execute the request with your client
Expand All @@ -90,7 +117,8 @@ def __init__(self, address):


if __name__ == "__main__":
with ModbusClient('127.0.0.1') as client:
request = CustomModbusRequest(0)
with ModbusClient(host='localhost', port=5020) as client:
client.register(CustomModbusResponse)
request = CustomModbusRequest(1, unit=1)
result = client.execute(request)
print(result)
print(result.values)
124 changes: 124 additions & 0 deletions examples/common/custom_message_async_clients.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#!/usr/bin/env python
"""
Pymodbus Synchronous Client Examples
--------------------------------------------------------------------------

The following is an example of how to use the synchronous modbus client
implementation from pymodbus.

It should be noted that the client can also be used with
the guard construct that is available in python 2.5 and up::

with ModbusClient('127.0.0.1') as client:
result = client.read_coils(1,10)
print result
"""
import struct
# --------------------------------------------------------------------------- #
# import the various server implementations
# --------------------------------------------------------------------------- #
from pymodbus.pdu import ModbusRequest, ModbusResponse, ModbusExceptions
from pymodbus.client.sync import ModbusTcpClient as ModbusClient
from pymodbus.bit_read_message import ReadCoilsRequest
from pymodbus.compat import int2byte, byte2int
# --------------------------------------------------------------------------- #
# configure the client logging
# --------------------------------------------------------------------------- #
import logging
logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.DEBUG)

# --------------------------------------------------------------------------- #
# create your custom message
# --------------------------------------------------------------------------- #
# The following is simply a read coil request that always reads 16 coils.
# Since the function code is already registered with the decoder factory,
# this will be decoded as a read coil response. If you implement a new
# method that is not currently implemented, you must register the request
# and response with a ClientDecoder factory.
# --------------------------------------------------------------------------- #


class CustomModbusResponse(ModbusResponse):
function_code = 55
_rtu_byte_count_pos = 2

def __init__(self, values=None, **kwargs):
ModbusResponse.__init__(self, **kwargs)
self.values = values or []

def encode(self):
""" Encodes response pdu

:returns: The encoded packet message
"""
result = int2byte(len(self.values) * 2)
for register in self.values:
result += struct.pack('>H', register)
return result

def decode(self, data):
""" Decodes response pdu

:param data: The packet data to decode
"""
byte_count = byte2int(data[0])
self.values = []
for i in range(1, byte_count + 1, 2):
self.values.append(struct.unpack('>H', data[i:i + 2])[0])


class CustomModbusRequest(ModbusRequest):

function_code = 55
_rtu_frame_size = 8

def __init__(self, address=None, **kwargs):
ModbusRequest.__init__(self, **kwargs)
self.address = address
self.count = 16

def encode(self):
return struct.pack('>HH', self.address, self.count)

def decode(self, data):
self.address, self.count = struct.unpack('>HH', data)

def execute(self, context):
if not (1 <= self.count <= 0x7d0):
return self.doException(ModbusExceptions.IllegalValue)
if not context.validate(self.function_code, self.address, self.count):
return self.doException(ModbusExceptions.IllegalAddress)
values = context.getValues(self.function_code, self.address,
self.count)
return CustomModbusResponse(values)

# --------------------------------------------------------------------------- #
# This could also have been defined as
# --------------------------------------------------------------------------- #


class Read16CoilsRequest(ReadCoilsRequest):

def __init__(self, address, **kwargs):
""" Initializes a new instance

:param address: The address to start reading from
"""
ReadCoilsRequest.__init__(self, address, 16, **kwargs)

# --------------------------------------------------------------------------- #
# execute the request with your client
# --------------------------------------------------------------------------- #
# using the with context, the client will automatically be connected
# and closed when it leaves the current scope.
# --------------------------------------------------------------------------- #


if __name__ == "__main__":
with ModbusClient(host='localhost', port=5020) as client:
client.register(CustomModbusResponse)
request = CustomModbusRequest(1, unit=1)
result = client.execute(request)
print(result.values)
116 changes: 116 additions & 0 deletions examples/common/custom_synchronous_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#!/usr/bin/env python
"""
Pymodbus Synchronous Server Example with Custom functions
--------------------------------------------------------------------------

Implements a custom function code not in standard modbus function code list
and its response which otherwise would throw `IllegalFunction (0x1)` error.

Steps:
1. Create CustomModbusRequest class derived from ModbusRequest
```class CustomModbusRequest(ModbusRequest):
function_code = 75 # Value less than 0x80)
_rtu_frame_size = <some_value> # Required only For RTU support

def __init__(custom_arg=None, **kwargs)
# Make sure the arguments has default values, will error out
# while decoding otherwise
ModbusRequest.__init__(self, **kwargs)
self.custom_request_arg = custom_arg

def encode(self):
# Implement encoding logic

def decode(self, data):
# implement decoding logic

def execute(self, context):
# Implement execute logic
...
return CustomModbusResponse(..)

```
2. Create CustomModbusResponse class derived from ModbusResponse
```class CustomModbusResponse(ModbusResponse):
function_code = 75 # Value less than 0x80)
_rtu_byte_count_pos = <some_value> # Required only For RTU support

def __init__(self, custom_args=None, **kwargs):
# Make sure the arguments has default values, will error out
# while decoding otherwise
ModbusResponse.__init__(self, **kwargs)
self.custom_reponse_values = values

def encode(self):
# Implement encoding logic
def decode(self, data):
# Implement decoding logic
```
3. Register with ModbusSlaveContext,
if your request has to access some values from the data-store.
```store = ModbusSlaveContext(...)
store.register(CustomModbusRequest.function_code, 'dummy_context_name')
```
4. Pass CustomModbusRequest class as argument to Start<protocol>Server
```
StartTcpServer(..., custom_functions=[CustomModbusRequest]..)
```

"""
# --------------------------------------------------------------------------- #
# import the various server implementations
# --------------------------------------------------------------------------- #
from pymodbus.server.sync import StartTcpServer

from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
from custom_message import CustomModbusRequest

# --------------------------------------------------------------------------- #
# configure the service logging
# --------------------------------------------------------------------------- #
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 run_server():
store = ModbusSlaveContext(
di=ModbusSequentialDataBlock(0, [17] * 100),
co=ModbusSequentialDataBlock(0, [17] * 100),
hr=ModbusSequentialDataBlock(0, [17] * 100),
ir=ModbusSequentialDataBlock(0, [17] * 100))

store.register(CustomModbusRequest.function_code, 'cm',
ModbusSequentialDataBlock(0, [17] * 100))
context = ModbusServerContext(slaves=store, single=True)

# ----------------------------------------------------------------------- #
# initialize the server information
# ----------------------------------------------------------------------- #
# If you don't set this or any fields, they are defaulted to empty strings.
# ----------------------------------------------------------------------- #
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 = '2.1.0'

# ----------------------------------------------------------------------- #
# run the server you want
# ----------------------------------------------------------------------- #
# Tcp:
StartTcpServer(context, identity=identity, address=("localhost", 5020),
custom_functions=[CustomModbusRequest])


if __name__ == "__main__":
run_server()

8 changes: 8 additions & 0 deletions pymodbus/client/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,14 @@ def _dump(self, data, direction):
self._logger.debug(hexlify_packets(data))
self._logger.exception(e)

def register(self, function):
"""
Registers a function and sub function class with the decoder
:param function: Custom function class to register
:return:
"""
self.framer.decoder.register(function)

def __str__(self):
""" Builds a string representation of the connection

Expand Down
11 changes: 11 additions & 0 deletions pymodbus/datastore/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,17 @@ def setValues(self, fx, address, values):
_logger.debug("setValues[%d] %d:%d" % (fx, address, len(values)))
self.store[self.decode(fx)].setValues(address, values)

def register(self, fc, fx, datablock=None):
"""
Registers a datablock with the slave context
:param fc: function code (int)
:param fx: string representation of function code (e.g 'cf' )
:param datablock: datablock to associate with this function code
:return:
"""
self.store[fx] = datablock or ModbusSequentialDataBlock.create()
self._IModbusSlaveContext__fx_mapper[fc] = fx


class ModbusServerContext(object):
''' This represents a master collection of slave contexts.
Expand Down
Loading