@@ -20,6 +20,7 @@ See what happened, check what is happening, save the moment, and answer the door
## Contents
- [What Ring View can do](#what-you-can-do)
+- [Choose official Ring, Ring-MQTT, or both](#choose-official-ring-ring-mqtt-or-both)
- [Install and get started](#get-started)
- [Configure visually](#configure-visually)
- [Choose the dashboard card behavior](#choose-how-the-dashboard-card-works)
@@ -43,16 +44,50 @@ See what happened, check what is happening, save the moment, and answer the door
- **Make it yours without YAML.** Choose cameras, opening behavior, layout, and doorbell features in the visual editor.
- **Use it across your home.** Responsive phone, tablet, and desktop layouts; Home Assistant themes; English and German; keyboard and screen-reader support.
+## Choose official Ring, Ring-MQTT, or both
+
+Ring View supports both Home Assistant's built-in **Ring integration** and the
+separate [**Ring-MQTT add-on**](https://github.com/tsightler/ring-mqtt). You do
+not have to choose one provider for the
+whole card: each feature can use the source that works best for it. The visual
+editor, viewer, controls, and layout remain the same whichever entities you
+select.
+
+| Ring View feature | Official Ring integration | Ring-MQTT | Our recommendation |
+| --- | --- | --- | --- |
+| **Last recording** | ✅ Select the **Last recording** camera. It is the simplest option, but [some cameras using 24/7 recording can remain on an old clip](https://github.com/home-assistant/core/issues/176299). | ✅ Select **Event Select**, then choose the event to play. **Ding 1** is the newest doorbell press; **Motion 1** is the newest motion event. | Use Event Select if the official camera is stale or missing. Otherwise, the official camera is simpler. |
+| **Live video** | ✅ Select the **Live view** camera. It works directly and is the only source that supports Ring View talkback. A [known upstream cleanup problem](docs/backend-patch.md) can affect repeated Live sessions on some systems. | ✅ Works after a one-time Home Assistant camera setup using Ring-MQTT's Live stream. It provides video and audio from the doorbell, but not talkback. | Use the official Live view camera, especially when you want **Hold to talk**. |
+| **Two-way audio** | ✅ **Hold to talk** is supported. | ❌ Ring-MQTT's Live stream has no microphone return path. This is a source limitation, not a Ring View setting or bug. | Use the official Live view camera. |
+| **Doorbell alert inside the card** | ✅ Works through the Ring Ding event while its realtime listener is healthy. Upstream failures can leave that listener stopped ([#526](https://github.com/python-ring-doorbell/python-ring-doorbell/issues/526), [#537](https://github.com/python-ring-doorbell/python-ring-doorbell/issues/537)). | ✅ Works through the Ding binary sensor. | Prefer the Ring-MQTT Ding sensor for reliability today. |
+| **Last activity time** | ✅ A Ring event or another Home Assistant timestamp can be used, but a Ring event depends on the same realtime listener. | ✅ Ring-MQTT Ding and motion sensors are supported directly; Ring View chooses the freshest activity time from the device. | Prefer a Ring-MQTT Ding or motion sensor. |
+| **Dashboard image and saved snapshots** | ⚠️ A Ring camera may provide a still image, but the official Live camera does not always expose the current Live frame for saving. | ✅ The snapshot camera provides the dashboard image. For a manual save, Ring View asks **Take Snapshot** for a new image first. | Use the Ring-MQTT snapshot camera for the freshest and most dependable image. |
+| **Phone-notification blueprint** | ✅ Select the Ding event and Last recording camera. The alert is immediate; a fresh recording preview follows when available. | ✅ Select the Ding binary sensor and Snapshot camera. Repeated presses are detected while Ding remains on, and only a fresh Ding snapshot is attached. | Prefer the Ring-MQTT Ding and Snapshot entities while the official realtime listener is unreliable. Set Snapshot Mode to an option that includes **Ding**. |
+| **Door access** | ✅ Any Home Assistant lock and optional contact sensor. | ✅ The same—door access is independent of the camera provider. | Use whichever lock is already connected to Home Assistant. |
+
+### Recommended mixed setup
+
+For the most complete experience today, keep both integrations and choose:
+
+- **Last recording:** Ring-MQTT **Event Select**.
+- **Live and Hold to talk:** official Ring **Live view** camera.
+- **Dashboard image and manual snapshots:** Ring-MQTT **Snapshot** camera.
+- **Doorbell alert and last activity:** Ring-MQTT **Ding** and motion sensors.
+- **Phone notification:** Ring-MQTT **Ding** sensor and **Snapshot** camera.
+- **Door access:** any Home Assistant lock and optional contact sensor.
+
+This combines Ring-MQTT's dependable events and snapshots with the official
+integration's Live talkback path. You can still choose only official Ring or
+only Ring-MQTT when that better matches your installation. See the
+[provider guide](docs/compatibility.md#official-ring-and-ring-mqtt-sources) for
+the detailed source mapping and limitations.
+
## Get started
You need Home Assistant **2026.7 or newer** and Ring media exposed through Home
-Assistant. Use the official [Ring integration](https://www.home-assistant.io/integrations/ring/),
-Ring-MQTT, or mix their entities by role. The official setup uses **Last
-recording** and **Live view** cameras. A Ring-MQTT setup uses **Event Select**
-for recordings, its snapshot camera, and a Home Assistant camera configured
-from Ring-MQTT's live RTSP path. Recording access requires a suitable Ring
-subscription. See [Choosing camera entities](docs/configuration.md#choosing-camera-entities)
-for both setups.
+Assistant. Install the official [Ring integration](https://www.home-assistant.io/integrations/ring/),
+Ring-MQTT, or both. Recording access requires a suitable Ring subscription.
+Ring View itself needs no Ring login; it uses the entities already available
+in Home Assistant.
The core viewer can also use another Home Assistant recording camera when it
exposes recorded media, and any Live camera that advertises stream support.
@@ -82,6 +117,22 @@ YAML is optional. The visual editor lets you select the media sources and config
dashboard behavior, manual snapshots, Talk, doorbell features, door access and
appearance.
+#### If you use Ring-MQTT Event Select for recordings
+
+Event Select is a Home Assistant menu that decides which recording Ring View
+plays. Choosing it in the card editor is only the first step:
+
+1. Open **Settings → Devices & services → Entities** in Home Assistant.
+2. Search for **Event Select**, then open the one belonging to your Ring camera.
+3. Choose **Ding 1** for the newest doorbell press or **Motion 1** for the newest
+ motion event. Higher numbers are older events.
+4. Open Ring View and select **Last recording**.
+
+If the normal recording does not play in your browser, choose the matching
+**(Transcoded)** option. Ring View refreshes the temporary playback link when
+needed, but it deliberately keeps your selected Ding, Motion, or on-demand
+event instead of changing that choice for you.
+
diff --git a/docs/compatibility.md b/docs/compatibility.md
index 6aa46a9..5fadf3a 100644
--- a/docs/compatibility.md
+++ b/docs/compatibility.md
@@ -22,6 +22,20 @@ controls, spacing, or status design.
| Last activity | Timestamp state from a sensor, event, or helper | Ding or motion binary-sensor attributes | Same localized relative time. The freshest supported same-device MQTT activity wins. |
| Door access | Any Home Assistant `lock.*` and optional contact sensor | Same | Provider-independent. |
+For the broadest feature coverage, use Ring-MQTT Event Select for recordings,
+the Ring-MQTT snapshot camera for stills and saved snapshots, Ring-MQTT Ding and
+motion sensors for alerts and activity time, and the official Ring Live view
+camera for Live video and two-way audio. This recommended mix is a choice of
+entities inside the same Ring View editor; it does not create a second card or
+change the design.
+
+Event Select is a persistent Home Assistant menu. The user must open that
+entity and choose the desired slot: **Ding 1** is the newest doorbell event,
+**Motion 1** is the newest motion event, and higher numbers are older. Ring View
+refreshes the playback link for the selected slot but does not silently switch
+the user's event choice. If the original recording format does not play, choose
+the matching **(Transcoded)** option.
+
The visual editor always offers the same fields and choices. It never reveals
or removes design settings based on an entity's integration. The runtime uses
the selected entity's registry identity only to adapt the transport behind the
diff --git a/docs/configuration.md b/docs/configuration.md
index 3b09455..7a6c405 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -389,9 +389,18 @@ For the official Ring integration, use its last-recording and live-view entities
The `_last_recording` and `_live_view` suffixes in this guide are examples, not requirements. Home Assistant entity IDs can be changed and may be assigned differently. In one [field report covering fresh 2K and 4K Ring doorbell installations](https://community.home-assistant.io/t/ring-doorbell-live-stream/855118/8), both camera entries appeared alike and neither entity ID used the expected suffix. Identify the entities by their roles and default enabled state rather than relying on their displayed names or ID suffixes.
-For Ring-MQTT recording playback, choose the device's **Event Select** entity as
-`recording_entity`. Its current option—such as **Ding 1**, **Motion 1**, or a
-transcoded variant—determines which event Ring View plays. Ring View reads the
+For Ring-MQTT recording playback:
+
+1. Choose the device's **Event Select** entity as **Last recording** in the Ring
+ View editor.
+2. Open **Settings → Devices & services → Entities** in Home Assistant.
+3. Search for **Event Select**, open the entity for this Ring camera, and choose
+ the recording you want. **Ding 1** is the newest doorbell press and **Motion
+ 1** is the newest motion event; higher numbers are older.
+4. Open Ring View and select **Last recording**.
+
+The current Event Select option, including a transcoded variant, determines
+which event Ring View plays. Ring View reads the
published `recordingUrl` directly. If the signed URL is missing, is within 30
seconds of expiry, or fails once in the browser, Ring View re-selects the
current option and waits up to 15 seconds for Ring-MQTT to publish a different,
diff --git a/docs/notifications.md b/docs/notifications.md
index d62f320..18d1b4e 100644
--- a/docs/notifications.md
+++ b/docs/notifications.md
@@ -10,8 +10,8 @@ binary sensor such as `binary_sensor.front_door_ding`.
A fresh `ring` event, an `off` to `on` Ding transition, or a newer Ring-MQTT
Ding timestamp while the sensor remains `on` displays the bell alert for
-twelve seconds.
-Tapping the card during that alert opens Live. An already active Live session
+twelve seconds. If Live is not running, Ring View also offers **Open live
+view** in the shared centered message area. An already active Live session
is left untouched. The alert expires from the original event time; rebuilding
the card does not restart its twelve-second lifetime.
From 3cef215fe5855b92f48f69262b49cb9ea2d756aa Mon Sep 17 00:00:00 2001
From: Thomas Gregg <2538782+thomasgregg@users.noreply.github.com>
Date: Sun, 13 Sep 2026 15:02:36 +0200
Subject: [PATCH 5/5] Prepare Ring View 0.10.0 stable release
---
CHANGELOG.md | 53 +++-
README.md | 13 +-
docs/compatibility.md | 4 +-
docs/notifications.md | 13 +-
docs/ring-listener-reliability-analysis.md | 320 +++++++++++++++++++++
package-lock.json | 4 +-
package.json | 2 +-
7 files changed, 394 insertions(+), 15 deletions(-)
create mode 100644 docs/ring-listener-reliability-analysis.md
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f82f34c..e51f5a9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,11 +4,43 @@ All notable changes to this project are documented here.
## [Unreleased]
+## [0.10.0] - 2026-09-13
+
+### Added
+
+- Support Ring-MQTT Ding and motion binary sensors as last-activity sources,
+ including renamed same-device companions and ISO, Unix-second, and
+ Unix-millisecond timestamps.
+- Refresh Ring-MQTT snapshots through the device's **Take Snapshot** button and
+ wait for an explicit new image timestamp before saving the JPEG.
+- Play Ring-MQTT Event Select recordings directly in the existing **Last
+ recording** view, refreshing missing, expiring, failed, or transcoding URLs
+ through Home Assistant state updates.
+- Support official Ring and Ring-MQTT entities in one phone-notification
+ blueprint, with the same visual inputs and notification flow.
+
+### Changed
+
+- Resolve recording, Live, snapshot, doorbell, and activity entities as
+ independent source roles so official Ring, Ring-MQTT, and mixed setups share
+ the same Ring View configuration and interface.
+- Discover renamed same-device Ring-MQTT companion entities from Home
+ Assistant's registry while refusing disabled or ambiguous matches.
+- Keep the visual editor's sections, controls, and preview geometry consistent
+ across official Ring and Ring-MQTT source profiles.
+- Keep two-way audio limited to an official Ring Live camera because Ring-MQTT
+ exposes one-way RTSP without a microphone return path.
+- Send the phone notification immediately, then update that same alert only
+ after a fresh official recording or Ring-MQTT Ding snapshot is available.
+
### Fixed
-- Support official Ring and Ring-MQTT entities in the included phone
- notification blueprint, including repeated MQTT presses and fresh snapshot
- previews without stale-image fallback.
+- Prevent Ring-MQTT snapshot saves from reporting success while persisting a
+ stale image.
+- Reject non-playable Ring-MQTT recording sentinels and URLs that are too close
+ to expiry, with one bounded refresh attempt after a browser playback failure.
+- Cancel pending snapshot and recording refreshes when the viewer closes,
+ changes mode, becomes hidden, or is suspended.
- Detect rapid and repeated Ring-MQTT doorbell presses from advancing
`lastDingTime`, `lastDing`, or entity-update timestamps while the configured
Ding sensor remains `on`, without relying on a fixed debounce window.
@@ -16,6 +48,8 @@ All notable changes to this project are documented here.
reconnects, while continuing to ignore stale retained MQTT state.
- Prefix the visible relative time with **Activity** so it cannot be mistaken
for the age of an older recording selected through Ring-MQTT Event Select.
+- Ignore stale, backward, malformed, and implausibly future doorbell markers,
+ and avoid attaching an old notification preview after a timeout.
### Documentation
@@ -25,6 +59,19 @@ All notable changes to this project are documented here.
Ring-MQTT Event Select recording before opening **Last recording**.
- Align the doorbell guide with the current temporary bell and centered **Open
live view** behavior.
+- Document how to update the blueprint and configure its official Ring or
+ Ring-MQTT Ding and preview sources.
+- Publish the code- and test-backed analysis of the official Ring realtime
+ listener failure and its recommended upstream repair sequence.
+
+### Tests
+
+- Pass 194 unit and component tests and 116 browser scenarios across desktop
+ and phone projects.
+- Cover provider parity, renamed entities, mixed sources, repeated Dings,
+ reconnect freshness, timestamp normalization, snapshot refresh, Event Select
+ playback, signed-URL expiry, cancellation, accessibility, and responsive
+ layout behavior.
## [0.10.0-beta.1] - 2026-09-13
diff --git a/README.md b/README.md
index c8af03a..417118a 100644
--- a/README.md
+++ b/README.md
@@ -48,10 +48,9 @@ See what happened, check what is happening, save the moment, and answer the door
Ring View supports both Home Assistant's built-in **Ring integration** and the
separate [**Ring-MQTT add-on**](https://github.com/tsightler/ring-mqtt). You do
-not have to choose one provider for the
-whole card: each feature can use the source that works best for it. The visual
-editor, viewer, controls, and layout remain the same whichever entities you
-select.
+not have to choose one provider for the whole card: each feature can use the
+source that works best for it. The visual editor, viewer, controls, and layout
+remain the same whichever entities you select.
| Ring View feature | Official Ring integration | Ring-MQTT | Our recommendation |
| --- | --- | --- | --- |
@@ -226,6 +225,11 @@ It works even when the dashboard is closed, and the preview does not start anoth
[](https://my.home-assistant.io/redirect/blueprint_import/?blueprint_url=https://github.com/thomasgregg/ring-view/blob/main/blueprints/automation/ring_view/doorbell_notification.yaml)
+Already using an older version? Import it again and choose **Overwrite**. Your
+existing automation remains compatible because the blueprint keeps the same
+input fields; you can then switch its Ding and preview entities between
+official Ring and Ring-MQTT in the normal visual editor.
+
Use the same doorbell signal in your own Home Assistant automations for porch
lights, announcements, or presence-aware alerts. Those are ideas for
additional automations—not actions the included blueprint runs automatically.
@@ -239,6 +243,7 @@ additional automations—not actions the included blueprint runs automatically.
- [Playback, talkback, iPhone rotation, and troubleshooting](docs/playback-and-troubleshooting.md)
- [Optional temporary backend patch](docs/backend-patch.md)
- [Doorbell notifications and automation blueprint](docs/notifications.md)
+- [Official Ring realtime-listener reliability analysis](docs/ring-listener-reliability-analysis.md)
- [Security, privacy, and accessibility](docs/privacy-and-accessibility.md)
- [Compatibility](docs/compatibility.md) · [Testing and development](docs/testing.md) · [Rollback](docs/rollback.md) · [Changelog](CHANGELOG.md)
diff --git a/docs/compatibility.md b/docs/compatibility.md
index 5fadf3a..d064a3d 100644
--- a/docs/compatibility.md
+++ b/docs/compatibility.md
@@ -2,7 +2,9 @@
## Supported Home Assistant versions
-The release target is Home Assistant 2026.9 and the previous two monthly releases where practical. Version 0.1.0 declares Home Assistant 2026.7.0 as its minimum.
+The release target is Home Assistant 2026.9 and the previous two monthly
+releases where practical. Ring View declares Home Assistant 2026.7.0 as its
+minimum.
## Official Ring and Ring-MQTT sources
diff --git a/docs/notifications.md b/docs/notifications.md
index 18d1b4e..ee01c31 100644
--- a/docs/notifications.md
+++ b/docs/notifications.md
@@ -26,18 +26,23 @@ runs in Home Assistant instead.
### Setup
-1. Import the blueprint and create an automation from it.
+1. Import the blueprint and create an automation from it. If it is already
+ installed, import it again and choose **Overwrite**; existing automations
+ keep their configured inputs.
2. Under **Ring View sources**, select:
- For official Ring: the **Ding event** and matching **Last recording**
camera.
- For Ring-MQTT: the **Ding binary sensor** and matching **Snapshot** camera.
-3. Choose the phone registered with the Home Assistant **Companion app**.
-4. Set **Dashboard path** to the actual view containing your Ring View card,
+3. For Ring-MQTT, set the camera's **Snapshot Mode** to an option that includes
+ **Ding**. Choose **Interval + Ding** if you also want periodic dashboard
+ images.
+4. Choose the phone registered with the Home Assistant **Companion app**.
+5. Set **Dashboard path** to the actual view containing your Ring View card,
such as `/lovelace/entrance`. Replace the example/test path; the blueprint
does not create or configure a dashboard for you.
-5. Save, then test with a real doorbell press. Confirm phone notification
+6. Save, then test with a real doorbell press. Confirm phone notification
permission is enabled.
### What happens
diff --git a/docs/ring-listener-reliability-analysis.md b/docs/ring-listener-reliability-analysis.md
new file mode 100644
index 0000000..252939f
--- /dev/null
+++ b/docs/ring-listener-reliability-analysis.md
@@ -0,0 +1,320 @@
+# Ring realtime-event failure: deep code and test analysis
+
+Date: 2026-09-12
+System examined: Home Assistant Core 2026.9.2 on HAOS 18.2, Python 3.14.6, arm64
+Primary symptom: the official Ring app reports a physical doorbell press, while Home Assistant receives no Ding event and the Ring View automation does not run
+
+## Executive conclusion
+
+The card and automation are using the correct Home Assistant entities. The failure occurs earlier, in the backend push-notification path, before Home Assistant can produce an event for either the card or the automation.
+
+The direct failure is an application-level rejection from Google's legacy GCM registration endpoint:
+
+```text
+Error=PHONE_REGISTRATION_ERROR
+```
+
+The request reached Google and received a parsed response, so this is not consistent with a simple DNS, firewall, or TCP-connectivity failure. It also occurs before the library subscribes the resulting push token with Ring, so changing the doorbell entity, camera entity, external chime, card configuration, or automation trigger cannot repair it.
+
+The underlying Google-side reason cannot be proven from the returned error alone. The strongest defensible statement is that Google is rejecting the synthetic Android/GCM registration identity used by `firebase-messaging`. Google's Android SDK team has associated a similarly named error with duplicated Firebase installation identities, but that report concerns the official Android SDK and a later Firebase Installation stage. Our failure occurs at the older `c2dm/register3` GCM stage, so that explanation is a useful clue, not a confirmed diagnosis. [Google Firebase Android issue #7025](https://github.com/firebase/firebase-android-sdk/issues/7025)
+
+Three implementation weaknesses turn that external rejection into a persistent Home Assistant outage:
+
+1. `firebase-messaging` retries a terminal-looking `PHONE_REGISTRATION_ERROR` with the same identity, then converts it into a generic `RuntimeError`. It does not preserve enough structured error information for a caller to apply an appropriate recovery policy.
+2. `python-ring-doorbell` does not have a reliable listener lifecycle. It can claim to be started before the FCM socket has logged in, remain started after the receiver dies, leak callbacks/tasks across retries, and fail to clean up a partially initialized receiver.
+3. Home Assistant starts the Ring listener once. An exception ends that task with no retry or health monitor. Reauthentication and reconfiguration also replace the config-entry data without carrying forward the saved FCM credentials, forcing a new Google registration.
+
+This explains all observed behavior, including why the official Ring app still works: it has its own independent, official push registration. Its notification proves the physical doorbell and Ring cloud event are working; it does not prove Home Assistant's separate unofficial push receiver is registered.
+
+## Confidence assessment
+
+| Conclusion | Confidence | Basis |
+|---|---:|---|
+| The Ring View card and automation entity mapping is correct | High | `event.front_door_ding`, `camera.front_door`, and `camera.front_door_live_view` match the loaded Ring device and the simulated Ding event exercised the UI path. |
+| The physical Ding is lost before Home Assistant's event entity | Very high | A real press reaches the official Ring app, while HA's listener never reaches its “started” log and the Ding entity remains unavailable. |
+| The immediate failure is Google registration, not Ring account authentication | Very high | Four controlled HA listener starts returned `PHONE_REGISTRATION_ERROR` during GCM registration; Ring devices and cameras loaded successfully. |
+| Removing Ring authorized devices can fix this specific state | Low | A full cleanup and reconfiguration produced one clean Ring authorized device but the same Google rejection. The failing identity belongs to the Google push path, not the Ring account-device list. |
+| Duplicate Firebase installation ID is the exact Google-side cause | Low to medium | Google documents that cause for an official Android SDK case, but our rejection is at a different protocol stage and fresh identities were also rejected. |
+| The libraries need lifecycle and recovery changes even if Google later accepts registration | Very high | Reproduced by focused tests and independently reported upstream. |
+
+## End-to-end failure chain
+
+```text
+physical button
+ -> Ring cloud event works (official app receives it)
+ -> Google push registration for HA client FAILS: PHONE_REGISTRATION_ERROR
+ -> Ring push-token subscription never reached
+ -> FCM/MCS socket login never reached
+ -> python-ring-doorbell callback never called
+ -> HA event.front_door_ding remains unavailable / unchanged
+ -> Ring View automation and card have nothing to react to
+```
+
+The Ring project explicitly uses Firebase Cloud Messaging for realtime Ding and motion delivery; this is separate from its ordinary REST polling path. [Ring notification architecture and troubleshooting](https://github.com/dgreif/ring/wiki/Notification-Troubleshooting)
+
+## Exact versions and source examined
+
+The analysis used immutable releases corresponding to the live Home Assistant installation:
+
+| Component | Version / commit | Relevant source |
+|---|---|---|
+| Home Assistant Core | `2026.9.2`, `33c3e0cca60e73a8c4970ee677d75b8bc6464cdf` | [Ring manifest](https://github.com/home-assistant/core/blob/2026.9.2/homeassistant/components/ring/manifest.json), [coordinator](https://github.com/home-assistant/core/blob/2026.9.2/homeassistant/components/ring/coordinator.py), [config flow](https://github.com/home-assistant/core/blob/2026.9.2/homeassistant/components/ring/config_flow.py), [event entity](https://github.com/home-assistant/core/blob/2026.9.2/homeassistant/components/ring/event.py) |
+| `ring-doorbell` | `0.9.14`, `28fd01c47130350614d555b3b4e360aa9a9e4c71` | [event listener](https://github.com/python-ring-doorbell/python-ring-doorbell/blob/0.9.14/ring_doorbell/listen/eventlistener.py) |
+| `firebase-messaging` | `0.4.5`, `61231a66625ba9e16c7fb385071b27984b021290` | [registration](https://github.com/sdb9696/firebase-messaging/blob/0.4.5/firebase_messaging/fcmregister.py), [push client](https://github.com/sdb9696/firebase-messaging/blob/0.4.5/firebase_messaging/fcmpushclient.py) |
+
+Home Assistant 2026.9.2 pins `ring-doorbell==0.9.14` and `aiohttp==3.14.3`. The latest upstream branches were also checked on 2026-09-12. The important behavior is still present: current `firebase-messaging` increases GCM attempts from two to four but still repeats the same request; current `python-ring-doorbell` still uses the stale `started` boolean and immediate-return receiver start; current HA still has the one-shot listener start and data replacement in reauth/reconfigure. [Current firebase registration source](https://github.com/sdb9696/firebase-messaging/blob/main/firebase_messaging/fcmregister.py), [current Ring listener](https://github.com/python-ring-doorbell/python-ring-doorbell/blob/master/ring_doorbell/listen/eventlistener.py), [current HA Ring coordinator](https://github.com/home-assistant/core/blob/dev/homeassistant/components/ring/coordinator.py), [current HA Ring config flow](https://github.com/home-assistant/core/blob/dev/homeassistant/components/ring/config_flow.py)
+
+## Live-system evidence
+
+The account and integration were deliberately normalized before drawing a code conclusion:
+
+- The Ring integration was disabled while the old authorized-device backlog was removed.
+- Reconfiguration created a single fresh `ring-doorbell:HomeAssistant/ring-integration` authorized device.
+- Ring's normal API path loaded devices and cameras correctly after reconfiguration.
+- Listener startup attempts at approximately 14:30, 14:32, 14:33, and 14:40 CEST all failed at GCM registration.
+- Each installed-version attempt made two calls and returned `Error=PHONE_REGISTRATION_ERROR` both times.
+- `ring_doorbell.listen.eventlistener` logged “Starting event listener” but never “Started event listener.”
+- `event.front_door_ding` remained unavailable.
+- A real press produced an official Ring-app notification but no HA event or automation run.
+
+That sequence rules out stale Ring authorized sessions as the primary cause. It also shows the listener is failing before it can subscribe a push token to the Ring account.
+
+## Test methodology and results
+
+All experiments were isolated from the installed Ring View card and live Home Assistant configuration. The tests mock Google, Ring, and socket boundaries; they did not send synthetic registrations to Google's live service.
+
+### Baseline validation
+
+The first run used the newest unconstrained test dependencies. Both upstream suites failed because `aioresponses` could not construct an `aiohttp 3.14.3` response (`stream_writer` became a required argument). This was a test-tool compatibility problem, not treated as a product failure.
+
+The suites were rerun with their tagged lock-compatible versions (`aiohttp 3.11.18`, `aioresponses 0.7.8`, `pytest 8.3.5`, and related pinned packages):
+
+| Suite | Result |
+|---|---:|
+| `firebase-messaging` 0.4.5 upstream tests | 11 passed |
+| `ring-doorbell` 0.9.14 upstream tests | 40 passed |
+| Baseline total | **51 passed** |
+
+This proves the test harness and exact tagged sources work before adversarial cases are added. It also exposes a CI gap: Home Assistant runs `aiohttp 3.14.3`, while the library's historical test lock exercises an older `aiohttp`. A compatibility CI job using HA's current constraint would catch integration drift earlier.
+
+### Adversarial reproduction tests
+
+Twenty-six focused tests were run against unchanged source in a disposable Python 3.14.7 environment, matching HA's Python generation and `aiohttp 3.14.3`. The tests pass when they successfully reproduce the current hazardous behavior.
+
+| Area | Reproduced behavior |
+|---|---|
+| Google rejection | The same Android ID, security token, and GCM app ID are submitted twice for `PHONE_REGISTRATION_ERROR`; the code sleeps even after the final attempt. |
+| Error semantics | The provider error becomes a generic runtime error, preventing policy decisions upstream. |
+| Registration state | Failed registration does not persist the partial GCM identity, so a reload starts another fresh identity; a missing final FCM registration is wrapped in a truthy mapping and can be persisted as if valid. |
+| Resource cleanup | The registration helper is not closed when registration raises. Calling `stop()` before `start()` raises because its lock has not been created. |
+| Socket readiness | `FcmPushClient.start()` returns after scheduling tasks, before connection or MCS login. Initial connection exhaustion leaves the monitor alive and the state stuck at “starting connection.” |
+| Ring listener state | `RingEventListener.started` can be true while `FcmPushClient.is_started()` is false, and stays true after receiver death. |
+| Retry safety | Timeout/repeated start can duplicate callbacks and refresh tasks; one refresh task becomes unreachable and cannot be canceled by normal stop. |
+| Callback invariant | Stop/restart clears callbacks without resetting their counter, so the actual default callback is no longer ID 1 even though ID 1 alone is protected. |
+| Session maintenance | The 12-hour refresh algorithm first checks at 65,535 seconds (18:12:15), overshooting by 6:12:15, then exits permanently after that single refresh. |
+| Payload safety | A legacy Ding comparison uses `action.lower` instead of `action.lower()`; a missing current-payload `ding.id` raises through the Ring callback; a callback removing itself mutates the dictionary during iteration. |
+| HA config flow | Both reauth and reconfigure replace entry data and drop the stored `listen_token`. |
+| HA coordinator | A start exception aborts without callback, entity update, retry, or repair signal; a false return still installs a callback; unload skips partial listener cleanup; the removal callback is not idempotent. |
+
+Two public reports independently corroborate the highest-impact post-start failures:
+
+- The Ring listener can remain marked started after its FCM receiver shuts down, leaving HA event automations dead until reload. [python-ring-doorbell issue #526](https://github.com/python-ring-doorbell/python-ring-doorbell/issues/526)
+- New Ring payloads can omit `ding.id`; three such callback failures terminate the whole FCM receiver. [python-ring-doorbell issue #537](https://github.com/python-ring-doorbell/python-ring-doorbell/issues/537)
+
+### Candidate-fix validation
+
+A proof-of-concept patch was applied only to isolated copies of the three upstream projects. It was not installed into Home Assistant and did not alter this card project.
+
+The patch implemented typed terminal registration errors, guaranteed cleanup, safe pre-start stop, socket-readiness waiting, a lifecycle lock, dynamic health, idempotent start/stop, continuous session refresh, payload isolation, preserved HA credentials, coordinator health monitoring, bounded backoff, and start/stop race cancellation.
+
+| Validation | Result |
+|---|---:|
+| Original upstream suites after the candidate library changes | **51 passed** |
+| Candidate acceptance tests on Python 3.12 | **14 passed** |
+| Candidate acceptance tests on disposable Python 3.14.7 / HA-style environment | **21 passed** |
+| Rapid HA event-listener remove/re-add race | Passed; stale start and stop tasks were canceled, one final stop remained |
+| Static compile and diff whitespace checks | Passed |
+| Tagged Ruff checks for changed Firebase and Ring library files | Passed |
+
+These results validate the architecture, not production readiness. A real staging account must still confirm that the modified registration path interoperates with Google's live endpoint and that Ring accepts the resulting push token.
+
+## Detailed code findings
+
+### 1. `firebase-messaging`: terminal rejection is handled like a transient timeout
+
+`gcm_register()` creates one `gcm_app_id` before its retry loop and uses the same Android ID, security token, headers, and request body on every attempt. The installed release retries twice; current `main` retries four times. A deterministic `PHONE_REGISTRATION_ERROR` is therefore replayed without changing the property Google rejected. [0.4.5 registration loop](https://github.com/sdb9696/firebase-messaging/blob/0.4.5/firebase_messaging/fcmregister.py#L172-L231)
+
+The function returns `None`; the caller raises a generic message. The structured provider code is available only in logs. Home Assistant consequently cannot distinguish a timeout, rate limit, malformed request, invalid identity, or provider policy rejection.
+
+The safe behavior is:
+
+- Parse `Error=` into a typed exception with a stable `code` field.
+- Do not immediately repeat a known terminal identity error with the same identity.
+- Let callers use long, jittered backoff and expose a repair issue.
+- Keep ordinary bounded retries for network timeouts and 5xx errors.
+- Never include tokens or security credentials in normal logs.
+
+The proof-of-concept stops after one `PHONE_REGISTRATION_ERROR`, preserves its code, and lets HA begin at a 15-minute delay rather than creating a tight identity-registration storm.
+
+### 2. Registration is not transactional
+
+`fcm_install_and_register()` returns a truthy outer dictionary even when the inner registration result is `None`. That dictionary is then accepted and passed to the credentials callback. Later, `FcmPushClient` indexes `registration["token"]` and fails. [FCM installation wrapper](https://github.com/sdb9696/firebase-messaging/blob/0.4.5/firebase_messaging/fcmregister.py#L253-L262)
+
+Registration should be committed only if all required fields validate:
+
+- generated encryption keys;
+- GCM Android ID/security token/app ID/token;
+- Firebase installation token/FID/refresh token;
+- final FCM registration token.
+
+Partial state may be retained internally for a controlled retry, but it must not be published as complete credentials. If partial identities are persisted in the future, they need an explicit schema version and expiry so HA does not confuse them with usable credentials.
+
+### 3. FCM start does not mean ready
+
+`FcmPushClient.start()` schedules `_listen()` and `_do_monitor()` and returns immediately. Successful MCS login occurs later, when a `LoginResponse` changes `run_state` to `STARTED`. [push-client start](https://github.com/sdb9696/firebase-messaging/blob/0.4.5/firebase_messaging/fcmpushclient.py#L749-L791)
+
+`RingEventListener` wraps only the immediate scheduling call in its ten-second timeout, then sets its own `started=True`. The timeout therefore does not verify connection or login. A correct contract needs either:
+
+- `start()` to await readiness, or
+- a separate `wait_until_started()` / health event that Ring awaits inside its timeout.
+
+The candidate uses the second option to preserve API compatibility.
+
+### 4. Initial socket failure and later receiver death are invisible upstream
+
+If all initial MCS connection attempts fail, `_listen()` returns but leaves `do_listen=True`; the monitor remains alive in a state it does not handle. Later sequential connection or callback errors call `_terminate()`, but no health-change callback reaches `RingEventListener` or HA.
+
+`RingEventListener.started` is an unrelated boolean. HA's event entity reports availability solely from that boolean. [HA Ring event availability](https://github.com/home-assistant/core/blob/2026.9.2/homeassistant/components/ring/event.py#L111-L115)
+
+The minimum fix is a dynamic health predicate backed by the receiver's actual run state. A stronger API exposes a state enum and metadata:
+
+```text
+STOPPED -> REGISTERING -> SUBSCRIBING -> CONNECTING -> HEALTHY
+ | | |
+ +------------> BACKOFF <-------+
+```
+
+Useful diagnostics are `last_error_code`, `last_error_at`, `last_notification_at`, current retry delay, and whether the Ring token subscription succeeded. They must exclude secrets.
+
+### 5. Lifecycle operations are not idempotent or race-safe
+
+The listener lacks a lifecycle lock. A timeout occurs after the internal Ring callback is added but before `started=True`; retry adds it again. Repeated starts overwrite the only stored refresh-task reference, leaking the previous task. Stop clears callbacks but does not reset their counter, violating the hard-coded “ID 1 is default” rule.
+
+HA has a related race: removing the last entity schedules stop; adding an entity again schedules start, but neither operation cancels or serializes the other. The candidate serializes Ring start/stop and tracks/cancels stale HA start and stop tasks. It passed a controlled remove/re-add race test.
+
+### 6. Session refresh stops after one delayed run
+
+The refresh sleep is `1 + elapsed_since_refresh`, producing check times of 1, 3, 7, 15, … seconds. With a 43,200-second threshold, the first over-threshold check is at 65,535 seconds. The function then uses `break`, so it never refreshes a second time. [Ring session refresh loop](https://github.com/python-ring-doorbell/python-ring-doorbell/blob/0.9.14/ring_doorbell/listen/eventlistener.py#L204-L219)
+
+Use a monotonic deadline and a bounded health-check interval, refresh at the deadline, then continue the loop. The candidate refreshed exactly at the test deadlines twice and remained cancelable.
+
+### 7. One malformed message can kill every realtime entity
+
+The Ring parser performs unguarded JSON and key access. The Firebase client catches the exception as a callback failure and terminates after its sequential-error threshold. A payload variant from one camera can therefore disable Ding and motion events for every Ring device sharing the global receiver. The real-world missing-`ding.id` report demonstrates this exact blast radius. [Issue #537](https://github.com/python-ring-doorbell/python-ring-doorbell/issues/537)
+
+Parsing must be an isolation boundary:
+
+- Validate the envelope and required fields.
+- Use a deterministic synthetic event ID when safe, or skip only that message.
+- Log a sanitized schema summary, not the full token-bearing payload.
+- Catch failures per message and per callback.
+- Iterate over a snapshot of callbacks so a callback may unsubscribe itself.
+
+The listener must not terminate its transport because Ring added or omitted a payload field.
+
+### 8. Home Assistant discards durable push credentials
+
+On a successful initial FCM registration, HA intentionally writes `CONF_LISTEN_CREDENTIALS` (`listen_token`) into the config entry. [HA Ring setup](https://github.com/home-assistant/core/blob/2026.9.2/homeassistant/components/ring/__init__.py#L34-L65)
+
+Reauth and reconfigure later build a new data mapping containing only username, Ring API token, and hardware ID, then replace the entry data. The saved push credentials disappear. [HA reauth/reconfigure](https://github.com/home-assistant/core/blob/2026.9.2/homeassistant/components/ring/config_flow.py#L164-L235)
+
+Reauth should unquestionably preserve unrelated entry data. Reconfigure should also preserve the FCM credentials while rotating the Ring hardware ID; failed credential check-in already provides a natural path to full registration. If HA wants a manual push reset, it should be an explicit repair action rather than an incidental side effect of account reconfiguration.
+
+### 9. Home Assistant has no recovery state machine
+
+The coordinator awaits listener start once. An exception exits before callback installation and before entity updates. A false return still installs the callback and updates entities even though the listener is unavailable. Unload calls stop only when the stale boolean is true. [HA Ring listener coordinator](https://github.com/home-assistant/core/blob/2026.9.2/homeassistant/components/ring/coordinator.py#L162-L229)
+
+The coordinator should own one background state machine for the account:
+
+- Start only while at least one event entity is subscribed.
+- Propagate cancellation immediately.
+- On transient failure, use exponential backoff with jitter and a one-hour cap.
+- On `PHONE_REGISTRATION_ERROR`, avoid rapid identity rotation; begin with a substantially longer delay.
+- Update entity availability on every health transition.
+- Always clean up partial state on unload.
+- Create a user-visible repair issue after a sustained outage, including the error code and next retry but no credentials.
+- Automatically clear the issue after a confirmed healthy MCS login.
+
+## Recommended change sequence
+
+The fixes should be split at ownership boundaries so each project can release and test independently.
+
+### PR 1: `firebase-messaging`
+
+1. Add a typed registration exception with provider code and stage.
+2. Classify `PHONE_REGISTRATION_ERROR` as non-immediately-retryable for the same identity.
+3. Validate complete registration before invoking the credentials callback.
+4. Close the registration helper in `finally`.
+5. Make stop safe before, during, and after start.
+6. Terminate all tasks when initial connection attempts are exhausted.
+7. Add readiness/health waiting and an optional health-change callback.
+8. Add tests for terminal errors, partial results, cleanup, cancellation, and initial-connect exhaustion.
+
+### PR 2: `python-ring-doorbell`
+
+1. Require the Firebase release containing typed errors and readiness.
+2. Guard lifecycle operations with one lock and make start/stop idempotent.
+3. Do not mark started until MCS login is confirmed.
+4. Derive health from the receiver rather than a stale boolean.
+5. Clean up on every failed stage before allowing retry.
+6. Install internal callbacks only after readiness; track the actual default callback ID.
+7. Make session refresh continuous and deadline-based.
+8. Isolate malformed payloads and callback failures.
+9. Fix the legacy `action.lower()` comparison.
+10. Add restart, concurrent-start, receiver-death, malformed-payload, and callback-mutation tests.
+
+### PR 3: Home Assistant Core
+
+1. Preserve `listen_token` and unknown future entry fields during reauth/reconfigure.
+2. Replace the one-shot task with a cancelable, single-owner listener state machine.
+3. Use typed-error-aware, jittered backoff.
+4. Monitor health and update all Ring event entities when it changes.
+5. Always stop partial listener state on unload.
+6. Make entity listener-removal callbacks idempotent and cancel stale start/stop tasks.
+7. Add a repair issue for sustained realtime-event outage.
+8. Run Ring dependency tests against HA's actual `aiohttp` constraint in CI.
+
+## What should not be changed
+
+- Do not point the automation at the external chime or a different camera. The event source is the doorbell's Ding event, and that mapping is already correct.
+- Do not add card-level polling or URL-opening fallbacks to conceal listener failure. The card cannot reconstruct a realtime press that HA never received.
+- Do not repeatedly delete Ring authorized devices. That operates on a different registration domain and the controlled cleanup already disproved it as the primary fix.
+- Do not retry `PHONE_REGISTRATION_ERROR` every few seconds with newly generated identities. That can amplify throttling or identity-policy problems and makes diagnosis harder.
+- Do not install the proof-of-concept directly on the production HA host. It spans three versioned projects and needs staging with a disposable Ring/HA environment first.
+
+## Practical next step
+
+The highest-value first upstream change is the Firebase typed-error/cleanup/readiness patch, followed by the Ring lifecycle patch. HA can then consume reliable health and error semantics instead of guessing from a boolean. In parallel, the small HA credential-preservation fix can be submitted independently because it is low risk and prevents reauth from unnecessarily forcing a new Google registration.
+
+For this installation, a later successful retry would show that the Google rejection was temporary; it would not invalidate the lifecycle findings. If it fails again with the same provider code, further Ring reauthentication is unlikely to help. The next live diagnostic should record only the error code, stage, attempt time, and whether stored push credentials were present—never the credentials themselves.
+
+## Limitations
+
+- Google's legacy GCM endpoint does not provide a documented explanation with this response, and the project uses a reverse-engineered, unofficial Ring API. [python-ring-doorbell project statement](https://github.com/python-ring-doorbell/python-ring-doorbell)
+- The isolated tests deliberately did not call live Google or Ring endpoints, avoiding registration spam and account impact.
+- The candidate patch proves the proposed state-machine behavior under deterministic failure, timeout, cancellation, malformed-payload, and concurrency tests. It does not replace staging against live push delivery.
+- The official Ring app's internal implementation and credentials are not available for comparison.
+
+## Sources
+
+- [Home Assistant Core 2026.9.2 Ring integration](https://github.com/home-assistant/core/tree/2026.9.2/homeassistant/components/ring)
+- [Home Assistant current Ring integration](https://github.com/home-assistant/core/tree/dev/homeassistant/components/ring)
+- [`python-ring-doorbell` 0.9.14 event listener](https://github.com/python-ring-doorbell/python-ring-doorbell/blob/0.9.14/ring_doorbell/listen/eventlistener.py)
+- [`python-ring-doorbell` current event listener](https://github.com/python-ring-doorbell/python-ring-doorbell/blob/master/ring_doorbell/listen/eventlistener.py)
+- [`firebase-messaging` 0.4.5 registration source](https://github.com/sdb9696/firebase-messaging/blob/0.4.5/firebase_messaging/fcmregister.py)
+- [`firebase-messaging` 0.4.5 push-client source](https://github.com/sdb9696/firebase-messaging/blob/0.4.5/firebase_messaging/fcmpushclient.py)
+- [`firebase-messaging` current registration source](https://github.com/sdb9696/firebase-messaging/blob/main/firebase_messaging/fcmregister.py)
+- [Ring notification troubleshooting](https://github.com/dgreif/ring/wiki/Notification-Troubleshooting)
+- [Stale Ring listener health issue #526](https://github.com/python-ring-doorbell/python-ring-doorbell/issues/526)
+- [Missing `ding.id` receiver-termination issue #537](https://github.com/python-ring-doorbell/python-ring-doorbell/issues/537)
+- [Google Firebase duplicate-FID discussion #7025](https://github.com/firebase/firebase-android-sdk/issues/7025)
diff --git a/package-lock.json b/package-lock.json
index 6e6c100..d92b60e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "ring-view",
- "version": "0.10.0-beta.1",
+ "version": "0.10.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ring-view",
- "version": "0.10.0-beta.1",
+ "version": "0.10.0",
"license": "MIT",
"dependencies": {
"@mdi/js": "^7.4.47",
diff --git a/package.json b/package.json
index c2575de..702c4dd 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "ring-view",
- "version": "0.10.0-beta.1",
+ "version": "0.10.0",
"description": "A polished Home Assistant view for Ring recordings, Live, snapshots, talkback, and door access.",
"type": "module",
"main": "dist/ring-view.js",