modbus-crc 2.0__py3-none-any.whl
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.
modbus_crc/__init__.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CRC-16 calculation for the Modbus protocol.
|
|
3
|
+
|
|
4
|
+
The checksum is CRC-16/MODBUS: polynomial 0xA001 (reflected 0x8005),
|
|
5
|
+
initial value 0xFFFF, no final XOR, transmitted low byte first.
|
|
6
|
+
|
|
7
|
+
Copyright (c) 2026 webtoucher
|
|
8
|
+
Distributed under the BSD 3-Clause license. See LICENSE for more info.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from typing import Union
|
|
12
|
+
|
|
13
|
+
__all__ = ["crc16", "add_crc", "check_crc"]
|
|
14
|
+
|
|
15
|
+
__version__ = "2.0"
|
|
16
|
+
|
|
17
|
+
BytesLike = Union[bytes, bytearray, memoryview]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _build_crc_table(polynomial: int = 0xA001) -> tuple:
|
|
21
|
+
"""Build the 256-entry lookup table for CRC-16/MODBUS."""
|
|
22
|
+
table = []
|
|
23
|
+
for index in range(256):
|
|
24
|
+
crc = index
|
|
25
|
+
for _ in range(8):
|
|
26
|
+
crc = (crc >> 1) ^ polynomial if crc & 1 else crc >> 1
|
|
27
|
+
table.append(crc)
|
|
28
|
+
return tuple(table)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
_CRC_TABLE = _build_crc_table()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _crc_register(data: BytesLike) -> int:
|
|
35
|
+
"""Return the raw 16-bit CRC register value of data."""
|
|
36
|
+
crc = 0xFFFF
|
|
37
|
+
for byte in data:
|
|
38
|
+
crc = (crc >> 8) ^ _CRC_TABLE[(crc ^ byte) & 0xFF]
|
|
39
|
+
return crc
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def crc16(data: BytesLike) -> bytes:
|
|
43
|
+
"""Calculate CRC-16/MODBUS of data, low byte first as on the wire."""
|
|
44
|
+
crc = _crc_register(data)
|
|
45
|
+
return bytes((crc & 0xFF, crc >> 8))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def add_crc(package: BytesLike) -> bytes:
|
|
49
|
+
"""Return package with CRC-16/MODBUS appended."""
|
|
50
|
+
return bytes(package) + crc16(package)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def check_crc(package: BytesLike) -> bool:
|
|
54
|
+
"""Return True if package carries a valid trailing CRC-16/MODBUS."""
|
|
55
|
+
return _crc_register(package) == 0
|
modbus_crc/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: modbus-crc
|
|
3
|
+
Version: 2.0
|
|
4
|
+
Summary: CRC-16 calculation for Modbus protocol
|
|
5
|
+
Author-email: Alexey Kuznetsov <webtoucher@ya.ru>
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/webtoucher/modbus-crc
|
|
8
|
+
Project-URL: Repository, https://github.com/webtoucher/modbus-crc
|
|
9
|
+
Project-URL: Issues, https://github.com/webtoucher/modbus-crc/issues
|
|
10
|
+
Keywords: modbus,crc,crc16,checksum,serial,rtu
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
14
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
15
|
+
Classifier: Operating System :: POSIX
|
|
16
|
+
Classifier: Operating System :: Unix
|
|
17
|
+
Classifier: Operating System :: MacOS
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE.md
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# Modbus CRC-16
|
|
27
|
+
|
|
28
|
+

|
|
29
|
+
[](https://pypi.python.org/pypi/modbus-crc)
|
|
30
|
+
[](https://pypi.python.org/pypi/modbus-crc)
|
|
31
|
+
[](https://pypi.python.org/pypi/modbus-crc)
|
|
32
|
+
[](https://github.com/webtoucher/modbus-crc/actions/workflows/tests.yml)
|
|
33
|
+
|
|
34
|
+
CRC-16 calculation library for Modbus protocol. Tiny, dependency-free and fast: a single
|
|
35
|
+
256-entry lookup table, no runtime dependencies, type hints included.
|
|
36
|
+
|
|
37
|
+
## Algorithm
|
|
38
|
+
|
|
39
|
+
The checksum is **CRC-16/MODBUS**:
|
|
40
|
+
|
|
41
|
+
| Parameter | Value |
|
|
42
|
+
|-----------------------|-------------------------------------------|
|
|
43
|
+
| Width | 16 bits |
|
|
44
|
+
| Polynomial | `0x8005` (reflected: `0xA001`) |
|
|
45
|
+
| Initial value | `0xFFFF` |
|
|
46
|
+
| Reflect in / out | yes / yes |
|
|
47
|
+
| Final XOR | `0x0000` |
|
|
48
|
+
| Check (`"123456789"`) | `0x4B37`, transmitted as `b'\x37\x4B'` |
|
|
49
|
+
| Byte order on wire | low byte first |
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
Install it with pip:
|
|
54
|
+
|
|
55
|
+
```shell
|
|
56
|
+
$ pip install modbus-crc
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Or you can add it as dependency in requirements.txt file of your python application:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
modbus-crc~=2.0
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Usage
|
|
66
|
+
|
|
67
|
+
For signing byte package use `add_crc`:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from modbus_crc import add_crc
|
|
71
|
+
|
|
72
|
+
signed_package = add_crc(b'\x00\x12\x34\xAB\xCD\xEF')
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Result is `b'\x00\x12\x34\xAB\xCD\xEF\xD2\xD4'`. For validation signed package use `check_crc`:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from modbus_crc import check_crc
|
|
79
|
+
|
|
80
|
+
if not check_crc(b'\x00\x12\x34\xAB\xCD\xEF\xD2\xD4'):
|
|
81
|
+
raise SomeException()
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
To get the checksum alone (low byte first, exactly as it is transmitted on the wire), use `crc16`:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from modbus_crc import crc16
|
|
88
|
+
|
|
89
|
+
crc16(b'\x01\x03\x00\x00\x00\x0A') # b'\xC5\xCD'
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Here is a real Modbus RTU request — "read 10 holding registers from slave 1":
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
add_crc(b'\x01\x03\x00\x00\x00\x0A') # b'\x01\x03\x00\x00\x00\x0A\xC5\xCD'
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`check_crc` validates the whole signed frame, so it works with data read straight from
|
|
99
|
+
the serial port, without splitting off the trailing bytes:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
check_crc(b'\x01\x03\x00\x00\x00\x0A\xC5\xCD') # True
|
|
103
|
+
check_crc(b'\x01\x03\x00\x00\x00\x0A\xC5\xCE') # False
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## API
|
|
107
|
+
|
|
108
|
+
All three functions accept `bytes`, `bytearray` and `memoryview` (declared as `BytesLike`).
|
|
109
|
+
|
|
110
|
+
| Function | Returns | Description |
|
|
111
|
+
|---------------------------------|---------|-----------------------------------------------------------------|
|
|
112
|
+
| `crc16(data)` | `bytes` | 2-byte checksum of `data`, low byte first |
|
|
113
|
+
| `add_crc(package)` | `bytes` | `package` with the checksum appended; input type is normalized |
|
|
114
|
+
| `check_crc(package)` | `bool` | `True` if `package` carries a valid trailing checksum |
|
|
115
|
+
|
|
116
|
+
Notes:
|
|
117
|
+
|
|
118
|
+
- `add_crc` always returns `bytes`, even for `bytearray` and `memoryview` input.
|
|
119
|
+
- `check_crc(b'')` is `False`: an empty package has no valid checksum.
|
|
120
|
+
- Functions are pure: they keep no state between calls. The lookup table is built once,
|
|
121
|
+
at import time.
|
|
122
|
+
|
|
123
|
+
## Compatibility
|
|
124
|
+
|
|
125
|
+
- Python **3.9 and newer**, including free-threaded builds. Tested on 3.9 and 3.15 and
|
|
126
|
+
in CI on every version from 3.9 to 3.15.
|
|
127
|
+
- No runtime dependencies.
|
|
128
|
+
- Type hints are shipped with the package (`py.typed`, PEP 561), so mypy and pyright
|
|
129
|
+
see real signatures instead of `Any`.
|
|
130
|
+
|
|
131
|
+
## Development
|
|
132
|
+
|
|
133
|
+
```shell
|
|
134
|
+
$ python -m venv .venv
|
|
135
|
+
$ .venv\Scripts\activate # Windows; on POSIX: source .venv/bin/activate
|
|
136
|
+
$ pip install -e ".[dev]" # installs the package and pytest
|
|
137
|
+
$ python -m pytest
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The test suite covers:
|
|
141
|
+
|
|
142
|
+
- canonical Modbus frames with published checksums,
|
|
143
|
+
- an independent bit-by-bit implementation written from the specification, used as an
|
|
144
|
+
oracle against the fast table-driven code (including random payloads up to 1 KiB),
|
|
145
|
+
- edge cases: empty package, one-byte packages, `bytearray` / `memoryview` input,
|
|
146
|
+
- detection of every single-byte corruption of a signed frame.
|
|
147
|
+
|
|
148
|
+
Project layout:
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
modbus_crc/__init__.py the whole library
|
|
152
|
+
tests/test_modbus_crc.py test suite
|
|
153
|
+
pyproject.toml metadata, build config and pytest settings
|
|
154
|
+
.github/workflows/ CI: tests on Python 3.9-3.15, sdist/wheel build check
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
To build and verify a release:
|
|
158
|
+
|
|
159
|
+
```shell
|
|
160
|
+
$ pip install build twine
|
|
161
|
+
$ python -m build
|
|
162
|
+
$ python -m twine check dist/*
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Migrating from 1.x
|
|
166
|
+
|
|
167
|
+
The public API is unchanged, so most projects need no code changes. What did change:
|
|
168
|
+
|
|
169
|
+
- **Python 3.9+ is required.** Python 2 support and the legacy `setup.py` / `setup.cfg`
|
|
170
|
+
are gone; metadata now lives in `pyproject.toml` (PEP 621).
|
|
171
|
+
- **`LOW_BYTES` and `HIGH_BYTES` were removed.** Those were two 256-byte tables with
|
|
172
|
+
inverted naming that leaked from the implementation. The lookup table is now generated
|
|
173
|
+
at import time and is private — use the documented functions instead.
|
|
174
|
+
- **`add_crc` always returns `bytes`.** Previously `add_crc(bytearray(...))` returned a
|
|
175
|
+
`bytearray`; the result type is now stable regardless of the input type.
|
|
176
|
+
- **`modbus_crc.__version__` is available** and is the single source of truth for the
|
|
177
|
+
package version.
|
|
178
|
+
- **The package ships `py.typed`**, so type checkers no longer treat it as untyped.
|
|
179
|
+
- **Faster and clearer internals:** one table instead of two, a single 16-bit register
|
|
180
|
+
instead of two byte variables, no `int()` call per byte (over 20% faster per KB on
|
|
181
|
+
CPython 3.11).
|
|
182
|
+
|
|
183
|
+
## License
|
|
184
|
+
|
|
185
|
+
Distributed under the BSD 3-Clause license. See [LICENSE.md](LICENSE.md) for details.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
modbus_crc/__init__.py,sha256=1IrVei-2BOf9Fm9dcjp6LuscdbLr5hTITbCrXU9HrVQ,1492
|
|
2
|
+
modbus_crc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
modbus_crc-2.0.dist-info/licenses/LICENSE.md,sha256=Y4f1dDEJp690QgZ4S1ilY4Lbb-2re6hUdAeLA2sMXIg,1495
|
|
4
|
+
modbus_crc-2.0.dist-info/METADATA,sha256=u3CfUcbJ4RGcN5MEwOYE7qR6b4FCYdhfyViCp_r22Cw,6968
|
|
5
|
+
modbus_crc-2.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
6
|
+
modbus_crc-2.0.dist-info/top_level.txt,sha256=AafRKooQK4rOOe2tq8tnLq-AIG3v0zqAiYuzoSvlY6c,11
|
|
7
|
+
modbus_crc-2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Copyright (c) 2023, Alexey Kuznetsov
|
|
2
|
+
|
|
3
|
+
All rights reserved.
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
6
|
+
|
|
7
|
+
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
8
|
+
|
|
9
|
+
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
10
|
+
|
|
11
|
+
* Neither the names of Alexey Kuznetsov or webtoucher nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
|
12
|
+
|
|
13
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
modbus_crc
|