modpoll 1.1.0__tar.gz → 1.2.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.
- {modpoll-1.1.0 → modpoll-1.2.0}/PKG-INFO +21 -19
- {modpoll-1.1.0 → modpoll-1.2.0}/README.md +16 -14
- {modpoll-1.1.0 → modpoll-1.2.0}/modpoll/arg_parser.py +17 -2
- {modpoll-1.1.0 → modpoll-1.2.0}/modpoll/main.py +64 -20
- {modpoll-1.1.0 → modpoll-1.2.0}/modpoll/modbus_task.py +20 -12
- {modpoll-1.1.0 → modpoll-1.2.0}/modpoll/mqtt_task.py +14 -6
- {modpoll-1.1.0 → modpoll-1.2.0}/pyproject.toml +5 -5
- {modpoll-1.1.0 → modpoll-1.2.0}/LICENSE +0 -0
- {modpoll-1.1.0 → modpoll-1.2.0}/modpoll/__init__.py +0 -0
- {modpoll-1.1.0 → modpoll-1.2.0}/modpoll/__main__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: modpoll
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: A New Command-line Tool for Modbus and MQTT
|
|
5
5
|
Home-page: https://github.com/gavinying/modpoll
|
|
6
6
|
Author: Ying Shaodong
|
|
@@ -12,12 +12,12 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
14
|
Provides-Extra: serial
|
|
15
|
-
Requires-Dist: importlib-metadata (>=8.
|
|
15
|
+
Requires-Dist: importlib-metadata (>=8.3.0,<9.0.0)
|
|
16
16
|
Requires-Dist: paho-mqtt (>=2.1.0,<3.0.0)
|
|
17
|
-
Requires-Dist: prettytable (>=3.
|
|
18
|
-
Requires-Dist: pymodbus (>=3.
|
|
17
|
+
Requires-Dist: prettytable (>=3.9.0,<4.0.0)
|
|
18
|
+
Requires-Dist: pymodbus (>=3.6.9,<3.7.0)
|
|
19
19
|
Requires-Dist: pyserial (>=3.5,<4.0) ; extra == "serial"
|
|
20
|
-
Requires-Dist: requests (>=2.
|
|
20
|
+
Requires-Dist: requests (>=2.32.0,<3.0.0)
|
|
21
21
|
Project-URL: Documentation, https://gavinying.github.io/modpoll
|
|
22
22
|
Project-URL: Repository, https://github.com/gavinying/modpoll
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
@@ -29,27 +29,27 @@ Description-Content-Type: text/markdown
|
|
|
29
29
|
[](https://img.shields.io/github/license/gavinying/modpoll)
|
|
30
30
|
[](https://pepy.tech/project/modpoll)
|
|
31
31
|
|
|
32
|
+
Show your love for this project by starring our repo 🌟, so we are encouraged to continue improving.
|
|
33
|
+
|
|
32
34
|
> Learn more about *modpoll* usage at [documentation](https://gavinying.github.io/modpoll) site.
|
|
33
35
|
|
|
34
36
|
## Motivation
|
|
35
37
|
|
|
36
|
-
The
|
|
38
|
+
The idea for creating this tool originated from my need to efficiently check new devices during site surveys. These surveys are often time-constrained and space-limited, with on-site work adding to the pressure. In such situations, a portable Swiss Army knife toolkit becomes a valuable companion.
|
|
37
39
|
|
|
38
|
-
This
|
|
40
|
+
This tool can be easily deployed on a Raspberry Pi or similar embedded devices, polling data from a Modbus network or connected devices. Users can choose to log the data locally or publish it to an MQTT broker for further troubleshooting.
|
|
39
41
|
|
|
40
|
-
The MQTT broker can be
|
|
42
|
+
The MQTT broker can be set up either on the same Raspberry Pi or in the cloud. Once data is successfully published, users can subscribe to the relevant MQTT topics and conveniently view the data on their smartphones.
|
|
41
43
|
|
|
42
44
|
<p align="center">
|
|
43
45
|
<img src="docs/assets/modpoll-usage.png">
|
|
44
46
|
</p>
|
|
45
47
|
|
|
46
|
-
Moreover, you can also run this
|
|
48
|
+
Moreover, you can also run this tool continuously on a server as a Modbus-MQTT gateway, i.e. polling from local Modbus devices and forwarding data to a centralized cloud service.
|
|
47
49
|
|
|
48
50
|
In fact, *modpoll* helps to bridge between the traditional field-bus world and the new IoT world.
|
|
49
51
|
|
|
50
|
-
> This
|
|
51
|
-
|
|
52
|
-
> If you are looing for a modbus python library, please consider the following great open source projects, [pymodbus](https://github.com/riptideio/pymodbus) or [minimalmodbus](https://github.com/pyhys/minimalmodbus)
|
|
52
|
+
> 💡 **Tips:** This tool is designed to be a standalone executable application, which works out-of-the-box on Linux/macOS/Windows. If you are looking for a Modbus python library, please consider the following great open-source projects, [pymodbus](https://github.com/riptideio/pymodbus) or [minimalmodbus](https://github.com/pyhys/minimalmodbus)
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
## Feature
|
|
@@ -63,7 +63,7 @@ In fact, *modpoll* helps to bridge between the traditional field-bus world and t
|
|
|
63
63
|
|
|
64
64
|
## Installation
|
|
65
65
|
|
|
66
|
-
This
|
|
66
|
+
This tool tested on Python 3.8+, the package is available in the [Python Package Index](https://pypi.org/), users can easily install it using `pip` or `pipx`.
|
|
67
67
|
|
|
68
68
|
### Using PIP
|
|
69
69
|
|
|
@@ -108,7 +108,7 @@ pipx upgrade modpoll
|
|
|
108
108
|
Let's start exploring *modpoll* with *modsim* device, run the following command to get a first glimpse,
|
|
109
109
|
|
|
110
110
|
```bash
|
|
111
|
-
modpoll \
|
|
111
|
+
modpoll --once \
|
|
112
112
|
--tcp modsim.topmaker.net \
|
|
113
113
|
--config https://raw.githubusercontent.com/gavinying/modpoll/master/examples/modsim.csv
|
|
114
114
|
```
|
|
@@ -211,7 +211,7 @@ modpoll \
|
|
|
211
211
|
|
|
212
212
|
### Publish data to MQTT broker
|
|
213
213
|
|
|
214
|
-
This is a useful function of this new *modpoll* tool, which provides a simple way to publish collected
|
|
214
|
+
This is a useful function of this new *modpoll* tool, which provides a simple way to publish collected Modbus data to MQTT broker, so users can view data from a smart phone via a MQTT client.
|
|
215
215
|
|
|
216
216
|
The following example uses a public MQTT broker `mqtt.eclipseprojects.io` for test purpose. You can also set up your own MQTT broker locally using [mosquitto](https://mosquitto.org/download/).
|
|
217
217
|
|
|
@@ -224,7 +224,9 @@ modpoll \
|
|
|
224
224
|
|
|
225
225
|
With successful data polling and publishing, you can subscribe the topic `modpoll/modsim` on the same MQTT broker `mqtt.eclipseprojects.io` to view the collected data.
|
|
226
226
|
|
|
227
|
-
|
|
227
|
+
The MQTT topic uses `<mqtt_topic_prefix>/<device_name>` pattern, <mqtt_topic_prefix> is provided by `--mqtt-topic-prefix` argument, the default value is `modpoll` and <device_name> is provided by the Modbus configure file.
|
|
228
|
+
|
|
229
|
+
> ⚠️ **Note:** The `--mqtt-topic-prefix` argument is deprecated and will be removed in the future release. Use `--mqtt-publish-topic-pattern` and `--mqtt-subscribe-topic-pattern` instead. If both are used, `--mqtt-topic-prefix` argument will take precedence in order to keep backward compatibility. See [document](https://gavinying.github.io/modpoll/usage.html#Named%20Arguments) for details.
|
|
228
230
|
|
|
229
231
|
|
|
230
232
|
<p align="center">
|
|
@@ -234,7 +236,7 @@ With successful data polling and publishing, you can subscribe the topic `modpol
|
|
|
234
236
|
|
|
235
237
|
### Write registers via MQTT publish
|
|
236
238
|
|
|
237
|
-
The *modpoll* tool will subscribe to the topic
|
|
239
|
+
The *modpoll* tool will subscribe to the topic `modpoll/<device_name>/set` by default once it successfully connected to MQTT broker, user can write device register(s) via MQTT publish,
|
|
238
240
|
|
|
239
241
|
- To write a single holding register (address at `40001`)
|
|
240
242
|
|
|
@@ -259,13 +261,13 @@ The *modpoll* tool will subscribe to the topic `<mqtt_topic_prefix>/<deviceid>/s
|
|
|
259
261
|
|
|
260
262
|
## Run with docker
|
|
261
263
|
|
|
262
|
-
A docker image has been provided for user to directly run the
|
|
264
|
+
A docker image has been provided for user to directly run the tool without local installation,
|
|
263
265
|
|
|
264
266
|
```bash
|
|
265
267
|
docker run --rm helloysd/modpoll
|
|
266
268
|
```
|
|
267
269
|
|
|
268
|
-
It shows the version of the
|
|
270
|
+
It shows the version of the tool by default.
|
|
269
271
|
|
|
270
272
|
Similar to the above *modsim* test, we can poll data with `docker run`, in order to avoid printing out received data, the argument `--daemon` or `-d` is recommended to use with docker.
|
|
271
273
|
|
|
@@ -5,27 +5,27 @@
|
|
|
5
5
|
[](https://img.shields.io/github/license/gavinying/modpoll)
|
|
6
6
|
[](https://pepy.tech/project/modpoll)
|
|
7
7
|
|
|
8
|
+
Show your love for this project by starring our repo 🌟, so we are encouraged to continue improving.
|
|
9
|
+
|
|
8
10
|
> Learn more about *modpoll* usage at [documentation](https://gavinying.github.io/modpoll) site.
|
|
9
11
|
|
|
10
12
|
## Motivation
|
|
11
13
|
|
|
12
|
-
The
|
|
14
|
+
The idea for creating this tool originated from my need to efficiently check new devices during site surveys. These surveys are often time-constrained and space-limited, with on-site work adding to the pressure. In such situations, a portable Swiss Army knife toolkit becomes a valuable companion.
|
|
13
15
|
|
|
14
|
-
This
|
|
16
|
+
This tool can be easily deployed on a Raspberry Pi or similar embedded devices, polling data from a Modbus network or connected devices. Users can choose to log the data locally or publish it to an MQTT broker for further troubleshooting.
|
|
15
17
|
|
|
16
|
-
The MQTT broker can be
|
|
18
|
+
The MQTT broker can be set up either on the same Raspberry Pi or in the cloud. Once data is successfully published, users can subscribe to the relevant MQTT topics and conveniently view the data on their smartphones.
|
|
17
19
|
|
|
18
20
|
<p align="center">
|
|
19
21
|
<img src="docs/assets/modpoll-usage.png">
|
|
20
22
|
</p>
|
|
21
23
|
|
|
22
|
-
Moreover, you can also run this
|
|
24
|
+
Moreover, you can also run this tool continuously on a server as a Modbus-MQTT gateway, i.e. polling from local Modbus devices and forwarding data to a centralized cloud service.
|
|
23
25
|
|
|
24
26
|
In fact, *modpoll* helps to bridge between the traditional field-bus world and the new IoT world.
|
|
25
27
|
|
|
26
|
-
> This
|
|
27
|
-
|
|
28
|
-
> If you are looing for a modbus python library, please consider the following great open source projects, [pymodbus](https://github.com/riptideio/pymodbus) or [minimalmodbus](https://github.com/pyhys/minimalmodbus)
|
|
28
|
+
> 💡 **Tips:** This tool is designed to be a standalone executable application, which works out-of-the-box on Linux/macOS/Windows. If you are looking for a Modbus python library, please consider the following great open-source projects, [pymodbus](https://github.com/riptideio/pymodbus) or [minimalmodbus](https://github.com/pyhys/minimalmodbus)
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
## Feature
|
|
@@ -39,7 +39,7 @@ In fact, *modpoll* helps to bridge between the traditional field-bus world and t
|
|
|
39
39
|
|
|
40
40
|
## Installation
|
|
41
41
|
|
|
42
|
-
This
|
|
42
|
+
This tool tested on Python 3.8+, the package is available in the [Python Package Index](https://pypi.org/), users can easily install it using `pip` or `pipx`.
|
|
43
43
|
|
|
44
44
|
### Using PIP
|
|
45
45
|
|
|
@@ -84,7 +84,7 @@ pipx upgrade modpoll
|
|
|
84
84
|
Let's start exploring *modpoll* with *modsim* device, run the following command to get a first glimpse,
|
|
85
85
|
|
|
86
86
|
```bash
|
|
87
|
-
modpoll \
|
|
87
|
+
modpoll --once \
|
|
88
88
|
--tcp modsim.topmaker.net \
|
|
89
89
|
--config https://raw.githubusercontent.com/gavinying/modpoll/master/examples/modsim.csv
|
|
90
90
|
```
|
|
@@ -187,7 +187,7 @@ modpoll \
|
|
|
187
187
|
|
|
188
188
|
### Publish data to MQTT broker
|
|
189
189
|
|
|
190
|
-
This is a useful function of this new *modpoll* tool, which provides a simple way to publish collected
|
|
190
|
+
This is a useful function of this new *modpoll* tool, which provides a simple way to publish collected Modbus data to MQTT broker, so users can view data from a smart phone via a MQTT client.
|
|
191
191
|
|
|
192
192
|
The following example uses a public MQTT broker `mqtt.eclipseprojects.io` for test purpose. You can also set up your own MQTT broker locally using [mosquitto](https://mosquitto.org/download/).
|
|
193
193
|
|
|
@@ -200,7 +200,9 @@ modpoll \
|
|
|
200
200
|
|
|
201
201
|
With successful data polling and publishing, you can subscribe the topic `modpoll/modsim` on the same MQTT broker `mqtt.eclipseprojects.io` to view the collected data.
|
|
202
202
|
|
|
203
|
-
|
|
203
|
+
The MQTT topic uses `<mqtt_topic_prefix>/<device_name>` pattern, <mqtt_topic_prefix> is provided by `--mqtt-topic-prefix` argument, the default value is `modpoll` and <device_name> is provided by the Modbus configure file.
|
|
204
|
+
|
|
205
|
+
> ⚠️ **Note:** The `--mqtt-topic-prefix` argument is deprecated and will be removed in the future release. Use `--mqtt-publish-topic-pattern` and `--mqtt-subscribe-topic-pattern` instead. If both are used, `--mqtt-topic-prefix` argument will take precedence in order to keep backward compatibility. See [document](https://gavinying.github.io/modpoll/usage.html#Named%20Arguments) for details.
|
|
204
206
|
|
|
205
207
|
|
|
206
208
|
<p align="center">
|
|
@@ -210,7 +212,7 @@ With successful data polling and publishing, you can subscribe the topic `modpol
|
|
|
210
212
|
|
|
211
213
|
### Write registers via MQTT publish
|
|
212
214
|
|
|
213
|
-
The *modpoll* tool will subscribe to the topic
|
|
215
|
+
The *modpoll* tool will subscribe to the topic `modpoll/<device_name>/set` by default once it successfully connected to MQTT broker, user can write device register(s) via MQTT publish,
|
|
214
216
|
|
|
215
217
|
- To write a single holding register (address at `40001`)
|
|
216
218
|
|
|
@@ -235,13 +237,13 @@ The *modpoll* tool will subscribe to the topic `<mqtt_topic_prefix>/<deviceid>/s
|
|
|
235
237
|
|
|
236
238
|
## Run with docker
|
|
237
239
|
|
|
238
|
-
A docker image has been provided for user to directly run the
|
|
240
|
+
A docker image has been provided for user to directly run the tool without local installation,
|
|
239
241
|
|
|
240
242
|
```bash
|
|
241
243
|
docker run --rm helloysd/modpoll
|
|
242
244
|
```
|
|
243
245
|
|
|
244
|
-
It shows the version of the
|
|
246
|
+
It shows the version of the tool by default.
|
|
245
247
|
|
|
246
248
|
Similar to the above *modsim* test, we can poll data with `docker run`, in order to avoid printing out received data, the argument `--daemon` or `-d` is recommended to use with docker.
|
|
247
249
|
|
|
@@ -102,8 +102,23 @@ def get_parser():
|
|
|
102
102
|
)
|
|
103
103
|
parser.add_argument(
|
|
104
104
|
"--mqtt-topic-prefix",
|
|
105
|
-
default=
|
|
106
|
-
help=
|
|
105
|
+
default=None,
|
|
106
|
+
help="(DEPRECATED) Topic prefix for MQTT subscribing/publishing. Defaults to None",
|
|
107
|
+
)
|
|
108
|
+
parser.add_argument(
|
|
109
|
+
"--mqtt-publish-topic-pattern",
|
|
110
|
+
default="modpoll/<device_name>",
|
|
111
|
+
help='Topic pattern for MQTT publish. Use <device_name> as placeholder for the device names in Modbus config. Defaults to "modpoll/<device_name>"',
|
|
112
|
+
)
|
|
113
|
+
parser.add_argument(
|
|
114
|
+
"--mqtt-subscribe-topic-pattern",
|
|
115
|
+
default="modpoll/<device_name>/set",
|
|
116
|
+
help='Topic pattern for MQTT subscribe. Use <device_name> as placeholder for the device names in Modbus config. Defaults to "modpoll/<device_name>/set"',
|
|
117
|
+
)
|
|
118
|
+
parser.add_argument(
|
|
119
|
+
"--mqtt-diagnostics-topic-pattern",
|
|
120
|
+
default=None,
|
|
121
|
+
help="Topic pattern for MQTT diagnostics. Use <device_name> as placeholder for the device names in Modbus config. Defaults to None",
|
|
107
122
|
)
|
|
108
123
|
parser.add_argument(
|
|
109
124
|
"--mqtt-qos",
|
|
@@ -10,6 +10,7 @@ from datetime import timezone
|
|
|
10
10
|
from modpoll.arg_parser import get_parser
|
|
11
11
|
from modpoll.modbus_task import (
|
|
12
12
|
modbus_setup,
|
|
13
|
+
modbus_device_list,
|
|
13
14
|
modbus_poll,
|
|
14
15
|
modbus_publish,
|
|
15
16
|
modbus_publish_diagnostics,
|
|
@@ -57,36 +58,59 @@ def app(name="modpoll"):
|
|
|
57
58
|
|
|
58
59
|
# parse args
|
|
59
60
|
args = get_parser().parse_args()
|
|
60
|
-
# sanity check
|
|
61
|
-
if args.mqtt_topic_prefix.endswith("/"):
|
|
62
|
-
args.mqtt_topic_prefix = args.mqtt_topic_prefix[:-1]
|
|
63
61
|
|
|
64
62
|
# get logger
|
|
65
63
|
setup_logging(args.loglevel, LOG_SIMPLE)
|
|
66
64
|
global logger
|
|
67
65
|
logger = logging.getLogger(__name__)
|
|
68
66
|
|
|
67
|
+
# setup modbus
|
|
68
|
+
if not modbus_setup(args, event_exit):
|
|
69
|
+
logger.error("Failed to setup modbus client(master)")
|
|
70
|
+
modbus_close()
|
|
71
|
+
mqttc_close()
|
|
72
|
+
exit(1)
|
|
73
|
+
|
|
74
|
+
# Overwrite topic pattern if topic prefix is specified for backward compatibility
|
|
75
|
+
if args.mqtt_topic_prefix:
|
|
76
|
+
logger.warning(
|
|
77
|
+
"(DEPRECATED): The `--mqtt-topic-prefix` argument is deprecated and will be removed in the future release. Use `--mqtt-publish-topic-pattern` and `--mqtt-subscribe-topic-pattern` instead. If both are used, `--mqtt-topic-prefix` argument will take precedence in order to keep backward compatibility."
|
|
78
|
+
)
|
|
79
|
+
if args.mqtt_topic_prefix.endswith("/"):
|
|
80
|
+
args.mqtt_topic_prefix = args.mqtt_topic_prefix[:-1]
|
|
81
|
+
args.mqtt_publish_topic_pattern = f"{args.mqtt_topic_prefix}/<device_name>"
|
|
82
|
+
args.mqtt_subscribe_topic_pattern = (
|
|
83
|
+
f"{args.mqtt_topic_prefix}/<device_name>/set"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# create mqtt subscribe topics
|
|
87
|
+
subscribe_topics = []
|
|
88
|
+
if args.mqtt_subscribe_topic_pattern:
|
|
89
|
+
if "<device_name>" in args.mqtt_subscribe_topic_pattern:
|
|
90
|
+
for device in modbus_device_list():
|
|
91
|
+
topic = args.mqtt_subscribe_topic_pattern.replace(
|
|
92
|
+
"<device_name>", device.name
|
|
93
|
+
)
|
|
94
|
+
subscribe_topics.append(topic)
|
|
95
|
+
else:
|
|
96
|
+
subscribe_topics.append(args.mqtt_subscribe_topic_pattern)
|
|
97
|
+
|
|
69
98
|
# setup mqtt
|
|
70
99
|
if args.mqtt_host:
|
|
71
100
|
logger.info(f"Setup MQTT connection to {args.mqtt_host}")
|
|
72
|
-
if not mqttc_setup(args):
|
|
101
|
+
if not mqttc_setup(args, subscribe_topics):
|
|
73
102
|
logger.error("Failed to setup MQTT client")
|
|
103
|
+
modbus_close()
|
|
74
104
|
mqttc_close()
|
|
75
105
|
exit(1)
|
|
76
106
|
if not mqttc_connect():
|
|
77
107
|
logger.error("Failed to connect to MQTT host")
|
|
108
|
+
modbus_close()
|
|
78
109
|
mqttc_close()
|
|
79
110
|
exit(1)
|
|
80
111
|
else:
|
|
81
112
|
logger.info("No MQTT host specified, skip MQTT setup.")
|
|
82
113
|
|
|
83
|
-
# setup modbus
|
|
84
|
-
if not modbus_setup(args, event_exit):
|
|
85
|
-
logger.error("Failed to setup modbus client(master)")
|
|
86
|
-
modbus_close()
|
|
87
|
-
mqttc_close()
|
|
88
|
-
exit(1)
|
|
89
|
-
|
|
90
114
|
# main loop
|
|
91
115
|
last_check = 0
|
|
92
116
|
last_diag = 0
|
|
@@ -123,19 +147,39 @@ def app(name="modpoll"):
|
|
|
123
147
|
# Check if receive mqtt request
|
|
124
148
|
topic, payload = mqttc_receive()
|
|
125
149
|
if topic and payload:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
)
|
|
150
|
+
topic_regex = args.mqtt_subscribe_topic_pattern.replace(
|
|
151
|
+
"<device_name>", "([^/\n]*)"
|
|
152
|
+
)
|
|
153
|
+
device_name = re.search(topic_regex, topic).group(1)
|
|
129
154
|
if device_name:
|
|
155
|
+
logger.info(f"Received request to write data for device {device_name}")
|
|
130
156
|
try:
|
|
131
157
|
reg = json.loads(payload)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
158
|
+
object_type = reg["object_type"]
|
|
159
|
+
address = reg["address"]
|
|
160
|
+
value = reg["value"]
|
|
161
|
+
if "coil" == object_type:
|
|
162
|
+
if modbus_write_coil(device_name, address, value):
|
|
163
|
+
logger.info(
|
|
164
|
+
f"Successfully write coil(s): device={device_name}, address={address}, value={value}"
|
|
165
|
+
)
|
|
166
|
+
else:
|
|
167
|
+
logger.warning(
|
|
168
|
+
f"Failed to write coil(s): device={device_name}, address={address}, value={value}"
|
|
169
|
+
)
|
|
170
|
+
elif "holding_register" == object_type:
|
|
171
|
+
if modbus_write_register(device_name, address, value):
|
|
172
|
+
logger.info(
|
|
173
|
+
f"Successfully write register(s): device={device_name}, address={address}, value={value}"
|
|
174
|
+
)
|
|
175
|
+
else:
|
|
176
|
+
logger.warning(
|
|
177
|
+
f"Failed to write register(s): device={device_name}, address={address}, value={value}"
|
|
178
|
+
)
|
|
179
|
+
except KeyError:
|
|
180
|
+
logger.error(f"No required key found: {payload}")
|
|
137
181
|
except json.decoder.JSONDecodeError:
|
|
138
|
-
logger.
|
|
182
|
+
logger.error(f"Failed to parse json message: {payload}")
|
|
139
183
|
if args.once:
|
|
140
184
|
event_exit.set()
|
|
141
185
|
break
|
|
@@ -410,6 +410,11 @@ def load_config(file) -> bool:
|
|
|
410
410
|
return False
|
|
411
411
|
|
|
412
412
|
|
|
413
|
+
def modbus_device_list() -> []:
|
|
414
|
+
global deviceList
|
|
415
|
+
return deviceList
|
|
416
|
+
|
|
417
|
+
|
|
413
418
|
def modbus_setup(config, event) -> bool:
|
|
414
419
|
global args
|
|
415
420
|
args = config
|
|
@@ -494,7 +499,7 @@ def modbus_poll():
|
|
|
494
499
|
modbus_print()
|
|
495
500
|
|
|
496
501
|
|
|
497
|
-
def modbus_write_coil(device_name, address: int, value):
|
|
502
|
+
def modbus_write_coil(device_name: str, address: int, value) -> bool:
|
|
498
503
|
if not master:
|
|
499
504
|
return False
|
|
500
505
|
master.connect()
|
|
@@ -502,7 +507,7 @@ def modbus_write_coil(device_name, address: int, value):
|
|
|
502
507
|
logger.debug(f"Master connected. Delay of {args.delay} seconds.")
|
|
503
508
|
for d in deviceList:
|
|
504
509
|
if d.name == device_name:
|
|
505
|
-
logger.
|
|
510
|
+
logger.debug(
|
|
506
511
|
f"Writing coil(s): device={device_name}, address={address}, value={value}"
|
|
507
512
|
)
|
|
508
513
|
if isinstance(value, int):
|
|
@@ -514,7 +519,7 @@ def modbus_write_coil(device_name, address: int, value):
|
|
|
514
519
|
return False
|
|
515
520
|
|
|
516
521
|
|
|
517
|
-
def modbus_write_register(device_name, address: int, value):
|
|
522
|
+
def modbus_write_register(device_name, address: int, value) -> bool:
|
|
518
523
|
if not master:
|
|
519
524
|
return False
|
|
520
525
|
master.connect()
|
|
@@ -522,7 +527,7 @@ def modbus_write_register(device_name, address: int, value):
|
|
|
522
527
|
logger.debug(f"Master connected. Delay of {args.delay} seconds.")
|
|
523
528
|
for d in deviceList:
|
|
524
529
|
if d.name == device_name:
|
|
525
|
-
logger.
|
|
530
|
+
logger.debug(
|
|
526
531
|
f"Writing register(s): device={device_name}, address={address}, value={value}"
|
|
527
532
|
)
|
|
528
533
|
if isinstance(value, int):
|
|
@@ -567,27 +572,30 @@ def modbus_publish(timestamp=None, on_change=False):
|
|
|
567
572
|
else:
|
|
568
573
|
payload[f"{ref.name}"] = ref.val
|
|
569
574
|
if args.mqtt_single:
|
|
570
|
-
topic = f"{args.
|
|
575
|
+
topic = f"{args.mqtt_publish_topic_pattern.replace('<device_name>', dev.name)}/{ref.name}"
|
|
571
576
|
if isinstance(ref.val, list):
|
|
572
577
|
for i, ref_val_entry in enumerate(ref.val):
|
|
573
578
|
mqttc_publish(
|
|
574
|
-
topic
|
|
579
|
+
f"{topic}/{str(i)}", ref_val_entry, qos=args.mqtt_qos
|
|
575
580
|
)
|
|
576
581
|
else:
|
|
577
582
|
mqttc_publish(topic, ref.val, qos=args.mqtt_qos)
|
|
578
583
|
if timestamp:
|
|
579
584
|
payload["timestamp_ms"] = int(timestamp * 1000)
|
|
580
585
|
if not args.mqtt_single:
|
|
581
|
-
topic =
|
|
586
|
+
topic = args.mqtt_publish_topic_pattern.replace("<device_name>", dev.name)
|
|
582
587
|
mqttc_publish(topic, json.dumps(payload), qos=args.mqtt_qos)
|
|
583
588
|
|
|
584
589
|
|
|
585
590
|
def modbus_publish_diagnostics():
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
+
if args.mqtt_diagnostics_topic_pattern:
|
|
592
|
+
for dev in deviceList:
|
|
593
|
+
logger.debug(f"Publishing diagnostics for device {dev.name} ...")
|
|
594
|
+
payload = {"pollCount": dev.pollCount, "errorCount": dev.errorCount}
|
|
595
|
+
topic = args.mqtt_diagnostics_topic_pattern.replace(
|
|
596
|
+
"<device_name>", dev.name
|
|
597
|
+
)
|
|
598
|
+
mqttc_publish(topic, json.dumps(payload), qos=args.mqtt_qos)
|
|
591
599
|
|
|
592
600
|
|
|
593
601
|
def modbus_export(file, timestamp=None):
|
|
@@ -42,9 +42,9 @@ def _on_connect(client, userdata, flags, reason_code, properties):
|
|
|
42
42
|
logger.info("New session created")
|
|
43
43
|
# Subscribing in on_connect() means that if we lose the connection and
|
|
44
44
|
# reconnect then subscriptions will be renewed.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
topics = userdata.get("subscribe_topics", [])
|
|
46
|
+
qos = userdata.get("subscribe_qos", 0) # Default to QoS 0
|
|
47
|
+
for topic in topics:
|
|
48
48
|
logger.info(f"Subscribe to topic: {topic} with QoS: {qos}")
|
|
49
49
|
client.subscribe(topic, qos)
|
|
50
50
|
else:
|
|
@@ -87,7 +87,7 @@ def _on_log(client, userdata, level, buf):
|
|
|
87
87
|
logger.debug(f"{level} | {buf}")
|
|
88
88
|
|
|
89
89
|
|
|
90
|
-
def mqttc_setup(config: Any) -> bool:
|
|
90
|
+
def mqttc_setup(config: Any, subscribe_topics: []) -> bool:
|
|
91
91
|
global args, logger, mqttc, clean_start_or_session
|
|
92
92
|
args = config
|
|
93
93
|
logger = logging.getLogger(__name__)
|
|
@@ -99,11 +99,16 @@ def mqttc_setup(config: Any) -> bool:
|
|
|
99
99
|
|
|
100
100
|
clean_start_or_session = args.mqtt_qos == 0
|
|
101
101
|
|
|
102
|
+
sub_topic = args.mqtt_subscribe_topic_pattern
|
|
103
|
+
|
|
102
104
|
if args.mqtt_version == "5.0":
|
|
103
105
|
mqttc = MQTTClient(
|
|
104
106
|
CallbackAPIVersion.VERSION2,
|
|
105
107
|
client_id=clientid,
|
|
106
|
-
userdata={
|
|
108
|
+
userdata={
|
|
109
|
+
"subscribe_topics": subscribe_topics,
|
|
110
|
+
"subscribe_qos": args.mqtt_qos,
|
|
111
|
+
},
|
|
107
112
|
protocol=MQTTProtocolVersion.MQTTv5,
|
|
108
113
|
)
|
|
109
114
|
else:
|
|
@@ -111,7 +116,10 @@ def mqttc_setup(config: Any) -> bool:
|
|
|
111
116
|
CallbackAPIVersion.VERSION2,
|
|
112
117
|
client_id=clientid,
|
|
113
118
|
clean_session=clean_start_or_session,
|
|
114
|
-
userdata={
|
|
119
|
+
userdata={
|
|
120
|
+
"subscribe_topics": subscribe_topics,
|
|
121
|
+
"subscribe_qos": args.mqtt_qos,
|
|
122
|
+
},
|
|
115
123
|
protocol=MQTTProtocolVersion.MQTTv311,
|
|
116
124
|
)
|
|
117
125
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "modpoll"
|
|
3
|
-
version = "v1.
|
|
3
|
+
version = "v1.2.0"
|
|
4
4
|
description = "A New Command-line Tool for Modbus and MQTT"
|
|
5
5
|
authors = ["Ying Shaodong <helloysd@gmail.com>"]
|
|
6
6
|
repository = "https://github.com/gavinying/modpoll"
|
|
@@ -12,11 +12,11 @@ packages = [
|
|
|
12
12
|
|
|
13
13
|
[tool.poetry.dependencies]
|
|
14
14
|
python = ">=3.8.2,<4.0"
|
|
15
|
-
importlib-metadata = "^8.
|
|
16
|
-
pymodbus = "~3.
|
|
15
|
+
importlib-metadata = "^8.3.0"
|
|
16
|
+
pymodbus = "~3.6.9"
|
|
17
17
|
paho-mqtt = "^2.1.0"
|
|
18
|
-
prettytable = "^3.
|
|
19
|
-
requests = "^2.
|
|
18
|
+
prettytable = "^3.9.0"
|
|
19
|
+
requests = "^2.32.0"
|
|
20
20
|
pyserial = { version = "^3.5", optional = true }
|
|
21
21
|
|
|
22
22
|
[tool.poetry.extras]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|