Skip to content

Releases: dasimon135/ha-bluesight

v0.8.0 — one line on the dashboard, and a full read of the code behind it

Choose a tag to compare

@dasimon135 dasimon135 released this 19 Sep 23:34

One line on the dashboard, and a full read of the code behind it

Two things ship together. The card gains a layout that takes one row instead of a screen, and the integration went through a line-by-line review of everything it runs — Python, card and firmware — which found four bugs. One of them contradicted the README.

layout: tile — the whole fleet on one line

type: custom:bluesight-card
layout: tile
● BlueSight                              5/12 slots · No incidents

A coloured dot, the fleet's slot count, how many incidents are open. Tapping the line opens the full card in a popup, so the rack costs no room on the page until you ask for it; tile_tap: more-info opens Home Assistant's own dialog for the incident sensor instead.

The dot answers do I need to look and nothing else. Red is an incident that wastes a slot, amber is any other, green is a quiet fleet — and grey is the one the tile could not do without: the diagnostic is not answering. A green dot on a fleet whose diagnostic has stopped reporting would be the single lie a diagnostic card must never tell.

It is a button to the keyboard too: Tab reaches it, Enter or Space opens the popup, Escape closes it and hands the focus back.

A flashed proxy counted every failed pairing twice

The README, the ESPHome doc and the v1.5 design all say the storm evidence is replaced per proxy. The code never did it. On a proxy running the ESPHome component, one failed pairing was two events in the storm window — once from the firmware's SMP counter, once inferred from the slot it released — so the storm threshold was reached at half of what you set.

A proxy that reports SMP counters now feeds the window from those alone. The honest consequence is in Known limitations: on such a proxy, a connection that fails before pairing is even attempted (a timeout, a device out of range) is no longer a storm event. It is a pairing storm that is measured there, and only that. Proxies without the component are judged exactly as before.

Three more, all quieter

  • A scan-only proxy read 100 % saturated, forever. habluetooth registers a passive scanner with slots=0, free=0, and "no free slot" was the whole test. It has no slots to run out of, so sensor.<proxy>_saturation_24h now reads unknown for it.
  • binary_sensor.<proxy>_online could never be off. A proxy that dropped off left the snapshot entirely, so the sensor had two states, on and unavailable, and an automation waiting for off never ran. It is now off the moment the proxy is no longer a registered scanner. There is no grace period on the sensor — it reports the fact; the proxy offline incident still applies the patience, so an OTA update is a brief off and no alert.
  • A stalled or idle incident rewrote its attribute on every snapshot. Both details carried the measured seconds, so an open incident changed the incidents attribute — and a recorder row — every poll. They now state the threshold that was crossed (for over 180s), which is true for the whole life of the incident. This changes the wording of two published detail strings, in English and French; the notification, written once when the incident opens, still carries the measured number.

A proxy stays remembered — and can finally be retired

"A proxy seen online once is remembered for good" held until the next restart, and editing the options reloads the entry: a dead proxy's incident was one click from disappearing. Known proxies are now read back from the device registry at setup. The offline grace period restarts once Home Assistant has finished starting, so a slow ESPHome reconnect is not counted against the proxy.

The way out had to follow, because a retired proxy would otherwise bring its alert back at every restart — and its device had no Delete button at all:

  • the device of a proxy that is gone can be deleted from its page;
  • bluesight.forget_proxy now deletes that device too;
  • both refuse a proxy that is still a registered scanner.

If you replaced a proxy in the past and never called forget_proxy, its old BlueSight device is still in your registry, and this release will report it offline. That is the promise above being kept for the first time. Delete the device and it is gone for good.

Smaller things

  • A burst of allocation changes earns one snapshot a quarter of a second later, instead of one walk of the device registry per change.
  • The card is registered when dashboards are in YAML and resources in storage — the wrong Lovelace attribute was being asked.
  • Home Assistant logged that BlueSight used the device registry's deprecated mapping API (removal in 2027.9). It no longer does, on old and new cores alike.

Upgrading

Update the integration and reload the dashboard. No configuration change and no new option. No wire-format change and no firmware change since v0.6.1 — no reflash is needed; the pin moves with the release as always:

external_components:
  - source: github://dasimon135/ha-bluesight@v0.8.0
    components: [bluesight]

What to expect after the update, so none of it reads as a regression: an automation that reads detail for a stalled or idle incident sees the new wording; a flashed proxy reaches a storm later than before, at the threshold you actually configured; and the Online sensors go off rather than unavailable.

What was and was not run on hardware

Everything here ran on a real Home Assistant with four ESPHome proxies, across two restarts: it loads clean, the proxies keep their user-given names through the registry read-back, no false proxy offline follows a restart, and the deprecation warning is gone from the system log.

Not exercised on hardware, and covered by tests only: the keyboard path on the tile, deleting a proxy device, forget_proxy, a proxy genuinely going offline, and a scan-only proxy — that fleet has none.

v0.7.1 - The card can be themed

Choose a tag to compare

@dasimon135 dasimon135 released this 05 Sep 21:56
2147c25

The card can be themed

The card already read Home Assistant variables for every colour but two: the text painted over an incident badge (#fff on critical, #1a1a1a on warning). They are now --bluesight-on-status and --bluesight-on-warning, with the old values as fallbacks, so a warm or light theme can keep that text legible. A test scans the shipped file so a literal colour cannot creep back in.

The ESPHome component pins in the docs move to this tag. (#27)

v0.7.0 — pressure, before it becomes a fault

Choose a tag to compare

@dasimon135 dasimon135 released this 29 Aug 07:59
3cbfa33

BlueSight can now say something before anything breaks

Everything BlueSight raised until now was an autopsy. By the time an incident opens, a device has already gone unavailable — the tool explains what happened, after it happened.

A proxy at zero free slots is not broken. Every slot it holds is doing useful work, and no detector should fire. But the next device that needs that proxy will not get in, and it will go unavailable with no error and no log line — which is exactly the symptom this integration exists to explain. Saturation is that symptom, visible before it happens.

It is also the one signal nothing else in Home Assistant can produce: seeing it requires per-proxy slot accounting.

On the fleet this was written against, across 24 hours: one proxy's occupancy changed 435 times and it repeatedly sat at zero free, while two others stayed at zero slots used all day. Nothing anywhere reported that.

sensor.<proxy>_saturation_24h

Share of the last day the proxy spent with no free slot, per proxy, as a percentage.

Nothing detects on it, and that is deliberate rather than unfinished. A proxy dedicated to three permanent connections is saturated by design, and the point where busy becomes too busy is not knowable from a single fleet. Inventing a threshold now would repeat what idle_threshold_s had to be measured out of, where 300s was an argument and 1800s was a measurement. The reading ships first so a threshold can be chosen from data, on more installations than one.

Three numbers, because one hides too much — ten one-second squeezes and a single ten-minute lockout give the same percentage, and only the second is an outage:

  • longest_saturated_s — the worst single stretch
  • episodes — how many times it filled up
  • observed_s — how much evidence any of it rests on

The state is unknown rather than 0 before a proxy has been observed. Zero would claim a comfortable day for a proxy nobody has watched yet, which is at its most misleading on the first snapshot after a restart.

The window lives in memory and restarts with Home Assistant; the sensor's own recorder history does not, which is what makes a week of it worth reading.

Upgrading

Update the integration and reload the dashboard. One new sensor appears per proxy. No configuration change, no wire-format change, and no firmware change since v0.6.1 — the pin moves with the release as always:

external_components:
  - source: github://dasimon135/ha-bluesight@v0.7.0
    components: [bluesight]

No detection behaviour changes. Every incident raised before this release is raised after it, and no new one is introduced.

v0.6.5 — a dump that names the thresholds it ran at

Choose a tag to compare

@dasimon135 dasimon135 released this 28 Aug 18:52
d85d2f3

Diagnostics report the thresholds actually in force

A config entry's options holds only what the options dialog actually submitted. Every tunable is absent from it until someone opens that dialog and presses Submit — while being very much in force, because setup falls back to the constant.

The diagnostics dump reported that map verbatim, so it was silent about exactly those thresholds. Found on a live instance right after upgrading to v0.6.4: the entry was running at bond_threshold: 3 and the dump's options block did not mention it.

That failure is worst on the newest option and best on the ones nobody is asking about, which is the wrong way round for the one artifact attached to a bug report. The dump now merges the shipped defaults underneath the persisted values: a saved value still wins, and everything else reads as what the code is using.

The defaults table is written by hand rather than derived from the options schema, deliberately. The dump is downloaded when things are already broken, and running it through voluptuous would let a single out-of-range persisted value raise while a report is being collected. A test pins the table against the schema key for key instead, so the two cannot drift silently.

The ESPHome pin cannot go stale again

Both copies of the external_components snippet are hand-maintained, and both still said v0.6.3 on the day v0.6.4 shipped — so a reader pasting one flashed firmware a tag behind the integration they had just updated.

Every copyable snippet is now pinned to the manifest version by a test, which also refuses a snippet added later in a file the check does not cover: the failure mode is silence, so the file list is verified rather than trusted. The same treatment the card's CARD_VERSION already had, for the same reason.

Upgrading

Update the integration and reload the dashboard. No configuration change, no wire-format change, and no firmware change since v0.6.1 — the pin moves with the release as always:

external_components:
  - source: github://dasimon135/ha-bluesight@v0.6.5
    components: [bluesight]

No detection behaviour changes in this release. Incidents raised before it are raised after it.

v0.6.4 — a missing key is news while it is still happening

Choose a tag to compare

@dasimon135 dasimon135 released this 28 Aug 07:39
cc9c9ae

BOND_LOST now judges failures that are still happening

detect_bond_lost read the ESPHome firmware's SMP counter — monotonic since the proxy booted — and fired at count > 0. No window, no threshold, no decay. One refusal in a proxy's entire life opened an incident that closed only when that proxy rebooted.

Found on a live fleet. A thermostat connected and exchanging normally through the proxy that holds its bond was reported as a missing pairing key, and notified, because a different proxy had refused it five times at some point in the past. Nothing about that report was current, and nothing would ever retract it.

STORM, measured from the very same counter, never worked this way: its deltas go through a rolling window. Only one of the two halves of the measured evidence was on a clock.

BOND_LOST now reads that same window. The deltas already land there carrying the proxy that measured them, so nothing new is collected and no second window exists — the window is simply read back per proxy, which is the shape of this verdict: a bond lives in one proxy's own store, and the remedy names one proxy. Failures that cannot name a proxy — the release heuristic on any proxy without the component — can never raise it, at any count.

Two consequences, both intended. A fault that stops happening ages out, so the incident clears itself once a device is re-paired. And a proxy that stops publishing its counters for a snapshot while still publishing its bonds keeps its incident, because the evidence outlives the snapshot it arrived in.

One behaviour change worth knowing: like a storm, a missing key now needs a second reading to appear. The first reading of a counter only establishes a baseline — it has been climbing since the proxy booted, and replaying that history is the defect above.

A threshold, at last

bond_threshold ships at 3, floor 2 — the third options-flow setting the v1.5 design specified and the only one that never shipped.

It sits below the storm threshold of 5 on purpose. A missing bond is deterministic: it refuses every attempt, so three refusals inside the window is a pattern rather than noise. And because a missing key already supersedes the storm it causes, firing first is the entire benefit — you get the exact remedy ("re-pair through this proxy") instead of the generic one. The floor of 2 is this fix expressed as a bound: at 1, a single refusal is a diagnosis again, and this diagnosis asks you to go and physically re-pair a device.

"3 connections refused" now says over what

The count changed meaning — from "ever" to "in the last N seconds" — so the sentence says so, matching the pattern its siblings already use ("5 failures in 300s"). Left alone it would still read as a lifetime tally: the same defect v0.6.2 and v0.6.3 went to fix, reintroduced by the change that makes the number honest.

Upgrading

Update the integration and reload the dashboard. No wire-format change and no firmware change since v0.6.1 — the pin moves with the release as always:

external_components:
  - source: github://dasimon135/ha-bluesight@v0.6.4
    components: [bluesight]

Expect incident_count to drop wherever a stale missing-key verdict was being held open, and expect any that remain to be about failures still occurring. That is the fix, not a regression.

v0.6.3 — one fault, one incident

Choose a tag to compare

@dasimon135 dasimon135 released this 27 Aug 22:04
d92a31f

Incident precedence now governs the sensor, not only notifications

dedupe_incidents decides which of two overlapping verdicts is the one worth raising — a missing pairing key over the storm it causes, a deadlock over the ghost slot that is what a deadlock looks like. It was applied by the notification layer and by diagnostics, and by nothing else. binary_sensor.bluesight_incident published the unfiltered list, so the card drew two rows for one fault, incident_count counted it twice, and every automation keyed on that count inherited the error.

Found on a live fleet: one thermostat whose bond had been invalidated by re-pairing through another proxy rendered as both a storm and a missing-pairing-key incident, side by side, naming the same address and the same proxy. The banner said three incidents where there were two.

Deduping now happens where the incident list is assembled, so one policy governs every surface. Both address-layer rules were affected — a deadlock was also publishing the ghost slots it is meant to absorb.

A storm with no bond evidence at all is deliberately still raised: that is every proxy without the ESPHome component, where the heuristic is the only voice there is.

"GATT" is gone from the prose

The last piece of specification vocabulary left in a sentence a user reads — the same defect as bond in v0.6.2. The idle-slot strings now say "nothing exchanged with the device" / "sans aucun échange avec l'appareil".

Upgrading

Update the integration and reload the dashboard. No configuration changes, no wire-format change, no firmware change since v0.6.1 — the pin moves with the release as always:

external_components:
  - source: github://dasimon135/ha-bluesight@v0.6.3
    components: [bluesight]

Expect incident_count to drop on upgrade wherever two verdicts were being published for one fault. That is the fix, not a regression.

v0.6.2 — incidents in words a user reads

Choose a tag to compare

@dasimon135 dasimon135 released this 27 Aug 21:50
362c0cf

Presentation-only release. No detector changed its mind about anything; the incident card just stopped being unreadable.

A user looking at their own card could not tell what it was saying:

Appairage perdu   9C:AC:6D:D4:F9:FC
1 échec d'appairage sur Proxy Buanderie, qui ne détient aucun bond
pour cet appareil — réappairez via Proxy Buanderie
sur D0:CF:13:0F:05:5A

Three defects, all ours.

Incidents now name the device

The badge printed a bare MAC while the sentence beside it had already resolved the proxy to the name its user gave it — so the reader had to translate the address by hand to learn which device was involved. Both name maps were already built every snapshot for other callers; they never reached the incident. They do now.

The address stays on the badge and stays first in the payload: it is the correlation key, and for a peripheral Home Assistant cannot account for it is the only identity there is — which is itself the diagnostic. The name leads, the address follows demoted, and an unnamed device keeps the address alone rather than being given a confident wrong label.

The proxy footer is named the same way, so one badge can no longer call one proxy two things.

No more "bond"

It was the correct BLE term and it is not a word a user has — and in the French catalogue it was not even translated. Both catalogues now say pairing key / clé d'appairage.

A single failure no longer reads as an emergency

Every count rendered the same imperative. One refused connection is the common case — Home Assistant picks the route by signal strength and simply takes another — so the singular now says it is harmless and defers the remedy to "if the count climbs". The plural keeps the instruction, and with it the fact that makes it worth stating: keys are stored per proxy, so pairing through a different one will not help.

Upgrading

Update the integration and reload the dashboard. incidents[] gains device_name and source_names; both are additive, and the card falls back to sources when a backend does not publish them. Existing automations reading kind / address / detail are unaffected.

Proxy firmware is unchanged since v0.6.1, but the pin moves with the release as always:

external_components:
  - source: github://dasimon135/ha-bluesight@v0.6.2
    components: [bluesight]

v0.6.1 — one GATT link is one slot

Choose a tag to compare

@dasimon135 dasimon135 released this 27 Aug 06:20
3adc666

Bug-fix release. 0.6.0 was never tagged; if you installed it from a branch you have the defect described below — upgrade.

One physical link was recorded as several slots

ESP_GATTC_CONNECT_EVT reports the physical ACL link, and ESP-IDF delivers it to every registered GATT client application under its own gattc_if. BlueSight keys slots on (gattc_if, conn_id), so opening a slot there recorded one connection once per registered client — a proxy's connection slots plus every ble_client.

Only the interface owning the connection receives the traffic events, so the other records sat frozen at the moment the link came up and aged without bound. On a live proxy a device that had spoken 7.3 s earlier also appeared five times at 29831.7 s, and two proxies reported exactly nine records — the size of the table — so phantoms were crowding real connections out of it.

The firmware now tracks OPEN, the per-application event, and closes on DISCONNECT by address rather than by key.

The parser no longer lets a frozen record decide

Duplicate addresses were reduced by last-field-wins, which handed the verdict to whichever record was written last and could report a device that spoke seconds ago as idle for hours — a manufactured ghost slot. Duplicates now fold explicitly: min for idle seconds, max for the monotonic SMP counters.

This fix stands on its own. Proxies are flashed on their own schedule, and a proxy still running the old firmware cannot invent an incident on an updated integration.

Upgrading

Update the integration, then rebuild your proxies against the new tag:

external_components:
  - source: github://dasimon135/ha-bluesight@v0.6.1
    components: [bluesight]

No configuration changes, no wire-format change.

v0.5.0 — BlueSight parle français

Choose a tag to compare

@dasimon135 dasimon135 released this 25 Aug 07:41
629bfb0

BlueSight parle maintenant français. / BlueSight now speaks French.

Every user-visible string — the card, the incident details, the persistent
notifications — is rendered from a shared catalogue and shipped in English and
French. Nothing to configure: the card follows each viewer's own profile
language, and the backend follows the installation's.

Two language scopes, on purpose

The card renders in the viewer's profile language (hass.language), so two
people can legitimately look at the same dashboard in two languages.

Incident details and notifications render in the installation's language
(hass.config.language), because detail is a published entity attribute that
your automations format push notifications from. Changing which language it
speaks per-viewer would break those automations, so it doesn't.

The consequence, stated plainly: if your profile is English on a French
installation, the card's chrome is English and the incident detail line is
French. That is the deliberate trade — the automation contract wins.

Adding a language

Drop an incidents.<lang>.json next to the English one, translate the 48 keys,
open a PR. The drift guards discover it automatically and hold it to the same
standard as the shipped languages — an incomplete translation fails CI rather
than shipping half-empty. See docs/translations.md.

Missing keys fall back through fr-CAfren, and never render blank.

Fixed on the way

Translating the strings meant reading them all, which surfaced a factual error
that predates this release: the stalled-proxy detail said the proxy "hasn't seen
any device for a while". The measure is seconds_since_detection — time since
the proxy's last advertisement, not since it last saw a device. Corrected in
both languages.

Also fixed: plural agreement ("1 devices seen"), a CSS text-transform that
mangled French capitalisation, and a stale card version that left the locale
files cacheable for 31 days — a corrected translation would have stayed
invisible for a month.

Known limitation

Only singular/plural (.one / .other) is implemented. Languages with richer
plural rules — Polish, Russian, Arabic — will need a real plural selector before
they can be translated properly.

Upgrading

HACS → BlueSight → Redownload, then restart Home Assistant and hard-refresh
your dashboard (Ctrl/Cmd+Shift+R) — your browser caches the card module.

Full changelog: v0.4.0...v0.5.0

v0.4.0 — the card ships with the integration

Choose a tag to compare

@dasimon135 dasimon135 released this 24 Aug 21:30
5752e23

The Lovelace card is now installed by HACS along with everything else. Nothing to copy, no resource to declare.

Until now BlueSight was a HACS integration, so HACS copied custom_components/bluesight/ and nothing else — the card was yours to place, in two manual steps that failed silently when missed: nothing crashed, the card simply never appeared. The card now lives inside the integration directory, so HACS delivers it, and the integration serves and registers it itself.

Thanks to bastgau on the HACF launch thread for pointing out this was possible, and to KipK for the guide.

Upgrading from 0.3.x

Nothing to do. Your existing /local/bluesight-card.js dashboard resource is rewritten in place to the served path — it is not duplicated, and the card keeps working across the upgrade.

Two optional bits of tidying afterwards:

  • Delete config/www/bluesight-card.js. Nothing references it any more.
  • Hard-refresh your browser (Ctrl/Cmd+Shift+R) once, so it picks up the module from its new URL.

YAML-mode dashboards still declare the resource by hand — Home Assistant does not let an integration write to a YAML-managed resource list. One line, and the file is served either way:

resources:
  - url: /bluesight/bluesight-card.js
    type: module

Details

  • Card served at /bluesight/bluesight-card.js?v=<version>; the version is read from the manifest, so the cache-buster cannot drift.
  • Serving the file and registering the resource are separate steps. Only the resource needs Lovelace, which is not up until Home Assistant has finished starting — on a 59-integration install, over a minute after the UI is reachable. Serving happens at entry setup instead, so the dashboard does not show "Custom element doesn't exist" in the meantime.
  • The card is served from a Python-free subdirectory, so the integration's own source is never published over HTTP.
  • The card guards its customElements.define(): a duplicate definition throws, and that exception would break the whole Lovelace view rather than just this card.

Also in this release

The v1.5 ESPHome telemetry component design, which corrects two premises the roadmap had wrong: ESPHome's esp32_ble is Bluedroid-only, not NimBLE, and the SMP failure evidence already crosses the native API — it is habluetooth that exposes no surface for it.

Full changelog: v0.3.1...v0.4.0