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.
@@ -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 for test purpose only (don't use in project).
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.5, 3.6, 3.7, 3.8, 3.9 and 3.10.
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
- EXP_TXT, EXP_DETAILS, EXP_NONE, \
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 random
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(object):
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 = None # socket
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
- if self._sock:
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
- if self._sock:
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.0'
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
- MODBUS_ENCAPSULATED_INTERFACE = 0x2B
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._coils)):
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._coils)):
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._h_regs)):
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._h_regs)):
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 ValueError('data_bank arg is invalid')
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
- self.data_hdl = None
725
- self.data_bank = None
726
- # if external engine is defined, ignore data_hdl and data_bank
727
- if ext_engine:
728
- if not callable(self.ext_engine):
729
- raise ValueError('ext_engine must be callable')
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 ValueError
945
+ raise TypeError
786
946
  # call ad-hoc func
787
947
  func(session_data)
788
- except (ValueError, KeyError):
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 re.match(r'^[a-z][a-z0-9.\-]+$', host_str):
317
- return True
318
- # on invalid host
319
- return False
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")
@@ -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