"""Feature-модель: какие возможности реально есть у прибора. Дефолты берутся из `device_capabilities` и фактических ответов устройства, пользователь может переопределить их в мастере (и в reconfigure); результат хранится в entry.data[CONF_FEATURES] и фильтрует создание сущностей. """ from __future__ import annotations from typing import Any, Mapping, Optional from pyfglair import Prop, Value from .const import FEATURES DIRECTION_KEYS = { "af_vertical_direction": Prop.AF_VERTICAL_NUM_DIR, "af_horizontal_direction": Prop.AF_HORIZONTAL_NUM_DIR, } def _int_value(values: Mapping[Prop, Value], prop: Prop) -> Optional[int]: value = values.get(prop) return value.int_value if value is not None else None def _caps(values: Mapping[Prop, Value]) -> Optional[int]: raw = _int_value(values, Prop.DEVICE_CAPABILITIES) return raw if raw else None def device_default( key: str, values: Mapping[Prop, Value], answered: Optional[set[Prop]] = None, ) -> bool: """Дефолт фичи по данным устройства (без пользовательских override).""" bit, prop = FEATURES[key] if bit is not None: caps = _caps(values) if caps is None: return True # возможностей не сообщили — не режем return bool(caps & (1 << bit)) if key in DIRECTION_KEYS: num = _int_value(values, DIRECTION_KEYS[key]) return bool(num and num > 1) if prop is not None: if answered is not None: return prop in answered return prop in values return True def resolve_features( values: Mapping[Prop, Value], answered: Optional[set[Prop]] = None, explicit: Optional[Mapping[str, Any]] = None, ) -> dict[str, bool]: """Полный набор фич: дефолт с устройства + пользовательские значения.""" resolved = { key: bool(value) for key, value in ( explicit or {} ).items() if key in FEATURES } for key in FEATURES: resolved.setdefault(key, device_default(key, values, answered)) return resolved class LiveValues(Mapping): """Mapping поверх client.cached(prop) — значения кэша ядра «на сейчас». Используется вместо снимка coordinator.data, чтобы фильтрация сущностей не зависела от гонки с обработкой push-событий в loop. """ def __init__(self, getter) -> None: self._getter = getter def __getitem__(self, prop: Prop) -> Value: value = self._getter(prop) if value is None: raise KeyError(prop) return value def __iter__(self): return iter(()) def __len__(self) -> int: return 0 class FeatureSet: """Фильтр создания сущностей для runtime (entry + live-значения ядра).""" def __init__( self, explicit: Optional[Mapping[str, Any]], values: Mapping[Prop, Value], ) -> None: self._explicit = { key: bool(value) for key, value in (explicit or {}).items() if key in FEATURES } self._values = values def enabled(self, key: str) -> bool: if key in self._explicit: return self._explicit[key] return device_default(key, self._values)