Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
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
1 change: 1 addition & 0 deletions pymodbus/client/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def __init__(self, framer, **kwargs):
self.transaction = FifoTransactionManager(self, **kwargs)
self._debug = False
self._debugfd = None
self.broadcast_enable = kwargs.get('broadcast_enable', Defaults.broadcast_enable)

# ----------------------------------------------------------------------- #
# Client interface
Expand Down
10 changes: 10 additions & 0 deletions pymodbus/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,15 @@ class Defaults(Singleton):
should be returned or simply ignored. This is useful for the case of a
serial server emulater where a request to a non-existant slave on a bus
will never respond. The client in this case will simply timeout.
.. attribute:: broadcast_enable
When False unit_id 0 will be treated as any other unit_id. When True and
the unit_id is 0 the server will execute all requests on all server
contexts and not respond and the client will skip trying to receive a
response. Default value False does not conform to Modbus spec but maintains
legacy behavior for existing pymodbus users.
'''
Port = 502
Retries = 3
Expand All @@ -104,6 +113,7 @@ class Defaults(Singleton):
ZeroMode = False
IgnoreMissingSlaves = False
ReadSize = 1024
broadcast_enable = False

class ModbusStatus(Singleton):
'''
Expand Down
45 changes: 36 additions & 9 deletions pymodbus/server/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,16 @@ def execute(self, request):

:param request: The decoded request message
"""
broadcast = False
try:
context = self.server.context[request.unit_id]
response = request.execute(context)
if self.server.broadcast_enable and request.unit_id == 0:
broadcast = True
# if broadcasting then execute on all slave contexts, note response will be ignored
for unit_id in self.server.context.slaves():
response = request.execute(self.server.context[unit_id])
else:
context = self.server.context[request.unit_id]
response = request.execute(context)
except NoSuchSlaveException as ex:
_logger.debug("requested slave does "
"not exist: %s" % request.unit_id )
Expand All @@ -71,9 +78,11 @@ def execute(self, request):
_logger.debug("Datastore unable to fulfill request: "
"%s; %s", ex, traceback.format_exc())
response = request.doException(merror.SlaveFailure)
response.transaction_id = request.transaction_id
response.unit_id = request.unit_id
self.send(response)
# no response when broadcasting
if not broadcast:
response.transaction_id = request.transaction_id
response.unit_id = request.unit_id
self.send(response)

# ----------------------------------------------------------------------- #
# Base class implementations
Expand Down Expand Up @@ -107,6 +116,12 @@ def handle(self):
data = self.request.recv(1024)
if data:
units = self.server.context.slaves()
if not isinstance(units, (list, tuple)):
units = [units]
# if broadcast is enabled make sure to process requests to address 0
if self.server.broadcast_enable:
if 0 not in units:
units.append(0)
single = self.server.context.single
self.framer.processIncomingPacket(data, self.execute,
units, single=single)
Expand Down Expand Up @@ -291,8 +306,10 @@ def __init__(self, context, framer=None, identity=None,
ModbusConnectedRequestHandler
:param allow_reuse_address: Whether the server will allow the
reuse of an address.
:param ignore_missing_slaves: True to not send errors on a request
to a missing slave
:param ignore_missing_slaves: True to not send errors on a request
to a missing slave
:param broadcast_enable: True to treat unit_id 0 as broadcast address,
False to treat 0 as any other unit_id
"""
self.threads = []
self.allow_reuse_address = allow_reuse_address
Expand All @@ -304,6 +321,8 @@ def __init__(self, context, framer=None, identity=None,
self.handler = handler or ModbusConnectedRequestHandler
self.ignore_missing_slaves = kwargs.get('ignore_missing_slaves',
Defaults.IgnoreMissingSlaves)
self.broadcast_enable = kwargs.get('broadcast_enable',
Defaults.broadcast_enable)

if isinstance(identity, ModbusDeviceIdentification):
self.control.Identity.update(identity)
Expand Down Expand Up @@ -361,7 +380,9 @@ def __init__(self, context, framer=None, identity=None, address=None,
:param handler: A handler for each client session; default is
ModbusDisonnectedRequestHandler
:param ignore_missing_slaves: True to not send errors on a request
to a missing slave
to a missing slave
:param broadcast_enable: True to treat unit_id 0 as broadcast address,
False to treat 0 as any other unit_id
"""
self.threads = []
self.decoder = ServerDecoder()
Expand All @@ -372,6 +393,8 @@ def __init__(self, context, framer=None, identity=None, address=None,
self.handler = handler or ModbusDisconnectedRequestHandler
self.ignore_missing_slaves = kwargs.get('ignore_missing_slaves',
Defaults.IgnoreMissingSlaves)
self.broadcast_enable = kwargs.get('broadcast_enable',
Defaults.broadcast_enable)

if isinstance(identity, ModbusDeviceIdentification):
self.control.Identity.update(identity)
Expand Down Expand Up @@ -426,7 +449,9 @@ def __init__(self, context, framer=None, identity=None, **kwargs):
:param baudrate: The baud rate to use for the serial device
:param timeout: The timeout to use for the serial device
:param ignore_missing_slaves: True to not send errors on a request
to a missing slave
to a missing slave
:param broadcast_enable: True to treat unit_id 0 as broadcast address,
False to treat 0 as any other unit_id
"""
self.threads = []
self.decoder = ServerDecoder()
Expand All @@ -445,6 +470,8 @@ def __init__(self, context, framer=None, identity=None, **kwargs):
self.timeout = kwargs.get('timeout', Defaults.Timeout)
self.ignore_missing_slaves = kwargs.get('ignore_missing_slaves',
Defaults.IgnoreMissingSlaves)
self.broadcast_enable = kwargs.get('broadcast_enable',
Defaults.broadcast_enable)
self.socket = None
if self._connect():
self.is_running = True
Expand Down
149 changes: 80 additions & 69 deletions pymodbus/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,70 +118,74 @@ def execute(self, request):
_logger.debug("Clearing current Frame : - {}".format(_buffer))
self.client.framer.resetFrame()

expected_response_length = None
if not isinstance(self.client.framer, ModbusSocketFramer):
if hasattr(request, "get_response_pdu_size"):
response_pdu_size = request.get_response_pdu_size()
if isinstance(self.client.framer, ModbusAsciiFramer):
response_pdu_size = response_pdu_size * 2
if response_pdu_size:
expected_response_length = self._calculate_response_length(response_pdu_size)
if request.unit_id in self._no_response_devices:
full = True
if request.unit_id == 0 and self.client.broadcast_enable:
response, last_exception = self._transact(request, None)
response = b'Broadcast write sent - no response expected'
else:
full = False
c_str = str(self.client)
if "modbusudpclient" in c_str.lower().strip():
full = True
if not expected_response_length:
expected_response_length = Defaults.ReadSize
response, last_exception = self._transact(request,
expected_response_length,
full=full
)
if not response and (
request.unit_id not in self._no_response_devices):
self._no_response_devices.append(request.unit_id)
elif request.unit_id in self._no_response_devices and response:
self._no_response_devices.remove(request.unit_id)
if not response and self.retry_on_empty and retries:
while retries > 0:
if hasattr(self.client, "state"):
_logger.debug("RESETTING Transaction state to "
"'IDLE' for retry")
self.client.state = ModbusTransactionState.IDLE
_logger.debug("Retry on empty - {}".format(retries))
response, last_exception = self._transact(
request,
expected_response_length
)
if not response:
retries -= 1
continue
# Remove entry
self._no_response_devices.remove(request.unit_id)
break
addTransaction = partial(self.addTransaction,
tid=request.transaction_id)
self.client.framer.processIncomingPacket(response,
addTransaction,
request.unit_id)
response = self.getTransaction(request.transaction_id)
if not response:
if len(self.transactions):
response = self.getTransaction(tid=0)
expected_response_length = None
if not isinstance(self.client.framer, ModbusSocketFramer):
if hasattr(request, "get_response_pdu_size"):
response_pdu_size = request.get_response_pdu_size()
if isinstance(self.client.framer, ModbusAsciiFramer):
response_pdu_size = response_pdu_size * 2
if response_pdu_size:
expected_response_length = self._calculate_response_length(response_pdu_size)
if request.unit_id in self._no_response_devices:
full = True
else:
last_exception = last_exception or (
"No Response received from the remote unit"
"/Unable to decode response")
response = ModbusIOException(last_exception,
request.function_code)
if hasattr(self.client, "state"):
_logger.debug("Changing transaction state from "
"'PROCESSING REPLY' to "
"'TRANSACTION_COMPLETE'")
self.client.state = (
ModbusTransactionState.TRANSACTION_COMPLETE)
full = False
c_str = str(self.client)
if "modbusudpclient" in c_str.lower().strip():
full = True
if not expected_response_length:
expected_response_length = Defaults.ReadSize
response, last_exception = self._transact(request,
expected_response_length,
full=full
)
if not response and (
request.unit_id not in self._no_response_devices):
self._no_response_devices.append(request.unit_id)
elif request.unit_id in self._no_response_devices and response:
self._no_response_devices.remove(request.unit_id)
if not response and self.retry_on_empty and retries:
while retries > 0:
if hasattr(self.client, "state"):
_logger.debug("RESETTING Transaction state to "
"'IDLE' for retry")
self.client.state = ModbusTransactionState.IDLE
_logger.debug("Retry on empty - {}".format(retries))
response, last_exception = self._transact(
request,
expected_response_length
)
if not response:
retries -= 1
continue
# Remove entry
self._no_response_devices.remove(request.unit_id)
break
addTransaction = partial(self.addTransaction,
tid=request.transaction_id)
self.client.framer.processIncomingPacket(response,
addTransaction,
request.unit_id)
response = self.getTransaction(request.transaction_id)
if not response:
if len(self.transactions):
response = self.getTransaction(tid=0)
else:
last_exception = last_exception or (
"No Response received from the remote unit"
"/Unable to decode response")
response = ModbusIOException(last_exception,
request.function_code)
if hasattr(self.client, "state"):
_logger.debug("Changing transaction state from "
"'PROCESSING REPLY' to "
"'TRANSACTION_COMPLETE'")
self.client.state = (
ModbusTransactionState.TRANSACTION_COMPLETE)
return response
except ModbusIOException as ex:
# Handle decode errors in processIncomingPacket method
Expand All @@ -205,13 +209,20 @@ def _transact(self, packet, response_length, full=False):
if _logger.isEnabledFor(logging.DEBUG):
_logger.debug("SEND: " + hexlify_packets(packet))
size = self._send(packet)
if size:
_logger.debug("Changing transaction state from 'SENDING' "
"to 'WAITING FOR REPLY'")
self.client.state = ModbusTransactionState.WAITING_FOR_REPLY
result = self._recv(response_length, full)
if _logger.isEnabledFor(logging.DEBUG):
_logger.debug("RECV: " + hexlify_packets(result))
if response_length is not None:
if size:
_logger.debug("Changing transaction state from 'SENDING' "
"to 'WAITING FOR REPLY'")
self.client.state = ModbusTransactionState.WAITING_FOR_REPLY
result = self._recv(response_length, full)
if _logger.isEnabledFor(logging.DEBUG):
_logger.debug("RECV: " + hexlify_packets(result))
else:
if size:
_logger.debug("Changing transaction state from 'SENDING' "
"to 'TRANSACTION_COMPLETE'")
self.client.state = ModbusTransactionState.TRANSACTION_COMPLETE
result = b''
except (socket.error, ModbusIOException,
InvalidMessageReceivedException) as msg:
self.client.close()
Expand Down