fix(health): reflect Ecowitt as a protocol in the diagnostics summary
The health model only knew "wu"/"wslink", so with an Ecowitt setup the active_protocol / integration_status fell back to "PWS/WU" even while Ecowitt data was flowing. - Replace _protocol_name() with _configured_protocol() which returns wu / wslink / ecowitt (legacy endpoint takes precedence; ecowitt-only -> ecowitt). - _refresh_summary: treat "ecowitt" as a real accepted protocol (active_protocol and online_<proto> now track it); the WU-vs-WSLink mismatch -> degraded check is scoped to the legacy pair so coexisting Ecowitt is never falsely degraded. - Add the "ecowitt" / "online_ecowitt" entity states to strings/en/cs. 310 passing, 100% coverage; ruff + basedpyright clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parent
514ab823d6
commit
72c383ffba
|
|
@ -37,8 +37,10 @@ from homeassistant.util import dt as dt_util
|
|||
from .const import (
|
||||
DEFAULT_URL,
|
||||
DOMAIN,
|
||||
ECOWITT_ENABLED,
|
||||
ECOWITT_URL_PREFIX,
|
||||
HEALTH_URL,
|
||||
LEGACY_ENABLED,
|
||||
POCASI_CZ_ENABLED,
|
||||
WINDY_ENABLED,
|
||||
WSLINK,
|
||||
|
|
@ -53,9 +55,23 @@ from .windy_func import WindyPush
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _protocol_name(wslink_enabled: bool) -> str:
|
||||
"""Return the configured protocol name."""
|
||||
return "wslink" if wslink_enabled else "wu"
|
||||
# Protocols that represent a real, accepted ingress (not health / unknown).
|
||||
_REAL_PROTOCOLS: frozenset[str] = frozenset({"wu", "wslink", "ecowitt"})
|
||||
_LEGACY_PROTOCOLS: frozenset[str] = frozenset({"wu", "wslink"})
|
||||
|
||||
|
||||
def _configured_protocol(config: SWSConfigEntry) -> str:
|
||||
"""Return the primary configured protocol (wu / wslink / ecowitt).
|
||||
|
||||
The legacy PWS/WSLink endpoint takes precedence when enabled; otherwise an
|
||||
Ecowitt-only setup reports "ecowitt". (Legacy and Ecowitt can be enabled at the
|
||||
same time; this just labels the primary protocol for the summary.)
|
||||
"""
|
||||
if checked_or(config.options.get(LEGACY_ENABLED), bool, True):
|
||||
return "wslink" if checked_or(config.options.get(WSLINK), bool, False) else "wu"
|
||||
if checked_or(config.options.get(ECOWITT_ENABLED), bool, False):
|
||||
return "ecowitt"
|
||||
return "wu"
|
||||
|
||||
|
||||
def _protocol_from_path(path: str) -> str:
|
||||
|
|
@ -117,7 +133,7 @@ def _empty_forwarding_state(enabled: bool) -> dict[str, Any]:
|
|||
|
||||
def _default_health_data(config: SWSConfigEntry) -> dict[str, Any]:
|
||||
"""Build the default health/debug payload for this config entry."""
|
||||
configured_protocol = _protocol_name(checked_or(config.options.get(WSLINK), bool, False))
|
||||
configured_protocol = _configured_protocol(config)
|
||||
return {
|
||||
"integration_status": f"online_{configured_protocol}",
|
||||
"configured_protocol": configured_protocol,
|
||||
|
|
@ -211,18 +227,25 @@ class HealthCoordinator(DataUpdateCoordinator):
|
|||
accepted = bool(ingress.get("accepted"))
|
||||
reason = ingress.get("reason")
|
||||
|
||||
if (reason in {"route_disabled", "route_not_registered", "unauthorized"}) or (
|
||||
last_protocol in {"wu", "wslink"} and last_protocol != configured_protocol
|
||||
):
|
||||
# A WU vs WSLink mismatch means the station is misconfigured for the legacy
|
||||
# endpoint. Ecowitt coexists with the legacy endpoint, so it never counts as a
|
||||
# mismatch - it is a valid protocol whenever a payload arrives on its route.
|
||||
legacy_mismatch = (
|
||||
last_protocol in _LEGACY_PROTOCOLS
|
||||
and configured_protocol in _LEGACY_PROTOCOLS
|
||||
and last_protocol != configured_protocol
|
||||
)
|
||||
|
||||
if (reason in {"route_disabled", "route_not_registered", "unauthorized"}) or legacy_mismatch:
|
||||
integration_status = "degraded"
|
||||
elif accepted and last_protocol in {"wu", "wslink"}:
|
||||
elif accepted and last_protocol in _REAL_PROTOCOLS:
|
||||
integration_status = f"online_{last_protocol}"
|
||||
else:
|
||||
integration_status = "online_idle"
|
||||
|
||||
data["integration_status"] = integration_status
|
||||
data["active_protocol"] = (
|
||||
last_protocol if accepted and last_protocol in {"wu", "wslink"} else configured_protocol
|
||||
last_protocol if accepted and last_protocol in _REAL_PROTOCOLS else configured_protocol
|
||||
)
|
||||
|
||||
async def _async_update_data(self) -> dict[str, Any]:
|
||||
|
|
@ -281,7 +304,7 @@ class HealthCoordinator(DataUpdateCoordinator):
|
|||
def update_routing(self, routes: Routes | None) -> None:
|
||||
"""Store the currently enabled routes for diagnostics."""
|
||||
data = deepcopy(self.data)
|
||||
data["configured_protocol"] = _protocol_name(checked_or(self.config.options.get(WSLINK), bool, False))
|
||||
data["configured_protocol"] = _configured_protocol(self.config)
|
||||
if routes is not None:
|
||||
data["routes"] = {
|
||||
"wu_enabled": routes.path_enabled(DEFAULT_URL),
|
||||
|
|
|
|||
|
|
@ -191,6 +191,7 @@
|
|||
"state": {
|
||||
"online_wu": "Online PWS/WU",
|
||||
"online_wslink": "Online WSLink",
|
||||
"online_ecowitt": "Online Ecowitt",
|
||||
"online_idle": "Waiting for data",
|
||||
"degraded": "Degraded",
|
||||
"error": "Error"
|
||||
|
|
@ -200,7 +201,8 @@
|
|||
"name": "Active protocol",
|
||||
"state": {
|
||||
"wu": "PWS/WU",
|
||||
"wslink": "WSLink API"
|
||||
"wslink": "WSLink API",
|
||||
"ecowitt": "Ecowitt"
|
||||
}
|
||||
},
|
||||
"wslink_addon_status": {
|
||||
|
|
@ -235,7 +237,8 @@
|
|||
"name": "Last access protocol",
|
||||
"state": {
|
||||
"wu": "PWS/WU",
|
||||
"wslink": "WSLink API"
|
||||
"wslink": "WSLink API",
|
||||
"ecowitt": "Ecowitt"
|
||||
}
|
||||
},
|
||||
"last_ingress_route_enabled": {
|
||||
|
|
|
|||
|
|
@ -192,6 +192,7 @@
|
|||
"state": {
|
||||
"online_wu": "Online PWS/WU",
|
||||
"online_wslink": "Online WSLink",
|
||||
"online_ecowitt": "Online Ecowitt",
|
||||
"online_idle": "Čekám na data",
|
||||
"degraded": "Degradovaný",
|
||||
"error": "Nefunkční"
|
||||
|
|
@ -201,7 +202,8 @@
|
|||
"name": "Aktivní protokol",
|
||||
"state": {
|
||||
"wu": "PWS/WU",
|
||||
"wslink": "WSLink API"
|
||||
"wslink": "WSLink API",
|
||||
"ecowitt": "Ecowitt"
|
||||
}
|
||||
},
|
||||
"wslink_addon_status": {
|
||||
|
|
@ -236,7 +238,8 @@
|
|||
"name": "Protokol posledního přístupu",
|
||||
"state": {
|
||||
"wu": "PWS/WU",
|
||||
"wslink": "WSLink API"
|
||||
"wslink": "WSLink API",
|
||||
"ecowitt": "Ecowitt"
|
||||
}
|
||||
},
|
||||
"last_ingress_route_enabled": {
|
||||
|
|
|
|||
|
|
@ -191,6 +191,7 @@
|
|||
"state": {
|
||||
"online_wu": "Online PWS/WU",
|
||||
"online_wslink": "Online WSLink",
|
||||
"online_ecowitt": "Online Ecowitt",
|
||||
"online_idle": "Waiting for data",
|
||||
"degraded": "Degraded",
|
||||
"error": "Error"
|
||||
|
|
@ -200,7 +201,8 @@
|
|||
"name": "Active protocol",
|
||||
"state": {
|
||||
"wu": "PWS/WU",
|
||||
"wslink": "WSLink API"
|
||||
"wslink": "WSLink API",
|
||||
"ecowitt": "Ecowitt"
|
||||
}
|
||||
},
|
||||
"wslink_addon_status": {
|
||||
|
|
@ -235,7 +237,8 @@
|
|||
"name": "Last access protocol",
|
||||
"state": {
|
||||
"wu": "PWS/WU",
|
||||
"wslink": "WSLink API"
|
||||
"wslink": "WSLink API",
|
||||
"ecowitt": "Ecowitt"
|
||||
}
|
||||
},
|
||||
"last_ingress_route_enabled": {
|
||||
|
|
|
|||
|
|
@ -32,8 +32,10 @@ from custom_components.sws12500 import health_coordinator as hc, health_sensor a
|
|||
from custom_components.sws12500.const import (
|
||||
DEFAULT_URL,
|
||||
DOMAIN,
|
||||
ECOWITT_ENABLED,
|
||||
ECOWITT_URL_PREFIX,
|
||||
HEALTH_URL,
|
||||
LEGACY_ENABLED,
|
||||
POCASI_CZ_ENABLED,
|
||||
WINDY_ENABLED,
|
||||
WSLINK,
|
||||
|
|
@ -122,9 +124,14 @@ def _patch_network(monkeypatch, session: _FakeSession, ip: str = "1.2.3.4") -> N
|
|||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_protocol_name() -> None:
|
||||
assert hc._protocol_name(True) == "wslink"
|
||||
assert hc._protocol_name(False) == "wu"
|
||||
def test_configured_protocol() -> None:
|
||||
# Legacy enabled (default) -> wu / wslink based on the WSLINK flag.
|
||||
assert hc._configured_protocol(_make_entry()) == "wu"
|
||||
assert hc._configured_protocol(_make_entry({WSLINK: True})) == "wslink"
|
||||
# Ecowitt-only (legacy off, ecowitt on) -> ecowitt.
|
||||
assert hc._configured_protocol(_make_entry({LEGACY_ENABLED: False, ECOWITT_ENABLED: True})) == "ecowitt"
|
||||
# Nothing configured -> wu fallback.
|
||||
assert hc._configured_protocol(_make_entry({LEGACY_ENABLED: False, ECOWITT_ENABLED: False})) == "wu"
|
||||
|
||||
|
||||
def test_protocol_from_path_all_branches() -> None:
|
||||
|
|
@ -168,6 +175,13 @@ def test_default_health_data_wu_default() -> None:
|
|||
assert data["integration_status"] == "online_wu"
|
||||
|
||||
|
||||
def test_default_health_data_ecowitt() -> None:
|
||||
data = hc._default_health_data(_make_entry({LEGACY_ENABLED: False, ECOWITT_ENABLED: True}))
|
||||
assert data["configured_protocol"] == "ecowitt"
|
||||
assert data["active_protocol"] == "ecowitt"
|
||||
assert data["integration_status"] == "online_ecowitt"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# HealthCoordinator construction & persistence
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -262,6 +276,20 @@ def test_refresh_summary_online_idle(hass, entry) -> None:
|
|||
assert data["active_protocol"] == "wu"
|
||||
|
||||
|
||||
def test_refresh_summary_online_ecowitt(hass, entry) -> None:
|
||||
# Ecowitt ingress is a valid protocol: active_protocol must track it (not fall back
|
||||
# to the legacy "wu"), and it must not be flagged as a WU/WSLink mismatch.
|
||||
coordinator = HealthCoordinator(hass, entry)
|
||||
data = hc._default_health_data(entry)
|
||||
data["configured_protocol"] = "wu"
|
||||
data["last_ingress"] = {"protocol": "ecowitt", "accepted": True, "reason": "accepted"}
|
||||
|
||||
coordinator._refresh_summary(data)
|
||||
|
||||
assert data["integration_status"] == "online_ecowitt"
|
||||
assert data["active_protocol"] == "ecowitt"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _async_update_data: offline and online paths
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Reference in New Issue