pyModbusTCP 0.2.0__tar.gz → 0.2.2__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.
- {pyModbusTCP-0.2.0 → pyModbusTCP-0.2.2}/CHANGES +13 -1
- pyModbusTCP-0.2.0/README.rst → pyModbusTCP-0.2.2/PKG-INFO +13 -21
- pyModbusTCP-0.2.2/README.rst +109 -0
- {pyModbusTCP-0.2.0 → pyModbusTCP-0.2.2}/pyModbusTCP/__init__.py +1 -6
- {pyModbusTCP-0.2.0 → pyModbusTCP-0.2.2}/pyModbusTCP/client.py +172 -12
- {pyModbusTCP-0.2.0 → pyModbusTCP-0.2.2}/pyModbusTCP/constants.py +9 -3
- {pyModbusTCP-0.2.0 → pyModbusTCP-0.2.2}/pyModbusTCP/server.py +306 -28
- {pyModbusTCP-0.2.0 → pyModbusTCP-0.2.2}/pyModbusTCP/utils.py +8 -4
- pyModbusTCP-0.2.2/pyModbusTCP.egg-info/PKG-INFO +120 -0
- pyModbusTCP-0.2.0/PKG-INFO +0 -138
- pyModbusTCP-0.2.0/pyModbusTCP.egg-info/PKG-INFO +0 -138
- {pyModbusTCP-0.2.0 → pyModbusTCP-0.2.2}/LICENSE +0 -0
- {pyModbusTCP-0.2.0 → pyModbusTCP-0.2.2}/MANIFEST.in +0 -0
- {pyModbusTCP-0.2.0 → pyModbusTCP-0.2.2}/pyModbusTCP.egg-info/SOURCES.txt +0 -0
- {pyModbusTCP-0.2.0 → pyModbusTCP-0.2.2}/pyModbusTCP.egg-info/dependency_links.txt +0 -0
- {pyModbusTCP-0.2.0 → pyModbusTCP-0.2.2}/pyModbusTCP.egg-info/top_level.txt +0 -0
- {pyModbusTCP-0.2.0 → pyModbusTCP-0.2.2}/setup.cfg +0 -0
- {pyModbusTCP-0.2.0 → pyModbusTCP-0.2.2}/setup.py +0 -0
|
@@ -1,6 +1,18 @@
|
|
|
1
|
-
|
|
2
1
|
Revision history for pyModbusTCP
|
|
3
2
|
|
|
3
|
+
0.2.2 2024-07-31
|
|
4
|
+
|
|
5
|
+
- fix ModbusServer: wrong check of discrete inputs length in DataBank (thanks to OTnetproj).
|
|
6
|
+
- updated compatibility test (python versions): remove 3.7, add 3.12.
|
|
7
|
+
|
|
8
|
+
0.2.1 2023-11-21
|
|
9
|
+
|
|
10
|
+
- fix ModbusServer: wrong check of input registers length in DataBank (thanks to monsieurvor).
|
|
11
|
+
- improve hostname validation in utils (thanks to MCXIV and schmocker).
|
|
12
|
+
- experimental add of read_device_identification() (over modbus encapsulated interface) to client and server.
|
|
13
|
+
- experimental add of write_read_multiple_registers() function (code 0x17) to client and server (thanks to tanj).
|
|
14
|
+
- updated compatibility test (python versions): remove 3.5/3.6, add 3.11.
|
|
15
|
+
|
|
4
16
|
0.2.0 2022-06-05
|
|
5
17
|
|
|
6
18
|
- ModbusClient: parameters are now properties instead of methods (more intuitive).
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: pyModbusTCP
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: A simple Modbus/TCP library for Python
|
|
5
|
+
Home-page: https://github.com/sourceperl/pyModbusTCP
|
|
6
|
+
Author: Loic Lefebvre
|
|
7
|
+
Author-email: loic.celine@free.fr
|
|
8
|
+
License: MIT
|
|
9
|
+
Platform: any
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
|
|
1
12
|
.. |badge_tests| image:: https://github.com/sourceperl/pyModbusTCP/actions/workflows/tests.yml/badge.svg?branch=master
|
|
2
13
|
:target: https://github.com/sourceperl/pyModbusTCP/actions/workflows/tests.yml
|
|
3
14
|
|
|
@@ -10,12 +21,12 @@ pyModbusTCP |badge_tests| |badge_docs|
|
|
|
10
21
|
A simple Modbus/TCP client library for Python.
|
|
11
22
|
pyModbusTCP is pure Python code without any extension or external module dependency.
|
|
12
23
|
|
|
13
|
-
Since version 0.1.0, a server is also available
|
|
24
|
+
Since version 0.1.0, a server is also available.
|
|
14
25
|
|
|
15
26
|
Tests
|
|
16
27
|
-----
|
|
17
28
|
|
|
18
|
-
The module is currently test on Python 3.
|
|
29
|
+
The module is currently test on Python 3.8, 3.9, 3.10, 3.11 and 3.12.
|
|
19
30
|
|
|
20
31
|
For Linux, Mac OS and Windows.
|
|
21
32
|
|
|
@@ -107,22 +118,3 @@ Write value 44 and 55 to registers at modbus address 10 :
|
|
|
107
118
|
print("write ok")
|
|
108
119
|
else:
|
|
109
120
|
print("write error")
|
|
110
|
-
|
|
111
|
-
Know issue with older Python version on Windows
|
|
112
|
-
-----------------------------------------------
|
|
113
|
-
|
|
114
|
-
On windows OS with older Python version (<3), win_inet_pton module is require. This avoid exception "AttributeError:
|
|
115
|
-
'module' object has no attribute 'inet_pton'".
|
|
116
|
-
|
|
117
|
-
install win_inet_pton:
|
|
118
|
-
|
|
119
|
-
.. code-block:: bash
|
|
120
|
-
|
|
121
|
-
sudo pip install win_inet_pton
|
|
122
|
-
|
|
123
|
-
import win_inet_pton at beginning of your code:
|
|
124
|
-
|
|
125
|
-
.. code-block:: python
|
|
126
|
-
|
|
127
|
-
import win_inet_pton
|
|
128
|
-
from pyModbusTCP.client import ModbusClient
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
.. |badge_tests| image:: https://github.com/sourceperl/pyModbusTCP/actions/workflows/tests.yml/badge.svg?branch=master
|
|
2
|
+
:target: https://github.com/sourceperl/pyModbusTCP/actions/workflows/tests.yml
|
|
3
|
+
|
|
4
|
+
.. |badge_docs| image:: https://readthedocs.org/projects/pymodbustcp/badge/?version=latest
|
|
5
|
+
:target: http://pymodbustcp.readthedocs.io/
|
|
6
|
+
|
|
7
|
+
pyModbusTCP |badge_tests| |badge_docs|
|
|
8
|
+
======================================
|
|
9
|
+
|
|
10
|
+
A simple Modbus/TCP client library for Python.
|
|
11
|
+
pyModbusTCP is pure Python code without any extension or external module dependency.
|
|
12
|
+
|
|
13
|
+
Since version 0.1.0, a server is also available.
|
|
14
|
+
|
|
15
|
+
Tests
|
|
16
|
+
-----
|
|
17
|
+
|
|
18
|
+
The module is currently test on Python 3.8, 3.9, 3.10, 3.11 and 3.12.
|
|
19
|
+
|
|
20
|
+
For Linux, Mac OS and Windows.
|
|
21
|
+
|
|
22
|
+
Documentation
|
|
23
|
+
-------------
|
|
24
|
+
|
|
25
|
+
Documentation of the last release is available online at https://pymodbustcp.readthedocs.io/.
|
|
26
|
+
|
|
27
|
+
Setup
|
|
28
|
+
-----
|
|
29
|
+
|
|
30
|
+
You can install this package from:
|
|
31
|
+
|
|
32
|
+
PyPI, the easy way:
|
|
33
|
+
|
|
34
|
+
.. code-block:: bash
|
|
35
|
+
|
|
36
|
+
# install the last available release (stable)
|
|
37
|
+
sudo pip install pyModbusTCP
|
|
38
|
+
|
|
39
|
+
.. code-block:: bash
|
|
40
|
+
|
|
41
|
+
# install a specific version (here release v0.1.10)
|
|
42
|
+
sudo pip install pyModbusTCP==v0.1.10
|
|
43
|
+
|
|
44
|
+
From GitHub:
|
|
45
|
+
|
|
46
|
+
.. code-block:: bash
|
|
47
|
+
|
|
48
|
+
# install a specific version (here release v0.1.10) directly from github servers
|
|
49
|
+
sudo pip install git+https://github.com/sourceperl/pyModbusTCP.git@v0.1.10
|
|
50
|
+
|
|
51
|
+
Note on the use of versions:
|
|
52
|
+
|
|
53
|
+
Over time, some things can change. So, it's a good practice that you always use a specific version of a package for
|
|
54
|
+
your project, instead of just relying on the default behavior. Without precision, the installation tools will always
|
|
55
|
+
install the latest version available for a package, this may have some drawbacks. For example, in pyModbusTCP, the TCP
|
|
56
|
+
automatic open mode will be active by default from version 0.2.0. It is not the case with previous versions and it just
|
|
57
|
+
doesn't exist before the 0.0.12. This can lead to some strange behaviour of your application if you are not aware of
|
|
58
|
+
the change. Look at `CHANGES <https://github.com/sourceperl/pyModbusTCP/blob/master/CHANGES>`_ for details on versions
|
|
59
|
+
available.
|
|
60
|
+
|
|
61
|
+
Usage example
|
|
62
|
+
-------------
|
|
63
|
+
|
|
64
|
+
See examples/ for full scripts.
|
|
65
|
+
|
|
66
|
+
include (for all samples)
|
|
67
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
68
|
+
|
|
69
|
+
.. code-block:: python
|
|
70
|
+
|
|
71
|
+
from pyModbusTCP.client import ModbusClient
|
|
72
|
+
|
|
73
|
+
module init (TCP always open)
|
|
74
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
75
|
+
|
|
76
|
+
.. code-block:: python
|
|
77
|
+
|
|
78
|
+
# TCP auto connect on first modbus request
|
|
79
|
+
c = ModbusClient(host="localhost", port=502, unit_id=1, auto_open=True)
|
|
80
|
+
|
|
81
|
+
module init (TCP open/close for each request)
|
|
82
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
83
|
+
|
|
84
|
+
.. code-block:: python
|
|
85
|
+
|
|
86
|
+
# TCP auto connect on modbus request, close after it
|
|
87
|
+
c = ModbusClient(host="127.0.0.1", auto_open=True, auto_close=True)
|
|
88
|
+
|
|
89
|
+
Read 2x 16 bits registers at modbus address 0 :
|
|
90
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
91
|
+
|
|
92
|
+
.. code-block:: python
|
|
93
|
+
|
|
94
|
+
regs = c.read_holding_registers(0, 2)
|
|
95
|
+
|
|
96
|
+
if regs:
|
|
97
|
+
print(regs)
|
|
98
|
+
else:
|
|
99
|
+
print("read error")
|
|
100
|
+
|
|
101
|
+
Write value 44 and 55 to registers at modbus address 10 :
|
|
102
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
103
|
+
|
|
104
|
+
.. code-block:: python
|
|
105
|
+
|
|
106
|
+
if c.write_multiple_registers(10, [44,55]):
|
|
107
|
+
print("write ok")
|
|
108
|
+
else:
|
|
109
|
+
print("write error")
|
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
|
|
3
1
|
# Python package: Client and Server for ModBus/TCP
|
|
4
|
-
# Version: 0.2.0
|
|
5
2
|
# Website: https://github.com/sourceperl/pyModbusTCP
|
|
6
|
-
# Date: 2022-06-05
|
|
7
3
|
# License: MIT (http://http://opensource.org/licenses/mit-license.php)
|
|
8
4
|
# Description: Client/Server ModBus/TCP
|
|
9
5
|
# Support functions 3 and 16 (class 0)
|
|
10
6
|
# 1,2,4,5,6 (Class 1)
|
|
11
|
-
# 15
|
|
12
|
-
# Charset: utf-8
|
|
7
|
+
# 15,23,43
|
|
13
8
|
|
|
14
9
|
from .constants import VERSION
|
|
15
10
|
|
|
@@ -1,19 +1,68 @@
|
|
|
1
1
|
""" pyModbusTCP Client """
|
|
2
2
|
|
|
3
|
+
from dataclasses import dataclass, field
|
|
3
4
|
from .constants import READ_COILS, READ_DISCRETE_INPUTS, READ_HOLDING_REGISTERS, READ_INPUT_REGISTERS, \
|
|
4
5
|
WRITE_MULTIPLE_COILS, WRITE_MULTIPLE_REGISTERS, WRITE_SINGLE_COIL, WRITE_SINGLE_REGISTER, \
|
|
5
|
-
|
|
6
|
+
WRITE_READ_MULTIPLE_REGISTERS, ENCAPSULATED_INTERFACE_TRANSPORT, MEI_TYPE_READ_DEVICE_ID, \
|
|
6
7
|
MB_ERR_TXT, MB_NO_ERR, MB_SEND_ERR, MB_RECV_ERR, MB_TIMEOUT_ERR, MB_EXCEPT_ERR, MB_CONNECT_ERR, \
|
|
7
|
-
MB_SOCK_CLOSE_ERR, VERSION
|
|
8
|
+
EXP_TXT, EXP_DETAILS, EXP_NONE, MB_SOCK_CLOSE_ERR, VERSION
|
|
8
9
|
from .utils import byte_length, set_bit, valid_host
|
|
10
|
+
import random
|
|
9
11
|
import socket
|
|
10
12
|
from socket import AF_UNSPEC, SOCK_STREAM
|
|
11
13
|
import struct
|
|
12
|
-
import
|
|
14
|
+
from typing import Dict
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class DeviceIdentificationResponse:
|
|
19
|
+
"""Modbus TCP client function read_device_identification() response struct.
|
|
20
|
+
|
|
21
|
+
:param conformity_level: this represents supported access and object type
|
|
22
|
+
:type conformity_level: int
|
|
23
|
+
:param more_follows: for stream request can be set to 0xff if other objects are available (0x00 in other cases)
|
|
24
|
+
:type more_follows: int
|
|
25
|
+
:param next_object_id: the next object id to be asked by following transaction
|
|
26
|
+
:type next_object_id: int
|
|
27
|
+
:param objects_by_id: a dictionary with requested object (dict keys are object id as int)
|
|
28
|
+
:type objects_by_id: dict
|
|
29
|
+
"""
|
|
30
|
+
conformity_level: int = 0
|
|
31
|
+
more_follows: int = 0
|
|
32
|
+
next_object_id: int = 0
|
|
33
|
+
objects_by_id: Dict[int, bytes] = field(default_factory=lambda: {})
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def vendor_name(self):
|
|
37
|
+
return self.objects_by_id.get(0x00)
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def product_code(self):
|
|
41
|
+
return self.objects_by_id.get(0x01)
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def major_minor_revision(self):
|
|
45
|
+
return self.objects_by_id.get(0x02)
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def vendor_url(self):
|
|
49
|
+
return self.objects_by_id.get(0x03)
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def product_name(self):
|
|
53
|
+
return self.objects_by_id.get(0x04)
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def model_name(self):
|
|
57
|
+
return self.objects_by_id.get(0x05)
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def user_application_name(self):
|
|
61
|
+
return self.objects_by_id.get(0x06)
|
|
13
62
|
|
|
14
63
|
|
|
15
|
-
class ModbusClient
|
|
16
|
-
"""Modbus TCP client"""
|
|
64
|
+
class ModbusClient:
|
|
65
|
+
"""Modbus TCP client."""
|
|
17
66
|
|
|
18
67
|
class _InternalError(Exception):
|
|
19
68
|
pass
|
|
@@ -57,7 +106,7 @@ class ModbusClient(object):
|
|
|
57
106
|
self._debug = None
|
|
58
107
|
self._auto_open = None
|
|
59
108
|
self._auto_close = None
|
|
60
|
-
self._sock =
|
|
109
|
+
self._sock = socket.socket()
|
|
61
110
|
self._transaction_id = 0 # MBAP transaction ID
|
|
62
111
|
self._version = VERSION # this package version number
|
|
63
112
|
self._last_error = MB_NO_ERR # last error code
|
|
@@ -232,10 +281,7 @@ class ModbusClient(object):
|
|
|
232
281
|
@property
|
|
233
282
|
def is_open(self):
|
|
234
283
|
"""Get current status of the TCP connection (True = open)."""
|
|
235
|
-
|
|
236
|
-
return self._sock.fileno() > 0
|
|
237
|
-
else:
|
|
238
|
-
return False
|
|
284
|
+
return self._sock.fileno() > 0
|
|
239
285
|
|
|
240
286
|
def open(self):
|
|
241
287
|
"""Connect to modbus server (open TCP connection).
|
|
@@ -279,8 +325,7 @@ class ModbusClient(object):
|
|
|
279
325
|
|
|
280
326
|
def close(self):
|
|
281
327
|
"""Close current TCP connection."""
|
|
282
|
-
|
|
283
|
-
self._sock.close()
|
|
328
|
+
self._sock.close()
|
|
284
329
|
|
|
285
330
|
def custom_request(self, pdu):
|
|
286
331
|
"""Send a custom modbus request.
|
|
@@ -458,6 +503,59 @@ class ModbusClient(object):
|
|
|
458
503
|
self._req_except_handler(e)
|
|
459
504
|
return None
|
|
460
505
|
|
|
506
|
+
def read_device_identification(self, read_code=1, object_id=0):
|
|
507
|
+
"""Modbus function Read Device Identification (0x2B/0x0E).
|
|
508
|
+
|
|
509
|
+
:param read_code: read device id code, 1 to 3 for respectively: basic, regular and extended stream access,
|
|
510
|
+
4 for one specific identification object individual access (default is 1)
|
|
511
|
+
:type read_code: int
|
|
512
|
+
:param object_id: object id of the first object to obtain (default is 0)
|
|
513
|
+
:type object_id: int
|
|
514
|
+
:returns: a DeviceIdentificationResponse instance with the data or None if the requests fails
|
|
515
|
+
:rtype: DeviceIdentificationResponse or None
|
|
516
|
+
"""
|
|
517
|
+
# check params
|
|
518
|
+
if not 1 <= int(read_code) <= 4:
|
|
519
|
+
raise ValueError('read_code out of range (valid from 1 to 4)')
|
|
520
|
+
if not 0 <= int(object_id) <= 0xff:
|
|
521
|
+
raise ValueError('object_id out of range (valid from 0 to 255)')
|
|
522
|
+
# make request
|
|
523
|
+
try:
|
|
524
|
+
tx_pdu = struct.pack('BBBB', ENCAPSULATED_INTERFACE_TRANSPORT, MEI_TYPE_READ_DEVICE_ID, read_code, object_id)
|
|
525
|
+
rx_pdu = self._req_pdu(tx_pdu=tx_pdu, rx_min_len=7)
|
|
526
|
+
# init response object for populate it
|
|
527
|
+
response = DeviceIdentificationResponse()
|
|
528
|
+
# extract fields
|
|
529
|
+
# field "conformity level"
|
|
530
|
+
response.conformity_level = rx_pdu[3]
|
|
531
|
+
# field "more follows"
|
|
532
|
+
response.more_follows = rx_pdu[4]
|
|
533
|
+
# field "next object id"
|
|
534
|
+
response.next_object_id = rx_pdu[5]
|
|
535
|
+
# field "number of objects"
|
|
536
|
+
nb_of_objs = rx_pdu[6]
|
|
537
|
+
# decode [[obj_id, obj_len, obj_value],...]
|
|
538
|
+
pdu_offset = 7
|
|
539
|
+
for _ in range(nb_of_objs):
|
|
540
|
+
# parse object PDU bytes
|
|
541
|
+
try:
|
|
542
|
+
obj_id = rx_pdu[pdu_offset]
|
|
543
|
+
obj_len = rx_pdu[pdu_offset+1]
|
|
544
|
+
obj_value = rx_pdu[pdu_offset+2:pdu_offset+2+obj_len]
|
|
545
|
+
except IndexError:
|
|
546
|
+
raise ModbusClient._NetworkError(MB_RECV_ERR, 'rx byte count mismatch')
|
|
547
|
+
if obj_len != len(obj_value):
|
|
548
|
+
raise ModbusClient._NetworkError(MB_RECV_ERR, 'rx byte count mismatch')
|
|
549
|
+
# set offset to next object
|
|
550
|
+
pdu_offset += 2 + obj_len
|
|
551
|
+
# add result to request list
|
|
552
|
+
response.objects_by_id[obj_id] = obj_value
|
|
553
|
+
return response
|
|
554
|
+
# handle error during request
|
|
555
|
+
except ModbusClient._InternalError as e:
|
|
556
|
+
self._req_except_handler(e)
|
|
557
|
+
return None
|
|
558
|
+
|
|
461
559
|
def write_single_coil(self, bit_addr, bit_value):
|
|
462
560
|
"""Modbus function WRITE_SINGLE_COIL (0x05).
|
|
463
561
|
|
|
@@ -607,6 +705,68 @@ class ModbusClient(object):
|
|
|
607
705
|
self._req_except_handler(e)
|
|
608
706
|
return False
|
|
609
707
|
|
|
708
|
+
def write_read_multiple_registers(self, write_addr, write_values, read_addr, read_nb=1):
|
|
709
|
+
"""Modbus function WRITE_READ_MULTIPLE_REGISTERS (0x17).
|
|
710
|
+
|
|
711
|
+
:param write_addr: write registers address (0 to 65535)
|
|
712
|
+
:type write_addr: int
|
|
713
|
+
:param write_values: registers values to write
|
|
714
|
+
:type write_values: list
|
|
715
|
+
:param read_addr: read register address (0 to 65535)
|
|
716
|
+
:type read_addr: int
|
|
717
|
+
:param read_nb: number of registers to read (1 to 125)
|
|
718
|
+
:type read_nb: int
|
|
719
|
+
:returns: registers list or None if fail
|
|
720
|
+
:rtype: list of int or None
|
|
721
|
+
"""
|
|
722
|
+
# check params
|
|
723
|
+
check_l = [(not 0 <= int(write_addr) <= 0xffff, 'write_addr out of range (valid from 0 to 65535)'),
|
|
724
|
+
(not 1 <= len(write_values) <= 121, 'number of registers out of range (valid from 1 to 121)'),
|
|
725
|
+
(int(write_addr) + len(write_values) > 0x10000, 'write after end of modbus address space'),
|
|
726
|
+
(not 0 <= int(read_addr) <= 0xffff, 'read_addr out of range (valid from 0 to 65535)'),
|
|
727
|
+
(not 1 <= int(read_nb) <= 125, 'read_nb out of range (valid from 1 to 125)'),
|
|
728
|
+
(int(read_addr) + int(read_nb) > 0x10000, 'read after end of modbus address space'), ]
|
|
729
|
+
for err, msg in check_l:
|
|
730
|
+
if err:
|
|
731
|
+
raise ValueError(msg)
|
|
732
|
+
# make request
|
|
733
|
+
try:
|
|
734
|
+
# init PDU registers part
|
|
735
|
+
pdu_regs_part = b''
|
|
736
|
+
# populate it with register values
|
|
737
|
+
for reg in write_values:
|
|
738
|
+
# check current register value
|
|
739
|
+
if not 0 <= int(reg) <= 0xffff:
|
|
740
|
+
raise ValueError('write_values list contains out of range values')
|
|
741
|
+
# pack register for build frame
|
|
742
|
+
pdu_regs_part += struct.pack('>H', reg)
|
|
743
|
+
bytes_nb = len(pdu_regs_part)
|
|
744
|
+
# concatenate PDU parts
|
|
745
|
+
tx_pdu = struct.pack('>BHHHHB', WRITE_READ_MULTIPLE_REGISTERS, read_addr, read_nb,
|
|
746
|
+
write_addr, len(write_values), bytes_nb)
|
|
747
|
+
tx_pdu += pdu_regs_part
|
|
748
|
+
# make a request
|
|
749
|
+
rx_pdu = self._req_pdu(tx_pdu=tx_pdu, rx_min_len=4)
|
|
750
|
+
# response decode
|
|
751
|
+
# extract field "byte count"
|
|
752
|
+
byte_count = rx_pdu[1]
|
|
753
|
+
# frame with regs value
|
|
754
|
+
f_regs = rx_pdu[2:]
|
|
755
|
+
# check rx_byte_count: buffer size must be consistent and have at least the requested number of registers
|
|
756
|
+
if byte_count < 2 * read_nb or byte_count != len(f_regs):
|
|
757
|
+
raise ModbusClient._NetworkError(MB_RECV_ERR, 'rx byte count mismatch')
|
|
758
|
+
# allocate a reg_nb size list
|
|
759
|
+
registers = [0] * read_nb
|
|
760
|
+
# fill registers list with register items
|
|
761
|
+
for i in range(read_nb):
|
|
762
|
+
registers[i] = struct.unpack('>H', f_regs[i * 2:i * 2 + 2])[0]
|
|
763
|
+
# return registers list
|
|
764
|
+
return registers
|
|
765
|
+
# handle error during request
|
|
766
|
+
except ModbusClient._InternalError as e:
|
|
767
|
+
self._req_except_handler(e)
|
|
768
|
+
return
|
|
769
|
+
|
|
610
770
|
def _send(self, frame):
|
|
611
771
|
"""Send frame over current socket.
|
|
612
772
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
""" pyModbusTCP package constants definition """
|
|
2
2
|
|
|
3
3
|
# Package version
|
|
4
|
-
VERSION = '0.2.
|
|
4
|
+
VERSION = '0.2.2'
|
|
5
5
|
# Modbus/TCP
|
|
6
6
|
MODBUS_PORT = 502
|
|
7
7
|
# Modbus function code
|
|
@@ -13,9 +13,13 @@ WRITE_SINGLE_COIL = 0x05
|
|
|
13
13
|
WRITE_SINGLE_REGISTER = 0x06
|
|
14
14
|
WRITE_MULTIPLE_COILS = 0x0F
|
|
15
15
|
WRITE_MULTIPLE_REGISTERS = 0x10
|
|
16
|
-
|
|
16
|
+
WRITE_READ_MULTIPLE_REGISTERS = 0x17
|
|
17
|
+
ENCAPSULATED_INTERFACE_TRANSPORT = 0x2B
|
|
17
18
|
SUPPORTED_FUNCTION_CODES = (READ_COILS, READ_DISCRETE_INPUTS, READ_HOLDING_REGISTERS, READ_INPUT_REGISTERS,
|
|
18
|
-
WRITE_SINGLE_COIL, WRITE_SINGLE_REGISTER, WRITE_MULTIPLE_COILS, WRITE_MULTIPLE_REGISTERS
|
|
19
|
+
WRITE_SINGLE_COIL, WRITE_SINGLE_REGISTER, WRITE_MULTIPLE_COILS, WRITE_MULTIPLE_REGISTERS,
|
|
20
|
+
WRITE_READ_MULTIPLE_REGISTERS, ENCAPSULATED_INTERFACE_TRANSPORT)
|
|
21
|
+
# MEI type
|
|
22
|
+
MEI_TYPE_READ_DEVICE_ID = 0x0E
|
|
19
23
|
# Modbus except code
|
|
20
24
|
EXP_NONE = 0x00
|
|
21
25
|
EXP_ILLEGAL_FUNCTION = 0x01
|
|
@@ -85,3 +89,5 @@ MB_ERR_TXT = {
|
|
|
85
89
|
MB_CRC_ERR: 'bad CRC on receive frame',
|
|
86
90
|
MB_SOCK_CLOSE_ERR: 'socket is closed'
|
|
87
91
|
}
|
|
92
|
+
# Misc
|
|
93
|
+
MAX_PDU_SIZE = 253
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from .constants import READ_COILS, READ_DISCRETE_INPUTS, READ_HOLDING_REGISTERS, READ_INPUT_REGISTERS, \
|
|
4
4
|
WRITE_MULTIPLE_COILS, WRITE_MULTIPLE_REGISTERS, WRITE_SINGLE_COIL, WRITE_SINGLE_REGISTER, \
|
|
5
|
-
EXP_NONE, EXP_ILLEGAL_FUNCTION, EXP_DATA_ADDRESS, EXP_DATA_VALUE
|
|
5
|
+
EXP_NONE, EXP_ILLEGAL_FUNCTION, EXP_DATA_ADDRESS, EXP_DATA_VALUE, WRITE_READ_MULTIPLE_REGISTERS, \
|
|
6
|
+
ENCAPSULATED_INTERFACE_TRANSPORT, MEI_TYPE_READ_DEVICE_ID, MAX_PDU_SIZE
|
|
6
7
|
from .utils import test_bit, set_bit
|
|
7
8
|
import logging
|
|
8
9
|
import socket
|
|
@@ -170,7 +171,7 @@ class DataBank:
|
|
|
170
171
|
"""
|
|
171
172
|
# secure extract of data from list used by server thread
|
|
172
173
|
with self._d_inputs_lock:
|
|
173
|
-
if (address >= 0) and (address + number <= len(self.
|
|
174
|
+
if (address >= 0) and (address + number <= len(self._d_inputs)):
|
|
174
175
|
return self._d_inputs[address: number + address]
|
|
175
176
|
else:
|
|
176
177
|
return None
|
|
@@ -190,7 +191,7 @@ class DataBank:
|
|
|
190
191
|
bit_list = [bool(b) for b in bit_list]
|
|
191
192
|
# ensure atomic update of internal data
|
|
192
193
|
with self._d_inputs_lock:
|
|
193
|
-
if (address >= 0) and (address + len(bit_list) <= len(self.
|
|
194
|
+
if (address >= 0) and (address + len(bit_list) <= len(self._d_inputs)):
|
|
194
195
|
for offset, b_value in enumerate(bit_list):
|
|
195
196
|
self._d_inputs[address + offset] = b_value
|
|
196
197
|
else:
|
|
@@ -264,7 +265,7 @@ class DataBank:
|
|
|
264
265
|
"""
|
|
265
266
|
# secure extract of data from list used by server thread
|
|
266
267
|
with self._i_regs_lock:
|
|
267
|
-
if (address >= 0) and (address + number <= len(self.
|
|
268
|
+
if (address >= 0) and (address + number <= len(self._i_regs)):
|
|
268
269
|
return self._i_regs[address: number + address]
|
|
269
270
|
else:
|
|
270
271
|
return None
|
|
@@ -284,7 +285,7 @@ class DataBank:
|
|
|
284
285
|
word_list = [int(w) & 0xffff for w in word_list]
|
|
285
286
|
# ensure atomic update of internal data
|
|
286
287
|
with self._i_regs_lock:
|
|
287
|
-
if (address >= 0) and (address + len(word_list) <= len(self.
|
|
288
|
+
if (address >= 0) and (address + len(word_list) <= len(self._i_regs)):
|
|
288
289
|
for offset, c_value in enumerate(word_list):
|
|
289
290
|
c_address = address + offset
|
|
290
291
|
if self._i_regs[c_address] != c_value:
|
|
@@ -351,7 +352,7 @@ class DataHandler:
|
|
|
351
352
|
"""
|
|
352
353
|
# check data_bank type
|
|
353
354
|
if data_bank and not isinstance(data_bank, DataBank):
|
|
354
|
-
raise
|
|
355
|
+
raise TypeError('data_bank arg is invalid')
|
|
355
356
|
# public
|
|
356
357
|
self.data_bank = data_bank or DataBank()
|
|
357
358
|
|
|
@@ -473,6 +474,158 @@ class DataHandler:
|
|
|
473
474
|
return DataHandler.Return(exp_code=EXP_DATA_ADDRESS)
|
|
474
475
|
|
|
475
476
|
|
|
477
|
+
class DeviceIdentification:
|
|
478
|
+
""" Container class for device identification objects (MEI type 0x0E) return by function 0x2B. """
|
|
479
|
+
|
|
480
|
+
def __init__(self, vendor_name=b'', product_code=b'', major_minor_revision=b'', vendor_url=b'',
|
|
481
|
+
product_name=b'', model_name=b'', user_application_name=b'', objects_id=None):
|
|
482
|
+
"""
|
|
483
|
+
Constructor
|
|
484
|
+
|
|
485
|
+
:param vendor_name: VendorName mandatory object
|
|
486
|
+
:type vendor_name: bytes
|
|
487
|
+
:param product_code: ProductCode mandatory object
|
|
488
|
+
:type product_code: bytes
|
|
489
|
+
:param major_minor_revision: MajorMinorRevision mandatory object
|
|
490
|
+
:type major_minor_revision: bytes
|
|
491
|
+
:param vendor_url: VendorUrl regular object
|
|
492
|
+
:type vendor_url: bytes
|
|
493
|
+
:param product_name: ProductName regular object
|
|
494
|
+
:type product_name: bytes
|
|
495
|
+
:param model_name: ModelName regular object
|
|
496
|
+
:type model_name: bytes
|
|
497
|
+
:param user_application_name: UserApplicationName regular object
|
|
498
|
+
:type user_application_name: bytes
|
|
499
|
+
:param objects_id: Objects values by id as dict example: {42:b'value'} (optional)
|
|
500
|
+
:type objects_id: dict
|
|
501
|
+
"""
|
|
502
|
+
# private
|
|
503
|
+
self._objs_d = {}
|
|
504
|
+
self._objs_lock = Lock()
|
|
505
|
+
# default values
|
|
506
|
+
self.vendor_name = vendor_name
|
|
507
|
+
self.product_code = product_code
|
|
508
|
+
self.major_minor_revision = major_minor_revision
|
|
509
|
+
self.vendor_url = vendor_url
|
|
510
|
+
self.product_name = product_name
|
|
511
|
+
self.model_name = model_name
|
|
512
|
+
self.user_application_name = user_application_name
|
|
513
|
+
# process objects_id dict (populate no name objects)
|
|
514
|
+
if isinstance(objects_id, dict):
|
|
515
|
+
for key, value in objects_id.items():
|
|
516
|
+
self[key] = value
|
|
517
|
+
|
|
518
|
+
def __getitem__(self, key):
|
|
519
|
+
if not isinstance(key, int):
|
|
520
|
+
raise TypeError('key must be an int')
|
|
521
|
+
with self._objs_lock:
|
|
522
|
+
return self._objs_d[key]
|
|
523
|
+
|
|
524
|
+
def __setitem__(self, key, value):
|
|
525
|
+
if not isinstance(key, int):
|
|
526
|
+
raise TypeError('key must be an int')
|
|
527
|
+
if 0xff >= key >= 0x00:
|
|
528
|
+
if not isinstance(value, bytes):
|
|
529
|
+
raise TypeError('this object is of type bytes only')
|
|
530
|
+
with self._objs_lock:
|
|
531
|
+
self._objs_d[key] = value
|
|
532
|
+
else:
|
|
533
|
+
raise ValueError('key not in valid range (0 to 255)')
|
|
534
|
+
|
|
535
|
+
def __repr__(self):
|
|
536
|
+
named_params = ''
|
|
537
|
+
# add named parameters
|
|
538
|
+
for prop_name in ('vendor_name', 'product_code', 'major_minor_revision', 'vendor_url',
|
|
539
|
+
'product_name', 'model_name', 'user_application_name'):
|
|
540
|
+
prop_value = getattr(self, prop_name)
|
|
541
|
+
if prop_value:
|
|
542
|
+
if named_params:
|
|
543
|
+
named_params += ', '
|
|
544
|
+
named_params += '%s=%r' % (prop_name, getattr(self, prop_name))
|
|
545
|
+
# add parameters without shortcut name
|
|
546
|
+
objs_id_d_str = ''
|
|
547
|
+
for _id in range(0x07, 0x100):
|
|
548
|
+
try:
|
|
549
|
+
obj_id_item = '%r: %r' % (_id, self[_id])
|
|
550
|
+
if objs_id_d_str:
|
|
551
|
+
objs_id_d_str += ', '
|
|
552
|
+
objs_id_d_str += obj_id_item
|
|
553
|
+
except KeyError:
|
|
554
|
+
pass
|
|
555
|
+
# format str: classname(params_name=value, ..., objects_id={42: 'value'})
|
|
556
|
+
class_args = named_params
|
|
557
|
+
if objs_id_d_str:
|
|
558
|
+
if class_args:
|
|
559
|
+
class_args += ', '
|
|
560
|
+
class_args += 'objects_id={%s}' % objs_id_d_str
|
|
561
|
+
return '%s(%s)' % (self.__class__.__name__, class_args)
|
|
562
|
+
|
|
563
|
+
@property
|
|
564
|
+
def vendor_name(self):
|
|
565
|
+
return self[0]
|
|
566
|
+
|
|
567
|
+
@vendor_name.setter
|
|
568
|
+
def vendor_name(self, value):
|
|
569
|
+
self[0] = value
|
|
570
|
+
|
|
571
|
+
@property
|
|
572
|
+
def product_code(self):
|
|
573
|
+
return self[1]
|
|
574
|
+
|
|
575
|
+
@product_code.setter
|
|
576
|
+
def product_code(self, value):
|
|
577
|
+
self[1] = value
|
|
578
|
+
|
|
579
|
+
@property
|
|
580
|
+
def major_minor_revision(self):
|
|
581
|
+
return self[2]
|
|
582
|
+
|
|
583
|
+
@major_minor_revision.setter
|
|
584
|
+
def major_minor_revision(self, value):
|
|
585
|
+
self[2] = value
|
|
586
|
+
|
|
587
|
+
@property
|
|
588
|
+
def vendor_url(self):
|
|
589
|
+
return self[3]
|
|
590
|
+
|
|
591
|
+
@vendor_url.setter
|
|
592
|
+
def vendor_url(self, value):
|
|
593
|
+
self[3] = value
|
|
594
|
+
|
|
595
|
+
@property
|
|
596
|
+
def product_name(self):
|
|
597
|
+
return self[4]
|
|
598
|
+
|
|
599
|
+
@product_name.setter
|
|
600
|
+
def product_name(self, value):
|
|
601
|
+
self[4] = value
|
|
602
|
+
|
|
603
|
+
@property
|
|
604
|
+
def model_name(self):
|
|
605
|
+
return self[5]
|
|
606
|
+
|
|
607
|
+
@model_name.setter
|
|
608
|
+
def model_name(self, value):
|
|
609
|
+
self[5] = value
|
|
610
|
+
|
|
611
|
+
@property
|
|
612
|
+
def user_application_name(self):
|
|
613
|
+
return self[6]
|
|
614
|
+
|
|
615
|
+
@user_application_name.setter
|
|
616
|
+
def user_application_name(self, value):
|
|
617
|
+
self[6] = value
|
|
618
|
+
|
|
619
|
+
def items(self, start=0x00, end=0xff):
|
|
620
|
+
items_l = []
|
|
621
|
+
for obj_id in range(start, end + 1):
|
|
622
|
+
try:
|
|
623
|
+
items_l.append((obj_id, self[obj_id]))
|
|
624
|
+
except KeyError:
|
|
625
|
+
pass
|
|
626
|
+
return items_l
|
|
627
|
+
|
|
628
|
+
|
|
476
629
|
class ModbusServer:
|
|
477
630
|
""" Modbus TCP server """
|
|
478
631
|
|
|
@@ -695,7 +848,7 @@ class ModbusServer:
|
|
|
695
848
|
self.request.close()
|
|
696
849
|
|
|
697
850
|
def __init__(self, host='localhost', port=502, no_block=False, ipv6=False,
|
|
698
|
-
data_bank=None, data_hdl=None, ext_engine=None):
|
|
851
|
+
data_bank=None, data_hdl=None, ext_engine=None, device_id=None):
|
|
699
852
|
"""Constructor
|
|
700
853
|
|
|
701
854
|
Modbus server constructor.
|
|
@@ -714,31 +867,36 @@ class ModbusServer:
|
|
|
714
867
|
:type data_hdl: DataHandler
|
|
715
868
|
:param ext_engine: an external engine reference (ref to ext_engine(session_data)) (optional)
|
|
716
869
|
:type ext_engine: callable
|
|
870
|
+
:param device_id: instance of DeviceIdentification class for read device identification request (optional)
|
|
871
|
+
:type device_id: DeviceIdentification
|
|
717
872
|
"""
|
|
873
|
+
# check data_bank
|
|
874
|
+
if data_bank and not isinstance(data_bank, DataBank):
|
|
875
|
+
raise TypeError('data_bank is not a DataBank instance')
|
|
876
|
+
# check data_hdl
|
|
877
|
+
if data_hdl and not isinstance(data_hdl, DataHandler):
|
|
878
|
+
raise TypeError('data_hdl is not a DataHandler instance')
|
|
879
|
+
# data_hdl and data_bank can't be set at same time
|
|
880
|
+
if data_hdl and data_bank:
|
|
881
|
+
raise ValueError('when data_hdl is set, you must define data_bank in it')
|
|
882
|
+
# check ext_engine
|
|
883
|
+
if ext_engine and not callable(ext_engine):
|
|
884
|
+
raise TypeError('ext_engine must be callable')
|
|
885
|
+
# check device_id
|
|
886
|
+
if device_id and not isinstance(device_id, DeviceIdentification):
|
|
887
|
+
raise TypeError('device_id is not a DeviceIdentification instance')
|
|
718
888
|
# public
|
|
719
889
|
self.host = host
|
|
720
890
|
self.port = port
|
|
721
891
|
self.no_block = no_block
|
|
722
892
|
self.ipv6 = ipv6
|
|
723
893
|
self.ext_engine = ext_engine
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
# if external engine is
|
|
727
|
-
if ext_engine
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
else:
|
|
731
|
-
# default data handler is ModbusServerDataHandler or a child of it
|
|
732
|
-
if data_hdl is None:
|
|
733
|
-
self.data_hdl = DataHandler(data_bank=data_bank)
|
|
734
|
-
elif isinstance(data_hdl, DataHandler):
|
|
735
|
-
self.data_hdl = data_hdl
|
|
736
|
-
if data_bank:
|
|
737
|
-
raise ValueError('when data_hdl is set, you must define data_bank in it')
|
|
738
|
-
else:
|
|
739
|
-
raise ValueError('data_hdl is not a ModbusServerDataHandler (or child of it) instance')
|
|
740
|
-
# data bank shortcut
|
|
741
|
-
self.data_bank = self.data_hdl.data_bank
|
|
894
|
+
# First, internal data_bank will be linked to an external data handler if defined.
|
|
895
|
+
# If not, an external or internal DataBank will be used instead.
|
|
896
|
+
# "virtual mode" will be set for save memory if an external engine is in use.
|
|
897
|
+
self.data_bank = data_hdl.data_bank if data_hdl else data_bank or DataBank(virtual_mode=bool(ext_engine))
|
|
898
|
+
self.data_hdl = data_hdl or DataHandler(data_bank=self.data_bank)
|
|
899
|
+
self.device_id = device_id
|
|
742
900
|
# private
|
|
743
901
|
self._evt_running = Event()
|
|
744
902
|
self._service = None
|
|
@@ -751,7 +909,9 @@ class ModbusServer:
|
|
|
751
909
|
WRITE_SINGLE_COIL: self._write_single_coil,
|
|
752
910
|
WRITE_SINGLE_REGISTER: self._write_single_register,
|
|
753
911
|
WRITE_MULTIPLE_COILS: self._write_multiple_coils,
|
|
754
|
-
WRITE_MULTIPLE_REGISTERS: self._write_multiple_registers
|
|
912
|
+
WRITE_MULTIPLE_REGISTERS: self._write_multiple_registers,
|
|
913
|
+
WRITE_READ_MULTIPLE_REGISTERS: self._write_read_multiple_registers,
|
|
914
|
+
ENCAPSULATED_INTERFACE_TRANSPORT: self._encapsulated_interface_transport}
|
|
755
915
|
|
|
756
916
|
def __repr__(self):
|
|
757
917
|
r_str = 'ModbusServer(host=\'%s\', port=%d, no_block=%s, ipv6=%s, data_bank=%s, data_hdl=%s, ext_engine=%s)'
|
|
@@ -782,10 +942,10 @@ class ModbusServer:
|
|
|
782
942
|
func = self._func_map[session_data.request.pdu.func_code]
|
|
783
943
|
# check function found is callable
|
|
784
944
|
if not callable(func):
|
|
785
|
-
raise
|
|
945
|
+
raise TypeError
|
|
786
946
|
# call ad-hoc func
|
|
787
947
|
func(session_data)
|
|
788
|
-
except (
|
|
948
|
+
except (TypeError, KeyError):
|
|
789
949
|
session_data.response.pdu.build_except(session_data.request.pdu.func_code, EXP_ILLEGAL_FUNCTION)
|
|
790
950
|
|
|
791
951
|
def _read_bits(self, session_data):
|
|
@@ -964,6 +1124,124 @@ class ModbusServer:
|
|
|
964
1124
|
else:
|
|
965
1125
|
send_pdu.build_except(recv_pdu.func_code, EXP_DATA_VALUE)
|
|
966
1126
|
|
|
1127
|
+
def _write_read_multiple_registers(self, session_data):
|
|
1128
|
+
"""
|
|
1129
|
+
Function Write Read Multiple Registers (0x17).
|
|
1130
|
+
|
|
1131
|
+
:param session_data: server engine data
|
|
1132
|
+
:type session_data: ModbusServer.SessionData
|
|
1133
|
+
"""
|
|
1134
|
+
# pdu alias
|
|
1135
|
+
recv_pdu = session_data.request.pdu
|
|
1136
|
+
send_pdu = session_data.response.pdu
|
|
1137
|
+
# decode pdu
|
|
1138
|
+
(read_start_addr,
|
|
1139
|
+
read_quantity_regs,
|
|
1140
|
+
write_start_addr,
|
|
1141
|
+
write_quantity_regs,
|
|
1142
|
+
byte_count) = recv_pdu.unpack('>HHHHB', from_byte=1, to_byte=10)
|
|
1143
|
+
# ok flags: some tests on pdu fields
|
|
1144
|
+
write_qty_regs_ok = 0x0001 <= write_quantity_regs <= 0x007B
|
|
1145
|
+
write_b_count_ok = byte_count == write_quantity_regs * 2
|
|
1146
|
+
write_pdu_len_ok = len(recv_pdu.raw[10:]) >= byte_count
|
|
1147
|
+
read_qty_regs_ok = 0x0001 <= read_quantity_regs <= 0x007B
|
|
1148
|
+
# test ok flags
|
|
1149
|
+
if write_qty_regs_ok and write_b_count_ok and write_pdu_len_ok and read_qty_regs_ok:
|
|
1150
|
+
# allocate words list
|
|
1151
|
+
regs_l = [0] * write_quantity_regs
|
|
1152
|
+
# populate words list with words from rx frame
|
|
1153
|
+
for i, _ in enumerate(regs_l):
|
|
1154
|
+
offset = i * 2 + 10
|
|
1155
|
+
regs_l[i] = recv_pdu.unpack('>H', from_byte=offset, to_byte=offset + 2)[0]
|
|
1156
|
+
# data handler update request
|
|
1157
|
+
ret_hdl = self.data_hdl.write_h_regs(write_start_addr, regs_l, session_data.srv_info)
|
|
1158
|
+
# format regular or except response
|
|
1159
|
+
if ret_hdl.ok:
|
|
1160
|
+
ret_hdl = self.data_hdl.read_h_regs(read_start_addr, read_quantity_regs, session_data.srv_info)
|
|
1161
|
+
if ret_hdl.ok:
|
|
1162
|
+
# build pdu
|
|
1163
|
+
send_pdu.add_pack('BB', recv_pdu.func_code, read_quantity_regs * 2)
|
|
1164
|
+
# add_pack requested words
|
|
1165
|
+
send_pdu.add_pack('>%dH' % len(ret_hdl.data), *ret_hdl.data)
|
|
1166
|
+
else:
|
|
1167
|
+
send_pdu.build_except(recv_pdu.func_code, ret_hdl.exp_code)
|
|
1168
|
+
else:
|
|
1169
|
+
send_pdu.build_except(recv_pdu.func_code, ret_hdl.exp_code)
|
|
1170
|
+
else:
|
|
1171
|
+
send_pdu.build_except(recv_pdu.func_code, EXP_DATA_VALUE)
|
|
1172
|
+
|
|
1173
|
+
def _encapsulated_interface_transport(self, session_data):
|
|
1174
|
+
"""
|
|
1175
|
+
Modbus Encapsulated Interface transport (MEI) endpoint (0x2B).
|
|
1176
|
+
|
|
1177
|
+
:param session_data: server engine data
|
|
1178
|
+
:type session_data: ModbusServer.SessionData
|
|
1179
|
+
"""
|
|
1180
|
+
# pdu alias
|
|
1181
|
+
recv_pdu = session_data.request.pdu
|
|
1182
|
+
send_pdu = session_data.response.pdu
|
|
1183
|
+
# decode pdu
|
|
1184
|
+
(mei_type,) = recv_pdu.unpack('B', from_byte=1, to_byte=2)
|
|
1185
|
+
# MEI type: read device identification
|
|
1186
|
+
if mei_type == MEI_TYPE_READ_DEVICE_ID:
|
|
1187
|
+
# check device_id property is set (default is None)
|
|
1188
|
+
if not self.device_id:
|
|
1189
|
+
# return except 2 if unset
|
|
1190
|
+
send_pdu.build_except(recv_pdu.func_code, EXP_DATA_ADDRESS)
|
|
1191
|
+
return
|
|
1192
|
+
# list of requested objects
|
|
1193
|
+
req_objects_l = list()
|
|
1194
|
+
(device_id_code, object_id) = recv_pdu.unpack('BB', from_byte=2, to_byte=4)
|
|
1195
|
+
# get basic device id (object id from 0x00 to 0x02)
|
|
1196
|
+
if device_id_code == 1:
|
|
1197
|
+
start_id = object_id
|
|
1198
|
+
req_objects_l.extend(self.device_id.items(start=start_id, end=0x2))
|
|
1199
|
+
# get regular device id (object id 0x03 to 0x7f)
|
|
1200
|
+
elif device_id_code == 2:
|
|
1201
|
+
start_id = max(object_id, 0x03)
|
|
1202
|
+
req_objects_l.extend(self.device_id.items(start=start_id, end=0x7f))
|
|
1203
|
+
# get extended device id (object id 0x80 to 0xff)
|
|
1204
|
+
elif device_id_code == 3:
|
|
1205
|
+
start_id = max(object_id, 0x80)
|
|
1206
|
+
req_objects_l.extend(self.device_id.items(start=start_id, end=0xff))
|
|
1207
|
+
# get specific id object
|
|
1208
|
+
elif device_id_code == 4:
|
|
1209
|
+
start_id = object_id
|
|
1210
|
+
req_objects_l.extend(self.device_id.items(start=start_id, end=start_id))
|
|
1211
|
+
else:
|
|
1212
|
+
# return except 3 for unknown device id code
|
|
1213
|
+
send_pdu.build_except(recv_pdu.func_code, EXP_DATA_VALUE)
|
|
1214
|
+
return
|
|
1215
|
+
# init variables for response PDU build
|
|
1216
|
+
conformity_level = 0x83
|
|
1217
|
+
more_follow = 0
|
|
1218
|
+
next_obj_id = 0
|
|
1219
|
+
number_of_objs = 0
|
|
1220
|
+
fmt_pdu_head = 'BBBBBBB'
|
|
1221
|
+
# format objects data part = [[obj id, obj len, obj val], ...]
|
|
1222
|
+
obj_data_part = b''
|
|
1223
|
+
for req_obj_id, req_obj_value in req_objects_l:
|
|
1224
|
+
fmt_obj_blk = 'BB%ss' % len(req_obj_value)
|
|
1225
|
+
# skip if the next add to data part will exceed max PDU size of modbus frame
|
|
1226
|
+
if struct.calcsize(fmt_pdu_head) + len(obj_data_part) + struct.calcsize(fmt_obj_blk) > MAX_PDU_SIZE:
|
|
1227
|
+
# turn on "more follow" field and set "next object id" field with next object id to ask
|
|
1228
|
+
more_follow = 0xff
|
|
1229
|
+
next_obj_id = req_obj_id
|
|
1230
|
+
break
|
|
1231
|
+
# ensure bytes type for object value
|
|
1232
|
+
if isinstance(req_obj_value, str):
|
|
1233
|
+
req_obj_value = req_obj_value.encode()
|
|
1234
|
+
# add current object to data part
|
|
1235
|
+
obj_data_part += struct.pack(fmt_obj_blk, req_obj_id, len(req_obj_value), req_obj_value)
|
|
1236
|
+
number_of_objs += 1
|
|
1237
|
+
# full PDU response = [PDU header] + [objects data part]
|
|
1238
|
+
send_pdu.add_pack(fmt_pdu_head, recv_pdu.func_code, mei_type, device_id_code,
|
|
1239
|
+
conformity_level, more_follow, next_obj_id, number_of_objs)
|
|
1240
|
+
send_pdu.raw += obj_data_part
|
|
1241
|
+
else:
|
|
1242
|
+
# return except 2 for an unknown MEI type
|
|
1243
|
+
send_pdu.build_except(recv_pdu.func_code, EXP_DATA_ADDRESS)
|
|
1244
|
+
|
|
967
1245
|
def start(self):
|
|
968
1246
|
"""Start the server.
|
|
969
1247
|
|
|
@@ -313,7 +313,11 @@ def valid_host(host_str):
|
|
|
313
313
|
except socket.error:
|
|
314
314
|
pass
|
|
315
315
|
# valid hostname ?
|
|
316
|
-
if
|
|
317
|
-
return
|
|
318
|
-
#
|
|
319
|
-
|
|
316
|
+
if len(host_str) > 255:
|
|
317
|
+
return False
|
|
318
|
+
# strip final dot, if present
|
|
319
|
+
if host_str[-1] == '.':
|
|
320
|
+
host_str = host_str[:-1]
|
|
321
|
+
# validate each part of the hostname (part_1.part_2.part_3)
|
|
322
|
+
re_part_ok = re.compile('(?!-)[a-z0-9-_]{1,63}(?<!-)$', re.IGNORECASE)
|
|
323
|
+
return all(re_part_ok.match(part) for part in host_str.split('.'))
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: pyModbusTCP
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: A simple Modbus/TCP library for Python
|
|
5
|
+
Home-page: https://github.com/sourceperl/pyModbusTCP
|
|
6
|
+
Author: Loic Lefebvre
|
|
7
|
+
Author-email: loic.celine@free.fr
|
|
8
|
+
License: MIT
|
|
9
|
+
Platform: any
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
|
|
12
|
+
.. |badge_tests| image:: https://github.com/sourceperl/pyModbusTCP/actions/workflows/tests.yml/badge.svg?branch=master
|
|
13
|
+
:target: https://github.com/sourceperl/pyModbusTCP/actions/workflows/tests.yml
|
|
14
|
+
|
|
15
|
+
.. |badge_docs| image:: https://readthedocs.org/projects/pymodbustcp/badge/?version=latest
|
|
16
|
+
:target: http://pymodbustcp.readthedocs.io/
|
|
17
|
+
|
|
18
|
+
pyModbusTCP |badge_tests| |badge_docs|
|
|
19
|
+
======================================
|
|
20
|
+
|
|
21
|
+
A simple Modbus/TCP client library for Python.
|
|
22
|
+
pyModbusTCP is pure Python code without any extension or external module dependency.
|
|
23
|
+
|
|
24
|
+
Since version 0.1.0, a server is also available.
|
|
25
|
+
|
|
26
|
+
Tests
|
|
27
|
+
-----
|
|
28
|
+
|
|
29
|
+
The module is currently test on Python 3.8, 3.9, 3.10, 3.11 and 3.12.
|
|
30
|
+
|
|
31
|
+
For Linux, Mac OS and Windows.
|
|
32
|
+
|
|
33
|
+
Documentation
|
|
34
|
+
-------------
|
|
35
|
+
|
|
36
|
+
Documentation of the last release is available online at https://pymodbustcp.readthedocs.io/.
|
|
37
|
+
|
|
38
|
+
Setup
|
|
39
|
+
-----
|
|
40
|
+
|
|
41
|
+
You can install this package from:
|
|
42
|
+
|
|
43
|
+
PyPI, the easy way:
|
|
44
|
+
|
|
45
|
+
.. code-block:: bash
|
|
46
|
+
|
|
47
|
+
# install the last available release (stable)
|
|
48
|
+
sudo pip install pyModbusTCP
|
|
49
|
+
|
|
50
|
+
.. code-block:: bash
|
|
51
|
+
|
|
52
|
+
# install a specific version (here release v0.1.10)
|
|
53
|
+
sudo pip install pyModbusTCP==v0.1.10
|
|
54
|
+
|
|
55
|
+
From GitHub:
|
|
56
|
+
|
|
57
|
+
.. code-block:: bash
|
|
58
|
+
|
|
59
|
+
# install a specific version (here release v0.1.10) directly from github servers
|
|
60
|
+
sudo pip install git+https://github.com/sourceperl/pyModbusTCP.git@v0.1.10
|
|
61
|
+
|
|
62
|
+
Note on the use of versions:
|
|
63
|
+
|
|
64
|
+
Over time, some things can change. So, it's a good practice that you always use a specific version of a package for
|
|
65
|
+
your project, instead of just relying on the default behavior. Without precision, the installation tools will always
|
|
66
|
+
install the latest version available for a package, this may have some drawbacks. For example, in pyModbusTCP, the TCP
|
|
67
|
+
automatic open mode will be active by default from version 0.2.0. It is not the case with previous versions and it just
|
|
68
|
+
doesn't exist before the 0.0.12. This can lead to some strange behaviour of your application if you are not aware of
|
|
69
|
+
the change. Look at `CHANGES <https://github.com/sourceperl/pyModbusTCP/blob/master/CHANGES>`_ for details on versions
|
|
70
|
+
available.
|
|
71
|
+
|
|
72
|
+
Usage example
|
|
73
|
+
-------------
|
|
74
|
+
|
|
75
|
+
See examples/ for full scripts.
|
|
76
|
+
|
|
77
|
+
include (for all samples)
|
|
78
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
79
|
+
|
|
80
|
+
.. code-block:: python
|
|
81
|
+
|
|
82
|
+
from pyModbusTCP.client import ModbusClient
|
|
83
|
+
|
|
84
|
+
module init (TCP always open)
|
|
85
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
86
|
+
|
|
87
|
+
.. code-block:: python
|
|
88
|
+
|
|
89
|
+
# TCP auto connect on first modbus request
|
|
90
|
+
c = ModbusClient(host="localhost", port=502, unit_id=1, auto_open=True)
|
|
91
|
+
|
|
92
|
+
module init (TCP open/close for each request)
|
|
93
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
94
|
+
|
|
95
|
+
.. code-block:: python
|
|
96
|
+
|
|
97
|
+
# TCP auto connect on modbus request, close after it
|
|
98
|
+
c = ModbusClient(host="127.0.0.1", auto_open=True, auto_close=True)
|
|
99
|
+
|
|
100
|
+
Read 2x 16 bits registers at modbus address 0 :
|
|
101
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
102
|
+
|
|
103
|
+
.. code-block:: python
|
|
104
|
+
|
|
105
|
+
regs = c.read_holding_registers(0, 2)
|
|
106
|
+
|
|
107
|
+
if regs:
|
|
108
|
+
print(regs)
|
|
109
|
+
else:
|
|
110
|
+
print("read error")
|
|
111
|
+
|
|
112
|
+
Write value 44 and 55 to registers at modbus address 10 :
|
|
113
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
114
|
+
|
|
115
|
+
.. code-block:: python
|
|
116
|
+
|
|
117
|
+
if c.write_multiple_registers(10, [44,55]):
|
|
118
|
+
print("write ok")
|
|
119
|
+
else:
|
|
120
|
+
print("write error")
|
pyModbusTCP-0.2.0/PKG-INFO
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 1.0
|
|
2
|
-
Name: pyModbusTCP
|
|
3
|
-
Version: 0.2.0
|
|
4
|
-
Summary: A simple Modbus/TCP library for Python
|
|
5
|
-
Home-page: https://github.com/sourceperl/pyModbusTCP
|
|
6
|
-
Author: Loic Lefebvre
|
|
7
|
-
Author-email: loic.celine@free.fr
|
|
8
|
-
License: MIT
|
|
9
|
-
Description: .. |badge_tests| image:: https://github.com/sourceperl/pyModbusTCP/actions/workflows/tests.yml/badge.svg?branch=master
|
|
10
|
-
:target: https://github.com/sourceperl/pyModbusTCP/actions/workflows/tests.yml
|
|
11
|
-
|
|
12
|
-
.. |badge_docs| image:: https://readthedocs.org/projects/pymodbustcp/badge/?version=latest
|
|
13
|
-
:target: http://pymodbustcp.readthedocs.io/
|
|
14
|
-
|
|
15
|
-
pyModbusTCP |badge_tests| |badge_docs|
|
|
16
|
-
======================================
|
|
17
|
-
|
|
18
|
-
A simple Modbus/TCP client library for Python.
|
|
19
|
-
pyModbusTCP is pure Python code without any extension or external module dependency.
|
|
20
|
-
|
|
21
|
-
Since version 0.1.0, a server is also available for test purpose only (don't use in project).
|
|
22
|
-
|
|
23
|
-
Tests
|
|
24
|
-
-----
|
|
25
|
-
|
|
26
|
-
The module is currently test on Python 3.5, 3.6, 3.7, 3.8, 3.9 and 3.10.
|
|
27
|
-
|
|
28
|
-
For Linux, Mac OS and Windows.
|
|
29
|
-
|
|
30
|
-
Documentation
|
|
31
|
-
-------------
|
|
32
|
-
|
|
33
|
-
Documentation of the last release is available online at https://pymodbustcp.readthedocs.io/.
|
|
34
|
-
|
|
35
|
-
Setup
|
|
36
|
-
-----
|
|
37
|
-
|
|
38
|
-
You can install this package from:
|
|
39
|
-
|
|
40
|
-
PyPI, the easy way:
|
|
41
|
-
|
|
42
|
-
.. code-block:: bash
|
|
43
|
-
|
|
44
|
-
# install the last available release (stable)
|
|
45
|
-
sudo pip install pyModbusTCP
|
|
46
|
-
|
|
47
|
-
.. code-block:: bash
|
|
48
|
-
|
|
49
|
-
# install a specific version (here release v0.1.10)
|
|
50
|
-
sudo pip install pyModbusTCP==v0.1.10
|
|
51
|
-
|
|
52
|
-
From GitHub:
|
|
53
|
-
|
|
54
|
-
.. code-block:: bash
|
|
55
|
-
|
|
56
|
-
# install a specific version (here release v0.1.10) directly from github servers
|
|
57
|
-
sudo pip install git+https://github.com/sourceperl/pyModbusTCP.git@v0.1.10
|
|
58
|
-
|
|
59
|
-
Note on the use of versions:
|
|
60
|
-
|
|
61
|
-
Over time, some things can change. So, it's a good practice that you always use a specific version of a package for
|
|
62
|
-
your project, instead of just relying on the default behavior. Without precision, the installation tools will always
|
|
63
|
-
install the latest version available for a package, this may have some drawbacks. For example, in pyModbusTCP, the TCP
|
|
64
|
-
automatic open mode will be active by default from version 0.2.0. It is not the case with previous versions and it just
|
|
65
|
-
doesn't exist before the 0.0.12. This can lead to some strange behaviour of your application if you are not aware of
|
|
66
|
-
the change. Look at `CHANGES <https://github.com/sourceperl/pyModbusTCP/blob/master/CHANGES>`_ for details on versions
|
|
67
|
-
available.
|
|
68
|
-
|
|
69
|
-
Usage example
|
|
70
|
-
-------------
|
|
71
|
-
|
|
72
|
-
See examples/ for full scripts.
|
|
73
|
-
|
|
74
|
-
include (for all samples)
|
|
75
|
-
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
76
|
-
|
|
77
|
-
.. code-block:: python
|
|
78
|
-
|
|
79
|
-
from pyModbusTCP.client import ModbusClient
|
|
80
|
-
|
|
81
|
-
module init (TCP always open)
|
|
82
|
-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
83
|
-
|
|
84
|
-
.. code-block:: python
|
|
85
|
-
|
|
86
|
-
# TCP auto connect on first modbus request
|
|
87
|
-
c = ModbusClient(host="localhost", port=502, unit_id=1, auto_open=True)
|
|
88
|
-
|
|
89
|
-
module init (TCP open/close for each request)
|
|
90
|
-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
91
|
-
|
|
92
|
-
.. code-block:: python
|
|
93
|
-
|
|
94
|
-
# TCP auto connect on modbus request, close after it
|
|
95
|
-
c = ModbusClient(host="127.0.0.1", auto_open=True, auto_close=True)
|
|
96
|
-
|
|
97
|
-
Read 2x 16 bits registers at modbus address 0 :
|
|
98
|
-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
99
|
-
|
|
100
|
-
.. code-block:: python
|
|
101
|
-
|
|
102
|
-
regs = c.read_holding_registers(0, 2)
|
|
103
|
-
|
|
104
|
-
if regs:
|
|
105
|
-
print(regs)
|
|
106
|
-
else:
|
|
107
|
-
print("read error")
|
|
108
|
-
|
|
109
|
-
Write value 44 and 55 to registers at modbus address 10 :
|
|
110
|
-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
111
|
-
|
|
112
|
-
.. code-block:: python
|
|
113
|
-
|
|
114
|
-
if c.write_multiple_registers(10, [44,55]):
|
|
115
|
-
print("write ok")
|
|
116
|
-
else:
|
|
117
|
-
print("write error")
|
|
118
|
-
|
|
119
|
-
Know issue with older Python version on Windows
|
|
120
|
-
-----------------------------------------------
|
|
121
|
-
|
|
122
|
-
On windows OS with older Python version (<3), win_inet_pton module is require. This avoid exception "AttributeError:
|
|
123
|
-
'module' object has no attribute 'inet_pton'".
|
|
124
|
-
|
|
125
|
-
install win_inet_pton:
|
|
126
|
-
|
|
127
|
-
.. code-block:: bash
|
|
128
|
-
|
|
129
|
-
sudo pip install win_inet_pton
|
|
130
|
-
|
|
131
|
-
import win_inet_pton at beginning of your code:
|
|
132
|
-
|
|
133
|
-
.. code-block:: python
|
|
134
|
-
|
|
135
|
-
import win_inet_pton
|
|
136
|
-
from pyModbusTCP.client import ModbusClient
|
|
137
|
-
|
|
138
|
-
Platform: any
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 1.0
|
|
2
|
-
Name: pyModbusTCP
|
|
3
|
-
Version: 0.2.0
|
|
4
|
-
Summary: A simple Modbus/TCP library for Python
|
|
5
|
-
Home-page: https://github.com/sourceperl/pyModbusTCP
|
|
6
|
-
Author: Loic Lefebvre
|
|
7
|
-
Author-email: loic.celine@free.fr
|
|
8
|
-
License: MIT
|
|
9
|
-
Description: .. |badge_tests| image:: https://github.com/sourceperl/pyModbusTCP/actions/workflows/tests.yml/badge.svg?branch=master
|
|
10
|
-
:target: https://github.com/sourceperl/pyModbusTCP/actions/workflows/tests.yml
|
|
11
|
-
|
|
12
|
-
.. |badge_docs| image:: https://readthedocs.org/projects/pymodbustcp/badge/?version=latest
|
|
13
|
-
:target: http://pymodbustcp.readthedocs.io/
|
|
14
|
-
|
|
15
|
-
pyModbusTCP |badge_tests| |badge_docs|
|
|
16
|
-
======================================
|
|
17
|
-
|
|
18
|
-
A simple Modbus/TCP client library for Python.
|
|
19
|
-
pyModbusTCP is pure Python code without any extension or external module dependency.
|
|
20
|
-
|
|
21
|
-
Since version 0.1.0, a server is also available for test purpose only (don't use in project).
|
|
22
|
-
|
|
23
|
-
Tests
|
|
24
|
-
-----
|
|
25
|
-
|
|
26
|
-
The module is currently test on Python 3.5, 3.6, 3.7, 3.8, 3.9 and 3.10.
|
|
27
|
-
|
|
28
|
-
For Linux, Mac OS and Windows.
|
|
29
|
-
|
|
30
|
-
Documentation
|
|
31
|
-
-------------
|
|
32
|
-
|
|
33
|
-
Documentation of the last release is available online at https://pymodbustcp.readthedocs.io/.
|
|
34
|
-
|
|
35
|
-
Setup
|
|
36
|
-
-----
|
|
37
|
-
|
|
38
|
-
You can install this package from:
|
|
39
|
-
|
|
40
|
-
PyPI, the easy way:
|
|
41
|
-
|
|
42
|
-
.. code-block:: bash
|
|
43
|
-
|
|
44
|
-
# install the last available release (stable)
|
|
45
|
-
sudo pip install pyModbusTCP
|
|
46
|
-
|
|
47
|
-
.. code-block:: bash
|
|
48
|
-
|
|
49
|
-
# install a specific version (here release v0.1.10)
|
|
50
|
-
sudo pip install pyModbusTCP==v0.1.10
|
|
51
|
-
|
|
52
|
-
From GitHub:
|
|
53
|
-
|
|
54
|
-
.. code-block:: bash
|
|
55
|
-
|
|
56
|
-
# install a specific version (here release v0.1.10) directly from github servers
|
|
57
|
-
sudo pip install git+https://github.com/sourceperl/pyModbusTCP.git@v0.1.10
|
|
58
|
-
|
|
59
|
-
Note on the use of versions:
|
|
60
|
-
|
|
61
|
-
Over time, some things can change. So, it's a good practice that you always use a specific version of a package for
|
|
62
|
-
your project, instead of just relying on the default behavior. Without precision, the installation tools will always
|
|
63
|
-
install the latest version available for a package, this may have some drawbacks. For example, in pyModbusTCP, the TCP
|
|
64
|
-
automatic open mode will be active by default from version 0.2.0. It is not the case with previous versions and it just
|
|
65
|
-
doesn't exist before the 0.0.12. This can lead to some strange behaviour of your application if you are not aware of
|
|
66
|
-
the change. Look at `CHANGES <https://github.com/sourceperl/pyModbusTCP/blob/master/CHANGES>`_ for details on versions
|
|
67
|
-
available.
|
|
68
|
-
|
|
69
|
-
Usage example
|
|
70
|
-
-------------
|
|
71
|
-
|
|
72
|
-
See examples/ for full scripts.
|
|
73
|
-
|
|
74
|
-
include (for all samples)
|
|
75
|
-
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
76
|
-
|
|
77
|
-
.. code-block:: python
|
|
78
|
-
|
|
79
|
-
from pyModbusTCP.client import ModbusClient
|
|
80
|
-
|
|
81
|
-
module init (TCP always open)
|
|
82
|
-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
83
|
-
|
|
84
|
-
.. code-block:: python
|
|
85
|
-
|
|
86
|
-
# TCP auto connect on first modbus request
|
|
87
|
-
c = ModbusClient(host="localhost", port=502, unit_id=1, auto_open=True)
|
|
88
|
-
|
|
89
|
-
module init (TCP open/close for each request)
|
|
90
|
-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
91
|
-
|
|
92
|
-
.. code-block:: python
|
|
93
|
-
|
|
94
|
-
# TCP auto connect on modbus request, close after it
|
|
95
|
-
c = ModbusClient(host="127.0.0.1", auto_open=True, auto_close=True)
|
|
96
|
-
|
|
97
|
-
Read 2x 16 bits registers at modbus address 0 :
|
|
98
|
-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
99
|
-
|
|
100
|
-
.. code-block:: python
|
|
101
|
-
|
|
102
|
-
regs = c.read_holding_registers(0, 2)
|
|
103
|
-
|
|
104
|
-
if regs:
|
|
105
|
-
print(regs)
|
|
106
|
-
else:
|
|
107
|
-
print("read error")
|
|
108
|
-
|
|
109
|
-
Write value 44 and 55 to registers at modbus address 10 :
|
|
110
|
-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
111
|
-
|
|
112
|
-
.. code-block:: python
|
|
113
|
-
|
|
114
|
-
if c.write_multiple_registers(10, [44,55]):
|
|
115
|
-
print("write ok")
|
|
116
|
-
else:
|
|
117
|
-
print("write error")
|
|
118
|
-
|
|
119
|
-
Know issue with older Python version on Windows
|
|
120
|
-
-----------------------------------------------
|
|
121
|
-
|
|
122
|
-
On windows OS with older Python version (<3), win_inet_pton module is require. This avoid exception "AttributeError:
|
|
123
|
-
'module' object has no attribute 'inet_pton'".
|
|
124
|
-
|
|
125
|
-
install win_inet_pton:
|
|
126
|
-
|
|
127
|
-
.. code-block:: bash
|
|
128
|
-
|
|
129
|
-
sudo pip install win_inet_pton
|
|
130
|
-
|
|
131
|
-
import win_inet_pton at beginning of your code:
|
|
132
|
-
|
|
133
|
-
.. code-block:: python
|
|
134
|
-
|
|
135
|
-
import win_inet_pton
|
|
136
|
-
from pyModbusTCP.client import ModbusClient
|
|
137
|
-
|
|
138
|
-
Platform: any
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|