moat-lib-modbus 0.0.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.
@@ -0,0 +1,14 @@
1
+ The code in this repository, and all MoaT submodules it refers to,
2
+ is part of the MoaT project.
3
+
4
+ Unless a submodule's LICENSE.txt states otherwise, all included files are
5
+ licensed under the LGPL V3, as published by the FSF at
6
+ https://www.gnu.org/licenses/lgpl-3.0.html .
7
+
8
+ In addition to the LGPL's terms, the author(s) respectfully ask all users of
9
+ this code to contribute any bug fixes or enhancements. Also, please link back to
10
+ https://M-o-a-T.org.
11
+
12
+ Thank you.
13
+
14
+ Copyright © 2021 ff.: the MoaT contributor(s), as per the git changelog(s).
@@ -0,0 +1,65 @@
1
+ Metadata-Version: 2.4
2
+ Name: moat-lib-modbus
3
+ Version: 0.0.2
4
+ Summary: Sans-IO Modbus protocol core (no pymodbus)
5
+ Author-email: Matthias Urlichs <matthias@urlichs.de>
6
+ Maintainer-email: Matthias Urlichs <matthias@urlichs.de>
7
+ Project-URL: homepage, https://m-o-a-t.org
8
+ Project-URL: repository, https://github.com/M-o-a-T/moat
9
+ Keywords: MoaT
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Framework :: AnyIO
12
+ Classifier: Framework :: Trio
13
+ Classifier: Framework :: AsyncIO
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Intended Audience :: Developers
16
+ Requires-Python: >=3.11
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE.txt
19
+ Dynamic: license-file
20
+
21
+ # moat-lib-modbus
22
+
23
+ % start synopsis
24
+ % start main
25
+
26
+ A sans-IO Modbus protocol core implementing the minimal subset used by
27
+ ``moat.modbus``: PDU encoding/decoding for function codes 1, 2, 3, 4,
28
+ 5, 6, 15, 16, plus TCP (MBAP) and RTU framers with a client/server
29
+ role flag, and an RTU-only ``monitor`` role for passive bus sniffing.
30
+
31
+ Stdlib only -- no ``pymodbus``, no ``anyio``.
32
+
33
+ % end synopsis
34
+
35
+ ## Design
36
+
37
+ The library is strictly sans-IO: it never opens a socket, touches a
38
+ serial port, sleeps, waits, or spawns a task. Every component is a
39
+ pure transform -- bytes in -> messages/PDUs out, messages in -> bytes
40
+ out. The only mutable state permitted is a framer's internal byte
41
+ accumulator. All real I/O stays in ``moat.modbus``.
42
+
43
+ ### Role flag
44
+
45
+ The same function-code byte decodes to a request on a server and a
46
+ response on a client. The role is therefore a constructor flag on the
47
+ framer itself: ``FramerTCP(role)`` / ``FramerRTU(role)`` where ``role``
48
+ is ``True`` / ``"server"`` (decode incoming bytes as requests),
49
+ ``False`` / ``"client"`` (decode as responses), or -- for RTU only --
50
+ ``"monitor"`` (passive bus sniffer; see below).
51
+
52
+ ### Monitor mode (RTU only)
53
+
54
+ ``FramerRTU("monitor")`` taps a live master/slave RTU line without
55
+ participating in it. Sending is prohibited (``buildFrame`` raises
56
+ ``ModbusIOError``). Frames are decoded as an alternating
57
+ request/response sequence starting with a request; each decoded frame
58
+ flips the expectation (queryable via ``expecting_request``). Since the
59
+ framer is sans-IO it cannot time the inter-frame gap, so the caller
60
+ calls ``resetFrame()`` on an inter-frame timeout, which clears the
61
+ accumulator and returns the framer to expecting a request -- covering
62
+ both torn partial frames and silent slaves. Monitor mode is RTU-only;
63
+ ``FramerTCP("monitor")`` is rejected.
64
+
65
+ % end main
@@ -0,0 +1,45 @@
1
+ # moat-lib-modbus
2
+
3
+ % start synopsis
4
+ % start main
5
+
6
+ A sans-IO Modbus protocol core implementing the minimal subset used by
7
+ ``moat.modbus``: PDU encoding/decoding for function codes 1, 2, 3, 4,
8
+ 5, 6, 15, 16, plus TCP (MBAP) and RTU framers with a client/server
9
+ role flag, and an RTU-only ``monitor`` role for passive bus sniffing.
10
+
11
+ Stdlib only -- no ``pymodbus``, no ``anyio``.
12
+
13
+ % end synopsis
14
+
15
+ ## Design
16
+
17
+ The library is strictly sans-IO: it never opens a socket, touches a
18
+ serial port, sleeps, waits, or spawns a task. Every component is a
19
+ pure transform -- bytes in -> messages/PDUs out, messages in -> bytes
20
+ out. The only mutable state permitted is a framer's internal byte
21
+ accumulator. All real I/O stays in ``moat.modbus``.
22
+
23
+ ### Role flag
24
+
25
+ The same function-code byte decodes to a request on a server and a
26
+ response on a client. The role is therefore a constructor flag on the
27
+ framer itself: ``FramerTCP(role)`` / ``FramerRTU(role)`` where ``role``
28
+ is ``True`` / ``"server"`` (decode incoming bytes as requests),
29
+ ``False`` / ``"client"`` (decode as responses), or -- for RTU only --
30
+ ``"monitor"`` (passive bus sniffer; see below).
31
+
32
+ ### Monitor mode (RTU only)
33
+
34
+ ``FramerRTU("monitor")`` taps a live master/slave RTU line without
35
+ participating in it. Sending is prohibited (``buildFrame`` raises
36
+ ``ModbusIOError``). Frames are decoded as an alternating
37
+ request/response sequence starting with a request; each decoded frame
38
+ flips the expectation (queryable via ``expecting_request``). Since the
39
+ framer is sans-IO it cannot time the inter-frame gap, so the caller
40
+ calls ``resetFrame()`` on an inter-frame timeout, which clears the
41
+ accumulator and returns the framer to expecting a request -- covering
42
+ both torn partial frames and silent slaves. Monitor mode is RTU-only;
43
+ ``FramerTCP("monitor")`` is rejected.
44
+
45
+ % end main
@@ -0,0 +1,5 @@
1
+ moat-lib-modbus (0.0.2-1) unstable; urgency=medium
2
+
3
+ * Initial release for 26.2.13
4
+
5
+ -- Matthias Urlichs <matthias@urlichs.de> Wed, 23 Sep 2026 18:03:49 +0200
@@ -0,0 +1,17 @@
1
+ Source: moat-lib-modbus
2
+ Maintainer: Matthias Urlichs <matthias@urlichs.de>
3
+ Section: python
4
+ Priority: optional
5
+ Build-Depends: dh-python, python3-all, debhelper (>= 13), debhelper-compat (= 13),
6
+ python3-setuptools,
7
+ python3-wheel,
8
+ Standards-Version: 3.9.6
9
+ Homepage: https://github.com/M-o-a-T/moat
10
+
11
+ Package: moat-lib-modbus
12
+ Architecture: all
13
+ Depends: ${misc:Depends}, ${python3:Depends},
14
+ Description: Sans-IO Modbus protocol core
15
+ A dependency-free, sans-IO implementation of the minimal Modbus
16
+ protocol subset used by moat-modbus: PDU encoding/decoding for
17
+ function codes 1,2,3,4,5,6,15,16, plus TCP (MBAP) and RTU framers.
@@ -0,0 +1,30 @@
1
+ [build-system]
2
+ build-backend = "setuptools.build_meta"
3
+ requires = ["wheel","setuptools"]
4
+
5
+ [project]
6
+ classifiers = [
7
+ "Development Status :: 4 - Beta",
8
+ "Framework :: AnyIO",
9
+ "Framework :: Trio",
10
+ "Framework :: AsyncIO",
11
+ "Programming Language :: Python :: 3",
12
+ "Intended Audience :: Developers",
13
+ ]
14
+ dependencies = []
15
+ keywords = ["MoaT"]
16
+ requires-python = ">=3.11"
17
+ name = "moat-lib-modbus"
18
+ maintainers = [{email = "matthias@urlichs.de", name = "Matthias Urlichs"}]
19
+ description = "Sans-IO Modbus protocol core (no pymodbus)"
20
+ readme = "README.md"
21
+ authors = [{email = "matthias@urlichs.de", name = "Matthias Urlichs"}]
22
+ version = "0.0.2"
23
+
24
+ [project.urls]
25
+ homepage = "https://m-o-a-t.org"
26
+ repository = "https://github.com/M-o-a-T/moat"
27
+
28
+ [tool.setuptools]
29
+ [tool.setuptools.packages.find]
30
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,68 @@
1
+ """
2
+ Sans-IO Modbus protocol core.
3
+
4
+ This library implements the minimal Modbus protocol subset used by
5
+ ``moat.modbus`` -- PDU encoding/decoding for function codes 1, 2, 3,
6
+ 4, 5, 6, 15, 16, plus TCP (MBAP) and RTU framers with a role flag.
7
+
8
+ It is strictly **sans-IO**: no sockets, no serial ports, no sleeping,
9
+ no waiting, no task spawning. Bytes in -> PDUs out, and vice-versa.
10
+ The only mutable state permitted is a framer's internal byte
11
+ accumulator.
12
+
13
+ Stdlib only -- no ``pymodbus``, no ``anyio``.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from ._crc import crc16
19
+ from .errors import ExcCodes, ModbusIOError
20
+ from .framer import FramerRTU, FramerTCP
21
+ from .pdu import (
22
+ PDU,
23
+ ExceptionResponse,
24
+ ReadCoilsRequest,
25
+ ReadCoilsResponse,
26
+ ReadDiscreteInputsRequest,
27
+ ReadDiscreteInputsResponse,
28
+ ReadHoldingRegistersRequest,
29
+ ReadHoldingRegistersResponse,
30
+ ReadInputRegistersRequest,
31
+ ReadInputRegistersResponse,
32
+ WriteMultipleCoilsRequest,
33
+ WriteMultipleCoilsResponse,
34
+ WriteMultipleRegistersRequest,
35
+ WriteMultipleRegistersResponse,
36
+ WriteSingleCoilRequest,
37
+ WriteSingleCoilResponse,
38
+ WriteSingleRegisterRequest,
39
+ WriteSingleRegisterResponse,
40
+ decode_pdu,
41
+ )
42
+
43
+ __all__ = [
44
+ "PDU",
45
+ "ExcCodes",
46
+ "ExceptionResponse",
47
+ "FramerRTU",
48
+ "FramerTCP",
49
+ "ModbusIOError",
50
+ "ReadCoilsRequest",
51
+ "ReadCoilsResponse",
52
+ "ReadDiscreteInputsRequest",
53
+ "ReadDiscreteInputsResponse",
54
+ "ReadHoldingRegistersRequest",
55
+ "ReadHoldingRegistersResponse",
56
+ "ReadInputRegistersRequest",
57
+ "ReadInputRegistersResponse",
58
+ "WriteMultipleCoilsRequest",
59
+ "WriteMultipleCoilsResponse",
60
+ "WriteMultipleRegistersRequest",
61
+ "WriteMultipleRegistersResponse",
62
+ "WriteSingleCoilRequest",
63
+ "WriteSingleCoilResponse",
64
+ "WriteSingleRegisterRequest",
65
+ "WriteSingleRegisterResponse",
66
+ "crc16",
67
+ "decode_pdu",
68
+ ]
@@ -0,0 +1,36 @@
1
+ """
2
+ CRC-16/Modbus checksum.
3
+
4
+ Implements :func:`crc16` per the Modbus specification: polynomial
5
+ ``0xA001`` (bit-reversed ``0x8005``), init ``0xFFFF``, no final XOR,
6
+ LSB-first processing, byte-wise loop (no lookup table).
7
+
8
+ The CRC is appended to RTU frames as two bytes, low byte first.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+
14
+ def crc16(data: bytes) -> int:
15
+ """Compute the CRC-16/Modbus checksum of *data*.
16
+
17
+ Args:
18
+ data: The bytes to checksum.
19
+
20
+ Returns:
21
+ The 16-bit CRC value (0--65535).
22
+
23
+ The algorithm uses polynomial ``0xA001`` (reverse of ``0x8005``),
24
+ initial value ``0xFFFF``, and no final XOR. Bytes are processed
25
+ LSB-first. The result is emitted low byte first when appended to
26
+ a frame.
27
+ """
28
+ crc = 0xFFFF
29
+ for byte in data:
30
+ crc ^= byte
31
+ for _ in range(8):
32
+ if crc & 1:
33
+ crc = (crc >> 1) ^ 0xA001
34
+ else:
35
+ crc >>= 1
36
+ return crc & 0xFFFF
@@ -0,0 +1,49 @@
1
+ """
2
+ Modbus exception codes and I/O error.
3
+
4
+ Defines :class:`ExcCodes` as an :class:`enum.IntEnum` covering the
5
+ standard Modbus exception codes, plus two source-compatibility
6
+ aliases used by the legacy ``moat.modbus`` code, and
7
+ :class:`ModbusIOError` replacing pymodbus'
8
+ ``pymodbus.exceptions.ModbusIOException``.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import enum
14
+
15
+
16
+ class ExcCodes(enum.IntEnum):
17
+ """Standard Modbus exception codes.
18
+
19
+ The aliases :attr:`DEVICE_FAILURE` and :attr:`GATEWAY_NO_RESPONSE`
20
+ are kept for source compatibility with the old ``moat.modbus`` code
21
+ that referenced pymodbus' ``ExcCodes.DEVICE_FAILURE`` /
22
+ ``ExcCodes.GATEWAY_NO_RESPONSE``.
23
+ """
24
+
25
+ ILLEGAL_FUNCTION = 1
26
+ ILLEGAL_DATA_ADDRESS = 2
27
+ ILLEGAL_DATA_VALUE = 3
28
+ SERVER_DEVICE_FAILURE = 4
29
+ ACKNOWLEDGE = 5
30
+ SERVER_DEVICE_BUSY = 6
31
+ NEGATIVE_ACKNOWLEDGE = 7
32
+ MEMORY_PARITY_ERROR = 8
33
+ GATEWAY_PATH_UNAVAILABLE = 10
34
+ GATEWAY_TARGET_NO_RESPONSE = 11
35
+
36
+ # Source-compatibility aliases.
37
+ DEVICE_FAILURE = 4
38
+ """Alias for :attr:`~ExcCodes.SERVER_DEVICE_FAILURE`."""
39
+
40
+ GATEWAY_NO_RESPONSE = 11
41
+ """Alias for :attr:`~ExcCodes.GATEWAY_TARGET_NO_RESPONSE`."""
42
+
43
+
44
+ class ModbusIOError(Exception):
45
+ """Generic Modbus I/O error.
46
+
47
+ Replaces pymodbus' ``ModbusIOException``. Raised when a frame
48
+ cannot be decoded or a protocol violation is detected.
49
+ """