ha(ux): пробинг отдаёт fatal-reason (одна сессия на недоступном модуле); SWITCH_PROPS без битов

This commit is contained in:
2026-09-29 18:12:31 +03:00
parent 976a2dcbb8
commit 5f09716dce
6 changed files with 25 additions and 20 deletions
+4 -1
View File
@@ -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(
+10 -10
View File
@@ -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",
}
# ---------------------------------------------------------------------------
+1 -1
View File
@@ -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):
+7 -6
View File
@@ -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