diff --git a/custom_components/fglair/config_flow.py b/custom_components/fglair/config_flow.py index 339d615..95d097d 100644 --- a/custom_components/fglair/config_flow.py +++ b/custom_components/fglair/config_flow.py @@ -275,7 +275,7 @@ def _run_trial(data: dict[str, Any]) -> tuple[TrialResult, Template, bool]: model = str(data.get(CONF_MODEL) or "") probed = False if not model or detect(model) is None: - detected, _probe_props = probe_template( + detected, _probe_props, fatal = probe_template( data[CONF_HOST], data[CONF_DSN], data[CONF_LANIP_KEY], @@ -285,6 +285,9 @@ def _run_trial(data: dict[str, Any]) -> tuple[TrialResult, Template, bool]: timeout=TRIAL_TIMEOUT, ) probed = True + if fatal is not None: + # Модуль недоступен/нет слотов/ключ — вторую сессию не открываем. + return TrialResult(ok=False, reason=fatal), template, True if detected is not None: template = detected result = trial_connect( diff --git a/custom_components/fglair/const.py b/custom_components/fglair/const.py index f2083c3..c77b718 100644 --- a/custom_components/fglair/const.py +++ b/custom_components/fglair/const.py @@ -96,16 +96,16 @@ PRESET_PROP = { } SWING_MODES = ["off", "on"] -# switch-свойства: prop -> (translation_key, capability bit | None) -SWITCH_PROPS: dict[Prop, tuple[str, int | None]] = { - Prop.ECONOMY_MODE: ("economy_mode", CAP_ECONOMY), - Prop.POWERFUL_MODE: ("powerful_mode", CAP_POWERFUL), - Prop.COIL_DRY_MODE: ("coil_dry_mode", CAP_COIL_DRY), - Prop.MIN_HEAT: ("min_heat", CAP_MIN_HEAT), - Prop.OUTDOOR_LOW_NOISE: ("outdoor_low_noise", CAP_OUTDOOR_LOW_NOISE), - Prop.HUMAN_DET_AUTO_SAVE: ("human_det_auto_save", None), - Prop.WIFI_LED_ENABLE: ("wifi_led_enable", None), - Prop.INDOOR_FAN_CONTROL: ("indoor_fan_control", CAP_INDOOR_FAN_CONTROL), +# switch-свойства: prop -> translation_key (filtracija po FEATURES) +SWITCH_PROPS: dict[Prop, str] = { + Prop.ECONOMY_MODE: "economy_mode", + Prop.POWERFUL_MODE: "powerful_mode", + Prop.COIL_DRY_MODE: "coil_dry_mode", + Prop.MIN_HEAT: "min_heat", + Prop.OUTDOOR_LOW_NOISE: "outdoor_low_noise", + Prop.HUMAN_DET_AUTO_SAVE: "human_det_auto_save", + Prop.WIFI_LED_ENABLE: "wifi_led_enable", + Prop.INDOOR_FAN_CONTROL: "indoor_fan_control", } # --------------------------------------------------------------------------- diff --git a/custom_components/fglair/switch.py b/custom_components/fglair/switch.py index 496acf1..0f65ca0 100644 --- a/custom_components/fglair/switch.py +++ b/custom_components/fglair/switch.py @@ -40,7 +40,7 @@ async def async_setup_entry(hass, entry, async_add_entities) -> None: template = runtime.client.template entities = [] - for prop, (key, _capability) in SWITCH_PROPS.items(): + for prop, key in SWITCH_PROPS.items(): if prop_info(template, prop) is None: continue if not coordinator.features.enabled(key): diff --git a/custom_components/fglair/trial.py b/custom_components/fglair/trial.py index cbc69cc..b5831f7 100644 --- a/custom_components/fglair/trial.py +++ b/custom_components/fglair/trial.py @@ -226,11 +226,12 @@ def probe_template( device_port: int = 80, listen_port: int = 10275, timeout: float = 10.0, -) -> tuple[Template | None, dict[Prop, Value]]: +) -> tuple[Template | None, dict[Prop, Value], str | None]: """Определяет шаблон по ответам, перебирая A/B/F короткими сессиями. - Используется только когда oem_model неизвестна ядру: для известных - моделей шаблон берётся из C-таблиц без лишних подключений. + Возвращает (template, properties, fatal_reason): если модуль недоступен + (или нет слотов/ключ не тот), перебор бессмыслен и причина возвращается + третьим элементом, чтобы вызывающий не открывал ещё одну сессию. """ answered: set[Prop] = set() properties: dict[Prop, Value] = {} @@ -248,10 +249,10 @@ def probe_template( # Модуль доступен, но свойств шаблона нет — пробуем дальше. continue # Недоступен/нет слотов/ключ — перебор шаблонов бессмыслен. - return None, properties + return None, properties, result.reason answered |= result.answered properties.update(result.properties) detected = classify_template(answered) if detected is not None: - return detected, properties - return classify_template(answered), properties + return detected, properties, None + return classify_template(answered), properties, None diff --git a/tests/components/fglair/test_config_flow.py b/tests/components/fglair/test_config_flow.py index 8359c7c..493db91 100644 --- a/tests/components/fglair/test_config_flow.py +++ b/tests/components/fglair/test_config_flow.py @@ -580,7 +580,7 @@ async def test_run_trial_probes_unknown_model(hass: HomeAssistant): from custom_components.fglair import config_flow as cf with patch.object( - cf, "probe_template", return_value=(Template.B, {}) + cf, "probe_template", return_value=(Template.B, {}, None) ) as probe, patch.object( cf, "trial_connect", return_value=TrialResult(ok=True) ): diff --git a/tests/components/fglair/test_features.py b/tests/components/fglair/test_features.py index eb6338b..bb9b950 100644 --- a/tests/components/fglair/test_features.py +++ b/tests/components/fglair/test_features.py @@ -115,7 +115,8 @@ def test_probe_stops_on_unreachable(monkeypatch): return tr.TrialResult(ok=False, reason="unreachable") monkeypatch.setattr(tr, "trial_connect", fake_trial) - template, props = tr.probe_template("host", "dsn", "key", 1) + template, props, fatal = tr.probe_template("host", "dsn", "key", 1) assert template is None assert props == {} + assert fatal == "unreachable" assert calls == [Template.A]