The problem
Home Assistant's persistent_notification integration does not use the HA event bus. It
communicates through an internal dispatcher signal and a dedicated WebSocket subscription
(persistent_notification/subscribe). This means that none of the standard HassModel APIs
fire for persistent notification changes:
ha.Events — never emits state_changed for persistent_notification.*
StateAllChanges() / StateChanges() — never fires for these entities
ha.GetAllEntities() — does not include persistent_notification.* entities
There is no error, no warning — the observable simply never emits. This is particularly painful
when trying to detect user-initiated dismissals of persistent notifications from the HA sidebar,
which is a common pattern for device acknowledge flows.
Confirmed root cause: The HA core source (homeassistant/components/persistent_notification/__init__.py)
uses async_dispatcher_send(hass, SIGNAL_PERSISTENT_NOTIFICATIONS_UPDATED, ...) rather than
hass.bus.async_fire(...), so EntityStateCache (which feeds ha.Events) never sees these changes.
The proposed solution
Expose a SubscribePersistentNotificationsAsync() helper (or equivalent) that wraps the HA
WebSocket persistent_notification/subscribe command and returns an
IObservable<PersistentNotificationUpdate> (or similar push-based stream).
Suggested API shape
public enum PersistentNotificationUpdateType { Added, Removed, Updated, Current }
public record PersistentNotification(
string NotificationId,
string Message,
string Title,
string Status, // "read" | "unread"
DateTime CreatedAt
);
public record PersistentNotificationUpdate(
PersistentNotificationUpdateType Type,
IReadOnlyDictionary<string, PersistentNotification> Notifications
);
Extension on IHaContext (or IHomeAssistantConnection):
// Subscribes via persistent_notification/subscribe WebSocket command.
// Emits on: added, removed, updated, current (initial snapshot).
IObservable<PersistentNotificationUpdate> SubscribePersistentNotifications();
Typical consumer pattern (dismiss detection)
ha.SubscribePersistentNotifications()
.Where(update => update.Type == PersistentNotificationUpdateType.Removed
&& update.Notifications.ContainsKey("my_notification_id"))
.Subscribe(_ =>
{
// User dismissed the notification — write back to device
});
Workaround (current, polling-based)
Without this API, the only reliable approach is to poll persistent_notification/get on every
cyclic tick while the notification is expected to be shown:
private sealed record GetNotificationsCommand : CommandMessage
{
public GetNotificationsCommand() { Type = "persistent_notification/get"; }
}
private sealed class HassNotificationItem
{
[JsonPropertyName("notification_id")]
public string? NotificationId { get; init; }
}
// Called every N seconds while notification is active:
var list = await runner.CurrentConnection!
.SendCommandAndReturnResponseAsync<GetNotificationsCommand, List<HassNotificationItem>>(
new GetNotificationsCommand(), cancel);
bool stillActive = list?.Any(n => n.NotificationId == "my_notification_id") == true;
This works but requires polling, bypasses the HassModel abstraction layer, exposes internal
NetDaemon.Client types, and forces apps to manage their own interval logic.
The alternatives
-
Polling via persistent_notification/get — works but is fragile and not idiomatic
(see workaround above).
-
Expose persistent_notification/subscribe directly as a raw IObservable<HassEvent>
on the connection level — lower effort than a typed helper, still better than nothing.
Additional context
-
HA WebSocket protocol reference: persistent_notification/subscribe emits messages with
type: "added" | "removed" | "updated" | "current" and a notifications dictionary keyed
by notification_id. See HA frontend source:
src/data/persistent_notification.ts
-
NetDaemon version tested: 26.11.0
-
The same gap likely exists for other HA integrations that use the dispatcher signal pattern
instead of the bus (e.g. repairs, todo lists). A generic SubscribeMessage<T> helper on
IHaContext would cover all such cases.
The problem
Home Assistant's
persistent_notificationintegration does not use the HA event bus. Itcommunicates through an internal dispatcher signal and a dedicated WebSocket subscription
(
persistent_notification/subscribe). This means that none of the standard HassModel APIsfire for persistent notification changes:
ha.Events— never emitsstate_changedforpersistent_notification.*StateAllChanges()/StateChanges()— never fires for these entitiesha.GetAllEntities()— does not includepersistent_notification.*entitiesThere is no error, no warning — the observable simply never emits. This is particularly painful
when trying to detect user-initiated dismissals of persistent notifications from the HA sidebar,
which is a common pattern for device acknowledge flows.
Confirmed root cause: The HA core source (
homeassistant/components/persistent_notification/__init__.py)uses
async_dispatcher_send(hass, SIGNAL_PERSISTENT_NOTIFICATIONS_UPDATED, ...)rather thanhass.bus.async_fire(...), soEntityStateCache(which feedsha.Events) never sees these changes.The proposed solution
Expose a
SubscribePersistentNotificationsAsync()helper (or equivalent) that wraps the HAWebSocket
persistent_notification/subscribecommand and returns anIObservable<PersistentNotificationUpdate>(or similar push-based stream).Suggested API shape
Extension on
IHaContext(orIHomeAssistantConnection):Typical consumer pattern (dismiss detection)
Workaround (current, polling-based)
Without this API, the only reliable approach is to poll
persistent_notification/geton everycyclic tick while the notification is expected to be shown:
This works but requires polling, bypasses the HassModel abstraction layer, exposes internal
NetDaemon.Clienttypes, and forces apps to manage their own interval logic.The alternatives
Polling via
persistent_notification/get— works but is fragile and not idiomatic(see workaround above).
Expose
persistent_notification/subscribedirectly as a rawIObservable<HassEvent>on the connection level — lower effort than a typed helper, still better than nothing.
Additional context
HA WebSocket protocol reference:
persistent_notification/subscribeemits messages withtype: "added" | "removed" | "updated" | "current"and anotificationsdictionary keyedby
notification_id. See HA frontend source:src/data/persistent_notification.tsNetDaemon version tested: 26.11.0
The same gap likely exists for other HA integrations that use the dispatcher signal pattern
instead of the bus (e.g. repairs, todo lists). A generic
SubscribeMessage<T>helper onIHaContextwould cover all such cases.