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