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.
- moat_lib_modbus-0.0.2/LICENSE.txt +14 -0
- moat_lib_modbus-0.0.2/PKG-INFO +65 -0
- moat_lib_modbus-0.0.2/README.md +45 -0
- moat_lib_modbus-0.0.2/debian/changelog +5 -0
- moat_lib_modbus-0.0.2/debian/control +17 -0
- moat_lib_modbus-0.0.2/pyproject.toml +30 -0
- moat_lib_modbus-0.0.2/setup.cfg +4 -0
- moat_lib_modbus-0.0.2/src/moat/lib/modbus/__init__.py +68 -0
- moat_lib_modbus-0.0.2/src/moat/lib/modbus/_crc.py +36 -0
- moat_lib_modbus-0.0.2/src/moat/lib/modbus/errors.py +49 -0
- moat_lib_modbus-0.0.2/src/moat/lib/modbus/framer.py +385 -0
- moat_lib_modbus-0.0.2/src/moat/lib/modbus/pdu.py +814 -0
- moat_lib_modbus-0.0.2/src/moat_lib_modbus.egg-info/PKG-INFO +65 -0
- moat_lib_modbus-0.0.2/src/moat_lib_modbus.egg-info/SOURCES.txt +14 -0
- moat_lib_modbus-0.0.2/src/moat_lib_modbus.egg-info/dependency_links.txt +1 -0
- moat_lib_modbus-0.0.2/src/moat_lib_modbus.egg-info/top_level.txt +1 -0
|
@@ -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,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,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
|
+
"""
|