Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,63 @@

All notable changes to Weather Station Core are documented here.

## [2.1.0] - 2026-06-13

### Added

- **Nowcast local blending.** The precipitation nowcast now blends live local gauge data into the first two 15-minute NWP buckets (70% local / 30% NWP for t+0–15 min; 50/50 at t+15–30 min) when the local rain rate sensor confirms active precipitation. A new diagnostic sensor `sensor.ws_nowcast_confidence` (`high` / `medium` / `low`) reflects how well the local gauge and NWP grid agree. Sensor is gated behind the Precipitation Nowcast feature toggle.

- **Soil sensor support.** New optional feature group (disabled by default) that reads soil moisture (volumetric %, auto-normalised from 0–1 or 0–100 formats) and soil temperature sensors. Derives:
- `sensor.ws_soil_moisture` — volumetric soil moisture %
- `sensor.ws_soil_temperature` — soil temperature °C
- `sensor.ws_soil_moisture_deficit` — field-capacity deficit (40% FC minus current)
- `sensor.ws_irrigation_need` — text label: None / Low / Moderate / High / Critical
- `sensor.ws_irrigation_need_score` — 0–100 numeric irrigation demand score based on soil deficit and net ET₀ demand
Enable in Configure → Features → Soil sensors.

- **Seasonal anomaly sensors (90-day climatology).** The rolling climatology buffer has been extended from 30 to 90 days. Two new diagnostic sensors compare the most recent 30-day period against the 90-day seasonal baseline:
- `sensor.ws_temp_anomaly_90d` — temperature anomaly (°C above/below the 90-day mean)
- `sensor.ws_rain_anomaly_90d` — precipitation anomaly (mm/d above/below the 90-day mean)
Both sensors activate after 60 days of data and are gated behind the Diagnostics feature toggle.

- **Individual forecast skill sensors.** Three new diagnostic entities expose the per-source Brier score and learned blend weight that the self-learning rain probability system maintains internally:
- `sensor.ws_forecast_brier_local` — Brier score for the local sensor model (lower is better)
- `sensor.ws_forecast_brier_api` — Brier score for the NWP API model
- `sensor.ws_forecast_blend_weight_local` — current learned weight of the local model (%)
All three are gated behind the Diagnostics feature toggle and require ≥10 verified forecast outcomes.

- **Alert hysteresis / debounce.** Wind, rain, and freeze alert states now require a condition to be sustained for 2 consecutive update ticks before activating, and must be absent for 3 ticks before clearing. Eliminates chatty automations caused by sensor noise around threshold boundaries. Configurable via `ALERT_DEBOUNCE_ON_TICKS` and `ALERT_DEBOUNCE_OFF_TICKS` in `const.py`.

- **Current conditions text summary.** New always-on sensor `sensor.ws_conditions_summary` provides a human-readable description of current conditions (e.g. "Warm · 68% RH · Light rain · SE 12 km/h"). Useful for notification templates, TTS announcements, and Assist voice responses. Attributes include temperature, feels-like, humidity, rain rate, wind, and condition label.

- **HA Repairs issues for sensor faults.** Two new issue types appear in Settings → Repairs when sensor hardware problems are detected:
- `stuck_sensors` — fires when one or more sensors report an unchanged value for an extended period (typical of frozen/failed sensor hardware)
- `sensor_drift_detected` — fires when a sensor's value diverges from its historical pattern beyond expected bounds (e.g. mounting shift, electronics degradation)
Both issues are cleared automatically when the sensor recovers or when Suppress Notifications is enabled.

- **Five automation blueprints.** Import-ready blueprints in `blueprints/automation/ws_core/`:
- `heat_alert.yaml` — notifies when feels-like exceeds a threshold for ≥5 minutes
- `freeze_alert.yaml` — notifies when temperature drops to or below a freeze threshold; optionally shuts off an irrigation switch
- `rain_start.yaml` — triggers on rain start and/or stop; configurable rain rate threshold, optional actions for each event
- `high_wind.yaml` — notifies on gust exceedances; optionally retracts covers/awnings
- `poor_aqi.yaml` — notifies when AQI exceeds threshold for ≥10 minutes; optionally closes windows and activates air purifiers

- **Calibration service range validation.** The `ws_core.apply_calibration` service now enforces the same bounds used by the UI number entities via voluptuous `Range()` validators (temp ±10 °C, humidity ±20%, pressure ±10 hPa, wind ±5 m/s). A HA Repairs advisory (`large_calibration_offset`) fires when any applied offset exceeds 50% of its maximum, suggesting possible sensor hardware failure rather than calibration drift.

- **`WsData` typed coordinator model.** A new `models.py` module introduces `WsData(dict)` — a `dict` subclass with typed annotations for all ~140 coordinator data fields. IDE tools now provide autocomplete and type hints for all sensor data keys with zero breaking changes to existing code.

- **Voice/Assist optimisation.** The `weather.*` entity now sets `native_precipitation_unit = mm` and `suggested_display_precision = 1` for cleaner display in the frontend and Assist responses. The `attribution` property now always returns a non-null string.

### Changed

- **Climatology buffer extended from 30 to 90 days.** The rolling climatology window (`CLIMATOLOGY_WINDOW`) has been increased from 30 to 90 days. The existing 30-day anomaly sensors (`sensor.ws_temp_anomaly_30d`, `sensor.ws_rain_anomaly_30d`) are unaffected and continue to use the most recent 30 days; they now have a richer baseline to compare against.

- **`suggested_display_precision = 1` added to temperature and precipitation sensors.** All temperature sensors (dew point, feels-like, wet-bulb, frost point, 24h high/low/average) and precipitation sensors (rain rate, 1h/24h/today/week/month/year accumulators) now declare 1 decimal place as the preferred display precision for the HA frontend statistics graphs.

### Fixed

- **Calibration service accepted out-of-range offsets.** Offsets larger than the sensor's expected calibration range (e.g. `cal_temp_c: 100`) were silently written to the config entry options, corrupting all derived sensors. Voluptuous range validation now rejects these before writing.

## [2.0.8] - 2026-06-13

### Fixed
Expand Down
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ precipitation nowcasting, fire danger, irrigation, lightning detection, and data

## Why ws_core

Capabilities verified against `custom_components/ws_core/` at v2.0.7.
Capabilities verified against `custom_components/ws_core/` at v2.1.0.
Each point describes functionality not available in any other Home Assistant weather integration.

- **Precipitation nowcast with minutes-until-rain.** `sensor.ws_minutes_until_rain`
Expand Down Expand Up @@ -43,6 +43,12 @@ Each point describes functionality not available in any other Home Assistant wea
- **Adaptive rain probability.** `sensor.ws_rain_probability_combined` uses a
rolling 90-day Brier-score blend that learns, per location, whether local sensors
or the NWP provider have been more accurate.
- **Nowcast ground-truth blending.** For the critical first 30 minutes, ws_core blends the live local rain gauge into the Open-Meteo NWP buckets (70 % local / 30 % NWP tapering to 50/50). A `sensor.ws_nowcast_confidence` diagnostic reflects how well the local gauge and NWP grid agree.
- **Soil sensors and irrigation need.** Optional soil moisture and soil temperature inputs (volumetric, 0–100 % or 0–1 auto-detected) produce a soil moisture deficit, an irrigation need score (0–100), and a human-readable irrigation need label (None / Low / Moderate / High / Critical) informed by soil deficit and net ET₀ demand.
- **90-day seasonal anomaly sensors.** The rolling climatology buffer extended from 30 to 90 days feeds `sensor.ws_temp_anomaly_90d` and `sensor.ws_rain_anomaly_90d`, which compare the recent 30-day period against the 90-day seasonal baseline — turning ws_core into a micro-climate record that grows more valuable over time.
- **Alert hysteresis.** Wind, rain, and freeze alert states require a condition to be sustained for multiple consecutive update ticks before activating (and multiple ticks clear before de-activating). Eliminates chatty automations from sensor noise around threshold boundaries.
- **Human-readable conditions summary.** `sensor.ws_conditions_summary` produces a one-line description of current conditions useful for TTS, notifications, and Assist voice responses.
- **Five import-ready automation blueprints.** Heat alert, freeze alert, rain start/stop, high wind gust, and poor air quality — all with configurable thresholds and optional device actions.

---

Expand Down
66 changes: 66 additions & 0 deletions blueprints/automation/ws_core/freeze_alert.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
blueprint:
name: "WS Core — Freeze Alert"
description: >
Notifies when temperature drops to or below a freeze threshold and
optionally calls a script (e.g. to stop irrigation controllers).
domain: automation
source_url: https://github.com/Aephir/ha_ws_core
input:
temperature_sensor:
name: Temperature Sensor
description: "The ws_core temperature sensor (e.g. sensor.ws_temperature)."
selector:
entity:
domain: sensor
device_class: temperature
freeze_threshold:
name: Freeze Threshold (°C)
description: "Alert fires when temperature drops at or below this value."
default: 0
selector:
number:
min: -10
max: 5
step: 0.5
unit_of_measurement: "°C"
notify_target:
name: Notification Target
description: "The notify service to call."
default: notify.notify
selector:
text:
irrigation_switch:
name: Irrigation Switch (optional)
description: "A switch to turn OFF when frost is detected (e.g. your irrigation controller)."
default: {}
selector:
entity:
domain: switch

trigger:
- platform: numeric_state
entity_id: !input temperature_sensor
at_or_below: !input freeze_threshold
for:
minutes: 3

condition: []

action:
- service: !input notify_target
data:
title: "❄️ Freeze Alert"
message: >
Temperature is {{ states(trigger.entity_id) | round(1) }}°C —
at or below your {{ freeze_threshold }}°C frost threshold.
Protect sensitive plants and outdoor pipes.
- if:
- condition: template
value_template: "{{ irrigation_switch != {} and irrigation_switch != '' }}"
then:
- service: switch.turn_off
target:
entity_id: !input irrigation_switch

mode: single
max_exceeded: silent
62 changes: 62 additions & 0 deletions blueprints/automation/ws_core/heat_alert.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
blueprint:
name: "WS Core — Heat Alert"
description: >
Sends a notification when the feels-like temperature (apparent temperature)
stays above a configurable threshold for a sustained period.
Uses the ws_core alert hysteresis, so a single brief spike won't trigger.
domain: automation
source_url: https://github.com/Aephir/ha_ws_core
input:
feels_like_sensor:
name: Feels-Like Temperature Sensor
description: "The ws_core apparent temperature sensor (e.g. sensor.ws_feels_like)"
selector:
entity:
domain: sensor
device_class: temperature
heat_threshold:
name: Heat Threshold (°C)
description: "Alert fires when feels-like exceeds this temperature."
default: 35
selector:
number:
min: 20
max: 55
step: 0.5
unit_of_measurement: "°C"
notify_target:
name: Notification Target
description: "The notify service to call (e.g. notify.mobile_app_phone)."
default: notify.notify
selector:
text:
cooldown_minutes:
name: Cooldown (minutes)
description: "Minimum minutes between repeated notifications."
default: 60
selector:
number:
min: 10
max: 480
step: 10

trigger:
- platform: numeric_state
entity_id: !input feels_like_sensor
above: !input heat_threshold
for:
minutes: 5

condition: []

action:
- service: !input notify_target
data:
title: "🌡️ Heat Alert"
message: >
Feels like {{ states(trigger.entity_id) | round(1) }}°C outside —
above your {{ heat_threshold }}°C threshold.
Stay hydrated and avoid prolonged sun exposure.

mode: single
max_exceeded: silent
75 changes: 75 additions & 0 deletions blueprints/automation/ws_core/high_wind.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
blueprint:
name: "WS Core — High Wind / Gust Alert"
description: >
Notifies when wind gusts exceed a threshold and optionally retracts
covers, awnings, or other wind-sensitive devices.
Uses a sustained-condition requirement to avoid false triggers.
domain: automation
source_url: https://github.com/Aephir/ha_ws_core
input:
wind_gust_sensor:
name: Wind Gust Sensor
description: "The ws_core wind gust sensor (e.g. sensor.ws_wind_gust)."
selector:
entity:
domain: sensor
device_class: wind_speed
gust_threshold:
name: Gust Threshold (m/s)
description: "Alert fires when gusts exceed this speed. 10 m/s = 36 km/h = Beaufort 5."
default: 10.0
selector:
number:
min: 5
max: 50
step: 0.5
unit_of_measurement: "m/s"
sustained_minutes:
name: Sustained Duration (minutes)
description: "Gusts must exceed threshold for this many minutes before triggering."
default: 2
selector:
number:
min: 1
max: 15
step: 1
notify_target:
name: Notification Target
default: notify.notify
selector:
text:
cover_targets:
name: Covers/Awnings to Retract (optional)
description: "Cover entities to close when high wind is detected."
default: {}
selector:
target:
entity:
domain: cover

trigger:
- platform: numeric_state
entity_id: !input wind_gust_sensor
above: !input gust_threshold
for:
minutes: !input sustained_minutes

condition: []

action:
- service: !input notify_target
data:
title: "💨 High Wind Alert"
message: >
Wind gust of {{ states(trigger.entity_id) | round(1) }} m/s
({{ (states(trigger.entity_id) | float * 3.6) | round(0) }} km/h) detected —
above your {{ gust_threshold }} m/s threshold.
- if:
- condition: template
value_template: "{{ cover_targets != {} }}"
then:
- service: cover.close_cover
target: !input cover_targets

mode: single
max_exceeded: silent
86 changes: 86 additions & 0 deletions blueprints/automation/ws_core/poor_aqi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
blueprint:
name: "WS Core — Poor Air Quality Alert"
description: >
Notifies when the Air Quality Index crosses a configurable threshold
and optionally closes windows or activates air purifiers.
Requires the Air Quality feature to be enabled in ws_core (Settings →
Integrations → ws_core → Configure → Enable Air Quality).
domain: automation
source_url: https://github.com/Aephir/ha_ws_core
input:
aqi_sensor:
name: AQI Sensor
description: "The ws_core Air Quality Index sensor (e.g. sensor.ws_air_quality_index)."
selector:
entity:
domain: sensor
aqi_threshold:
name: AQI Alert Threshold
description: >
Trigger level. US EPA categories:
0-50 Good, 51-100 Moderate, 101-150 Unhealthy for Sensitive Groups,
151-200 Unhealthy, 201-300 Very Unhealthy, 301+ Hazardous.
default: 100
selector:
number:
min: 50
max: 300
step: 1
notify_target:
name: Notification Target
default: notify.notify
selector:
text:
window_covers:
name: Windows / Vents to Close (optional)
description: "Cover or switch entities to close/turn off when air quality is poor."
default: {}
selector:
target:
entity:
domain:
- cover
- switch
air_purifier:
name: Air Purifier to Activate (optional)
description: "A switch or fan entity to turn ON when air quality is poor."
default: {}
selector:
target:
entity:
domain:
- switch
- fan

trigger:
- platform: numeric_state
entity_id: !input aqi_sensor
above: !input aqi_threshold
for:
minutes: 10

condition: []

action:
- service: !input notify_target
data:
title: "😷 Poor Air Quality"
message: >
AQI is {{ states(trigger.entity_id) | int }} —
above your {{ aqi_threshold }} threshold.
Consider closing windows and limiting outdoor activity.
- if:
- condition: template
value_template: "{{ window_covers != {} }}"
then:
- service: homeassistant.turn_off
target: !input window_covers
- if:
- condition: template
value_template: "{{ air_purifier != {} }}"
then:
- service: homeassistant.turn_on
target: !input air_purifier

mode: single
max_exceeded: silent
Loading
Loading