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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: modpoll
3
- Version: 1.1.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)
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.6.0,<4.0.0)
18
- Requires-Dist: pymodbus (>=3.5.4,<3.6.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
 
@@ -102,8 +102,23 @@ def get_parser():
102
102
  )
103
103
  parser.add_argument(
104
104
  "--mqtt-topic-prefix",
105
- default="modpoll/",
106
- 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",
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
- device_name = re.search(
127
- f"^{args.mqtt_topic_prefix}([^/\n]*)/set", topic
128
- ).group(1)
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
- if "coil" == reg["object_type"]:
133
- if modbus_write_coil(device_name, reg["address"], reg["value"]):
134
- logger.info("")
135
- elif "holding_register" == reg["object_type"]:
136
- modbus_write_register(device_name, reg["address"], reg["value"])
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.warning(f"Failed to parse json message: {payload}")
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.info(
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.info(
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.mqtt_topic_prefix}/{dev.name}/{ref.name}"
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 + "/" + str(i), ref_val_entry, qos=args.mqtt_qos
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 = f"{args.mqtt_topic_prefix}/{dev.name}"
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
- for dev in deviceList:
587
- logger.debug(f"Publishing diagnostics for device {dev.name} ...")
588
- payload = {"pollCount": dev.pollCount, "errorCount": dev.errorCount}
589
- topic = f"{args.mqtt_topic_prefix}/diagnostics/{dev.name}"
590
- mqttc_publish(topic, json.dumps(payload), qos=args.mqtt_qos)
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
- if "subscribe_topic" in userdata:
46
- topic = userdata.get("subscribe_topic")
47
- qos = userdata.get("subscribe_qos", 0) # Default to QoS 0
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={"qos": args.mqtt_qos},
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={"qos": args.mqtt_qos},
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.1.0"
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.0.0"
16
- pymodbus = "~3.5.4"
15
+ importlib-metadata = "^8.3.0"
16
+ pymodbus = "~3.6.9"
17
17
  paho-mqtt = "^2.1.0"
18
- prettytable = "^3.6.0"
19
- requests = "^2.28.1"
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