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>
API_KEY, WINDY_STATION_PWD and POCASI_CZ_API_KEY now use a password TextSelector so
the values are masked in the UI. Field keys are unchanged, so stored options and the
flow contract are unaffected.
308 passing, 100% coverage; ruff + basedpyright clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Every entity translation_key used in code now resolves in cs.json (Czech users with
channels 5-8 or the deprecated enum battery sensors no longer see raw keys).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- manifest: add single_config_entry, integration_type "device", loggers
["aioecowitt"]; drop empty homekit/ssdp/zeroconf placeholders.
- Q10: raise ConfigEntryNotReady (not PlatformNotReady) when route registration
fails in async_setup_entry; drop the now-unused import.
- Q11: delete the ~105-line commented-out sqlite3 statistics-migration block in
utils.py (would have been blocking I/O in the event loop) and its dead import.
Deferred (with reason): quality_scale (needs a rule audit + quality_scale.yaml),
device-identifier 2-tuple and suggested_entity_id (entity-naming/identity changes,
to land with the planned integration rename), CoordinatorEntity generics in
sensor.py (would introduce a circular import - intentionally untyped).
Note: the health-probe protocol gate was reverted - the proxy add-on can front
WU/WSLink/Ecowitt, so polling must stay unconditional.
308 passing, 100% coverage; ruff 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>
Security (from the deep audit):
- S1: health "integration_health" sensor no longer dumps the full snapshot in
extra_state_attributes (readable by any HA user). public_health_snapshot()
strips internal network details (source IP, internal URLs, raw add-on status);
full data stays on the authenticated endpoint and admin-only diagnostics.
- S2: anonymize() now masks the Ecowitt passkey/PASSKEY.
- S3: Windy station id masked before logging the dataset.
- S4: diagnostics redacts internal network fields (source IP, URLs, raw_status).
i18n (Q1/Q2):
- Sync strings.json + en.json with the actual flow: add wslink_port_setup step,
the ecowitt/pocasi/wslink_port_setup menu entries, the pocasi_* and
windy_key_required error keys, the stale_sensors_detected issue; fix wrong data
keys (WSLINK->wslink + legacy_enabled, POCASI_CZ_SEND_INTERVAL->POCASI_SEND_INTERVAL,
pocasi_enabled_checkbox->pocasi_enabled_chcekbox); translate the English ecowitt
options step (was Czech); de-duplicate the legacy-battery issue description.
- Q2: rename wbgt_index -> wbgt_temp to match the entity translation_key.
- cs.json: add windy_key_required and fix the pocasi data/data_description keys.
Tests: 300 passing, 100% coverage maintained; basedpyright clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The /station/health route is registered directly on the aiohttp router, so HA's
auth middleware only flags requests without blocking them - the endpoint was
reachable unauthenticated and returned the full health snapshot.
- health_status now requires HA authentication (bearer token or signed request)
via KEY_AUTHENTICATED and returns 401 otherwise.
- Mask the Ecowitt webhook id (the endpoint's only credential) in last_ingress
paths via _sanitize_path, so it never enters the snapshot exposed by the health
endpoint or the diagnostics download.
- Redact ECOWITT_WEBHOOK_ID in diagnostics.
- Compare the Ecowitt webhook id in constant time (hmac.compare_digest), matching
the WU/WSLink credential checks.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The azimut value_from_data_fn now passes dir.get(WIND_DIR) (typed Any | None),
which tripped basedpyright reportArgumentType against the float-only signature.
The body already coerces via to_float() and handles None, so widen the
annotation to match the real inputs (raw str payload, float, or None).
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>
Pass config_entry explicitly to super().__init__() in both
WeatherDataUpdateCoordinator and HealthCoordinator instead of relying on the
implicit ContextVar (deprecated, breaks in HA 2026.8 for core and was breaking
async_config_entry_first_refresh outside the setup context).
Also resolve each discovered sensor's display name once in received_data
(the previous comprehension awaited translations() twice per key).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
After a config reload a new HealthCoordinator is created, but the sticky
/station/health route kept calling the previous (stale) instance. Add
Routes.rebind_handler() and call it from async_setup_entry's reload branch
so the endpoint always serves the current coordinator.
Also make _resolve_route() tolerant of requests without match_info/route/
resource instead of raising AttributeError.
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
Updated config flow's initial setup.
Ecowitt stations does not require `API_ID` and `API_KEY`, so we need to
separate Ecowitt setup from PWS/WSLink setup.
Adds the SWSRuntimeData dataclass and SWSConfigEntry type alias so
per-entry state can move from loosely-typed hass.data[DOMAIN][entry_id]
dicts to HA 2025+ typed entry.runtime_data. The legacy ENTRY_* string
keys are kept in place for now so callers can be migrated incrementally.
This commit is not final update. Just preparing for future complete refactor.
- 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.
- Introduce `to_int`/`to_float` helpers to safely handle None/blank payload values.
- Use the converters across weather/wslink sensor descriptions and simplify wind direction handling.
- Add low-battery binary sensor entities and definitions.
Include HTTP method in route keys and dispatch, and fix
Routes.show_enabled.
Update register_path to accept a HealthCoordinator and adjust router
stubs in tests. Update WindyPush tests to use response objects
(status/text)
and adapt related exception/notification expectations.
Introduce HealthSensorEntityDescription and a tuple of sensor
descriptions for integration status, source IP, base URL and addon
response. Instantiate one HealthDiagnosticSensor per description in
async_setup_entry. Update HealthDiagnosticSensor to accept a
description, derive unique_id from description.key and add a cached
device_info returning a SERVICE-type device. Adjust imports.
Replace manual isinstance checks and casts with py_typecheck.checked()
to validate hass and entry data and return early on errors. Simplify
add_new_sensors by unwrapping values, renaming vars, and passing the
coordinator to WeatherSensor
Use checked and checked_or to validate option and hass.data types,
remove unsafe typing.cast calls, simplify coordinator and entry_data
handling, and cache boolean option flags for Windy and Pocasí checks
- Extend constants and WSLink key remapping for channels 3–8 (temp, humidity, battery and connection)
- Add new WSLink sensor entity descriptions for the extra channel readings
- Update English translations for the newly added channel sensors and battery states
- Add WINDY_MAX_RETRIES constant and use it consistently when deciding to disable resending
- Refactor Windy response verification to rely on HTTP status codes per stations.windy.com API
- Improve error handling for missing password, duplicate payloads and rate limiting
- Enhance retry logging and disable Windy resend via persistent notification on repeated failures
- Register aiohttp webhook routes once and switch the active dispatcher handler on option changes
- Make the internal route registry method-aware (GET/POST) and improve enabled-route logging
- Fix OptionsFlow initialization by passing the config entry and using safe defaults for credentials
- Harden Windy resend by validating credentials early, auto-disabling the feature on invalid responses, and notifying the user
- Update translations for Windy credential validation errors
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