Pure functions for parsing and encoding Home Assistant WebSocket frames.
No side effects, no processes — data in, data out. Hassock.Connection
uses these to translate between JSON on the wire and tagged Elixir terms.
HA WebSocket protocol summary
All messages are JSON objects with a type field. After authentication,
client commands carry an integer id for response correlation.
Server → Client
auth_required,auth_ok,auth_invalidresult— response to a command (keyed byid)event— subscription event (state_changed, entities deltas, etc.)pong— response to ping
Client → Server
auth— send access tokensubscribe_events— subscribe to a generic event typesubscribe_entities— efficient targeted entity subscriptionunsubscribe_events— cancel any subscriptioncall_service,get_states,get_services,ping- any other command through
encode_command/3
Results are returned undecoded ({:result, id, success, result}): only the
sender knows which command an id belongs to, so Hassock.Connection
decodes them by command kind with decode_states/1 and decode_services/1.
Summary
Types
Registry change action; unrecognised actions are kept as strings.
Which registry a *_registry_updated event is about.
Functions
The call_service fields (everything but id and type) for a call.
Decode a get_services result into %{domain => [service_name]}.
Decode a get_states result into %Hassock.EntityState{} structs.
Encode an authentication message.
Encode a call_service command.
Encode any command: payload merged with id and type. Top-level
payload keys may be atoms or strings; id and type always win.
Encode a get_services command.
Encode a get_states command.
Encode a ping command.
Encode a subscribe_entities command. Pass nil to subscribe to every
entity, or a list of entity_ids to scope the subscription.
Encode a subscribe_events command for a given event type.
Encode an unsubscribe_events command (works for both subscription types).
Parse a raw JSON string from the HA WebSocket into a tagged term.
The HA event types that parse/1 decodes into :registry_updated.
True if a new state differs from the prior in a meaningful way (state value
or attributes). Returns true when old is nil. last_updated-only churn
returns false.
Types
@type entities_payload() :: %{ added: %{required(String.t()) => Hassock.EntityState.t()}, changed: %{required(String.t()) => entity_diff()}, removed: [String.t()] }
@type parsed() :: :auth_required | :auth_ok | {:auth_invalid, String.t()} | {:result, integer(), boolean(), term()} | {:state_changed, integer(), Hassock.EntityState.t() | nil, Hassock.EntityState.t() | nil} | {:entities, integer(), entities_payload()} | {:registry_updated, integer(), registry_kind(), registry_action(), String.t() | nil} | {:event, integer(), String.t() | nil, map()} | {:pong, integer() | nil} | {:unknown, term()} | {:invalid, Exception.t()}
@type registry_action() :: :create | :update | :remove | String.t()
Registry change action; unrecognised actions are kept as strings.
@type registry_kind() :: :floor | :area | :device | :entity | :label
Which registry a *_registry_updated event is about.
Functions
@spec call_service_payload(Hassock.ServiceCall.t()) :: map()
The call_service fields (everything but id and type) for a call.
Decode a get_services result into %{domain => [service_name]}.
@spec decode_states([map()]) :: [Hassock.EntityState.t()]
Decode a get_states result into %Hassock.EntityState{} structs.
Encode an authentication message.
@spec encode_call_service(integer(), Hassock.ServiceCall.t()) :: String.t()
Encode a call_service command.
Encode any command: payload merged with id and type. Top-level
payload keys may be atoms or strings; id and type always win.
Encode a get_services command.
Encode a get_states command.
Encode a ping command.
Encode a subscribe_entities command. Pass nil to subscribe to every
entity, or a list of entity_ids to scope the subscription.
Encode a subscribe_events command for a given event type.
Encode an unsubscribe_events command (works for both subscription types).
Parse a raw JSON string from the HA WebSocket into a tagged term.
Text that isn't valid JSON, or JSON whose fields don't have the types HA
sends, returns {:invalid, exception} rather than raising: frames come
from the network, and one bad frame shouldn't take the connection down.
Fields of an entities event that have the wrong type are dropped.
@spec registry_event_types() :: [String.t()]
The HA event types that parse/1 decodes into :registry_updated.
@spec state_changed?(Hassock.EntityState.t() | nil, Hassock.EntityState.t()) :: boolean()
True if a new state differs from the prior in a meaningful way (state value
or attributes). Returns true when old is nil. last_updated-only churn
returns false.