boneIO 0.6.0dev4__py3-none-any.whl → 0.6.0dev5__py3-none-any.whl

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.

Potentially problematic release.


This version of boneIO might be problematic. Click here for more details.

@@ -1,26 +1,26 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: boneIO
3
- Version: 0.6.0dev4
3
+ Version: 0.6.0dev5
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>
7
7
  Requires-Python: >=3.7
8
8
  Requires-Dist: Adafruit-BBIO>=1.2.0
9
- Requires-Dist: Adafruit-Blinka>=8.0.2
9
+ Requires-Dist: Adafruit-Blinka>=8.19.0
10
10
  Requires-Dist: Cerberus>=1.3.4
11
11
  Requires-Dist: PyYAML>=6.0
12
- Requires-Dist: adafruit-circuitpython-ds18x20>=1.3.12
13
- Requires-Dist: adafruit-circuitpython-mcp230xx>=2.5.5
14
- Requires-Dist: adafruit-circuitpython-mcp9808>=3.3.14
12
+ Requires-Dist: adafruit-circuitpython-ds18x20>=1.3.16
13
+ Requires-Dist: adafruit-circuitpython-mcp230xx>=2.5.9
14
+ Requires-Dist: adafruit-circuitpython-mcp9808>=3.3.19
15
15
  Requires-Dist: adafruit-circuitpython-onewire>=2.0.4
16
- Requires-Dist: adafruit-circuitpython-pca9685>=3.4.6
17
- Requires-Dist: adafruit-circuitpython-pct2075>=1.1.14
18
- Requires-Dist: asyncio-mqtt>=0.12.1
19
- Requires-Dist: colorlog>=6.6.0
20
- Requires-Dist: gpio>=0.3.0
21
- Requires-Dist: luma.core>=2.3.1
22
- Requires-Dist: luma.oled>=3.8.1
23
- Requires-Dist: psutil>=5.9.1
16
+ Requires-Dist: adafruit-circuitpython-pca9685>=3.4.8
17
+ Requires-Dist: adafruit-circuitpython-pct2075>=1.1.18
18
+ Requires-Dist: asyncio-mqtt>=0.16.1
19
+ Requires-Dist: colorlog>=6.7.0
20
+ Requires-Dist: gpio>=1.0.0
21
+ Requires-Dist: luma-core>=2.4.0
22
+ Requires-Dist: luma-oled>=3.12.0
23
+ Requires-Dist: psutil>=5.9.5
24
24
  Requires-Dist: pymodbus>=2.5.3
25
25
  Requires-Dist: pyserial-asyncio>=0.6
26
26
  Requires-Dist: w1thermsensor[async]>=2.0.0
@@ -1,5 +1,5 @@
1
1
  boneio/__init__.py,sha256=hoLKnu6h1uEsiCHA9Ql2MMDC7VQS0E-s_2cxBS-CTOg,147
2
- boneio/bonecli.py,sha256=Wg3Wc2xZ9FjF_23PK6Ja3YzXUkYanzYyQ_QdtQbKESw,3155
2
+ boneio/bonecli.py,sha256=VhY1BMt6OaZz-752UjhEexdn8fDJkJaDWcGQNhCmgN0,3341
3
3
  boneio/const.py,sha256=jowaP49OLdHOK2Bop2KDVPVKRVbqjBPHl_Fgl5aRzIg,2924
4
4
  boneio/cover.py,sha256=nr76ft5smAPdBB8Jytk-uFd6RWuqtcZiG6EE129OUjw,8454
5
5
  boneio/example_config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -14,15 +14,15 @@ boneio/fonts/danube__.ttf,sha256=KBRPUcGvmroCjrWC8zK4MulUb9lV8R0lXkOY7lNnQWc,193
14
14
  boneio/helper/__init__.py,sha256=r-9dubCtDlrs_HFF2OGPQEPmw1XREgb4qQF6I486Li4,2039
15
15
  boneio/helper/async_updater.py,sha256=BVh5mXFxgaZqoekjQbQSCTssfyVIdCQ80LCJny-rOo4,985
16
16
  boneio/helper/click_timer.py,sha256=blWofzFIwuYcF_yFfCGDtI8nGSMpSdXdl9qMLXlKdXc,1169
17
- boneio/helper/config.py,sha256=XeHG7OSMCMmEL43rCbWy6Ij0RcTNlrIpevWZ0UJyrlo,1311
17
+ boneio/helper/config.py,sha256=hhM4KUNaAFVXgQ1ZX56Zu7XV0pjp636vrcBuwoTH18g,2233
18
18
  boneio/helper/events.py,sha256=EHYJy507RF1LikWQ2s4r990JRvR9rO4z7FkaJqt1Mrk,7494
19
- boneio/helper/exceptions.py,sha256=Tll3ZZPOo0JZskN67hkoAxxGJ4I5ZY4IjeXtKyveziI,637
19
+ boneio/helper/exceptions.py,sha256=vyYSUojwB7OttOPUjbxeHFM7C_o5ZQmRUcZ_vYpxYYI,717
20
20
  boneio/helper/filter.py,sha256=UcjifjaC5wHDW2He7wmwCX2IjSSeLVhXyhrXHeT8RmA,885
21
21
  boneio/helper/gpio.py,sha256=v4QrJfwlhIOqRCcD1SCpwPVcY6zsTlm1tens0eLbazM,2718
22
- boneio/helper/ha_discovery.py,sha256=e2xjArXixpbVru3oD_8L8UKMgEkpbL5OV2ybZaNi-f0,6129
23
- boneio/helper/loader.py,sha256=8lWjNVLcV-uqIGGgD7bY3lGEK9dhEDB0wQppxFb_Vys,13488
22
+ boneio/helper/ha_discovery.py,sha256=s80gwx1Fik2_vtq-6kqabUqPx4KwwZUt4G1Oy6RgGgU,6278
23
+ boneio/helper/loader.py,sha256=dKlk42tI7RkyI_FmX6CneN3FIGnHQ55c20sfn11B_bk,13547
24
24
  boneio/helper/logger.py,sha256=zyF_Vk3o2eSHuXypJhNzK99eZgkWDWybuB5KcJgurDs,1801
25
- boneio/helper/mqtt.py,sha256=Aqf6Sx4-3GQsK5GjgJ5QxVMyCTdRcL5Zq_GTIDu7ne0,759
25
+ boneio/helper/mqtt.py,sha256=UVa4KaxvVif6By_sVMdSMiYI_doZjiXF4LS9RFltxJk,765
26
26
  boneio/helper/oled.py,sha256=f0XYbaFstUbJ6ISmFe1vQCnmNVMctQWtnStNdROrblc,351
27
27
  boneio/helper/onewire/W1ThermSensor.py,sha256=Pu5Fp29pKbxjJqDbFs05Mjg3Vy-9z_DTVoBpSWM_byA,2430
28
28
  boneio/helper/onewire/__init__.py,sha256=4djH-uxiq5XhAiFTOxyqjvWVoJe0c8U8iN0BfmH8nSU,448
@@ -35,29 +35,29 @@ boneio/helper/timeperiod.py,sha256=xpg9oe30Z5WiLDQPq3EBKKScejOnN47NhcpeFvDd3bY,5
35
35
  boneio/helper/util.py,sha256=QfSoT3H3WnOQAKO5bFRIBCy62HT5NMa9Ek-FmfTW_rw,508
36
36
  boneio/helper/yaml_util.py,sha256=gAX7hCaHUlPgkhGCIQ4zjv0Vj9JmLzQsSyF51ZmSb9g,11304
37
37
  boneio/input/__init__.py,sha256=u9wuehuC_TNizL4tyn7EygZW5LaTFjSnFaZeZr3zZ1U,99
38
- boneio/input/gpio.py,sha256=oCJNJ7FYvznsNt2xwtmEGMOOeXCJj-3Ssh-xMYVXOfQ,2583
39
- boneio/manager.py,sha256=D_VT_VpGAZo3CVjoxDecYPlQwIbdEA-2rkABXUIDxW4,20273
38
+ boneio/input/gpio.py,sha256=7teo2fDyDUarorCn9REF5sGFMIimRrWaKOqildYG8_Y,2597
39
+ boneio/manager.py,sha256=cs2vy-26kvQROWHjHopACY2U1OLufcsu522RjShVXyQ,21023
40
40
  boneio/modbus.py,sha256=eA_sGnmdcIneXX75ZcXOI223-UkKN1UYmWFqiJBAhJo,4176
41
- boneio/mqtt_client.py,sha256=ioOBEmnbjpZjYrD5u8W5CrVsrNFRHDJ93fyk6XcWkeE,6983
41
+ boneio/mqtt_client.py,sha256=NYvok8y_g8iRXDopVojzcDGVnDJEFqHBEYhOGy8m7T8,8331
42
42
  boneio/oled.py,sha256=d_ofDQHc7zr3j0qOR0B_L3C8TJhvRH85nnb9Le-88nk,5244
43
43
  boneio/relay/__init__.py,sha256=iLEUL8kxnEndCQtgDIJfHqUZOfyWfWmuOrqSYAUxb2w,181
44
- boneio/relay/basic.py,sha256=zOXf84yTGeArk3ghXt_WI2ul8L0RxyziOTD6U7-nCLM,2629
44
+ boneio/relay/basic.py,sha256=QEWG8HodBqEqLpQxcQm29hdPIxO7c6ep0uDgfo3pcco,2623
45
45
  boneio/relay/gpio.py,sha256=s64FBf6zlxciF57xJ6nrvy0t5Rie0uiXlwos0HL3RKM,1163
46
46
  boneio/relay/mcp.py,sha256=DhgqZbFHB6TucL9hJaJiqdVAbkw4Rv5VPHm_khSBIFo,2669
47
47
  boneio/relay/pca.py,sha256=tCwXvMJcsxzFj48ZO2tPYyvElZfyIRTFmEMp7ENKvtk,4873
48
- boneio/runner.py,sha256=geT_ps_vo1SLoBszmhtcpkzk_LwWpG8OZYgUcQg3nKU,2287
48
+ boneio/runner.py,sha256=eLRd3NtE8c5fqYu76B4Kz7vs5ftNDSZ_L-KHbipRu90,2569
49
49
  boneio/schema/actions.yaml,sha256=WD0LR9SxVPgwU12x10RvFaTeyDiCjdRiicfkrCGLNvc,1030
50
- boneio/schema/actions_sensor.yaml,sha256=LhXEsYsg6qs7iHqgQE6n-2jj-o-ZZRusDW_9nWIiZAY,129
51
- boneio/schema/actions_switch.yaml,sha256=qyooX0izkgl4wBEh8xUsYFEKMrvhbIAQYxOGTB5AROY,124
50
+ boneio/schema/actions_sensor.yaml,sha256=U_VfdjqdorQZHYD6dZOzhlRKnOGwsnTJ3CDGiqrKxW8,124
51
+ boneio/schema/actions_switch.yaml,sha256=sdqhwz0LrcfaiAN3-RfAR_xApxo5dh9RxXQubuKcsjk,129
52
52
  boneio/schema/filters.yaml,sha256=WopX1SmCZsSZXNiGccLoi7uMCsUpdbs0eVfsAjmbay4,286
53
53
  boneio/schema/filters_adc.yaml,sha256=3e0ts92MoFKOnfGOOn6WYIHxu0-tnZe9OjAjN9m7hiI,304
54
54
  boneio/schema/id.yaml,sha256=vHI_6dxkJwoiGp5ien_pPpyaWA1F06eirTgfWMyWa1E,75
55
- boneio/schema/schema.yaml,sha256=CbwRtUW1cn8H-q_FQSVoXyTsPYFKOrzxA5hW-JeXTK4,12551
55
+ boneio/schema/schema.yaml,sha256=GHqcUaCUJypX2JD7tXVKoIZ3XO4rqLaoHW0ppWFWvzU,12988
56
56
  boneio/schema/temp_unit.yaml,sha256=m32VGz9RTW92Lz08l9n6RftkcVNZquOGPPr5mc3eZVc,65
57
57
  boneio/schema/update_interval.yaml,sha256=L_YLddAZdybcdO-T57Qhlv1GaWIVDwhvH-GadD1oOIo,133
58
58
  boneio/sensor/__init__.py,sha256=VjFBEQQv_U0T2ENkzefuBdzmqNvBTEv4M-gQKScc8o0,431
59
59
  boneio/sensor/adc.py,sha256=Yf5F8JcZJt0K4ijpPqI8do0BqDfLvfja_LE8_Zu3u7U,1281
60
- boneio/sensor/gpio.py,sha256=a4rn4cALZBPqqU-nlpzOjpPd8MjEAnxBTCS6pdJ0tpA,1289
60
+ boneio/sensor/gpio.py,sha256=oAUYHGLX0_JK6AJMplmDBnLBIAZ2GaDJ7ts8AceljvQ,1303
61
61
  boneio/sensor/modbus/__init__.py,sha256=LEOnMqBACNxZmCN2adZtTsq76gPbsNba5JewMiUENBw,8500
62
62
  boneio/sensor/modbus/dts1964_3f.json,sha256=WABph_2X4OgMidSM28vw4dLd9ZByRoiUP-RqE0UMpR8,8810
63
63
  boneio/sensor/modbus/pt100.json,sha256=bzKNYDc3lMto94Ut8gutVIsHytQiMobwFCkonOiMegs,526
@@ -69,9 +69,9 @@ boneio/sensor/temp/__init__.py,sha256=h5rGbkC8K6y2GJSNHtm9f8EmoBS3wFlSNIFjcrRj47
69
69
  boneio/sensor/temp/dallas.py,sha256=xrvd1LNBh1fRcjz_yd_9S29Lv26ItVtLCmhZgVQWJos,2485
70
70
  boneio/sensor/temp/lm75.py,sha256=-mpOv78429plRPBp9s09Ljz1x2Bdxuagalqz40vlAKE,243
71
71
  boneio/sensor/temp/mcp9808.py,sha256=yAh5yJzZLGTqa39xV4fyqsruj7oy3niOZAUV2W6eyCM,271
72
- boneio/version.py,sha256=c4Y90PQyvZ4QIDtX6BiDOphICDX-64mrmDrLk75FXoU,41
73
- boneIO-0.6.0.dev4.dist-info/entry_points.txt,sha256=Igl1SMDEWwMg144bwRz28UOCb80hdxomC99QJ3u6qC0,46
74
- boneIO-0.6.0.dev4.dist-info/WHEEL,sha256=lsei1NEk3eJVaxXEuOSSqU002B_yCocFbsEPxBs2x2s,87
75
- boneIO-0.6.0.dev4.dist-info/METADATA,sha256=S4FUlkbD_kjqB7DboCk17eVjdIYKWoLk_8aSxXLVvTI,1696
76
- boneIO-0.6.0.dev4.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
77
- boneIO-0.6.0.dev4.dist-info/RECORD,,
72
+ boneio/version.py,sha256=MRoHBbiT0xKVNIGf8uc_weaaaOMvuoXa9HcssIlg3_0,41
73
+ boneIO-0.6.0.dev5.dist-info/entry_points.txt,sha256=Igl1SMDEWwMg144bwRz28UOCb80hdxomC99QJ3u6qC0,46
74
+ boneIO-0.6.0.dev5.dist-info/WHEEL,sha256=B19PGBCYhWaz2p_UjAoRVh767nYQfk14Sn4TpIZ-nfU,87
75
+ boneIO-0.6.0.dev5.dist-info/METADATA,sha256=b5xgbS9_foPU_jVQq9CPRUDE1XV9Ceq_LlI0e6KBz9U,1698
76
+ boneIO-0.6.0.dev5.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
77
+ boneIO-0.6.0.dev5.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: pdm-pep517 1.1.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
boneio/bonecli.py CHANGED
@@ -1,9 +1,11 @@
1
1
  """Bonecli script."""
2
+ from __future__ import annotations
2
3
  import argparse
3
4
  import asyncio
4
5
  import logging
5
6
  import sys
6
7
  import os
8
+ from typing import Any
7
9
 
8
10
  os.environ["W1THERMSENSOR_NO_KERNEL_MODULE"] = "1"
9
11
 
@@ -12,7 +14,7 @@ from yaml import MarkedYAMLError
12
14
 
13
15
  from boneio.const import ACTION
14
16
  from boneio.helper import load_config_from_file
15
- from boneio.helper.exceptions import ConfigurationException
17
+ from boneio.helper.exceptions import ConfigurationException, RestartRequestException
16
18
  from boneio.helper.logger import configure_logger
17
19
  from boneio.runner import async_run
18
20
  from boneio.version import __version__
@@ -69,7 +71,7 @@ def get_arguments() -> argparse.Namespace:
69
71
  return arguments
70
72
 
71
73
 
72
- def run(config: str, debug: int, mqttusername: str = "", mqttpassword: str = ""):
74
+ def run(config: str, debug: int, mqttusername: str = "", mqttpassword: str = "") -> int:
73
75
  """Run BoneIO."""
74
76
  _LOGGER.info("BoneIO %s starting.", __version__)
75
77
  try:
@@ -78,7 +80,7 @@ def run(config: str, debug: int, mqttusername: str = "", mqttpassword: str = "")
78
80
  _LOGGER.error("Config not loaded. Exiting.")
79
81
  return 1
80
82
  configure_logger(log_config=_config.get("logger"), debug=debug)
81
- return asyncio.run(
83
+ asyncio.run(
82
84
  async_run(
83
85
  config=_config,
84
86
  config_file=config,
@@ -86,6 +88,10 @@ def run(config: str, debug: int, mqttusername: str = "", mqttpassword: str = "")
86
88
  mqttpassword=mqttpassword,
87
89
  ),
88
90
  )
91
+ return 0
92
+ except RestartRequestException as err:
93
+ _LOGGER.info(err)
94
+ return 0
89
95
  except (ConfigurationException, MarkedYAMLError) as err:
90
96
  _LOGGER.error("Failed to load config. %s Exiting.", err)
91
97
  return 1
boneio/helper/config.py CHANGED
@@ -1,9 +1,9 @@
1
1
  """
2
2
  Module to provide basic config options.
3
3
  """
4
+ from __future__ import annotations
4
5
  from _collections_abc import dict_values
5
-
6
- from boneio.const import BONEIO, HOMEASSISTANT
6
+ from boneio.const import BONEIO, HOMEASSISTANT, LIGHT, SENSOR, COVER, BUTTON, SWITCH, BINARY_SENSOR
7
7
 
8
8
 
9
9
  class ConfigHelper:
@@ -16,7 +16,16 @@ class ConfigHelper:
16
16
  self._topic_prefix = topic_prefix
17
17
  self._ha_discovery = ha_discovery
18
18
  self._ha_discovery_prefix = ha_discovery_prefix
19
- self._autodiscovery_messages = {}
19
+ self._fetch_old_discovery = None
20
+ self._autodiscovery_messages = {
21
+ SWITCH: {},
22
+ LIGHT: {},
23
+ BINARY_SENSOR: {},
24
+ SENSOR: {},
25
+ COVER: {},
26
+ BUTTON: {}
27
+ }
28
+ self.manager_ready: bool = False
20
29
 
21
30
  @property
22
31
  def topic_prefix(self) -> str:
@@ -38,11 +47,28 @@ class ConfigHelper:
38
47
  def subscribe_topic(self) -> str:
39
48
  return f"{self.cmd_topic_prefix}+/+/#"
40
49
 
41
- def add_autodiscovery_msg(self, topic: str, payload: str):
50
+ def add_autodiscovery_msg(self, ha_type: str, topic: str, payload: str):
42
51
  """Add autodiscovery message."""
43
- self._autodiscovery_messages[topic] = {"topic": topic, "payload": payload}
52
+ self._autodiscovery_messages[ha_type][topic] = {"topic": topic, "payload": payload}
53
+
54
+ @property
55
+ def ha_types(self) -> list[str]:
56
+ return list(self._autodiscovery_messages.keys())
57
+
58
+ def is_topic_in_autodiscovery(self, topic: str) -> bool:
59
+ topic_parts_raw = topic[len(f"{self._ha_discovery_prefix}/") :].split("/")
60
+ ha_type = topic_parts_raw[0]
61
+ if ha_type in self._autodiscovery_messages:
62
+ if topic in self._autodiscovery_messages[ha_type]:
63
+ return True
64
+ return False
65
+
66
+
44
67
 
45
68
  @property
46
69
  def autodiscovery_msgs(self) -> dict_values:
47
70
  """Get autodiscovery messages"""
48
- return self._autodiscovery_messages.values()
71
+ output = {}
72
+ for ha_type in self._autodiscovery_messages:
73
+ output.update(self._autodiscovery_messages[ha_type])
74
+ return output.values()
@@ -8,6 +8,7 @@ class BoneIOException(Exception):
8
8
  class GPIOInputException(BoneIOException):
9
9
  """GPIOInput Exception."""
10
10
 
11
+
11
12
  class GPIOOutputException(BoneIOException):
12
13
  """GPIOInput Exception."""
13
14
 
@@ -30,3 +31,7 @@ class CoverRelayException(BoneIOException):
30
31
 
31
32
  class ModbusUartException(BoneIOException):
32
33
  """Cover configuration exception."""
34
+
35
+
36
+ class RestartRequestException(BoneIOException):
37
+ """Restart exception."""
@@ -51,6 +51,7 @@ def ha_light_availabilty_message(id: str, topic: str = "boneIO", **kwargs):
51
51
  msg["state_value_template"] = "{{ value_json.state }}"
52
52
  return msg
53
53
 
54
+
54
55
  def ha_led_availabilty_message(id: str, topic: str = "boneIO", **kwargs):
55
56
  """Create LED availability topic for HA."""
56
57
  msg = ha_availabilty_message(device_type=RELAY, topic=topic, id=id, **kwargs)
@@ -64,11 +65,14 @@ def ha_led_availabilty_message(id: str, topic: str = "boneIO", **kwargs):
64
65
  msg["brightness_value_template"] = "{{ value_json.brightness }}"
65
66
  return msg
66
67
 
67
- def ha_button_availabilty_message(id: str, topic: str = "boneIO", **kwargs):
68
+
69
+ def ha_button_availabilty_message(
70
+ id: str, topic: str = "boneIO", payload_press: str = "reload", **kwargs
71
+ ):
68
72
  """Create BUTTON availability topic for HA."""
69
73
  msg = ha_availabilty_message(device_type="button", topic=topic, id=id, **kwargs)
70
74
  msg["command_topic"] = f"{topic}/cmd/button/{id}/set"
71
- msg["payload_press"] = "reload"
75
+ msg["payload_press"] = payload_press
72
76
  return msg
73
77
 
74
78
 
@@ -102,9 +106,11 @@ def ha_sensor_availabilty_message(unit_of_measurement: str = None, **kwargs):
102
106
  return msg
103
107
 
104
108
 
105
- def ha_binary_sensor_availabilty_message(id: str, name: str, topic: str = "boneIO"):
109
+ def ha_binary_sensor_availabilty_message(id: str, name: str, device_class: str, topic: str = "boneIO"):
106
110
  """Create availability topic for HA."""
111
+ kwargs = {"device_class": device_class} if device_class else {}
107
112
  return {
113
+ **kwargs,
108
114
  "availability": [{"topic": f"{topic}/{STATE}"}],
109
115
  "device": {
110
116
  "identifiers": [topic],
boneio/helper/loader.py CHANGED
@@ -354,6 +354,7 @@ def configure_input(
354
354
  id=pin,
355
355
  name=gpio.get(ID, pin),
356
356
  ha_type=getattr(input, "ha_type"),
357
+ device_class=gpio.get(DEVICE_CLASS, None),
357
358
  availability_msg_func=getattr(input, "availability_msg_f"),
358
359
  )
359
360
  return pin
boneio/helper/mqtt.py CHANGED
@@ -21,11 +21,11 @@ class BasicMqtt:
21
21
  self._send_topic = f"{topic_prefix}/{topic_type}/{self.id}"
22
22
 
23
23
  @property
24
- def id(self) -> bool:
24
+ def id(self) -> str:
25
25
  """Id of the module."""
26
26
  return self._id
27
27
 
28
28
  @property
29
- def name(self):
29
+ def name(self) -> str:
30
30
  """Return name of the sensor."""
31
31
  return self._name
boneio/input/gpio.py CHANGED
@@ -53,7 +53,7 @@ class GpioInputButton(GpioBaseClass):
53
53
  self.check_state(state=self.is_pressed)
54
54
  await asyncio.sleep(self._bounce_time.total_in_seconds)
55
55
 
56
- def check_state(self, state):
56
+ def check_state(self, state: bool) -> None:
57
57
  if state == self._state:
58
58
  return
59
59
  self._state = state
boneio/manager.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import asyncio
2
2
  import logging
3
3
  from collections import deque
4
- from typing import Callable, Coroutine, List, Optional, Set, Union
4
+ from typing import Callable, Coroutine, List, Optional, Set, Union, Awaitable
5
5
  from board import SCL, SDA
6
6
  from busio import I2C
7
7
 
@@ -80,34 +80,35 @@ class Manager:
80
80
  def __init__(
81
81
  self,
82
82
  send_message: Callable[[str, Union[str, dict], bool], None],
83
+ stop_client: Callable[[], Awaitable[None]],
83
84
  state_manager: StateManager,
84
85
  config_helper: ConfigHelper,
85
86
  config_file_path: str,
86
87
  relay_pins: List = [],
87
88
  input_pins: List = [],
88
89
  sensors: dict = {},
89
- modbus: dict = None,
90
- pca9685: Optional[List] = None,
91
- mcp23017: Optional[List] = None,
90
+ modbus: dict = {},
91
+ pca9685: list = [],
92
+ mcp23017: list = [],
92
93
  ds2482: Optional[List] = [],
93
94
  dallas: Optional[dict] = None,
94
95
  oled: dict = {},
95
- adc_list: Optional[List] = None,
96
- covers: Optional[List] = [],
96
+ adc: Optional[List] = None,
97
+ cover: list = [],
97
98
  ) -> None:
98
99
  """Initialize the manager."""
99
100
  _LOGGER.info("Initializing manager module.")
100
101
 
101
102
  self._loop = asyncio.get_event_loop()
102
- used_pins = set()
103
+
103
104
  self._config_helper = config_helper
104
105
  self._host_data = None
105
106
  self._config_file_path = config_file_path
106
107
  self._state_manager = state_manager
107
108
  self._event_bus = EventBus(self._loop)
108
- self._autodiscovery_messages = []
109
109
 
110
110
  self.send_message = send_message
111
+ self.stop_client = stop_client
111
112
  self._input_pins = input_pins
112
113
  self._i2cbusio = I2C(SCL, SDA)
113
114
  self._mcp = {}
@@ -135,7 +136,7 @@ class Manager:
135
136
  create_pca9685(manager=self, pca9685=pca9685, i2cbusio=self._i2cbusio)
136
137
  )
137
138
 
138
- self._configure_adc(adc_list=adc_list)
139
+ self._configure_adc(adc_list=adc)
139
140
 
140
141
  for _config in relay_pins:
141
142
  _id = _config[ID].replace(" ", "")
@@ -166,7 +167,7 @@ class Manager:
166
167
  out.send_state,
167
168
  )
168
169
 
169
- for _config in covers:
170
+ for _config in cover:
170
171
  _id = _config[ID].replace(" ", "")
171
172
  open_relay = self._output.get(_config.get("open_relay"))
172
173
  close_relay = self._output.get(_config.get("close_relay"))
@@ -204,24 +205,12 @@ class Manager:
204
205
  )
205
206
 
206
207
  _LOGGER.info("Initializing inputs. This will take a while.")
207
- for gpio in self._input_pins:
208
- pin = gpio.pop(PIN)
209
- if pin in used_pins:
210
- _LOGGER.warn("This PIN %s is already configured. Omitting it.", pin)
211
- continue
212
- used_pins.add(
213
- configure_input(
214
- gpio=gpio,
215
- pin=pin,
216
- press_callback=self.press_callback,
217
- send_ha_autodiscovery=self.send_ha_autodiscovery,
218
- )
219
- )
208
+ self.configure_inputs(reload_config=False)
220
209
 
221
210
  if oled.get("enabled", False):
222
211
  from boneio.oled import Oled
223
212
 
224
- screens = oled.get("screens")
213
+ screens = oled.get("screens", [])
225
214
 
226
215
  self._host_data = HostData(
227
216
  manager=self,
@@ -240,9 +229,26 @@ class Manager:
240
229
  except (GPIOInputException, I2CError) as err:
241
230
  _LOGGER.error("Can't configure OLED display. %s", err)
242
231
  self.prepare_ha_buttons()
243
-
244
232
  _LOGGER.info("BoneIO manager is ready.")
245
233
 
234
+ def configure_inputs(self, reload_config: bool = False):
235
+ used_pins = set()
236
+ if reload_config:
237
+ load_config_from_file(self._config_file_path)
238
+ for gpio in self._input_pins:
239
+ pin = gpio.pop(PIN)
240
+ if pin in used_pins:
241
+ _LOGGER.warn("This PIN %s is already configured. Omitting it.", pin)
242
+ continue
243
+ used_pins.add(
244
+ configure_input(
245
+ gpio=gpio,
246
+ pin=pin,
247
+ press_callback=self.press_callback,
248
+ send_ha_autodiscovery=self.send_ha_autodiscovery,
249
+ )
250
+ )
251
+
246
252
  def append_task(self, coro: Coroutine, name: str = "Unknown") -> asyncio.Future:
247
253
  """Add task to run with asyncio loop."""
248
254
  _LOGGER.debug("Appending update task for %s", name)
@@ -337,20 +343,22 @@ class Manager:
337
343
  )
338
344
 
339
345
  def _configure_modbus(self, modbus: dict) -> None:
340
- if modbus and modbus.get(UART) in UARTS:
346
+ uart = modbus.get(UART)
347
+ if uart and uart in UARTS:
341
348
  try:
342
- self._modbus = Modbus(UARTS[modbus.get(UART)])
349
+ self._modbus = Modbus(UARTS[uart])
343
350
  except ModbusUartException:
344
351
  _LOGGER.error(
345
352
  "This UART %s can't be used for modbus communication.",
346
- modbus.get(UART),
353
+ uart,
347
354
  )
348
355
  self._modbus = None
349
356
 
350
357
  def _configure_temp_sensors(self, sensors: dict) -> None:
351
358
  for sensor_type in (LM75, MCP_TEMP_9808):
352
- if sensors.get(sensor_type):
353
- for temp_def in sensors.get(sensor_type):
359
+ sensor = sensors.get(sensor_type)
360
+ if sensor:
361
+ for temp_def in sensor:
354
362
  temp_sensor = create_temp_sensor(
355
363
  manager=self,
356
364
  topic_prefix=self._config_helper.topic_prefix,
@@ -409,10 +417,19 @@ class Manager:
409
417
  def prepare_ha_buttons(self) -> None:
410
418
  """Prepare HA buttons for reload."""
411
419
  self.send_ha_autodiscovery(
412
- id="Logger",
420
+ id="logger",
413
421
  name="Logger reload",
414
422
  ha_type=BUTTON,
415
423
  availability_msg_func=ha_button_availabilty_message,
424
+ entity_category="config",
425
+ )
426
+ self.send_ha_autodiscovery(
427
+ id="restart",
428
+ name="Restart boneIO",
429
+ ha_type=BUTTON,
430
+ payload_press="restart",
431
+ availability_msg_func=ha_button_availabilty_message,
432
+ entity_category="config",
416
433
  )
417
434
 
418
435
  @property
@@ -431,7 +448,7 @@ class Manager:
431
448
  """Press callback to use in input gpio.
432
449
  If relay input map is provided also toggle action on relay or cover or mqtt."""
433
450
  topic = f"{self._config_helper.topic_prefix}/{input_type}/{inpin}"
434
- self.send_message(topic=topic, payload=x)
451
+ self.send_message(topic=topic, payload=x, retain=False)
435
452
  for action_definition in actions:
436
453
  _LOGGER.debug("Executing action %s", action_definition)
437
454
  if action_definition[ACTION] == OUTPUT:
@@ -480,13 +497,14 @@ class Manager:
480
497
  payload = availability_msg_func(topic=topic_prefix, id=id, name=name, **kwargs)
481
498
  topic = f"{self._config_helper.ha_discovery_prefix}/{ha_type}/{topic_prefix}/{id}/config"
482
499
  _LOGGER.debug("Sending HA discovery for %s, %s.", ha_type, name)
483
- self._config_helper.add_autodiscovery_msg(topic=topic, payload=payload)
500
+ self._config_helper.add_autodiscovery_msg(topic=topic, ha_type=ha_type, payload=payload)
484
501
  self.send_message(topic=topic, payload=payload, retain=True)
485
502
 
486
503
  def resend_autodiscovery(self) -> None:
487
504
  for msg in self._config_helper.autodiscovery_msgs:
488
505
  self.send_message(**msg, retain=True)
489
506
 
507
+
490
508
  async def receive_message(self, topic: str, message: str) -> None:
491
509
  """Callback for receiving action from Mqtt."""
492
510
  _LOGGER.debug("Processing topic %s with message %s.", topic, message)
@@ -558,6 +576,9 @@ class Manager:
558
576
  if device_id == "logger" and message == "reload":
559
577
  _LOGGER.info("Reloading logger configuration.")
560
578
  self._logger_reload()
579
+ elif device_id == "restart" and message == "restart":
580
+ _LOGGER.info("Exiting process. Systemd should restart it soon.")
581
+ await self.stop_client()
561
582
 
562
583
  @property
563
584
  def output(self) -> dict:
boneio/mqtt_client.py CHANGED
@@ -2,12 +2,13 @@
2
2
  Provide an MQTT client for providing BoneIO MQTT broker.
3
3
  Code based on cgarwood/python-openzwave-mqtt.
4
4
  """
5
+ from __future__ import annotations
5
6
  import asyncio
6
7
  import json
7
8
  import logging
8
9
  import uuid
9
10
  from contextlib import AsyncExitStack
10
- from typing import Any, Callable, Optional, Set, Tuple, Union
11
+ from typing import Any, Callable, Optional, Set, Union, Awaitable
11
12
 
12
13
  import paho.mqtt.client as mqtt
13
14
  from asyncio_mqtt import Client as AsyncioClient
@@ -18,6 +19,8 @@ from paho.mqtt.subscribeoptions import SubscribeOptions
18
19
  from boneio.const import ONLINE, PAHO, STATE
19
20
  from boneio.helper import UniqueQueue
20
21
  from boneio.helper.config import ConfigHelper
22
+ from boneio.manager import Manager
23
+ from boneio.helper.exceptions import RestartRequestException
21
24
 
22
25
  _LOGGER = logging.getLogger(__name__)
23
26
 
@@ -45,6 +48,8 @@ class MQTTClient:
45
48
  self.reconnect_interval = 1
46
49
  self._connection_established = False
47
50
  self.publish_queue: UniqueQueue = UniqueQueue()
51
+ self._discovery_topics = [f"{self._config_helper.ha_discovery_prefix}/{ha_type}/{self._config_helper.topic_prefix}/#" for ha_type in self._config_helper.ha_types] if self._config_helper.ha_discovery else []
52
+ self._topics = [self._config_helper.subscribe_topic, "homeassistant/status"]
48
53
 
49
54
  def create_client(self) -> None:
50
55
  """Create the asyncio client."""
@@ -85,7 +90,7 @@ class MQTTClient:
85
90
 
86
91
  async def subscribe( # pylint:disable=too-many-arguments
87
92
  self,
88
- topics: Tuple[str, str],
93
+ topics: list[str],
89
94
  qos: int = 0,
90
95
  options: Optional[SubscribeOptions] = None,
91
96
  properties: Optional[Properties] = None,
@@ -108,7 +113,10 @@ class MQTTClient:
108
113
  await self.asyncio_client.subscribe(topic=args, **params, timeout=timeout)
109
114
 
110
115
  async def unsubscribe(
111
- self, topic: str, properties: Optional[Properties] = None, timeout: float = 10.0
116
+ self,
117
+ topics: list[str],
118
+ properties: Optional[Properties] = None,
119
+ timeout: float = 10.0,
112
120
  ) -> None:
113
121
  """Unsubscribe from topic.
114
122
 
@@ -118,10 +126,10 @@ class MQTTClient:
118
126
  if properties:
119
127
  params["properties"] = properties
120
128
 
121
- await self.asyncio_client.unsubscribe(topic, **params)
129
+ await self.asyncio_client.unsubscribe(topic=topics, **params)
122
130
 
123
131
  def send_message(
124
- self, topic: str, payload: Union[str, dict], retain: bool = False
132
+ self, topic: str, payload: Union[str, dict, None], retain: bool = False
125
133
  ) -> None:
126
134
  """Send a message from the manager options."""
127
135
  to_publish = (
@@ -138,7 +146,7 @@ class MQTTClient:
138
146
  await self.publish(*to_publish)
139
147
  self.publish_queue.task_done()
140
148
 
141
- async def start_client(self, manager: any) -> None:
149
+ async def start_client(self, manager: Manager) -> None:
142
150
  """Start the client with the manager."""
143
151
  # Reconnect automatically until the client is stopped.
144
152
  while True:
@@ -155,7 +163,13 @@ class MQTTClient:
155
163
  await asyncio.sleep(self.reconnect_interval)
156
164
  self.create_client() # reset connect/reconnect futures
157
165
 
158
- async def _subscribe_manager(self, manager: any) -> None:
166
+ async def stop_client(self) -> None:
167
+ await self.unsubscribe(
168
+ topics=self._topics
169
+ )
170
+ raise RestartRequestException("Restart requested.")
171
+
172
+ async def _subscribe_manager(self, manager: Manager) -> None:
159
173
  """Connect and subscribe to manager topics + host stats."""
160
174
  async with AsyncExitStack() as stack:
161
175
  tasks: Set[asyncio.Task] = set()
@@ -170,11 +184,11 @@ class MQTTClient:
170
184
 
171
185
  # Messages that doesn't match a filter will get logged and handled here.
172
186
  messages = await stack.enter_async_context(
173
- self.asyncio_client.unfiltered_messages()
187
+ self.asyncio_client.messages()
174
188
  )
175
189
 
176
190
  messages_task = asyncio.create_task(
177
- handle_messages(messages, manager.receive_message)
191
+ self.handle_messages(messages, manager.receive_message)
178
192
  )
179
193
  if not self._connection_established:
180
194
  self._connection_established = True
@@ -183,16 +197,28 @@ class MQTTClient:
183
197
  tasks.add(messages_task)
184
198
 
185
199
  await self.subscribe(
186
- topics=(self._config_helper.subscribe_topic, "homeassistant/status")
200
+ topics=self._topics
201
+ )
202
+ await self.subscribe(
203
+ topics=self._discovery_topics
187
204
  )
188
205
 
189
206
  # Wait for everything to complete (or fail due to, e.g., network errors).
190
207
  await asyncio.gather(*tasks)
191
208
 
192
-
193
- async def handle_messages(messages: Any, callback: Callable[[str, str], None]) -> None:
194
- """Handle messages with callback."""
195
- async for message in messages:
196
- payload = message.payload.decode()
197
- _LOGGER.debug("Received message topic: %s, payload: %s", message.topic, payload)
198
- await callback(message.topic, payload)
209
+ async def handle_messages(self, messages: Any, callback: Callable[[str, str], Awaitable[None]]):
210
+ """Handle messages with callback or remove osbolete HA discovery messages."""
211
+ async for message in messages:
212
+ payload = message.payload.decode()
213
+ callback_start = True
214
+ for discovery_topic in self._discovery_topics:
215
+ if message.topic.matches(discovery_topic):
216
+ callback_start = False
217
+ topic = str(message.topic)
218
+ if message.payload and not self._config_helper.is_topic_in_autodiscovery(topic):
219
+ _LOGGER.info("Removing unused discovery entity %s", topic)
220
+ self.send_message(topic=topic, payload=None, retain=True)
221
+ break
222
+ if callback_start:
223
+ _LOGGER.debug("Received message topic: %s, payload: %s", message.topic, payload)
224
+ await callback(str(message.topic), payload)
boneio/relay/basic.py CHANGED
@@ -49,13 +49,13 @@ class BasicRelay(BasicMqtt):
49
49
  return self._output_type == LIGHT
50
50
 
51
51
  @property
52
- def id(self) -> bool:
52
+ def id(self) -> str:
53
53
  """Id of the relay.
54
54
  Has to be trimmed out of spaces because of MQTT handling in HA."""
55
55
  return self._id or self._pin
56
56
 
57
57
  @property
58
- def name(self) -> bool:
58
+ def name(self) -> str:
59
59
  """Not trimmed id."""
60
60
  return self._name or self._pin
61
61
 
@@ -70,8 +70,7 @@ class BasicRelay(BasicMqtt):
70
70
  self._state = state
71
71
  if self.output_type != NONE:
72
72
  self._send_message(
73
- topic=self._send_topic,
74
- payload={STATE: state},
73
+ topic=self._send_topic, payload={STATE: state}, retain=True
75
74
  )
76
75
  self._loop.call_soon_threadsafe(self._callback)
77
76
 
boneio/runner.py CHANGED
@@ -1,7 +1,9 @@
1
1
  """Runner code for boneIO. Based on HA runner."""
2
+ from __future__ import annotations
2
3
  import asyncio
3
4
  import logging
4
5
  import os
6
+ from typing import Any
5
7
 
6
8
  from boneio.const import (
7
9
  ADC,
@@ -34,13 +36,24 @@ from boneio.mqtt_client import MQTTClient
34
36
 
35
37
  _LOGGER = logging.getLogger(__name__)
36
38
 
39
+ config_modules = [
40
+ {"name": MCP23017, "default": []},
41
+ {"name": PCA9685, "default": []},
42
+ {"name": DS2482, "default": []},
43
+ {"name": ADC, "default": []},
44
+ {"name": COVER, "default": []},
45
+ {"name": MODBUS, "default": {}},
46
+ {"name": OLED, "default": {}},
47
+ {"name": DALLAS, "default": None},
48
+ ]
49
+
37
50
 
38
51
  async def async_run(
39
52
  config: dict,
40
53
  config_file: str,
41
54
  mqttusername: str = "",
42
55
  mqttpassword: str = "",
43
- ):
56
+ ) -> list[Any]:
44
57
  """Run BoneIO."""
45
58
 
46
59
  _config_helper = ConfigHelper(
@@ -56,9 +69,14 @@ async def async_run(
56
69
  port=config[MQTT].get(PORT, 1883),
57
70
  config_helper=_config_helper,
58
71
  )
72
+ manager_kwargs = {
73
+ item["name"]: config.get(item["name"], item["default"])
74
+ for item in config_modules
75
+ }
59
76
 
60
77
  manager = Manager(
61
78
  send_message=client.send_message,
79
+ stop_client=client.stop_client,
62
80
  relay_pins=config.get(OUTPUT, []),
63
81
  input_pins=config.get(INPUT, []),
64
82
  config_file_path=config_file,
@@ -72,14 +90,7 @@ async def async_run(
72
90
  MODBUS: config.get("modbus_sensors"),
73
91
  ONEWIRE: config.get(SENSOR, []),
74
92
  },
75
- mcp23017=config.get(MCP23017, []),
76
- pca9685=config.get(PCA9685, []),
77
- ds2482=config.get(DS2482, []),
78
- dallas=config.get(DALLAS),
79
- modbus=config.get(MODBUS),
80
- oled=config.get(OLED),
81
- adc_list=config.get(ADC, []),
82
- covers=config.get(COVER, []),
93
+ **manager_kwargs,
83
94
  )
84
95
  tasks = set()
85
96
  tasks.update(manager.get_tasks())
@@ -1,6 +1,6 @@
1
1
  required: False
2
2
  type: list
3
- excludes: ["single", "double", "long"]
3
+ excludes: ["pressed", "released"]
4
4
  dependencies:
5
5
  kind: ['sensor']
6
6
  schema: !include actions.yaml
@@ -1,6 +1,6 @@
1
1
  required: False
2
2
  type: list
3
- excludes: ["pressed", "released"]
3
+ excludes: ["single", "double", "long"]
4
4
  dependencies:
5
5
  kind: ['switch']
6
6
  schema: !include actions.yaml
boneio/schema/schema.yaml CHANGED
@@ -376,6 +376,12 @@ input:
376
376
  required: True
377
377
  meta:
378
378
  label: Check if sensor type is inverted. Only aplicable for sensor kind.
379
+ device_class:
380
+ type: string
381
+ required: False
382
+ allowed: ["battery", "battery_charging", "carbon_monoxide", "cold", "connectivity", "door", "garage_door", "gas", "heat", "light", "lock", "moisture", "motion", "moving", "occupancy", "opening", "plug", "power", "presence", "problem", "running", "safety", "smoke", "sound", "tamper", "vibration", "window"]
383
+ meta:
384
+ label: Device class to use in HA
379
385
  actions:
380
386
  required: False
381
387
  type: dict
boneio/sensor/gpio.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """GPIOInputButton to receive signals."""
2
2
  import logging
3
3
  from functools import partial
4
-
4
+ import asyncio
5
5
  from boneio.const import BOTH, PRESSED, RELEASED
6
6
  from boneio.helper import GpioBaseClass, edge_detect
7
7
 
@@ -14,24 +14,25 @@ class GpioInputSensor(GpioBaseClass):
14
14
  def __init__(self, **kwargs) -> None:
15
15
  """Setup GPIO Input Button"""
16
16
  super().__init__(**kwargs)
17
+ self._state = self.is_pressed
17
18
  self._click_type = (
18
19
  (RELEASED, PRESSED)
19
20
  if kwargs.get("inverted", False)
20
21
  else (PRESSED, RELEASED)
21
22
  )
22
- edge_detect(
23
- self._pin,
24
- callback=self._handle_press,
25
- bounce=self._bounce_time.total_milliseconds,
26
- edge=BOTH,
27
- )
28
- self._loop.call_soon(self._handle_press, self._pin)
29
23
  _LOGGER.debug("Configured sensor pin %s", self._pin)
24
+ asyncio.create_task(self._run())
25
+
26
+ async def _run(self) -> None:
27
+ while True:
28
+ self.check_state(state=self.is_pressed)
29
+ await asyncio.sleep(self._bounce_time.total_in_seconds)
30
30
 
31
- def _handle_press(self, pin: str) -> None:
32
- """Handle the button press callback"""
33
- # Ignore if we are in a long press
34
- click_type = self._click_type[0] if self.is_pressed else self._click_type[1]
31
+ def check_state(self, state: bool) -> None:
32
+ if state == self._state:
33
+ return
34
+ self._state = state
35
+ click_type = self._click_type[0] if state else self._click_type[1]
35
36
  _LOGGER.debug("%s event on pin %s", click_type, self._pin)
36
37
  self._loop.call_soon_threadsafe(
37
38
  partial(self._press_callback, click_type, self._pin)
boneio/version.py CHANGED
@@ -1,2 +1,2 @@
1
1
  # flake8: noqa
2
- __version__ = "0.6.0dev4"
2
+ __version__ = "0.6.0dev5"
@@ -1,4 +0,0 @@
1
- Wheel-Version: 1.0
2
- Generator: pdm-pep517 1.1.3
3
- Root-Is-Purelib: True
4
- Tag: py3-none-any