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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: modpoll
3
- Version: 1.0.0
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.0.0,<9.0.0)
16
- Requires-Dist: paho-mqtt (>=1.6.1,<2.0.0)
17
- Requires-Dist: prettytable (>=3.6.0,<4.0.0)
18
- Requires-Dist: pymodbus (>=3.5.4,<3.6.0)
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.28.1,<3.0.0)
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
  [![License](https://img.shields.io/github/license/gavinying/modpoll)](https://img.shields.io/github/license/gavinying/modpoll)
30
30
  [![Downloads](https://static.pepy.tech/badge/modpoll/week)](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 initial idea of creating this tool is to help myself debugging new devices during site survey. A site survey usually has limited time and space, working on-site also piles up some pressures. At that time, a portable swiss-knife toolkit is our best friend.
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 program can be easily deployed to Raspberry Pi or similar embedded devices, polling data from modbus devices, users can choose to log data locally or publish to a MQTT broker for further debugging.
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 setup on the same Raspberry Pi or on the cloud. Once data successfully published, users can subscribe to a specific MQTT topic to view the data via a smartphone at your fingertip.
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 program continuously on a server as a Modbus-MQTT gateway, i.e. polling from local Modbus devices and forwarding data to a centralized cloud service.
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 program is designed to be a standalone tool, it works out-of-the-box on Linux/macOS/Windows.
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 program 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`.
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 modbus data to MQTT broker, so users can view data from a smart phone via a MQTT client.
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
- > The MQTT topic uses `<mqtt_topic_prefix>/<deviceid>` pattern, <mqtt_topic_prefix> is provided by `--mqtt-topic-prefix` argument, the default value is `modpoll/` and <deviceid> is provided by the Modbus configure file.
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 `<mqtt_topic_prefix>/<deviceid>/set` once it successfully connected to MQTT broker, user can write device register(s) via MQTT publish,
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 program without local installation,
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 program by default.
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
  [![License](https://img.shields.io/github/license/gavinying/modpoll)](https://img.shields.io/github/license/gavinying/modpoll)
6
6
  [![Downloads](https://static.pepy.tech/badge/modpoll/week)](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 initial idea of creating this tool is to help myself debugging new devices during site survey. A site survey usually has limited time and space, working on-site also piles up some pressures. At that time, a portable swiss-knife toolkit is our best friend.
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 program can be easily deployed to Raspberry Pi or similar embedded devices, polling data from modbus devices, users can choose to log data locally or publish to a MQTT broker for further debugging.
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 setup on the same Raspberry Pi or on the cloud. Once data successfully published, users can subscribe to a specific MQTT topic to view the data via a smartphone at your fingertip.
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 program continuously on a server as a Modbus-MQTT gateway, i.e. polling from local Modbus devices and forwarding data to a centralized cloud service.
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 program is designed to be a standalone tool, it works out-of-the-box on Linux/macOS/Windows.
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 program 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`.
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 modbus data to MQTT broker, so users can view data from a smart phone via a MQTT client.
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
- > The MQTT topic uses `<mqtt_topic_prefix>/<deviceid>` pattern, <mqtt_topic_prefix> is provided by `--mqtt-topic-prefix` argument, the default value is `modpoll/` and <deviceid> is provided by the Modbus configure file.
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 `<mqtt_topic_prefix>/<deviceid>/set` once it successfully connected to MQTT broker, user can write device register(s) via MQTT publish,
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 program without local installation,
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 program by default.
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="modpoll/",
100
- help='Topic prefix for MQTT subscribing/publishing. Defaults to "modpoll/"',
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()