tmodbus 0.1.0__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.
- tmodbus-0.1.0/.gitignore +26 -0
- tmodbus-0.1.0/PKG-INFO +152 -0
- tmodbus-0.1.0/README.md +139 -0
- tmodbus-0.1.0/examples/async_rtu_client.py +49 -0
- tmodbus-0.1.0/examples/async_tcp_client.py +58 -0
- tmodbus-0.1.0/examples/async_tcp_tls_client.py +60 -0
- tmodbus-0.1.0/examples/ruff.toml +7 -0
- tmodbus-0.1.0/pyproject.toml +125 -0
- tmodbus-0.1.0/src/tmodbus/__init__.py +137 -0
- tmodbus-0.1.0/src/tmodbus/_version.py +34 -0
- tmodbus-0.1.0/src/tmodbus/client/__init__.py +5 -0
- tmodbus-0.1.0/src/tmodbus/client/async_client.py +341 -0
- tmodbus-0.1.0/src/tmodbus/const.py +42 -0
- tmodbus-0.1.0/src/tmodbus/exceptions.py +195 -0
- tmodbus-0.1.0/src/tmodbus/pdu/__init__.py +127 -0
- tmodbus-0.1.0/src/tmodbus/pdu/base.py +206 -0
- tmodbus-0.1.0/src/tmodbus/pdu/coils.py +362 -0
- tmodbus-0.1.0/src/tmodbus/pdu/device.py +135 -0
- tmodbus-0.1.0/src/tmodbus/pdu/discrete_inputs.py +11 -0
- tmodbus-0.1.0/src/tmodbus/pdu/holding_registers.py +539 -0
- tmodbus-0.1.0/src/tmodbus/pdu/holding_registers_struct.py +619 -0
- tmodbus-0.1.0/src/tmodbus/transport/__init__.py +13 -0
- tmodbus-0.1.0/src/tmodbus/transport/async_base.py +88 -0
- tmodbus-0.1.0/src/tmodbus/transport/async_rtu.py +346 -0
- tmodbus-0.1.0/src/tmodbus/transport/async_smart.py +245 -0
- tmodbus-0.1.0/src/tmodbus/transport/async_tcp.py +266 -0
- tmodbus-0.1.0/src/tmodbus/utils/__init__.py +1 -0
- tmodbus-0.1.0/src/tmodbus/utils/crc.py +63 -0
- tmodbus-0.1.0/src/tmodbus/utils/raw_traffic_logger.py +29 -0
- tmodbus-0.1.0/src/tmodbus/utils/word_aware_struct.py +135 -0
- tmodbus-0.1.0/tests/__init__.py +1 -0
- tmodbus-0.1.0/tests/client/__init__.py +1 -0
- tmodbus-0.1.0/tests/client/test_async_client.py +397 -0
- tmodbus-0.1.0/tests/pdu/__init__.py +1 -0
- tmodbus-0.1.0/tests/pdu/test_base.py +355 -0
- tmodbus-0.1.0/tests/pdu/test_coils.py +297 -0
- tmodbus-0.1.0/tests/pdu/test_device.py +285 -0
- tmodbus-0.1.0/tests/pdu/test_holding_registers.py +533 -0
- tmodbus-0.1.0/tests/pdu/test_holding_registers_struct.py +846 -0
- tmodbus-0.1.0/tests/pdu/test_pdu.py +274 -0
- tmodbus-0.1.0/tests/ruff.toml +12 -0
- tmodbus-0.1.0/tests/test_exceptions.py +35 -0
- tmodbus-0.1.0/tests/test_init.py +87 -0
- tmodbus-0.1.0/tests/transport/__init__.py +1 -0
- tmodbus-0.1.0/tests/transport/test_async_base.py +55 -0
- tmodbus-0.1.0/tests/transport/test_async_rtu.py +469 -0
- tmodbus-0.1.0/tests/transport/test_async_smart.py +369 -0
- tmodbus-0.1.0/tests/transport/test_async_tcp.py +309 -0
- tmodbus-0.1.0/tests/utils/__init__.py +1 -0
- tmodbus-0.1.0/tests/utils/test_crc.py +17 -0
- tmodbus-0.1.0/tests/utils/test_raw_traffic_logger.py +47 -0
- tmodbus-0.1.0/tests/utils/test_word_aware_struct.py +203 -0
tmodbus-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
*.egg-info
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv
|
|
11
|
+
.python-version
|
|
12
|
+
|
|
13
|
+
# coverage
|
|
14
|
+
.coverage
|
|
15
|
+
|
|
16
|
+
# ruff
|
|
17
|
+
.ruff_cache/
|
|
18
|
+
|
|
19
|
+
# pytest
|
|
20
|
+
.pytest_cache/
|
|
21
|
+
|
|
22
|
+
# docs
|
|
23
|
+
docs/_build/
|
|
24
|
+
|
|
25
|
+
# version file
|
|
26
|
+
src/tmodbus/_version.py
|
tmodbus-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tmodbus
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A modern, strongly typed Modbus client library.
|
|
5
|
+
Author: wlcrs
|
|
6
|
+
Keywords: modbus,modbus-rtu,modbus-tcp
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Provides-Extra: async-rtu
|
|
9
|
+
Requires-Dist: pyserial-asyncio-fast>=0.16; extra == 'async-rtu'
|
|
10
|
+
Provides-Extra: smart
|
|
11
|
+
Requires-Dist: tenacity>=9.1.2; extra == 'smart'
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# tModbus
|
|
15
|
+
[](https://github.com/wlcrs/tmodbus/releases)
|
|
16
|
+
[](https://pypi.org/p/tmodbus/)
|
|
17
|
+
[](https://github.com/wlcrs/tmodbus/actions/workflows/tests.yml)
|
|
18
|
+
|
|
19
|
+
## About
|
|
20
|
+
|
|
21
|
+
A modern Python Modbus library that is fully **t**yped and well-**t**ested.
|
|
22
|
+
|
|
23
|
+
Modbus is based on the [_master/slave_](https://en.wikipedia.org/wiki/Master%E2%80%93slave_(technology)) communication pattern.
|
|
24
|
+
We choose to use the terminology _client_ and _server_ instead, as it is more clear.
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- Pure Python library with minimal dependencies
|
|
29
|
+
- Fully **t**yped
|
|
30
|
+
- Full **t**est coverage
|
|
31
|
+
- Support for both Modbus TCP and RTU clients
|
|
32
|
+
- Support for TCP over SSL connections
|
|
33
|
+
- Auto reconnect and retry functionality (which can be enabled optionally)
|
|
34
|
+
- Extensible with custom Modbus functions and exception codes
|
|
35
|
+
- Open source (BSD)
|
|
36
|
+
|
|
37
|
+
## Supported function codes
|
|
38
|
+
|
|
39
|
+
* Read coils (`0x01`)
|
|
40
|
+
* Read discrete inputs (`0x02`)
|
|
41
|
+
* Read holding registers (`0x03`)
|
|
42
|
+
* Read input registers (`0x04`)
|
|
43
|
+
* Write single coil (`0x05`)
|
|
44
|
+
* Write single register (`0x06`)
|
|
45
|
+
* Write multiple coils (`0x0f`)
|
|
46
|
+
* Write multiple registers (`0x10`)
|
|
47
|
+
* Read device identification (`0x2B / 0x0E`)
|
|
48
|
+
|
|
49
|
+
## Examples
|
|
50
|
+
|
|
51
|
+
A simple example of an Async TCP client:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
import asyncio
|
|
55
|
+
|
|
56
|
+
from tmodbus import create_async_tcp_client
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
async def main() -> None:
|
|
60
|
+
"""Show example of reading a Modbus register."""
|
|
61
|
+
async with create_async_tcp_client("127.0.0.1", 502, unit_id=1) as client:
|
|
62
|
+
response = await client.read_holding_registers(start_address=100, quantity=2)
|
|
63
|
+
print("Contents of holding registers 100 and 101: ", response)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
asyncio.run(main())
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Various examples for Modbus RTU and TCP can be found in the [examples](./examples) folder.
|
|
72
|
+
|
|
73
|
+
## Dependencies
|
|
74
|
+
|
|
75
|
+
**async-rtu**
|
|
76
|
+
|
|
77
|
+
This library uses [pyserial-asyncio-fast](https://pypi.org/project/pyserial-asyncio-fast/) to
|
|
78
|
+
access the serial port when using async RTU.
|
|
79
|
+
|
|
80
|
+
Use `pip install tmodbus[async-rtu]` to install.
|
|
81
|
+
|
|
82
|
+
**smart**
|
|
83
|
+
|
|
84
|
+
This library uses [tenacity](https://github.com/jd/tenacity) to implement the reconnect and retry-logic,
|
|
85
|
+
giving you access to a powerful API to customize the retry behavior of this library.
|
|
86
|
+
|
|
87
|
+
Use `pip install tmodbus[smart]` to install.
|
|
88
|
+
|
|
89
|
+
## Changelog & releases
|
|
90
|
+
|
|
91
|
+
This repository keeps a change log using [GitHub's releases](https://github.com/wlcrs/tmodbus/releases)
|
|
92
|
+
functionality. The format of the log is based on
|
|
93
|
+
[Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
|
|
94
|
+
|
|
95
|
+
Releases are based on [Semantic Versioning](http://semver.org/spec/v2.0.0.html), and use the format
|
|
96
|
+
of `MAJOR.MINOR.PATCH`. In a nutshell, the version will be incremented
|
|
97
|
+
based on the following:
|
|
98
|
+
|
|
99
|
+
- `MAJOR`: Incompatible or major changes.
|
|
100
|
+
- `MINOR`: Backwards-compatible new features and enhancements.
|
|
101
|
+
- `PATCH`: Backwards-compatible bugfixes and package updates.
|
|
102
|
+
|
|
103
|
+
## Contributing
|
|
104
|
+
|
|
105
|
+
This is an active open-source project. We are always open to people who want to
|
|
106
|
+
use the code or contribute to it.
|
|
107
|
+
|
|
108
|
+
We've set up a separate document for our
|
|
109
|
+
[contribution guidelines](.github/CONTRIBUTING.md).
|
|
110
|
+
|
|
111
|
+
Thank you for being involved! :heart_eyes:
|
|
112
|
+
|
|
113
|
+
### Setting up a development environment
|
|
114
|
+
|
|
115
|
+
This Python project is fully managed using the [uv] dependency manager.
|
|
116
|
+
|
|
117
|
+
You need at least:
|
|
118
|
+
|
|
119
|
+
- Python 3.12+
|
|
120
|
+
- [uv][uv-install]
|
|
121
|
+
|
|
122
|
+
To install all packages, including all development requirements:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
uv sync --all-extras --dev
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
As this repository uses the [pre-commit][pre-commit] framework, all changes
|
|
129
|
+
are linted and tested with each commit. You can run all checks and tests
|
|
130
|
+
manually, using the following command:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
uv run pre-commit run --all-files
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
To run just the Python tests:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
uv run pytest
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
## Protocol-Specification
|
|
144
|
+
|
|
145
|
+
- [Modbus Application Protocol Specification v1.1b3 (PDF)](http://modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf)
|
|
146
|
+
- [Modbus over serial line specification and implementation guide v1.02 (PDF)](http://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf)
|
|
147
|
+
- [Modbus Messaging on TCP/IP Implementation Guide v1.0b (PDF)](http://modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
[uv-install]: https://docs.astral.sh/uv/getting-started/installation/
|
|
151
|
+
[uv]: https://docs.astral.sh/uv/
|
|
152
|
+
[pre-commit]: https://pre-commit.com/
|
tmodbus-0.1.0/README.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# tModbus
|
|
2
|
+
[](https://github.com/wlcrs/tmodbus/releases)
|
|
3
|
+
[](https://pypi.org/p/tmodbus/)
|
|
4
|
+
[](https://github.com/wlcrs/tmodbus/actions/workflows/tests.yml)
|
|
5
|
+
|
|
6
|
+
## About
|
|
7
|
+
|
|
8
|
+
A modern Python Modbus library that is fully **t**yped and well-**t**ested.
|
|
9
|
+
|
|
10
|
+
Modbus is based on the [_master/slave_](https://en.wikipedia.org/wiki/Master%E2%80%93slave_(technology)) communication pattern.
|
|
11
|
+
We choose to use the terminology _client_ and _server_ instead, as it is more clear.
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- Pure Python library with minimal dependencies
|
|
16
|
+
- Fully **t**yped
|
|
17
|
+
- Full **t**est coverage
|
|
18
|
+
- Support for both Modbus TCP and RTU clients
|
|
19
|
+
- Support for TCP over SSL connections
|
|
20
|
+
- Auto reconnect and retry functionality (which can be enabled optionally)
|
|
21
|
+
- Extensible with custom Modbus functions and exception codes
|
|
22
|
+
- Open source (BSD)
|
|
23
|
+
|
|
24
|
+
## Supported function codes
|
|
25
|
+
|
|
26
|
+
* Read coils (`0x01`)
|
|
27
|
+
* Read discrete inputs (`0x02`)
|
|
28
|
+
* Read holding registers (`0x03`)
|
|
29
|
+
* Read input registers (`0x04`)
|
|
30
|
+
* Write single coil (`0x05`)
|
|
31
|
+
* Write single register (`0x06`)
|
|
32
|
+
* Write multiple coils (`0x0f`)
|
|
33
|
+
* Write multiple registers (`0x10`)
|
|
34
|
+
* Read device identification (`0x2B / 0x0E`)
|
|
35
|
+
|
|
36
|
+
## Examples
|
|
37
|
+
|
|
38
|
+
A simple example of an Async TCP client:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
import asyncio
|
|
42
|
+
|
|
43
|
+
from tmodbus import create_async_tcp_client
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
async def main() -> None:
|
|
47
|
+
"""Show example of reading a Modbus register."""
|
|
48
|
+
async with create_async_tcp_client("127.0.0.1", 502, unit_id=1) as client:
|
|
49
|
+
response = await client.read_holding_registers(start_address=100, quantity=2)
|
|
50
|
+
print("Contents of holding registers 100 and 101: ", response)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
asyncio.run(main())
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Various examples for Modbus RTU and TCP can be found in the [examples](./examples) folder.
|
|
59
|
+
|
|
60
|
+
## Dependencies
|
|
61
|
+
|
|
62
|
+
**async-rtu**
|
|
63
|
+
|
|
64
|
+
This library uses [pyserial-asyncio-fast](https://pypi.org/project/pyserial-asyncio-fast/) to
|
|
65
|
+
access the serial port when using async RTU.
|
|
66
|
+
|
|
67
|
+
Use `pip install tmodbus[async-rtu]` to install.
|
|
68
|
+
|
|
69
|
+
**smart**
|
|
70
|
+
|
|
71
|
+
This library uses [tenacity](https://github.com/jd/tenacity) to implement the reconnect and retry-logic,
|
|
72
|
+
giving you access to a powerful API to customize the retry behavior of this library.
|
|
73
|
+
|
|
74
|
+
Use `pip install tmodbus[smart]` to install.
|
|
75
|
+
|
|
76
|
+
## Changelog & releases
|
|
77
|
+
|
|
78
|
+
This repository keeps a change log using [GitHub's releases](https://github.com/wlcrs/tmodbus/releases)
|
|
79
|
+
functionality. The format of the log is based on
|
|
80
|
+
[Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
|
|
81
|
+
|
|
82
|
+
Releases are based on [Semantic Versioning](http://semver.org/spec/v2.0.0.html), and use the format
|
|
83
|
+
of `MAJOR.MINOR.PATCH`. In a nutshell, the version will be incremented
|
|
84
|
+
based on the following:
|
|
85
|
+
|
|
86
|
+
- `MAJOR`: Incompatible or major changes.
|
|
87
|
+
- `MINOR`: Backwards-compatible new features and enhancements.
|
|
88
|
+
- `PATCH`: Backwards-compatible bugfixes and package updates.
|
|
89
|
+
|
|
90
|
+
## Contributing
|
|
91
|
+
|
|
92
|
+
This is an active open-source project. We are always open to people who want to
|
|
93
|
+
use the code or contribute to it.
|
|
94
|
+
|
|
95
|
+
We've set up a separate document for our
|
|
96
|
+
[contribution guidelines](.github/CONTRIBUTING.md).
|
|
97
|
+
|
|
98
|
+
Thank you for being involved! :heart_eyes:
|
|
99
|
+
|
|
100
|
+
### Setting up a development environment
|
|
101
|
+
|
|
102
|
+
This Python project is fully managed using the [uv] dependency manager.
|
|
103
|
+
|
|
104
|
+
You need at least:
|
|
105
|
+
|
|
106
|
+
- Python 3.12+
|
|
107
|
+
- [uv][uv-install]
|
|
108
|
+
|
|
109
|
+
To install all packages, including all development requirements:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
uv sync --all-extras --dev
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
As this repository uses the [pre-commit][pre-commit] framework, all changes
|
|
116
|
+
are linted and tested with each commit. You can run all checks and tests
|
|
117
|
+
manually, using the following command:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
uv run pre-commit run --all-files
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
To run just the Python tests:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
uv run pytest
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
## Protocol-Specification
|
|
131
|
+
|
|
132
|
+
- [Modbus Application Protocol Specification v1.1b3 (PDF)](http://modbus.org/docs/Modbus_Application_Protocol_V1_1b3.pdf)
|
|
133
|
+
- [Modbus over serial line specification and implementation guide v1.02 (PDF)](http://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf)
|
|
134
|
+
- [Modbus Messaging on TCP/IP Implementation Guide v1.0b (PDF)](http://modbus.org/docs/Modbus_Messaging_Implementation_Guide_V1_0b.pdf)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
[uv-install]: https://docs.astral.sh/uv/getting-started/installation/
|
|
138
|
+
[uv]: https://docs.astral.sh/uv/
|
|
139
|
+
[pre-commit]: https://pre-commit.com/
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Example of an asynchronous RTU Modbus client using tmodbus."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
from tmodbus import create_async_rtu_client
|
|
6
|
+
from tmodbus.exceptions import InvalidResponseError, ModbusConnectionError, ModbusResponseError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async def example_rtu_client() -> None:
|
|
10
|
+
"""Asynchronous RTU Modbus client example."""
|
|
11
|
+
# Replace with your Modbus server's IP and por
|
|
12
|
+
port = "/dev/ttyUSB0"
|
|
13
|
+
baudrate = 9600 # Adjust baudrate as needed
|
|
14
|
+
|
|
15
|
+
unit_id = 1 # Modbus unit ID of the target device
|
|
16
|
+
|
|
17
|
+
# The create_async_rtu_client function returns an instance of AsyncModbusClient
|
|
18
|
+
client = create_async_rtu_client(port, baudrate=baudrate, unit_id=unit_id)
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
await client.connect()
|
|
22
|
+
# Read 2 holding registers starting at address 100
|
|
23
|
+
coils_response = await client.read_holding_registers(start_address=100, quantity=2)
|
|
24
|
+
|
|
25
|
+
print("Contents of holding registers 100 and 101: ", coils_response)
|
|
26
|
+
|
|
27
|
+
# Write value 123 to holding register at address 1
|
|
28
|
+
await client.write_single_register(address=1, value=123)
|
|
29
|
+
|
|
30
|
+
# Write values [10, 20, 30] to holding registers starting at address 10
|
|
31
|
+
await client.write_multiple_registers(start_address=10, values=[10, 20, 30])
|
|
32
|
+
|
|
33
|
+
except ModbusResponseError as e:
|
|
34
|
+
print(f"The server responded with error code {e.error_code:#04x} for function {e.function_code:#04x}")
|
|
35
|
+
except InvalidResponseError as e:
|
|
36
|
+
print(f"Received invalid response: {e}")
|
|
37
|
+
except ModbusConnectionError as e:
|
|
38
|
+
print(f"A connection error occurred: {e}")
|
|
39
|
+
finally:
|
|
40
|
+
await client.disconnect()
|
|
41
|
+
|
|
42
|
+
# Alternatively, you can use the client as an async context manager
|
|
43
|
+
# which automatically handles connection and disconnection
|
|
44
|
+
async with create_async_rtu_client(port, baudrate=baudrate, unit_id=unit_id) as client2:
|
|
45
|
+
print("Status of coils 0-7: ", await client2.read_coils(start_address=0, quantity=8))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if __name__ == "__main__":
|
|
49
|
+
asyncio.run(example_rtu_client())
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Example of an asynchronous TCP Modbus client using tmodbus."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
from tmodbus import create_async_tcp_client
|
|
6
|
+
from tmodbus.exceptions import InvalidResponseError, ModbusConnectionError, ModbusResponseError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async def example_tcp_client() -> None:
|
|
10
|
+
"""Asynchronous TCP Modbus client example."""
|
|
11
|
+
# Replace with your Modbus server's IP and port
|
|
12
|
+
host = "127.0.0.1"
|
|
13
|
+
port = 502
|
|
14
|
+
|
|
15
|
+
unit_id = 1 # Modbus unit ID of the target device
|
|
16
|
+
|
|
17
|
+
# The create_async_tcp_client function returns an instance of AsyncModbusClient
|
|
18
|
+
client = create_async_tcp_client(host, port, unit_id=unit_id)
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
await client.connect()
|
|
22
|
+
# Read 2 holding registers starting at address 100
|
|
23
|
+
response = await client.read_holding_registers(start_address=100, quantity=2)
|
|
24
|
+
|
|
25
|
+
print("Contents of holding registers 100 and 101: ", response)
|
|
26
|
+
|
|
27
|
+
client_for_unit_id_2 = client.for_unit_id(2)
|
|
28
|
+
response2 = await client_for_unit_id_2.read_holding_registers(start_address=100, quantity=2)
|
|
29
|
+
print("Contents of holding registers 100 and 101 for unit ID 2: ", response2)
|
|
30
|
+
|
|
31
|
+
response6 = await client.for_unit_id(6).read_float(start_address=1)
|
|
32
|
+
print("Float value at address 1 for unit ID 6: ", response6)
|
|
33
|
+
|
|
34
|
+
# Write value 123 to holding register at address 1
|
|
35
|
+
await client.write_single_register(address=1, value=123)
|
|
36
|
+
print("Wrote 123 to holding register at address 1")
|
|
37
|
+
|
|
38
|
+
# Write values [10, 20, 30] to holding registers starting at address 10
|
|
39
|
+
await client.write_multiple_registers(start_address=10, values=[10, 20, 30])
|
|
40
|
+
print("Wrote [10, 20, 30] to holding registers starting at address 10")
|
|
41
|
+
|
|
42
|
+
except ModbusResponseError as e:
|
|
43
|
+
print(f"The server responded with error code {e.error_code:#04x} for function {e.function_code:#04x}")
|
|
44
|
+
except InvalidResponseError as e:
|
|
45
|
+
print(f"Received invalid response: {e}")
|
|
46
|
+
except ModbusConnectionError as e:
|
|
47
|
+
print(f"A connection error occurred: {e}")
|
|
48
|
+
finally:
|
|
49
|
+
await client.disconnect()
|
|
50
|
+
|
|
51
|
+
# Alternatively, you can use the client as an async context manager
|
|
52
|
+
# which automatically handles connection and disconnection
|
|
53
|
+
async with create_async_tcp_client(host, port, unit_id=unit_id) as client2:
|
|
54
|
+
print("Status of coils 0-7: ", await client2.read_coils(start_address=0, quantity=8))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
asyncio.run(example_tcp_client())
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Example of an asynchronous TCP Modbus client over an SSL connection using tmodbus."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import ssl
|
|
5
|
+
|
|
6
|
+
from tmodbus import create_async_tcp_client
|
|
7
|
+
from tmodbus.exceptions import InvalidResponseError, ModbusConnectionError, ModbusResponseError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async def example_tcp_client() -> None:
|
|
11
|
+
"""Asynchronous TCP Modbus client example."""
|
|
12
|
+
# Replace with your Modbus server's IP and port
|
|
13
|
+
host = "127.0.0.1"
|
|
14
|
+
port = 502
|
|
15
|
+
|
|
16
|
+
unit_id = 1 # Modbus unit ID of the target device
|
|
17
|
+
|
|
18
|
+
# You can pass any additional parameters supported by `asyncio.open_connection`
|
|
19
|
+
# For example, to use an SSL context, you can set `ssl=True` or provide a custom SSLContext
|
|
20
|
+
|
|
21
|
+
# The create_async_tcp_client function returns an instance of AsyncModbusClient
|
|
22
|
+
client = create_async_tcp_client(host, port, unit_id=unit_id, ssl=True)
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
await client.connect()
|
|
26
|
+
# Read 2 holding registers starting at address 100
|
|
27
|
+
coils_response = await client.read_holding_registers(start_address=100, quantity=2)
|
|
28
|
+
|
|
29
|
+
print("Contents of holding registers 100 and 101: ", coils_response)
|
|
30
|
+
|
|
31
|
+
# Write value 123 to holding register at address 1
|
|
32
|
+
await client.write_single_register(address=1, value=123)
|
|
33
|
+
|
|
34
|
+
# Write values [10, 20, 30] to holding registers starting at address 10
|
|
35
|
+
await client.write_multiple_registers(start_address=10, values=[10, 20, 30])
|
|
36
|
+
|
|
37
|
+
except ModbusResponseError as e:
|
|
38
|
+
print(f"The server responded with error code {e.error_code:#04x} for function {e.function_code:#04x}")
|
|
39
|
+
except InvalidResponseError as e:
|
|
40
|
+
print(f"Received invalid response: {e}")
|
|
41
|
+
except ModbusConnectionError as e:
|
|
42
|
+
print(f"A connection error occurred: {e}")
|
|
43
|
+
finally:
|
|
44
|
+
await client.disconnect()
|
|
45
|
+
|
|
46
|
+
# Alternatively, you can use the client as an async context manager
|
|
47
|
+
# which automatically handles connection and disconnection.
|
|
48
|
+
# We also demonstrate passing of a custom SSL context here.
|
|
49
|
+
|
|
50
|
+
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
|
51
|
+
ssl_context.load_default_certs(purpose=ssl.Purpose.SERVER_AUTH)
|
|
52
|
+
ssl_context.check_hostname = True
|
|
53
|
+
ssl_context.verify_mode = ssl.CERT_REQUIRED
|
|
54
|
+
|
|
55
|
+
async with create_async_tcp_client(host, port, unit_id=unit_id, ssl=ssl_context) as client2:
|
|
56
|
+
print("Status of coils 0-7: ", await client2.read_coils(start_address=0, quantity=8))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if __name__ == "__main__":
|
|
60
|
+
asyncio.run(example_tcp_client())
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "tmodbus"
|
|
3
|
+
authors= [{name="wlcrs"}]
|
|
4
|
+
description = "A modern, strongly typed Modbus client library."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
keywords = ["modbus", "modbus-tcp", "modbus-rtu"]
|
|
8
|
+
dynamic = ["version"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
[project.optional-dependencies]
|
|
12
|
+
|
|
13
|
+
async-rtu = [
|
|
14
|
+
"pyserial-asyncio-fast>=0.16",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
smart = [
|
|
18
|
+
"tenacity>=9.1.2",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[build-system]
|
|
22
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
23
|
+
build-backend = "hatchling.build"
|
|
24
|
+
|
|
25
|
+
[tool.hatch.build.targets.wheel]
|
|
26
|
+
only-packages = true
|
|
27
|
+
|
|
28
|
+
[tool.hatch.build.targets.sdist]
|
|
29
|
+
only-include = ["examples", "src", "tests"]
|
|
30
|
+
|
|
31
|
+
[tool.hatch.version]
|
|
32
|
+
source = "vcs"
|
|
33
|
+
|
|
34
|
+
[tool.hatch.build.hooks.vcs]
|
|
35
|
+
version-file = "src/tmodbus/_version.py"
|
|
36
|
+
|
|
37
|
+
[tool.ruff]
|
|
38
|
+
line-length = 120
|
|
39
|
+
|
|
40
|
+
[tool.ruff.lint]
|
|
41
|
+
ignore = [
|
|
42
|
+
"ANN401", # Opinioated warning on disallowing dynamically typed expressions
|
|
43
|
+
"D203", # Conflicts with other rules
|
|
44
|
+
"D213", # Conflicts with other rules
|
|
45
|
+
"PLR2004", # Just annoying, not really useful
|
|
46
|
+
"S101", # allow asserts
|
|
47
|
+
|
|
48
|
+
# Conflicts with the Ruff formatter
|
|
49
|
+
"COM812",
|
|
50
|
+
"ISC001",
|
|
51
|
+
|
|
52
|
+
]
|
|
53
|
+
select = ["ALL"]
|
|
54
|
+
|
|
55
|
+
[tool.mypy]
|
|
56
|
+
# Specify the target platform details in config, so your developers are
|
|
57
|
+
# free to run mypy on Windows, Linux, or macOS and get consistent
|
|
58
|
+
# results.
|
|
59
|
+
platform = "linux"
|
|
60
|
+
python_version = "3.12"
|
|
61
|
+
|
|
62
|
+
# show error messages from unrelated files
|
|
63
|
+
follow_imports = "normal"
|
|
64
|
+
|
|
65
|
+
# suppress errors about unsatisfied imports
|
|
66
|
+
ignore_missing_imports = true
|
|
67
|
+
|
|
68
|
+
# be strict
|
|
69
|
+
check_untyped_defs = true
|
|
70
|
+
disallow_any_generics = true
|
|
71
|
+
disallow_incomplete_defs = true
|
|
72
|
+
disallow_subclassing_any = true
|
|
73
|
+
disallow_untyped_calls = true
|
|
74
|
+
disallow_untyped_decorators = true
|
|
75
|
+
disallow_untyped_defs = true
|
|
76
|
+
no_implicit_optional = true
|
|
77
|
+
no_implicit_reexport = true
|
|
78
|
+
strict_optional = true
|
|
79
|
+
warn_incomplete_stub = true
|
|
80
|
+
warn_no_return = true
|
|
81
|
+
warn_redundant_casts = true
|
|
82
|
+
warn_return_any = true
|
|
83
|
+
warn_unused_configs = true
|
|
84
|
+
warn_unused_ignores = true
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
[tool.pytest.ini_options]
|
|
88
|
+
addopts = "--cov"
|
|
89
|
+
asyncio_mode = "auto"
|
|
90
|
+
|
|
91
|
+
[tool.coverage.run]
|
|
92
|
+
plugins = ["covdefaults"]
|
|
93
|
+
source = ["tmodbus"]
|
|
94
|
+
omit = [
|
|
95
|
+
"src/tmodbus/_version.py", # automatically generated by hatch-vcs
|
|
96
|
+
]
|
|
97
|
+
[tool.coverage.report]
|
|
98
|
+
show_missing = true
|
|
99
|
+
fail_under = 100
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
[tool.uv]
|
|
103
|
+
prerelease = "allow"
|
|
104
|
+
|
|
105
|
+
[dependency-groups]
|
|
106
|
+
dev = [
|
|
107
|
+
"codespell==2.4.1",
|
|
108
|
+
"covdefaults==2.3.0",
|
|
109
|
+
"coverage[toml]==7.10.7",
|
|
110
|
+
"mypy==1.18.2",
|
|
111
|
+
"pre-commit==4.3.0",
|
|
112
|
+
"pre-commit-hooks==6.0.0",
|
|
113
|
+
"pytest==8.4.2",
|
|
114
|
+
"pytest-asyncio==1.2.0",
|
|
115
|
+
"pytest-cov==7.0.0",
|
|
116
|
+
"ruff==0.13.3",
|
|
117
|
+
"yamllint==1.37.1",
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
docs = [
|
|
121
|
+
"sphinx>=8.2.3",
|
|
122
|
+
"sphinx-rtd-theme>=3.0.2",
|
|
123
|
+
"sphinx-autobuild",
|
|
124
|
+
"sphinxcontrib-mermaid",
|
|
125
|
+
]
|