Skip to content

Commit 0693806

Browse files
author
Galen Collins
committed
Fixes #42
1 parent 8c432d6 commit 0693806

File tree

7 files changed

+96
-40
lines changed

7 files changed

+96
-40
lines changed

pymodbus/constants.py

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -81,20 +81,28 @@ class Defaults(Singleton):
8181
8282
Indicates if the slave datastore should use indexing at 0 or 1.
8383
Mor about this can be read in section 4.4 of the modbus specification.
84+
85+
.. attribute:: IgnoreMissingSlaves
86+
87+
In case a request is made to a missing slave, this defines if an error
88+
should be returned or simply ignored. This is useful for the case of a
89+
serial server emulater where a request to a non-existant slave on a bus
90+
will never respond. The client in this case will simply timeout.
8491
'''
85-
Port = 502
86-
Retries = 3
87-
RetryOnEmpty = False
88-
Timeout = 3
89-
Reconnects = 0
90-
TransactionId = 0
91-
ProtocolId = 0
92-
UnitId = 0x00
93-
Baudrate = 19200
94-
Parity = 'N'
95-
Bytesize = 8
96-
Stopbits = 1
97-
ZeroMode = False
92+
Port = 502
93+
Retries = 3
94+
RetryOnEmpty = False
95+
Timeout = 3
96+
Reconnects = 0
97+
TransactionId = 0
98+
ProtocolId = 0
99+
UnitId = 0x00
100+
Baudrate = 19200
101+
Parity = 'N'
102+
Bytesize = 8
103+
Stopbits = 1
104+
ZeroMode = False
105+
IgnoreMissingSlaves = False
98106

99107

100108
class ModbusStatus(Singleton):

pymodbus/datastore/context.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from pymodbus.exceptions import ParameterException
1+
from pymodbus.exceptions import NoSuchSlaveException
22
from pymodbus.interfaces import IModbusSlaveContext
33
from pymodbus.datastore.store import ModbusSequentialDataBlock
44
from pymodbus.constants import Defaults
@@ -129,7 +129,7 @@ def __setitem__(self, slave, context):
129129
if self.single: slave = Defaults.UnitId
130130
if 0xf7 >= slave >= 0x00:
131131
self.__slaves[slave] = context
132-
else: raise ParameterException('slave index out of range')
132+
else: raise NoSuchSlaveException('slave index[%d] out of range' % slave)
133133

134134
def __delitem__(self, slave):
135135
''' Wrapper used to access the slave context
@@ -138,7 +138,7 @@ def __delitem__(self, slave):
138138
'''
139139
if not self.single and (0xf7 >= slave >= 0x00):
140140
del self.__slaves[slave]
141-
else: raise ParameterException('slave index out of range')
141+
else: raise NoSuchSlaveException('slave index[%d] out of range' % slave)
142142

143143
def __getitem__(self, slave):
144144
''' Used to get access to a slave context
@@ -149,4 +149,4 @@ def __getitem__(self, slave):
149149
if self.single: slave = Defaults.UnitId
150150
if slave in self.__slaves:
151151
return self.__slaves.get(slave)
152-
else: raise ParameterException("slave does not exist, or is out of range")
152+
else: raise NoSuchSlaveException('slave index[%d] out of range' % slave)

pymodbus/exceptions.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class ModbusException(Exception):
1111

1212
def __init__(self, string):
1313
''' Initialize the exception
14+
1415
:param string: The message to append to the error
1516
'''
1617
self.string = string
@@ -24,6 +25,7 @@ class ModbusIOException(ModbusException):
2425

2526
def __init__(self, string=""):
2627
''' Initialize the exception
28+
2729
:param string: The message to append to the error
2830
'''
2931
message = "[Input/Output] %s" % string
@@ -35,17 +37,32 @@ class ParameterException(ModbusException):
3537

3638
def __init__(self, string=""):
3739
''' Initialize the exception
40+
3841
:param string: The message to append to the error
3942
'''
4043
message = "[Invalid Paramter] %s" % string
4144
ModbusException.__init__(self, message)
4245

4346

47+
class NoSuchSlaveException(ModbusException):
48+
''' Error resulting from making a request to a slave
49+
that does not exist '''
50+
51+
def __init__(self, string=""):
52+
''' Initialize the exception
53+
54+
:param string: The message to append to the error
55+
'''
56+
message = "[No Such Slave] %s" % string
57+
ModbusException.__init__(self, message)
58+
59+
4460
class NotImplementedException(ModbusException):
4561
''' Error resulting from not implemented function '''
4662

4763
def __init__(self, string=""):
4864
''' Initialize the exception
65+
4966
:param string: The message to append to the error
5067
'''
5168
message = "[Not Implemented] %s" % string
@@ -57,6 +74,7 @@ class ConnectionException(ModbusException):
5774

5875
def __init__(self, string=""):
5976
''' Initialize the exception
77+
6078
:param string: The message to append to the error
6179
'''
6280
message = "[Connection] %s" % string
@@ -68,5 +86,5 @@ def __init__(self, string=""):
6886
__all__ = [
6987
"ModbusException", "ModbusIOException",
7088
"ParameterException", "NotImplementedException",
71-
"ConnectionException",
89+
"ConnectionException", "NoSuchSlaveException",
7290
]

pymodbus/server/async.py

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ def _execute(self, request):
6565
try:
6666
context = self.factory.store[request.unit_id]
6767
response = request.execute(context)
68+
except NoSuchSlaveException, ex:
69+
_logger.debug("requested slave does not exist: %s; %s", ex, traceback.format_exc() )
70+
if self.factory.ignore_missing_slaves:
71+
return # the client will simply timeout waiting for a response
72+
response = request.doException(merror.GatewayNoResponse)
6873
except Exception, ex:
6974
_logger.debug("Datastore unable to fulfill request: %s" % ex)
7075
response = request.doException(merror.SlaveFailure)
@@ -96,7 +101,7 @@ class ModbusServerFactory(ServerFactory):
96101

97102
protocol = ModbusTcpProtocol
98103

99-
def __init__(self, store, framer=None, identity=None):
104+
def __init__(self, store, framer=None, identity=None, **kwargs):
100105
''' Overloaded initializer for the modbus factory
101106
102107
If the identify structure is not passed in, the ModbusControlBlock
@@ -105,13 +110,14 @@ def __init__(self, store, framer=None, identity=None):
105110
:param store: The ModbusServerContext datastore
106111
:param framer: The framer strategy to use
107112
:param identity: An optional identify structure
108-
113+
:param ignore_missing_slaves: True to not send errors on a request to a missing slave
109114
'''
110115
self.decoder = ServerDecoder()
111116
self.framer = framer or ModbusSocketFramer
112117
self.store = store or ModbusServerContext()
113118
self.control = ModbusControlBlock()
114119
self.access = ModbusAccessControl()
120+
self.ignore_missing_slaves = kwargs.get('ignore_missing_slaves', Defaults.IgnoreMissingSlaves)
115121

116122
if isinstance(identity, ModbusDeviceIdentification):
117123
self.control.Identity.update(identity)
@@ -123,7 +129,7 @@ def __init__(self, store, framer=None, identity=None):
123129
class ModbusUdpProtocol(protocol.DatagramProtocol):
124130
''' Implements a modbus udp server in twisted '''
125131

126-
def __init__(self, store, framer=None, identity=None):
132+
def __init__(self, store, framer=None, identity=None, **kwargs):
127133
''' Overloaded initializer for the modbus factory
128134
129135
If the identify structure is not passed in, the ModbusControlBlock
@@ -132,13 +138,14 @@ def __init__(self, store, framer=None, identity=None):
132138
:param store: The ModbusServerContext datastore
133139
:param framer: The framer strategy to use
134140
:param identity: An optional identify structure
135-
141+
:param ignore_missing_slaves: True to not send errors on a request to a missing slave
136142
'''
137143
framer = framer or ModbusSocketFramer
138144
self.framer = framer(decoder=ServerDecoder())
139145
self.store = store or ModbusServerContext()
140146
self.control = ModbusControlBlock()
141147
self.access = ModbusAccessControl()
148+
self.ignore_missing_slaves = kwargs.get('ignore_missing_slaves', Defaults.IgnoreMissingSlaves)
142149

143150
if isinstance(identity, ModbusDeviceIdentification):
144151
self.control.Identity.update(identity)
@@ -163,6 +170,11 @@ def _execute(self, request, addr):
163170
try:
164171
context = self.store[request.unit_id]
165172
response = request.execute(context)
173+
except NoSuchSlaveException, ex:
174+
_logger.debug("requested slave does not exist: %s; %s", ex, traceback.format_exc() )
175+
if self.ignore_missing_slaves:
176+
return # the client will simply timeout waiting for a response
177+
response = request.doException(merror.GatewayNoResponse)
166178
except Exception, ex:
167179
_logger.debug("Datastore unable to fulfill request: %s" % ex)
168180
response = request.doException(merror.SlaveFailure)
@@ -187,38 +199,40 @@ def _send(self, message, addr):
187199
#---------------------------------------------------------------------------#
188200
# Starting Factories
189201
#---------------------------------------------------------------------------#
190-
def StartTcpServer(context, identity=None, address=None, console=False):
202+
def StartTcpServer(context, identity=None, address=None, console=False, **kwargs):
191203
''' Helper method to start the Modbus Async TCP server
192204
193205
:param context: The server data context
194206
:param identify: The server identity to use (default empty)
195207
:param address: An optional (interface, port) to bind to.
196208
:param console: A flag indicating if you want the debug console
209+
:param ignore_missing_slaves: True to not send errors on a request to a missing slave
197210
'''
198211
from twisted.internet import reactor
199212

200213
address = address or ("", Defaults.Port)
201214
framer = ModbusSocketFramer
202-
factory = ModbusServerFactory(context, framer, identity)
215+
factory = ModbusServerFactory(context, framer, identity, **kwargs)
203216
if console: InstallManagementConsole({'factory': factory})
204217

205218
_logger.info("Starting Modbus TCP Server on %s:%s" % address)
206219
reactor.listenTCP(address[1], factory, interface=address[0])
207220
reactor.run()
208221

209222

210-
def StartUdpServer(context, identity=None, address=None):
223+
def StartUdpServer(context, identity=None, address=None, **kwargs):
211224
''' Helper method to start the Modbus Async Udp server
212225
213226
:param context: The server data context
214227
:param identify: The server identity to use (default empty)
215228
:param address: An optional (interface, port) to bind to.
229+
:param ignore_missing_slaves: True to not send errors on a request to a missing slave
216230
'''
217231
from twisted.internet import reactor
218232

219233
address = address or ("", Defaults.Port)
220234
framer = ModbusSocketFramer
221-
server = ModbusUdpProtocol(context, framer, identity)
235+
server = ModbusUdpProtocol(context, framer, identity, **kwargs)
222236

223237
_logger.info("Starting Modbus UDP Server on %s:%s" % address)
224238
reactor.listenUDP(address[1], server, interface=address[0])
@@ -235,6 +249,7 @@ def StartSerialServer(context, identity=None,
235249
:param port: The serial port to attach to
236250
:param baudrate: The baud rate to use for the serial device
237251
:param console: A flag indicating if you want the debug console
252+
:param ignore_missing_slaves: True to not send errors on a request to a missing slave
238253
'''
239254
from twisted.internet import reactor
240255
from twisted.internet.serialport import SerialPort
@@ -244,7 +259,7 @@ def StartSerialServer(context, identity=None,
244259
console = kwargs.get('console', False)
245260

246261
_logger.info("Starting Modbus Serial Server on %s" % port)
247-
factory = ModbusServerFactory(context, framer, identity)
262+
factory = ModbusServerFactory(context, framer, identity, **kwargs)
248263
if console: InstallManagementConsole({'factory': factory})
249264

250265
protocol = factory.buildProtocol(None)

0 commit comments

Comments
 (0)