149 lines
4.2 KiB
Python
149 lines
4.2 KiB
Python
"""Deliver v1 JSON to drivers via bridge devices envelope."""
|
|
|
|
import asyncio
|
|
import json
|
|
from typing import Any, Dict, List, Optional, Set, Union
|
|
|
|
from util.bridge_envelope import (
|
|
BROADCAST_MAC,
|
|
build_devices_envelope,
|
|
format_mac_key,
|
|
normalize_mac_key,
|
|
split_v1_body_for_espnow,
|
|
)
|
|
from util.espnow_message import build_message
|
|
_MAX_JSON_ESPNOW = 240
|
|
|
|
|
|
def _body_from_message(msg: Union[str, bytes, bytearray, Dict[str, Any]]) -> Optional[Dict[str, Any]]:
|
|
if isinstance(msg, dict):
|
|
if msg.get("v") == "1" and "devices" not in msg:
|
|
return dict(msg)
|
|
return None
|
|
if isinstance(msg, str):
|
|
try:
|
|
data = json.loads(msg)
|
|
except (ValueError, TypeError):
|
|
return None
|
|
return data if isinstance(data, dict) else None
|
|
if isinstance(msg, (bytes, bytearray)):
|
|
raw = bytes(msg)
|
|
if not raw or raw[0] != ord("{"):
|
|
return None
|
|
try:
|
|
data = json.loads(raw.decode("utf-8"))
|
|
except (UnicodeError, ValueError, TypeError):
|
|
return None
|
|
return data if isinstance(data, dict) else None
|
|
return None
|
|
|
|
|
|
async def _deliver_v1_body(bridge, mac_key: str, body: Dict[str, Any], delay_s: float) -> int:
|
|
deliveries = 0
|
|
try:
|
|
chunks = split_v1_body_for_espnow(body)
|
|
except ValueError:
|
|
return 0
|
|
for chunk in chunks:
|
|
env = build_devices_envelope({mac_key: chunk})
|
|
if await bridge.send(env):
|
|
deliveries += 1
|
|
if delay_s > 0:
|
|
await asyncio.sleep(delay_s)
|
|
return deliveries
|
|
|
|
|
|
def build_preset_json_chunks(
|
|
presets_by_name: Dict[str, Any],
|
|
*,
|
|
save: bool = False,
|
|
default: Optional[str] = None,
|
|
max_payload: int = _MAX_JSON_ESPNOW,
|
|
) -> List[str]:
|
|
entries = list(presets_by_name.items())
|
|
chunks: List[str] = []
|
|
batch: Dict[str, Any] = {}
|
|
|
|
def _msg_for(presets_map: Dict[str, Any], *, final_save: bool, def_id: Optional[str]) -> str:
|
|
return build_message(
|
|
presets=presets_map,
|
|
save=final_save,
|
|
default=def_id,
|
|
)
|
|
|
|
for name, preset_obj in entries:
|
|
trial = dict(batch)
|
|
trial[name] = preset_obj
|
|
try:
|
|
msg = _msg_for(trial, final_save=False, def_id=None)
|
|
except (TypeError, ValueError):
|
|
msg = ""
|
|
if len(msg.encode("utf-8")) <= max_payload or not batch:
|
|
batch = trial
|
|
else:
|
|
chunks.append(_msg_for(batch, final_save=False, def_id=None))
|
|
batch = {name: preset_obj}
|
|
|
|
if batch:
|
|
chunks.append(
|
|
_msg_for(
|
|
batch,
|
|
final_save=save,
|
|
def_id=str(default) if default else None,
|
|
)
|
|
)
|
|
return [c for c in chunks if c]
|
|
|
|
|
|
def _unicast_mac_keys(target_macs: Optional[List[str]]) -> List[str]:
|
|
"""One formatted MAC per target; empty list means broadcast."""
|
|
if not target_macs:
|
|
return [BROADCAST_MAC]
|
|
keys: List[str] = []
|
|
seen: set = set()
|
|
for raw in target_macs:
|
|
h = normalize_mac_key(raw)
|
|
if h and h not in seen:
|
|
seen.add(h)
|
|
keys.append(format_mac_key(h))
|
|
return keys if keys else [BROADCAST_MAC]
|
|
|
|
|
|
async def deliver_json_messages(
|
|
bridge,
|
|
messages,
|
|
target_macs,
|
|
devices_model,
|
|
delay_s=0.1,
|
|
*,
|
|
unicast: bool = False,
|
|
):
|
|
"""
|
|
Deliver v1 JSON to drivers. Default: ESP-NOW broadcast (``ff:ff:…``); drivers
|
|
filter on ``groups`` in the body. Set ``unicast=True`` only for per-device settings
|
|
or single-device identify.
|
|
|
|
Uses the current bridge connection only (per-group bridge assignment is disabled).
|
|
"""
|
|
del devices_model
|
|
from models.transport import get_current_bridge
|
|
|
|
active = get_current_bridge() or bridge
|
|
if active is None:
|
|
raise RuntimeError("Transport not configured")
|
|
|
|
if unicast and target_macs:
|
|
mac_keys = _unicast_mac_keys(target_macs)
|
|
else:
|
|
mac_keys = [BROADCAST_MAC]
|
|
|
|
deliveries = 0
|
|
for mac_key in mac_keys:
|
|
for msg in messages:
|
|
body = _body_from_message(msg)
|
|
if not body:
|
|
continue
|
|
deliveries += await _deliver_v1_body(active, mac_key, body, delay_s)
|
|
|
|
return deliveries, len(messages)
|