Skip to content

Commit 1ea0c6f

Browse files
authored
Correct bit handling (each byte is LSB->MSB). (#2707)
* Revert "Bit handling LSB -> MSB across bytes. (#2634)" This reverts commit 968564c. * Correct and test bit handling.
1 parent 2c948c6 commit 1ea0c6f

File tree

5 files changed

+64
-49
lines changed

5 files changed

+64
-49
lines changed

API_changes.rst

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,22 @@ API changes
22
===========
33
Versions (X.Y.Z) where Z > 0 e.g. 3.0.1 do NOT have API changes!
44

5+
API changes 3.11.0
6+
------------------
7+
- Revert wrong byte handling in v3.10.0
8+
bit handling order is LSB-> MSB for each byte
9+
REMARK: word are ordered depending on big/little endian
10+
readCoils and other bit functions now return bit in logical order (NOT byte order)
11+
12+
Example:
13+
Hex bytes: 0x00 0x01
14+
delivers False * 8 True False * 7
15+
16+
Hex bytes: 0x01 0x03
17+
delivers True False * 7 True True False * 6
18+
519
API changes 3.10.0
6-
-----------------
20+
------------------
721
- ModbusSlaveContext replaced by ModbusDeviceContext
822
- payload removed (replaced by "convert_to/from_registers")
923
- slave=, slaves= replaced by device_id=, device_ids=

doc/source/upgrade_40.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ Pymodbus 4.0 upgrade procedure
44
Pymodbus 4.0 contains a number of incompatibilities with Pymodbus 3.x, however
55
most of these are simple edits.
66

7-
87
Python 3.9
98
----------
109
Python 3.9 is reaching end of life and from october 2025 no longer receives security updates.

pymodbus/pdu/pdu.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -166,14 +166,15 @@ def pack_bitstring(bits: list[bool], align_byte=True) -> bytes:
166166
bits_extra = 8 if align_byte else 16
167167
if (extra := len(bits) % bits_extra):
168168
t_bits += [False] * (bits_extra - extra)
169-
for bit in reversed(t_bits):
170-
packed <<= 1
171-
if bit:
172-
packed += 1
173-
i += 1
174-
if i == 8:
175-
ret += struct.pack(">B", packed)
176-
i = packed = 0
169+
for byte_inx in range(0, len(t_bits), 8):
170+
for bit in reversed(t_bits[byte_inx:byte_inx+8]):
171+
packed <<= 1
172+
if bit:
173+
packed += 1
174+
i += 1
175+
if i == 8:
176+
ret += struct.pack(">B", packed)
177+
i = packed = 0
177178
return ret
178179

179180

@@ -185,13 +186,11 @@ def unpack_bitstring(data: bytes) -> list[bool]:
185186
bytes 0x05 0x81
186187
result = unpack_bitstring(bytes)
187188
188-
[True, False, False, False] +
189-
[False, False, False, True] +
190-
[True, False, True, False] +
191-
[False, False, False, False]
189+
[True, False, True, False] + [False, False, False, False]
190+
[True, False, False, False] + [False, False, False, True]
192191
"""
193192
res = []
194-
for byte_index in range(len(data) -1, -1, -1):
193+
for _, t_byte in enumerate(data):
195194
for bit in (1, 2, 4, 8, 16, 32, 64, 128):
196-
res.append(bool(data[byte_index] & bit))
195+
res.append(bool(t_byte & bit))
197196
return res

test/client/test_client.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -170,55 +170,55 @@ def fake_execute(_self, _no_response_expected, request):
170170
(
171171
ModbusClientMixin.DATATYPE.BITS,
172172
[True],
173-
[1], # 0x00 0x01
173+
[256], # 0x01 0x00
174174
None,
175175
),
176176
(
177177
ModbusClientMixin.DATATYPE.BITS,
178178
[True] + [False] * 15,
179-
[1], # 0x00 0x01
179+
[256], # 0x01 0x00
180180
None,
181181
),
182182
(
183183
ModbusClientMixin.DATATYPE.BITS,
184184
[False] * 8 + [True] + [False] * 7,
185-
[256], # 0x01 0x00
185+
[1], # 0x00 0x01
186186
None,
187187
),
188188
(
189189
ModbusClientMixin.DATATYPE.BITS,
190190
[False] * 15 + [True],
191-
[32768], # 0x80 0x00
191+
[128], # 0x00 0x80
192192
None,
193193
),
194194
(
195195
ModbusClientMixin.DATATYPE.BITS,
196196
[True] + [False] * 14 + [True],
197-
[32769], # 0x80 0x01
197+
[384], # 0x01 0x80
198198
None,
199199
),
200200
(
201201
ModbusClientMixin.DATATYPE.BITS,
202202
[False] * 8 + [True, False, True] + [False] * 5,
203-
[1280], # 0x05 0x00
203+
[5], # 0x00 0x05
204204
None,
205205
),
206206
(
207207
ModbusClientMixin.DATATYPE.BITS,
208208
[True] + [False] * 7 + [True, False, True] + [False] * 5,
209-
[1281], # 0x05 0x01
209+
[261], # 0x01 0x05
210210
None,
211211
),
212212
(
213213
ModbusClientMixin.DATATYPE.BITS,
214214
[True] + [False] * 6 + [True, True, False, True] + [False] * 5,
215-
[1409], # 0x05 0x81
215+
[33029], # 0x81 0x05
216216
None,
217217
),
218218
(
219219
ModbusClientMixin.DATATYPE.BITS,
220220
[False] * 8 + [True] + [False] * 7 + [True] + [False] * 6 + [True, True, False, True] + [False] * 5,
221-
[1409, 256], # 92340480 = 0x05 0x81 0x01 0x00
221+
[1, 33029], # 92340480 = 0x00 0x01 0x81 0x05
222222
None,
223223
),
224224
],

test/pdu/test_pdu.py

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -255,21 +255,21 @@ async def test_pdu_default_datastore(self, mock_context):
255255
@pytest.mark.parametrize(
256256
("bytestream", "bitlist"),
257257
[
258-
(b"\x00\x01", [True] + [False] * 15),
259-
(b"\x01\x00", [False] * 8 + [True] + [False] * 7),
260-
(b"\x80\x00", [False] * 15 + [True]),
261-
(b"\x80\x01", [True] + [False] * 14 + [True]),
262-
(b"\x05\x00", [False] * 8 + [True, False, True] + [False] * 5),
263-
(b"\x05\x01", [True] + [False] * 7 + [True, False, True] + [False] * 5),
264-
(b"\x05\x81", [True] + [False] * 6 + [True, True, False, True] + [False] * 5),
265-
(b"\x05\x81\x01\x00", [False] * 8 + [True] + [False] * 7 + [True] + [False] * 6 + [True, True, False, True] + [False] * 5),
258+
(b"\x01\x00", [True] + [False] * 15),
259+
(b"\x00\x80", [False] * 15 + [True]),
260+
(b"\x00\x01", [False] * 8 + [True] + [False] * 7),
261+
(b"\x01\x80", [True] + [False] * 14 + [True]),
262+
(b"\x00\x05", [False] * 8 + [True, False, True] + [False] * 5),
263+
(b"\x01\x05", [True] + [False] * 7 + [True, False, True] + [False] * 5),
264+
(b"\x81\x05", [True] + [False] * 6 + [True, True, False, True] + [False] * 5),
265+
(b"\x00\x01\x81\x05", [False] * 8 + [True] + [False] * 7 + [True] + [False] * 6 + [True, True, False, True] + [False] * 5),
266266
267-
(b"\x00\x01", [True]),
268-
(b"\x01\x00", [False] * 8 + [True]),
269-
(b"\x05\x00", [False] * 8 + [True, False, True]),
270-
(b"\x05\x01", [True] + [False] * 7 + [True, False, True]),
271-
(b"\x05\x81", [True] + [False] * 6 + [True, True, False, True]),
272-
(b"\x05\x81\x01\x00", [False] * 8 + [True] + [False] * 7 + [True] + [False] * 6 + [True, True, False, True]),
267+
(b"\x01\x00", [True]),
268+
(b"\x00\x01", [False] * 8 + [True]),
269+
(b"\x00\x05", [False] * 8 + [True, False, True]),
270+
(b"\x01\x05", [True] + [False] * 7 + [True, False, True]),
271+
(b"\x81\x05", [True] + [False] * 6 + [True, True, False, True]),
272+
(b"\x00\x01\x81\x05", [False] * 8 + [True] + [False] * 7 + [True] + [False] * 6 + [True, True, False, True]),
273273
],
274274
)
275275
def test_bit_packing(self, bytestream, bitlist):
@@ -280,9 +280,9 @@ def test_bit_packing(self, bytestream, bitlist):
280280
("bytestream", "bitlist"),
281281
[
282282
(b"\x01", [True]),
283-
(b"\x01\x00", [False] * 8 + [True]),
283+
(b"\x00\x01", [False] * 8 + [True]),
284284
(b"\x05", [True, False, True]),
285-
(b"\x05\x01", [True] + [False] * 7 + [True, False, True]),
285+
(b"\x01\x05", [True] + [False] * 7 + [True, False, True]),
286286
],
287287
)
288288
def test_bit_packing8(self, bytestream, bitlist):
@@ -293,14 +293,17 @@ def test_bit_packing8(self, bytestream, bitlist):
293293
("bytestream", "bitlist"),
294294
[
295295
(b"\x01", [True] + [False] * 7),
296-
(b"\x00\x01", [True] + [False] * 15),
297-
(b"\x01\x00", [False] * 8 + [True] + [False] * 7),
298-
(b"\x80\x00", [False] * 15 + [True]),
299-
(b"\x80\x01", [True] + [False] * 14 + [True]),
300-
(b"\x05\x00", [False] * 8 + [True, False, True] + [False] * 5),
301-
(b"\x05\x01", [True] + [False] * 7 + [True, False, True] + [False] * 5),
302-
(b"\x05\x81", [True] + [False] * 6 + [True, True, False, True] + [False] * 5),
303-
(b"\x05\x81\x01\x00", [False] * 8 + [True] + [False] * 7 + [True] + [False] * 6 + [True, True, False, True] + [False] * 5),
296+
(b"\x01\x00", [True] + [False] * 15),
297+
(b"\x00\x01", [False] * 8 + [True] + [False] * 7),
298+
(b"\x00\x80", [False] * 15 + [True]),
299+
(b"\x01\x80", [True] + [False] * 14 + [True]),
300+
(b"\x00\x05", [False] * 8 + [True, False, True] + [False] * 5),
301+
(b"\x01\x05", [True] + [False] * 7 + [True, False, True] + [False] * 5),
302+
(b"\x81\x05", [True] + [False] * 6 + [True, True, False, True] + [False] * 5),
303+
(b"\x05\x81\x01\x00", [True, False, True] + [False] * 5 +
304+
[True] + [False] * 6 + [True] +
305+
[True] + [False] * 7 +
306+
[False] * 8),
304307
],
305308
)
306309
def test_bit_unpacking(self, bytestream, bitlist):

0 commit comments

Comments
 (0)