# Changelog

All notable changes to `@bcourbage/homebridge-ambient-weather-sensors` are
documented here. The format is loosely based on [Keep a Changelog][kac];
versions follow [Semantic Versioning][semver]. This file is read by
`homebridge-config-ui-x` to show "What's new" notes after an update — keep
entries short and user-facing.

[kac]: https://keepachangelog.com/en/1.1.0/
[semver]: https://semver.org/

## [2.0.0] - 2026-10-02

Version 2.0 brings individual sensor control to the Homebridge settings page, with broader sensor support and a preview of changes before they reach Apple Home.

### Added

- **An editor for individual sensors and Connection settings.** Rename sensors, choose units and thresholds, or disable unwanted readings without editing JSON. The preview explains accessory additions, removals, replacements, and settings changes before saving.
- **More measurements and sensor types.** New support covers additional wind and rain readings, soil and leaf measurements, evapotranspiration, AQI values, and leak sensors. Custom assignments include contact, occupancy, smoke, and on/off motion, plus a generic numeric option with your own unit label and an optional threshold.
- **Change interpretation for saved custom sensors.** Revise a sensor's type or source unit without deleting its assignment first. The preview identifies any accessory replacement, which may affect rooms and automations.

### Fixed

Legacy fields beyond the former fixed tables remain recognized, saved custom
assignments survive sensor-support updates, and temporarily offline accessories
are retained. Invalid state readings report a fault rather than an alert, while
unknown battery readings keep the last reported state. Connection settings can
also be saved before discovery, and setup saves remain tied to their preview
even if the plugin is updated in between.

### Upgrade notes

Back up your Homebridge configuration before upgrading. Installation does not
rewrite it or automatically enable newer sensor support. Conversion preserves
your original sensor settings, and **Review new sensor support** separately
previews additional capabilities and corrected lightning/leak battery readings.
New definitions start disabled, although an enabled custom assignment awaiting
support can become active; the preview lists that addition.

After conversion, known disabled sensors stay disabled, but legacy category-wide
and sensor-name exclusions no longer govern fields that first report later.
A newly reporting, recognized sensor may appear; disable its row if unwanted.

Extended numeric readings use motion accessories in Apple Home, with labelled
values in compatible controller apps. AQI is shown as a number, not a health
category, and native carbon-monoxide detection is not included.

The minimum Homebridge version is now 1.9, correcting the earlier 1.8 claim.
Use Node.js 22.13+ within 22.x or 24.x, provided your Homebridge version also
supports it; Homebridge 1.9.0 requires the Node 22 option. See the
[upgrade guide](https://github.com/bcourbage/homebridge-ambient-weather-sensors/blob/main/UPGRADING.md)
for conversion, restart, and rollback instructions. Disabling the v2 flag alone
is not a safe rollback after conversion.

If you already use beta.19, your sensors will work as before. The editor now
explains what may change if you return to an older plugin version, and the
upgrade instructions and release notes have been updated for 2.0.0.

## [2.0.0-beta.19] - 2026-09-26

Change how saved custom sensors are interpreted, with clearer previews and consistent editor controls.

### Added

- **Change interpretation for saved custom sensors.** Choose a different sensor type or source unit without first deleting the saved assignment. Preview shows whether the Apple Home accessory updates in place or is replaced. Replacement may affect rooms and automations.
- **Explicit threshold control.** Switch threshold triggering off or back on in the row editor. Changing the sensor type or source unit clears the threshold rather than guessing a conversion; leaving it blank keeps triggering off.

### Fixed

- **Custom-sensor saves with shared settings.** Changing a custom sensor's interpretation no longer causes a valid save to be refused when it also inherits settings shared across stations.
- **First-time setup saves stay tied to their preview.** Saving after a plugin update now requires a fresh preview if that update changes the new configuration's starting sensor-support version. Existing configurations keep their saved versions.

### Changed

- **Consistent editor controls.** Dropdowns and single-line fields now share the same height and text size across Connection, Units, and sensor editing. Labels and status badges wrap on narrow screens.

### Upgrade notes

- Installing this beta leaves saved sensor interpretations and sensor-support adoption unchanged. After saving configuration changes, use Homebridge's **Restart Child Bridge** action or a full Homebridge restart.
- Runtime dependency ranges and locked dependency versions are unchanged. The release candidate's production install resolved `socket.io-client` 4.8.4, an in-range patch update from 4.8.3; a fresh beta.18 install can resolve the same version.

## [2.0.0-beta.18] - 2026-09-23

Expanded sensor support, preserved custom assignments, and clearer previews before configuration changes.

### Added

- **More supported measurements.** Additional wind and rain fields, soil moisture, leaf wetness, soil tension, evapotranspiration, and AQI values are available through the sensor-support update. New definitions do not replace your saved custom assignments.
- **Complete sensor-type choices.** The editor offers separate leak, contact, occupancy, smoke, and on/off motion choices. Types requiring a sensor-support update remain visible with their prerequisite. Each choice explains the input encoding and what Apple Home can display.
- **Generic numeric labels.** Assign an otherwise unsupported numeric quantity with a literal unit label and an optional threshold. Labels are display-only, support Unicode, and can inherit, be cleared, or be set independently at the edited scope.
- **Explicit conversion and sensor-support previews.** Convert legacy settings, then use **Review new sensor support** to preview newer capabilities separately from sensor edits. The preview lists accessory effects, battery interpretation changes, and newly available fields that remain switched off. Opening the page or previewing does not save anything.

### Changed

- **Sensors are managed individually after conversion.** Existing disabled sensors stay disabled. V2 does not keep legacy category-wide or sensor-name exclusions as rules for future fields. A newly reporting, recognized sensor may appear after conversion; disable that row individually if unwanted. Unknown fields still need assignment, and new catalog features keep their adoption requirements.
- **Clearer Units and support details.** Long measurement names stay readable, Evapotranspiration has a **?** explanation, and Technical details distinguish your starting sensor-support version, the version in use, and the latest version included with the plugin.

### Fixed

- **Legacy upgrades preserve more sensor identities.** Fields recognized by the 1.7.3 matcher, including numbered fields beyond the former fixed tables, remain recognized. Explicit custom assignments keep their measurement and source unit instead of being replaced by a guessed identity.
- **Connection settings can be saved before sensors are discovered.** First-time setup and credential recovery no longer depend on a populated sensor inventory.
- **Offline accessories are preserved during temporary discovery gaps.** Cached sensor identities remain available to the resolver instead of being removed merely because a field is missing from a current sample.
- **State and battery readings are handled consistently.** Invalid or offline state readings report a fault rather than an active alert. Lightning and leak battery-polarity corrections are previewed as part of the sensor-support update, rather than silently changing on upgrade. Unknown battery readings preserve an existing reported state.
- **Rollback mirrors preserve rainfall units and mixed custom assignments.** Legacy-compatible sensors retain their settings; custom identities that 1.7.3 cannot represent remain outside that compatibility boundary.
- **Save stays tied to its preview.** Obsolete preview responses cannot restore Save after edits or cancellation. Failed post-save reloads, mismatched receipts, and save-control restoration failures require reload before further editing.

### Upgrade notes

- Existing configurations do not automatically enable newer sensor support. Review and save that update explicitly. Applying saved configuration changes requires Homebridge's **Restart Child Bridge** action or a full Homebridge restart.
- Extended numeric measurements use a motion accessory in Apple Home, with an optional threshold. Compatible controller apps can show the labelled numeric value. Native carbon-monoxide detection is not included.
- Back up your Homebridge configuration before upgrading. To revert a converted configuration, follow the [documented rollback procedure](https://github.com/bcourbage/homebridge-ambient-weather-sensors#rollback), rather than simply turning off the v2 flag.

## [2.0.0-beta.17] — 2026-09-15

### Changed

- **The sensor map is on by default.** The `_sensorMapV2` opt-in is retired: every installation now runs the v2 sensor-map runtime and editor without any flag. A legacy 1.x configuration keeps producing exactly the accessories it produced before — the conversion happens only when you save from the editor, and your original settings are preserved in a snapshot first. To opt out, set `_sensorMapV2: false` in the plugin's config block or `SENSOR_MAP_V2=0` in Homebridge's environment.
- **One settings page, one Save.** Everything editable now lives on the plugin's settings page: platform name, data source, API keys, station filter, tile-name update interval, display units, and the sensor map. The page's own Save is the only functional save path and runs the guarded, previewed transaction; the standard Homebridge Save button below it is disabled by design (the Homebridge UI offers no way to hide it).
- **The preview is the confirmation.** Preview lists every consequence of a save — each accessory that would register, deregister, or re-register, with per-row Skip — and Save applies exactly what was previewed. The separate confirmation step is gone.
- **API keys are handled as secrets.** Stored keys display as dots and never leave the server: type over the dots to replace a key, delete them and leave the field empty to clear it, and leave them untouched to keep the stored key. Sensor-map saves never touch credentials.
- **Notes appear where they apply.** A note about one sensor (battery ownership, for example) renders under that row in its table and in the preview on the change it concerns, instead of in a block at the top of the page.

### Added

- **See what your station actually reports.** Sensors the station has never reported carry a "no data" chip — they create no HomeKit accessories even while enabled — with a per-station action to disable them all and a "Hide sensors with no data" filter that remembers your choice. Both appear only when the plugin has real observation history to base them on.
- **Use defaults shows the defaults.** The row editor's Use defaults button now sets the form to what the row returns to — the plugin defaults plus your page-level unit choices — instead of just clearing the override behind a closed form. A category unit chosen in the Units panel is never undone by a row-level reset.
- **Rollback status.** A disclosure at the bottom of the page states whether the manual rollback to 1.7.3 is currently safe and gives the exact steps, including the environment-variable case.

### Removed

- **The preview-era chrome is gone.** The read-only status panel, the "Discovered stations & datapoints" dump, and the reduced legacy settings form no longer render; the dynamic-schema mechanism that hid legacy form controls is retired, and the plugin removes its stale schema file at startup.

## [2.0.0-beta.16] — 2026-09-13

### Added

- **Assign unrecognized fields to custom sensors.** An unrecognized row (a `?` in the Kind column) now offers Assign in place of Edit: choose a measurement and the unit the station reports it in, and the field becomes a real accessory. The accessory kind follows from the measurement, the choices offered are exactly the combinations this plugin can build, and the assignment applies to that one station. New sensors are enabled by default and preview as a registration like any other structural change; name, display unit, threshold, and trigger direction can be set in the same form or edited later like any row. Switching the source unit clears a typed threshold, since a threshold is expressed in the source unit and 10 mph and 10 km/hr are different triggers.

### Changed

- **Applying a saved sensor map no longer requires a full Homebridge restart.** Restarting this plugin's child bridge from the Homebridge UI re-reads the saved configuration before the bridge comes back (verified on Homebridge 2.4.0); the documentation now says so. Killing the child process by hand still does not apply a save, and a full restart also works.
- **A custom count sensor displays a plain number.** The "strike" wording belongs to the built-in lightning counters; a count the plugin knows nothing about is shown without a label it cannot vouch for.

### Fixed

- **Row editors no longer offer trigger controls that cannot work.** Wind direction and last-rain time rows are motion accessories that never cross a threshold; their editors previously rendered threshold and trigger fields whose values a save would silently strip. Those controls now appear only where a threshold is real.

## [2.0.0-beta.15] — 2026-09-10

### Added

- **Display units for whole categories.** A Units panel above the sensor-map tables sets the display unit per category the way AmbientWeather.net does: Barometer, Wind Speed, Rainfall (one choice keeps rain rate and accumulation totals consistent), and Distance. A choice applies to every station, including stations added later; single rows can still be changed in their row editor, and a category whose rows disagree shows Mixed. Temperature and other natively displayed kinds have no unit choice: Apple Home renders those in each device's own region format.
- **Skip single rows in a preview.** Every modified row in the preview carries a Skip action that keeps that row's current settings while the rest of the change proceeds. Rows where no setting can represent the skip (for example a re-registration caused by battery ownership moving) offer no Skip.
- **Previews account for every draft.** Changes that save without touching a registered accessory, such as unit changes on disabled rows, are now listed with a "disabled" marker instead of silently making the draft count and the change list disagree.
- **Kind column help.** A ? next to the Kind header explains that Kind is the Apple Home accessory type and lists the currently supported and reserved kinds.
- **Sponsor link.** The plugin settings page and the fallback settings form link to [github.com/sponsors/bcourbage](https://github.com/sponsors/bcourbage).

### Changed

- **The settings form hides controls the v2 configuration no longer uses.** With a converted configuration, the sensor category checkboxes, extended-sensor thresholds, display-units fieldset, and the Exclude Sensors / Include Only filters disappear from the settings form: the sensor map governs all of that, and the plugin maintains those legacy fields only as a rollback mirror for 1.7.x downgrades. The full form remains for legacy configurations and multi-Home setups.
- **Tooltips render inside the page.** Homebridge UI X's settings window competes with native browser tooltips, so the page now draws its own: instant, keyboard-accessible, and correctly themed. The Units column tooltips also state whether a value is the station's own unit, a chosen conversion, or a conversion fixed by HomeKit (light level is always lux).

### Fixed

- **The settings form's Save button can no longer undo a fresh editor save.** The form's copy of the configuration predates the save, and saving it again overwrote the editor's work, in the worst case deregistering every accessory. After a successful editor save the form's Save button now stays off; reload the plugin settings page to edit those fields again.
- **Saving no longer fails with "unsaved settings changes" on a converted configuration.** The check now compares the form against the reduced schema it actually renders, so hidden legacy fields no longer read as pending edits.
- **Units dropdowns behave.** They follow the light/dark theme, align with their labels, and choosing one no longer scrolls the page away from the opening menu.
- **Previews update in place.** Skipping a row or re-previewing refreshes the existing list instead of tearing it down and rebuilding it.

## [2.0.0-beta.14] — 2026-08-31

### Fixed

- **The save confirmation is actually visible.** The server-verified save confirmation was centered against the iframe's full content height, which in Homebridge UI X placed it far below the visible page; all you saw was a dimmed backdrop. It is now an in-flow card that appears where Save was clicked and scrolls into view, with the other controls disabled until it is answered.
- **The sensor-map editor works in the real Homebridge UI again.** In beta.13, every preview and save was refused with `stale-base`: Homebridge UI X hands custom pages its settings form's in-memory copy of the plugin config, which never byte-matches `config.json` once the form materializes its defaults. The editor session now carries a digest issued by the server for the exact on-disk block it loaded, and saves are written back as a verbatim replacement, immune to the UI's merge-style config update (which could silently resurrect fields a save had removed and invalidate the freshly written rollback mirror). After every save, the editor re-reads the configuration from disk and warns if it differs from what was composed.
- **Live theme switching keeps the whole page on the new theme.** Homebridge UI X freezes the page background and text color at the theme active when the page opened; switching themes with the page open left the page ground on the old theme while the panels followed. The page now keeps its ground on the live theme in both directions.
- **The Edit button is always visible.** The sensor-map table scrolls horizontally in its own container and the action column stays pinned to the right edge, instead of rendering past the clipped edge of the panel.
- **Less layout jumping while editing.** The draft bar (with the Preview and Discard controls) is now always visible above the table instead of appearing on the first edit and pushing the page down mid-interaction; its wording and the row validation messages were also rewritten.
- **Settings form console noise removed.** Three legacy form-layout entries declared an invalid widget type, producing "widget type string not found" errors in the browser console on every page open.
- **Editor saves protect unsaved settings-form changes.** Saving a sensor-map draft while the settings form above holds unsaved edits (a credential, a toggle, a filter) would have silently discarded them; the save is now refused with a message to save or discard the form changes first. The settings form and its Save button are also frozen for the duration of an editor save (and restored after), so an edit cannot race the save either.
- **Multi-Home configurations stay read-only in the editor.** The sensor-map editor supports exactly one AmbientWeatherSensors platform block; with more than one present (a supported multi-Home setup, see MultiHome.md) it previews nothing and refuses saves instead of risking a write to the wrong block, and directs edits to the JSON config editor.

### Changed

- **Slimmer sensor-map table.** The Enabled and Kind columns are now icons: a check or dash state icon at the far left, and a per-kind icon (thermometer, water drop, sun, motion wave) with compact badges for CO₂, CO, PM2.5 and PM10. The full wording remains available as tooltips and screen-reader text.
- **The row editor closes like a dialog.** OK collapses the row and keeps its draft edits; Cancel discards them; Use defaults (offered when the row has authored settings) drafts their removal so the row falls back to the built-in defaults. These replace the former Close, Reset row and Remove override buttons.
- **Messages use real grammar.** The remaining "(s)" pluralizations in editor and server messages are rewritten as properly pluralized sentences.

### Added

- **Sponsor link.** The plugin is now sponsorable at [github.com/sponsors/bcourbage](https://github.com/sponsors/bcourbage) (`funding` in package.json and the repository Sponsor button).

## [2.0.0-beta.13] — 2026-08-20

### Added

- **The sensor-map editor can now save (GA task #69, PR C).** Drafted changes persist through the guarded snapshot-first boundary: the server validates the draft against the on-disk configuration, and any change that would register, deregister, or re-register a HomeKit accessory requires explicit confirmation of a server-verified preview. A save whose consequences drifted since that preview (for example, a station appearing in the meantime) is refused; on a legacy configuration, the first save converts it to the v2 format with the original settings preserved first in `legacy-config-snapshot.json`. Saving requires the sensor-map v2 flag to be on (with it off, the runtime could not read the saved map, so the editor stays preview-only and the server refuses saves outright). Nothing is written on any refusal; if the browser loses contact mid-save, the editor reports the outcome as uncertain and directs you to reload and inspect rather than claiming nothing changed. Structural changes take effect on the next full Homebridge restart (restarting only the plugin's child bridge does not re-read the configuration).

- **Rolling back and returning to v2 both work now (production-drill finding).** The documented current-state rollback leaves the synchronized mirror form of your settings, which differs from the original snapshot, so re-enabling v2 and saving again used to be refused. Such a reconversion now records the rolled-back settings in an append-only `legacy-conversion-journal` folder (next to the snapshot, one numbered entry file per baseline, same credential-free field set) before converting, so nothing is lost in either direction; the original snapshot is never modified, and concurrent settings sessions cannot overwrite each other's journal entries.

## [2.0.0-beta.12] — 2026-08-20

### Added

- **Sensor-map draft editor with server-verified preview (GA task #69, PR B).** The sensor-map table is now editable as a DRAFT: enable or disable rows, rename them, choose display units, set motion thresholds and trigger direction, and remove overrides. Nothing is saved — drafts live in the page, and a new "Preview changes" action dry-runs them through the exact server-side save pipeline (validation, canonical form, and a row-by-row diff over the registered accessories, marking which would register, deregister, or re-register) with zero writes. Saving from the editor arrives in a later beta through the guarded snapshot-first boundary. Also fixed: the section heading hierarchy, so "Sensor map" visibly contains its notes and station tables, and a save/preview is now refused outright when config.json carries a malformed `sensorMap` the runtime would refuse to interpret.

### Changed

- **Node.js support floor corrected to 22.13.** The beta.11 release process demonstrated that the previously advertised 22.12 floor cannot run the Homebridge 2 dependency tree (an optional Homebridge dependency excludes Node 22.0–22.12); the plugin's engines, documentation, and CI now state and test 22.13 as the floor.

## [2.0.0-beta.11] — 2026-08-19

### Added

- **Sensor-map viewer: read-only editor foundation (GA task #69, PR A).** The plugin settings page now renders the effective sensor map grouped by station: data point, name, kind and measurement, units with AmbientWeather.net-matching labels (source → display when a conversion applies), enabled state, authoring layer (default / global / station / unrecognized), and battery field. The view is strictly read-only; editing ships in a later beta and every future save will flow through the guarded `/compose-save` boundary. Two new read-only UI bridge endpoints back it: `/editor-state` (a sanitized read model derived from the on-disk config.json — credentials and internal machinery never reach the browser; legacy configs render as their migration preview) and `/vocabulary` (per-measurement unit options with display labels).

### Fixed

- **Live theme switching in the plugin settings page.** Homebridge UI X sends its theme to a custom UI iframe only when the page opens, and the client library only ever adds theme classes, so switching between light and dark with the page open left it rendered in the old theme until reopened (observed on beta.8). The page now mirrors the parent's theme classes directly (adding and removing) and follows live theme changes in both directions.

## [2.0.0-beta.10] — 2026-08-19

### Added

- **Guarded sensor-map save boundary (GA task #67 / finding 5).** The UI bridge now exposes `/compose-save`: the single endpoint through which any future sensor-map write must flow. It validates proposals with the same machinery the runtime uses, assembles the canonical `sensorMap` server-side, and writes (or verifies) the immutable legacy snapshot BEFORE returning the composed configuration for the client to persist — so a legacy config's pre-conversion state is durably recorded before config.json can change. Safe-mode configs, stale editor sessions, invalid rows, missing station inventory, and snapshot mismatches are all refused with structured errors and zero writes. The sensor-map editor itself has not shipped; ordinary settings remain editable through the standard form.

- **Unit vocabulary matching AmbientWeather.net (GA task #70, engine + vocabulary layer).** New units for AWN parity: `mmHg` (barometric pressure, mmHg = inHg × 25.4) and `fps` (wind, ft/sec) as v2 row display/source units, plus `fc` (foot-candles, lux = fc × 10.7639104167) as a custom-row source unit. A new vocabulary module records AWN-matching labels, picker ordering, selection contexts, and a dated snapshot of AWN's own units page as the parity reference. The legacy Extended Sensors unit dropdowns are unchanged (v2-only units arrive with the upcoming row editor), and a 1.7.x rollback config never receives a unit that 1.7 cannot render: the mirror omits the family key instead, so a downgrade falls back to the default display unit for that family.

## [2.0.0-beta.9] — 2026-07-29

Documentation, metadata, and plugin-UI polish on top of beta.8; no runtime behavior change.

### Changed

- **Independent-maintenance framing.** The original project's compatibility pull requests were closed unmerged in June 2026, so the "temporary bridge" language is retired across the README, package description, and NOTICE: the plugin is now described as independently maintained, with the acknowledgment of Deac Karns' original work kept. The npm `funding` field (a Fund button pointing at the original author's PayPal) is removed.

### Fixed

- **Plugin UI page is now legible in Homebridge UI's dark theme.** The custom-UI panels (status, discovered stations, notices) previously shipped light-theme-only colors that clashed with the theme stylesheet HB UI X injects into custom-UI iframes, producing dark-on-dark table headers and light-on-light banner text. The panels now carry explicit light AND dark palettes keyed off the `dark-mode` body class HB UI X sets, scoped so the injected theme can't half-override them; all text/background pairs meet WCAG AA contrast in both themes.

## [2.0.0-beta.8] — 2026-07-29

The big handoff: the `_sensorMapV2` flag now selects the **live** sensor-map v2 path instead of the compare-only "shadow mode" of beta.0–beta.7.

### Changed

- **`_sensorMapV2` (or `SENSOR_MAP_V2=1`) now runs v2 for real.** With the flag on, accessory registration, naming, and value routing are driven by the v2 sensor map — including custom sensors declared via `configVersion: 2` + `sensorMap` — and the plugin may re-register an accessory when its structure changes. Each structural change is recorded as a notice, visible on the plugin's page in Homebridge Config UI X. The shadow-mode observer is retired.
- **Flag off (the default) is unchanged:** behavior identical to v1.7.0, byte for byte. Beta.8 never migrates your config or generates `sensorMap` on its own, so unless you hand-authored `configVersion: 2`/`sensorMap`, rollback remains "turn it off and restart" (accessories the v2 path structurally re-registered may need room reassignment in Apple Home).
- Config-schema, README, and plugin-UI text rewritten to describe the live behavior; the UI status field formerly named `shadowFlag` is now `v2Flag`.

### Rollback warning for hand-authored v2 configs

- If you added `configVersion: 2` + `sensorMap` to your config yourself, restore a 1.x-compatible config BEFORE disabling the flag or downgrading: with the flag off, the v1 path cannot read `sensorMap` and can deregister cached accessories. No 1.x release can operate or update custom v2-only sensors. v1.7.1's emergency freeze preserves their cached tiles at last-known values; restoring a legacy config and resuming normal 1.x reconciliation removes them.
- An emergency downgrade while a v2 config is still in place must target **v1.7.1** (published on `latest`): it freezes safely — cached accessories are preserved but stop updating — until a legacy config is restored. v1.7.0 and earlier misread v2 configs and are not safe downgrade targets.

### Notes

- This beta still cannot WRITE a v2 config — the config-editing UI (and its snapshot-before-mutation safety machinery) is later 2.x work.

## [2.0.0-beta.7] — 2026-07-19

Two-track release plan starts here. Same code shipping to `@latest` as **v1.7.0** (shadow-mode observer with the flag OFF, no UI change) and to `@beta` as v2.0.0-beta.7 (same code + the write-capable UI preview + `_sensorMapV2` config-schema field). Users on `@latest` are dormant testers of the observer's code-load path; users on `@beta` are active testers of the v2 UI + shadow-mode logging.

### Fixed

- No functional change since v2.0.0-beta.6. Version sync only.

## [2.0.0-beta.6] — 2026-07-10

### Fixed

- **Modal usable even when the observation panels can't load.** In
  beta.5 the custom UI's initial `homebridge.showSpinner()` call
  gated the entire modal on `homebridge.request('/status')`
  completing. On Bruno's Demeter setup the round-trip crashed inside
  HB UI X's frontend with `TypeError: null is not an object
  (evaluating 'this.iframe.contentWindow.postMessage')` — HB UI X
  received the server-side reply but couldn't relay it to the iframe.
  The request promise never resolved, `hideSpinner()` never ran, the
  modal stayed greyed out behind the spinner, and users couldn't
  interact with the schema form. Fix: don't block on `refresh()`.
  Fire it as background work. The schema form is usable immediately;
  the observation panels populate if the request path is healthy or
  stay on placeholder text otherwise.
- Underlying HB UI X iframe delivery issue still needs root-cause
  work — filed as follow-up on task #69 (UI polish).

## [2.0.0-beta.5] — 2026-07-10

### Fixed

- **Save button spun forever when toggling the shadow-mode flag.**
  In custom UI mode HB UI X disables its built-in Save button by
  default; the custom UI has to call `homebridge.enableSaveButton()`
  to opt in. Without that call, clicking Save on the schema form
  posted an `action: button.save.enabled` message that was never
  serviced — HB UI X spun on the modal indefinitely. Confirmed the
  pattern by inspecting homebridge-smartthings-oauth's custom UI on
  Demeter. Fix: call `enableSaveButton()` before `showSchemaForm()`.
- Custom UI now also listens for HB UI X's `configChanged` event
  and re-fetches the observation panels so the sensor-map status
  pill stays in sync with the flag the user just toggled.

## [2.0.0-beta.4] — 2026-07-10

### Fixed

- **Custom UI page spun forever with no server-side response.** The
  `homebridge-ui/server.js` bridge imports
  `@homebridge/plugin-ui-utils`, which we shipped as a
  **devDependency** through beta.3. End-user installs run
  `npm install --production`, so the module wasn't present in the
  tarball's runtime tree — server.js threw on load, HB UI X's
  request channel never responded, and the UI froze on the loading
  spinner. Verified by inspecting
  `/opt/homebrew/lib/node_modules/@bcourbage/homebridge-ambient-weather-sensors/node_modules/`
  on Demeter (no `@homebridge/` subdirectory). Fix: moved
  `@homebridge/plugin-ui-utils` to `dependencies`.

## [2.0.0-beta.3] — 2026-07-10

### Fixed

- **Custom UI preview page was invisible in Homebridge Config UI X.**
  beta.0-beta.2 shipped a `homebridge-ui/` directory but did not set
  `"customUi": true` at the top of `config.schema.json`, which is
  the flag HB UI X checks to decide whether to open the custom UI
  or the schema-based config form. Fix: added the flag.
- `homebridge-ui/public/index.html` now calls
  `homebridge.showSchemaForm()` on load so the schema-based config
  form (thresholds, excludes, etc.) renders at the top of the
  custom UI page. Users keep their normal config-editing surface;
  the observation panels appear below. Save writes flow through HB
  UI X's own APIs, preserving the Path B "no writes from our code"
  guarantee.

### How to see the preview page

Update to beta.3, restart Homebridge, and click **Plugin Config** on
the Ambient Weather card in HB UI X. The schema form renders at the
top; the new v2 observation panels (status, discovered stations,
notices) appear below.

## [2.0.0-beta.2] — 2026-07-10

### Fixed

- **Critical: EISDIR child-bridge crash on startup.** In beta.0 and
  beta.1 the shadow-mode observer wrote persistence files under
  `<homebridge-storage>/persist/plugin-data/ambient-weather/`. That
  path is inside HAP-NodeJS's own persistence tree, which
  node-persist scans with `readFileSync` per entry. Our subdirectory
  crashed HAP with `EISDIR` on every child-bridge restart — even
  after the shadow-mode flag was turned off, because the leftover
  directory persisted on disk. Fix: write plugin data under
  `<homebridge-storage>/plugin-data/ambient-weather/` instead, which
  is outside HAP's scan area. Regression test added.
- Startup detects any beta.0/beta.1 leftover directory and warns
  with the `rm -rf` command needed to clean up. We deliberately
  don't auto-delete: the path is under HAP's storage tree and
  automated deletes there scare people (rightly).

### Migration from beta.0 / beta.1

If you ran either previous beta with the shadow flag on:

    rm -rf ~/.homebridge/persist/plugin-data

After removing that directory and restarting Homebridge, the crash
loop stops and beta.2's shadow mode (with the flag on) writes to
the correct location.

## [2.0.0-beta.1] — 2026-07-10

### Changed

- Remove personal names from the "Advanced (v2.0 preview)" fieldset
  help text in the config UI. Cosmetic only; no functional change.

## [2.0.0-beta.0] — 2026-07-10

First beta of the sensor-map v2.0 architecture. **Ships with the new
pipeline OFF by default.** For everyone who doesn't opt in, this
release is byte-identical to v1.6.0. Opting in enables a shadow-mode
observer that runs the new pipeline in parallel to the current code
and logs any divergence — a bake period, not a switchover. See
`docs/future/sensor-map.md` for the full design.

### How to shadow-run

Set either the environment variable `SENSOR_MAP_V2=1` or the
`_sensorMapV2: true` config field (exposed as the "Advanced (v2.0
preview)" fieldset at the bottom of the config form). Then restart
Homebridge. The plugin log will begin emitting `[sensor-map v2
shadow]` lines: the detected config mode, per-cache-restore
bootstrap inferences, and any (station, dataPoint) pair where the
v2 pipeline would decide differently than v1.6.0.

Nothing changes in HomeKit while shadow mode is on. The v1.6.0 code
path stays the source of truth for every accessory-registration
decision.

A new preview page is accessible via the plugin's UI (Homebridge
Config UI X → plugins → Ambient Weather Sensors → UI). It shows the
detected config mode, discovered stations, observed datapoints,
and any structural-change notices. Read-only during the beta.

### Rollback

Flip the flag off (unset the env var or set `_sensorMapV2: false`)
and restart. Shadow mode touches no config or accessory state, so
the flip-back is clean. Downgrade from v2.0.0-beta.0 back to v1.6.0
works the same way: nothing in `config.json` or the accessory cache
was written by shadow mode.

### Added

- Sensor-map data model: `SensorKind`, `Measurement`, `SensorUnit`,
  `SensorMapOverride`, `EffectiveSensorRow` (discriminated union),
  `WrapperDescriptor` with stable ids frozen at ship.
- `DEFAULT_SENSOR_MAP` — 69 rows covering every AWN datapoint the
  plugin recognizes (41 static + 28 numbered probes).
- `buildEffectiveSensorMap()` — pure function merging defaults +
  user overrides + observational data into per-(station,dataPoint)
  effective rows.
- Compat layer translating every v1.6.0 config field into synthetic
  overrides. Consumed by the new pipeline; never written back to
  `config.json`.
- `configVersion` detection with strict safe mode on
  `configVersion > 2` or malformed values. Existing accessories keep
  running from cache; the UI refuses writes; an upgrade banner is
  surfaced.
- Bootstrap rule for pre-v2 cached accessories: three-level fallback
  on kind (context → legacy type → HAP service walk) and measurement
  (default map → legacy type table → preserve-cached).
- Three persistence stores under
  `<homebridge-storage>/plugin-data/ambient-weather/`:
  `discovery.json` (observed datapoints), `notices.json`
  (structural changes, capped to the newest 100), `ui-state.json`
  (dismissed IDs + forgotten fields). Atomic writes with
  `<name>.<pid>.<random>.tmp` temp files + rename, corrupt-file
  quarantine.
- `homebridge-ui/` custom UI: read-only preview page listing the
  detected config mode, shadow-flag source, discovered stations
  and datapoints, and any structural-change notices.
- Shadow-mode observer wired into `platform.ts` behind the flag.

### Tests

Suite grew from 385 tests to 694 (309 new). Includes a
migration-equivalence matrix (23 legacy configs × 4 AWN payloads =
92 assertions) verifying byte-identical accessory-registration
decisions between v1.6.0 and the v2 pipeline. This is the safety
net that gates the eventual flag flip to on-by-default (planned for
v2.1.0).

### Not changing

- v1.6.0 code path unchanged. Every user who doesn't set the flag
  sees no behavior difference.
- Accessory cache contexts are never mutated by shadow mode.
- `config.json` is never written by the plugin.
- `stationFilter`, child-bridge multi-Home behavior, and the
  realtime data source path are unchanged.

## [1.6.0] — 2026-06-20

### Changed

- **Embed display mode now forces the polling data source.** If both
  `extendedDisplayMode: "embed"` and `dataSource: "realtime"` are set,
  the plugin coerces the data source to `polling` at startup with a
  one-line warn log. The combination was observed to drain iOS phone
  battery 5-7× faster than normal idle (solmssen, ~15 extended sensors
  active) because each tile-name update generates an HAP notification
  forwarded to every paired iOS device. Polling's 2-minute cadence
  keeps the notification volume to a level that's not noticeable.
  Affected users: anyone currently on embed + realtime. Tile names
  will update every 2 minutes instead of every ~30 seconds. To silence
  the warning, set `dataSource` to `polling` explicitly, or switch
  `extendedDisplayMode` to `static`.

### Added

- **`embedNameUpdateMinIntervalMinutes` config field** caps the
  per-accessory name-update rate in embed mode. Default 2 minutes
  matches the polling cadence — effectively a no-op for new installs.
  Can be increased (e.g., to 5 or 10 minutes) for users who want to
  further reduce HAP notification volume to paired iOS devices, at the
  cost of less-frequent tile-name updates. Has no effect in static
  mode (which never rewrites tile names).

## [1.5.0] — 2026-06-16

GA release. The full 24-beta cycle is summarized below; this entry
captures what changes if you're upgrading directly from 1.4.x.

### Added (since 1.4.x)

- **Battery sub-services for every probe-backed sensor.** Native
  HomeKit low-battery push notifications fire when AWN reports
  any sensor's battery as low. Per-probe dedup (one Battery
  sub-service per physical probe, attached to its canonical
  sensor) avoids overwhelming Apple Home with redundant tiles.
- **Feels-like and dew-point temperature accessories** appear
  automatically when Temperature Sensors is enabled, on every
  probe AWN reports calculated values for (outdoor, indoor, each
  WH31 channel).
- **Extended Sensors (opt-in)** — wind, rain, barometric
  pressure, UV, and lightning exposed as MotionSensor accessories
  with threshold-driven Apple Home automations. Custom
  Value/Intensity/LastUpdated characteristics surface live numeric
  readings in Eve / Controller for HomeKit.
- **`stationFilter` config field** for multi-Home setups —
  combined with multiple platform instances and Homebridge child
  bridges, lets users with stations in physically separate
  locations expose each station in its own HomeKit Home. Full
  walkthrough in `MultiHome.md`.
- **Battery sub-service suppression via `excludeSensors`** — three
  accepted forms (`<friendly name>-batt`, `<sensorKey>-batt`, raw
  AWN battery field name) hide a probe's Battery sub-service
  without removing the parent accessories. Primary use case: the
  documented `batt_lightning` AWN API false-low bug.
- **Startup discovery log** announces each AWN station's name +
  MAC + sensor count on first poll, so users can identify the
  exact string to put in `stationFilter`.
- **Realtime data source (opt-in)** — websocket subscription to
  AWN's `rt2.ambientweather.net` endpoint with automatic reconnect.
  ~30-second update cadence vs. polling's 2 minutes.

### Changed (since 1.4.x)

- **Tile names are bare in single-station setups** ("Outdoor
  Temperature" not "Backyard Station Outdoor Temperature"). The
  station prefix is preserved when an instance exposes multiple
  stations, for disambiguation. Migration is automatic; user
  renames in Apple Home are preserved.
- **Per-accessory poll timers consolidated into one platform-level
  timer** — eliminates parallel-fetch race against AWN's 1 req/s
  rate limit.
- **AccessoryInformation Name characteristic updated on every
  restart** to keep HAP-side metadata aligned with the displayed
  name across all clients.

### Documentation

- New `UPGRADING.md` with a full upgrade walkthrough from 1.4.x.
- New `MultiHome.md` with the multi-Home setup pattern.
- New `docs/future/tabbed-config-ui.md` capturing the deferred
  custom-UI design.
- README and trademark notice clarify the fork's relationship to
  upstream `peledies/homebridge-ambient-weather-sensors`.

### Final beta.24 → GA changes

- Corrected the embed-mode help text: the room-reset behavior
  reported during beta testing is specific to the Homebridge UI
  Accessories page, NOT Apple Home or Eve. Apple Home and Eve
  preserve room assignments correctly across name changes; the
  HB UI tracks rooms by display name and resets affected tiles
  when the name updates. Updated wording recommends users who
  primarily use the HB UI Accessories page stay on generic
  names.
- Downgraded the `[embed-diag]` log line from info to debug
  level — the mechanism was characterized during beta.24 testing,
  so the instrument no longer needs to be on by default. Toggle
  `HB_LOG_LEVEL=debug` to capture if needed for future
  investigation.

## [1.5.0-beta.24] — 2026-06-14

### Added

- **Friendly names for AQIN's internal temperature and humidity
  sensors.** Previously these fields fell through to the
  `hapClean`'d raw AWN key, surfacing in HomeKit as "pm in temp aqin"
  and "pm in humidity aqin". Now they render as "AQIN Temperature"
  and "AQIN Humidity" — distinct from the room indoor temperature
  / humidity, and parallel to how the other AQIN-family sensors
  are named. Reported by solmssen on beta.23.

- **Battery sub-service suppression via `excludeSensors`.** Three
  new accepted forms in the existing field, all targeting the
  Battery sub-service of a probe WITHOUT hiding the parent
  accessories:
  - `<friendly name>-batt` (e.g. `Lightning Strikes Today-batt`) —
    reverse-looks up the sensor's battery field
  - `<sensorKey>-batt` (e.g. `lightning_distance-batt`) — same,
    works directly with AWN keys
  - `<battery field>` (e.g. `batt_lightning`) — direct match on the
    raw AWN battery field name

  Primary use case: working around upstream AWN API bugs that
  report a probe as low-battery even with known-good cells — the
  `batt_lightning` (WH31L) case documented in the README. Previously
  the only options were to live with the spurious low-battery
  notifications or to hide the entire lightning accessory family
  via the existing accessory-exclusion path.

- **Embed-mode diagnostic logging.** Every embed-mode tile name
  update now emits a single `[embed-diag]` info-level line capturing
  the decision state (current ConfiguredName, lastSet, userRenamed,
  newName, willUpdate). Goal is to characterize the root cause of
  solmssen's reported "tile gets reassigned to default room when
  the value changes" symptom that pushed him off embed mode in
  beta.22. Static-mode users see none of these lines (embed mode is
  opt-in).

### Changed

- **Help text added under the Extended Sensors display-mode dropdown**
  warning users that embed mode may cause Apple Home to reassign
  tiles to the bridge's default room on every value change.

## [1.5.0-beta.23] — 2026-06-14

### Fixed

- **`Cannot read properties of undefined (reading 'toFixed')` crash
  when LightningDistanceAccessory is being added as a new accessory.**
  Reported by solmssen on beta.22 after a clean-slate reset that
  promoted Lightning Distance from cached to fresh-add. Root cause
  was a constructor-ordering bug: `ExtendedSensorBase`'s constructor
  ended with a `setValue(cachedValue)` call to seed the tile, but
  subclasses set their unit/formatter state via `this.field = ...`
  lines that run AFTER `super()` returns. So `setValue` →
  `formatValue` would dereference an undefined `this.distanceUnit`,
  pass it to `convertDistance()`, which is a switch with no default
  case and returns `undefined`, then `.toFixed()` crashes the
  `discoverDevices` for-loop — silently dropping every accessory
  that was supposed to be registered after Lightning Distance
  (AQIN in solmssen's case). The same ordering bug existed in
  Wind / Pressure / Rain but didn't crash because their convert
  functions tolerate undefined; only Distance and Speed switches
  fall through to undefined.

  Fix: move the seed-from-cache step out of the base-class
  constructor and into the platform layer (`discoverDevices()`),
  AFTER the subclass constructor has fully completed and its
  fields are populated. Also wrapped the seed call in a try/catch
  as defense-in-depth — a future similar bug would now produce a
  one-line warn rather than halting platform setup.

## [1.5.0-beta.22] — 2026-06-13

### Changed

- **Documented the "clean slate" use of `stationFilter`.** Setting a
  deliberately non-matching value deregisters every accessory from
  that platform instance — this is the intended behavior of the
  filter and is also useful as a no-filesystem way to wipe and
  re-discover accessories without manually touching the cache file.
  Config form description, help text, and MultiHome.md
  troubleshooting entry all now mention this explicitly, with the
  obvious caveat that HomeKit state (room assignments,
  automations, custom names) is lost during the wipe as it would
  be on any HomeKit accessory removal.

## [1.5.0-beta.21] — 2026-06-13

### Changed

- **Renamed the Station Filter form field to "Include Only These
  Stations (allowlist)"** to match the existing "Include Only These
  Sensors (allowlist)" pattern. Logic is unchanged. The previous
  "Station Filter" label was ambiguous — "filter" can colloquially
  mean either an allowlist (keep matching) or a blocklist (drop
  matching), and the first-user test made clear the latter reading
  was the natural one. New title and description make the allowlist
  semantics unmistakable by direct analogy to the per-sensor field
  below it. The underlying config key (`stationFilter`) is
  unchanged so existing configs continue to work.

## [1.5.0-beta.20] — 2026-06-13

### Changed

- **Station Filter moved above Exclude / Include Only in the config
  form.** It operates at the station level — dropping whole stations
  before any per-sensor processing — so the coarser-granularity
  filter belongs above the per-sensor allowlist/blocklist. Help text
  updated to clarify the relationship.

### Added

- **Positive confirmation log line when `stationFilter` is active.**
  Without it, a user whose filter matches every available station
  saw zero "filtered out" log lines and reasonably assumed the
  filter wasn't doing anything. New line fires once per session on
  the first tick where at least one station passes the filter:
  `stationFilter active: [Fairhills WS-2000] — 1 of 1 station(s) passed`.
  Complements the existing one-time warn when the filter matches
  zero stations.

## [1.5.0-beta.19] — 2026-06-13

### Added

- **Discovered-station log line at startup.** On the first successful
  poll of the AWN API, each station is announced once with its name
  (AWN's `info.name`), MAC address, and sensor count, e.g.
  `Discovered station "Fairhills WS-2000" (MAC: AA:BB:CC:11:22:33) — 19 sensor fields reported`.
  This is the primary way users find the exact string to put in their
  new `stationFilter` config (beta.18) — the value isn't surfaced
  anywhere else in the Homebridge UI. Logged BEFORE filtering so users
  running the plugin to discover stations see every one AWN reports,
  not the filtered subset.

## [1.5.0-beta.18] — 2026-06-13

### Added

- **`stationFilter` config field** for multi-Home setups. Accepts an
  array of station names or MAC addresses; when set, the platform
  instance only sees the matching stations (everything else is
  dropped before sensor processing). Combined with multiple platform
  instances in `config.json` and Homebridge child bridges, this lets
  users with stations in physically separate places (main house +
  cabin, primary + rental property, etc.) expose each station in its
  own HomeKit Home. The plugin's existing `isMultiStation` logic is
  recomputed after filtering, so an instance reduced to one station
  gets bare tile names ("Outdoor Temperature") while an instance
  retaining multiple stations keeps the disambiguating prefix
  ("Cabin Outdoor Temperature").

- **`MultiHome.md`** — full walkthrough for splitting stations
  across HomeKit Homes: concepts, step-by-step setup, variations,
  troubleshooting. Linked from `README.md` Features section and
  from the `UPGRADING.md` FAQ.

- **`docs/future/tabbed-config-ui.md`** — design proposal for a
  custom Angular-based plugin config UI with per-Home tabs that
  would replace the JSON-Config workflow for multi-Home setups.
  Deferred for now (zero confirmed multi-Home users, ~2-3 weeks of
  dev effort); revisit triggers documented in the proposal.

### Changed

- Tile names are now bare in a multi-Home setup where each platform
  instance has been filtered down to one station, even though the
  underlying AWN account has multiple stations. Previously the
  `isMultiStation` boolean used the unfiltered AWN response and
  multi-Home users would have seen station-prefixed tile names in
  each Home. The recompute happens silently — no migration needed
  for users not using `stationFilter`.

## [1.5.0-beta.17] — 2026-06-13

### Fixed

- **AccessoryInformation Name characteristic now updated on every
  restore, not just when displayName changes.** beta.16's fix only
  updated the HAP-side Name characteristic inside the
  `if (displayName !== device.displayName)` block. But users who
  had run beta.15 (where `accessory.displayName` got updated but
  the Name characteristic didn't) ended up with cached accessories
  where `displayName` was already correct (so the migration block
  didn't fire) but the underlying Name characteristic was still
  stuck at the original long value. Result: tiles in Apple Home
  still showed the long name. Fix: pull the AccessoryInformation
  Name update OUT of the conditional and run it on every restore.
  Idempotent; ensures the Name characteristic matches displayName
  on every plugin restart, regardless of whether displayName itself
  changed.

## [1.5.0-beta.16] — 2026-06-13

Two follow-up fixes for issues observed during beta.15 testing.

### Fixed

- **Apple Home tile names didn't update for accessories the user
  had never renamed.** The beta.15 migration step updated
  `accessory.displayName` and called `updatePlatformAccessories`,
  which writes to Homebridge's cached accessory file. But Apple
  Home reads the **`AccessoryInformation.Name` characteristic** —
  a separate HAP-level field that wasn't touched by the
  `displayName` assignment. Result: tiles for accessories the user
  had renamed in Apple Home (where Apple Home uses `ConfiguredName`,
  which the plugin DOES update on every restart) saw the new short
  name; tiles for accessories the user had never renamed stayed on
  the original long name. Fix: explicitly call
  `updateCharacteristic(Characteristic.Name, ...)` on the
  AccessoryInformation service in the migration block so the
  HAP-side Name characteristic gets the new value too.

- **`excludeSensors` / `includeOnly` back-compat for entries whose
  station name had non-alphanumeric characters.** beta.15 added a
  `prefixedForm` to `matchCandidates` for backward compatibility
  with existing config entries that referenced the pre-beta.15
  long-form displayName. But it computed `prefixedForm` from AWN's
  RAW `info.name`, while the pre-beta.15 displayName had run that
  through `hapClean` (stripping hyphens, periods, etc.). User
  configs whose old-form excludes used the cleaned station name
  (e.g. "Fairhills WS 2000 Indoor Feels Like" for a station whose
  raw AWN name is "Fairhills WS-2000") therefore didn't match the
  back-compat candidate, and previously-excluded sensors started
  re-appearing as new accessories on the first beta.15 restart.
  Fix: apply `hapClean` to the prefixedForm so it produces the
  same cleaned string the old displayName produced.

## [1.5.0-beta.15] — 2026-06-13

### Changed

- **Single-station setups now get clean Apple Home tile names by
  default.** `composeDisplayName()` previously always prepended the
  station name (`info.name` from AWN), producing tiles like
  `"Fairhills WS 2000 Outdoor Temperature"` — which Apple Home
  truncated in the tile view to `"Fairhills WS 20..."`. For users
  with a single AWN station (the common case), the prefix was just
  noise. Now: when the AWN payload contains exactly one device, the
  accessory displayName is just the bare sensor label
  (`"Outdoor Temperature"`); multi-station accounts continue to get
  the station prefix for disambiguation, same as before.

  **Existing accessories migrate automatically** on the first
  beta.15 restart: the existing rename path in `discoverDevices`
  detects the displayName change and calls
  `updatePlatformAccessories` to push the new name to HomeKit. Users
  who previously renamed tiles manually in Apple Home keep their
  custom names — Apple Home preserves user renames over plugin-side
  updates.

- **Why the change matters now.** The maintainer's beta.14 diagnostic
  proved that Apple Home only honors the service `ConfiguredName`
  characteristic for tile rendering AFTER the user explicitly
  renames the accessory in the Home app — at which point Apple Home
  flips an internal "user-confirmed" flag and starts using
  `ConfiguredName`. Until then, the tile reads `accessory.displayName`
  directly, regardless of what the plugin sets `ConfiguredName` to.
  So the fix had to happen at the displayName level, not the service
  Name level.

  For the multi-station case, the prefix is still load-bearing —
  without it, two stations would produce indistinguishable
  "Outdoor Temperature" tiles. Branching on `json.length > 1` in
  `parseDevices` correctly distinguishes the two paths.

- **Backward compat for excludeSensors / includeOnly.** The
  `matchCandidates` array now includes BOTH the new unprefixed
  displayName AND the legacy prefixed form, so a user with an
  existing config entry like `"Fairhills WS 2000 Indoor Dew Point"`
  continues to filter correctly even though the new displayName is
  just `"Indoor Dew Point"`.

## [1.5.0-beta.14] — 2026-06-13

### Changed

- **Excluded sensors now log at info level the first time they're
  filtered each session.** Previously a sensor on the `excludeSensors`
  blacklist (or filtered out by the `includeOnly` allowlist) was only
  logged at debug level, invisible at default verbosity. After
  Homebridge restart, users had no way to confirm their filter
  config was being applied without enabling debug logs.
  Now: on the first poll/realtime tick after restart, each filtered
  sensor produces one info-level line:
    `Excluding <displayName>: matched Exclude Sensors list`
    `Excluding <displayName>: not in Include Only These Sensors allowlist`
  Subsequent polls stay at debug level, so the log doesn't flood
  every 2 minutes. Pattern modeled on `homebridge-smartthings-oauth`'s
  startup "Ignoring X because..." lines.

## [1.5.0-beta.13] — 2026-06-11

Acts on solmssen's beta.12 feedback. Becomes the new GA candidate.

### Changed

- **One Battery sub-service per physical probe, not one per
  accessory.** Previously every sensor whose probe reported a
  battery field got its own HomeKit Battery sub-service — a fully
  populated WS-2000 with 4 channels + AQIN + WH31L produced 30+
  battery tiles, which solmssen reasonably called "overwhelming."
  Each `batt*` field now attaches to exactly ONE canonical sensor:
    - `battout` → Outdoor Temperature
    - `battin` → Indoor Temperature
    - `batt1..10` → channel temperature for that channel
    - `batt_co2` → CO2 (AQIN)
    - `batt_lightning` → Lightning Strikes Today
  A typical fully-populated station now shows ~7 battery tiles
  instead of 30+, while still surfacing every physical probe's
  battery state to Apple Home automations.
  On upgrade, stale Battery sub-services attached to non-canonical
  accessories by beta.1 → beta.12 are removed automatically on the
  next plugin restart.

- **Renamed "WH57" → "WH31L" across docs and code comments.** AWN
  catalogs the lightning sensor as the WH31L; Ecowitt catalogs the
  same hardware as the WH57. Since this plugin is specifically for
  AWN users, use AWN's name as primary with the Ecowitt name as a
  cross-reference. Affects README, UPGRADING, schema helpvalue,
  and two code-comment files. CHANGELOG entries from prior betas
  retain the old "WH57" name as historical record.

### Added

- **FAQ entry documenting the AWN lightning-battery API quirk.**
  AWN's API has been observed to report `batt_lightning = 0`
  (which the plugin reads as "low") even when the WH31L has
  known-good batteries and AWN's own dashboard shows the sensor as
  healthy. The plugin reads what AWN's API returns; the
  discrepancy is upstream. UPGRADING's Troubleshooting / FAQ
  section now documents this so users hitting the same problem
  don't burn batteries trying to fix it. README also adds a short
  note in the Battery status section.

## [1.5.0-beta.12] — 2026-06-10

Branding + assets refresh. No code or behavior changes.

### Changed

- **New plugin icon** (`images/icon.png`). House outline with weather
  data flowing through plus a small sensor dot. Transparent
  background; pngquant-optimized to 72 KB. Replaces the four
  inherited upstream variants (`homebridge_ambient_weather*.png`).
- **Display name shortened** from "Homebridge Ambient Weather
  Sensors (bcourbage fork)" to **"Ambient Weather"**. Matches the
  Homebridge convention used by other plugins (Eve, Tado, Honeywell,
  UniFi Protect, etc.) of stripping "Homebridge" and generic suffix
  words. The npm scope `@bcourbage/...` continues to disambiguate
  from upstream.
- **README image URL switched to a relative path** (`images/icon.png`)
  so the icon renders correctly on GitHub branch views, npmjs.com,
  and local Markdown previews without depending on which branch the
  image happens to be on.

### Added

- **Trademark notice** in the README acknowledging "Ambient Weather"
  is a trademark of Ambient Weather, Inc. and that this plugin is an
  independent, unofficial integration (nominative fair use). Standard
  defensive practice for unofficial integration plugins.

## [1.5.0-beta.11] — 2026-06-09

Config-form-only patch — no code or behavior changes.

### Changed

- **extendedSensors master-toggle description** moved into a
  `<help>` block in the form array. This was the one I missed in
  beta.10's "move all field descriptions" pass — it was the master
  toggle description ("Adds wind, rain, barometric pressure...")
  that still rendered at the smaller schema-description font.
  Programmatically confirmed afterwards that no non-array property
  in the schema retains a `description` field: all explanatory
  text lives in form-array `<help>` blocks.

## [1.5.0-beta.10] — 2026-06-09

Config-form-only patch — no code or behavior changes. Completes the
font-size unification.

### Changed

- **All remaining field-level help text now renders in the larger
  helpvalue font.** Beta.9 caught the units + dataSource holdouts;
  beta.10 catches the rest: name, apiKey, applicationKey, co2Sensors,
  airQualitySensors, lightningSensors (WH57 caveat), and the
  multi-paragraph extendedDisplayMode walkthrough. All seven were
  schema-level descriptions rendering at the small caption size —
  now migrated to explicit `<help>` items in the form array,
  matching every other description in the form.

  The schema is now description-free for individual properties
  (other than the two array-typed fields where HB UI X auto-renders
  descriptions at the larger size anyway). All explanatory prose
  lives in the form array as `<help>` blocks.

## [1.5.0-beta.9] — 2026-06-09

Config-form-only patch — no code or behavior changes.

### Changed

- **Unit-field and Data Source descriptions render in the larger
  helpvalue font** instead of the smaller schema-description font.
  Beta.8 missed these on the cleanup pass — the per-unit "Selecting
  None uses the default (...)" notes and the Data Source polling vs
  realtime explanation were still using schema-level `description`,
  which HB UI X renders smaller than `helpvalue` blocks.

  Fix: removed the per-unit "Selecting None" lines entirely (the
  units section preamble already covers None behavior), and moved
  the Data Source description into a `<help>` block in the form
  array.

## [1.5.0-beta.8] — 2026-06-09

This release replaces the unreachable "blank threshold = hidden"
mechanic from beta.6 with explicit per-threshold enable checkboxes,
and unifies all extended-sensor help text to render at the larger
font size used elsewhere in the form. Becomes the new GA candidate.

### Changed

- **Per-threshold enable checkbox.** Each of the 6 user-configurable
  thresholds (controlling 8 accessories — wind speed, wind gust pair,
  rain rate, UV, lightning distance, pressure pair) now has a paired
  enable checkbox in the **Motion thresholds for extended sensors**
  section. When the checkbox is unchecked, the corresponding sensor
  accessory is hidden from HomeKit entirely and the threshold field
  is hidden from the form. Default is ON (checked) for all six, so
  upgrading from beta.6 → beta.8 produces no behavior change for
  users who hadn't tried to use the blank-threshold mechanic.

  This works around homebridge-config-ui-x re-injecting schema
  default values into number fields after save — the beta.6 "leave
  blank to hide" approach was non-functional because the form never
  let "blank" persist.

  Workaround for "show value without trigger" unchanged: keep the
  checkbox enabled, set the threshold to an unreachable value
  (99999 mph for wind, 99 for UV, 0 for inverted-direction pressure
  and lightning-distance sensors).

- **Consistent font sizes for help text.** Per-threshold descriptions
  ("Fires when sustained wind speed equals or exceeds this...") and
  the units section preamble now render in the larger `helpvalue`
  font instead of the smaller per-field-description font. Achieved
  by moving the descriptions out of the schema's individual
  property descriptions and into explicit `<help>` items in the
  form array. Matches the larger size that section preambles
  (Motion thresholds / Display units / Exclude Sensors / Include
  Only) already used.

### Implementation

- `config.schema.json` adds six new booleans inside the `thresholds`
  object: `windSpeedEnabled`, `windGustEnabled`, `rainRateEnabled`,
  `uvEnabled`, `lightningDistanceEnabled`, `pressureEnabled`. All
  default `true`. Each threshold value field has a
  `condition.functionBody` that hides it when its enable peer is
  explicitly false.
- `platform.ts#determineSensorType` checks each `*Enabled` flag and
  returns `NOT_SUPPORTED` when false, replacing the previous
  blank-threshold check. Default-true semantics: only an explicit
  `false` disables.
- Form array restructured: per-threshold sections now interleave
  enable checkbox → threshold field → help paragraph (all three
  hidden when the enable is unchecked).

## [1.5.0-beta.7] — 2026-06-09

Config-form-only fix. No code or behavior changes.

### Fixed

- **Exclude Sensors and Include Only These Sensors lists rendered
  with no input fields** in homebridge-config-ui-x. The custom
  `form` array added in beta.3 referenced these array-typed fields
  by string only; angular-schema-form's custom-layout mode requires
  an explicit `items` template to render the array's "Add" button
  and per-entry input row. Without it, only the title and
  description appeared — users couldn't add or edit allowlist /
  blocklist entries through the form (though the underlying
  config was still readable if edited by hand).

  Fix: replaced the bare string references in the form array with
  explicit array form objects containing `items: [{ key, type,
  placeholder }]` so the rendering re-finds its way to the
  per-entry input.

  Pre-existing entries are preserved — this is a render-side bug
  only; the schema and saved config were always intact.

## [1.5.0-beta.6] — 2026-06-09

This release refines the "blank threshold" semantics to match the
natural user intuition. Becomes the new GA candidate (supersedes
beta.5).

### Changed (behavior change for any user who set thresholds in beta.5)

- **Blank threshold field now hides the accessory from HomeKit
  entirely** instead of showing it with a permanently-false motion
  state. The previous behavior (introduced in beta.2) made it easy
  to disable a trigger but cluttered Apple Home with useless always-off
  tiles. The new behavior is what most users actually want when they
  blank a threshold: "I don't care about this sensor, please remove it."

  Affects the 6 user-configurable thresholds (8 accessories total):

  | Threshold | Sensors hidden when blank |
  |---|---|
  | `windSpeedMph` | Wind Speed |
  | `windGustMph` | Wind Gust, Max Daily Gust |
  | `rainRateInHr` | Rain Rate |
  | `uv` | UV Index |
  | `lightningDistanceMi` | Lightning Distance |
  | `pressureInHg` | Pressure Sea Level, Pressure Station |

  Sensors without a user-configurable threshold (wind direction +
  10-min avg, rain accumulation totals, time-since-event sensors,
  lightning strike counts) continue to appear when their category
  toggle is on — use `excludeSensors` to hide them individually.

  **Workaround for "show the value but don't trigger automations":**
  set the threshold to a value the sensor can never reach (e.g.
  `99999` for wind/rain, `99` for UV, `0` for the inverted-direction
  pressure and lightning-distance sensors since both are always
  positive). Schema and UPGRADING.md document this.

## [1.5.0-beta.5] — 2026-06-09

This release fixes a latent runtime bug that affected every Extended
Sensor accessory. Becomes the new GA candidate (supersedes beta.4
which had the same bug from beta.0).

### Fixed

- **"Cannot read properties of undefined (reading 'updateValue')"
  error when adding any Extended Sensor accessory.** ExtendedSensorBase
  called `service.updateCharacteristic(UUID_STRING, value)` to update
  the custom Value / Intensity / Last Updated characteristics on every
  poll tick. HAP-NodeJS's `Service#getCharacteristic(string)` overload
  matches by `displayName` only — *not* by UUID. Since our
  characteristics have displayNames like "Value" and "Last Updated"
  (not their UUIDs), the lookup returned undefined and then the
  internal `.updateValue()` call threw.

  Only manifested on Extended Sensors (wind / rain / pressure / UV /
  lightning) — native HAP services use the constructor-form lookup,
  which works correctly. The bug was latent from beta.0 because none
  of the betas were tested with an Extended Sensor enabled until now.

  Fix: cache the Characteristic instances at construction time (via
  the new `attachCustomCharacteristic` helper) and call
  `.updateValue()` directly on the cached refs, bypassing the
  string-lookup path entirely. The MotionDetected characteristic
  continues to use the standard service helper since
  constructor-form lookup works for stock HAP characteristics.

## [1.5.0-beta.4] — 2026-06-09

Housekeeping release — no code or behavior changes. This is the
release intended to roll forward to v1.5.0 GA once beta testing
completes; solmssen and other testers should validate this exact
build.

### Changed

- **Repo self-containment.** README header image URL switched from
  `raw.githubusercontent.com/peledies/...` to `.../bcourbage/...`
  Previously the README depended on upstream's repo staying online
  for its header image; now we serve our own copy from `images/`.
- **UPGRADING.md label parity.** The closing sentence of the "Pick
  a display mode" section used informal shorthand ("generic names"
  / "embed mode") instead of the actual dropdown labels. Updated
  to quote the labels verbatim so users skimming the doc can find
  the exact strings in the settings form.

### Removed (upstream-template vestiges)

- `README_DEV.md` — Generic "how to develop a Homebridge plugin"
  boilerplate inherited from the upstream template. Nothing
  project-specific; nothing else referenced it.
- `nodemon.json` + the `watch` npm script + the `nodemon`
  devDependency — Auto-restart dev loop that this project's
  edit-on-Heracles → push → CI workflow never used. Drops 18
  transitive packages from `npm install`.
- `.vscode/settings.json` `workbench.colorCustomizations` block —
  Upstream maintainer's personal terminal/title-bar colors. Kept
  the universally-useful editor settings (LF line endings,
  ESLint-on-save, 140-char ruler).
- `.DS_Store` — macOS Finder cache cleanup. Already in
  `.gitignore`; was never tracked.

## [1.5.0-beta.3] — 2026-06-09

Config-form-only release — no code or behavior changes. All fixes are
to how the settings form renders in homebridge-config-ui-x.

### Changed

- **Display-mode picker is a dropdown again.** Reverted from the
  radio-button widget tried in beta.2 — angular-schema-form's radio
  rendering left the unselected option indented and misaligned in
  HB UI X, which looked worse than the dropdown's "None" placeholder.
- **Description text now uses HTML line breaks.** Previously
  paragraph breaks in the extendedDisplayMode description were
  written as `\n\n` and rendered as a single run-on block. They're
  now `<br><br>` and render as actual paragraphs, matching the
  intended structure (generic-names paragraph / live-value paragraph
  / rename-and-log-note paragraph).
- **"Motion thresholds" and "Display units" section help text now
  appears at the top of each section** — directly under the section
  title rather than after all the fields. Achieved via a top-level
  `form` array with explicit `fieldset` blocks and `helpvalue`
  items so layout is deterministic instead of inferred from the
  schema.
- **Dropdown "None" placeholder documented.** Every `oneOf`
  dropdown (extendedDisplayMode, units.*, dataSource) now ends its
  description with "Selecting None uses the default (...)" so users
  who see the placeholder understand it's safe — picking None is
  equivalent to picking the default.

## [1.5.0-beta.2] — 2026-06-09

### Fixed

- **Plugin-name warning in Homebridge logs.** Every newly-registered
  accessory was logging *"A platform configured a new accessory under
  the plugin name 'homebridge-ambient-weather-sensors'. However no
  loaded plugin could be found for the name!"* The `PLUGIN_NAME`
  constant was inherited unchanged from upstream and pointed at the
  unscoped npm name. v1.5.0-beta.2 fixes it to match the scoped
  package name. On first restart Homebridge auto-migrates any
  cached accessories under the old name (one-time
  "Plugin association is now being transformed!" log line per
  accessory), then the warnings stop permanently. Latent bug since
  the v1.4.0 fork; surfaced by the new accessories in beta.0.

### Changed

- **Realtime reconnect logs are quiet during healthy operation.**
  AWN cycles long-lived websockets every 45m-3h as part of normal
  server-side grooming. Previously every cycle emitted 5 info-level
  log lines (~40/day on a healthy box). Now: clean disconnects
  (`transport close`, `ping timeout`, `io server disconnect`) and
  their follow-on reconnect cycle log at **debug** instead. Real
  anomalies (transport errors, connect errors, stalled subscriptions)
  still surface at warn/error. The first connect of a session always
  logs at info so users see realtime start. Net effect: typical day's
  log goes from ~40 reconnect lines to 0.
- **Threshold help text consolidation.** The "Motion thresholds for
  extended sensors" section header now covers the "blank = disabled"
  behavior in one place. Per-field descriptions are back to just
  describing each threshold and its default; no more repeated "Leave
  blank to disable..." line per field.

### Added

- **Blank threshold field disables the motion trigger entirely.**
  Previously a cleared threshold silently fell back to the schema
  default — same trigger, just at the default value. Now: clearing
  a field means no automation trigger at all (`MotionDetected`
  stays permanently false), but the accessory still exists so the
  reading remains visible in Eve / Controller for HomeKit. Useful
  for users who want to see a value without it driving automations.
  Applies to all 6 configurable thresholds (wind speed, wind gust,
  rain rate, UV, lightning distance, low pressure). Schema defaults
  unchanged — first-install behavior is identical to beta.1.

### Documented

- **`UPGRADING.md`** — full step-by-step upgrade guide from v1.4.x.
  Covers what appears automatically (battery sub-service, feels-like,
  dew point), how to opt into the Extended Sensors and pick a display
  mode, threshold tuning, example automations (close awning, skip
  sprinkler, lightning alert, low-battery reminder), the battery
  coverage table, and a troubleshooting / FAQ section.
- README adds a "What's New in v1.5.0" callout near the top pointing
  to UPGRADING.md.
- `excludeSensors` and `includeOnly` descriptions explicitly call out
  that they work on Extended Sensors too, with friendly-name and
  raw-AWN-field examples for both natives and extended.
- Display-mode dropdown switched to radio buttons (no spurious "None"
  option), and the description text uses the actual radio labels
  ("Show generic names" / "Show live value") rather than invented
  "Static mode" / "Embed mode" terms.
- Corrected wording about user-renamed tiles: the plugin already
  detects renames via `isUserRenamed()` and stops overwriting; the
  earlier "plugin will overwrite your custom name" copy was wrong.

## [1.5.0-beta.1] — 2026-06-09

### Added — Battery sub-service for low-battery automations

Every sensor accessory whose physical probe reports a battery in
AWN's payload now exposes a HomeKit `Battery` sub-service. Apple
Home and every third-party HomeKit client trigger their built-in
low-battery push notifications off this. The user-facing automation
*"When Outdoor Temperature battery is low, remind me to replace
it"* now works without any third-party app.

Per-probe coverage (driven by AWN's `batt*` fields):

| Probe | AWN field | Sensors affected |
|---|---|---|
| Outdoor base | `battout` | Outdoor temp/humidity, feels-like, dew point, solar, UV, wind, rain |
| Indoor display | `battin` | Indoor temp/humidity, feels-like, dew point, both pressures |
| WH31 probe 1..N | `batt1`..`battN` | Temp/humidity/feels-like/dew-point per channel |
| AQIN module | `batt_co2` | CO2, PM2.5, PM10, AQIN-housing temp/humidity |
| WH57 lightning | `batt_lightning` | Strike count, distance, time-since-last |

Probes that AWN doesn't report a battery for (e.g. outdoor PM2.5 on
some firmwares) get no Battery sub-service — better than showing a
misleading "battery normal" reading we can't actually confirm.

### Implementation notes

- `src/batteryFields.ts` maps each AWN sensor key to its battery
  field name (a 35-entry table + numbered-probe regex + AQIN suffix
  rule). Inverts AWN's 0=low/1=good polarity to HomeKit's
  true=low/false=normal at the data-source boundary.
- `src/batteryService.ts` exports a single `setupBatteryService`
  helper that the existing 5 native accessory classes and the
  ExtendedSensorBase all call from their constructor. Returns a
  setter callback or undefined; clean polymorphic shape.
- `DEVICE` type gains an optional `batteryLow?: boolean` field;
  presence on the cached accessory context drives whether to attach
  the sub-service after a Homebridge restart.
- `SensorAccessory` interface gains an optional `setBatteryLow`
  method. The distribute loop pushes both `setValue` and
  `setBatteryLow` per tick.
- Realtime path: `RealtimeUpdate` gains `batteryLow?: boolean`;
  realtimeSource looks up each sensor's battery field from the same
  lastData payload it's iterating, so realtime users see the same
  battery updates as polling users.
- HomeKit Battery service requires three characteristics; we set
  `ChargingState=NOT_CHARGEABLE` (correct — these are battery-only
  sensors), `BatteryLevel=5 or 100` (sentinel since AWN reports
  only low/good, not a percentage), and `StatusLowBattery`
  (the one that drives Apple Home's notifications).

## [1.5.0-beta.0] — 2026-06-09

First beta of v1.5.0. **Not the `latest` npm tag — install with
`npm install -g @bcourbage/homebridge-ambient-weather-sensors@beta` to
opt in.** Users on the `latest` tag stay on v1.4.x until v1.5.0 GA.

### Added — Extended Sensors (off by default)

Apple Home doesn't natively support wind, rain, barometric pressure,
UV, or lightning. v1.5.0 exposes them via the same pattern the
verified [homebridge-ecowitt-weather-sensors][ecowitt] plugin uses: a
`MotionSensor` per datapoint with three custom characteristics
(`Value`, `Intensity`, `Last Updated`). Apple Home users get on/off
motion tiles driven by configurable thresholds — useful for stock
Home automations like *"When Wind Speed motion detected, close the
awning"*. The live numeric value renders in Eve and Controller for
HomeKit.

- **Wind**: speed, gust, max-daily gust, direction (instantaneous +
  10-minute average)
- **Rain**: hourly rate, event/daily/weekly/monthly/yearly totals,
  time-since-last-rain
- **Barometric pressure**: relative (sea-level corrected) and
  absolute (raw at station altitude). Inverted threshold — low
  pressure triggers the motion event
- **UV index**: with EPA bucket label (Low / Moderate / High / Very
  High / Extreme)
- **Lightning**: today's strike count, this-hour's strike count,
  distance to last strike (inverted threshold — close strikes
  trigger), time since last strike. Requires a WH57-compatible
  sensor on the station

Configuration: master toggle "Enable Extended Sensors" (default off)
gates per-category sub-toggles (wind, rain, pressure, UV, lightning).
Display mode is selectable between **static names** (recommended —
"Wind Speed" tile with motion state, value visible in Eve) and
**embed live value in tile name** ("Wind Speed 14 mph" updating on
each reading). Per-sensor thresholds and display units (mph/kph/mps/
kts, in/mm, inHg/hPa, mi/km) all configurable.

### Added — Native bonus sensors

- **Feels-like** temperature (heat index / wind chill) per probe —
  AWN pre-calculates `feelsLike`, `feelsLike1..N`, `feelsLikein` and
  these now expose as standard `TemperatureSensor` accessories
  alongside the raw temperatures.
- **Dew point** per probe — same pattern, `dewPoint`,
  `dewPoint1..N`, `dewPointin` as `TemperatureSensor`.

### Implementation notes

- Custom HAP characteristics use fresh UUIDs owned by this plugin
  (not Eve's, not Ecowitt's) — third-party HomeKit apps render them
  via the characteristic display name.
- `Service#testCharacteristic` guard in the base class makes
  re-attachment idempotent across child-bridge restarts.
- Pre-conversion of `lastRain` ISO timestamps to Unix-ms in
  `parseDevices()` keeps the `SensorAccessory#setValue(raw: number)`
  interface uniform.
- Verified-plugin status preserved: extended sensors are off by
  default, so v1.4.x users see zero behavior change. Existing
  homebridge/plugins verification checks still pass.

### Want to test?

Issue [#1][issue1] is the live thread for v1.5.0 beta testing. If
you have a station with sensors this plugin previously skipped
(particularly lightning), help is welcome — install the beta tag
and report findings.

[ecowitt]: https://github.com/rhockenbury/homebridge-ecowitt-weather-sensors
[issue1]: https://github.com/bcourbage/homebridge-ambient-weather-sensors/issues/1

## [1.4.3] — 2026-05-30

### Fixed
- **Empty "What's new" pane in homebridge-config-ui-x after an update.**
  This release introduces `CHANGELOG.md` (shipped in the npm tarball, read
  by the HB UI update dialog) and extends the GitHub Actions release
  pipeline to create a GitHub Release with the relevant changelog section
  on every `v*` tag push. From this version forward, the HB UI "What's
  new" pane will be populated.
- **Invalid JSON Schema** in `config.schema.json` — individual property
  schemas previously used `"required": true | false`, which is not valid
  JSON Schema (`required` must be an array of property names at the
  parent object level). Replaced with a top-level
  `"required": ["apiKey", "applicationKey"]` and removed the boolean
  `required` from every individual property. Flagged by the
  homebridge/plugins verification check.

### Changed
- Genericized the example station name in the config form placeholder
  and docstrings (`Backyard Station` instead of the original maintainer's
  personal weather station identifier). No functional change.
- **`package.json` metadata** improvements (flagged by the
  homebridge/plugins verification check): added a `homepage` field
  pointing to the GitHub README, and expanded `keywords` from
  `["homebridge-plugin"]` to nine relevant terms (ambient-weather,
  weather-station, homekit, awn, websocket, co2, air-quality, pm2.5) so
  the package surfaces correctly in npm and HB UI plugin search.

## [1.4.2] — 2026-05-30

First public release of the `@bcourbage` soft fork of
[`homebridge-ambient-weather-sensors`][upstream] by Deac Karns. This
release rolls up every change that accumulated since upstream v1.3.2 while
upstream pull requests [#21][pr21] (Homebridge 2.x compatibility) and
[#22][pr22] (cache validity bug fix) remain unmerged.

### Added
- **Homebridge 2.x / HAP 2.x compatibility.** Plugin now starts cleanly on
  Homebridge 2 with no "Name does not conform" warnings.
- **CO2 sensor** support for the AWN AQIN family (`co2_in_aqin` and
  standalone `co2` fields).
- **PM2.5 and PM10 air quality sensors** with EPA-bucket-derived
  AirQuality enum.
- **Opt-in WebSocket realtime data source** (`dataSource: "realtime"`)
  via `rt2.ambientweather.net` — sensor updates as the station reports
  them instead of every 2 minutes. Bounded exponential reconnect (1s→60s)
  with heartbeat logging. Polling remains the default.
- **Multi-station naming.** Accessory display names now use the station
  name set in the AWN account (`info.name`) instead of bare MAC, e.g.
  "Backyard Station Indoor Temperature" instead of `84F3EB66D267-tempinf`.
- **Allowlist (`includeOnly`)** companion to `excludeSensors`. Set it to
  expose only specific sensors / stations; everything else is hidden.
- **Human-readable filter matching.** `excludeSensors` and `includeOnly`
  accept friendly names ("Indoor Temperature"), station names, raw AWN
  fields, MAC addresses, or full uniqueIds — case-insensitive,
  whitespace-trimmed.
- **Password masking** on API key and application key fields in the
  homebridge-config-ui-x configuration form (`x-schema-form: password`).
- **Per-sensor-type toggles** in the config: `temperatureSensors`,
  `humiditySensors`, `solarRadiationSensors`, `co2Sensors`,
  `airQualitySensors`.
- **Automated release pipeline** — GitHub Actions publishes to npm with
  provenance attestations on every `v*` tag push.

### Changed
- **Polling refactor.** A single platform-owned timer now fans out to all
  accessory wrappers instead of N parallel timers per accessory —
  eliminates the parallel-fetch race against AWN's 1 req/s rate limit
  that occasionally caused the disk cache to flap.
- **Disk cache removed.** No longer needed after the polling refactor;
  fewer moving parts, no cache-validity edge cases.
- **Solar radiation sensor:** dropped the `ProductData` characteristic
  abuse that previously displayed W/m² as a string. HomeKit now shows the
  native lux value; the README documents the W/m²↔lux conversion factor
  for users who want exact W/m².
- **Display names** simplified to `<station name> <sensor>` —
  city/state are no longer interpolated since HomeKit's room/home
  hierarchy already expresses location.

### Fixed
- **Cache validity race** (independent of upstream PR #22) where
  `Cache.isValid()` read `this.valid` before the async `fs.access`
  finished, so the first read after startup occasionally hit a stale
  flag.
- **`deregisterAccessories`** now matches by `uniqueId` so that
  accessories whose display names changed across upgrades are still
  matched and not duplicated.
- **API key leak** prevention: keys no longer flow into log lines on
  request failure.

### Maintenance
- Migrated to ESLint flat config (v9) + typescript-eslint v8.
- ESM build (`type: module`, NodeNext, `.js` suffixes on imports).
- Node 22.12+ / Node 24 supported; CI matrix runs both.
- TypeScript 5.7.

[upstream]: https://github.com/peledies/homebridge-ambient-weather-sensors
[pr21]: https://github.com/peledies/homebridge-ambient-weather-sensors/pull/21
[pr22]: https://github.com/peledies/homebridge-ambient-weather-sensors/pull/22
[1.5.0-beta.17]: https://github.com/bcourbage/homebridge-ambient-weather-sensors/releases/tag/v1.5.0-beta.17
[1.5.0-beta.16]: https://github.com/bcourbage/homebridge-ambient-weather-sensors/releases/tag/v1.5.0-beta.16
[1.5.0-beta.15]: https://github.com/bcourbage/homebridge-ambient-weather-sensors/releases/tag/v1.5.0-beta.15
[1.5.0-beta.14]: https://github.com/bcourbage/homebridge-ambient-weather-sensors/releases/tag/v1.5.0-beta.14
[1.5.0-beta.13]: https://github.com/bcourbage/homebridge-ambient-weather-sensors/releases/tag/v1.5.0-beta.13
[1.5.0-beta.12]: https://github.com/bcourbage/homebridge-ambient-weather-sensors/releases/tag/v1.5.0-beta.12
[1.5.0-beta.11]: https://github.com/bcourbage/homebridge-ambient-weather-sensors/releases/tag/v1.5.0-beta.11
[1.5.0-beta.10]: https://github.com/bcourbage/homebridge-ambient-weather-sensors/releases/tag/v1.5.0-beta.10
[1.5.0-beta.9]: https://github.com/bcourbage/homebridge-ambient-weather-sensors/releases/tag/v1.5.0-beta.9
[1.5.0-beta.8]: https://github.com/bcourbage/homebridge-ambient-weather-sensors/releases/tag/v1.5.0-beta.8
[1.5.0-beta.7]: https://github.com/bcourbage/homebridge-ambient-weather-sensors/releases/tag/v1.5.0-beta.7
[1.5.0-beta.6]: https://github.com/bcourbage/homebridge-ambient-weather-sensors/releases/tag/v1.5.0-beta.6
[1.5.0-beta.5]: https://github.com/bcourbage/homebridge-ambient-weather-sensors/releases/tag/v1.5.0-beta.5
[1.5.0-beta.4]: https://github.com/bcourbage/homebridge-ambient-weather-sensors/releases/tag/v1.5.0-beta.4
[1.5.0-beta.3]: https://github.com/bcourbage/homebridge-ambient-weather-sensors/releases/tag/v1.5.0-beta.3
[1.5.0-beta.2]: https://github.com/bcourbage/homebridge-ambient-weather-sensors/releases/tag/v1.5.0-beta.2
[1.5.0-beta.1]: https://github.com/bcourbage/homebridge-ambient-weather-sensors/releases/tag/v1.5.0-beta.1
[1.5.0-beta.0]: https://github.com/bcourbage/homebridge-ambient-weather-sensors/releases/tag/v1.5.0-beta.0
[1.4.3]: https://github.com/bcourbage/homebridge-ambient-weather-sensors/releases/tag/v1.4.3
[1.4.2]: https://github.com/bcourbage/homebridge-ambient-weather-sensors/releases/tag/v1.4.2
