boneIO 0.8.0.dev8__tar.gz → 0.8.0.dev10__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.
Files changed (103) hide show
  1. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/PKG-INFO +1 -1
  2. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/bonecli.py +57 -7
  3. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/helper/filter.py +3 -2
  4. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/helper/gpio.py +29 -8
  5. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/helper/loader.py +1 -0
  6. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/helper/stats.py +8 -6
  7. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/modbus/client.py +9 -0
  8. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/modbus/cwt.json +1 -1
  9. boneIO-0.8.0.dev10/boneio/modbus/liquid-sensor.json +37 -0
  10. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/modbus/modbuscli.py +91 -18
  11. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/modbus/orno-or-we-517.json +8 -0
  12. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/modbus/r4dcb08.json +7 -1
  13. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/modbus/sensor.py +16 -2
  14. boneIO-0.8.0.dev10/boneio/modbus/sht20.json +37 -0
  15. boneIO-0.8.0.dev8/boneio/modbus/socomec_e23.json → boneIO-0.8.0.dev10/boneio/modbus/socomec_e03.json +1 -1
  16. boneIO-0.8.0.dev10/boneio/modbus/socomec_e23.json +164 -0
  17. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/schema/schema.yaml +14 -0
  18. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/sensor/gpio_new.py +18 -4
  19. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/sensor/ina219.py +9 -13
  20. boneIO-0.8.0.dev10/boneio/version.py +2 -0
  21. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/pyproject.toml +1 -1
  22. boneIO-0.8.0.dev8/boneio/version.py +0 -2
  23. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/LICENSE +0 -0
  24. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/README.md +0 -0
  25. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/__init__.py +0 -0
  26. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/const.py +0 -0
  27. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/cover.py +0 -0
  28. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/example_config/__init__.py +0 -0
  29. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/example_config/adc.yaml +0 -0
  30. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/example_config/binary_sensor.yaml +0 -0
  31. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/example_config/binary_sensor_v_0_7.yaml +0 -0
  32. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/example_config/config.yaml +0 -0
  33. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/example_config/cover.yaml +0 -0
  34. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/example_config/event.yaml +0 -0
  35. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/example_config/event_all.yaml +0 -0
  36. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/example_config/event_v_0_7.yaml +0 -0
  37. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/example_config/led32x4A.yaml +0 -0
  38. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/example_config/output24x16A.yaml +0 -0
  39. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/example_config/output24x16A_v0.3.yaml +0 -0
  40. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/example_config/output32x10A.yaml +0 -0
  41. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/example_config/output32x5A.yaml +0 -0
  42. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/fonts/danube__.ttf +0 -0
  43. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/group/__init__.py +0 -0
  44. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/group/output.py +0 -0
  45. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/helper/__init__.py +0 -0
  46. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/helper/async_updater.py +0 -0
  47. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/helper/click_timer.py +0 -0
  48. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/helper/config.py +0 -0
  49. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/helper/events.py +0 -0
  50. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/helper/exceptions.py +0 -0
  51. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/helper/ha_discovery.py +0 -0
  52. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/helper/logger.py +0 -0
  53. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/helper/mqtt.py +0 -0
  54. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/helper/oled.py +0 -0
  55. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/helper/onewire/W1ThermSensor.py +0 -0
  56. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/helper/onewire/__init__.py +0 -0
  57. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/helper/onewire/ds2482.py +0 -0
  58. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/helper/onewire/onewire.py +0 -0
  59. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/helper/pcf8575.py +0 -0
  60. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/helper/queue.py +0 -0
  61. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/helper/sensor/ina_219_smbus.py +0 -0
  62. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/helper/state_manager.py +0 -0
  63. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/helper/timeperiod.py +0 -0
  64. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/helper/util.py +0 -0
  65. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/helper/yaml_util.py +0 -0
  66. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/input/__init__.py +0 -0
  67. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/input/gpio.py +0 -0
  68. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/input/gpio_new.py +0 -0
  69. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/manager.py +0 -0
  70. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/modbus/__init__.py +0 -0
  71. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/modbus/dts1964_3f.json +0 -0
  72. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/modbus/pt100.json +0 -0
  73. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/modbus/sdm120.json +0 -0
  74. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/modbus/sdm630.json +0 -0
  75. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/modbus/sofar.json +0 -0
  76. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/modbus/utils.py +0 -0
  77. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/mqtt_client.py +0 -0
  78. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/oled.py +0 -0
  79. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/relay/__init__.py +0 -0
  80. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/relay/basic.py +0 -0
  81. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/relay/gpio.py +0 -0
  82. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/relay/mcp.py +0 -0
  83. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/relay/pca.py +0 -0
  84. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/relay/pcf.py +0 -0
  85. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/runner.py +1 -1
  86. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/schema/actions.yaml +0 -0
  87. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/schema/actions_sensor.yaml +0 -0
  88. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/schema/actions_switch.yaml +0 -0
  89. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/schema/filters.yaml +0 -0
  90. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/schema/filters_adc.yaml +0 -0
  91. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/schema/id.yaml +0 -0
  92. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/schema/temp_unit.yaml +0 -0
  93. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/schema/update_interval.yaml +0 -0
  94. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/sensor/__init__.py +0 -0
  95. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/sensor/adc.py +0 -0
  96. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/sensor/gpio.py +0 -0
  97. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/sensor/temp/__init__.py +0 -0
  98. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/sensor/temp/dallas.py +0 -0
  99. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/sensor/temp/lm75.py +0 -0
  100. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/boneio/sensor/temp/mcp9808.py +0 -0
  101. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/tests/32_5_config.yaml +0 -0
  102. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/tests/bandit.yaml +0 -0
  103. {boneIO-0.8.0.dev8 → boneIO-0.8.0.dev10}/tests/relay_32_5.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: boneIO
3
- Version: 0.8.0.dev8
3
+ Version: 0.8.0.dev10
4
4
  Summary: Python App for BoneIO
5
5
  License: GNU General Public License v3.0
6
6
  Author-email: Paweł Szafer <pszafer@gmail.com>
@@ -13,7 +13,11 @@ import sys
13
13
  from colorlog import ColoredFormatter
14
14
  from yaml import MarkedYAMLError
15
15
 
16
- from boneio.modbus.modbuscli import async_run_modbus_set, async_run_modbus_get
16
+ from boneio.modbus.modbuscli import (
17
+ async_run_modbus_set,
18
+ async_run_modbus_get,
19
+ async_run_modbus_search,
20
+ )
17
21
  from boneio.modbus.client import VALUE_TYPES
18
22
 
19
23
 
@@ -99,13 +103,14 @@ def get_arguments() -> argparse.Namespace:
99
103
  modbus_parser.add_argument(
100
104
  "--address",
101
105
  type=lambda x: int(x, 0),
102
- required=True,
106
+ required=False,
107
+ default=1,
103
108
  help="Current device address (hex or integer)",
104
109
  )
105
110
  modbus_parser.add_argument(
106
111
  "--baudrate",
107
112
  type=int,
108
- choices=[2400, 4800, 9600, 19200],
113
+ choices=[2400, 4800, 9600, 14400, 19200],
109
114
  required=True,
110
115
  help="Current baudrate",
111
116
  )
@@ -139,11 +144,21 @@ def get_arguments() -> argparse.Namespace:
139
144
  dest="modbus_action", required=True
140
145
  )
141
146
  set_modbus_parser = modbus_sub_parser.add_parser("set")
147
+ set_modbus_parser.add_argument(
148
+ "--custom-value",
149
+ type=int,
150
+ help="Set Custom value",
151
+ )
152
+ set_modbus_parser.add_argument(
153
+ "--custom-register-address",
154
+ type=int,
155
+ help="Register address for custom value",
156
+ )
142
157
  set_modbus_parser.add_argument(
143
158
  "--device",
144
159
  type=str,
145
- choices=["cwt", "r4dcb08"],
146
- help="Choose device to set modbus address/baudrate",
160
+ choices=["cwt", "r4dcb08", "liquid-sensor", "sht20", "custom"],
161
+ help="Choose device to set modbus address/baudrate. For custom you must provide --custom-value and --custom-register-address",
147
162
  required=True,
148
163
  )
149
164
  set_modbus_parser_group = set_modbus_parser.add_mutually_exclusive_group()
@@ -182,6 +197,23 @@ def get_arguments() -> argparse.Namespace:
182
197
  required=True,
183
198
  )
184
199
  parser.add_argument("--version", action="version", version=__version__)
200
+ search_modbus_parser = modbus_sub_parser.add_parser(
201
+ name="search",
202
+ help="Search for device. Iterate over every address 1-253 with provided register address",
203
+ )
204
+ search_modbus_parser.add_argument(
205
+ "--register-address",
206
+ type=int,
207
+ help="Register address",
208
+ required=True,
209
+ )
210
+ search_modbus_parser.add_argument(
211
+ "--register-type",
212
+ type=str,
213
+ choices=["input", "holding"],
214
+ help="Register type",
215
+ required=True,
216
+ )
185
217
  arguments = parser.parse_args()
186
218
 
187
219
  return arguments
@@ -223,6 +255,7 @@ def run_modbus_command(
223
255
  _LOGGER.info("BoneIO %s starting.", __version__)
224
256
  try:
225
257
  configure_logger(log_config={}, debug=args.debug)
258
+ ret = 0
226
259
  if args.modbus_action == "set":
227
260
  ret = asyncio.run(
228
261
  async_run_modbus_set(
@@ -235,9 +268,11 @@ def run_modbus_command(
235
268
  stopbits=args.stopbits,
236
269
  new_baudrate=args.new_baudrate,
237
270
  new_address=args.new_address,
271
+ custom_address=args.custom_register_address,
272
+ custom_value=args.custom_value,
238
273
  ),
239
274
  )
240
- else:
275
+ elif args.modbus_action == "get":
241
276
  ret = asyncio.run(
242
277
  async_run_modbus_get(
243
278
  uart=args.uart,
@@ -251,6 +286,18 @@ def run_modbus_command(
251
286
  value_type=args.value_type,
252
287
  ),
253
288
  )
289
+ else:
290
+ ret = asyncio.run(
291
+ async_run_modbus_search(
292
+ uart=args.uart,
293
+ baudrate=args.baudrate,
294
+ register_address=args.register_address,
295
+ register_type=args.register_type,
296
+ parity=args.parity,
297
+ bytesize=args.bytesize,
298
+ stopbits=args.stopbits,
299
+ ),
300
+ )
254
301
  return ret
255
302
  except (RestartRequestException, GracefulExit) as err:
256
303
  if err is not None:
@@ -281,7 +328,10 @@ def main() -> int:
281
328
  args=args,
282
329
  )
283
330
 
284
- _LOGGER.info("Exiting with exit code %s", exit_code)
331
+ if exit_code == 0:
332
+ _LOGGER.info("Exiting with exit code %s", exit_code)
333
+ else:
334
+ _LOGGER.error("Exiting with exit code %s", exit_code)
285
335
  return exit_code
286
336
 
287
337
 
@@ -19,8 +19,9 @@ FILTERS = {
19
19
  class Filter:
20
20
  _filters = []
21
21
 
22
- def _apply_filters(self, value: float) -> float | None:
23
- for filter in self._filters:
22
+ def _apply_filters(self, value: float, filters: list = []) -> float | None:
23
+ filters = filters if filters else self._filters
24
+ for filter in filters:
24
25
  for k, v in filter.items():
25
26
  if k not in FILTERS:
26
27
  _LOGGER.warning(
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
  import asyncio
3
3
  import logging
4
4
  from datetime import datetime
5
+
5
6
  try:
6
7
  from Adafruit_BBIO import GPIO
7
8
  except ModuleNotFoundError:
@@ -21,7 +22,14 @@ from typing import Callable, Awaitable, List
21
22
 
22
23
  from boneio.const import CONFIG_PIN, FALLING
23
24
  from boneio.const import GPIO as GPIO_STR
24
- from boneio.const import GPIO_MODE, LOW, ClickTypes, Gpio_Edges, Gpio_States, InputTypes
25
+ from boneio.const import (
26
+ GPIO_MODE,
27
+ LOW,
28
+ ClickTypes,
29
+ Gpio_Edges,
30
+ Gpio_States,
31
+ InputTypes,
32
+ )
25
33
  from boneio.helper.exceptions import GPIOInputException
26
34
  from boneio.helper.timeperiod import TimePeriod
27
35
  from concurrent.futures import ThreadPoolExecutor
@@ -79,7 +87,9 @@ def edge_detect(
79
87
  ) -> None:
80
88
  """Add detection for RISING and FALLING events."""
81
89
  try:
82
- GPIO.add_event_detect(gpio=pin, edge=edge, callback=callback, bouncetime=bounce)
90
+ GPIO.add_event_detect(
91
+ gpio=pin, edge=edge, callback=callback, bouncetime=bounce
92
+ )
83
93
  except RuntimeError as err:
84
94
  raise GPIOInputException(err)
85
95
 
@@ -107,7 +117,8 @@ class GpioBaseClass:
107
117
  self,
108
118
  pin: str,
109
119
  press_callback: Callable[
110
- [ClickTypes, str, List, InputTypes, bool, float | None], Awaitable[None]
120
+ [ClickTypes, str, List, InputTypes, bool, float | None],
121
+ Awaitable[None],
111
122
  ],
112
123
  name: str,
113
124
  actions: dict,
@@ -118,7 +129,9 @@ class GpioBaseClass:
118
129
  """Setup GPIO Input Button"""
119
130
  self._pin = pin
120
131
  gpio_mode = kwargs.get(GPIO_MODE, GPIO_STR)
121
- bounce_time: TimePeriod = kwargs.get("bounce_time", TimePeriod(milliseconds=50))
132
+ bounce_time: TimePeriod = kwargs.get(
133
+ "bounce_time", TimePeriod(milliseconds=50)
134
+ )
122
135
  self._bounce_time = bounce_time.total_in_seconds
123
136
  self._loop = asyncio.get_running_loop()
124
137
  self._press_callback = press_callback
@@ -129,14 +142,22 @@ class GpioBaseClass:
129
142
  self._input_type = input_type
130
143
  self._empty_message_after = empty_message_after
131
144
 
132
- def press_callback(self, click_type: ClickTypes, duration: float | None = None) -> None:
145
+ def press_callback(
146
+ self, click_type: ClickTypes, duration: float | None = None
147
+ ) -> None:
133
148
  actions = self._actions.get(click_type, [])
134
- self._loop.create_task(self.async_press_callback(click_type, duration, actions))
149
+ self._loop.create_task(
150
+ self.async_press_callback(click_type, duration, actions)
151
+ )
135
152
 
136
153
  async def async_press_callback(
137
- self, click_type: ClickTypes, duration: float | None = None, actions: List = []
154
+ self,
155
+ click_type: ClickTypes,
156
+ duration: float | None = None,
157
+ actions: List = [],
138
158
  ) -> None:
139
- _LOGGER.warn("press callback %s", datetime.now())
159
+
160
+ _LOGGER.warning("press callback %s", datetime.now())
140
161
  await self._press_callback(
141
162
  click_type,
142
163
  self._pin,
@@ -211,6 +211,7 @@ def create_modbus_sensors(manager: Manager, sensors, **kwargs) -> None:
211
211
  update_interval=sensor.get(
212
212
  UPDATE_INTERVAL, TimePeriod(seconds=60)
213
213
  ),
214
+ sensors_filters=sensor.get("sensors_filters", {}),
214
215
  **kwargs,
215
216
  )
216
217
  except FileNotFoundError as err:
@@ -233,13 +233,15 @@ class HostData:
233
233
  },
234
234
  }
235
235
  if ina219 is not None:
236
+
237
+ def get_ina_values():
238
+ return {
239
+ sensor.device_class: f"{sensor.state} {sensor.unit_of_measurement}"
240
+ for sensor in ina219.sensors.values()
241
+ }
242
+
236
243
  host_stats[INA219] = {
237
- "f": lambda: {
238
- *{
239
- sensor.device_class: sensor.state
240
- for sensor in ina219.sensors.values()
241
- }
242
- },
244
+ "f": get_ina_values,
243
245
  "update_interval": TimePeriod(seconds=60),
244
246
  }
245
247
  self._data = {}
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import asyncio
4
4
  import logging
5
5
  import struct
6
+ import time
6
7
  from typing import Any
7
8
 
8
9
  from pymodbus.client.sync import BaseModbusClient, ModbusSerialClient
@@ -43,6 +44,7 @@ class Modbus:
43
44
  stopbits: int = 1,
44
45
  bytesize: int = 8,
45
46
  parity: str = "N",
47
+ timeout: float = 3,
46
48
  ) -> None:
47
49
  """Initialize the Modbus hub."""
48
50
  rx = uart.get(RX)
@@ -68,6 +70,7 @@ class Modbus:
68
70
  stopbits=stopbits,
69
71
  bytesize=bytesize,
70
72
  parity=parity,
73
+ timeout=timeout,
71
74
  )
72
75
  self._read_methods = {
73
76
  "input": self._client.read_input_registers,
@@ -129,6 +132,7 @@ class Modbus:
129
132
  ) -> ModbusResponse:
130
133
  """Call async pymodbus."""
131
134
  async with self._lock:
135
+ start_time = time.perf_counter()
132
136
  if not self._pymodbus_connect():
133
137
  _LOGGER.error("Can't connect to Modbus.")
134
138
  return None
@@ -151,6 +155,11 @@ class Modbus:
151
155
  if not hasattr(result, REGISTERS):
152
156
  _LOGGER.error(str(result))
153
157
  return None
158
+ end_time = time.perf_counter()
159
+ _LOGGER.debug(
160
+ "Time of execution of read_registers: %s ms.",
161
+ round((end_time - start_time) * 1000, 3),
162
+ )
154
163
  return result
155
164
 
156
165
  def decode_value(self, payload, value_type):
@@ -4,7 +4,7 @@
4
4
  "set_address_address": 2000,
5
5
  "set_baudrate": {
6
6
  "address": 2001,
7
- "possible_baudrates": [2400, 4800, 9600, 19200]
7
+ "possible_baudrates": { "2400": 0, "4800": 1, "9600": 2, "19200": 3 }
8
8
  }
9
9
  },
10
10
  "registers_base": [
@@ -0,0 +1,37 @@
1
+ {
2
+ "model": "Liquid sensor",
3
+ "set_base": {
4
+ "set_address_address": 0,
5
+ "set_baudrate": {
6
+ "address": 1,
7
+ "possible_baudrates": {
8
+ "1200": 0,
9
+ "2400": 1,
10
+ "4800": 2,
11
+ "9600": 3,
12
+ "19200": 4,
13
+ "38400": 5,
14
+ "57600": 6,
15
+ "115200": 7
16
+ }
17
+ }
18
+ },
19
+ "registers_base": [
20
+ {
21
+ "base": 0,
22
+ "length": 2,
23
+ "register_type": "holding",
24
+ "registers": [
25
+ {
26
+ "name": "Immersion depth",
27
+ "address": 0,
28
+ "unit_of_measurement": "mm",
29
+ "state_class": "measurement",
30
+ "device_class": "humidity",
31
+ "value_type": "S_WORD",
32
+ "filters": [{ "multiply": 0.1 }]
33
+ }
34
+ ]
35
+ }
36
+ ]
37
+ }
@@ -78,7 +78,7 @@ class ModbusHelper:
78
78
 
79
79
  def set_connection_speed(self, new_baudrate: int) -> int:
80
80
  baudrate_model = self._model[SET_BAUDRATE]
81
- ind = baudrate_model["possible_baudrates"].index(new_baudrate)
81
+ ind = baudrate_model["possible_baudrates"].get(str(new_baudrate))
82
82
  if ind:
83
83
  result = self._modbus.client.write_register(
84
84
  address=baudrate_model["address"],
@@ -110,6 +110,19 @@ class ModbusHelper:
110
110
  else:
111
111
  _LOGGER.error("Invalid new address.")
112
112
 
113
+ def set_custom_command(self, register_address: int, value: int | float):
114
+ result = self._modbus.client.write_register(
115
+ address=register_address,
116
+ value=value,
117
+ unit=self._device_address,
118
+ )
119
+ if result.isError():
120
+ _LOGGER.error("Operation failed.")
121
+ else:
122
+ _LOGGER.info(
123
+ "Operation succeeded. Now restart device by disconnecting it."
124
+ )
125
+
113
126
 
114
127
  async def async_run_modbus_set(
115
128
  device: str,
@@ -118,6 +131,8 @@ async def async_run_modbus_set(
118
131
  baudrate: int,
119
132
  new_baudrate: int,
120
133
  new_address: int,
134
+ custom_address: int,
135
+ custom_value: int,
121
136
  stopbits: int = 1,
122
137
  bytesize: int = 8,
123
138
  parity: str = "N",
@@ -125,15 +140,21 @@ async def async_run_modbus_set(
125
140
  """Run Modbus Set Function."""
126
141
  if new_address and new_baudrate:
127
142
  _LOGGER.error("Can't set both methods new_address and new_baudrate.")
128
- _db = open_json(path=os.path.dirname(__file__), model=device)
129
- set_base = _db.get(SET_BASE, {})
130
- first_reg_base = _db.get(REGISTERS_BASE, [])[0]
131
- if not first_reg_base:
132
- return False
133
- first_record = first_reg_base.get(REGISTERS, [])[0]
134
- _LOGGER.debug(
135
- f"Connecting with params uart: {uart}, baudrate: {baudrate}, stopbits: {stopbits}, bytesize: {bytesize}, parity: {parity}."
136
- )
143
+ custom_cmd = True if device == "custom" else False
144
+ set_base = {}
145
+ if not custom_cmd:
146
+ _db = open_json(path=os.path.dirname(__file__), model=device)
147
+ set_base = _db.get(SET_BASE, {})
148
+ first_reg_base = _db.get(REGISTERS_BASE, [])[0]
149
+ if not first_reg_base:
150
+ return False
151
+ first_record = first_reg_base.get(REGISTERS, [])[0]
152
+ _LOGGER.debug(
153
+ f"Connecting with params uart: {uart}, baudrate: {baudrate}, stopbits: {stopbits}, bytesize: {bytesize}, parity: {parity}."
154
+ )
155
+ else:
156
+ first_record = {}
157
+ first_reg_base = {}
137
158
  modbus_helper = ModbusHelper(
138
159
  device=device,
139
160
  uart=uart,
@@ -146,17 +167,69 @@ async def async_run_modbus_set(
146
167
  bytesize=bytesize,
147
168
  parity=parity,
148
169
  )
149
- if not await modbus_helper.check_connection():
150
- _LOGGER.error("Can't connect with sensor. Exiting")
151
- return 1
152
- if new_baudrate:
153
- return modbus_helper.set_connection_speed(new_baudrate=new_baudrate)
154
- if new_address:
155
- modbus_helper.set_new_address(new_address=new_address)
156
- return 0
170
+
171
+ async def default_action():
172
+ if not await modbus_helper.check_connection():
173
+ _LOGGER.error("Can't connect with sensor. Exiting")
174
+ return 1
175
+ if new_baudrate:
176
+ return modbus_helper.set_connection_speed(new_baudrate=new_baudrate)
177
+ if new_address:
178
+ modbus_helper.set_new_address(new_address=new_address)
179
+ return 0
180
+
181
+ if not custom_cmd:
182
+ _LOGGER.debug("Invoking default action.")
183
+ return await default_action()
184
+ elif custom_address is not None and custom_value is not None:
185
+ _LOGGER.debug("Invoking custom command action.")
186
+ modbus_helper.set_custom_command(
187
+ register_address=custom_address, value=custom_value
188
+ )
157
189
  return 1
158
190
 
159
191
 
192
+ async def async_run_modbus_search(
193
+ uart: str,
194
+ baudrate: int,
195
+ register_address: int,
196
+ register_type: str = "holding",
197
+ stopbits: int = 1,
198
+ bytesize: int = 8,
199
+ parity: str = "N",
200
+ ):
201
+ """Run Modbus Seach Function."""
202
+ _modbus = Modbus(
203
+ uart=UARTS[uart],
204
+ baudrate=baudrate,
205
+ stopbits=stopbits,
206
+ bytesize=bytesize,
207
+ parity=parity,
208
+ timeout=0.1,
209
+ )
210
+ units_found = []
211
+ for unit_id in range(1, 248): # Modbus RTU address range is 1 to 247
212
+ _LOGGER.debug(f"Searching device at address {unit_id}.")
213
+ value = await _modbus.read_registers(
214
+ unit=unit_id,
215
+ address=register_address,
216
+ count=2,
217
+ method=register_type,
218
+ )
219
+ if value:
220
+ units_found.append(unit_id)
221
+ _LOGGER.info(f"Found device at address {unit_id}.")
222
+ else:
223
+ _LOGGER.debug(f"No device found at address {unit_id}.")
224
+ if units_found:
225
+ _LOGGER.info(
226
+ "Found devices: [%s]", ", ".join(str(x) for x in units_found)
227
+ )
228
+ else:
229
+ _LOGGER.info("No devices found.")
230
+ return 0
231
+
232
+
160
233
  async def async_run_modbus_get(
161
234
  uart: str,
162
235
  device_address: int,
@@ -29,6 +29,14 @@
29
29
  "state_class": "measurement",
30
30
  "device_class": "voltage",
31
31
  "value_type": "FP32"
32
+ },
33
+ {
34
+ "name": "Grid Frequency",
35
+ "address": 18,
36
+ "unit_of_measurement": "V",
37
+ "state_class": "measurement",
38
+ "device_class": "voltage",
39
+ "value_type": "FP32"
32
40
  }
33
41
  ]
34
42
  }
@@ -4,7 +4,13 @@
4
4
  "set_address_address": 254,
5
5
  "set_baudrate": {
6
6
  "address": 255,
7
- "possible_baudrates": [1200, 2400, 4800, 9600, 19200]
7
+ "possible_baudrates": {
8
+ "1200": 0,
9
+ "2400": 1,
10
+ "4800": 2,
11
+ "9600": 3,
12
+ "19200": 4
13
+ }
8
14
  }
9
15
  },
10
16
  "registers_base": [
@@ -4,6 +4,8 @@ import logging
4
4
  import os
5
5
  from datetime import datetime
6
6
  from typing import Optional
7
+
8
+ from boneio.helper.filter import Filter
7
9
  from .utils import CONVERT_METHODS, REGISTERS_BASE, allowed_operations
8
10
  from boneio.const import (
9
11
  ADDRESS,
@@ -26,7 +28,7 @@ from boneio.helper.util import open_json
26
28
  _LOGGER = logging.getLogger(__name__)
27
29
 
28
30
 
29
- class ModbusSensor(BasicMqtt, AsyncUpdater):
31
+ class ModbusSensor(BasicMqtt, AsyncUpdater, Filter):
30
32
  """Represent Modbus sensor in BoneIO."""
31
33
 
32
34
  SensorClass = None
@@ -37,6 +39,7 @@ class ModbusSensor(BasicMqtt, AsyncUpdater):
37
39
  modbus: Modbus,
38
40
  address: str,
39
41
  model: str,
42
+ sensors_filters: dict,
40
43
  config_helper: ConfigHelper,
41
44
  event_bus: EventBus,
42
45
  id: str = DefaultName,
@@ -56,6 +59,9 @@ class ModbusSensor(BasicMqtt, AsyncUpdater):
56
59
  self._address = address
57
60
  self._discovery_sent = False
58
61
  self._payload_online = OFFLINE
62
+ self._sensors_filters = {
63
+ k.lower(): v for k, v in sensors_filters.items()
64
+ }
59
65
  event_bus.add_haonline_listener(target=self.set_payload_offline)
60
66
  AsyncUpdater.__init__(self, **kwargs)
61
67
 
@@ -154,7 +160,9 @@ class ModbusSensor(BasicMqtt, AsyncUpdater):
154
160
  method=data.get("register_type", "input"),
155
161
  )
156
162
  if self._payload_online == OFFLINE and values:
157
- _LOGGER.info("Sending online payload about device.")
163
+ _LOGGER.info(
164
+ "Sending online payload about device %s.", self._name
165
+ )
158
166
  self._payload_online = ONLINE
159
167
  self._send_message(
160
168
  topic=f"{self._config_helper.topic_prefix}/{self._id}{STATE}",
@@ -204,6 +212,12 @@ class ModbusSensor(BasicMqtt, AsyncUpdater):
204
212
  if key in allowed_operations:
205
213
  lamda_function = allowed_operations[key]
206
214
  decoded_value = lamda_function(decoded_value, value)
215
+ name = register.get("name").replace(" ", "")
216
+ user_filters = self._sensors_filters.get(name.lower(), [])
217
+ decoded_value = self._apply_filters(
218
+ value=decoded_value,
219
+ filters=user_filters,
220
+ )
207
221
  output[register.get("name").replace(" ", "")] = decoded_value
208
222
  self._send_message(
209
223
  topic=f"{self._send_topic}/{data[BASE]}",
@@ -0,0 +1,37 @@
1
+ {
2
+ "model": "SHT20 Temp and Humidity sensor",
3
+ "set_base": {
4
+ "set_address_address": 257,
5
+ "set_baudrate": {
6
+ "address": 258,
7
+ "possible_baudrates": { "9600": 9600, "14400": 14400, "19200": 19200 }
8
+ }
9
+ },
10
+ "registers_base": [
11
+ {
12
+ "base": 1,
13
+ "length": 2,
14
+ "register_type": "input",
15
+ "registers": [
16
+ {
17
+ "name": "Temperature",
18
+ "address": 1,
19
+ "unit_of_measurement": "°C",
20
+ "state_class": "measurement",
21
+ "device_class": "temperature",
22
+ "value_type": "S_WORD",
23
+ "filters": [{ "multiply": 0.1 }]
24
+ },
25
+ {
26
+ "name": "Humidity",
27
+ "address": 2,
28
+ "unit_of_measurement": "%",
29
+ "state_class": "measurement",
30
+ "device_class": "humidity",
31
+ "value_type": "S_WORD",
32
+ "filters": [{ "multiply": 0.1 }]
33
+ }
34
+ ]
35
+ }
36
+ ]
37
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "model": "Socomec E23/E24",
2
+ "model": "Socomec E03",
3
3
  "registers_base": [
4
4
  {
5
5
  "base": 50520,