91 lines
3.5 KiB
Python
91 lines
3.5 KiB
Python
"""Protocol conflict handling.
|
|
|
|
The legacy (PWS/WSLink) and Ecowitt endpoints both remap their payloads onto the *same*
|
|
internal sensor keys (`REMAP_ITEMS` / `REMAP_WSLINK_ITEMS` vs `REMAP_ECOWITT_COMPAT`) and
|
|
push them through the same coordinator. Running both at once is therefore unsound:
|
|
|
|
1. Units clash. The entity descriptions are chosen by the WSLink flag alone, so Ecowitt's
|
|
imperial payload (`tempf`, `baromrelin`, `windspeedmph`, ...) is rendered with the
|
|
WSLink set's metric units - 18 of the 26 Ecowitt-mapped keys disagree.
|
|
2. Payloads blank each other. `async_set_updated_data` *replaces* the coordinator payload,
|
|
so every Ecowitt push blanks the keys only the legacy protocol reports (and vice versa),
|
|
flipping those entities to `unknown` on every other push.
|
|
3. One entity, two sources. A single `sensor.outside_temp` would alternate between whatever
|
|
the two endpoints report.
|
|
|
|
The initial config flow already keeps them exclusive (the `pws` step disables Ecowitt, the
|
|
`ecowitt` step disables legacy); only the options flow could turn both on. This module is
|
|
the single place that states the rule, so the config flow, the route wiring and the device
|
|
model all agree on it.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, Final
|
|
|
|
from py_typecheck import checked_or
|
|
|
|
from homeassistant.core import HomeAssistant, callback
|
|
from homeassistant.helpers import issue_registry as ir
|
|
from homeassistant.helpers.issue_registry import IssueSeverity
|
|
|
|
from .const import DOMAIN, ECOWITT_ENABLED, LEGACY_ENABLED
|
|
|
|
if TYPE_CHECKING:
|
|
# Type-only: `data` imports `effective_protocols` from here at runtime, so a
|
|
# runtime import back into `data` would close an import cycle.
|
|
from .data import SWSConfigEntry
|
|
|
|
# Shown in the config/options flow when a submit would enable both protocols.
|
|
ERROR_MUTUALLY_EXCLUSIVE: Final = "protocols_mutually_exclusive"
|
|
|
|
|
|
@callback
|
|
def protocols_conflict(entry: SWSConfigEntry) -> bool:
|
|
"""Return whether this entry has both protocols enabled."""
|
|
return checked_or(entry.options.get(LEGACY_ENABLED), bool, True) and checked_or(
|
|
entry.options.get(ECOWITT_ENABLED), bool, False
|
|
)
|
|
|
|
|
|
@callback
|
|
def effective_protocols(entry: SWSConfigEntry) -> tuple[bool, bool]:
|
|
"""Return the `(legacy, ecowitt)` state to actually wire up.
|
|
|
|
Options written before this rule existed may still have both flags set. Rather than
|
|
ingesting two protocols into one entity namespace, legacy wins - matching the
|
|
precedence already documented in `health_coordinator._configured_protocol` - and
|
|
`update_protocol_conflict_issue` tells the user what happened.
|
|
"""
|
|
legacy = checked_or(entry.options.get(LEGACY_ENABLED), bool, True)
|
|
ecowitt = checked_or(entry.options.get(ECOWITT_ENABLED), bool, False)
|
|
|
|
if legacy and ecowitt:
|
|
return True, False
|
|
return legacy, ecowitt
|
|
|
|
|
|
def _issue_id(entry: SWSConfigEntry) -> str:
|
|
"""Return the Repairs issue id for this config entry."""
|
|
return f"protocol_conflict_{entry.entry_id}"
|
|
|
|
|
|
@callback
|
|
def update_protocol_conflict_issue(hass: HomeAssistant, entry: SWSConfigEntry) -> None:
|
|
"""Create or clear the Repairs issue for a legacy/Ecowitt conflict."""
|
|
|
|
issue_id = _issue_id(entry)
|
|
|
|
if protocols_conflict(entry):
|
|
ir.async_create_issue(
|
|
hass,
|
|
DOMAIN,
|
|
issue_id=issue_id,
|
|
is_persistent=True,
|
|
is_fixable=False,
|
|
severity=IssueSeverity.ERROR,
|
|
translation_key="protocol_conflict",
|
|
)
|
|
else:
|
|
ir.async_delete_issue(hass, DOMAIN, issue_id=issue_id)
|