Skip to content

Add HassModel API support for persistent_notification/subscribe (push-based dismiss detection) #1361

Description

@SatMeNow

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

  1. Polling via persistent_notification/get — works but is fragile and not idiomatic
    (see workaround above).

  2. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions