boneIO 0.6.0__tar.gz → 0.6.0dev2__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.

Files changed (82) hide show
  1. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/PKG-INFO +1 -1
  2. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/bonecli.py +3 -9
  3. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/const.py +1 -2
  4. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/helper/__init__.py +0 -2
  5. boneIO-0.6.0.dev2/boneio/helper/config.py +48 -0
  6. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/helper/events.py +5 -65
  7. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/helper/exceptions.py +0 -5
  8. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/helper/gpio.py +2 -2
  9. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/helper/ha_discovery.py +3 -9
  10. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/helper/loader.py +0 -1
  11. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/helper/mqtt.py +2 -2
  12. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/helper/yaml_util.py +6 -4
  13. boneIO-0.6.0.dev2/boneio/input/gpio.py +123 -0
  14. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/manager.py +33 -56
  15. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/mqtt_client.py +17 -43
  16. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/relay/basic.py +4 -3
  17. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/runner.py +9 -20
  18. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/schema/actions.yaml +1 -2
  19. boneIO-0.6.0/boneio/schema/actions_switch.yaml → boneIO-0.6.0.dev2/boneio/schema/actions_sensor.yaml +1 -1
  20. boneIO-0.6.0/boneio/schema/actions_sensor.yaml → boneIO-0.6.0.dev2/boneio/schema/actions_switch.yaml +1 -1
  21. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/schema/schema.yaml +0 -6
  22. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/sensor/gpio.py +12 -13
  23. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/sensor/temp/dallas.py +1 -3
  24. boneIO-0.6.0.dev2/boneio/version.py +2 -0
  25. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/pyproject.toml +19 -20
  26. boneIO-0.6.0/boneio/helper/click_timer.py +0 -37
  27. boneIO-0.6.0/boneio/helper/config.py +0 -74
  28. boneIO-0.6.0/boneio/input/gpio.py +0 -75
  29. boneIO-0.6.0/boneio/version.py +0 -2
  30. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/LICENSE +0 -0
  31. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/README.md +0 -0
  32. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/__init__.py +0 -0
  33. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/cover.py +0 -0
  34. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/example_config/__init__.py +0 -0
  35. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/example_config/adc.yaml +0 -0
  36. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/example_config/config.yaml +0 -0
  37. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/example_config/cover.yaml +0 -0
  38. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/example_config/input.yaml +0 -0
  39. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/example_config/led32x4A.yaml +0 -0
  40. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/example_config/output24x16A.yaml +0 -0
  41. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/example_config/output32x5A.yaml +0 -0
  42. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/fonts/danube__.ttf +0 -0
  43. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/helper/async_updater.py +0 -0
  44. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/helper/filter.py +0 -0
  45. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/helper/logger.py +0 -0
  46. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/helper/oled.py +0 -0
  47. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/helper/onewire/W1ThermSensor.py +0 -0
  48. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/helper/onewire/__init__.py +0 -0
  49. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/helper/onewire/ds2482.py +0 -0
  50. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/helper/onewire/onewire.py +0 -0
  51. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/helper/queue.py +0 -0
  52. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/helper/state_manager.py +0 -0
  53. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/helper/stats.py +0 -0
  54. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/helper/timeperiod.py +0 -0
  55. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/helper/util.py +0 -0
  56. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/input/__init__.py +0 -0
  57. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/modbus.py +0 -0
  58. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/oled.py +0 -0
  59. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/relay/__init__.py +0 -0
  60. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/relay/gpio.py +0 -0
  61. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/relay/mcp.py +0 -0
  62. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/relay/pca.py +0 -0
  63. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/schema/filters.yaml +0 -0
  64. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/schema/filters_adc.yaml +0 -0
  65. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/schema/id.yaml +0 -0
  66. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/schema/temp_unit.yaml +0 -0
  67. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/schema/update_interval.yaml +0 -0
  68. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/sensor/__init__.py +0 -0
  69. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/sensor/adc.py +0 -0
  70. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/sensor/modbus/__init__.py +0 -0
  71. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/sensor/modbus/dts1964_3f.json +0 -0
  72. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/sensor/modbus/pt100.json +0 -0
  73. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/sensor/modbus/r4dcb08.json +0 -0
  74. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/sensor/modbus/sdm120.json +0 -0
  75. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/sensor/modbus/sdm630.json +0 -0
  76. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/sensor/modbus/sofar.json +0 -0
  77. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/sensor/temp/__init__.py +0 -0
  78. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/sensor/temp/lm75.py +0 -0
  79. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/boneio/sensor/temp/mcp9808.py +0 -0
  80. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/tests/32_5_config.yaml +0 -0
  81. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/tests/bandit.yaml +0 -0
  82. {boneIO-0.6.0 → boneIO-0.6.0.dev2}/tests/relay_32_5.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: boneIO
3
- Version: 0.6.0
3
+ Version: 0.6.0dev2
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>
@@ -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, RestartRequestException
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 = "") -> int:
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 = time.time()
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 = timestamp - now
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, timestamp)
176
+ loop.call_soon(job, utc_point_in_time)
222
177
 
223
- now = time.time()
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) -> bool:
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) -> bool:
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"] = 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, device_class: str, topic: str = "boneIO"):
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],
@@ -354,7 +354,6 @@ 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),
358
357
  availability_msg_func=getattr(input, "availability_msg_f"),
359
358
  )
360
359
  return pin
@@ -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) -> str:
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) -> str:
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, TOGGLE
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("!include_files", BoneIOLoader.construct_include_files)
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).upper()
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 TOGGLE
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