The integration is moving to a new repository under a new domain. Home Assistant
keys recorder history and long-term statistics on entity_id, not on the domain,
so the move is free provided the registry entries are carried over instead of
recreated. A recreated entity gets a fresh entity_id and its history is orphaned.
predecessor.py does the carry-over with async_update_entity_platform, which
rewrites platform and config_entry_id and leaves entity_id alone. Everything the
user set by hand rides along, because the registry row itself survives: renames,
icons, areas, labels, categories, hidden and disabled state.
Guards, each one covered by a test that fails when it is removed:
- The startup placeholder state (unavailable + ATTR_RESTORED) is cleared first,
because async_update_entity_platform accepts only a missing or unknown state.
- A state that is not a placeholder means an integration is still driving the
entity, so it is refused rather than moved out from under a live platform.
- A unique_id already taken under our own domain is refused. Home Assistant does
not check this itself: the duplicate guard in _async_update_entity only runs
when new_unique_id is passed.
- Devices are repointed before the old entry is removed. A device that loses its
last config entry is deleted, and that takes its entities with it.
- The old config entry is removed only when every entity made it across.
Otherwise it stays, a retry stays possible, and nothing is lost.
Options are inherited in a separate, earlier pass. Adopting the registry alone
is not enough: SENSORS_TO_LOAD decides which entities the platform creates, so
an empty one leaves every adopted entry showing "no longer provided" until a
payload arrives, and derived sensors are never in a payload at all. The merge is
predecessor-fills-gaps, so anything just entered in the config flow wins. The
version 1 spelling of the Pocasi Meteo flag is translated on the way in, since
async_migrate_entry runs against this entry's own version and would never look
at a key copied in afterwards.
The two passes run at different points in async_setup_entry. Options first,
before the coordinator and routes read them, or an inherited WSLINK flag would
leave the entry listening on the wrong endpoint until a reload. Adoption after
the routes are proven but before async_forward_entry_setups, so the one
destructive step cannot run in a setup that then fails, and so our platforms
bind to the adopted registry entries rather than to freshly created ones.
Two problems the migration surfaced, fixed here as well:
- register_path now catches ValueError alongside RuntimeError and raises
ConfigEntryNotReady with a message naming HACS. aiohttp rejects a duplicate
route name with ValueError, and the route names are fixed rather than
domain-scoped, so a user who has not yet removed the old version hit an
unhandled traceback.
- add_new_sensors applies the derived-sensor expansion that async_setup_entry
already applied. Derived sensors are computed, never sent, so discovery can
only offer their inputs; the azimut unlocked by a newly discovered wind
direction went missing until the next restart. Only the sensors this batch
unlocks are added, so nothing is created twice.
PREDECESSOR_DOMAIN equals DOMAIN for now, which short-circuits the whole pass.
It stays correct until the day the domain changes.
Coverage went from 54 of 107 upload parameters to all 107, adding roughly 34
entities. New families: Type5 lightning, Type6 water leak (CH1-7), Type8
particulate matter, Type10 CO2 and Type11 CO, plus the parameters missing from
existing families - absolute pressure, feels-like temperature, the 10-minute
average wind speed, and the per-channel probe type.
Device classes follow Home Assistant's own list (PM25, PM10, AQI, CO2, CO,
DISTANCE, ATMOSPHERIC_PRESSURE, WIND_SPEED, and binary MOISTURE for leaks), so
the readings work with statistics and unit conversion without template helpers.
Batteries were the trap. The API annotates two incompatible conventions -
"(Normal=1, Low battery=0)" and "(0~5) remark: 5 is full" - and mixing them up is
silent, because a level battery read as a flag reports "not low" for every level
above empty. Eight new binary batteries (t5lsbat, t6c1-7bat) and three new level
batteries (t8bat, t10bat, t11bat) are classified from the document, and
tests/test_wslink_api_coverage.py pins each one to the wording it came from.
Water leak and battery use opposite conventions in the same family (leak 1 means
wet, battery 0 means low), so `on_value` moved into the description and one
entity class now serves both instead of two that differ by a comparison.
`t1cn` was in the API but wired to nothing, so a disconnected outdoor probe kept
publishing its last readings. Gating it required fixing the gate first: an absent
connection flag was treated as "disconnected", which would have blanked
temperature, wind and rain outright on any firmware that omits `t1cn`. Absence is
now treated as no information, and only an explicit non-1 drops the readings.
The probe type (`t234cXtp`) decides whether a channel's humidity reading is air
humidity or soil moisture, which are different device classes. It is resolved
once, when the entity is created, because Home Assistant records the device class
in the entity registry; swapping the physical probe means reinstalling.
Two parameters are handled with a documented caveat: `inbat` is the only battery
whose scale the API does not state and stays a 0/1 flag, and `t5lst` has no unit
(the vendor example uses 9999) so it is read as minutes with 9999 meaning
"nothing recorded".
Also replaces the now-stale TODO block in const.py - it listed exactly these
sensors as unimplemented - and documents the complete coverage in the README.
- C1: received_data / received_ecowitt_data return 503 (not 500) when the entry is
mid-reload (runtime_data gone); add_new_sensors / add_new_binary_sensors are
defensive no-ops in that window too.
- C2/C3: Windy and Pocasi reserve their next send window *before* the await, so
concurrent webhooks can no longer both pass the rate-limit check and double-send
(which could falsely trip the auto-disable threshold).
- C5: EcowittBridge.set_add_entities flushes unmapped sensors parsed before the
platform was ready (aioecowitt fires new_sensor_cb only once per key).
- C6: a forwarder auto-disabling itself from the hot path no longer forces a full
config-entry reload (update_listener now skips reload for live-read flags
SENSORS_TO_LOAD / WINDY_ENABLED / POCASI_CZ_ENABLED).
- C7: to_int accepts decimal-formatted integers ("180.0").
- C8: forwarders use dt_util.utcnow() (UTC-aware) instead of naive datetime.now().
Tests updated; 305 passing, 100% coverage; ruff + basedpyright clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The suite predated the coordinator extraction and the typed runtime_data
migration: 5 modules failed to import and 18 tests failed.
- Import IncorrectDataError/WeatherDataUpdateCoordinator from .coordinator.
- Point received_data monkeypatch targets at custom_components.sws12500.coordinator.*
- Stub config entries with async_on_unload + runtime_data (SWSRuntimeData).
- Rewrite test_data, test_sensor_platform and test_integration_lifecycle off the
removed ENTRY_* hass.data keys onto runtime_data (route dispatcher reuse, ecowitt
POST route, no hass.data pop on unload, health first_refresh mocked).
- Update config_flow tests for the user menu (pws step) and the options menu
gaining wslink_port_setup.
- Fix VOCLevel.EXCELENT typo and stale t9 expectations (tuple battery list,
HCHO int coercion, connection-gated subset).
Result: 168 passed, 0 collection errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
integration
- Introduce HealthDiagnosticSensor for device health status reporting
- Add new constants and data keys for health sensor integration
- Wire health_sensor module into sensor platform setup
- Refactor sensor descriptions to improve derived sensor handling
- Implement pytest fixtures and comprehensive tests covering:
- Config flows and options validation
- Data reception and authentication
- Sensor platform setup and dynamic sensor addition
- Push integration with Pocasi.cz and Windy API
- Route dispatching and error handling
- Utilities, conversions, and translation functions