boneIO 0.6.0__tar.gz → 0.6.0dev1__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.
Potentially problematic release.
This version of boneIO might be problematic. Click here for more details.
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/PKG-INFO +1 -1
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/bonecli.py +3 -9
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/const.py +1 -2
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/helper/__init__.py +0 -2
- boneIO-0.6.0.dev1/boneio/helper/config.py +48 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/helper/events.py +5 -65
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/helper/exceptions.py +0 -5
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/helper/gpio.py +2 -2
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/helper/ha_discovery.py +3 -9
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/helper/loader.py +0 -1
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/helper/mqtt.py +2 -2
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/helper/yaml_util.py +6 -4
- boneIO-0.6.0.dev1/boneio/input/gpio.py +123 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/manager.py +44 -78
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/mqtt_client.py +17 -43
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/relay/basic.py +4 -3
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/runner.py +9 -20
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/schema/actions.yaml +1 -2
- boneIO-0.6.0/boneio/schema/actions_switch.yaml → boneIO-0.6.0.dev1/boneio/schema/actions_sensor.yaml +1 -1
- boneIO-0.6.0/boneio/schema/actions_sensor.yaml → boneIO-0.6.0.dev1/boneio/schema/actions_switch.yaml +1 -1
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/schema/schema.yaml +0 -6
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/sensor/gpio.py +12 -13
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/sensor/temp/dallas.py +1 -3
- boneIO-0.6.0.dev1/boneio/version.py +2 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/pyproject.toml +19 -20
- boneIO-0.6.0/boneio/helper/click_timer.py +0 -37
- boneIO-0.6.0/boneio/helper/config.py +0 -74
- boneIO-0.6.0/boneio/input/gpio.py +0 -75
- boneIO-0.6.0/boneio/version.py +0 -2
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/LICENSE +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/README.md +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/__init__.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/cover.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/example_config/__init__.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/example_config/adc.yaml +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/example_config/config.yaml +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/example_config/cover.yaml +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/example_config/input.yaml +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/example_config/led32x4A.yaml +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/example_config/output24x16A.yaml +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/example_config/output32x5A.yaml +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/fonts/danube__.ttf +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/helper/async_updater.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/helper/filter.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/helper/logger.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/helper/oled.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/helper/onewire/W1ThermSensor.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/helper/onewire/__init__.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/helper/onewire/ds2482.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/helper/onewire/onewire.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/helper/queue.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/helper/state_manager.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/helper/stats.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/helper/timeperiod.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/helper/util.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/input/__init__.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/modbus.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/oled.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/relay/__init__.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/relay/gpio.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/relay/mcp.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/relay/pca.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/schema/filters.yaml +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/schema/filters_adc.yaml +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/schema/id.yaml +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/schema/temp_unit.yaml +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/schema/update_interval.yaml +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/sensor/__init__.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/sensor/adc.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/sensor/modbus/__init__.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/sensor/modbus/dts1964_3f.json +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/sensor/modbus/pt100.json +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/sensor/modbus/r4dcb08.json +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/sensor/modbus/sdm120.json +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/sensor/modbus/sdm630.json +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/sensor/modbus/sofar.json +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/sensor/temp/__init__.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/sensor/temp/lm75.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/boneio/sensor/temp/mcp9808.py +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/tests/32_5_config.yaml +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/tests/bandit.yaml +0 -0
- {boneIO-0.6.0 → boneIO-0.6.0.dev1}/tests/relay_32_5.py +0 -0
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
"""Bonecli script."""
|
|
2
|
-
from __future__ import annotations
|
|
3
2
|
import argparse
|
|
4
3
|
import asyncio
|
|
5
4
|
import logging
|
|
6
5
|
import sys
|
|
7
6
|
import os
|
|
8
|
-
from typing import Any
|
|
9
7
|
|
|
10
8
|
os.environ["W1THERMSENSOR_NO_KERNEL_MODULE"] = "1"
|
|
11
9
|
|
|
@@ -14,7 +12,7 @@ from yaml import MarkedYAMLError
|
|
|
14
12
|
|
|
15
13
|
from boneio.const import ACTION
|
|
16
14
|
from boneio.helper import load_config_from_file
|
|
17
|
-
from boneio.helper.exceptions import ConfigurationException
|
|
15
|
+
from boneio.helper.exceptions import ConfigurationException
|
|
18
16
|
from boneio.helper.logger import configure_logger
|
|
19
17
|
from boneio.runner import async_run
|
|
20
18
|
from boneio.version import __version__
|
|
@@ -71,7 +69,7 @@ def get_arguments() -> argparse.Namespace:
|
|
|
71
69
|
return arguments
|
|
72
70
|
|
|
73
71
|
|
|
74
|
-
def run(config: str, debug: int, mqttusername: str = "", mqttpassword: str = "")
|
|
72
|
+
def run(config: str, debug: int, mqttusername: str = "", mqttpassword: str = ""):
|
|
75
73
|
"""Run BoneIO."""
|
|
76
74
|
_LOGGER.info("BoneIO %s starting.", __version__)
|
|
77
75
|
try:
|
|
@@ -80,7 +78,7 @@ def run(config: str, debug: int, mqttusername: str = "", mqttpassword: str = "")
|
|
|
80
78
|
_LOGGER.error("Config not loaded. Exiting.")
|
|
81
79
|
return 1
|
|
82
80
|
configure_logger(log_config=_config.get("logger"), debug=debug)
|
|
83
|
-
asyncio.run(
|
|
81
|
+
return asyncio.run(
|
|
84
82
|
async_run(
|
|
85
83
|
config=_config,
|
|
86
84
|
config_file=config,
|
|
@@ -88,10 +86,6 @@ def run(config: str, debug: int, mqttusername: str = "", mqttpassword: str = "")
|
|
|
88
86
|
mqttpassword=mqttpassword,
|
|
89
87
|
),
|
|
90
88
|
)
|
|
91
|
-
return 0
|
|
92
|
-
except RestartRequestException as err:
|
|
93
|
-
_LOGGER.info(err)
|
|
94
|
-
return 0
|
|
95
89
|
except (ConfigurationException, MarkedYAMLError) as err:
|
|
96
90
|
_LOGGER.error("Failed to load config. %s Exiting.", err)
|
|
97
91
|
return 1
|
|
@@ -18,7 +18,6 @@ RELAY = "relay"
|
|
|
18
18
|
LED = "led"
|
|
19
19
|
ON = "ON"
|
|
20
20
|
OFF = "OFF"
|
|
21
|
-
TOGGLE = "TOGGLE"
|
|
22
21
|
STATE = "state"
|
|
23
22
|
BRIGHTNESS = "brightness"
|
|
24
23
|
SET_BRIGHTNESS = "set_brightness"
|
|
@@ -56,7 +55,7 @@ UARTS = {
|
|
|
56
55
|
"uart5": {ID: "/dev/ttyS5", TX: "P8.37", RX: "P8.38"},
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
relay_actions = {ON: "turn_on", OFF: "turn_off", TOGGLE: "toggle"}
|
|
58
|
+
relay_actions = {ON: "turn_on", OFF: "turn_off", "TOGGLE": "toggle"}
|
|
60
59
|
|
|
61
60
|
# HA CONSTS
|
|
62
61
|
HOMEASSISTANT = "homeassistant"
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Helper dir for BoneIO."""
|
|
2
2
|
|
|
3
3
|
from boneio.helper.exceptions import GPIOInputException, GPIOOutputException, I2CError
|
|
4
|
-
from boneio.helper.click_timer import ClickTimer
|
|
5
4
|
from boneio.helper.gpio import (
|
|
6
5
|
GpioBaseClass,
|
|
7
6
|
configure_pin,
|
|
@@ -71,5 +70,4 @@ __all__ = [
|
|
|
71
70
|
"TimePeriod",
|
|
72
71
|
"callback",
|
|
73
72
|
"is_callback",
|
|
74
|
-
"ClickTimer",
|
|
75
73
|
]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module to provide basic config options.
|
|
3
|
+
"""
|
|
4
|
+
from _collections_abc import dict_values
|
|
5
|
+
|
|
6
|
+
from boneio.const import BONEIO, HOMEASSISTANT
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ConfigHelper:
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
topic_prefix: str = BONEIO,
|
|
13
|
+
ha_discovery: bool = True,
|
|
14
|
+
ha_discovery_prefix: str = HOMEASSISTANT,
|
|
15
|
+
):
|
|
16
|
+
self._topic_prefix = topic_prefix
|
|
17
|
+
self._ha_discovery = ha_discovery
|
|
18
|
+
self._ha_discovery_prefix = ha_discovery_prefix
|
|
19
|
+
self._autodiscovery_messages = {}
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def topic_prefix(self) -> str:
|
|
23
|
+
return self._topic_prefix
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def ha_discovery(self) -> bool:
|
|
27
|
+
return self._ha_discovery
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def ha_discovery_prefix(self) -> str:
|
|
31
|
+
return self._ha_discovery_prefix
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def cmd_topic_prefix(self) -> str:
|
|
35
|
+
return f"{self.topic_prefix}/cmd/"
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def subscribe_topic(self) -> str:
|
|
39
|
+
return f"{self.cmd_topic_prefix}+/+/#"
|
|
40
|
+
|
|
41
|
+
def add_autodiscovery_msg(self, topic: str, payload: str):
|
|
42
|
+
"""Add autodiscovery message."""
|
|
43
|
+
self._autodiscovery_messages[topic] = {"topic": topic, "payload": payload}
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def autodiscovery_msgs(self) -> dict_values:
|
|
47
|
+
"""Get autodiscovery messages"""
|
|
48
|
+
return self._autodiscovery_messages.values()
|
|
@@ -21,9 +21,7 @@ def utcnow() -> dt.datetime:
|
|
|
21
21
|
time_tracker_utcnow = utcnow
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
def _async_create_timer(
|
|
25
|
-
loop: asyncio.AbstractEventLoop, event_callback
|
|
26
|
-
) -> CALLBACK_TYPE:
|
|
24
|
+
def _async_create_timer(loop: asyncio.AbstractEventLoop, event_callback) -> None:
|
|
27
25
|
"""Create a timer that will start on BoneIO start."""
|
|
28
26
|
handle = None
|
|
29
27
|
|
|
@@ -151,50 +149,7 @@ def async_track_point_in_time(
|
|
|
151
149
|
"""Add a listener that fires once after a specific point in UTC time."""
|
|
152
150
|
# Ensure point_in_time is UTC
|
|
153
151
|
utc_point_in_time = as_utc(point_in_time)
|
|
154
|
-
expected_fire_timestamp = utc_point_in_time.timestamp()
|
|
155
|
-
|
|
156
|
-
# Since this is called once, we accept a so we can avoid
|
|
157
|
-
# having to figure out how to call the action every time its called.
|
|
158
|
-
cancel_callback: Optional[asyncio.TimerHandle] = None
|
|
159
|
-
|
|
160
|
-
@callback
|
|
161
|
-
def run_action(job) -> None:
|
|
162
|
-
"""Call the action."""
|
|
163
|
-
nonlocal cancel_callback
|
|
164
|
-
|
|
165
|
-
# Depending on the available clock support (including timer hardware
|
|
166
|
-
# and the OS kernel) it can happen that we fire a little bit too early
|
|
167
|
-
# as measured by utcnow(). That is bad when callbacks have assumptions
|
|
168
|
-
# about the current time. Thus, we rearm the timer for the remaining
|
|
169
|
-
# time.
|
|
170
|
-
delta = expected_fire_timestamp - time.time()
|
|
171
|
-
if delta > 0:
|
|
172
|
-
_LOGGER.debug("Called %f seconds too early, rearming", delta)
|
|
173
|
-
|
|
174
|
-
cancel_callback = loop.call_later(delta, run_action, job)
|
|
175
|
-
return
|
|
176
|
-
|
|
177
|
-
loop.call_soon(job, utc_point_in_time)
|
|
178
|
-
|
|
179
|
-
delta = expected_fire_timestamp - time.time()
|
|
180
|
-
cancel_callback = loop.call_later(delta, run_action, action)
|
|
181
|
-
|
|
182
|
-
@callback
|
|
183
|
-
def unsub_point_in_time_listener() -> None:
|
|
184
|
-
"""Cancel the call_later."""
|
|
185
|
-
assert cancel_callback is not None
|
|
186
|
-
cancel_callback.cancel()
|
|
187
|
-
|
|
188
|
-
return unsub_point_in_time_listener
|
|
189
152
|
|
|
190
|
-
|
|
191
|
-
@callback
|
|
192
|
-
def async_track_point_in_timestamp(
|
|
193
|
-
loop: asyncio.AbstractEventLoop,
|
|
194
|
-
action,
|
|
195
|
-
timestamp: float,
|
|
196
|
-
) -> CALLBACK_TYPE:
|
|
197
|
-
"""Add a listener that fires once after a specific point in UTC time."""
|
|
198
153
|
# Since this is called once, we accept a so we can avoid
|
|
199
154
|
# having to figure out how to call the action every time its called.
|
|
200
155
|
cancel_callback: Optional[asyncio.TimerHandle] = None
|
|
@@ -204,24 +159,23 @@ def async_track_point_in_timestamp(
|
|
|
204
159
|
"""Call the action."""
|
|
205
160
|
nonlocal cancel_callback
|
|
206
161
|
|
|
207
|
-
now =
|
|
162
|
+
now = time_tracker_utcnow()
|
|
208
163
|
|
|
209
164
|
# Depending on the available clock support (including timer hardware
|
|
210
165
|
# and the OS kernel) it can happen that we fire a little bit too early
|
|
211
166
|
# as measured by utcnow(). That is bad when callbacks have assumptions
|
|
212
167
|
# about the current time. Thus, we rearm the timer for the remaining
|
|
213
168
|
# time.
|
|
214
|
-
delta =
|
|
169
|
+
delta = (utc_point_in_time - now).total_seconds()
|
|
215
170
|
if delta > 0:
|
|
216
171
|
_LOGGER.debug("Called %f seconds too early, rearming", delta)
|
|
217
172
|
|
|
218
173
|
cancel_callback = loop.call_later(delta, run_action, job)
|
|
219
174
|
return
|
|
220
175
|
|
|
221
|
-
loop.call_soon(job,
|
|
176
|
+
loop.call_soon(job, utc_point_in_time)
|
|
222
177
|
|
|
223
|
-
|
|
224
|
-
delta = timestamp - now
|
|
178
|
+
delta = utc_point_in_time.timestamp() - time.time()
|
|
225
179
|
cancel_callback = loop.call_later(delta, run_action, action)
|
|
226
180
|
|
|
227
181
|
@callback
|
|
@@ -231,17 +185,3 @@ def async_track_point_in_timestamp(
|
|
|
231
185
|
cancel_callback.cancel()
|
|
232
186
|
|
|
233
187
|
return unsub_point_in_time_listener
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
@callback
|
|
237
|
-
def async_call_later_miliseconds(
|
|
238
|
-
loop: asyncio.AbstractEventLoop,
|
|
239
|
-
action,
|
|
240
|
-
delay: float,
|
|
241
|
-
) -> CALLBACK_TYPE:
|
|
242
|
-
"""Add a listener that fires once after a specific point in UTC time."""
|
|
243
|
-
# Ensure point_in_time is UTC
|
|
244
|
-
expected_fire_timestamp = time.time() + (delay / 1000)
|
|
245
|
-
return async_track_point_in_timestamp(
|
|
246
|
-
loop=loop, action=action, timestamp=expected_fire_timestamp
|
|
247
|
-
)
|
|
@@ -8,7 +8,6 @@ class BoneIOException(Exception):
|
|
|
8
8
|
class GPIOInputException(BoneIOException):
|
|
9
9
|
"""GPIOInput Exception."""
|
|
10
10
|
|
|
11
|
-
|
|
12
11
|
class GPIOOutputException(BoneIOException):
|
|
13
12
|
"""GPIOInput Exception."""
|
|
14
13
|
|
|
@@ -31,7 +30,3 @@ class CoverRelayException(BoneIOException):
|
|
|
31
30
|
|
|
32
31
|
class ModbusUartException(BoneIOException):
|
|
33
32
|
"""Cover configuration exception."""
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class RestartRequestException(BoneIOException):
|
|
37
|
-
"""Restart exception."""
|
|
@@ -67,7 +67,7 @@ def write_output(pin: str, value: str) -> None:
|
|
|
67
67
|
GPIO.output(pin, value)
|
|
68
68
|
|
|
69
69
|
|
|
70
|
-
def read_input(pin: str, on_state: Gpio_States = LOW) ->
|
|
70
|
+
def read_input(pin: str, on_state: Gpio_States = LOW) -> None:
|
|
71
71
|
"""Read a value from a GPIO."""
|
|
72
72
|
return GPIO.input(pin) is on_state
|
|
73
73
|
|
|
@@ -97,6 +97,6 @@ class GpioBaseClass:
|
|
|
97
97
|
setup_input(pin=self._pin, pull_mode=gpio_mode)
|
|
98
98
|
|
|
99
99
|
@property
|
|
100
|
-
def is_pressed(self)
|
|
100
|
+
def is_pressed(self):
|
|
101
101
|
"""Is button pressed."""
|
|
102
102
|
return read_input(self._pin)
|
|
@@ -51,7 +51,6 @@ 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
|
-
|
|
55
54
|
def ha_led_availabilty_message(id: str, topic: str = "boneIO", **kwargs):
|
|
56
55
|
"""Create LED availability topic for HA."""
|
|
57
56
|
msg = ha_availabilty_message(device_type=RELAY, topic=topic, id=id, **kwargs)
|
|
@@ -65,14 +64,11 @@ def ha_led_availabilty_message(id: str, topic: str = "boneIO", **kwargs):
|
|
|
65
64
|
msg["brightness_value_template"] = "{{ value_json.brightness }}"
|
|
66
65
|
return msg
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
def ha_button_availabilty_message(
|
|
70
|
-
id: str, topic: str = "boneIO", payload_press: str = "reload", **kwargs
|
|
71
|
-
):
|
|
67
|
+
def ha_button_availabilty_message(id: str, topic: str = "boneIO", **kwargs):
|
|
72
68
|
"""Create BUTTON availability topic for HA."""
|
|
73
69
|
msg = ha_availabilty_message(device_type="button", topic=topic, id=id, **kwargs)
|
|
74
70
|
msg["command_topic"] = f"{topic}/cmd/button/{id}/set"
|
|
75
|
-
msg["payload_press"] =
|
|
71
|
+
msg["payload_press"] = "reload"
|
|
76
72
|
return msg
|
|
77
73
|
|
|
78
74
|
|
|
@@ -106,11 +102,9 @@ def ha_sensor_availabilty_message(unit_of_measurement: str = None, **kwargs):
|
|
|
106
102
|
return msg
|
|
107
103
|
|
|
108
104
|
|
|
109
|
-
def ha_binary_sensor_availabilty_message(id: str, name: str,
|
|
105
|
+
def ha_binary_sensor_availabilty_message(id: str, name: str, topic: str = "boneIO"):
|
|
110
106
|
"""Create availability topic for HA."""
|
|
111
|
-
kwargs = {"device_class": device_class} if device_class else {}
|
|
112
107
|
return {
|
|
113
|
-
**kwargs,
|
|
114
108
|
"availability": [{"topic": f"{topic}/{STATE}"}],
|
|
115
109
|
"device": {
|
|
116
110
|
"identifiers": [topic],
|
|
@@ -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) ->
|
|
24
|
+
def id(self) -> bool:
|
|
25
25
|
"""Id of the module."""
|
|
26
26
|
return self._id
|
|
27
27
|
|
|
28
28
|
@property
|
|
29
|
-
def name(self)
|
|
29
|
+
def name(self):
|
|
30
30
|
"""Return name of the sensor."""
|
|
31
31
|
return self._name
|
|
@@ -8,7 +8,7 @@ from typing import Any, Tuple
|
|
|
8
8
|
from cerberus import TypeDefinition, Validator
|
|
9
9
|
from yaml import MarkedYAMLError, SafeLoader, YAMLError, load
|
|
10
10
|
|
|
11
|
-
from boneio.const import COVER, ID, OUTPUT
|
|
11
|
+
from boneio.const import COVER, ID, OUTPUT
|
|
12
12
|
from boneio.helper.exceptions import ConfigurationException
|
|
13
13
|
from boneio.helper.timeperiod import TimePeriod
|
|
14
14
|
|
|
@@ -103,7 +103,9 @@ BoneIOLoader.add_constructor(
|
|
|
103
103
|
BoneIOLoader.add_constructor(
|
|
104
104
|
"!include_dir_merge_named", BoneIOLoader.construct_include_dir_merge_named
|
|
105
105
|
)
|
|
106
|
-
BoneIOLoader.add_constructor(
|
|
106
|
+
BoneIOLoader.add_constructor(
|
|
107
|
+
"!include_files", BoneIOLoader.construct_include_files
|
|
108
|
+
)
|
|
107
109
|
|
|
108
110
|
|
|
109
111
|
def filter_yaml_files(files):
|
|
@@ -244,7 +246,7 @@ class CustomValidator(Validator):
|
|
|
244
246
|
return str(value).upper()
|
|
245
247
|
|
|
246
248
|
def _normalize_coerce_actions_output(self, value):
|
|
247
|
-
return str(value).
|
|
249
|
+
return str(value).lower()
|
|
248
250
|
|
|
249
251
|
def _normalize_coerce_str(self, value):
|
|
250
252
|
return str(value)
|
|
@@ -322,4 +324,4 @@ class CustomValidator(Validator):
|
|
|
322
324
|
|
|
323
325
|
parent = get_parent()
|
|
324
326
|
if parent["action"] == COVER:
|
|
325
|
-
return
|
|
327
|
+
return "toggle"
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""GPIOInputButton to receive signals."""
|
|
2
|
+
import logging
|
|
3
|
+
from datetime import datetime, timedelta
|
|
4
|
+
from functools import partial
|
|
5
|
+
|
|
6
|
+
from boneio.const import DOUBLE, LONG, SINGLE
|
|
7
|
+
from boneio.helper import GpioBaseClass, edge_detect
|
|
8
|
+
|
|
9
|
+
# TIMINGS FOR BUTTONS
|
|
10
|
+
DEBOUNCE_DURATION = timedelta(microseconds=150000)
|
|
11
|
+
LONG_PRESS_DURATION = timedelta(microseconds=700000)
|
|
12
|
+
DELAY_DURATION = 0.08
|
|
13
|
+
SECOND_DELAY_DURATION = 0.14
|
|
14
|
+
|
|
15
|
+
_LOGGER = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class GpioInputButton(GpioBaseClass):
|
|
19
|
+
"""Represent Gpio input switch."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, **kwargs) -> None:
|
|
22
|
+
"""Setup GPIO Input Button"""
|
|
23
|
+
super().__init__(**kwargs)
|
|
24
|
+
edge_detect(
|
|
25
|
+
self._pin,
|
|
26
|
+
callback=self._handle_press,
|
|
27
|
+
bounce=self._bounce_time.total_milliseconds,
|
|
28
|
+
)
|
|
29
|
+
self._first_press_timestamp = None
|
|
30
|
+
self._is_long_press = False
|
|
31
|
+
self._second_press_timestamp = None
|
|
32
|
+
self._second_check = False
|
|
33
|
+
_LOGGER.debug("Configured listening for input pin %s", self._pin)
|
|
34
|
+
|
|
35
|
+
def _handle_press(self, pin: str) -> None:
|
|
36
|
+
"""Handle the button press callback"""
|
|
37
|
+
# Ignore if we are in a long press
|
|
38
|
+
if self._is_long_press:
|
|
39
|
+
return
|
|
40
|
+
now = datetime.now()
|
|
41
|
+
|
|
42
|
+
# Debounce button
|
|
43
|
+
if (
|
|
44
|
+
self._first_press_timestamp is not None
|
|
45
|
+
and now - self._first_press_timestamp < DEBOUNCE_DURATION
|
|
46
|
+
):
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
# Second click debounce. Just in case.
|
|
50
|
+
if (
|
|
51
|
+
self._second_press_timestamp is not None
|
|
52
|
+
and now - self._second_press_timestamp < DEBOUNCE_DURATION
|
|
53
|
+
):
|
|
54
|
+
return
|
|
55
|
+
if not self._first_press_timestamp:
|
|
56
|
+
self._first_press_timestamp = now
|
|
57
|
+
elif not self._second_press_timestamp:
|
|
58
|
+
self._second_press_timestamp = now
|
|
59
|
+
|
|
60
|
+
self._loop.call_soon_threadsafe(
|
|
61
|
+
self._loop.call_later,
|
|
62
|
+
DELAY_DURATION,
|
|
63
|
+
self.check_press_length,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def check_press_length(self) -> None:
|
|
67
|
+
"""Check if it's a single, double or long press"""
|
|
68
|
+
# Check if button is still pressed
|
|
69
|
+
if self.is_pressed:
|
|
70
|
+
# Schedule a new check
|
|
71
|
+
self._loop.call_soon_threadsafe(
|
|
72
|
+
self._loop.call_later,
|
|
73
|
+
DELAY_DURATION,
|
|
74
|
+
self.check_press_length,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Handle edge case due to multiple clicks
|
|
78
|
+
if self._first_press_timestamp is None:
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
# Check if we reached a long press
|
|
82
|
+
diff = datetime.now() - self._first_press_timestamp
|
|
83
|
+
if not self._is_long_press and diff > LONG_PRESS_DURATION:
|
|
84
|
+
self._is_long_press = True
|
|
85
|
+
_LOGGER.debug("Long button press on pin %s, call callback", self._pin)
|
|
86
|
+
self._loop.call_soon_threadsafe(
|
|
87
|
+
partial(self._press_callback, LONG, self._pin)
|
|
88
|
+
)
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
# Handle short press
|
|
92
|
+
if not self._is_long_press:
|
|
93
|
+
if not self._second_press_timestamp and not self._second_check:
|
|
94
|
+
# let's try to check if second click will atempt
|
|
95
|
+
self._second_check = True
|
|
96
|
+
self._loop.call_soon_threadsafe(
|
|
97
|
+
self._loop.call_later,
|
|
98
|
+
SECOND_DELAY_DURATION,
|
|
99
|
+
self.check_press_length,
|
|
100
|
+
)
|
|
101
|
+
return
|
|
102
|
+
if self._second_check:
|
|
103
|
+
if self._second_press_timestamp:
|
|
104
|
+
_LOGGER.debug(
|
|
105
|
+
"Double click event on pin %s, diff %s",
|
|
106
|
+
self._pin,
|
|
107
|
+
self._second_press_timestamp - self._first_press_timestamp,
|
|
108
|
+
)
|
|
109
|
+
self._loop.call_soon_threadsafe(
|
|
110
|
+
partial(self._press_callback, DOUBLE, self._pin)
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
elif self._first_press_timestamp:
|
|
114
|
+
_LOGGER.debug("One click event on pin %s, call callback", self._pin)
|
|
115
|
+
self._loop.call_soon_threadsafe(
|
|
116
|
+
partial(self._press_callback, SINGLE, self._pin)
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Clean state on button released
|
|
120
|
+
self._first_press_timestamp = None
|
|
121
|
+
self._second_press_timestamp = None
|
|
122
|
+
self._second_check = False
|
|
123
|
+
self._is_long_press = False
|