Stabilize discovery lifecycle and rescan summary
This commit is contained in:
@@ -1,28 +1,96 @@
|
||||
from dataclasses import asdict, dataclass
|
||||
import logging
|
||||
from typing import Dict, List, Optional
|
||||
from typing import Dict, List
|
||||
|
||||
from app.models.device import DeviceSchema, GroupModel
|
||||
from app.core.discovery import DiscoveryService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DiscoveryApplyResult:
|
||||
found: int
|
||||
added: int
|
||||
updated: int
|
||||
removed_offline: int
|
||||
pending_removal: int
|
||||
online: int
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return asdict(self)
|
||||
|
||||
|
||||
class StateManager:
|
||||
def __init__(self):
|
||||
# Храним устройства как Pydantic объекты
|
||||
self.devices: Dict[str, DeviceSchema] = {}
|
||||
# Группы как модели SQLAlchemy
|
||||
self.groups: Dict[str, GroupModel] = {}
|
||||
# Сколько подряд циклов discovery устройство не видно
|
||||
self._missing_scan_counts: Dict[str, int] = {}
|
||||
|
||||
def update_device(self, device_data: dict):
|
||||
"""Обновляет или добавляет устройство в состояние."""
|
||||
|
||||
mac = device_data["mac"]
|
||||
|
||||
# Используем DeviceSchema вместо Device
|
||||
current = self.devices.get(mac)
|
||||
device = DeviceSchema(
|
||||
id=mac, ip=device_data["ip"], name=f"WiZ {mac[-4:]}", room="Default"
|
||||
id=mac,
|
||||
ip=device_data["ip"],
|
||||
name=current.name if current else f"WiZ {mac[-4:]}",
|
||||
room=current.room if current else "Default",
|
||||
)
|
||||
self.devices[mac] = device
|
||||
self._missing_scan_counts.pop(mac, None)
|
||||
|
||||
def apply_discovery_snapshot(
|
||||
self,
|
||||
found_devices: list[dict],
|
||||
*,
|
||||
remove_missing: bool,
|
||||
missing_threshold: int = 1,
|
||||
) -> DiscoveryApplyResult:
|
||||
found_by_mac = {device["mac"]: device for device in found_devices}
|
||||
|
||||
added = 0
|
||||
updated = 0
|
||||
for mac, device_data in found_by_mac.items():
|
||||
if mac in self.devices:
|
||||
updated += 1
|
||||
else:
|
||||
added += 1
|
||||
self.update_device(device_data)
|
||||
|
||||
removed_offline = 0
|
||||
if remove_missing:
|
||||
for mac in list(self.devices):
|
||||
if mac in found_by_mac:
|
||||
continue
|
||||
|
||||
missed_scans = self._missing_scan_counts.get(mac, 0) + 1
|
||||
self._missing_scan_counts[mac] = missed_scans
|
||||
if missed_scans < missing_threshold:
|
||||
logger.info(
|
||||
"Устройство %s не ответило (%s/%s), оставляю до следующего цикла",
|
||||
mac,
|
||||
missed_scans,
|
||||
missing_threshold,
|
||||
)
|
||||
continue
|
||||
|
||||
self.devices.pop(mac, None)
|
||||
self._missing_scan_counts.pop(mac, None)
|
||||
removed_offline += 1
|
||||
logger.info("Устройство %s не ответило -- убрано из списка", mac)
|
||||
|
||||
return DiscoveryApplyResult(
|
||||
found=len(found_by_mac),
|
||||
added=added,
|
||||
updated=updated,
|
||||
removed_offline=removed_offline,
|
||||
pending_removal=len(self._missing_scan_counts),
|
||||
online=len(self.devices),
|
||||
)
|
||||
|
||||
def get_group_ips(self, group_id: str) -> List[str]:
|
||||
"""Возвращает список IP всех ламп, входящих в группу."""
|
||||
|
||||
Reference in New Issue
Block a user