chore(release): beta-1.03
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -38,6 +38,29 @@ def normalize_mac(mac):
|
||||
return None
|
||||
|
||||
|
||||
def resolve_device_mac_for_select_routing(devices, name_key):
|
||||
"""
|
||||
Map a v1 ``select`` map key to device storage id (MAC).
|
||||
|
||||
Matches the registry **name**, or ``led-<12hex>`` as a MAC hint (default driver
|
||||
name form) so routing still works after the device is renamed in the registry.
|
||||
"""
|
||||
k = str(name_key or "").strip()
|
||||
if not k:
|
||||
return None
|
||||
for did in devices.list():
|
||||
doc = devices.read(did) or {}
|
||||
if str(doc.get("name") or "").strip() == k:
|
||||
m = normalize_mac(did)
|
||||
if m:
|
||||
return m
|
||||
if k.startswith("led-"):
|
||||
m = normalize_mac(k[4:])
|
||||
if m and devices.read(m):
|
||||
return m
|
||||
return None
|
||||
|
||||
|
||||
def derive_device_mac(mac=None, address=None, transport="espnow"):
|
||||
"""
|
||||
Resolve the device MAC used as storage id.
|
||||
|
||||
@@ -1,14 +1,66 @@
|
||||
from models.model import Model
|
||||
|
||||
|
||||
class Group(Model):
|
||||
"""Device groups (members + optional Wi‑Fi driver defaults); also pattern fields for sequences."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def load(self):
|
||||
super().load()
|
||||
changed = False
|
||||
for gid, doc in list(self.items()):
|
||||
if not isinstance(doc, dict):
|
||||
continue
|
||||
if self._migrate_record(doc):
|
||||
changed = True
|
||||
if changed:
|
||||
self.save()
|
||||
|
||||
def _migrate_record(self, doc):
|
||||
changed = False
|
||||
raw_dev = doc.get("devices")
|
||||
if raw_dev is None:
|
||||
doc["devices"] = []
|
||||
changed = True
|
||||
elif isinstance(raw_dev, list):
|
||||
norm = []
|
||||
for x in raw_dev:
|
||||
if x is None:
|
||||
continue
|
||||
s = str(x).strip().lower().replace(":", "").replace("-", "")
|
||||
if len(s) == 12 and all(c in "0123456789abcdef" for c in s):
|
||||
norm.append(s)
|
||||
else:
|
||||
norm.append(str(x).strip())
|
||||
if norm != raw_dev:
|
||||
doc["devices"] = norm
|
||||
changed = True
|
||||
for key in (
|
||||
"wifi_driver_display_name",
|
||||
"wifi_driver_num_leds",
|
||||
"wifi_color_order",
|
||||
"wifi_startup_mode",
|
||||
):
|
||||
if key not in doc:
|
||||
doc[key] = None
|
||||
changed = True
|
||||
if "output_brightness" not in doc:
|
||||
doc["output_brightness"] = 255
|
||||
changed = True
|
||||
return changed
|
||||
|
||||
def create(self, name=""):
|
||||
next_id = self.get_next_id()
|
||||
self[next_id] = {
|
||||
"name": name,
|
||||
"devices": [],
|
||||
"wifi_driver_display_name": None,
|
||||
"wifi_driver_num_leds": None,
|
||||
"wifi_color_order": None,
|
||||
"wifi_startup_mode": None,
|
||||
"output_brightness": 255,
|
||||
"pattern": "on",
|
||||
"colors": ["000000", "FF0000"],
|
||||
"brightness": 100,
|
||||
@@ -22,7 +74,7 @@ class Group(Model):
|
||||
"n5": 0,
|
||||
"n6": 0,
|
||||
"n7": 0,
|
||||
"n8": 0
|
||||
"n8": 0,
|
||||
}
|
||||
self.save()
|
||||
return next_id
|
||||
|
||||
@@ -261,33 +261,33 @@ async def _driver_connection_loop(ip: str) -> None:
|
||||
retry_interval_s = 2.0
|
||||
retry_interval_s = max(0.2, retry_interval_s)
|
||||
try:
|
||||
retry_window_s = float(_settings.get("wifi_driver_connect_retry_window_s", 120.0))
|
||||
max_boot_attempts = int(_settings.get("wifi_driver_initial_connect_attempts", 4))
|
||||
except (TypeError, ValueError):
|
||||
retry_window_s = 120.0
|
||||
retry_window_s = max(5.0, retry_window_s)
|
||||
max_boot_attempts = 4
|
||||
max_boot_attempts = max(1, max_boot_attempts)
|
||||
try:
|
||||
open_timeout = float(_settings.get("wifi_driver_ws_open_timeout", 45.0))
|
||||
except (TypeError, ValueError):
|
||||
open_timeout = 45.0
|
||||
open_timeout = max(5.0, open_timeout)
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
stagger = _stagger_delay_s_for_ip(ip)
|
||||
if stagger > 0:
|
||||
await asyncio.sleep(stagger)
|
||||
|
||||
# Only bound boot-time: after we have connected once, keep retrying (Wi-Fi drops, reboots).
|
||||
connected_once = False
|
||||
deadline = loop.time() + retry_window_s
|
||||
boot_attempts = 0
|
||||
try:
|
||||
while True:
|
||||
now = loop.time()
|
||||
if not connected_once and now >= deadline:
|
||||
print(
|
||||
f"[WS] driver {ip} still unreachable after {int(retry_window_s)}s "
|
||||
f"(initial window); stopping until next UDP hello / registry prime"
|
||||
)
|
||||
break
|
||||
if not connected_once:
|
||||
if boot_attempts >= max_boot_attempts:
|
||||
print(
|
||||
f"[WS] driver {ip} still unreachable after {max_boot_attempts} "
|
||||
f"initial dial attempt(s); stopping until next UDP hello / registry prime"
|
||||
)
|
||||
break
|
||||
boot_attempts += 1
|
||||
try:
|
||||
print(f"[WS] connecting to {uri!r}")
|
||||
async with websockets.connect(
|
||||
|
||||
@@ -27,11 +27,27 @@ class Zone(Model):
|
||||
Zone._migration_checked = True
|
||||
super().__init__()
|
||||
|
||||
def create(self, name="", names=None, presets=None):
|
||||
def load(self):
|
||||
super().load()
|
||||
changed = False
|
||||
for zid, doc in list(self.items()):
|
||||
if not isinstance(doc, dict):
|
||||
continue
|
||||
if "group_ids" not in doc:
|
||||
doc["group_ids"] = []
|
||||
changed = True
|
||||
if changed:
|
||||
self.save()
|
||||
|
||||
def create(self, name="", names=None, presets=None, group_ids=None):
|
||||
next_id = self.get_next_id()
|
||||
gid_list = []
|
||||
if isinstance(group_ids, list):
|
||||
gid_list = [str(x) for x in group_ids if x is not None]
|
||||
self[next_id] = {
|
||||
"name": name,
|
||||
"names": names if names else [],
|
||||
"group_ids": gid_list,
|
||||
"presets": presets if presets else [],
|
||||
"default_preset": None,
|
||||
"brightness": 255,
|
||||
|
||||
Reference in New Issue
Block a user