Skip to content

Commit de93b82

Browse files
committed
Merge branch 'master_sin'
2 parents 7fc8d3e + b6f3c2d commit de93b82

20 files changed

+97
-54
lines changed

AUTHORS.rst

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Thanks to
2424
- andrew-harness
2525
- banana-sun
2626
- Blaise Thompson
27+
- Breina
2728
- CapraTheBest
2829
- cgernert
2930
- corollaries
@@ -77,6 +78,7 @@ Thanks to
7778
- peufeu2
7879
- Philip Couling
7980
- Philip Jones
81+
- Robin Trabert
8082
- Qi Li
8183
- Sebastian Machuca
8284
- Sefa Keleş

CHANGELOG.rst

+13
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@ helps make pymodbus a better product.
77

88
:ref:`Authors`: contains a complete list of volunteers have contributed to each major version.
99

10+
Version 3.8.4
11+
-------------
12+
* Parameterize string encoding in convert_to_registers and convert_from_registers (#2558)
13+
* Fix client modbus function calls in remote by adding count as keyword argument (#2563)
14+
* Fix exception text in ModbusPDU.validateAddress (#2551)
15+
* Typo arround `no_response_expected` (#2550)
16+
* Trace new connection in server. (#2549)
17+
* Add trace to server.
18+
* Update misleading DATATYPE text. (#2547)
19+
* Fix pylint.
20+
* Clarify server usage.
21+
* Solve instable transaction testing. (#2538)
22+
1023
Version 3.8.3
1124
-------------
1225
* Remove deprecate from payload. (#2532)

MAKE_RELEASE.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ Prepare/make release on dev.
1515
* Control / Update API_changes.rst
1616
* Update CHANGELOG.rst
1717
* Add commits from last release, but selectively !
18-
git log --oneline v3.8.3..HEAD > commit.log
19-
git log --pretty="%an" v3.8.3..HEAD | sort -uf > authors.log
18+
git log --oneline v3.8.4..HEAD > commit.log
19+
git log --pretty="%an" v3.8.4..HEAD | sort -uf > authors.log
2020
update AUTHORS.rst and CHANGELOG.rst
2121
cd doc; ./build_html
2222
* rm -rf build/* dist/*

README.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Upgrade examples:
2626
- 3.7.1 -> 3.8.0: Smaller changes to the pymodbus calls might be needed
2727
- 2.5.4 -> 3.0.0: Major changes in the application might be needed
2828

29-
Current release is `3.8.3 <https://github.com/pymodbus-dev/pymodbus/releases/tag/v3.8.3>`_.
29+
Current release is `3.8.4 <https://github.com/pymodbus-dev/pymodbus/releases/tag/v3.8.4>`_.
3030

3131
Bleeding edge (not released) is `dev <https://github.com/pymodbus-dev/pymodbus/tree/dev>`_.
3232

doc/source/_static/examples.tgz

594 KB
Binary file not shown.

doc/source/_static/examples.zip

0 Bytes
Binary file not shown.

doc/source/client.rst

+5-5
Original file line numberDiff line numberDiff line change
@@ -199,12 +199,12 @@ The logical devices represented by the device is addressed with the :mod:`slave=
199199
With **Serial**, the comm port is defined when creating the object.
200200
The physical devices are addressed with the :mod:`slave=` parameter.
201201

202-
:mod:`slave=0` is defined as broadcast in the modbus standard, but pymodbus treats is a normal device.
202+
:mod:`slave=0` is defined as broadcast in the modbus standard, but pymodbus treats it as a normal device.
203203

204204
If an application is expecting multiple responses to a broadcast request, it must call :mod:`client.execute` and deal with the responses.
205205

206206
If no response is expected to a request, the :mod:`no_response_expected=True` argument can be used
207-
in the normal API calls, this will cause the call to return imidiatble with :mod:`None`
207+
in the normal API calls, this will cause the call to return immediately with :mod:`None`.
208208

209209

210210
Client response handling
@@ -224,7 +224,7 @@ The application should evaluate the result generically::
224224
raise ModbusException(txt)
225225

226226
:mod:`except ModbusException as exc:` happens generally when pymodbus experiences an internal error.
227-
There are a few situation where a unexpected response from a device can cause an exception.
227+
There are a few situation where an unexpected response from a device can cause an exception.
228228

229229
:mod:`rr.isError()` is set whenever the device reports a problem.
230230

@@ -257,7 +257,7 @@ There are a client class for each type of communication and for asynchronous/syn
257257

258258
Client common
259259
^^^^^^^^^^^^^
260-
Some methods are common to all client:
260+
Some methods are common to all clients:
261261

262262
.. autoclass:: pymodbus.client.base.ModbusBaseClient
263263
:members:
@@ -323,7 +323,7 @@ Modbus calls
323323

324324
Pymodbus makes all standard modbus requests/responses available as simple calls.
325325

326-
Using Modbus<transport>Client.register() custom messagees can be added to pymodbus,
326+
Using Modbus<transport>Client.register() custom messages can be added to pymodbus,
327327
and handled automatically.
328328

329329
.. autoclass:: pymodbus.client.mixin.ModbusClientMixin

doc/source/roadmap.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ It is the community that decides how pymodbus evolves NOT the maintainers !
1515

1616
The following bullet points are what the maintainers focus on:
1717

18-
- 3.8.4 bug fix release, with:
18+
- 3.8.5 bug fix release, with:
1919
- Currently not planned
2020
- 3.9.0, with:
2121
- All of branch wait_next_api

doc/source/server.rst

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ communication in 2 versions:
1818
synchronous servers are just an interface layer allowing synchronous
1919
applications to use the server as if it was synchronous.
2020

21+
*Warning* The current framer implementation does not support running the server on a shared rs485 line (multipoint).
2122

2223
.. automodule:: pymodbus.server
2324
:members:

pymodbus/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,5 @@
1818
from pymodbus.pdu import ExceptionResponse
1919

2020

21-
__version__ = "3.8.3"
21+
__version__ = "3.8.4"
2222
__version_full__ = f"[pymodbus, version {__version__}]"

pymodbus/client/mixin.py

+12-8
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ class ModbusClientMixin(Generic[T]): # pylint: disable=too-many-public-methods
3737
Advanced modbus message call::
3838
3939
request = ReadCoilsRequest(1,10)
40-
response = client.execute(request)
40+
response = client.execute(False, request)
4141
# or
4242
request = ReadCoilsRequest(1,10)
43-
response = await client.execute(request)
43+
response = await client.execute(False, request)
4444
4545
.. tip::
4646
All methods can be used directly (synchronous) or
@@ -51,7 +51,7 @@ def __init__(self):
5151
"""Initialize."""
5252

5353
@abstractmethod
54-
def execute(self, no_response_expected: bool, request: ModbusPDU,) -> T:
54+
def execute(self, no_response_expected: bool, request: ModbusPDU) -> T:
5555
"""Execute request."""
5656

5757
def read_coils(self, address: int, *, count: int = 1, slave: int = 1, no_response_expected: bool = False) -> T:
@@ -680,7 +680,7 @@ def read_device_information(self, *, read_code: int | None = None,
680680
# ------------------
681681

682682
class DATATYPE(Enum):
683-
"""Datatype enum (name and number of bytes), used for convert_* calls."""
683+
"""Datatype enum (name and internal data), used for convert_* calls."""
684684

685685
INT16 = ("h", 1)
686686
UINT16 = ("H", 1)
@@ -695,15 +695,17 @@ class DATATYPE(Enum):
695695

696696
@classmethod
697697
def convert_from_registers(
698-
cls, registers: list[int], data_type: DATATYPE, word_order: Literal["big", "little"] = "big"
698+
cls, registers: list[int], data_type: DATATYPE, word_order: Literal["big", "little"] = "big", string_encoding: str = "utf-8"
699699
) -> int | float | str | list[bool] | list[int] | list[float]:
700700
"""Convert registers to int/float/str.
701701
702702
:param registers: list of registers received from e.g. read_holding_registers()
703703
:param data_type: data type to convert to
704704
:param word_order: "big"/"little" order of words/registers
705+
:param string_encoding: The encoding with which to decode the bytearray, only used when data_type=DATATYPE.STRING
705706
:returns: scalar or array of "data_type"
706707
:raises ModbusException: when size of registers is not a multiple of data_type
708+
:raises ParameterException: when the specified string encoding is not supported
707709
"""
708710
if not (data_len := data_type.value[1]):
709711
byte_list = bytearray()
@@ -716,7 +718,7 @@ def convert_from_registers(
716718
while trailing_nulls_begin > 0 and not byte_list[trailing_nulls_begin - 1]:
717719
trailing_nulls_begin -= 1
718720
byte_list = byte_list[:trailing_nulls_begin]
719-
return byte_list.decode("utf-8")
721+
return byte_list.decode(string_encoding)
720722
return unpack_bitstring(byte_list)
721723
if (reg_len := len(registers)) % data_len:
722724
raise ModbusException(
@@ -736,15 +738,17 @@ def convert_from_registers(
736738

737739
@classmethod
738740
def convert_to_registers(
739-
cls, value: int | float | str | list[bool] | list[int] | list[float] , data_type: DATATYPE, word_order: Literal["big", "little"] = "big"
741+
cls, value: int | float | str | list[bool] | list[int] | list[float], data_type: DATATYPE, word_order: Literal["big", "little"] = "big", string_encoding: str = "utf-8"
740742
) -> list[int]:
741743
"""Convert int/float/str to registers (16/32/64 bit).
742744
743745
:param value: value to be converted
744746
:param data_type: data type to convert from
745747
:param word_order: "big"/"little" order of words/registers
748+
:param string_encoding: The encoding with which to encode the bytearray, only used when data_type=DATATYPE.STRING
746749
:returns: List of registers, can be used directly in e.g. write_registers()
747750
:raises TypeError: when there is a mismatch between data_type and value
751+
:raises ParameterException: when the specified string encoding is not supported
748752
"""
749753
if data_type == cls.DATATYPE.BITS:
750754
if not isinstance(value, list):
@@ -755,7 +759,7 @@ def convert_to_registers(
755759
elif data_type == cls.DATATYPE.STRING:
756760
if not isinstance(value, str):
757761
raise TypeError(f"Value should be string but is {type(value)}.")
758-
byte_list = value.encode()
762+
byte_list = value.encode(string_encoding)
759763
if len(byte_list) % 2:
760764
byte_list += b"\x00"
761765
else:

pymodbus/datastore/remote.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -74,16 +74,16 @@ def __build_mapping(self):
7474
params["slave"] = self.slave
7575
self.__get_callbacks = {
7676
"d": lambda a, c: self._client.read_discrete_inputs(
77-
a, c, **params
77+
a, count=c, **params
7878
),
7979
"c": lambda a, c: self._client.read_coils(
80-
a, c, **params
80+
a, count=c, **params
8181
),
8282
"h": lambda a, c: self._client.read_holding_registers(
83-
a, c, **params
83+
a, count=c, **params
8484
),
8585
"i": lambda a, c: self._client.read_input_registers(
86-
a, c, **params
86+
a, count=c, **params
8787
),
8888
}
8989
self.__set_callbacks = {

pymodbus/framer/rtu.py

+3
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ class FramerRTU(FramerBase):
7373
------------------------------------------------------------------
7474
1 Byte = start + 8 bits + parity + stop = 11 bits
7575
(1/Baud)(bits) = delay seconds
76+
77+
.. Danger:: Current framerRTU does not support running the server on a multipoint rs485 line.
78+
7679
"""
7780

7881
MIN_SIZE = 4 # <slave id><function code><crc 2 bytes>

pymodbus/pdu/pdu.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def validateAddress(self, address: int = -1) -> None:
5353
if address == -1:
5454
address = self.address
5555
if not 0 <= address <= 65535:
56-
raise ValueError(f"9 < address {address} < 65535 !")
56+
raise ValueError(f"0 < address {address} < 65535 !")
5757

5858
def __str__(self) -> str:
5959
"""Build a representation of an exception response."""

pymodbus/server/__init__.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
"""Server.
2-
3-
import external classes, to make them easier to use:
4-
"""
1+
"""**Server classes**."""
52

63
__all__ = [
74
"ModbusSerialServer",

pymodbus/server/base.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,14 @@ def __init__( # pylint: disable=too-many-arguments
6060

6161
def callback_new_connection(self):
6262
"""Handle incoming connect."""
63-
return ServerRequestHandler(self)
63+
if self.trace_connect:
64+
self.trace_connect(True)
65+
return ServerRequestHandler(
66+
self,
67+
self.trace_packet,
68+
self.trace_pdu,
69+
self.trace_connect
70+
)
6471

6572
async def shutdown(self):
6673
"""Close server."""

pymodbus/server/requesthandler.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
class ServerRequestHandler(TransactionManager):
1515
"""Handle client connection."""
1616

17-
def __init__(self, owner):
17+
def __init__(self, owner, trace_packet, trace_pdu, trace_connect):
1818
"""Initialize."""
1919
params = CommParams(
2020
comm_name="server",
@@ -34,9 +34,9 @@ def __init__(self, owner):
3434
self.framer,
3535
0,
3636
True,
37-
None,
38-
None,
39-
None,
37+
trace_packet,
38+
trace_pdu,
39+
trace_connect,
4040
)
4141

4242
def callback_new_connection(self) -> ModbusProtocol:

test/client/test_client.py

+29-17
Original file line numberDiff line numberDiff line change
@@ -97,73 +97,85 @@ def fake_execute(_self, _no_response_expected, request):
9797

9898
@pytest.mark.parametrize(("word_order"), ["big", "little", None])
9999
@pytest.mark.parametrize(
100-
("datatype", "value", "registers"),
100+
("datatype", "value", "registers", "string_encoding"),
101101
[
102-
(ModbusClientMixin.DATATYPE.STRING, "abcd", [0x6162, 0x6364]),
103-
(ModbusClientMixin.DATATYPE.STRING, "a", [0x6100]),
104-
(ModbusClientMixin.DATATYPE.UINT16, 27123, [0x69F3]),
105-
(ModbusClientMixin.DATATYPE.INT16, -27123, [0x960D]),
106-
(ModbusClientMixin.DATATYPE.UINT32, 27123, [0x0000, 0x69F3]),
107-
(ModbusClientMixin.DATATYPE.UINT32, 32145678, [0x01EA, 0x810E]),
108-
(ModbusClientMixin.DATATYPE.INT32, -32145678, [0xFE15, 0x7EF2]),
102+
(ModbusClientMixin.DATATYPE.STRING, "abcdÇ", [0x6162, 0x6364, 0xc387], "utf-8"),
103+
(ModbusClientMixin.DATATYPE.STRING, "abcdÇ", [0x6162, 0x6364, 0xc387], None),
104+
(ModbusClientMixin.DATATYPE.STRING, "abcdÇ", [0x6162, 0x6364, 0x8000], "cp437"),
105+
(ModbusClientMixin.DATATYPE.STRING, "a", [0x6100], None),
106+
(ModbusClientMixin.DATATYPE.UINT16, 27123, [0x69F3], None),
107+
(ModbusClientMixin.DATATYPE.INT16, -27123, [0x960D], None),
108+
(ModbusClientMixin.DATATYPE.UINT32, 27123, [0x0000, 0x69F3], None),
109+
(ModbusClientMixin.DATATYPE.UINT32, 32145678, [0x01EA, 0x810E], None),
110+
(ModbusClientMixin.DATATYPE.INT32, -32145678, [0xFE15, 0x7EF2], None),
109111
(
110112
ModbusClientMixin.DATATYPE.UINT64,
111113
1234567890123456789,
112114
[0x1122, 0x10F4, 0x7DE9, 0x8115],
115+
None,
113116
),
114117
(
115118
ModbusClientMixin.DATATYPE.INT64,
116119
-1234567890123456789,
117120
[0xEEDD, 0xEF0B, 0x8216, 0x7EEB],
121+
None,
118122
),
119-
(ModbusClientMixin.DATATYPE.FLOAT32, 27123.5, [0x46D3, 0xE700]),
120-
(ModbusClientMixin.DATATYPE.FLOAT32, 3.141592, [0x4049, 0x0FD8]),
121-
(ModbusClientMixin.DATATYPE.FLOAT32, -3.141592, [0xC049, 0x0FD8]),
122-
(ModbusClientMixin.DATATYPE.FLOAT64, 27123.5, [0x40DA, 0x7CE0, 0x0000, 0x0000]),
123+
(ModbusClientMixin.DATATYPE.FLOAT32, 27123.5, [0x46D3, 0xE700], None),
124+
(ModbusClientMixin.DATATYPE.FLOAT32, 3.141592, [0x4049, 0x0FD8], None),
125+
(ModbusClientMixin.DATATYPE.FLOAT32, -3.141592, [0xC049, 0x0FD8], None),
126+
(ModbusClientMixin.DATATYPE.FLOAT64, 27123.5, [0x40DA, 0x7CE0, 0x0000, 0x0000], None),
123127
(
124128
ModbusClientMixin.DATATYPE.FLOAT64,
125129
3.14159265358979,
126130
[0x4009, 0x21FB, 0x5444, 0x2D11],
131+
None,
127132
),
128133
(
129134
ModbusClientMixin.DATATYPE.FLOAT64,
130135
-3.14159265358979,
131136
[0xC009, 0x21FB, 0x5444, 0x2D11],
137+
None,
132138
),
133139
(
134140
ModbusClientMixin.DATATYPE.BITS,
135141
[True],
136142
[256],
143+
None,
137144
),
138145
(
139146
ModbusClientMixin.DATATYPE.BITS,
140147
[True, False, True],
141148
[1280],
149+
None,
142150
),
143151
(
144152
ModbusClientMixin.DATATYPE.BITS,
145153
[True, False, True] + [False] * 5 + [True],
146154
[1281],
155+
None,
147156
),
148157
(
149158
ModbusClientMixin.DATATYPE.BITS,
150159
[True, False, True] + [False] * 5 + [True] + [False] * 6 + [True],
151160
[1409],
161+
None,
152162
),
153163
(
154164
ModbusClientMixin.DATATYPE.BITS,
155165
[True, False, True] + [False] * 5 + [True] + [False] * 6 + [True] * 2,
156166
[1409, 256],
167+
None,
157168
),
158169
],
159170
)
160-
def test_client_mixin_convert(self, datatype, word_order, registers, value):
171+
def test_client_mixin_convert(self, datatype, word_order, registers, value, string_encoding):
161172
"""Test converter methods."""
162173
if word_order == "little":
163-
x = registers.copy()
164-
x.reverse()
165-
registers = x
166-
kwargs = {"word_order": word_order} if word_order else {}
174+
registers = list(reversed(registers))
175+
176+
kwargs = {**({"word_order": word_order} if word_order else {}),
177+
**({"string_encoding": string_encoding} if string_encoding else {})}
178+
167179
regs = ModbusClientMixin.convert_to_registers(value, datatype, **kwargs)
168180
assert regs == registers
169181
result = ModbusClientMixin.convert_from_registers(registers, datatype, **kwargs)

0 commit comments

Comments
 (0)