pymodbus 0.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. pymodbus-0.9.0/PKG-INFO +28 -0
  2. pymodbus-0.9.0/pymodbus/__init__.py +32 -0
  3. pymodbus-0.9.0/pymodbus/bit_read_message.py +228 -0
  4. pymodbus-0.9.0/pymodbus/bit_write_message.py +251 -0
  5. pymodbus-0.9.0/pymodbus/client/__init__.py +0 -0
  6. pymodbus-0.9.0/pymodbus/client/async.py +140 -0
  7. pymodbus-0.9.0/pymodbus/client/common.py +133 -0
  8. pymodbus-0.9.0/pymodbus/client/sync.py +384 -0
  9. pymodbus-0.9.0/pymodbus/constants.py +126 -0
  10. pymodbus-0.9.0/pymodbus/datastore/__init__.py +12 -0
  11. pymodbus-0.9.0/pymodbus/datastore/context.py +129 -0
  12. pymodbus-0.9.0/pymodbus/datastore/database.py +174 -0
  13. pymodbus-0.9.0/pymodbus/datastore/modredis.py +244 -0
  14. pymodbus-0.9.0/pymodbus/datastore/remote.py +100 -0
  15. pymodbus-0.9.0/pymodbus/datastore/store.py +230 -0
  16. pymodbus-0.9.0/pymodbus/device.py +450 -0
  17. pymodbus-0.9.0/pymodbus/diag_message.py +636 -0
  18. pymodbus-0.9.0/pymodbus/events.py +192 -0
  19. pymodbus-0.9.0/pymodbus/exceptions.py +67 -0
  20. pymodbus-0.9.0/pymodbus/factory.py +150 -0
  21. pymodbus-0.9.0/pymodbus/file_message.py +124 -0
  22. pymodbus-0.9.0/pymodbus/interfaces.py +201 -0
  23. pymodbus-0.9.0/pymodbus/internal/__init__.py +0 -0
  24. pymodbus-0.9.0/pymodbus/internal/ptwisted.py +45 -0
  25. pymodbus-0.9.0/pymodbus/other_message.py +430 -0
  26. pymodbus-0.9.0/pymodbus/pdu.py +197 -0
  27. pymodbus-0.9.0/pymodbus/register_read_message.py +339 -0
  28. pymodbus-0.9.0/pymodbus/register_write_message.py +229 -0
  29. pymodbus-0.9.0/pymodbus/server/__init__.py +2 -0
  30. pymodbus-0.9.0/pymodbus/server/async.py +233 -0
  31. pymodbus-0.9.0/pymodbus/server/sync.py +320 -0
  32. pymodbus-0.9.0/pymodbus/transaction.py +746 -0
  33. pymodbus-0.9.0/pymodbus/utilities.py +190 -0
  34. pymodbus-0.9.0/pymodbus/version.py +44 -0
  35. pymodbus-0.9.0/pymodbus.egg-info/PKG-INFO +28 -0
  36. pymodbus-0.9.0/pymodbus.egg-info/SOURCES.txt +65 -0
  37. pymodbus-0.9.0/pymodbus.egg-info/dependency_links.txt +1 -0
  38. pymodbus-0.9.0/pymodbus.egg-info/requires.txt +3 -0
  39. pymodbus-0.9.0/pymodbus.egg-info/top_level.txt +2 -0
  40. pymodbus-0.9.0/pymodbus.egg-info/zip-safe +1 -0
  41. pymodbus-0.9.0/setup.cfg +21 -0
  42. pymodbus-0.9.0/setup.py +98 -0
  43. pymodbus-0.9.0/test/__init__.py +0 -0
  44. pymodbus-0.9.0/test/modbus_mocks.py +34 -0
  45. pymodbus-0.9.0/test/test_all_messages.py +74 -0
  46. pymodbus-0.9.0/test/test_bit_read_messages.py +124 -0
  47. pymodbus-0.9.0/test/test_bit_write_messages.py +113 -0
  48. pymodbus-0.9.0/test/test_client.py +25 -0
  49. pymodbus-0.9.0/test/test_client_common.py +49 -0
  50. pymodbus-0.9.0/test/test_datastore.py +126 -0
  51. pymodbus-0.9.0/test/test_device.py +219 -0
  52. pymodbus-0.9.0/test/test_diag_messages.py +130 -0
  53. pymodbus-0.9.0/test/test_events.py +77 -0
  54. pymodbus-0.9.0/test/test_exceptions.py +38 -0
  55. pymodbus-0.9.0/test/test_factory.py +134 -0
  56. pymodbus-0.9.0/test/test_file_message.py +87 -0
  57. pymodbus-0.9.0/test/test_interfaces.py +61 -0
  58. pymodbus-0.9.0/test/test_other_messages.py +98 -0
  59. pymodbus-0.9.0/test/test_pdu.py +82 -0
  60. pymodbus-0.9.0/test/test_register_read_messages.py +166 -0
  61. pymodbus-0.9.0/test/test_register_write_messages.py +98 -0
  62. pymodbus-0.9.0/test/test_remote_datastore.py +69 -0
  63. pymodbus-0.9.0/test/test_server_context.py +86 -0
  64. pymodbus-0.9.0/test/test_transaction.py +344 -0
  65. pymodbus-0.9.0/test/test_utilities.py +88 -0
  66. pymodbus-0.9.0/test/test_version.py +27 -0
@@ -0,0 +1,28 @@
1
+ Metadata-Version: 1.0
2
+ Name: pymodbus
3
+ Version: 0.9.0
4
+ Summary: A fully featured modbus protocol stack in python
5
+ Home-page: http://code.google.com/p/pymodbus/
6
+ Author: Galen Collins
7
+ Author-email: bashwork@gmail.com
8
+ License: BSD
9
+ Description:
10
+ Pymodbus aims to be a fully implemented modbus protocol stack implemented
11
+ using twisted. Its orignal goal was to allow simulation of thousands of
12
+ modbus devices on a single machine for monitoring software testing.
13
+
14
+ Keywords: modbus,twisted
15
+ Platform: Linux
16
+ Platform: Mac OS X
17
+ Platform: Win
18
+ Classifier: Development Status :: 4 - Beta
19
+ Classifier: Environment :: Console
20
+ Classifier: Environment :: X11 Applications :: GTK
21
+ Classifier: Framework :: Twisted
22
+ Classifier: Intended Audience :: Developers
23
+ Classifier: License :: OSI Approved :: BSD License
24
+ Classifier: Operating System :: POSIX :: Linux
25
+ Classifier: Operating System :: Unix
26
+ Classifier: Programming Language :: Python
27
+ Classifier: Topic :: System :: Networking
28
+ Classifier: Topic :: Utilities
@@ -0,0 +1,32 @@
1
+ """
2
+ Pymodbus: Modbus Protocol Implementation
3
+ -----------------------------------------
4
+
5
+ TwistedModbus is built on top of the code developed by:
6
+
7
+ Copyright (c) 2001-2005 S.W.A.C. GmbH, Germany.
8
+ Copyright (c) 2001-2005 S.W.A.C. Bohemia s.r.o., Czech Republic.
9
+ Hynek Petrak <hynek@swac.cz>
10
+
11
+ Released under the the BSD license
12
+ """
13
+
14
+ from pymodbus.version import _version
15
+ __version__ = _version.short()
16
+ __author__ = 'Galen Collins'
17
+
18
+
19
+ #---------------------------------------------------------------------------#
20
+ # Block unhandled logging
21
+ #---------------------------------------------------------------------------#
22
+ import logging
23
+ try:
24
+ from logging import NullHandler
25
+ except ImportError:
26
+ class NullHandler(logging.Handler):
27
+ def emit(self, record):
28
+ pass
29
+
30
+ h = NullHandler()
31
+ logging.getLogger(__name__).addHandler(h)
32
+
@@ -0,0 +1,228 @@
1
+ """
2
+ Bit Reading Request/Response messages
3
+ --------------------------------------
4
+
5
+ """
6
+ import struct
7
+ from pymodbus.pdu import ModbusRequest
8
+ from pymodbus.pdu import ModbusResponse
9
+ from pymodbus.pdu import ModbusExceptions as merror
10
+ from pymodbus.utilities import pack_bitstring, unpack_bitstring
11
+
12
+ class ReadBitsRequestBase(ModbusRequest):
13
+ ''' Base class for Messages Requesting bit values '''
14
+
15
+ _rtu_frame_size = 8
16
+
17
+ def __init__(self, address, count, **kwargs):
18
+ ''' Initializes the read request data
19
+
20
+ :param address: The start address to read from
21
+ :param count: The number of bits after 'address' to read
22
+ '''
23
+ ModbusRequest.__init__(self, **kwargs)
24
+ self.address = address
25
+ self.count = count
26
+
27
+ def encode(self):
28
+ ''' Encodes a request pdu
29
+
30
+ :returns: The encoded pdu
31
+ '''
32
+ return struct.pack('>HH', self.address, self.count)
33
+
34
+ def decode(self, data):
35
+ ''' Decodes a request pdu
36
+
37
+ :param data: The packet data to decode
38
+ '''
39
+ self.address, self.count = struct.unpack('>HH', data)
40
+
41
+ def __str__(self):
42
+ ''' Returns a string representation of the instance
43
+
44
+ :returns: A string representation of the instance
45
+ '''
46
+ return "ReadBitRequest(%d,%d)" % (self.address, self.count)
47
+
48
+ class ReadBitsResponseBase(ModbusResponse):
49
+ ''' Base class for Messages responding to bit-reading values '''
50
+
51
+ _rtu_byte_count_pos = 2
52
+
53
+ def __init__(self, values, **kwargs):
54
+ ''' Initializes a new instance
55
+
56
+ :param values: The requested values to be returned
57
+ '''
58
+ ModbusResponse.__init__(self, **kwargs)
59
+ self.bits = values or []
60
+
61
+ def encode(self):
62
+ ''' Encodes response pdu
63
+
64
+ :returns: The encoded packet message
65
+ '''
66
+ result = pack_bitstring(self.bits)
67
+ packet = struct.pack(">B", len(result)) + result
68
+ return packet
69
+
70
+ def decode(self, data):
71
+ ''' Decodes response pdu
72
+
73
+ :param data: The packet data to decode
74
+ '''
75
+ self.byte_count = struct.unpack(">B", data[0])[0]
76
+ self.bits = unpack_bitstring(data[1:])
77
+
78
+ def setBit(self, address, value=1):
79
+ ''' Helper function to set the specified bit
80
+
81
+ :param address: The bit to set
82
+ :param value: The value to set the bit to
83
+ '''
84
+ self.bits[address] = (value != 0)
85
+
86
+ def resetBit(self, address):
87
+ ''' Helper function to set the specified bit to 0
88
+
89
+ :param address: The bit to reset
90
+ '''
91
+ self.setBit(address, 0)
92
+
93
+ def getBit(self, address):
94
+ ''' Helper function to get the specified bit's value
95
+
96
+ :param address: The bit to query
97
+ :returns: The value of the requested bit
98
+ '''
99
+ return self.bits[address]
100
+
101
+ def __str__(self):
102
+ ''' Returns a string representation of the instance
103
+
104
+ :returns: A string representation of the instance
105
+ '''
106
+ return "ReadBitResponse(%d)" % len(self.bits)
107
+
108
+ class ReadCoilsRequest(ReadBitsRequestBase):
109
+ '''
110
+ This function code is used to read from 1 to 2000(0x7d0) contiguous status
111
+ of coils in a remote device. The Request PDU specifies the starting
112
+ address, ie the address of the first coil specified, and the number of
113
+ coils. In the PDU Coils are addressed starting at zero. Therefore coils
114
+ numbered 1-16 are addressed as 0-15.
115
+ '''
116
+ function_code = 1
117
+
118
+ def __init__(self, address=None, count=None, **kwargs):
119
+ ''' Initializes a new instance
120
+
121
+ :param address: The address to start reading from
122
+ :param count: The number of bits to read
123
+ '''
124
+ ReadBitsRequestBase.__init__(self, address, count, **kwargs)
125
+
126
+ def execute(self, context):
127
+ ''' Run a read coils request against a datastore
128
+
129
+ Before running the request, we make sure that the request is in
130
+ the max valid range (0x001-0x7d0). Next we make sure that the
131
+ request is valid against the current datastore.
132
+
133
+ :param context: The datastore to request from
134
+ :returns: The initializes response message, exception message otherwise
135
+ '''
136
+ if not (1 <= self.count <= 0x7d0):
137
+ return self.doException(merror.IllegalValue)
138
+ if not context.validate(self.function_code, self.address, self.count):
139
+ return self.doException(merror.IllegalAddress)
140
+ values = context.getValues(self.function_code, self.address, self.count)
141
+ return ReadCoilsResponse(values)
142
+
143
+ class ReadCoilsResponse(ReadBitsResponseBase):
144
+ '''
145
+ The coils in the response message are packed as one coil per bit of
146
+ the data field. Status is indicated as 1= ON and 0= OFF. The LSB of the
147
+ first data byte contains the output addressed in the query. The other
148
+ coils follow toward the high order end of this byte, and from low order
149
+ to high order in subsequent bytes.
150
+
151
+ If the returned output quantity is not a multiple of eight, the
152
+ remaining bits in the final data byte will be padded with zeros
153
+ (toward the high order end of the byte). The Byte Count field specifies
154
+ the quantity of complete bytes of data.
155
+ '''
156
+ function_code = 1
157
+
158
+ def __init__(self, values=None, **kwargs):
159
+ ''' Intializes a new instance
160
+
161
+ :param values: The request values to respond with
162
+ '''
163
+ ReadBitsResponseBase.__init__(self, values, **kwargs)
164
+
165
+ class ReadDiscreteInputsRequest(ReadBitsRequestBase):
166
+ '''
167
+ This function code is used to read from 1 to 2000(0x7d0) contiguous status
168
+ of discrete inputs in a remote device. The Request PDU specifies the
169
+ starting address, ie the address of the first input specified, and the
170
+ number of inputs. In the PDU Discrete Inputs are addressed starting at
171
+ zero. Therefore Discrete inputs numbered 1-16 are addressed as 0-15.
172
+ '''
173
+ function_code = 2
174
+
175
+ def __init__(self, address=None, count=None, **kwargs):
176
+ ''' Intializes a new instance
177
+
178
+ :param address: The address to start reading from
179
+ :param count: The number of bits to read
180
+ '''
181
+ ReadBitsRequestBase.__init__(self, address, count, **kwargs)
182
+
183
+ def execute(self, context):
184
+ ''' Run a read discrete input request against a datastore
185
+
186
+ Before running the request, we make sure that the request is in
187
+ the max valid range (0x001-0x7d0). Next we make sure that the
188
+ request is valid against the current datastore.
189
+
190
+ :param context: The datastore to request from
191
+ :returns: The initializes response message, exception message otherwise
192
+ '''
193
+ if not (1 <= self.count <= 0x7d0):
194
+ return self.doException(merror.IllegalValue)
195
+ if not context.validate(self.function_code, self.address, self.count):
196
+ return self.doException(merror.IllegalAddress)
197
+ values = context.getValues(self.function_code, self.address, self.count)
198
+ return ReadDiscreteInputsResponse(values)
199
+
200
+ class ReadDiscreteInputsResponse(ReadBitsResponseBase):
201
+ '''
202
+ The discrete inputs in the response message are packed as one input per
203
+ bit of the data field. Status is indicated as 1= ON; 0= OFF. The LSB of
204
+ the first data byte contains the input addressed in the query. The other
205
+ inputs follow toward the high order end of this byte, and from low order
206
+ to high order in subsequent bytes.
207
+
208
+ If the returned input quantity is not a multiple of eight, the
209
+ remaining bits in the final data byte will be padded with zeros
210
+ (toward the high order end of the byte). The Byte Count field specifies
211
+ the quantity of complete bytes of data.
212
+ '''
213
+ function_code = 2
214
+
215
+ def __init__(self, values=None, **kwargs):
216
+ ''' Intializes a new instance
217
+
218
+ :param values: The request values to respond with
219
+ '''
220
+ ReadBitsResponseBase.__init__(self, values, **kwargs)
221
+
222
+ #---------------------------------------------------------------------------#
223
+ # Exported symbols
224
+ #---------------------------------------------------------------------------#
225
+ __all__ = [
226
+ "ReadCoilsRequest", "ReadCoilsResponse",
227
+ "ReadDiscreteInputsRequest", "ReadDiscreteInputsResponse",
228
+ ]
@@ -0,0 +1,251 @@
1
+ """
2
+ Bit Writing Request/Response
3
+ ------------------------------
4
+
5
+ TODO write mask request/response
6
+ """
7
+ import struct
8
+ from pymodbus.constants import ModbusStatus
9
+ from pymodbus.pdu import ModbusRequest
10
+ from pymodbus.pdu import ModbusResponse
11
+ from pymodbus.pdu import ModbusExceptions as merror
12
+ from pymodbus.exceptions import ParameterException
13
+ from pymodbus.utilities import pack_bitstring, unpack_bitstring
14
+
15
+ #---------------------------------------------------------------------------#
16
+ # Local Constants
17
+ #---------------------------------------------------------------------------#
18
+ # These are defined in the spec to turn a coil on/off
19
+ #---------------------------------------------------------------------------#
20
+ _turn_coil_on = struct.pack(">H", ModbusStatus.On)
21
+ _turn_coil_off = struct.pack(">H", ModbusStatus.Off)
22
+
23
+ class WriteSingleCoilRequest(ModbusRequest):
24
+ '''
25
+ This function code is used to write a single output to either ON or OFF
26
+ in a remote device.
27
+
28
+ The requested ON/OFF state is specified by a constant in the request
29
+ data field. A value of FF 00 hex requests the output to be ON. A value
30
+ of 00 00 requests it to be OFF. All other values are illegal and will
31
+ not affect the output.
32
+
33
+ The Request PDU specifies the address of the coil to be forced. Coils
34
+ are addressed starting at zero. Therefore coil numbered 1 is addressed
35
+ as 0. The requested ON/OFF state is specified by a constant in the Coil
36
+ Value field. A value of 0XFF00 requests the coil to be ON. A value of
37
+ 0X0000 requests the coil to be off. All other values are illegal and
38
+ will not affect the coil.
39
+ '''
40
+ function_code = 5
41
+ _rtu_frame_size = 8
42
+
43
+ def __init__(self, address=None, value=None, **kwargs):
44
+ ''' Initializes a new instance
45
+
46
+ :param address: The variable address to write
47
+ :param value: The value to write at address
48
+ '''
49
+ ModbusRequest.__init__(self, **kwargs)
50
+ self.address = address
51
+ self.value = True if value else False
52
+
53
+ def encode(self):
54
+ ''' Encodes write coil request
55
+
56
+ :returns: The byte encoded message
57
+ '''
58
+ result = struct.pack('>H', self.address)
59
+ result += _turn_coil_on if self.value else _turn_coil_off
60
+ return result
61
+
62
+ def decode(self, data):
63
+ ''' Decodes a write coil request
64
+
65
+ :param data: The packet data to decode
66
+ '''
67
+ self.address, value = struct.unpack('>HH', data)
68
+ self.value = True if value == ModbusStatus.On else False
69
+
70
+ def execute(self, context):
71
+ ''' Run a write coil request against a datastore
72
+
73
+ :param context: The datastore to request from
74
+ :returns: The populated response or exception message
75
+ '''
76
+ #if self.value not in [ModbusStatus.Off, ModbusStatus.On]:
77
+ # return self.doException(merror.IllegalValue)
78
+ if not context.validate(self.function_code, self.address, 1):
79
+ return self.doException(merror.IllegalAddress)
80
+
81
+ context.setValues(self.function_code, self.address, self.value)
82
+ values = context.getValues(self.function_code, self.address, 1)
83
+ return WriteSingleCoilResponse(self.address, values[0])
84
+
85
+ def __str__(self):
86
+ ''' Returns a string representation of the instance
87
+
88
+ :return: A string representation of the instance
89
+ '''
90
+ return "WriteCoilRequest(%d, %s) => " % (self.address, self.value)
91
+
92
+ class WriteSingleCoilResponse(ModbusResponse):
93
+ '''
94
+ The normal response is an echo of the request, returned after the coil
95
+ state has been written.
96
+ '''
97
+ function_code = 5
98
+ _rtu_frame_size = 8
99
+
100
+ def __init__(self, address=None, value=None, **kwargs):
101
+ ''' Initializes a new instance
102
+
103
+ :param address: The variable address written to
104
+ :param value: The value written at address
105
+ '''
106
+ ModbusResponse.__init__(self, **kwargs)
107
+ self.address = address
108
+ self.value = value
109
+
110
+ def encode(self):
111
+ ''' Encodes write coil response
112
+
113
+ :return: The byte encoded message
114
+ '''
115
+ result = struct.pack('>H', self.address)
116
+ result += _turn_coil_on if self.value else _turn_coil_off
117
+ return result
118
+
119
+ def decode(self, data):
120
+ ''' Decodes a write coil response
121
+
122
+ :param data: The packet data to decode
123
+ '''
124
+ self.address, value = struct.unpack('>HH', data)
125
+ self.value = (value == ModbusStatus.On)
126
+
127
+ def __str__(self):
128
+ ''' Returns a string representation of the instance
129
+
130
+ :returns: A string representation of the instance
131
+ '''
132
+ return "WriteCoilResponse(%d) => %d" % (self.address, self.value)
133
+
134
+ class WriteMultipleCoilsRequest(ModbusRequest):
135
+ '''
136
+ "This function code is used to force each coil in a sequence of coils to
137
+ either ON or OFF in a remote device. The Request PDU specifies the coil
138
+ references to be forced. Coils are addressed starting at zero. Therefore
139
+ coil numbered 1 is addressed as 0.
140
+
141
+ The requested ON/OFF states are specified by contents of the request
142
+ data field. A logical '1' in a bit position of the field requests the
143
+ corresponding output to be ON. A logical '0' requests it to be OFF."
144
+ '''
145
+ function_code = 15
146
+ _rtu_byte_count_pos = 6
147
+
148
+ def __init__(self, address=None, values=None, **kwargs):
149
+ ''' Initializes a new instance
150
+
151
+ :param address: The starting request address
152
+ :param values: The values to write
153
+ '''
154
+ ModbusRequest.__init__(self, **kwargs)
155
+ self.address = address
156
+ if not values: values = []
157
+ elif not hasattr(values, '__iter__'): values = [values]
158
+ self.values = values
159
+ self.byte_count = (len(self.values) + 7) / 8
160
+
161
+ def encode(self):
162
+ ''' Encodes write coils request
163
+
164
+ :returns: The byte encoded message
165
+ '''
166
+ count = len(self.values)
167
+ self.byte_count = (count + 7) / 8
168
+ packet = struct.pack('>HHB', self.address, count, self.byte_count)
169
+ packet += pack_bitstring(self.values)
170
+ return packet
171
+
172
+ def decode(self, data):
173
+ ''' Decodes a write coils request
174
+
175
+ :param data: The packet data to decode
176
+ '''
177
+ self.address, count, self.byte_count = struct.unpack('>HHB', data[0:5])
178
+ values = unpack_bitstring(data[5:])
179
+ self.values = values[:count]
180
+
181
+ def execute(self, context):
182
+ ''' Run a write coils request against a datastore
183
+
184
+ :param context: The datastore to request from
185
+ :returns: The populated response or exception message
186
+ '''
187
+ count = len(self.values)
188
+ if not (1 <= count <= 0x07b0):
189
+ return self.doException(merror.IllegalValue)
190
+ if (self.byte_count != (count + 7) / 8):
191
+ return self.doException(merror.IllegalValue)
192
+ if not context.validate(self.function_code, self.address, count):
193
+ return self.doException(merror.IllegalAddress)
194
+
195
+ context.setValues(self.function_code, self.address, self.values)
196
+ return WriteMultipleCoilsResponse(self.address, count)
197
+
198
+ def __str__(self):
199
+ ''' Returns a string representation of the instance
200
+
201
+ :returns: A string representation of the instance
202
+ '''
203
+ params = (self.address, len(self.values))
204
+ return "WriteNCoilRequest (%d) => %d " % params
205
+
206
+ class WriteMultipleCoilsResponse(ModbusResponse):
207
+ '''
208
+ The normal response returns the function code, starting address, and
209
+ quantity of coils forced.
210
+ '''
211
+ function_code = 15
212
+ _rtu_frame_size = 8
213
+
214
+ def __init__(self, address=None, count=None, **kwargs):
215
+ ''' Initializes a new instance
216
+
217
+ :param address: The starting variable address written to
218
+ :param count: The number of values written
219
+ '''
220
+ ModbusResponse.__init__(self, **kwargs)
221
+ self.address = address
222
+ self.count = count
223
+
224
+ def encode(self):
225
+ ''' Encodes write coils response
226
+
227
+ :returns: The byte encoded message
228
+ '''
229
+ return struct.pack('>HH', self.address, self.count)
230
+
231
+ def decode(self, data):
232
+ ''' Decodes a write coils response
233
+
234
+ :param data: The packet data to decode
235
+ '''
236
+ self.address, self.count = struct.unpack('>HH', data)
237
+
238
+ def __str__(self):
239
+ ''' Returns a string representation of the instance
240
+
241
+ :returns: A string representation of the instance
242
+ '''
243
+ return "WriteNCoilResponse(%d, %d)" % (self.address, self.count)
244
+
245
+ #---------------------------------------------------------------------------#
246
+ # Exported symbols
247
+ #---------------------------------------------------------------------------#
248
+ __all__ = [
249
+ "WriteSingleCoilRequest", "WriteSingleCoilResponse",
250
+ "WriteMultipleCoilsRequest", "WriteMultipleCoilsResponse",
251
+ ]
File without changes
@@ -0,0 +1,140 @@
1
+ """
2
+ Implementation of a Modbus Client Using Twisted
3
+ --------------------------------------------------
4
+
5
+ Example Run::
6
+
7
+ from pymodbus.client.async import ModbusClientFactory
8
+ from pymodbus.bit_read_message import ReadCoilsRequest
9
+
10
+ def clientTest():
11
+ requests = [ ReadCoilsRequest(0,99) ]
12
+ p = reactor.connectTCP("localhost", 502, ModbusClientFactory(requests))
13
+
14
+ if __name__ == "__main__":
15
+ reactor.callLater(1, clientTest)
16
+ reactor.run()
17
+ """
18
+ import struct
19
+ from collections import deque
20
+
21
+ from twisted.internet import reactor, defer, protocol
22
+
23
+ from pymodbus.factory import ClientDecoder
24
+ from pymodbus.exceptions import ConnectionException
25
+ from pymodbus.transaction import ModbusSocketFramer
26
+ from pymodbus.client.common import ModbusClientMixin
27
+
28
+ #---------------------------------------------------------------------------#
29
+ # Logging
30
+ #---------------------------------------------------------------------------#
31
+ import logging
32
+ _logger = logging.getLogger(__name__)
33
+
34
+ #---------------------------------------------------------------------------#
35
+ # Client Protocols
36
+ #---------------------------------------------------------------------------#
37
+ class ModbusClientProtocol(protocol.Protocol, ModbusClientMixin):
38
+ '''
39
+ This represents the base modbus client protocol. All the application
40
+ layer code is deferred to a higher level wrapper.
41
+ '''
42
+ __tid = 0
43
+
44
+ def __init__(self, framer=None):
45
+ ''' Initializes the framer module
46
+
47
+ :param framer: The framer to use for the protocol
48
+ '''
49
+ self.framer = framer or ModbusSocketFramer(ClientDecoder())
50
+ self._requests = deque() # link queue to tid
51
+ self._connected = False
52
+
53
+ def connectionMade(self):
54
+ ''' Called upon a successful client connection.
55
+ '''
56
+ _logger.debug("Client connected to modbus server")
57
+ self._connected = True
58
+
59
+ def connectionLost(self, reason):
60
+ ''' Called upon a client disconnect
61
+
62
+ :param reason: The reason for the disconnect
63
+ '''
64
+ _logger.debug("Client disconnected from modbus server: %s" % reason)
65
+ self._connected = False
66
+
67
+ def dataReceived(self, data):
68
+ ''' Get response, check for valid message, decode result
69
+
70
+ :param data: The data returned from the server
71
+ '''
72
+ self.framer.processIncomingPacket(data, self._callback)
73
+
74
+ def execute(self, request):
75
+ ''' Starts the producer to send the next request to
76
+ consumer.write(Frame(request))
77
+ '''
78
+ request.transaction_id = self.__getNextTID()
79
+ #self.handler[request.transaction_id] = request
80
+ packet = self.framer.buildPacket(request)
81
+ self.transport.write(packet)
82
+ return self._buildResponse()
83
+
84
+ def _callback(self, reply):
85
+ ''' The callback to call with the response message
86
+
87
+ :param reply: The decoded response message
88
+ '''
89
+ # todo errback/callback
90
+ if self._requests:
91
+ self._requests.popleft().callback(reply)
92
+
93
+ def _buildResponse(self):
94
+ ''' Helper method to return a deferred response
95
+ for the current request.
96
+
97
+ :returns: A defer linked to the latest request
98
+ '''
99
+ if not self._connected:
100
+ return defer.fail(ConnectionException('Client is not connected'))
101
+
102
+ d = defer.Deferred()
103
+ self._requests.append(d)
104
+ return d
105
+
106
+ def __getNextTID(self):
107
+ ''' Used to retrieve the next transaction id
108
+ :return: The next unique transaction id
109
+
110
+ As the transaction identifier is represented with two
111
+ bytes, the highest TID is 0xffff
112
+
113
+ ..todo:: Remove this and use the transaction manager
114
+ '''
115
+ tid = (ModbusClientProtocol.__tid + 1) & 0xffff
116
+ ModbusClientProtocol.__tid = tid
117
+ return tid
118
+
119
+ #----------------------------------------------------------------------#
120
+ # Extra Functions
121
+ #----------------------------------------------------------------------#
122
+ #if send_failed:
123
+ # if self.retry > 0:
124
+ # deferLater(clock, self.delay, send, message)
125
+ # self.retry -= 1
126
+
127
+ #---------------------------------------------------------------------------#
128
+ # Client Factories
129
+ #---------------------------------------------------------------------------#
130
+ class ModbusClientFactory(protocol.ReconnectingClientFactory):
131
+ ''' Simple client protocol factory '''
132
+
133
+ protocol = ModbusClientProtocol
134
+
135
+ #---------------------------------------------------------------------------#
136
+ # Exported symbols
137
+ #---------------------------------------------------------------------------#
138
+ __all__ = [
139
+ "ModbusClientProtocol", "ModbusClientFactory",
140
+ ]