Skip to content

Conn: support subscribing to particular message IDs#43

Merged
saml-dev merged 16 commits intosaml-dev:mainfrom
mhagger:message-id-subscribers
Dec 16, 2025
Merged

Conn: support subscribing to particular message IDs#43
saml-dev merged 16 commits intosaml-dev:mainfrom
mhagger:message-id-subscribers

Conversation

@mhagger
Copy link
Copy Markdown
Contributor

@mhagger mhagger commented Dec 7, 2025

Note: this branch builds on top of #42, so it includes those commits, too.

Home Assistant subscriptions are based on message IDs. To subscribe to something, you send an outgoing message to HA asking to subscribe. This message, like all requests, has a unique message ID. When HA sends events related to that subscription, the event messages that it sends back to you contain the same message ID. This allows the incoming events easily to be mapped back to the subscriber who requested them.

Add support for this mechanism at the Conn level:

  • Add methods to LockedConn that allow subscribing or unsubscribing to a particular message ID. The Subscriber is a callback function that is invoked whenever any message arrives with that message ID.
  • The Subscribe() method returns a Subscription, which includes a newly-allocated message ID. That ID can be used as the message ID for a message requesting a subscription from HA, thus causing Conn to route those events automatically to the registered Subscriber.
  • Change App to register its event listeners and entity listeners using this new mechanism. This means that much of the event routing is done by Conn. The rest is done in the event listener Subscriber.
  • Now that Conn.ListenWebsocket() does all of the message handling itself, the caller doesn't need to know what it does. So rename it to Conn.Run().

The usual caveat: I still can't run my own home setup on this branch, because it relies on other changes in #21 that haven't been re-rolled yet. So, while every commit compiles and example_live_test.go runs correctly at the tip of this branch, I can't do a lot more testing than that.

/cc @saml-dev

@mhagger mhagger mentioned this pull request Dec 7, 2025
Copy link
Copy Markdown
Owner

@saml-dev saml-dev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks! do you mind resolving the conflicts / rebasing?

@mhagger
Copy link
Copy Markdown
Contributor Author

mhagger commented Dec 10, 2025

do you mind resolving the conflicts / rebasing?

👍 I'll try to get to it this weekend.

Instead of shoving incoming messages into a channel, pass them to a
callback of type `Subscriber`. Have `App` deal with the channel on its
side (for now).
It will be easier to route messages to the correct subscriber, and
prevent subscribers from interfering with each other, if subscribing
at the `Conn` level is done by message ID. Add a mechanism for doing
so.

For now, messages are also passed to a `defaultSubscriber`. That will
soon go away.
The subscriber is invoked every time a message arrives with the
message ID corresponding to the subscription.
The old code wrote incoming messages to a channel, and then (in a
separate goroutine) read and processed the messages from the channel.
Note that the second goroutine created yet another goroutine for
processing each message.

Instead, write a `Subscriber` that processes the messages directly
(while continuing to create a goroutine for each message). Pass this
subscriber to `Conn.ListenWebsocket()` as the default subscriber.

In future commits, we will add more specific subscribers for each type
of message.
Add helper methods to add single listeners.
The thing that really has to be done specially for new event types is
to subscribe to them. It is possible to append to a nil list, so that
can be done in shared code after the `if`.
Extract method `EventListener.maybeCall()` from
`App.callEventListeners()`.
Pick the event type out of the message in the caller, and pass it into
`App.callEventListeners()`. This will soon enable a simplification.
It is OK to append to a nil slice.
Use `Conn`'s subscription feature to register a listener specific to
each type of event that we are watching. This allows some logic to be
removed from the default subscriber, because when the even arrives, we
know what event type it is for based on its message ID.
Extract method `EntityListener.maybeCall()` from
`App.callEntityListeners()`.
Use `Conn`'s subscription feature to register a listener specific to
state changed events. This means that we can change the default
subscriber to a `NoopSubscriber`.
Now that the method handles incoming messages internally, all that its
caller really has to know is that it is a method that has to be called
to manage the connection.
@mhagger mhagger force-pushed the message-id-subscribers branch from 7742a1c to 7815d6b Compare December 12, 2025 17:20
@mhagger
Copy link
Copy Markdown
Contributor Author

mhagger commented Dec 12, 2025

I just rebased this branch onto the current main branch 👍

@saml-dev saml-dev merged commit 096a86d into saml-dev:main Dec 16, 2025
@mhagger mhagger deleted the message-id-subscribers branch December 28, 2025 12:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants