boneIO 1.3.0.dev18__py3-none-any.whl → 1.3.0.dev20__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. boneio/core/config/config_helper.py +3 -6
  2. boneio/core/config/yaml_util.py +13 -16
  3. boneio/core/manager/inputs.py +4 -3
  4. boneio/core/manager/manager.py +349 -228
  5. boneio/core/manager/modbus.py +1 -1
  6. boneio/core/manager/outputs.py +0 -2
  7. boneio/core/manager/sensors.py +0 -2
  8. boneio/core/manager/update.py +261 -181
  9. boneio/core/state/manager.py +3 -6
  10. boneio/hardware/display/oled.py +4 -6
  11. boneio/migrations/assets/MANIFEST.sha256 +1 -5
  12. boneio/migrations/assets/usr-sbin/oled_boot_splash.py +1 -1
  13. boneio/migrations/versions/v1_3_0_baseline.py +5 -0
  14. boneio/migrations/versions/{v1_3_0dev18_fix_oled_boot.py → v1_3_1_fix_oled_boot.py} +1 -1
  15. boneio/runner.py +0 -2
  16. boneio/version.py +1 -1
  17. boneio/webui/frontend-dist/assets/{ConfigEditor-vyhv5yQV.js → ConfigEditor-DKIsOog1.js} +1 -1
  18. boneio/webui/frontend-dist/assets/index-C_S5rhz7.css +2 -0
  19. boneio/webui/frontend-dist/assets/{index-D97SbEW0.js → index-ixNf6oOr.js} +31 -31
  20. boneio/webui/frontend-dist/assets/useTranslation-CS9arJOj.js +13 -0
  21. boneio/webui/frontend-dist/index.html +3 -3
  22. boneio/webui/frontend-dist/sw.js +1 -1
  23. boneio/webui/routes/config.py +27 -15
  24. boneio/webui/routes/modbus.py +2 -4
  25. {boneio-1.3.0.dev18.dist-info → boneio-1.3.0.dev20.dist-info}/METADATA +1 -1
  26. {boneio-1.3.0.dev18.dist-info → boneio-1.3.0.dev20.dist-info}/RECORD +30 -30
  27. boneio/webui/frontend-dist/assets/index-D7hfuu2N.css +0 -2
  28. boneio/webui/frontend-dist/assets/useTranslation-DxGR4SjZ.js +0 -13
  29. {boneio-1.3.0.dev18.dist-info → boneio-1.3.0.dev20.dist-info}/WHEEL +0 -0
  30. {boneio-1.3.0.dev18.dist-info → boneio-1.3.0.dev20.dist-info}/entry_points.txt +0 -0
  31. {boneio-1.3.0.dev18.dist-info → boneio-1.3.0.dev20.dist-info}/licenses/LICENSE +0 -0
  32. {boneio-1.3.0.dev18.dist-info → boneio-1.3.0.dev20.dist-info}/top_level.txt +0 -0
@@ -40,7 +40,7 @@ class ConfigHelper:
40
40
  device_type: str = "boneIO Black",
41
41
  ha_discovery: bool = True,
42
42
  ha_discovery_prefix: str = HOMEASSISTANT,
43
- network_info: dict = {},
43
+ network_info: dict | None = None,
44
44
  is_web_active: bool = False,
45
45
  web_port: int = 8090,
46
46
  proxy_port: int | None = None,
@@ -56,7 +56,7 @@ class ConfigHelper:
56
56
  self._name = name
57
57
 
58
58
  # Generate serial number from MAC - always required for topic prefix
59
- self._serial_no = get_serial_from_mac(network_info)
59
+ self._serial_no = get_serial_from_mac(network_info or {})
60
60
 
61
61
  # Build fixed topic prefix: boneio/blk_{serial}
62
62
  # This is no longer configurable - always uses this format
@@ -309,10 +309,7 @@ class ConfigHelper:
309
309
  def is_topic_in_autodiscovery(self, topic: str) -> bool:
310
310
  topic_parts_raw = topic[len(f"{self._ha_discovery_prefix}/") :].split("/")
311
311
  ha_type = topic_parts_raw[0]
312
- if ha_type in self._autodiscovery_messages:
313
- if topic in self._autodiscovery_messages[ha_type]:
314
- return True
315
- return False
312
+ return ha_type in self._autodiscovery_messages and topic in self._autodiscovery_messages[ha_type]
316
313
 
317
314
  def clear_autodiscovery_type(self, ha_type: str):
318
315
  self._autodiscovery_messages[ha_type] = {}
@@ -1,3 +1,4 @@
1
+ import contextlib
1
2
  import fnmatch
2
3
  import logging
3
4
  import os
@@ -309,7 +310,7 @@ def merge_board_config(config: dict) -> dict:
309
310
  if not board_config:
310
311
  raise ConfigurationException(f"Bottom board configuration file {board_file} is empty")
311
312
  except FileNotFoundError:
312
- raise ConfigurationException(f"Board configuration for {board_name} version {version} not found")
313
+ raise ConfigurationException(f"Board configuration for {board_name} version {version} not found") from None
313
314
  _LOGGER.debug(f"Loaded board configuration: {board_name}")
314
315
 
315
316
  # Copy MCP configuration if not already defined
@@ -427,9 +428,8 @@ class CustomValidator(Validator):
427
428
  doc_value = self.document[key] # type: ignore[attr-defined]
428
429
  if isinstance(doc_value, str):
429
430
  doc_value = doc_value.lower()
430
- if doc_value in [v.lower() if isinstance(v, str) else v for v in values]:
431
- if field not in self.document: # type: ignore[attr-defined]
432
- self._error(field, f"required when {key} is {doc_value}") # type: ignore[attr-defined]
431
+ if doc_value in [v.lower() if isinstance(v, str) else v for v in values] and field not in self.document: # type: ignore[attr-defined]
432
+ self._error(field, f"required when {key} is {doc_value}") # type: ignore[attr-defined]
433
433
 
434
434
  def _validate_forbidden_if(self, forbidden_if, field, value):
435
435
  """Validate that a field is forbidden if a condition is met.
@@ -448,9 +448,12 @@ class CustomValidator(Validator):
448
448
  if isinstance(doc_value, str):
449
449
  doc_value = doc_value.lower()
450
450
 
451
- if doc_value in [v.lower() if isinstance(v, str) else v for v in values]:
452
- if field in self.document and value != default_value: # type: ignore[attr-defined]
453
- self._error(field, f"forbidden when {key} is {doc_value}") # type: ignore[attr-defined]
451
+ if (
452
+ doc_value in [v.lower() if isinstance(v, str) else v for v in values]
453
+ and field in self.document
454
+ and value != default_value
455
+ ): # type: ignore[attr-defined]
456
+ self._error(field, f"forbidden when {key} is {doc_value}") # type: ignore[attr-defined]
454
457
 
455
458
  def _normalize_coerce_action_field(self, value):
456
459
  """Handle conditional defaults for action fields."""
@@ -809,7 +812,6 @@ def _compute_config_dir_hash(config_file: str) -> str:
809
812
  hashed — other files in the directory (e.g. ``state.json``) are ignored.
810
813
  """
811
814
  import hashlib
812
- import re
813
815
  from pathlib import Path
814
816
 
815
817
  config_path = Path(config_file)
@@ -1062,7 +1064,7 @@ def load_config_from_file(
1062
1064
  try:
1063
1065
  config_yaml = load_yaml_file(config_file)
1064
1066
  except FileNotFoundError as err:
1065
- raise ConfigurationException(err)
1067
+ raise ConfigurationException(err) from err
1066
1068
  _LOGGER.debug("[STARTUP TIMING] load_yaml_file: %.2fs", _time.monotonic() - _t0)
1067
1069
  if not config_yaml:
1068
1070
  _LOGGER.warning("Missing yaml file. %s", config_file)
@@ -1205,7 +1207,6 @@ def update_config_section(config_file: str, section: str, data: dict | list) ->
1205
1207
  Returns:
1206
1208
  dict: Status response with success/error message
1207
1209
  """
1208
- import os
1209
1210
  from pathlib import Path
1210
1211
 
1211
1212
  config_dir = Path(config_file).parent
@@ -1222,10 +1223,8 @@ def update_config_section(config_file: str, section: str, data: dict | list) ->
1222
1223
  if addr.startswith("0x") or addr.startswith("0X"):
1223
1224
  entry["address"] = int(addr, 16)
1224
1225
  else:
1225
- try:
1226
+ with contextlib.suppress(ValueError):
1226
1227
  entry["address"] = int(addr, 10)
1227
- except ValueError:
1228
- pass # Keep as string if conversion fails
1229
1228
 
1230
1229
  # Strip default values to keep YAML clean
1231
1230
  cleaned_data = strip_default_values(data, {}, section)
@@ -1366,8 +1365,6 @@ def update_yaml_field(config_file: str, section: str, field: str, value: str) ->
1366
1365
  Returns:
1367
1366
  dict: Status response with success/error message.
1368
1367
  """
1369
- import re
1370
-
1371
1368
  try:
1372
1369
  with open(config_file, encoding="utf-8") as f:
1373
1370
  lines = f.readlines()
@@ -1382,7 +1379,7 @@ def update_yaml_field(config_file: str, section: str, field: str, value: str) ->
1382
1379
  insert_index = -1
1383
1380
  updated_lines = []
1384
1381
 
1385
- for i, line in enumerate(lines):
1382
+ for line in lines:
1386
1383
  stripped = line.strip()
1387
1384
  line_indent = len(line) - len(line.lstrip())
1388
1385
 
@@ -177,7 +177,7 @@ class InputManager:
177
177
  gpio: dict,
178
178
  pin: str,
179
179
  existing_input: GpioEventButton | None = None,
180
- actions: dict = {},
180
+ actions: dict | None = None,
181
181
  ) -> GpioEventButton | None:
182
182
  """Configure event input sensor with multiclick detection.
183
183
 
@@ -190,6 +190,7 @@ class InputManager:
190
190
  Returns:
191
191
  Configured GpioEventButton instance or None on error
192
192
  """
193
+ actions = actions or {}
193
194
  try:
194
195
  # Determine display name (ensure it's always a string)
195
196
  if "name" in gpio:
@@ -313,7 +314,7 @@ class InputManager:
313
314
  gpio: dict,
314
315
  pin: str,
315
316
  existing_input: GpioInputBinarySensor | None = None,
316
- actions: dict = {},
317
+ actions: dict | None = None,
317
318
  ) -> GpioInputBinarySensor | None:
318
319
  """Configure binary sensor input with state detection.
319
320
 
@@ -326,6 +327,7 @@ class InputManager:
326
327
  Returns:
327
328
  Configured GpioInputBinarySensor instance or None on error
328
329
  """
330
+ actions = actions or {}
329
331
  try:
330
332
  # Determine display name (ensure it's always a string)
331
333
  if "name" in gpio:
@@ -564,7 +566,6 @@ class InputManager:
564
566
  import time
565
567
 
566
568
  from boneio.models import InputState
567
- from boneio.models.events import InputEvent
568
569
 
569
570
  timestamp = time.time()
570
571