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.
- pymodbus-0.9.0/PKG-INFO +28 -0
- pymodbus-0.9.0/pymodbus/__init__.py +32 -0
- pymodbus-0.9.0/pymodbus/bit_read_message.py +228 -0
- pymodbus-0.9.0/pymodbus/bit_write_message.py +251 -0
- pymodbus-0.9.0/pymodbus/client/__init__.py +0 -0
- pymodbus-0.9.0/pymodbus/client/async.py +140 -0
- pymodbus-0.9.0/pymodbus/client/common.py +133 -0
- pymodbus-0.9.0/pymodbus/client/sync.py +384 -0
- pymodbus-0.9.0/pymodbus/constants.py +126 -0
- pymodbus-0.9.0/pymodbus/datastore/__init__.py +12 -0
- pymodbus-0.9.0/pymodbus/datastore/context.py +129 -0
- pymodbus-0.9.0/pymodbus/datastore/database.py +174 -0
- pymodbus-0.9.0/pymodbus/datastore/modredis.py +244 -0
- pymodbus-0.9.0/pymodbus/datastore/remote.py +100 -0
- pymodbus-0.9.0/pymodbus/datastore/store.py +230 -0
- pymodbus-0.9.0/pymodbus/device.py +450 -0
- pymodbus-0.9.0/pymodbus/diag_message.py +636 -0
- pymodbus-0.9.0/pymodbus/events.py +192 -0
- pymodbus-0.9.0/pymodbus/exceptions.py +67 -0
- pymodbus-0.9.0/pymodbus/factory.py +150 -0
- pymodbus-0.9.0/pymodbus/file_message.py +124 -0
- pymodbus-0.9.0/pymodbus/interfaces.py +201 -0
- pymodbus-0.9.0/pymodbus/internal/__init__.py +0 -0
- pymodbus-0.9.0/pymodbus/internal/ptwisted.py +45 -0
- pymodbus-0.9.0/pymodbus/other_message.py +430 -0
- pymodbus-0.9.0/pymodbus/pdu.py +197 -0
- pymodbus-0.9.0/pymodbus/register_read_message.py +339 -0
- pymodbus-0.9.0/pymodbus/register_write_message.py +229 -0
- pymodbus-0.9.0/pymodbus/server/__init__.py +2 -0
- pymodbus-0.9.0/pymodbus/server/async.py +233 -0
- pymodbus-0.9.0/pymodbus/server/sync.py +320 -0
- pymodbus-0.9.0/pymodbus/transaction.py +746 -0
- pymodbus-0.9.0/pymodbus/utilities.py +190 -0
- pymodbus-0.9.0/pymodbus/version.py +44 -0
- pymodbus-0.9.0/pymodbus.egg-info/PKG-INFO +28 -0
- pymodbus-0.9.0/pymodbus.egg-info/SOURCES.txt +65 -0
- pymodbus-0.9.0/pymodbus.egg-info/dependency_links.txt +1 -0
- pymodbus-0.9.0/pymodbus.egg-info/requires.txt +3 -0
- pymodbus-0.9.0/pymodbus.egg-info/top_level.txt +2 -0
- pymodbus-0.9.0/pymodbus.egg-info/zip-safe +1 -0
- pymodbus-0.9.0/setup.cfg +21 -0
- pymodbus-0.9.0/setup.py +98 -0
- pymodbus-0.9.0/test/__init__.py +0 -0
- pymodbus-0.9.0/test/modbus_mocks.py +34 -0
- pymodbus-0.9.0/test/test_all_messages.py +74 -0
- pymodbus-0.9.0/test/test_bit_read_messages.py +124 -0
- pymodbus-0.9.0/test/test_bit_write_messages.py +113 -0
- pymodbus-0.9.0/test/test_client.py +25 -0
- pymodbus-0.9.0/test/test_client_common.py +49 -0
- pymodbus-0.9.0/test/test_datastore.py +126 -0
- pymodbus-0.9.0/test/test_device.py +219 -0
- pymodbus-0.9.0/test/test_diag_messages.py +130 -0
- pymodbus-0.9.0/test/test_events.py +77 -0
- pymodbus-0.9.0/test/test_exceptions.py +38 -0
- pymodbus-0.9.0/test/test_factory.py +134 -0
- pymodbus-0.9.0/test/test_file_message.py +87 -0
- pymodbus-0.9.0/test/test_interfaces.py +61 -0
- pymodbus-0.9.0/test/test_other_messages.py +98 -0
- pymodbus-0.9.0/test/test_pdu.py +82 -0
- pymodbus-0.9.0/test/test_register_read_messages.py +166 -0
- pymodbus-0.9.0/test/test_register_write_messages.py +98 -0
- pymodbus-0.9.0/test/test_remote_datastore.py +69 -0
- pymodbus-0.9.0/test/test_server_context.py +86 -0
- pymodbus-0.9.0/test/test_transaction.py +344 -0
- pymodbus-0.9.0/test/test_utilities.py +88 -0
- pymodbus-0.9.0/test/test_version.py +27 -0
pymodbus-0.9.0/PKG-INFO
ADDED
|
@@ -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
|
+
]
|