boneIO 0.7.0.dev2__tar.gz → 0.7.0.dev3__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 (86) hide show
  1. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/PKG-INFO +1 -1
  2. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/bonecli.py +4 -2
  3. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/helper/click_timer.py +2 -1
  4. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/helper/events.py +4 -7
  5. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/helper/gpio.py +2 -1
  6. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/helper/loader.py +19 -6
  7. boneIO-0.7.0.dev3/boneio/helper/pcf8575.py +13 -0
  8. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/input/gpio_beta.py +7 -7
  9. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/manager.py +21 -17
  10. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/mqtt_client.py +20 -22
  11. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/relay/__init__.py +2 -1
  12. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/relay/pcf.py +8 -6
  13. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/runner.py +1 -5
  14. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/schema/schema.yaml +33 -1
  15. boneIO-0.7.0.dev3/boneio/version.py +2 -0
  16. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/pyproject.toml +1 -1
  17. boneIO-0.7.0.dev2/boneio/version.py +0 -2
  18. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/LICENSE +0 -0
  19. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/README.md +0 -0
  20. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/__init__.py +0 -0
  21. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/const.py +0 -0
  22. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/cover.py +0 -0
  23. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/example_config/__init__.py +0 -0
  24. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/example_config/adc_v0_4.yaml +0 -0
  25. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/example_config/binary_sensor.yaml +0 -0
  26. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/example_config/config.yaml +0 -0
  27. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/example_config/cover.yaml +0 -0
  28. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/example_config/event.yaml +0 -0
  29. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/example_config/led32x4A.yaml +0 -0
  30. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/example_config/output24x16A.yaml +0 -0
  31. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/example_config/output24x16A_v0.3.yaml +0 -0
  32. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/example_config/output32x5A.yaml +0 -0
  33. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/fonts/danube__.ttf +0 -0
  34. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/helper/__init__.py +0 -0
  35. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/helper/async_updater.py +0 -0
  36. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/helper/config.py +0 -0
  37. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/helper/exceptions.py +0 -0
  38. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/helper/filter.py +0 -0
  39. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/helper/ha_discovery.py +0 -0
  40. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/helper/logger.py +0 -0
  41. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/helper/mqtt.py +0 -0
  42. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/helper/oled.py +0 -0
  43. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/helper/onewire/W1ThermSensor.py +0 -0
  44. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/helper/onewire/__init__.py +0 -0
  45. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/helper/onewire/ds2482.py +0 -0
  46. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/helper/onewire/onewire.py +0 -0
  47. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/helper/queue.py +0 -0
  48. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/helper/state_manager.py +0 -0
  49. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/helper/stats.py +0 -0
  50. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/helper/timeperiod.py +0 -0
  51. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/helper/util.py +0 -0
  52. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/helper/yaml_util.py +0 -0
  53. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/input/__init__.py +0 -0
  54. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/input/gpio.py +0 -0
  55. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/modbus.py +0 -0
  56. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/oled.py +0 -0
  57. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/relay/basic.py +0 -0
  58. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/relay/gpio.py +0 -0
  59. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/relay/mcp.py +0 -0
  60. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/relay/pca.py +0 -0
  61. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/schema/actions.yaml +0 -0
  62. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/schema/actions_sensor.yaml +0 -0
  63. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/schema/actions_switch.yaml +0 -0
  64. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/schema/filters.yaml +0 -0
  65. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/schema/filters_adc.yaml +0 -0
  66. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/schema/id.yaml +0 -0
  67. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/schema/temp_unit.yaml +0 -0
  68. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/schema/update_interval.yaml +0 -0
  69. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/sensor/__init__.py +0 -0
  70. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/sensor/adc.py +0 -0
  71. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/sensor/gpio.py +0 -0
  72. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/sensor/gpio_beta.py +0 -0
  73. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/sensor/modbus/__init__.py +0 -0
  74. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/sensor/modbus/dts1964_3f.json +0 -0
  75. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/sensor/modbus/pt100.json +0 -0
  76. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/sensor/modbus/r4dcb08.json +0 -0
  77. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/sensor/modbus/sdm120.json +0 -0
  78. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/sensor/modbus/sdm630.json +0 -0
  79. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/sensor/modbus/sofar.json +0 -0
  80. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/sensor/temp/__init__.py +0 -0
  81. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/sensor/temp/dallas.py +0 -0
  82. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/sensor/temp/lm75.py +0 -0
  83. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/boneio/sensor/temp/mcp9808.py +0 -0
  84. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/tests/32_5_config.yaml +0 -0
  85. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/tests/bandit.yaml +0 -0
  86. {boneIO-0.7.0.dev2 → boneIO-0.7.0.dev3}/tests/relay_32_5.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: boneIO
3
- Version: 0.7.0.dev2
3
+ Version: 0.7.0.dev3
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>
@@ -15,6 +15,7 @@ from yaml import MarkedYAMLError
15
15
  from boneio.const import ACTION
16
16
  from boneio.helper import load_config_from_file
17
17
  from boneio.helper.exceptions import ConfigurationException, RestartRequestException
18
+ from boneio.helper.events import GracefulExit
18
19
  from boneio.helper.logger import configure_logger
19
20
  from boneio.runner import async_run
20
21
  from boneio.version import __version__
@@ -89,8 +90,9 @@ def run(config: str, debug: int, mqttusername: str = "", mqttpassword: str = "")
89
90
  ),
90
91
  )
91
92
  return 0
92
- except RestartRequestException as err:
93
- _LOGGER.info(err)
93
+ except (RestartRequestException, GracefulExit) as err:
94
+ if err is not None:
95
+ _LOGGER.info(err)
94
96
  return 0
95
97
  except (ConfigurationException, MarkedYAMLError) as err:
96
98
  _LOGGER.error("Failed to load config. %s Exiting.", err)
@@ -24,9 +24,10 @@ class ClickTimer:
24
24
  self._remove_listener = None
25
25
 
26
26
  async def _start_async_timer(self) -> None:
27
+ start = time.time()
27
28
  await asyncio.sleep(self._delay)
28
29
  self._remove_listener = None
29
- self._action(time.time())
30
+ self._action(round(time.time() - start, 2))
30
31
 
31
32
  def start_timer(self) -> None:
32
33
  """Start timer."""
@@ -77,11 +77,9 @@ class ListenerJob:
77
77
  class EventBus:
78
78
  """Simple event bus which ticks every second."""
79
79
 
80
- def __init__(self, last_will_mqtt = Callable) -> None:
80
+ def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
81
81
  """Initialize handler"""
82
- self._loop = asyncio.get_event_loop()
83
- self._last_will = last_will_mqtt
84
-
82
+ self._loop = loop or asyncio.get_event_loop()
85
83
  self._listeners = {}
86
84
  self._sigterm_listeners = []
87
85
  self._haonline_listeners = []
@@ -89,7 +87,7 @@ class EventBus:
89
87
  for signame in {"SIGINT", "SIGTERM"}:
90
88
  self._loop.add_signal_handler(
91
89
  getattr(signal, signame),
92
- lambda: asyncio.create_task(self.ask_exit()),
90
+ self.ask_exit,
93
91
  )
94
92
 
95
93
  def _run_second_event(self, time):
@@ -100,7 +98,7 @@ class EventBus:
100
98
  self._loop.call_soon(listener.target, time)
101
99
  )
102
100
 
103
- async def ask_exit(self):
101
+ def ask_exit(self):
104
102
  """Function to call on exit. Should invoke all sigterm listeners."""
105
103
  _LOGGER.debug("Exiting process started.")
106
104
  self._listeners = {}
@@ -108,7 +106,6 @@ class EventBus:
108
106
  target()
109
107
  self._timer_handle()
110
108
 
111
- await self._last_will()
112
109
  _LOGGER.info("Shutdown gracefully.")
113
110
  raise GracefulExit(code=0)
114
111
 
@@ -1,3 +1,4 @@
1
+ from __future__ import annotations
1
2
  import asyncio
2
3
  import logging
3
4
 
@@ -104,7 +105,7 @@ class GpioBaseClass:
104
105
  """Base class for initialize GPIO"""
105
106
 
106
107
  def __init__(
107
- self, pin: str, press_callback: Callable[[ClickTypes, str], None], **kwargs
108
+ self, pin: str, press_callback: Callable[[ClickTypes, str, bool | None], None], **kwargs
108
109
  ) -> None:
109
110
  """Setup GPIO Input Button"""
110
111
  self._pin = pin
@@ -7,7 +7,6 @@ from typing import TYPE_CHECKING, Any, Callable, Dict, Union
7
7
 
8
8
  from adafruit_mcp230xx.mcp23017 import MCP23017
9
9
  from adafruit_pca9685 import PCA9685
10
- from adafruit_pcf8575 import PCF8575
11
10
 
12
11
  from boneio.const import (
13
12
  ACTIONS,
@@ -64,6 +63,7 @@ from boneio.helper.onewire import (
64
63
  )
65
64
  from boneio.helper.ha_discovery import ha_cover_availabilty_message
66
65
  from boneio.helper.timeperiod import TimePeriod
66
+ from boneio.helper.pcf8575 import PCF8575
67
67
  from boneio.input import GpioEventButton, GpioEventButtonBeta
68
68
  from boneio.sensor import DallasSensorDS2482, GpioInputBinarySensor, GpioInputBinarySensorBeta
69
69
  from boneio.sensor.temp.dallas import DallasSensorW1
@@ -74,7 +74,7 @@ if TYPE_CHECKING:
74
74
 
75
75
  from busio import I2C
76
76
 
77
- from boneio.relay import GpioRelay, MCPRelay, PWMPCA
77
+ from boneio.relay import GpioRelay, MCPRelay, PWMPCA, PCFRelay
78
78
  from boneio.sensor import GpioADCSensor, initialize_adc
79
79
 
80
80
  _LOGGER = logging.getLogger(__name__)
@@ -153,12 +153,14 @@ def create_temp_sensor(
153
153
  _LOGGER.error("Can't configure Temp sensor. %s", err)
154
154
  pass
155
155
 
156
+
156
157
  expander_class = {
157
158
  MCP: MCP23017,
158
159
  PCA: PCA9685,
159
160
  PCF: PCF8575
160
161
  }
161
162
 
163
+
162
164
  def create_expander(
163
165
  expander_dict: dict,
164
166
  expander_config: list,
@@ -187,7 +189,6 @@ def create_expander(
187
189
  return grouped_outputs
188
190
 
189
191
 
190
-
191
192
  def create_modbus_sensors(manager: Manager, sensors, **kwargs) -> None:
192
193
  """Create Modbus sensor for each device."""
193
194
  from boneio.sensor.modbus import ModbusSensor
@@ -230,7 +231,7 @@ def output_chooser(output_kind: str, config):
230
231
  return OutputEntry(PWMPCA, PCA, output_id)
231
232
  elif output_kind == PCF:
232
233
  output_id = config.pop(PCF_ID, None)
233
- return OutputEntry(PWMPCA, PCF, output_id)
234
+ return OutputEntry(PCFRelay, PCF, output_id)
234
235
  else:
235
236
  raise GPIOOutputException(f"""Output type {output_kind} dont exists""")
236
237
 
@@ -279,6 +280,17 @@ def configure_relay(
279
280
  "pca_id": getattr(output, "output_id"),
280
281
  "output_type": output_type,
281
282
  }
283
+ elif getattr(output, "output_kind") == PCF:
284
+ expander = manager.pcf.get(getattr(output, "output_id"))
285
+ if not expander:
286
+ _LOGGER.error("No such PCF configured!")
287
+ return None
288
+ kwargs = {
289
+ "pin": int(config.pop(PIN)),
290
+ "expander": expander,
291
+ "expander_id": getattr(output, "output_id"),
292
+ "output_type": output_type,
293
+ }
282
294
  elif getattr(output, "output_kind") == GPIO:
283
295
  if GPIO not in manager.grouped_outputs:
284
296
  manager.grouped_outputs[GPIO] = {}
@@ -337,12 +349,13 @@ def configure_event_sensor(
337
349
  GpioEventButtonClass = GpioEventButton if gpio.get("detection_type", "stable") == "stable" else GpioEventButtonBeta
338
350
  GpioEventButtonClass(
339
351
  pin=pin,
340
- press_callback=lambda x, i: press_callback(
352
+ press_callback=lambda x, i, z: press_callback(
341
353
  x=x,
342
354
  inpin=i,
343
355
  actions=gpio.get(ACTIONS, {}).get(x, []),
344
356
  input_type=INPUT,
345
- empty_message_after=gpio.get("clear_message", False)
357
+ empty_message_after=gpio.get("clear_message", False),
358
+ duration=z
346
359
  ),
347
360
  **gpio,
348
361
  )
@@ -0,0 +1,13 @@
1
+ from __future__ import annotations
2
+
3
+ from adafruit_pcf8575 import PCF8575 as AdafruitPCF8575
4
+ try:
5
+ from busio import I2C
6
+ except ImportError:
7
+ pass
8
+
9
+
10
+ class PCF8575(AdafruitPCF8575):
11
+
12
+ def __init__(self, i2c: I2C, address: int, reset: bool) -> None:
13
+ super().__init__(i2c_bus=i2c, address=address)
@@ -19,6 +19,7 @@ _LOGGER = logging.getLogger(__name__)
19
19
  DOUBLE_CLICK_DURATION_MS = 300
20
20
  LONG_PRESS_DURATION_MS = 600
21
21
 
22
+
22
23
  class GpioEventButtonBeta(GpioBaseClass):
23
24
  """Represent Gpio input switch."""
24
25
 
@@ -38,7 +39,7 @@ class GpioEventButtonBeta(GpioBaseClass):
38
39
  )
39
40
  self._timer_long = ClickTimer(
40
41
  delay=TimePeriod(milliseconds=LONG_PRESS_DURATION_MS),
41
- action=lambda x: self.press_callback(click_type=LONG),
42
+ action=lambda x: self.press_callback(click_type=LONG, duration=x),
42
43
  )
43
44
  self._double_click_ran = False
44
45
  self._is_waiting_for_second_click = False
@@ -51,7 +52,7 @@ class GpioEventButtonBeta(GpioBaseClass):
51
52
  def double_click_press_callback(self):
52
53
  self._is_waiting_for_second_click = False
53
54
  if not self._state and not self._timer_long.is_waiting():
54
- self.press_callback(click_type=SINGLE)
55
+ self.press_callback(click_type=SINGLE, duration=None)
55
56
 
56
57
  def check_state(self, channel) -> None:
57
58
  time_now = time.time()
@@ -64,7 +65,7 @@ class GpioEventButtonBeta(GpioBaseClass):
64
65
  if self._timer_double.is_waiting():
65
66
  self._timer_double.reset()
66
67
  self._double_click_ran = True
67
- self.press_callback(click_type=DOUBLE)
68
+ self.press_callback(click_type=DOUBLE, duration=None)
68
69
  return
69
70
  self._timer_double.start_timer()
70
71
  self._is_waiting_for_second_click = True
@@ -72,13 +73,12 @@ class GpioEventButtonBeta(GpioBaseClass):
72
73
  else:
73
74
  if not self._is_waiting_for_second_click and not self._double_click_ran:
74
75
  if self._timer_long.is_waiting():
75
- self.press_callback(click_type=SINGLE)
76
+ self.press_callback(click_type=SINGLE, duration=None)
76
77
  self._timer_long.reset()
77
78
  self._double_click_ran = False
78
79
 
79
-
80
- def press_callback(self, click_type: ClickTypes):
80
+ def press_callback(self, click_type: ClickTypes, duration: float | None = None):
81
81
  self.click_count = 0
82
82
  self._loop.call_soon_threadsafe(
83
- partial(self._press_callback, click_type, self._pin)
83
+ partial(self._press_callback, click_type, self._pin, duration)
84
84
  )
@@ -1,3 +1,4 @@
1
+ from __future__ import annotations
1
2
  import asyncio
2
3
  import logging
3
4
  from collections import deque
@@ -86,7 +87,6 @@ class Manager:
86
87
  stop_client: Callable[[], Awaitable[None]],
87
88
  state_manager: StateManager,
88
89
  config_helper: ConfigHelper,
89
- event_bus: EventBus,
90
90
  config_file_path: str,
91
91
  relay_pins: List = [],
92
92
  event_pins: List = [],
@@ -111,7 +111,7 @@ class Manager:
111
111
  self._host_data = None
112
112
  self._config_file_path = config_file_path
113
113
  self._state_manager = state_manager
114
- self._event_bus = event_bus
114
+ self._event_bus = EventBus(loop=self._loop)
115
115
 
116
116
  self.send_message = send_message
117
117
  self.stop_client = stop_client
@@ -145,21 +145,17 @@ class Manager:
145
145
  )
146
146
  self.grouped_outputs.update(
147
147
  create_expander(
148
- expander_dict=self._pcf,
149
- expander_config=pcf8575,
150
- exp_type=PCF,
151
- i2cbusio=self._i2cbusio
152
- )
153
- )
148
+ expander_dict=self._pcf,
149
+ expander_config=pcf8575,
150
+ exp_type=PCF,
151
+ i2cbusio=self._i2cbusio))
154
152
  self.grouped_outputs.update(
155
153
  create_expander(
156
- expander_dict=self._pca,
157
- expander_config=pca9685,
158
- exp_type=PCA,
159
- i2cbusio=self._i2cbusio
160
- )
161
- )
162
-
154
+ expander_dict=self._pca,
155
+ expander_config=pca9685,
156
+ exp_type=PCA,
157
+ i2cbusio=self._i2cbusio))
158
+
163
159
  self._configure_adc(adc_list=adc)
164
160
 
165
161
  for _config in relay_pins:
@@ -484,12 +480,20 @@ class Manager:
484
480
  return self._pcf
485
481
 
486
482
  def press_callback(
487
- self, x: ClickTypes, inpin: str, actions: List, input_type: InputTypes = INPUT, empty_message_after: bool = False
483
+ self, x: ClickTypes, inpin: str, actions: List, input_type: InputTypes = INPUT, empty_message_after: bool = False, duration: float | None = None
488
484
  ) -> None:
489
485
  """Press callback to use in input gpio.
490
486
  If relay input map is provided also toggle action on relay or cover or mqtt."""
491
487
  topic = f"{self._config_helper.topic_prefix}/{input_type}/{inpin}"
492
- self.send_message(topic=topic, payload=x, retain=False)
488
+
489
+ def generate_payload():
490
+ if input_type == INPUT:
491
+ if duration:
492
+ return {"event_type": x, "duration": duration}
493
+ return {"event_type": x}
494
+ return x
495
+
496
+ self.send_message(topic=topic, payload=generate_payload(), retain=False)
493
497
  for action_definition in actions:
494
498
  _LOGGER.debug("Executing action %s", action_definition)
495
499
  if action_definition[ACTION] == OUTPUT:
@@ -15,9 +15,10 @@ from aiomqtt import Client as AsyncioClient, MqttError, Will
15
15
  from paho.mqtt.properties import Properties
16
16
  from paho.mqtt.subscribeoptions import SubscribeOptions
17
17
 
18
- from boneio.const import ONLINE, OFFLINE, PAHO, STATE
18
+ from boneio.const import OFFLINE, PAHO, STATE
19
19
  from boneio.helper import UniqueQueue
20
20
  from boneio.helper.config import ConfigHelper
21
+ from boneio.helper.events import GracefulExit
21
22
  from boneio.manager import Manager
22
23
  from boneio.helper.exceptions import RestartRequestException
23
24
 
@@ -58,7 +59,7 @@ class MQTTClient:
58
59
  self.port,
59
60
  will=Will(
60
61
  topic=f"{self._config_helper.topic_prefix}/{STATE}",
61
- payload=ONLINE,
62
+ payload=OFFLINE,
62
63
  qos=0,
63
64
  retain=False,
64
65
  ),
@@ -148,21 +149,23 @@ class MQTTClient:
148
149
  async def start_client(self, manager: Manager) -> None:
149
150
  """Start the client with the manager."""
150
151
  # Reconnect automatically until the client is stopped.
151
- while True:
152
- try:
153
- await self._subscribe_manager(manager)
154
- except MqttError as err:
155
- self.reconnect_interval = min(self.reconnect_interval * 2, 900)
156
- _LOGGER.error(
157
- "MQTT error: %s. Reconnecting in %s seconds",
158
- err,
159
- self.reconnect_interval,
160
- )
161
- self._connection_established = False
162
- await asyncio.sleep(self.reconnect_interval)
163
- self.create_client() # reset connect/reconnect futures
164
- except asyncio.CancelledError:
165
- _LOGGER.info("MQTT client task canceled.")
152
+ try:
153
+ while True:
154
+ try:
155
+ await self._subscribe_manager(manager)
156
+ except MqttError as err:
157
+ self.reconnect_interval = min(self.reconnect_interval * 2, 900)
158
+ _LOGGER.error(
159
+ "MQTT error: %s. Reconnecting in %s seconds",
160
+ err,
161
+ self.reconnect_interval,
162
+ )
163
+ self._connection_established = False
164
+ await asyncio.sleep(self.reconnect_interval)
165
+ self.create_client() # reset connect/reconnect futures
166
+ except (asyncio.CancelledError, GracefulExit):
167
+ _LOGGER.info("MQTT client task canceled.")
168
+ pass
166
169
 
167
170
  async def stop_client(self) -> None:
168
171
  await self.unsubscribe(
@@ -170,11 +173,6 @@ class MQTTClient:
170
173
  )
171
174
  raise RestartRequestException("Restart requested.")
172
175
 
173
- async def last_will_mqtt(self) -> None:
174
- _LOGGER.info("Sending offline state.")
175
- topic = f"{self._config_helper.topic_prefix}/{STATE}"
176
- self.send_message(topic=topic, payload=OFFLINE, retain=True)
177
-
178
176
  async def _subscribe_manager(self, manager: Manager) -> None:
179
177
  """Connect and subscribe to manager topics + host stats."""
180
178
  async with AsyncExitStack() as stack:
@@ -1,6 +1,7 @@
1
1
  """Relay module."""
2
2
  from boneio.relay.gpio import GpioRelay
3
3
  from boneio.relay.mcp import MCPRelay
4
+ from boneio.relay.pcf import PCFRelay
4
5
  from boneio.relay.pca import PWMPCA
5
6
 
6
- __all__ = ["MCPRelay", "GpioRelay", "PWMPCA"]
7
+ __all__ = ["MCPRelay", "GpioRelay", "PWMPCA", "PCFRelay"]
@@ -2,10 +2,11 @@
2
2
 
3
3
  import logging
4
4
 
5
- from adafruit_pcf8575 import PCF8575, DigitalInOut
5
+ from adafruit_pcf8575 import DigitalInOut
6
6
 
7
7
  from boneio.const import NONE, SWITCH, PCF
8
8
  from boneio.helper.events import async_track_point_in_time, utcnow
9
+ from boneio.helper.pcf8575 import PCF8575
9
10
  from boneio.relay.basic import BasicRelay
10
11
 
11
12
  _LOGGER = logging.getLogger(__name__)
@@ -17,14 +18,14 @@ class PCFRelay(BasicRelay):
17
18
  def __init__(
18
19
  self,
19
20
  pin: int,
20
- mcp: PCF8575,
21
+ expander: PCF8575,
21
22
  expander_id: str,
22
23
  output_type: str = SWITCH,
23
24
  restored_state: bool = False,
24
25
  **kwargs,
25
26
  ) -> None:
26
27
  """Initialize MCP relay."""
27
- self._pin: DigitalInOut = mcp.get_pin(pin)
28
+ self._pin: DigitalInOut = expander.get_pin(pin)
28
29
  if output_type == NONE:
29
30
  """Just in case to not restore state of covers etc."""
30
31
  restored_state = False
@@ -34,6 +35,7 @@ class PCFRelay(BasicRelay):
34
35
  )
35
36
  self._pin_id = pin
36
37
  self._expander_id = expander_id
38
+ self._active_state = False
37
39
  _LOGGER.debug("Setup PCF with pin %s", self._pin_id)
38
40
 
39
41
  @property
@@ -54,7 +56,7 @@ class PCFRelay(BasicRelay):
54
56
  @property
55
57
  def is_active(self) -> bool:
56
58
  """Is relay active."""
57
- return self.pin.value
59
+ return self.pin.value == self._active_state
58
60
 
59
61
  @property
60
62
  def pin(self) -> str:
@@ -63,7 +65,7 @@ class PCFRelay(BasicRelay):
63
65
 
64
66
  def turn_on(self) -> None:
65
67
  """Call turn on action."""
66
- self.pin.value = True
68
+ self.pin.value = self._active_state
67
69
  if self._momentary_turn_on:
68
70
  async_track_point_in_time(
69
71
  loop=self._loop,
@@ -74,7 +76,7 @@ class PCFRelay(BasicRelay):
74
76
 
75
77
  def turn_off(self) -> None:
76
78
  """Call turn off action."""
77
- self.pin.value = False
79
+ self.pin.value = not self._active_state
78
80
  if self._momentary_turn_off:
79
81
  async_track_point_in_time(
80
82
  loop=self._loop,
@@ -59,8 +59,6 @@ async def async_run(
59
59
  mqttpassword: str = "",
60
60
  ) -> list[Any]:
61
61
  """Run BoneIO."""
62
- _loop = asyncio.get_event_loop()
63
-
64
62
  _config_helper = ConfigHelper(
65
63
  topic_prefix=config[MQTT].pop(TOPIC_PREFIX),
66
64
  ha_discovery=config[MQTT][HA_DISCOVERY].pop(ENABLED),
@@ -74,7 +72,6 @@ async def async_run(
74
72
  port=config[MQTT].get(PORT, 1883),
75
73
  config_helper=_config_helper,
76
74
  )
77
- event_bus = EventBus(last_will_mqtt=client.last_will_mqtt)
78
75
  manager_kwargs = {
79
76
  item["name"]: config.get(item["name"], item["default"])
80
77
  for item in config_modules
@@ -83,7 +80,6 @@ async def async_run(
83
80
  manager = Manager(
84
81
  send_message=client.send_message,
85
82
  stop_client=client.stop_client,
86
- event_bus=event_bus,
87
83
  relay_pins=config.get(OUTPUT, []),
88
84
  event_pins=config.get(EVENT_ENTITY, []),
89
85
  binary_pins=config.get(BINARY_SENSOR, []),
@@ -104,4 +100,4 @@ async def async_run(
104
100
  tasks.update(manager.get_tasks())
105
101
  _LOGGER.info("Connecting to MQTT.")
106
102
  tasks.add(client.start_client(manager))
107
- return await asyncio.gather(*tasks)
103
+ return await asyncio.gather(*tasks, return_exceptions=True)
@@ -218,6 +218,33 @@ mcp23017:
218
218
  meta:
219
219
  label: How long to sleep for MCP to initialize.
220
220
 
221
+
222
+ pcf8575:
223
+ type: list
224
+ required: False
225
+ meta:
226
+ label: pcf8575 Pin config
227
+ schema:
228
+ type: dict
229
+ schema:
230
+ id: !include id.yaml
231
+ address:
232
+ type: integer
233
+ required: True
234
+ meta:
235
+ label: GPIO of I2C SDA
236
+ init_sleep:
237
+ type:
238
+ - string
239
+ - timeperiod
240
+ coerce:
241
+ - str
242
+ - positive_time_period
243
+ required: True
244
+ default: 0s
245
+ meta:
246
+ label: How long to sleep for PCF to initialize.
247
+
221
248
  pca9685:
222
249
  type: list
223
250
  required: False
@@ -260,7 +287,7 @@ output:
260
287
  label: Id to send to use in HA and in GPIO Input. Default to `kind_pin`
261
288
  kind:
262
289
  type: string
263
- allowed: ["gpio", "mcp", "pca"]
290
+ allowed: ["gpio", "mcp", "pca", "pcf"]
264
291
  meta:
265
292
  label: Either GPIO or i2c.
266
293
  mcp_id:
@@ -273,6 +300,11 @@ output:
273
300
  required: False
274
301
  meta:
275
302
  label: PCA ID to connect
303
+ pcf_id:
304
+ type: string
305
+ required: False
306
+ meta:
307
+ label: PCF ID to connect
276
308
  pin:
277
309
  type:
278
310
  - string
@@ -0,0 +1,2 @@
1
+ # flake8: noqa
2
+ __version__ = "0.7.0dev3"
@@ -29,7 +29,7 @@ dependencies = [
29
29
  "adafruit-circuitpython-pcf8575>=1.0.2",
30
30
  ]
31
31
  requires-python = ">=3.7"
32
- version = "0.7.0.dev2"
32
+ version = "0.7.0.dev3"
33
33
 
34
34
  [project.license]
35
35
  text = "GNU General Public License v3.0"
@@ -1,2 +0,0 @@
1
- # flake8: noqa
2
- __version__ = "0.7.0dev2"
File without changes
File without changes
File without changes
File without changes
File without changes