modpoll 1.0.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.0.0 → modpoll-1.2.0}/PKG-INFO +22 -20
- {modpoll-1.0.0 → modpoll-1.2.0}/README.md +16 -14
- {modpoll-1.0.0 → modpoll-1.2.0}/modpoll/arg_parser.py +23 -2
- modpoll-1.2.0/modpoll/main.py +191 -0
- {modpoll-1.0.0 → modpoll-1.2.0}/modpoll/modbus_task.py +77 -60
- modpoll-1.2.0/modpoll/mqtt_task.py +226 -0
- {modpoll-1.0.0 → modpoll-1.2.0}/pyproject.toml +6 -6
- modpoll-1.0.0/modpoll/main.py +0 -126
- modpoll-1.0.0/modpoll/mqtt_task.py +0 -173
- {modpoll-1.0.0 → modpoll-1.2.0}/LICENSE +0 -0
- {modpoll-1.0.0 → modpoll-1.2.0}/modpoll/__init__.py +0 -0
- {modpoll-1.0.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.
|
|
16
|
-
Requires-Dist: paho-mqtt (>=1.
|
|
17
|
-
Requires-Dist: prettytable (>=3.
|
|
18
|
-
Requires-Dist: pymodbus (>=3.
|
|
15
|
+
Requires-Dist: importlib-metadata (>=8.3.0,<9.0.0)
|
|
16
|
+
Requires-Dist: paho-mqtt (>=2.1.0,<3.0.0)
|
|
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
|
|
|
@@ -78,6 +78,12 @@ def get_parser():
|
|
|
78
78
|
default=None,
|
|
79
79
|
help="The file name to export references/registers",
|
|
80
80
|
)
|
|
81
|
+
parser.add_argument(
|
|
82
|
+
"--mqtt-version",
|
|
83
|
+
choices=["3.1.1", "5.0"],
|
|
84
|
+
default="3.1.1",
|
|
85
|
+
help="MQTT version. Defaults to MQTT v3.1.1",
|
|
86
|
+
)
|
|
81
87
|
parser.add_argument(
|
|
82
88
|
"--mqtt-host",
|
|
83
89
|
default=None,
|
|
@@ -96,8 +102,23 @@ def get_parser():
|
|
|
96
102
|
)
|
|
97
103
|
parser.add_argument(
|
|
98
104
|
"--mqtt-topic-prefix",
|
|
99
|
-
default=
|
|
100
|
-
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",
|
|
101
122
|
)
|
|
102
123
|
parser.add_argument(
|
|
103
124
|
"--mqtt-qos",
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
import re
|
|
5
|
+
import signal
|
|
6
|
+
import sys
|
|
7
|
+
import threading
|
|
8
|
+
from datetime import timezone
|
|
9
|
+
|
|
10
|
+
from modpoll.arg_parser import get_parser
|
|
11
|
+
from modpoll.modbus_task import (
|
|
12
|
+
modbus_setup,
|
|
13
|
+
modbus_device_list,
|
|
14
|
+
modbus_poll,
|
|
15
|
+
modbus_publish,
|
|
16
|
+
modbus_publish_diagnostics,
|
|
17
|
+
modbus_write_coil,
|
|
18
|
+
modbus_write_register,
|
|
19
|
+
modbus_export,
|
|
20
|
+
modbus_close,
|
|
21
|
+
)
|
|
22
|
+
from modpoll.mqtt_task import (
|
|
23
|
+
mqttc_setup,
|
|
24
|
+
mqttc_connect,
|
|
25
|
+
mqttc_receive,
|
|
26
|
+
mqttc_close,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
from . import __version__
|
|
30
|
+
|
|
31
|
+
LOG_SIMPLE = "%(asctime)s | %(levelname).1s | %(name)s | %(message)s"
|
|
32
|
+
logger = None
|
|
33
|
+
event_exit = threading.Event()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def setup_logging(level, format):
|
|
37
|
+
logging.basicConfig(level=level, format=format)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _signal_handler(signal, frame):
|
|
41
|
+
logger.info(f"Exiting {sys.argv[0]}")
|
|
42
|
+
event_exit.set()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_utc_time():
|
|
46
|
+
dt = datetime.datetime.now(timezone.utc)
|
|
47
|
+
utc_time = dt.replace(tzinfo=timezone.utc)
|
|
48
|
+
return utc_time.timestamp()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def app(name="modpoll"):
|
|
52
|
+
print(
|
|
53
|
+
f"\nModpoll v{__version__} - A New Command-line Tool for Modbus and MQTT\n",
|
|
54
|
+
flush=True,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
signal.signal(signal.SIGINT, _signal_handler)
|
|
58
|
+
|
|
59
|
+
# parse args
|
|
60
|
+
args = get_parser().parse_args()
|
|
61
|
+
|
|
62
|
+
# get logger
|
|
63
|
+
setup_logging(args.loglevel, LOG_SIMPLE)
|
|
64
|
+
global logger
|
|
65
|
+
logger = logging.getLogger(__name__)
|
|
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
|
+
|
|
98
|
+
# setup mqtt
|
|
99
|
+
if args.mqtt_host:
|
|
100
|
+
logger.info(f"Setup MQTT connection to {args.mqtt_host}")
|
|
101
|
+
if not mqttc_setup(args, subscribe_topics):
|
|
102
|
+
logger.error("Failed to setup MQTT client")
|
|
103
|
+
modbus_close()
|
|
104
|
+
mqttc_close()
|
|
105
|
+
exit(1)
|
|
106
|
+
if not mqttc_connect():
|
|
107
|
+
logger.error("Failed to connect to MQTT host")
|
|
108
|
+
modbus_close()
|
|
109
|
+
mqttc_close()
|
|
110
|
+
exit(1)
|
|
111
|
+
else:
|
|
112
|
+
logger.info("No MQTT host specified, skip MQTT setup.")
|
|
113
|
+
|
|
114
|
+
# main loop
|
|
115
|
+
last_check = 0
|
|
116
|
+
last_diag = 0
|
|
117
|
+
while not event_exit.is_set():
|
|
118
|
+
now = get_utc_time()
|
|
119
|
+
# routine check
|
|
120
|
+
if now > last_check + args.rate:
|
|
121
|
+
if last_check == 0:
|
|
122
|
+
elapsed = args.rate
|
|
123
|
+
else:
|
|
124
|
+
elapsed = round(now - last_check, 6)
|
|
125
|
+
last_check = now
|
|
126
|
+
logger.info(
|
|
127
|
+
f" ====== modpoll polling at rate:{args.rate}s, actual:{elapsed}s ======"
|
|
128
|
+
)
|
|
129
|
+
modbus_poll()
|
|
130
|
+
if event_exit.is_set():
|
|
131
|
+
break
|
|
132
|
+
if args.mqtt_host:
|
|
133
|
+
if args.timestamp:
|
|
134
|
+
modbus_publish(timestamp=now)
|
|
135
|
+
else:
|
|
136
|
+
modbus_publish()
|
|
137
|
+
if args.export:
|
|
138
|
+
if args.timestamp:
|
|
139
|
+
modbus_export(args.export, timestamp=now)
|
|
140
|
+
else:
|
|
141
|
+
modbus_export(args.export)
|
|
142
|
+
if args.diagnostics_rate > 0 and now > last_diag + args.diagnostics_rate:
|
|
143
|
+
last_diag = now
|
|
144
|
+
modbus_publish_diagnostics()
|
|
145
|
+
if event_exit.is_set():
|
|
146
|
+
break
|
|
147
|
+
# Check if receive mqtt request
|
|
148
|
+
topic, payload = mqttc_receive()
|
|
149
|
+
if topic and payload:
|
|
150
|
+
topic_regex = args.mqtt_subscribe_topic_pattern.replace(
|
|
151
|
+
"<device_name>", "([^/\n]*)"
|
|
152
|
+
)
|
|
153
|
+
device_name = re.search(topic_regex, topic).group(1)
|
|
154
|
+
if device_name:
|
|
155
|
+
logger.info(f"Received request to write data for device {device_name}")
|
|
156
|
+
try:
|
|
157
|
+
reg = json.loads(payload)
|
|
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}")
|
|
181
|
+
except json.decoder.JSONDecodeError:
|
|
182
|
+
logger.error(f"Failed to parse json message: {payload}")
|
|
183
|
+
if args.once:
|
|
184
|
+
event_exit.set()
|
|
185
|
+
break
|
|
186
|
+
modbus_close()
|
|
187
|
+
mqttc_close()
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
if __name__ == "__main__":
|
|
191
|
+
app()
|