boneIO 1.3.0.dev16__py3-none-any.whl → 1.3.0.dev18__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 (89) hide show
  1. boneio/bonecli.py +4 -1
  2. boneio/components/cover/cover.py +17 -9
  3. boneio/components/cover/time_based.py +21 -6
  4. boneio/core/config/config_helper.py +8 -9
  5. boneio/core/config/yaml_util.py +266 -225
  6. boneio/core/discovery.py +86 -77
  7. boneio/core/manager/covers.py +139 -98
  8. boneio/core/manager/display.py +12 -2
  9. boneio/core/manager/inputs.py +172 -182
  10. boneio/core/manager/manager.py +39 -2
  11. boneio/core/manager/modbus.py +2 -2
  12. boneio/core/manager/outputs.py +2 -2
  13. boneio/core/manager/remote.py +205 -235
  14. boneio/core/manager/sensors.py +288 -236
  15. boneio/core/manager/update.py +38 -1
  16. boneio/core/messaging/__init__.py +9 -1
  17. boneio/core/messaging/composite.py +85 -0
  18. boneio/core/messaging/lox.py +221 -0
  19. boneio/core/messaging/loxone.py +181 -0
  20. boneio/core/messaging/mqtt.py +3 -0
  21. boneio/core/remote/__init__.py +1 -1
  22. boneio/core/remote/base.py +21 -8
  23. boneio/core/remote/mqtt.py +14 -2
  24. boneio/core/state/manager.py +12 -2
  25. boneio/core/system/host_data.py +17 -0
  26. boneio/hardware/display/oled.py +77 -80
  27. boneio/integration/homeassistant.py +9 -0
  28. boneio/integration/lox_template.py +243 -0
  29. boneio/migrations/__init__.py +10 -0
  30. boneio/migrations/actions.py +230 -0
  31. boneio/migrations/assets/MANIFEST.sha256 +19 -0
  32. boneio/migrations/assets/docker/daemon.json +7 -0
  33. boneio/migrations/assets/docker/nodered/docker-compose.yaml +30 -0
  34. boneio/migrations/assets/docker/nodered/nginx/default.conf +50 -0
  35. boneio/migrations/assets/docker/nodered/node-red/settings.js +5 -0
  36. boneio/migrations/assets/journald/journald.conf +6 -0
  37. boneio/migrations/assets/mosquitto/boneio.conf +2 -0
  38. boneio/migrations/assets/sudoers/boneio +15 -0
  39. boneio/migrations/assets/systemd/boneio-oled-boot.service +17 -0
  40. boneio/migrations/assets/systemd/boneio.service +15 -0
  41. boneio/migrations/assets/systemd/set-hostname-once.service +12 -0
  42. boneio/migrations/assets/usr-local-bin/set-hostname-once.sh +10 -0
  43. boneio/migrations/assets/usr-sbin/oled_boot_splash.py +257 -0
  44. boneio/migrations/assets/usr-sbin/oled_boot_splash.sh +18 -0
  45. boneio/migrations/assets/usr-sbin/oled_msg.py +110 -0
  46. boneio/migrations/assets/usr-sbin/oled_msg.sh +17 -0
  47. boneio/migrations/bootstrap/boneio-migrate +389 -0
  48. boneio/migrations/bootstrap/install-helper.sh +43 -0
  49. boneio/migrations/bootstrap/sudoers-migrate +2 -0
  50. boneio/migrations/runner.py +469 -0
  51. boneio/migrations/versions/__init__.py +1 -0
  52. boneio/migrations/versions/v1_3_0_baseline.py +168 -0
  53. boneio/migrations/versions/v1_3_0dev18_fix_oled_boot.py +75 -0
  54. boneio/modbus/client.py +116 -2
  55. boneio/modbus/coordinator.py +7 -0
  56. boneio/models/events.py +17 -1
  57. boneio/runner.py +92 -67
  58. boneio/schema/remote_devices.yaml +8 -8
  59. boneio/schema/schema.yaml +39 -0
  60. boneio/version.py +1 -1
  61. boneio/webui/app.py +4 -0
  62. boneio/webui/frontend-dist/assets/ConfigEditor-vyhv5yQV.js +1 -0
  63. boneio/webui/frontend-dist/assets/{aiConfig-DCT969oj.js → aiConfig-LL3nlemJ.js} +2 -2
  64. boneio/webui/frontend-dist/assets/{aiWizardPrompt-Dpts0jpN.js → aiWizardPrompt-CmumVq54.js} +1 -1
  65. boneio/webui/frontend-dist/assets/index-D7hfuu2N.css +2 -0
  66. boneio/webui/frontend-dist/assets/index-D97SbEW0.js +44 -0
  67. boneio/webui/frontend-dist/assets/useTranslation-DxGR4SjZ.js +13 -0
  68. boneio/webui/frontend-dist/index.html +4 -4
  69. boneio/webui/frontend-dist/schema/config.schema.json +80 -0
  70. boneio/webui/frontend-dist/schema/lox_udp.schema.json +64 -0
  71. boneio/webui/frontend-dist/schema/mqtt.schema.json +22 -0
  72. boneio/webui/frontend-dist/sw.js +1 -1
  73. boneio/webui/routes/__init__.py +2 -0
  74. boneio/webui/routes/config.py +379 -306
  75. boneio/webui/routes/migrations.py +187 -0
  76. boneio/webui/routes/modbus.py +69 -4
  77. boneio/webui/schema/config.schema.json +80 -0
  78. boneio/webui/schema/lox_udp.schema.json +64 -0
  79. boneio/webui/schema/mqtt.schema.json +22 -0
  80. {boneio-1.3.0.dev16.dist-info → boneio-1.3.0.dev18.dist-info}/METADATA +1 -1
  81. {boneio-1.3.0.dev16.dist-info → boneio-1.3.0.dev18.dist-info}/RECORD +85 -53
  82. boneio/webui/frontend-dist/assets/ConfigEditor-BA-Cp1Kt.js +0 -1
  83. boneio/webui/frontend-dist/assets/index-BPw0PLPj.js +0 -39
  84. boneio/webui/frontend-dist/assets/index-CCPsr7-u.css +0 -2
  85. boneio/webui/frontend-dist/assets/useTranslation-Cdm82kxW.js +0 -13
  86. {boneio-1.3.0.dev16.dist-info → boneio-1.3.0.dev18.dist-info}/WHEEL +0 -0
  87. {boneio-1.3.0.dev16.dist-info → boneio-1.3.0.dev18.dist-info}/entry_points.txt +0 -0
  88. {boneio-1.3.0.dev16.dist-info → boneio-1.3.0.dev18.dist-info}/licenses/LICENSE +0 -0
  89. {boneio-1.3.0.dev16.dist-info → boneio-1.3.0.dev18.dist-info}/top_level.txt +0 -0
boneio/bonecli.py CHANGED
@@ -240,13 +240,16 @@ def run(
240
240
 
241
241
  try:
242
242
  _t1 = _time.monotonic()
243
- _config = load_config_from_file(config_file=config)
243
+ _config = load_config_from_file(
244
+ config_file=config, progress_callback=draw_status,
245
+ )
244
246
  _LOGGER.debug("[STARTUP TIMING] load_config_from_file: %.2fs", _time.monotonic() - _t1)
245
247
  if not _config:
246
248
  _LOGGER.error("Config not loaded. Exiting.")
247
249
  draw_config_error("Config file is empty or missing")
248
250
  return 1
249
251
  configure_logger(log_config=_config.get("logger") or {}, debug=debug)
252
+ draw_status("Importing modules...")
250
253
  # Granular timing of runner sub-imports to find the bottleneck
251
254
  _t_a = _time.monotonic()
252
255
  from boneio.const import ACTION
@@ -187,13 +187,17 @@ class BaseCover(BaseCoverABC, BasicMqtt):
187
187
 
188
188
  self._event_bus.add_sigterm_listener(self.on_exit)
189
189
 
190
- self._loop.call_soon_threadsafe(
191
- self._loop.call_later,
192
- 0.5,
193
- self.send_state,
194
- self.state,
195
- self.json_position
196
- )
190
+ try:
191
+ self._loop.call_soon_threadsafe(
192
+ self._loop.call_later,
193
+ 0.5,
194
+ self.send_state,
195
+ self.state,
196
+ self.json_position
197
+ )
198
+ except RuntimeError:
199
+ # Event loop is closed (e.g. during tests)
200
+ pass
197
201
 
198
202
  async def on_exit(self) -> None:
199
203
  """Stop on exit."""
@@ -206,8 +210,12 @@ class BaseCover(BaseCoverABC, BasicMqtt):
206
210
  self._open_relay.turn_off()
207
211
  self._close_relay.turn_off()
208
212
  # Send relay states to WebSocket (not MQTT - that's handled by output_type check)
209
- asyncio.create_task(self._open_relay.async_send_state())
210
- asyncio.create_task(self._close_relay.async_send_state())
213
+ try:
214
+ asyncio.create_task(self._open_relay.async_send_state())
215
+ asyncio.create_task(self._close_relay.async_send_state())
216
+ except RuntimeError:
217
+ # Event loop is closed (e.g. during tests or shutdown)
218
+ pass
211
219
  if self._current_operation in (OPENING, CLOSING):
212
220
  self._last_operation = self._current_operation
213
221
  self._current_operation = IDLE
@@ -43,7 +43,7 @@ class TimeBasedCover(BaseCover):
43
43
 
44
44
  def _move_cover(self, direction: str, duration: float, target_position: int | None = None):
45
45
  """Run in sepearate thread.
46
-
46
+
47
47
  Args:
48
48
  direction: Direction of movement (OPEN or CLOSE)
49
49
  duration: Full time for 0-100% movement in milliseconds
@@ -60,7 +60,10 @@ class TimeBasedCover(BaseCover):
60
60
 
61
61
  if total_steps == 0 or duration == 0:
62
62
  self._current_operation = IDLE
63
- self._loop.call_soon_threadsafe(lambda: self.send_state(self.state, self.json_position))
63
+ try:
64
+ self._loop.call_soon_threadsafe(lambda: self.send_state(self.state, self.json_position))
65
+ except RuntimeError:
66
+ pass
64
67
  return
65
68
 
66
69
  # Calculate actual duration based on remaining distance
@@ -69,7 +72,10 @@ class TimeBasedCover(BaseCover):
69
72
 
70
73
  relay.turn_on()
71
74
  # Send relay state to WebSocket (not MQTT - that's handled by output_type check)
72
- self._loop.call_soon_threadsafe(lambda r=relay: asyncio.ensure_future(r.async_send_state()))
75
+ try:
76
+ self._loop.call_soon_threadsafe(lambda r=relay: asyncio.ensure_future(r.async_send_state()))
77
+ except RuntimeError:
78
+ pass
73
79
  start_time = time.monotonic()
74
80
 
75
81
  while not self._stop_event.is_set():
@@ -84,7 +90,10 @@ class TimeBasedCover(BaseCover):
84
90
 
85
91
  self._last_timestamp = current_time # Użyj pobranego czasu
86
92
  if current_time - self._last_update_time >= 1:
87
- self._loop.call_soon_threadsafe(lambda: self.send_state(self.state, self.json_position))
93
+ try:
94
+ self._loop.call_soon_threadsafe(lambda: self.send_state(self.state, self.json_position))
95
+ except RuntimeError:
96
+ break
88
97
  self._last_update_time = current_time
89
98
 
90
99
  if target_position is not None:
@@ -98,9 +107,15 @@ class TimeBasedCover(BaseCover):
98
107
  time.sleep(0.05) # Małe opóźnienie, aby nie blokować CPU
99
108
  relay.turn_off()
100
109
  # Send relay state to WebSocket (not MQTT - that's handled by output_type check)
101
- self._loop.call_soon_threadsafe(lambda r=relay: asyncio.ensure_future(r.async_send_state()))
110
+ try:
111
+ self._loop.call_soon_threadsafe(lambda r=relay: asyncio.ensure_future(r.async_send_state()))
112
+ except RuntimeError:
113
+ pass
102
114
  self._current_operation = IDLE
103
- self._loop.call_soon_threadsafe(lambda: self.send_state_and_save(self.json_position))
115
+ try:
116
+ self._loop.call_soon_threadsafe(lambda: self.send_state_and_save(self.json_position))
117
+ except RuntimeError:
118
+ pass
104
119
  self._last_update_time = time.monotonic() # Upewnij się, że aktualizacja jest wysłana na końcu ruchu
105
120
 
106
121
  async def run_cover(self, current_operation: str, target_position: int | None = None) -> None:
@@ -386,8 +386,14 @@ class ConfigHelper:
386
386
  def reload_config(self) -> dict[str, Any]:
387
387
  """Reload configuration from file and update cache.
388
388
 
389
- Waits for any background config cache rebuild to complete before
390
- loading, so the fast cached path is used instead of full validation.
389
+ Loads config from file using load_config_from_file() which tries the
390
+ disk cache first (~0.5s) and falls back to full Cerberus validation
391
+ (~20s) on cache miss. This method does NOT wait for any background
392
+ cache rebuild — it's independent.
393
+
394
+ NOTE: This method may take up to ~20s on BeagleBone if disk cache
395
+ is not available. Callers should run it via asyncio.to_thread() to
396
+ avoid blocking the event loop.
391
397
 
392
398
  Returns:
393
399
  dict: Reloaded configuration dictionary
@@ -395,13 +401,6 @@ class ConfigHelper:
395
401
  Raises:
396
402
  ValueError: If config_file_path is not set
397
403
  """
398
- # Wait for background cache rebuild (started after config save)
399
- try:
400
- from boneio.webui.routes.config import wait_for_config_cache
401
- wait_for_config_cache(timeout=30.0)
402
- except ImportError:
403
- pass # Web module not available (e.g., during tests)
404
-
405
404
  _LOGGER.info("Reloading config from file: %s", self._config_file_path)
406
405
  return self.get_config(force_reload=True)
407
406