ha(ux): мастер из трёх страниц, feature-модель и capabilities-фильтрация
- config flow: template (A/B/F с пояснениями, значения с устройства, определение по oem_model; для неизвестных моделей — пробинг A/B/F тремя короткими сессиями) -> capabilities (23 тумблера с описаниями, дефолты из device_capabilities/num_dir/ответов) -> limits (диапазон/шаг, ручная конверсия); каждая страница с пометкой «значения определены автоматически, можно пропустить» - features.py: FeatureSet/LiveValues/device_default/resolve_features; entry.data["features"] хранит выбор пользователя; reconfigure предзаполняет; YAML-import получает дефолты - сущности создаются только для включённых фич: climate (режимы/скорости/ swing/пресеты), switch, select заслонок (caps+num_dir), датчик комнаты; исправлено появление select без ламелей - trial: caps/num_dir и presence-зонды (необязательные свойства), answered; координатор ждёт первые свойства перед созданием сущностей (feature- дефолты без гонки) - атрибут description у сущностей (HA не поддерживает тултипы) + описания фич в мастере (en/ru) - тесты: features, probing, обновлённые flow/entities/repairs; 57 components
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
"""Дефолты feature-модели: device_capabilities, num_dir, presence."""
|
||||
from __future__ import annotations
|
||||
|
||||
from custom_components.fglair.features import (
|
||||
FeatureSet,
|
||||
device_default,
|
||||
resolve_features,
|
||||
)
|
||||
from pyfglair import Prop, Template, Value, ValueKind
|
||||
|
||||
|
||||
def _value(value: int) -> Value:
|
||||
return Value(ValueKind.INT, value)
|
||||
|
||||
|
||||
def test_caps_defaults_ap_wc1e():
|
||||
values = {Prop.DEVICE_CAPABILITIES: _value(5119)} # 0x13FF
|
||||
assert device_default("mode_cool", values) is True
|
||||
assert device_default("mode_heat", values) is True
|
||||
assert device_default("fan_auto", values) is True
|
||||
assert device_default("swing_vertical", values) is False
|
||||
assert device_default("swing_horizontal", values) is False
|
||||
assert device_default("economy_mode", values) is True
|
||||
assert device_default("powerful_mode", values) is False
|
||||
assert device_default("min_heat", values) is False
|
||||
assert device_default("coil_dry_mode", values) is False
|
||||
|
||||
|
||||
def test_caps_unknown_keeps_everything():
|
||||
assert device_default("swing_vertical", {}) is True
|
||||
assert device_default(
|
||||
"min_heat", {Prop.DEVICE_CAPABILITIES: _value(0)}
|
||||
) is True
|
||||
|
||||
|
||||
def test_directions_need_num_dir():
|
||||
zero = {Prop.AF_VERTICAL_NUM_DIR: _value(0)}
|
||||
five = {Prop.AF_VERTICAL_NUM_DIR: _value(5)}
|
||||
assert device_default("af_vertical_direction", zero) is False
|
||||
assert device_default("af_vertical_direction", five) is True
|
||||
assert device_default("af_vertical_direction", {}) is False
|
||||
assert device_default("af_horizontal_direction", zero) is False
|
||||
|
||||
|
||||
def test_presence_defaults():
|
||||
values = {Prop.DISPLAY_TEMPERATURE: _value(200)}
|
||||
assert device_default("display_temperature", values) is True
|
||||
assert device_default("wifi_led_enable", values) is False
|
||||
assert (
|
||||
device_default(
|
||||
"display_temperature", {}, {Prop.DISPLAY_TEMPERATURE}
|
||||
)
|
||||
is True
|
||||
)
|
||||
assert device_default("wifi_led_enable", {}, set()) is False
|
||||
|
||||
|
||||
def test_resolve_and_featureset_override():
|
||||
values = {Prop.DEVICE_CAPABILITIES: _value(5119)}
|
||||
resolved = resolve_features(values, set(), {"swing_vertical": True})
|
||||
assert resolved["swing_vertical"] is True
|
||||
assert resolved["powerful_mode"] is False
|
||||
assert len(resolved) == len(
|
||||
{key for key in resolved}
|
||||
)
|
||||
feature_set = FeatureSet({"swing_vertical": True}, values)
|
||||
assert feature_set.enabled("swing_vertical") is True
|
||||
assert feature_set.enabled("powerful_mode") is False
|
||||
default_set = FeatureSet(None, values)
|
||||
assert default_set.enabled("economy_mode") is True
|
||||
assert default_set.enabled("swing_vertical") is False
|
||||
|
||||
|
||||
def test_classify_template():
|
||||
from custom_components.fglair.trial import classify_template
|
||||
|
||||
assert classify_template(
|
||||
{Prop.AF_VERTICAL_MOVE_STEP1}
|
||||
) is Template.B
|
||||
assert classify_template(
|
||||
{Prop.MONITOR1, Prop.AF_VERTICAL_DIRECTION}
|
||||
) is Template.F
|
||||
assert classify_template(
|
||||
{Prop.AF_VERTICAL_DIRECTION}
|
||||
) is Template.A
|
||||
assert classify_template({Prop.DISPLAY_TEMPERATURE}) is Template.A
|
||||
assert classify_template(set()) is None
|
||||
|
||||
|
||||
def test_resolved_features_for_ap_wc1e_without_sensor():
|
||||
"""Канальный блок: датчик комнаты есть, ламелей/swing нет."""
|
||||
values = {
|
||||
Prop.DEVICE_CAPABILITIES: _value(5119),
|
||||
Prop.AF_VERTICAL_NUM_DIR: _value(0),
|
||||
Prop.AF_HORIZONTAL_NUM_DIR: _value(0),
|
||||
Prop.DISPLAY_TEMPERATURE: _value(205),
|
||||
}
|
||||
resolved = resolve_features(values, set(values))
|
||||
assert resolved["display_temperature"] is True
|
||||
assert resolved["swing_vertical"] is False
|
||||
assert resolved["af_vertical_direction"] is False
|
||||
assert resolved["af_horizontal_direction"] is False
|
||||
assert resolved["mode_cool"] is True
|
||||
assert resolved["economy_mode"] is True
|
||||
assert resolved["powerful_mode"] is False
|
||||
Reference in New Issue
Block a user