Commit Graph

56 Commits (0407cb049d599cb6cb4df303916cfe718819430a)

Author SHA1 Message Date
SchiZzA 0400a9cdee feat(migration): adopt predecessor integration entities, keeping history
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.
2026-07-26 22:08:01 +02:00
SchiZzA 935953f20f
fix(document): Drop stale WSLink TODO preamble, refresh binary sensor docs 2026-07-26 16:28:05 +02:00
SchiZzA 3cdaffc81f
fix(review): parse battery flags via to_int, release routes on removal 2026-07-26 15:53:03 +02:00
SchiZzA d2ae26eab5
fix(review): persist WSLink probe types and harden flag parsing 2026-07-26 15:41:30 +02:00
SchiZzA 624de521c4
feat(wslink): implement every sensor the WSLink API v0.6 defines
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.
2026-07-26 15:20:07 +02:00
SchiZzA 56ea41c143
fix(document): Document Pocasi disable notification and timeout retry exclusion 2026-07-26 13:46:00 +02:00
SchiZzA 2c6b698ca9
fix(review): clamp heat index, unify Windy uv, bound forward timeouts 2026-07-26 13:13:05 +02:00
SchiZzA 2b266f33b4
feat(sws12500): forward Ecowitt payloads to Windy and Pocasi Meteo 2026-07-26 11:50:11 +02:00
SchiZzA 5adc684558
refactor(sws12500): centralize battery sensor classification 2026-07-25 23:24:45 +02:00
SchiZzA 1b69737408
no-mistakes(review): Migrate Pocasi option key, fix typos and Windy status 2026-07-25 22:12:55 +02:00
SchiZzA 5e4b02fd6e
fix: address remaining review findings across forwarders and utils
Forwarders (Windy / Pocasi):
- `enabled` becomes a property reading the option live. Toggling it does not
  reload the entry, so the cached copy left the diagnostics sensor reporting a
  stale value indefinitely for a disabled forwarder.
- Reject empty credentials explicitly: an empty string is still a `str`, so
  `checked` alone let an unconfigured forwarder send a request that could only
  ever be refused.
- Use `persistent_notification.async_create` instead of the sync variant.
- Use WINDY_MAX_RETRIES consistently; add POCASI_CZ_MAX_RETRIES.

Utils:
- `voc_level_to_text` / `battery_5step_to_pct` go through `to_int` like every
  other value_fn. A bare `int()` raised on a garbage payload value, which
  `WeatherSensor.native_value` then logged with a full traceback on every push.
  Out-of-range battery steps are clamped to a valid percentage.
- Simplify `check_disabled` (drop the redundant camelCase flag).

Config flow:
- `retain_data` re-reads SENSORS_TO_LOAD at submit time. Auto-discovery appends
  to it from the webhook handler, so writing back the snapshot taken when the
  step opened silently rolled back any sensor discovered meanwhile.

Coordinator:
- Drop the per-push staleness recalculation; the hourly timer already covers it.

Ecowitt:
- Tolerate aioecowitt moving/renaming the internal SENSOR_MAP rather than
  failing the whole module import; unit-variant dedup degrades instead.

Translations:
- Battery state key `unknown` -> `drained`: the entity emits UnitOfBat.UNKNOWN,
  whose value is "drained", so the old key never matched and the raw state
  leaked into the UI. cs.json was already correct.
- Remove the dead `migration` step; `async_step_migration` does not exist.

Also: remove verified-dead constants/helpers and fix ~30 docstring typos.
2026-07-25 21:47:54 +02:00
SchiZzA a9098555ba
fix(Pocasi): Add retry limit and response handling
Add POCASI_CZ_MAX_RETRIES and map HTTP status codes to explicit send
outcomes. Use the status (not the empty body) to classify sends as
"ok", "auth_error", or "unexpected_response". Introduce _disable_pocasi
to persistently turn off resending and record errors. Count unexpected
responses and disable resends after the retry limit; adjust client-error
handling to reuse the same disable logic. Update tests and config flow
expectations to match the new behavior.
2026-07-25 21:47:53 +02:00
SchiZzA a09e2aec9f
fix: Prevent addon probe from failing config entry
Extract WSLink add-on probe and treat expected network errors and
NoURLAvailableError as "offline" instead of raising. Log unexpected
errors and avoid raising ConfigEntryNotReady from diagnostics probes.

Add tests for probe robustness, add additional sensitive purge keys,
and fix config_flow host fallback handling.
2026-07-25 21:47:53 +02:00
SchiZzA 2b4e1a5f8c
fix: address review findings across forwarders and helpers
- 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>
2026-07-25 21:47:51 +02:00
SchiZzA 6363351d9c
fix: coordinator reuse, route dispatcher, binary sensor translations & test suite alignment
- 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
2026-07-25 21:47:50 +02:00
SchiZzA fda7555e81
Updated config flow
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.
2026-07-25 21:47:49 +02:00
SchiZzA 7bba644bec
Add HCHO / VOC air-quality sensors (T9 module)
Support the WSLink t9 air-quality module:
  - t9hcho (formaldehyde, ppb),
  - t9voclv (VOC level 1-5 -> ENUM state)
  - t9bat (0-5 battery -> percentage).

  The t9hcho/t9voclv/t9bat values are dropped when
  t9cn reports the module as disconnected,
  so no empty entities are created.

  - const: HCHO/VOC/T9_BATTERY keys, VOCLevel enum +
  VOC_LEVEL_MAP, BATTERY_NON_BINARY, CONNECTION_GATED_SENSORS,
  REMAP_WSLINK_ITEMS entries
  - utils: voc_level_to_text(),
  battery_5step_to_pct(), connection gating
  - sensors_wslink: HCHO / VOC / T9_BATTERY entity
  descriptions
  - translations (en, cs): hcho, voc (+ states),
  t9_battery
  - tests: tests/conftest.py +
  tests/test_t9_air_quality.py
2026-07-25 21:47:49 +02:00
schizza 9c9bca0c70
Implement Ecowitt protocol support and dynamic binary sensor discovery.
- 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.
2026-07-25 21:47:48 +02:00
SchiZzA dc69d20c3b
Add Ecowitt compatibility and deprecate legacy battery sensors. 2026-07-25 21:47:48 +02:00
SchiZzA 64a5bdb1d7
Add configuration options for WSLink Addon port. 2026-07-25 21:47:47 +02:00
SchiZzA affd26e7a5
Add health diagnostics coordinator and routing snapshot
Track ingress/forwarding status, expose detailed health sensors and translations, and include redacted diagnostics data.
2026-07-25 21:47:47 +02:00
SchiZzA 95b3452318
Update constants to more readable form. 2026-07-25 21:47:47 +02:00
SchiZzA 56950b5e1a
Improve Windy error handling and retry logic 2026-07-25 21:47:47 +02:00
SchiZzA 68555160c7
Add WSLink support for additional sensor channels
- 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
2026-07-25 21:47:46 +02:00
SchiZzA 38854698af
Align Windy resend with Stations API response handling
- 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
2026-07-25 21:47:46 +02:00
SchiZzA 0060369d8a
Add health diagnostic sensor and extensive tests for sws12500
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
2026-07-25 21:45:08 +02:00
SchiZzA 144108fba1
Refactor SWS12500 integration for push-based updates
- Add detailed architecture overview in __init__.py
- Introduce shared runtime keys in data.py to avoid key conflicts
- Implement dual route dispatcher for webhook endpoint selection
- Enhance sensor platform for dynamic sensor addition without reloads
- Rename battery level "unknown" to "drained" with updated icons
- Improve utils.py with centralized helpers for remapping and discovery
- Update translations and strings for consistency
2026-07-25 21:45:07 +02:00
SchiZzA 35fd0585f9
Config_flow for Ecowitt 2026-07-25 21:45:06 +02:00
SchiZzA aaaab107de
Refactor config_flow, add support for Ecowitt configuration 2026-07-25 21:45:06 +02:00
SchiZzA 93fd85a487
Add HCHO / VOC air-quality sensors (T9 module)
Support the WSLink t9 air-quality module:
  - t9hcho (formaldehyde, ppb),
  - t9voclv (VOC level 1-5 -> ENUM state)
  - t9bat (0-5 battery -> percentage).

  The t9hcho/t9voclv/t9bat values are dropped when
  t9cn reports the module as disconnected,
  so no empty entities are created.

  - const: HCHO/VOC/T9_BATTERY keys, VOCLevel enum +
  VOC_LEVEL_MAP, BATTERY_NON_BINARY, CONNECTION_GATED_SENSORS,
  REMAP_WSLINK_ITEMS entries
  - utils: voc_level_to_text(),
  battery_5step_to_pct(), connection gating
  - sensors_wslink: HCHO / VOC / T9_BATTERY entity
  descriptions
  - translations (en, cs): hcho, voc (+ states),
  t9_battery
  - tests: tests/conftest.py +
  tests/test_t9_air_quality.py
2026-05-11 23:55:36 +02:00
SchiZzA cf1026f2b9
Update Windy integration to use station ID and password authentication
- Replace API key with station ID and password for Windy data push
- Change Windy update URL and add authorization header
- Update config flow and constants to support new credentials
- Adjust data mapping for WSLink compatibility
- Change WSLink endpoint HTTP method to POST
2026-01-30 17:14:21 +01:00
SchiZzA 7950e1be46
Add Pocasi CZ push to server support
Added `pocasi_cz.py` component to handle resending data to Pocasi CZ
server.
2025-11-16 18:37:01 +01:00
SchiZzA 0d47e14834
Add Počasí Meteo CZ integration to options flow and constants
Introduce new config step for Počasí Meteo CZ with validation and UI
schema. Define related constants and translation keys for setting up
data forwarding.
2025-11-16 15:37:13 +01:00
SchiZzA 67d8acf9cc
Add constants for `pocasimeteo_cz`
const `POCASI_CZ_URL` added to constants. cont `POCASI_CZ_SEDN_INTERVAL`
and POCASI_CZ_SEND_MINIMUM are added to contants.
2025-11-16 13:13:41 +01:00
schizza 06a8a7ff1b
Adds WBGT temperature sensor via WSLink
Adds the Wet Bulb Globe Temperature (WBGT) sensor to the integration, pulling data via the WSLink API.

Corrects the state class for rain and wind direction.
2025-10-26 21:39:31 +01:00
schizza 80909e88c0
Adds indoor and channel 2 battery sensors
Adds support for displaying indoor console and channel 2 battery levels as sensors.

Updates sensor logic to use a common list to determine icon representation

Fixes #74
2025-10-26 20:59:57 +01:00
schizza 99fd6d266c Adds outside battery sensor
Adds an outside battery sensor to the integration, providing information about the battery level of the outdoor sensor.

This includes:
- Mapping the `t1bat` WSLink item to the `OUTSIDE_BATTERY` sensor.
- Implementing logic to convert the battery level to a human-readable text representation and a corresponding icon.
- Updates precipitation to intensity and fixes data type of battery level
2025-08-28 10:41:44 +02:00
schizza de013891c0 Adds missing constant and improves readability
Adds OUTSIDE_BATTERY to the DISABLED_BY_DEFAULT list.
Improves readability by formatting long strings with parenthesis.
2025-08-28 10:41:44 +02:00
schizza a68a4c929a Update const.py 2025-08-28 10:41:44 +02:00
Ferron Nijland a07af5a4fd Add outside battery sensor and related translations 2025-08-28 10:41:44 +02:00
schizza b1cec2f38f
Adds CH3 temperature and humidity sensors
Enables CH3 temperature and humidity sensors for WSLink devices.
2025-07-04 19:27:45 +02:00
Lukas Svoboda fdd4cacddf
Merge branch 'main' into add_sensors_for_wslink 2025-04-04 13:11:45 +02:00
schizza 601d1f3984 Adds rain sensors for different time periods for WSLink
Adds hourly, weekly, monthly, and yearly rain sensors to the integration for WSLink connection.

The units of measurement are corrected for the rain sensor, now displaying millimeters per hour as the station is sending data in mm/h for 'rain' sensor. And cumulative precipitation is in millimeters.

Also includes translations for the new sensors.
2025-04-04 13:10:10 +02:00
Lukas Svoboda 3d112757d0
Update const.py
Update path to database as it differs in development installation.
2025-03-15 15:44:57 +01:00
schizza 8e97fc7404 Different values displayed on station/cloud and HA - Total rainfall
Fixes #62

Fixes rainfall unit inconsistency

Updates daily rain sensor to use consistent measurement units and corrects device class and suggested unit.

Fixes #62

Adds database path constant and data migration function

Defines DATABASE_PATH constant for database file location
Introduces migrate_data function to update unit of measurement in long statistics.

Adds sensor migration feature

Introduces a migration step for sensor statistics
Updates schema and translations to support migration
Fixes data migration from mm/d to mm
2025-03-15 15:10:49 +01:00
schizza 37c3e2e77f Adds sensors for WSLink and improves data handling
Includes new sensor connection status constants and mappings
Updates WeatherSensor to handle null data values
Switches temperature unit to Celsius for WSLink sensors
Refines heat and chill index calculations with unit conversions
2025-03-10 08:42:17 +01:00
schizza 25bb26bb4d WSLink support in beta
Support for firmware 3.0 and WSLink connections.
As for now the 3.0 firmware sending data only on SSL connections.
2025-02-12 13:13:46 +01:00
schizza 66a9993dfc Support for CH3 and CH4 2024-07-15 22:14:20 -05:00
schizza e18762579b Wind chill index
* Add sensor for wind chill index
2024-05-09 14:17:01 +02:00
schizza 8b0cc2d25e Add Heat Index
* Heat index sensor
* Heat index computation is added
2024-05-09 13:26:01 +02:00