Two long-standing rough edges with Ecowitt running alongside the SWS device:
- Dedup (#1): aioecowitt emits both metric and imperial sensors per quantity
(tempc/tempf, rainratemm/rainratein, ...). Only the imperial twin is mapped to the
SWS sensors, so the metric twin (and other unmapped extras) showed up as duplicate
native entities. _on_new_sensor now skips a key whose unit-variant twin is already
mapped or already created (twin groups derived from aioecowitt's SENSOR_MAP). HA
converts units via device_class, so a single variant suffices.
- Translation (#2): native sensors used the raw English aioecowitt name. Common
single-instance families (rain rate / hourly / event / 24h / weekly / monthly /
yearly / total rain, absolute pressure, feels-like, indoor dew point, CO2) now use
curated translation_keys (added to strings/en/cs); long-tail / multi-channel
sensors keep the English name fallback.
- Also add the imperial RAIN_RATE_INCHES / RAIN_COUNT_INCHES to STYPE_TO_HA so those
native rain sensors get a device class and unit.
Device unification (one "WeatherHub" device + station-type sensor) stays for the
rename round, as agreed.
314 passing, 100% coverage; ruff + basedpyright clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- S8: received_data now rejects empty configured credentials (IncorrectDataError)
and present-but-empty incoming credentials (HTTPUnauthorized) instead of relying
solely on the config flow. Credential validation extracted into
_validate_credentials() (also resolves the C901 complexity warning and unifies
the WU/WSLink branches).
- S6: cap auto-created native Ecowitt entities (MAX_NATIVE_ECOWITT_SENSORS) to bound
entity-registry growth from a fabricated multi-key payload.
- S5: store only the exception class name in Windy/Pocasi last_error (surfaced via
entity attributes; str(ex) could embed the request URL).
- S7: verified safe - the native-sensor INFO log is parametrized (%s), so no
format-string injection; left as-is.
308 passing, 100% coverage; ruff + basedpyright clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 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>
- wind_dir_to_text: treat 0/missing direction as None (calm) so a missing
wind direction no longer renders as North; azimut lambdas pass None too.
- windy: log response status (was logging an un-awaited coroutine method);
count "unexpected response" toward the disable threshold even when logging
is off.
- pocasi: disable threshold >3 -> >=3 to match Windy's 3-strike behavior.
- ecowitt: always attach device_info to native sensors, including unknown
sensor types (previously left orphaned without a device).
- const: stop remapping WSLink connection flags (t1cn/t234cXcn) into the
payload; they had no entity and leaked ghost *_connection keys into data
and persisted SENSORS_TO_LOAD. Gating uses the raw keys and is unaffected.
- config_flow: fix WSLink port fallback typo 433 -> 443; drop mutable default
argument on async_step_init.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Reuse existing `WeatherDataUpdateCoordinator` instance across reloads instead of
creating a new one; routes keep pointing to the same coordinator, so entities
stay subscribed and updates never silently drop
- `update_listener` now skips full reload when only `SENSORS_TO_LOAD` changes
(auto-discovery); avoids the "frozen UI" window caused by temporary platform unload
- Replaced direct `route._handler` mutation with a proper `Routes` dispatcher;
all aiohttp routes are registered once and an internal dispatcher decides which
handler is active — safe to switch at runtime without touching the router
- Fixed `self.config_entry` / `self.config` inconsistency in coordinator
- `BatteryBinarySensor` now carries correct `device_info` (same identifiers as
`WeatherSensor`) → single device card in UI instead of two
- Translations for binary sensors must live under `entity.binary_sensor.*`,
not `entity.sensor.*`
- Fixed all tests broken by new `register_path(hass, coordinator, coordinator_h, entry)` signature
- Updated `_RouterStub` to accept `name=` kwargs and return objects with `.method`
- Added `async post()` to `_RequestStub` stubs
- Aligned `Routes` tests with new `method:path` dispatch key format and `show_enabled()` output
- Replaced `WindyApiKeyError` with `WindyPasswordMissing` to match actual exceptions
- Updated `_FakeResponse` to use HTTP status codes instead of response text strings
- Patched `persistent_notification.create` in Windy push tests to avoid `SimpleNamespace` errors
- Integrate aioecowitt to parse incoming weather station payloads and map sensors to the SWS pipeline.
- Add a new webhook endpoint at /weatherhub/{webhook_id} with support for dynamic route resolution.
- Expand battery binary sensor definitions and implement logic for dynamic entity creation.
- Bump version to 2.0.0-pre1.