boneIO 0.7.0.dev1__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.dev1 → boneIO-0.7.0.dev3}/PKG-INFO +1 -1
  2. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/bonecli.py +4 -2
  3. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/const.py +4 -0
  4. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/helper/click_timer.py +2 -1
  5. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/helper/events.py +1 -0
  6. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/helper/gpio.py +2 -1
  7. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/helper/loader.py +49 -41
  8. boneIO-0.7.0.dev3/boneio/helper/pcf8575.py +13 -0
  9. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/input/gpio_beta.py +7 -7
  10. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/manager.py +40 -10
  11. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/mqtt_client.py +22 -17
  12. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/relay/__init__.py +2 -1
  13. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/relay/basic.py +6 -1
  14. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/relay/mcp.py +8 -14
  15. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/relay/pca.py +4 -4
  16. boneIO-0.7.0.dev3/boneio/relay/pcf.py +86 -0
  17. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/runner.py +4 -2
  18. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/schema/schema.yaml +33 -1
  19. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/sensor/gpio_beta.py +2 -1
  20. boneIO-0.7.0.dev3/boneio/version.py +2 -0
  21. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/pyproject.toml +2 -3
  22. boneIO-0.7.0.dev1/boneio/version.py +0 -2
  23. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/LICENSE +0 -0
  24. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/README.md +0 -0
  25. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/__init__.py +0 -0
  26. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/cover.py +0 -0
  27. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/example_config/__init__.py +0 -0
  28. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/example_config/adc_v0_4.yaml +0 -0
  29. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/example_config/binary_sensor.yaml +0 -0
  30. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/example_config/config.yaml +0 -0
  31. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/example_config/cover.yaml +0 -0
  32. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/example_config/event.yaml +0 -0
  33. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/example_config/led32x4A.yaml +0 -0
  34. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/example_config/output24x16A.yaml +0 -0
  35. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/example_config/output24x16A_v0.3.yaml +0 -0
  36. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/example_config/output32x5A.yaml +0 -0
  37. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/fonts/danube__.ttf +0 -0
  38. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/helper/__init__.py +0 -0
  39. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/helper/async_updater.py +0 -0
  40. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/helper/config.py +0 -0
  41. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/helper/exceptions.py +0 -0
  42. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/helper/filter.py +0 -0
  43. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/helper/ha_discovery.py +0 -0
  44. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/helper/logger.py +0 -0
  45. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/helper/mqtt.py +0 -0
  46. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/helper/oled.py +0 -0
  47. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/helper/onewire/W1ThermSensor.py +0 -0
  48. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/helper/onewire/__init__.py +0 -0
  49. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/helper/onewire/ds2482.py +0 -0
  50. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/helper/onewire/onewire.py +0 -0
  51. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/helper/queue.py +0 -0
  52. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/helper/state_manager.py +0 -0
  53. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/helper/stats.py +0 -0
  54. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/helper/timeperiod.py +0 -0
  55. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/helper/util.py +0 -0
  56. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/helper/yaml_util.py +0 -0
  57. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/input/__init__.py +0 -0
  58. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/input/gpio.py +0 -0
  59. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/modbus.py +0 -0
  60. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/oled.py +0 -0
  61. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/relay/gpio.py +0 -0
  62. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/schema/actions.yaml +0 -0
  63. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/schema/actions_sensor.yaml +0 -0
  64. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/schema/actions_switch.yaml +0 -0
  65. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/schema/filters.yaml +0 -0
  66. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/schema/filters_adc.yaml +0 -0
  67. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/schema/id.yaml +0 -0
  68. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/schema/temp_unit.yaml +0 -0
  69. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/schema/update_interval.yaml +0 -0
  70. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/sensor/__init__.py +0 -0
  71. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/sensor/adc.py +0 -0
  72. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/sensor/gpio.py +0 -0
  73. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/sensor/modbus/__init__.py +0 -0
  74. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/sensor/modbus/dts1964_3f.json +0 -0
  75. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/sensor/modbus/pt100.json +0 -0
  76. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/sensor/modbus/r4dcb08.json +0 -0
  77. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/sensor/modbus/sdm120.json +0 -0
  78. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/sensor/modbus/sdm630.json +0 -0
  79. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/sensor/modbus/sofar.json +0 -0
  80. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/sensor/temp/__init__.py +0 -0
  81. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/sensor/temp/dallas.py +0 -0
  82. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/sensor/temp/lm75.py +0 -0
  83. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/boneio/sensor/temp/mcp9808.py +0 -0
  84. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/tests/32_5_config.yaml +0 -0
  85. {boneIO-0.7.0.dev1 → boneIO-0.7.0.dev3}/tests/bandit.yaml +0 -0
  86. {boneIO-0.7.0.dev1 → 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.dev1
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)
@@ -104,10 +104,13 @@ TOPIC_PREFIX = "topic_prefix"
104
104
  # I2C, PCA and MCP CONST
105
105
  ADDRESS = "address"
106
106
  MCP23017 = "mcp23017"
107
+ PCF8575 = "pcf8575"
107
108
  PCA9685 = "pca9685"
108
109
  MCP = "mcp"
110
+ PCF = "pcf"
109
111
  MCP_ID = "mcp_id"
110
112
  PCA_ID = "pca_id"
113
+ PCF_ID = "pcf_id"
111
114
  INIT_SLEEP = "init_sleep"
112
115
 
113
116
  # SENSOR CONST
@@ -141,6 +144,7 @@ OledDataTypes = Literal[UPTIME, NETWORK, CPU, DISK, MEMORY, SWAP, OUTPUT]
141
144
  Gpio_States = Literal[HIGH, LOW]
142
145
  Gpio_Edges = Literal[BOTH, FALLING]
143
146
  InputTypes = Literal[INPUT, INPUT_SENSOR]
147
+ ExpanderTypes = Literal[MCP23017, PCA9685, PCF8575]
144
148
  DEVICE_CLASS = "device_class"
145
149
  DallasBusTypes = Literal[DS2482, DALLAS]
146
150
  FILTERS = "filters"
@@ -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."""
@@ -105,6 +105,7 @@ class EventBus:
105
105
  for target in self._sigterm_listeners:
106
106
  target()
107
107
  self._timer_handle()
108
+
108
109
  _LOGGER.info("Shutdown gracefully.")
109
110
  raise GracefulExit(code=0)
110
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
@@ -37,7 +37,11 @@ from boneio.const import (
37
37
  SHOW_HA,
38
38
  UPDATE_INTERVAL,
39
39
  DallasBusTypes,
40
- EVENT_ENTITY
40
+ EVENT_ENTITY,
41
+ ExpanderTypes,
42
+ PCF,
43
+ PCA,
44
+ PCF_ID
41
45
  )
42
46
  from boneio.cover import Cover
43
47
  from boneio.helper import (
@@ -59,6 +63,7 @@ from boneio.helper.onewire import (
59
63
  )
60
64
  from boneio.helper.ha_discovery import ha_cover_availabilty_message
61
65
  from boneio.helper.timeperiod import TimePeriod
66
+ from boneio.helper.pcf8575 import PCF8575
62
67
  from boneio.input import GpioEventButton, GpioEventButtonBeta
63
68
  from boneio.sensor import DallasSensorDS2482, GpioInputBinarySensor, GpioInputBinarySensorBeta
64
69
  from boneio.sensor.temp.dallas import DallasSensorW1
@@ -69,7 +74,7 @@ if TYPE_CHECKING:
69
74
 
70
75
  from busio import I2C
71
76
 
72
- from boneio.relay import GpioRelay, MCPRelay, PWMPCA
77
+ from boneio.relay import GpioRelay, MCPRelay, PWMPCA, PCFRelay
73
78
  from boneio.sensor import GpioADCSensor, initialize_adc
74
79
 
75
80
  _LOGGER = logging.getLogger(__name__)
@@ -149,49 +154,37 @@ def create_temp_sensor(
149
154
  pass
150
155
 
151
156
 
152
- def create_mcp23017(
153
- manager: Manager,
154
- mcp23017: list,
155
- i2cbusio: I2C,
156
- ) -> dict:
157
- """Create MCP23017."""
158
- grouped_outputs = {}
159
- for mcp in mcp23017:
160
- id = mcp[ID] or mcp[ADDRESS]
161
- try:
162
- manager._mcp[id] = MCP23017(i2c=i2cbusio, address=mcp[ADDRESS], reset=False)
163
- sleep_time = mcp.get(INIT_SLEEP, TimePeriod(seconds=0))
164
- _LOGGER.debug(
165
- f"Sleeping for {sleep_time.total_seconds}s while MCP {id} is initializing."
166
- )
167
- time.sleep(sleep_time.total_seconds)
168
- grouped_outputs[id] = {}
169
- except TimeoutError as err:
170
- _LOGGER.error("Can't connect to MCP %s. %s", id, err)
171
- pass
172
- return grouped_outputs
157
+ expander_class = {
158
+ MCP: MCP23017,
159
+ PCA: PCA9685,
160
+ PCF: PCF8575
161
+ }
173
162
 
174
163
 
175
- def create_pca9685(
176
- manager: Manager,
177
- pca9685: list,
178
- i2cbusio: I2C,
164
+ def create_expander(
165
+ expander_dict: dict,
166
+ expander_config: list,
167
+ exp_type: ExpanderTypes,
168
+ i2cbusio: I2C
179
169
  ) -> dict:
180
- """Create MCP23017."""
181
170
  grouped_outputs = {}
182
- for pca in pca9685:
183
- id = pca[ID] or pca[ADDRESS]
171
+ for expander in expander_config:
172
+ id = expander[ID] or expander[ADDRESS]
184
173
  try:
185
- manager._pca[id] = PCA9685(i2c_bus=i2cbusio, address=pca[ADDRESS])
186
- manager._pca[id].frequency = 500
187
- sleep_time = pca.get(INIT_SLEEP, TimePeriod(seconds=0))
188
- _LOGGER.debug(
189
- f"Sleeping for {sleep_time.total_seconds}s while PCA {id} is initializing."
190
- )
191
- time.sleep(sleep_time.total_seconds)
174
+ expander_dict[id] = expander_class[exp_type](i2c=i2cbusio, address=expander[ADDRESS], reset=False)
175
+ sleep_time = expander.get(INIT_SLEEP, TimePeriod(seconds=0))
176
+ if sleep_time.total_seconds > 0:
177
+ _LOGGER.debug(
178
+ f"Sleeping for {sleep_time.total_seconds}s while {exp_type} {id} is initializing."
179
+ )
180
+ time.sleep(sleep_time.total_seconds)
181
+ else:
182
+ _LOGGER.debug(
183
+ f"{exp_type} {id} is initializing."
184
+ )
192
185
  grouped_outputs[id] = {}
193
186
  except TimeoutError as err:
194
- _LOGGER.error("Can't connect to PCA %s. %s", id, err)
187
+ _LOGGER.error("Can't connect to %s %s. %s", exp_type, id, err)
195
188
  pass
196
189
  return grouped_outputs
197
190
 
@@ -236,6 +229,9 @@ def output_chooser(output_kind: str, config):
236
229
  elif output_kind == PCA:
237
230
  output_id = config.pop(PCA_ID, None)
238
231
  return OutputEntry(PWMPCA, PCA, output_id)
232
+ elif output_kind == PCF:
233
+ output_id = config.pop(PCF_ID, None)
234
+ return OutputEntry(PCFRelay, PCF, output_id)
239
235
  else:
240
236
  raise GPIOOutputException(f"""Output type {output_kind} dont exists""")
241
237
 
@@ -284,6 +280,17 @@ def configure_relay(
284
280
  "pca_id": getattr(output, "output_id"),
285
281
  "output_type": output_type,
286
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
+ }
287
294
  elif getattr(output, "output_kind") == GPIO:
288
295
  if GPIO not in manager.grouped_outputs:
289
296
  manager.grouped_outputs[GPIO] = {}
@@ -342,12 +349,13 @@ def configure_event_sensor(
342
349
  GpioEventButtonClass = GpioEventButton if gpio.get("detection_type", "stable") == "stable" else GpioEventButtonBeta
343
350
  GpioEventButtonClass(
344
351
  pin=pin,
345
- press_callback=lambda x, i: press_callback(
352
+ press_callback=lambda x, i, z: press_callback(
346
353
  x=x,
347
354
  inpin=i,
348
355
  actions=gpio.get(ACTIONS, {}).get(x, []),
349
356
  input_type=INPUT,
350
- empty_message_after=gpio.get("clear_message", False)
357
+ empty_message_after=gpio.get("clear_message", False),
358
+ duration=z
351
359
  ),
352
360
  **gpio,
353
361
  )
@@ -373,7 +381,7 @@ def configure_binary_sensor(
373
381
  """Configure input sensor or button."""
374
382
  try:
375
383
  GpioInputBinarySensorClass = GpioInputBinarySensor if gpio.get("detection_type", "stable") == "stable" else GpioInputBinarySensorBeta
376
- GpioInputBinarySensor(
384
+ GpioInputBinarySensorClass(
377
385
  pin=pin,
378
386
  press_callback=lambda x, i: press_callback(
379
387
  x=x,
@@ -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
@@ -37,6 +38,9 @@ from boneio.const import (
37
38
  LIGHT,
38
39
  LED,
39
40
  SET_BRIGHTNESS,
41
+ MCP,
42
+ PCA,
43
+ PCF
40
44
  )
41
45
  from boneio.helper import (
42
46
  GPIOInputException,
@@ -57,8 +61,7 @@ from boneio.helper.loader import (
57
61
  configure_binary_sensor,
58
62
  configure_relay,
59
63
  create_dallas_sensor,
60
- create_mcp23017,
61
- create_pca9685,
64
+ create_expander,
62
65
  create_temp_sensor,
63
66
  )
64
67
  from boneio.helper.logger import configure_logger
@@ -92,6 +95,7 @@ class Manager:
92
95
  modbus: dict = {},
93
96
  pca9685: list = [],
94
97
  mcp23017: list = [],
98
+ pcf8575: list = [],
95
99
  ds2482: Optional[List] = [],
96
100
  dallas: Optional[dict] = None,
97
101
  oled: dict = {},
@@ -107,7 +111,7 @@ class Manager:
107
111
  self._host_data = None
108
112
  self._config_file_path = config_file_path
109
113
  self._state_manager = state_manager
110
- self._event_bus = EventBus(self._loop)
114
+ self._event_bus = EventBus(loop=self._loop)
111
115
 
112
116
  self.send_message = send_message
113
117
  self.stop_client = stop_client
@@ -115,6 +119,7 @@ class Manager:
115
119
  self._binary_pins = binary_pins
116
120
  self._i2cbusio = I2C(SCL, SDA)
117
121
  self._mcp = {}
122
+ self._pcf = {}
118
123
  self._pca = {}
119
124
  self._output = {}
120
125
  self._oled = None
@@ -132,13 +137,25 @@ class Manager:
132
137
  dallas=dallas, ds2482=ds2482, sensors=sensors.get(ONEWIRE)
133
138
  )
134
139
 
135
- self.grouped_outputs = create_mcp23017(
136
- manager=self, mcp23017=mcp23017, i2cbusio=self._i2cbusio
140
+ self.grouped_outputs = create_expander(
141
+ expander_dict=self._mcp,
142
+ expander_config=mcp23017,
143
+ exp_type=MCP,
144
+ i2cbusio=self._i2cbusio
137
145
  )
138
146
  self.grouped_outputs.update(
139
- create_pca9685(manager=self, pca9685=pca9685, i2cbusio=self._i2cbusio)
140
- )
141
-
147
+ create_expander(
148
+ expander_dict=self._pcf,
149
+ expander_config=pcf8575,
150
+ exp_type=PCF,
151
+ i2cbusio=self._i2cbusio))
152
+ self.grouped_outputs.update(
153
+ create_expander(
154
+ expander_dict=self._pca,
155
+ expander_config=pca9685,
156
+ exp_type=PCA,
157
+ i2cbusio=self._i2cbusio))
158
+
142
159
  self._configure_adc(adc_list=adc)
143
160
 
144
161
  for _config in relay_pins:
@@ -456,14 +473,27 @@ class Manager:
456
473
  def pca(self):
457
474
  """Get PCA by it's id."""
458
475
  return self._pca
476
+
477
+ @property
478
+ def pcf(self):
479
+ """Get PCF by it's id."""
480
+ return self._pcf
459
481
 
460
482
  def press_callback(
461
- 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
462
484
  ) -> None:
463
485
  """Press callback to use in input gpio.
464
486
  If relay input map is provided also toggle action on relay or cover or mqtt."""
465
487
  topic = f"{self._config_helper.topic_prefix}/{input_type}/{inpin}"
466
- 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)
467
497
  for action_definition in actions:
468
498
  _LOGGER.debug("Executing action %s", action_definition)
469
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, 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
  ),
@@ -128,7 +129,7 @@ class MQTTClient:
128
129
  await self.asyncio_client.unsubscribe(topic=topics, **params)
129
130
 
130
131
  def send_message(
131
- self, topic: str, payload: Union[str, dict, None], retain: bool = False
132
+ self, topic: str, payload: Union[str, int, dict, None], retain: bool = False
132
133
  ) -> None:
133
134
  """Send a message from the manager options."""
134
135
  to_publish = (
@@ -148,26 +149,30 @@ 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
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
164
169
 
165
170
  async def stop_client(self) -> None:
166
171
  await self.unsubscribe(
167
172
  topics=self._topics
168
173
  )
169
174
  raise RestartRequestException("Restart requested.")
170
-
175
+
171
176
  async def _subscribe_manager(self, manager: Manager) -> None:
172
177
  """Connect and subscribe to manager topics + host stats."""
173
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"]
@@ -3,7 +3,7 @@
3
3
  import asyncio
4
4
  import logging
5
5
  from typing import Callable
6
-
6
+ from boneio.helper.util import callback
7
7
  from boneio.const import LIGHT, NONE, OFF, ON, RELAY, STATE, SWITCH
8
8
  from boneio.helper import BasicMqtt
9
9
 
@@ -82,6 +82,11 @@ class BasicRelay(BasicMqtt):
82
82
  else:
83
83
  self.turn_on()
84
84
 
85
+ @callback
86
+ def _momentary_callback(self, time, action):
87
+ _LOGGER.info("Momentary callback at %s", time)
88
+ action()
89
+
85
90
  @property
86
91
  def is_active(self) -> bool:
87
92
  """Is active check."""
@@ -4,9 +4,8 @@ import logging
4
4
 
5
5
  from adafruit_mcp230xx.mcp23017 import MCP23017, DigitalInOut
6
6
 
7
- from boneio.const import NONE, SWITCH
7
+ from boneio.const import NONE, SWITCH, MCP
8
8
  from boneio.helper.events import async_track_point_in_time, utcnow
9
- from boneio.helper.util import callback
10
9
  from boneio.relay.basic import BasicRelay
11
10
 
12
11
  _LOGGER = logging.getLogger(__name__)
@@ -34,23 +33,23 @@ class MCPRelay(BasicRelay):
34
33
  **kwargs, output_type=output_type, restored_state=restored_state
35
34
  )
36
35
  self._pin_id = pin
37
- self._mcp_id = mcp_id
36
+ self._expander_id = mcp_id
38
37
  _LOGGER.debug("Setup MCP with pin %s", self._pin_id)
39
38
 
40
39
  @property
41
- def is_mcp_type(self) -> bool:
42
- """Check if relay is mcp type."""
43
- return True
40
+ def expander_type(self) -> str:
41
+ """Check expander type."""
42
+ return MCP
44
43
 
45
44
  @property
46
- def pin_id(self) -> str:
45
+ def pin_id(self) -> int:
47
46
  """Return PIN id."""
48
47
  return self._pin_id
49
48
 
50
49
  @property
51
- def mcp_id(self) -> str:
50
+ def expander_id(self) -> str:
52
51
  """Retrieve parent MCP ID."""
53
- return self._mcp_id
52
+ return self._expander_id
54
53
 
55
54
  @property
56
55
  def is_active(self) -> bool:
@@ -62,11 +61,6 @@ class MCPRelay(BasicRelay):
62
61
  """PIN of the relay"""
63
62
  return self._pin
64
63
 
65
- @callback
66
- def _momentary_callback(self, time, action):
67
- _LOGGER.info("Momentary callback at %s", time)
68
- action()
69
-
70
64
  def turn_on(self) -> None:
71
65
  """Call turn on action."""
72
66
  self.pin.value = True
@@ -9,7 +9,7 @@ from adafruit_pca9685 import PCA9685, PCAChannels
9
9
  from boneio.helper.events import async_track_point_in_time, utcnow
10
10
  from boneio.helper.util import callback
11
11
 
12
- from boneio.const import LED, NONE, OFF, ON, STATE, SWITCH, BRIGHTNESS, RELAY
12
+ from boneio.const import LED, NONE, OFF, ON, STATE, SWITCH, BRIGHTNESS, RELAY, PCA
13
13
  from boneio.helper import BasicMqtt
14
14
 
15
15
  _LOGGER = logging.getLogger(__name__)
@@ -58,9 +58,9 @@ class PWMPCA(BasicMqtt):
58
58
  _LOGGER.debug("Setup PCA with pin %s", self._pin_id)
59
59
 
60
60
  @property
61
- def is_pca_type(self) -> bool:
62
- """Check if relay is pca type."""
63
- return True
61
+ def expander_type(self) -> str:
62
+ """Check expander type."""
63
+ return PCA
64
64
 
65
65
  @property
66
66
  def output_type(self) -> str:
@@ -0,0 +1,86 @@
1
+ """PCF8575 Relay module."""
2
+
3
+ import logging
4
+
5
+ from adafruit_pcf8575 import DigitalInOut
6
+
7
+ from boneio.const import NONE, SWITCH, PCF
8
+ from boneio.helper.events import async_track_point_in_time, utcnow
9
+ from boneio.helper.pcf8575 import PCF8575
10
+ from boneio.relay.basic import BasicRelay
11
+
12
+ _LOGGER = logging.getLogger(__name__)
13
+
14
+
15
+ class PCFRelay(BasicRelay):
16
+ """Represents PCF Relay output"""
17
+
18
+ def __init__(
19
+ self,
20
+ pin: int,
21
+ expander: PCF8575,
22
+ expander_id: str,
23
+ output_type: str = SWITCH,
24
+ restored_state: bool = False,
25
+ **kwargs,
26
+ ) -> None:
27
+ """Initialize MCP relay."""
28
+ self._pin: DigitalInOut = expander.get_pin(pin)
29
+ if output_type == NONE:
30
+ """Just in case to not restore state of covers etc."""
31
+ restored_state = False
32
+ self._pin.switch_to_output(value=restored_state)
33
+ super().__init__(
34
+ **kwargs, output_type=output_type, restored_state=restored_state
35
+ )
36
+ self._pin_id = pin
37
+ self._expander_id = expander_id
38
+ self._active_state = False
39
+ _LOGGER.debug("Setup PCF with pin %s", self._pin_id)
40
+
41
+ @property
42
+ def expander_type(self) -> str:
43
+ """Check expander type."""
44
+ return PCF
45
+
46
+ @property
47
+ def pin_id(self) -> int:
48
+ """Return PIN id."""
49
+ return self._pin_id
50
+
51
+ @property
52
+ def expander_id(self) -> str:
53
+ """Retrieve parent MCP ID."""
54
+ return self._expander_id
55
+
56
+ @property
57
+ def is_active(self) -> bool:
58
+ """Is relay active."""
59
+ return self.pin.value == self._active_state
60
+
61
+ @property
62
+ def pin(self) -> str:
63
+ """PIN of the relay"""
64
+ return self._pin
65
+
66
+ def turn_on(self) -> None:
67
+ """Call turn on action."""
68
+ self.pin.value = self._active_state
69
+ if self._momentary_turn_on:
70
+ async_track_point_in_time(
71
+ loop=self._loop,
72
+ action=lambda x: self._momentary_callback(time=x, action=self.turn_off),
73
+ point_in_time=utcnow() + self._momentary_turn_on.as_timedelta,
74
+ )
75
+ self._loop.call_soon_threadsafe(self.send_state)
76
+
77
+ def turn_off(self) -> None:
78
+ """Call turn off action."""
79
+ self.pin.value = not self._active_state
80
+ if self._momentary_turn_off:
81
+ async_track_point_in_time(
82
+ loop=self._loop,
83
+ action=lambda x: self._momentary_callback(time=x, action=self.turn_on),
84
+ point_in_time=utcnow() + self._momentary_turn_off.as_timedelta,
85
+ )
86
+ self._loop.call_soon_threadsafe(self.send_state)
@@ -17,6 +17,7 @@ from boneio.const import (
17
17
  EVENT_ENTITY,
18
18
  LM75,
19
19
  MCP23017,
20
+ PCF8575,
20
21
  PCA9685,
21
22
  MCP_TEMP_9808,
22
23
  MODBUS,
@@ -34,11 +35,13 @@ from boneio.helper import StateManager
34
35
  from boneio.helper.config import ConfigHelper
35
36
  from boneio.manager import Manager
36
37
  from boneio.mqtt_client import MQTTClient
38
+ from boneio.helper.events import EventBus
37
39
 
38
40
  _LOGGER = logging.getLogger(__name__)
39
41
 
40
42
  config_modules = [
41
43
  {"name": MCP23017, "default": []},
44
+ {"name": PCF8575, "default": []},
42
45
  {"name": PCA9685, "default": []},
43
46
  {"name": DS2482, "default": []},
44
47
  {"name": ADC, "default": []},
@@ -56,7 +59,6 @@ async def async_run(
56
59
  mqttpassword: str = "",
57
60
  ) -> list[Any]:
58
61
  """Run BoneIO."""
59
-
60
62
  _config_helper = ConfigHelper(
61
63
  topic_prefix=config[MQTT].pop(TOPIC_PREFIX),
62
64
  ha_discovery=config[MQTT][HA_DISCOVERY].pop(ENABLED),
@@ -98,4 +100,4 @@ async def async_run(
98
100
  tasks.update(manager.get_tasks())
99
101
  _LOGGER.info("Connecting to MQTT.")
100
102
  tasks.add(client.start_client(manager))
101
- 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
@@ -1,9 +1,10 @@
1
1
  """GpioInputBinarySensor to receive signals."""
2
2
  import logging
3
3
  from functools import partial
4
- from boneio.const import PRESSED, RELEASED
4
+ from boneio.const import PRESSED, RELEASED, BOTH
5
5
  from boneio.helper import GpioBaseClass
6
6
  from boneio.helper.gpio import add_event_callback, add_event_detect
7
+
7
8
  _LOGGER = logging.getLogger(__name__)
8
9
 
9
10
 
@@ -0,0 +1,2 @@
1
+ # flake8: noqa
2
+ __version__ = "0.7.0dev3"
@@ -26,9 +26,10 @@ dependencies = [
26
26
  "adafruit-circuitpython-onewire>=2.0.5",
27
27
  "w1thermsensor[async]>=2.0.0",
28
28
  "adafruit-circuitpython-pca9685>=3.4.10",
29
+ "adafruit-circuitpython-pcf8575>=1.0.2",
29
30
  ]
30
31
  requires-python = ">=3.7"
31
- version = "0.7.0.dev1"
32
+ version = "0.7.0.dev3"
32
33
 
33
34
  [project.license]
34
35
  text = "GNU General Public License v3.0"
@@ -42,8 +43,6 @@ Changelog = "https://github.com/boneIO-eu/app_bbb/releases"
42
43
  [project.scripts]
43
44
  boneio = "boneio.bonecli:main"
44
45
 
45
- [project.optional-dependencies]
46
-
47
46
  [tool.pdm.version]
48
47
  from = "boneio/version.py"
49
48
 
@@ -1,2 +0,0 @@
1
- # flake8: noqa
2
- __version__ = "0.7.0dev1"
File without changes
File without changes
File without changes
File without changes