no-mistakes(document): Update README for Ecowitt, Windy and diagnostics changes

ecowitt_support
SchiZzA 2026-07-25 22:37:28 +02:00
parent 1b69737408
commit 6914d87f5c
No known key found for this signature in database
2 changed files with 173 additions and 21 deletions

109
README.md
View File

@ -5,24 +5,26 @@
This integration will listen for data from your station and passes them to respective sensors. It also provides the ability to push data to `Windy API` or `Pocasi Meteo`.
### Ecowitt support is coming in the next major release
### Ecowitt support
As of April 11, 2026, Ecowitt stations are supported in the pre-release version
Ecowitt stations are supported as of the pre-release version
[v2.0.0pre1](https://github.com/schizza/SWS-12500-custom-component/releases/tag/v2.0.0pre1).
You can download this pre-release in HACS under `target version`, where you can pick the exact
version of the integration. Please be aware that this pre-release is really for testing
purposes only.
See [Ecowitt stations](#ecowitt-stations) for the setup steps.
---
### Integration rename is planned for the next major release
The current name no longer reflects what the integration does. It was initially developed
primarily for the SWS 12500 station, but it already supports other weather stations as well
(Bresser, Garni and others), and Ecowitt support is on the way — so the current name has
become misleading. The transition will be announced via an update, and I'm also planning to
offer a full data migration from the existing integration to the new one, so you won't lose
any of your historical data.
(Bresser, Garni, Ecowitt and others) — so the current name has become misleading. The
transition will be announced via an update, and I'm also planning to offer a full data
migration from the existing integration to the new one, so you won't lose any of your
historical data.
- The transition date hasn't been set yet, but it's currently expected to happen within the
next ~23 months. At the moment, I'm working on a full refactor and general code cleanup.
@ -95,6 +97,8 @@ Web server repo is reachable here: <https://github.com/schizza/test-station-serv
- SWS 10500 (newer firmware revisions are also supported via the [WSLink SSL proxy add-on](https://github.com/schizza/wslink-addon))
- Bresser stations that support custom server upload — [this model is known to work](https://www.bresser.com/p/bresser-wi-fi-clearview-weather-station-with-7-in-1-sensor-7002586), for example
- Garni stations with WSLink support or custom server support
- Ecowitt stations that support the `Customized` / `Ecowitt` upload protocol — see
[Ecowitt stations](#ecowitt-stations)
- and a bunch of other models that aren't listed here are also supported
## Supported sensors
@ -107,16 +111,31 @@ Beyond the standard set (outdoor / indoor temperature and humidity, barometric p
wind speed / direction / gust, rain rate and daily / weekly / monthly / yearly totals, dew
point, UV index, solar irradiance) the WSLink protocol also exposes:
- additional channels **CH2** and **CH3** for temperature and humidity
- additional channels **CH2** **CH8** for temperature and humidity
- **WBGT** index, **heat index**, **wind chill**
- sensor battery levels (outdoor, indoor, CH2)
- **Formaldehyde (HCHO)** in ppb and **VOC level** as a 15 air-quality index
(1 = worst, 5 = best) from the WH46 / 7-in-1 air-quality combo sensor
- sensor battery state for the outdoor, indoor and **CH2** **CH8** probes, exposed as
binary `battery` sensors (`normal` / `low`) — these used to be regular sensors, so on an
upgraded install the old entities are left behind; a `Repairs` issue lists them and
tells you how to remove them
- **Formaldehyde (HCHO)** in ppb and **VOC level** from the WH46 / 7-in-1 air-quality
combo sensor, reported as a named air-quality level (`unhealthy`, `poor`, `moderate`,
`good`, `excellent` — the station's 15 index, where 1 is worst)
- **HCHO/VOC sensor battery** reported as a percentage
HCHO / VOC entities are only created when the station reports the air-quality module as
connected (`t9cn = 1`), so they don't clutter the device when no such sensor is attached.
Ecowitt stations map onto the same standard set and additionally report whatever their own
firmware sends — absolute pressure, event / hourly / weekly / monthly / yearly / total /
24h rain, feels-like temperature, indoor dew point and CO₂ readings among them. Only the
fields your station actually sends are created.
The integration also creates diagnostic entities describing its own state — the active
protocol, whether each endpoint is registered, details of the last received payload, the
status of the WSLink proxy add-on, and the state of the Windy / Pocasi Meteo forwarders.
They are grouped under `Diagnostic` on the device page, and the same data is included in
the config entry's `Download diagnostics` file.
## Installation
### For stations that send data through WSLink API
@ -159,7 +178,11 @@ For manual installation you must have access to your Home Assistant's `/config`
6. Save your configuration.
![station_setup](README/station_hint.png)
Once integration is added to Home Assistant, configuration dialog will ask you for `API_ID` and `API_KEY` as you set them in your station:
Once the integration is added to Home Assistant, the first dialog asks which type of
station you have. Choose `PWS/WSLink (Sencor, Garni, Bresser, other - Weather Underground
compatible)` — for Ecowitt stations see [Ecowitt stations](#ecowitt-stations) instead.
The next dialog asks for `API_ID` and `API_KEY` as you set them in your station:
```plain
API_ID: ID in station's config
@ -171,12 +194,38 @@ API_KEY: PASSWORD in station's config
If you change `API ID` or `API KEY` in the station, you have to reconfigure integration to accept data from your station.
- In `Settings` -> `Devices & services` find SWS12500 and click `Configure`.
- In dialog box choose `Basic - Configure credentials`
- In dialog box choose `Basic - configure credentials for Weather Station`
![reconfigure dialog](README/reconfigure.png)
As soon as the integration is added into Home Assistant it will listen for incoming data from the station and starts to fill sensors as soon as data will first arrive.
## Ecowitt stations
Ecowitt stations do not use the `API ID` / `API KEY` pair — they are authorized by a
secret webhook ID that the integration generates for you.
- When adding the integration, pick `Ecowitt` in the station-type dialog. For an entry
that already exists, go to `Settings` -> `Devices & services` -> SWS12500 ->
`Configure` and choose `Ecowitt configuration`.
- The dialog shows the endpoint your station has to post to and pre-fills a freshly
generated webhook ID:
```plain
<your Home Assistant host>:<port>/weatherhub/<webhook_id>
```
- Enter that path as the custom-server path in the Ecowitt app (`WS View` / `Customized`
upload), keep the protocol on `Ecowitt`, and tick `Enable Ecowitt station data`.
> [!IMPORTANT]
> The legacy PWS/WSLink endpoint and the Ecowitt endpoint cannot be enabled at the same
> time — both feed the same sensor entities, which would mix up units and blank out
> readings. The config flow refuses to enable one while the other is on. Picking
> `Ecowitt` when you first add the integration turns the legacy endpoint off for you; to
> switch back, disable Ecowitt first and then tick `Enable PWS/WSLink endpoint` under
> `Basic - configure credentials for Weather Station`.
## Upgrading from PWS to WSLink
If you upgrade your station — which was previously sending data in the PWS protocol — to a
@ -190,19 +239,26 @@ measurement scale.
## Resending data to Windy API
- First of all you need to create account at [Windy stations](https://stations.windy.com).
- Once you have an account created, copy your Windy API Key.
![windy api key](README/windy_key.png)
- Once you have an account created, open
[your stations](https://stations.windy.com/stations) and copy the **station ID** and the
**station password** of the station you want to feed.
![windy station credentials](README/windy_key.png)
- In `Settings` -> `Devices & services` find SWS12500 and click `Configure`.
- In dialog box choose `Windy configuration`.
![config dialog](README/cfg.png)
- Fill in `Key` you were provided at `Windy stations`.
- Tick `Enable` checkbox.
- Fill in `Station ID obtained form Windy` and `Station password obtained from Windy`.
Both are required — enabling the forwarder with either one empty is rejected.
- Tick `Enable resending data to Windy`.
![enable windy](README/windy_cfg.png)
- You are done.
If Windy rejects the data three times in a row, the integration disables resending on its
own, writes the reason to the log and raises a persistent notification — fix the
credentials and tick `Enable resending data to Windy` again.
## Resending data to Pocasi Meteo
- If you are willing to use [Pocasi Meteo Application](https://pocasimeteo.cz) you can enable resending your data to their servers
@ -210,10 +266,15 @@ measurement scale.
- In `Settings` -> `Devices & services` find SWS12500 and click `Configure`.
- In dialog box choose `Pocasi Meteo configuration`.
- Fill in `ID` and `KEY` you were provided at `Pocasi Meteo`.
- Tick `Enable` checkbox.
- Optionally adjust `Resend interval in seconds` (minimum 12s, default 30s).
- Tick `Enable resending data to Pocasi Meteo`.
- You are done.
As with Windy, three unexpected responses in a row switch resending off automatically and
log the reason. The `Forwarding to Počasí Meteo` and `Forwarding status to Počasí Meteo`
diagnostic sensors show the current state.
## WSLink notes
If your station sends WSLink data over SSL (see the
@ -239,3 +300,17 @@ WSLink proxy add-on is listening on port 4443
- Your station will send data to the SSL proxy and the add-on will handle the rest.
_Most stations do not care about self-signed certificates on the server side._
### Add-on health monitoring
The integration probes the add-on's health endpoint so the diagnostic entities can report
whether the proxy is online, which version it runs and which ports it uses. It has to know
the port to reach it on:
- In `Settings` -> `Devices & services` find SWS12500, click `Configure` and choose
`WSLink add-on port`.
- Set it to the same port the add-on listens on — the one you pointed the station at
above. The integration defaults to `443`, so if your add-on listens elsewhere (`4443`
in the example above), change it here.
This setting only affects the health probe; incoming station data is unaffected by it.

View File

@ -1,6 +1,83 @@
# Test file for strings.json module
"""Tests for the user-facing strings shipped with the integration.
def test_strings_functionality():
# Add your test cases here
pass
`strings.json` is the source of truth; `translations/en.json` and `translations/cs.json`
are what the frontend actually loads. A key that exists in one file and not in another
(or a state key that no entity ever emits) is invisible in code review and only shows up
as an untranslated raw value in the UI.
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
import pytest
from custom_components.sws12500.sensors_weather import SENSOR_TYPES_WEATHER_API
from custom_components.sws12500.sensors_wslink import SENSOR_TYPES_WSLINK
from homeassistant.components.sensor import SensorDeviceClass
COMPONENT_DIR = Path(__file__).resolve().parents[1] / "custom_components" / "sws12500"
STRINGS_FILE = COMPONENT_DIR / "strings.json"
TRANSLATION_FILES = (
COMPONENT_DIR / "translations" / "en.json",
COMPONENT_DIR / "translations" / "cs.json",
)
def _load(path: Path) -> dict[str, Any]:
return json.loads(path.read_text(encoding="utf-8"))
def _flatten(data: dict[str, Any], prefix: str = "") -> set[str]:
"""Return every leaf path in a translation document."""
keys: set[str] = set()
for key, value in data.items():
path = f"{prefix}.{key}" if prefix else key
if isinstance(value, dict):
keys |= _flatten(value, path)
else:
keys.add(path)
return keys
@pytest.mark.parametrize("translation_file", TRANSLATION_FILES, ids=lambda p: p.name)
def test_translation_keys_match_strings_json(translation_file: Path) -> None:
"""Every shipped language must carry exactly the keys defined in strings.json."""
expected = _flatten(_load(STRINGS_FILE))
actual = _flatten(_load(translation_file))
assert not expected - actual, f"{translation_file.name} is missing keys"
assert not actual - expected, f"{translation_file.name} has stale keys"
def _enum_descriptions() -> list[Any]:
"""Sensor descriptions that report a fixed set of states to the frontend."""
seen: dict[str, Any] = {}
for description in (*SENSOR_TYPES_WEATHER_API, *SENSOR_TYPES_WSLINK):
if description.device_class is not SensorDeviceClass.ENUM or not description.options:
continue
seen.setdefault(description.translation_key or description.key, description)
return list(seen.values())
@pytest.mark.parametrize(
("path", "description"),
[(path, description) for path in (STRINGS_FILE, *TRANSLATION_FILES) for description in _enum_descriptions()],
ids=lambda value: value.name if isinstance(value, Path) else (value.translation_key or value.key),
)
def test_enum_states_are_translated(path: Path, description: Any) -> None:
"""Each enum state an entity can emit needs a translation - and no extra ones.
The battery sensors emit `UnitOfBat.UNKNOWN`, whose *value* is "drained"; a state
block keyed on the member name ("unknown") never matches, so the frontend falls back
to showing the raw state.
"""
translation_key = description.translation_key or description.key
states = _load(path)["entity"]["sensor"][translation_key]["state"]
assert set(states) == {str(option) for option in description.options}, (
f"{path.name}: state keys for '{translation_key}' do not match the emitted values"
)