Help us shape the future of Go! Take the 2026 Go Developer Survey

esphomedevice

package module
v0.0.17 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 21, 2026 License: MIT Imports: 24 Imported by: 0

README

go-esphome-device

A Go library for implementing the device side of the ESPHome native API — write a program that Home Assistant talks to as if it were an ESPHome device.

Home Assistant's built-in esphome integration connects to you, enumerates your entities and drives them, so there's no custom integration for users to install.

Connections are encrypted with a Noise PSK.

Status: early. Not yet usable.

Usage

TODO once the API settles.

Protocol version

proto/api.proto comes from upstream ESPHome; proto/ESPHOME_VERSION records which release the generated bindings track. To move to a newer one:

make proto REF=2026.7.0

Testing

cmd/sim_voice_assistant is a simulated voice satellite. Point a real Home Assistant at it to check protocol behaviour without hardware:

make sim

License

MIT. api.proto is redistributed from ESPHome, whose dual license applies GPLv3 only to .c, .cpp, .h, .hpp, .tcc and .ino files, and MIT to everything else.

Documentation

Overview

Package esphomedevice implements the device side of the ESPHome native API, so a Go program can present itself to Home Assistant as an ESPHome device.

Index

Constants

View Source
const (
	APIVersionMajor = 1
	APIVersionMinor = 12
)

API version we advertise. Major mismatches make Home Assistant disconnect; minor mismatches only warn.

View Source
const (
	ColorModeOnOff      = api.ColorMode_COLOR_MODE_ON_OFF
	ColorModeBrightness = api.ColorMode_COLOR_MODE_BRIGHTNESS
	ColorModeRGB        = api.ColorMode_COLOR_MODE_RGB
)
View Source
const (
	MediaPlayerIdle    = api.MediaPlayerState_MEDIA_PLAYER_STATE_IDLE
	MediaPlayerPlaying = api.MediaPlayerState_MEDIA_PLAYER_STATE_PLAYING
	MediaPlayerPaused  = api.MediaPlayerState_MEDIA_PLAYER_STATE_PAUSED
	// MediaPlayerAnnouncing is in the protocol, and Home Assistant has no case for it: its state
	// mapper raises a KeyError on every update carrying it, so the entity stops updating. Report
	// playing instead.
	MediaPlayerAnnouncing = api.MediaPlayerState_MEDIA_PLAYER_STATE_ANNOUNCING
)

The commands Home Assistant can send.

How Home Assistant presents a text entity.

Number is a tunable scalar, used for thresholds, gain and volume. How Home Assistant presents a number. Auto lets it decide, which gives a slider for a short range; Box asks for a typed value.

View Source
const DefaultESPHomeVersion = "2026.5.1"

DefaultESPHomeVersion is reported when Info leaves ESPHomeVersion empty. It tracks a recent release, because Home Assistant compares the reported one against minimums of its own.

View Source
const DefaultPort = 6053
View Source
const DefaultVoiceFeatures = FeatureVoiceAssistant | FeatureAPIAudio

DefaultVoiceFeatures matches what ESPHome always sets. The rest are conditional there: Speaker when a speaker is configured, Timers when timers are, MultiChannelAudio when a second mic source exists, and Announce together with StartConversation when a media_player is present — ESPHome sets that pair as a unit.

Variables

View Source
var (
	ErrNoSubscriber   = errors.New("esphomedevice: no voice assistant subscriber")
	ErrTurnInProgress = errors.New("esphomedevice: a voice turn is already running")
)
View Source
var ErrDisconnect = errors.New("esphomedevice: client disconnected")

Functions

func NewMessage

func NewMessage(id uint32) (proto.Message, bool)

NewMessage allocates an empty message for the given wire ID.

Types

type Action

type Action struct {
	Name string
	Args []Arg

	// Run does the work. What it returns becomes the answer; nil means there is none. An error reaches
	// the caller as a failed action with its message.
	Run func(Call) (any, error)

	// Answers says the action has something to say. Home Assistant refuses return_response on one that
	// does not, and refuses to wait for one that says it only ever answers.
	Answers bool
}

An Action is something Home Assistant can call on the device, registered as esphome.<node>_<name>. Unlike an entity it holds no state: it takes arguments, does something, and may answer.

Answering is how a device serves what a state cannot carry — bytes, or a list. Home Assistant decodes the answer as JSON and gives it to whoever called with return_response, so Answers must marshal to a JSON object.

type Announce

type Announce struct {
	MediaID            string
	Text               string
	PreannounceMediaID string
	StartConversation  bool
}

Announce is Home Assistant asking the device to speak something outside a turn.

type Arg

type Arg struct {
	Name string
	Type ArgType
}

type ArgType

type ArgType int

ArgType is what an argument carries. Home Assistant checks a call against these before it arrives.

const (
	ArgString ArgType = iota
	ArgInt
	ArgFloat
	ArgBool
)

type Base

type Base struct {
	// ObjectID is the stable identifier the entity key is derived from. Changing it
	// makes Home Assistant treat the entity as a new one.
	ObjectID string

	Name              string
	Icon              string
	Category          EntityCategory
	DisabledByDefault bool

	// DeviceID puts the entity on one of Info.Devices rather than the device itself.
	DeviceID uint32
	// contains filtered or unexported fields
}

Base carries the fields every entity domain shares.

func (*Base) Key

func (b *Base) Key() uint32

func (*Base) Object

func (b *Base) Object() string

type BinarySensor

type BinarySensor struct {
	Base
	DeviceClass string
	// contains filtered or unexported fields
}

func (*BinarySensor) Get

func (b *BinarySensor) Get() bool

func (*BinarySensor) Set

func (b *BinarySensor) Set(v bool)

type BluetoothFeature

type BluetoothFeature uint32

BluetoothFeature is what a proxy tells Home Assistant it can do, in Info.BluetoothFeatures.

const (
	BluetoothPassiveScan BluetoothFeature = 1 << iota
	BluetoothActiveConnections
	BluetoothRemoteCaching
	BluetoothPairing
	BluetoothCacheClearing
	BluetoothRawAdvertisements
	BluetoothStateAndMode
	BluetoothConnectionParams
)

type BluetoothProxy

type BluetoothProxy struct {
	// OnSubscribed fires when Home Assistant starts or stops wanting advertisements.
	OnSubscribed func(subscribed bool)

	// OnMode fires when Home Assistant chooses active or passive scanning. Active asks the
	// controller to request scan responses, which is a transmission rather than a listen.
	OnMode func(active bool)
	// contains filtered or unexported fields
}

BluetoothProxy forwards BLE advertisements to Home Assistant.

Use it as a Handler, usually via Chain alongside Entities. Scanning belongs to the caller: this carries what the radio found and never touches hardware.

func (*BluetoothProxy) Active

func (b *BluetoothProxy) Active() bool

Active reports whether Home Assistant asked for active scanning.

func (*BluetoothProxy) Advertise

func (b *BluetoothProxy) Advertise(ads []*api.BluetoothLERawAdvertisement) error

Advertise sends a batch of reports, and gives up the subscription if the connection has gone: nothing tells a handler that a client disappeared, so a failed write is the notice.

func (*BluetoothProxy) Handle

func (b *BluetoothProxy) Handle(_ context.Context, c *Conn, msg proto.Message) error

func (*BluetoothProxy) Report

func (b *BluetoothProxy) Report(state ScannerState) error

Report tells Home Assistant what the radio is doing.

func (*BluetoothProxy) Subscribed

func (b *BluetoothProxy) Subscribed() bool

Subscribed reports whether Home Assistant is listening.

type Button

type Button struct {
	Base
	DeviceClass string
	OnPress     func()
}

Button is stateless; Home Assistant only ever presses it.

type Call

type Call struct {
	// contains filtered or unexported fields
}

Call is one invocation's arguments, by name.

func NewCall added in v0.0.17

func NewCall(args map[string]any) (Call, error)

func (Call) Bool

func (c Call) Bool(name string) bool

func (Call) Float

func (c Call) Float(name string) float32

func (Call) Int

func (c Call) Int(name string) int

Int reads an integer argument. Home Assistant sends an int in one of two fields depending on how old the client is, so both are read and whichever is set wins.

func (Call) String

func (c Call) String(name string) string

type Camera added in v0.0.9

type Camera struct {
	Base

	// Image is asked for a JPEG when Home Assistant wants one. It runs on the connection's
	// goroutine, so a camera that takes its time holds that connection up.
	Image func() ([]byte, error)
}

Camera is a still picture Home Assistant asks for, rather than a state the device pushes.

type ColorMode

type ColorMode = api.ColorMode

type Conn

type Conn struct {
	// contains filtered or unexported fields
}

Conn is a single Home Assistant connection. Writes are safe from multiple goroutines; the ESPHome transports carry a per-direction nonce, so frames must not interleave.

func (*Conn) ClientInfo

func (c *Conn) ClientInfo() string

ClientInfo reports the client's self-description from HelloRequest, e.g. "Home Assistant".

func (*Conn) Close

func (c *Conn) Close() error

func (*Conn) LogsSubscribed

func (c *Conn) LogsSubscribed() bool

LogsSubscribed reports whether the client asked for logs at all, whatever the level.

func (*Conn) Send

func (c *Conn) Send(msg proto.Message) error

Send encodes and writes one message.

func (*Conn) StatesSubscribed

func (c *Conn) StatesSubscribed() bool

StatesSubscribed reports whether the client asked for entity state updates. Pushing state before then is wasted work.

func (*Conn) WantsLog

func (c *Conn) WantsLog(level api.LogLevel) bool

WantsLog reports whether the client asked for log lines at this level or quieter. The levels count upwards from none, so a client that asked for INFO is not sent DEBUG.

type ConnCloser added in v0.0.15

type ConnCloser interface{ ConnClosed(*Conn) }

type Description added in v0.0.17

type Description struct {
	Object string          `json:"id"`
	Kind   string          `json:"kind"`
	Info   json.RawMessage `json:"info"`
}

type Device

type Device struct {
	ID     uint32
	Name   string
	AreaID uint32
}

Device is a sub-device. ID must be non-zero and unique, since zero means the device itself.

type Entities

type Entities struct {
	// contains filtered or unexported fields
}

Entities holds the device's entity set and answers the messages that operate on it: ListEntities, the initial state dump after SubscribeStates, and the per-domain command requests. Use it as a Server's Handler.

Wrap it with Chain to combine with other handlers, such as a voice satellite.

func NewEntities

func NewEntities() *Entities

func (*Entities) Actions added in v0.0.17

func (e *Entities) Actions() []Action

func (*Entities) Add

func (e *Entities) Add(ents ...Entity) error

Add registers entities. A duplicate key means two of them share an ObjectID, which silently breaks state routing: whichever one Home Assistant hears about, updates from the other go nowhere. That is always a bug in the caller, so nothing is registered when it happens — the set is left as it was.

func (*Entities) AddActions

func (e *Entities) AddActions(actions ...*Action) error

AddActions registers what Home Assistant may call. Two actions of one name would leave one of them unreachable, so a duplicate registers nothing.

func (*Entities) Descriptions added in v0.0.17

func (e *Entities) Descriptions() []Description

func (*Entities) Handle

func (e *Entities) Handle(ctx context.Context, c *Conn, msg proto.Message) error

func (*Entities) Listen added in v0.0.17

func (e *Entities) Listen(fn func(State)) (cancel func())

func (*Entities) Press added in v0.0.17

func (e *Entities) Press(object string) error

func (*Entities) Run added in v0.0.17

func (e *Entities) Run(name string, c Call) (any, error)

func (*Entities) States added in v0.0.17

func (e *Entities) States() []State

type Entity

type Entity interface {
	// Object is the identifier the entity is known by, which is what a caller holding a list of
	// entities can say about them: Key is a hash and the rest of the interface is unexported.
	Object() string

	Key() uint32
	// contains filtered or unexported methods
}

Entity is one Home Assistant entity exposed by the device. Implementations live in this package; describe and state are unexported so the set stays closed.

type EntityCategory

type EntityCategory = api.EntityCategory

type Event

type Event struct {
	Base
	DeviceClass string

	// Types are the event types this entity can emit. Home Assistant rejects any type not
	// declared here.
	Types []string
}

Event is something that happens rather than something with a state, such as a button press. Home Assistant records each occurrence and its type, so automations can trigger on them.

func (*Event) Trigger

func (e *Event) Trigger(eventType string)

Trigger reports that an event of this type happened. Unknown types are ignored, since Home Assistant would reject them anyway.

type ExternalWakeWord

type ExternalWakeWord struct {
	ID               string
	Phrase           string
	TrainedLanguages []string
	ModelType        string
	Size             uint32
	Hash             string
	URL              string
}

ExternalWakeWord is a model Home Assistant is offering, served from its custom_wake_words directory. The device downloads URL and should check the result against Hash and Size before using it.

type Handler

type Handler interface {
	Handle(ctx context.Context, c *Conn, msg proto.Message) error
}

Handler receives messages the connection does not answer itself. Returning messages from Handle sends them back on the same connection.

Handle runs on the connection's read loop, so blocking work belongs in a goroutine. Messages are delivered in order, so the library cannot dispatch them concurrently.

func Chain

func Chain(handlers ...Handler) Handler

Chain runs handlers in order, stopping at the first error.

It passes bindServer through to whichever handlers want it. Without that, chaining Entities with anything else silently loses every state update: the server binds its handler by type, a plain func is not bindable, and only the states sent on connect would ever reach Home Assistant.

type HandlerFunc

type HandlerFunc func(ctx context.Context, c *Conn, msg proto.Message) error

HandlerFunc adapts a function to Handler.

func (HandlerFunc) Handle

func (f HandlerFunc) Handle(ctx context.Context, c *Conn, msg proto.Message) error

type Info

type Info struct {
	Name          string
	FriendlyName  string
	MACAddress    string
	Manufacturer  string
	Model         string
	Version       string
	SuggestedArea string

	// ProjectName is "<manufacturer>.<model>"; Home Assistant splits it at the dot into those two.
	ProjectName string

	// ESPHomeVersion is the ESPHome release to report being. It describes the protocol spoken rather
	// than the program speaking it, which is why it is separate from Version. Empty reports
	// DefaultESPHomeVersion.
	ESPHomeVersion string

	// VoiceFeatures advertises voice satellite capability. Leave zero on devices that
	// aren't satellites; Home Assistant won't offer voice for them.
	VoiceFeatures VoiceFeature

	// BluetoothFeatures advertises Bluetooth proxy capability. Leave zero on devices that are not
	// proxies; Home Assistant won't register a scanner for them.
	BluetoothFeatures BluetoothFeature

	// Devices are sub-devices entities can be assigned to, by Base.DeviceID. Home Assistant shows
	// each as its own device page under this one.
	Devices []Device
}

Info is what a device reports in response to DeviceInfoRequest. Name is also the hostname Home Assistant identifies the device by, so it must be stable.

type Light

type Light struct {
	Base
	SupportedColorModes []ColorMode
	Effects             []string

	// OnCommand runs when Home Assistant changes the light. If nil the command is
	// applied directly. When the device drives the ring itself, the callback is where
	// precedence between satellite state and user control gets resolved.
	OnCommand func(LightState)
	// contains filtered or unexported fields
}

Light is an RGB light. For EchoLocal this is the LED ring, which the device also drives from satellite state — see the precedence note on OnCommand.

func (*Light) Get

func (l *Light) Get() LightState

func (*Light) Set

func (l *Light) Set(s LightState)

type LightState

type LightState struct {
	On         bool
	Brightness float32
	ColorMode  ColorMode
	Red        float32
	Green      float32
	Blue       float32
	Effect     string

	// TransitionLength is what Home Assistant asked for, in milliseconds. Only set on
	// commands; the device decides whether to honour it.
	TransitionLength uint32
}

LightState is the full light state. Colour channels and brightness are 0..1.

type MediaCommand

type MediaCommand struct {
	HasCommand bool
	Command    MediaPlayerCommand

	HasVolume bool
	Volume    float32

	HasMediaURL bool
	MediaURL    string

	// Announcement marks TTS and alerts, which should duck or interrupt rather than
	// replace whatever is playing.
	HasAnnouncement bool
	Announcement    bool
}

MediaCommand is one media_player command. Only the fields Home Assistant set are populated, indicated by the Has* flags.

type MediaFormat

type MediaFormat struct {
	// Format is a container or codec name as ffmpeg knows it, such as "wav" or "flac".
	Format      string
	SampleRate  uint32
	Channels    uint32
	SampleBytes uint32

	// Announcement marks this as the format for announcements rather than ordinary media.
	Announcement bool
}

MediaFormat is audio the device can play. Home Assistant converts a source to the first format matching the purpose, with ffmpeg, so advertising something simple avoids needing a decoder.

type MediaPlayer

type MediaPlayer struct {
	Base
	SupportsPause bool

	// SupportedFormats is what Home Assistant should convert to. With none, it sends whatever the
	// source happens to be.
	SupportedFormats []MediaFormat

	// Features is what Home Assistant offers in the UI. Pause is added from SupportsPause, which
	// older versions read instead of the flags.
	Features MediaPlayerFeature

	OnCommand func(MediaCommand)
	// contains filtered or unexported fields
}

func (*MediaPlayer) Muted

func (p *MediaPlayer) Muted() bool

func (*MediaPlayer) SetMuted

func (p *MediaPlayer) SetMuted(m bool)

func (*MediaPlayer) SetState

func (p *MediaPlayer) SetState(s MediaPlayerState)

func (*MediaPlayer) SetVolume

func (p *MediaPlayer) SetVolume(v float32)

func (*MediaPlayer) State

func (p *MediaPlayer) State() MediaPlayerState

func (*MediaPlayer) Volume

func (p *MediaPlayer) Volume() float32

type MediaPlayerCommand

type MediaPlayerCommand = api.MediaPlayerCommand

type MediaPlayerFeature

type MediaPlayerFeature uint32

MediaPlayerFeature is what the player tells Home Assistant it can do, sent as feature_flags. Home Assistant computes the entity's supported features from this, so a player that sends none gets no controls in the UI. The bits are Home Assistant's MediaPlayerEntityFeature values.

const (
	MediaPlayerFeaturePause MediaPlayerFeature = 1 << iota
	MediaPlayerFeatureSeek
	MediaPlayerFeatureVolumeSet
	MediaPlayerFeatureVolumeMute
	MediaPlayerFeaturePreviousTrack
	MediaPlayerFeatureNextTrack

	MediaPlayerFeatureTurnOn
	MediaPlayerFeatureTurnOff
	MediaPlayerFeaturePlayMedia
	MediaPlayerFeatureVolumeStep
	MediaPlayerFeatureSelectSource
	MediaPlayerFeatureStop
	MediaPlayerFeatureClearPlaylist
	MediaPlayerFeaturePlay
	MediaPlayerFeatureShuffleSet
	MediaPlayerFeatureSelectSound
	MediaPlayerFeatureBrowseMedia
	MediaPlayerFeatureRepeatSet
	MediaPlayerFeatureGrouping
	MediaPlayerFeatureAnnounce
	MediaPlayerFeatureEnqueue
)

type MediaPlayerState

type MediaPlayerState = api.MediaPlayerState

type MessageInfo

type MessageInfo struct {
	ID     uint32
	Source api.APISourceType
	Name   protoreflect.FullName
	Type   protoreflect.MessageType
}

MessageInfo describes one API message as declared in api.proto. ID is the value of the (id) option, which is what travels on the wire ahead of the payload.

func Lookup

func Lookup(m proto.Message) (MessageInfo, bool)

func LookupID

func LookupID(id uint32) (MessageInfo, bool)

func Messages

func Messages() []MessageInfo

func (MessageInfo) FromClient

func (m MessageInfo) FromClient() bool

FromClient reports whether Home Assistant is the sender.

func (MessageInfo) FromDevice

func (m MessageInfo) FromDevice() bool

FromDevice reports whether the device is the sender.

type Number

type Number struct {
	Base
	Min, Max, Step float64
	Unit           string
	DeviceClass    string
	Mode           api.NumberMode

	OnCommand func(value float32)
	// contains filtered or unexported fields
}

func (*Number) Get

func (n *Number) Get() float32

func (*Number) Set

func (n *Number) Set(v float32)

type PSK

type PSK [32]byte

PSK is the 32-byte Noise pre-shared key. Possession of it is the only identity the protocol has, so it must be unique per device. All-zeros is reserved to mark a device unprovisioned — see Unprovisioned.

func GeneratePSK

func GeneratePSK() (PSK, error)

func ParsePSK

func ParsePSK(s string) (PSK, error)

ParsePSK accepts the base64 form Home Assistant asks for.

func Unprovisioned

func Unprovisioned() *PSK

Unprovisioned returns the reserved all-zeros key. The connection is still Noise, but anyone can complete the handshake: passive-sniffing protection, no authentication.

func (PSK) IsZero

func (k PSK) IsZero() bool

IsZero reports whether this is the reserved unprovisioned key.

func (PSK) String

func (k PSK) String() string

type PipelineEvent

type PipelineEvent struct {
	Type VoiceEvent
	Data map[string]string
}

PipelineEvent is a stage transition reported by Home Assistant's pipeline, with the key/value pairs that stage carried (transcript text, TTS url, error details).

type ScannerState

type ScannerState = api.BluetoothScannerState

ScannerState is what the radio is doing.

type Select

type Select struct {
	Base
	Options []string

	// OnCommand runs when Home Assistant picks an option. If nil the new value is
	// applied directly; if set, the callback owns whether to call Set.
	OnCommand func(value string)
	// contains filtered or unexported fields
}

Select is a fixed list of options, used for things like the active wake word.

func (*Select) Get

func (s *Select) Get() string

func (*Select) Set

func (s *Select) Set(v string)

func (*Select) SetOptions added in v0.0.13

func (s *Select) SetOptions(options []string)

type Sensor

type Sensor struct {
	Base

	// Unit is what the number is in, as Home Assistant spells it: "kB", "%", "dB".
	Unit string

	// DeviceClass is what kind of quantity it is, which is what lets Home Assistant convert it.
	DeviceClass string

	// StateClass says how it accumulates. Measurement is the usual answer for anything that goes up
	// and down; the default is none, which keeps it out of long-term statistics.
	StateClass SensorStateClass

	// Decimals is how many places Home Assistant shows.
	Decimals int32
	// contains filtered or unexported fields
}

Sensor is a number Home Assistant reads. Unlike a TextSensor it is recorded and graphed, and with a device class Home Assistant will convert it for display — a size reported in kB can be shown in MB without the device knowing.

The protocol carries the state as a float32, so a value is exact to 24 bits: report a size in the unit it will be read in rather than in bytes, or a large one lands on the nearest representable multiple.

func (*Sensor) Get

func (s *Sensor) Get() float32

func (*Sensor) Set

func (s *Sensor) Set(v float32)

Set publishes a value.

type SensorStateClass

type SensorStateClass int32

SensorStateClass is how a sensor's value accumulates over time.

const (
	StateClassNone SensorStateClass = iota
	StateClassMeasurement
	StateClassTotalIncreasing
	StateClassTotal
)

type Server

type Server struct {
	Info Info

	// PSK selects the transport, and the three options serve different purposes:
	//
	//   a real key       authenticated Noise — production
	//   Unprovisioned()  Noise with the reserved zero key, so Home Assistant can push a
	//                    real one; needs aioesphomeapi 45.6.0 or newer
	//   nil              plaintext — readable on the wire, so useful for debugging,
	//                    but ESPHome is removing it in 2027.2.0
	PSK *PSK

	// OnSetEncryptionKey handles Home Assistant provisioning a real key over a zero-PSK
	// connection. Leave nil to refuse, which is right when keys are installed
	// out-of-band.
	OnSetEncryptionKey func(PSK) error

	// OnSubscribed fires when a client asks for entity state, which is the first moment
	// anything sent to it will arrive. Something the device wants to report but could not
	// while it was alone waits for this.
	//
	// It fires once per connection, on that connection's read loop, so it must not block.
	OnSubscribed func()

	// WriteTimeout bounds one write. Zero leaves writes unbounded, so a client that stops reading
	// blocks the write lock until TCP gives up and every other sender waits behind it.
	WriteTimeout time.Duration

	// Addr defaults to ":6053".
	Addr string

	Handler Handler
	Logger  *slog.Logger
	// contains filtered or unexported fields
}

func (*Server) Broadcast

func (s *Server) Broadcast(msg proto.Message) error

Broadcast sends a message to every connection that has subscribed to state updates, which is how entity state is pushed.

func (*Server) Close

func (s *Server) Close() error

func (*Server) FireEvent

func (s *Server) FireEvent(name string, data map[string]string) error

FireEvent asks Home Assistant to put an event on its bus. The name is passed whole and must be under the esphome domain, which is the only one Home Assistant accepts here.

Values are strings because the wire carries no other kind. Home Assistant adds the device id itself, so an event says which device it came from without being told.

func (*Server) ListenAndServe

func (s *Server) ListenAndServe(ctx context.Context) error

ListenAndServe accepts connections until ctx is cancelled or Close is called.

func (*Server) Log

func (s *Server) Log(level api.LogLevel, line string)

Log sends one line to every client that subscribed at this level or louder. A write that fails is left to the read loop to notice, since logging is not worth failing anything else over.

func (*Server) LogsSubscribed

func (s *Server) LogsSubscribed() bool

LogsSubscribed reports whether any client is taking logs. Lines produced before one is have nowhere to go, so a caller holding a backlog can wait rather than discard it.

func (*Server) Reconnect

func (s *Server) Reconnect()

Reconnect drops every client, so Home Assistant reconnects and reads the device afresh.

Some of what a device reports is only fetched once per connection — the voice satellite's available wake words among it — and there is no message for revising it. When that changes, the connection is the only lever: Home Assistant reconnects on its own, within seconds. A connection in the middle of a message closes once that message is answered.

func (*Server) Serve

func (s *Server) Serve(ctx context.Context, ln net.Listener) error

type State added in v0.0.17

type State struct {
	Object string          `json:"id"`
	Value  json.RawMessage `json:"state"`
}

type Switch

type Switch struct {
	Base
	DeviceClass string

	// Assumed tells Home Assistant the device cannot confirm the state, so it should offer
	// separate on and off controls rather than a toggle.
	Assumed bool

	// OnCommand runs when Home Assistant sets the switch. If nil the new value is applied
	// directly; if set, the callback owns whether to call Set.
	OnCommand func(value bool)
	// contains filtered or unexported fields
}

Switch is a boolean Home Assistant can both read and set, for state the device owns but the user can change from either end — a microphone mute being the obvious one.

func (*Switch) Get

func (s *Switch) Get() bool

func (*Switch) Set

func (s *Switch) Set(v bool)

type Text added in v0.0.11

type Text struct {
	Base
	MinLength, MaxLength uint32
	Pattern              string
	Mode                 api.TextMode

	// OnCommand runs when Home Assistant sets a value. If nil the new value is applied directly; if
	// set, the callback owns whether to call Set.
	OnCommand func(value string)
	// contains filtered or unexported fields
}

Text is a string Home Assistant can set.

func (*Text) Get added in v0.0.11

func (t *Text) Get() string

func (*Text) Set added in v0.0.11

func (t *Text) Set(v string)

type TextSensor

type TextSensor struct {
	Base
	DeviceClass string
	// contains filtered or unexported fields
}

TextSensor is a string Home Assistant reads. With no unit or state class its changes appear in the logbook, unlike a numeric sensor's. Home Assistant truncates a state at 255 characters.

func (*TextSensor) Get

func (t *TextSensor) Get() string

func (*TextSensor) Set

func (t *TextSensor) Set(v string)

type TimerEvent

type TimerEvent struct {
	Type         VoiceTimerEvent
	TimerID      string
	Name         string
	TotalSeconds uint32
	SecondsLeft  uint32
	IsActive     bool
}

type Update

type Update struct {
	Base
	DeviceClass string

	// OnCommand runs when Home Assistant asks to install or check for an update.
	OnCommand func(UpdateCommand)
	// contains filtered or unexported fields
}

Update is a firmware update entity. ESPHome devices expose one even when they never self-update, and Home Assistant's satellite setup wizard expects it.

func (*Update) Get

func (u *Update) Get() UpdateState

func (*Update) Set

func (u *Update) Set(s UpdateState)

type UpdateCommand

type UpdateCommand = api.UpdateCommand

type UpdateState

type UpdateState struct {
	CurrentVersion string
	LatestVersion  string
	Title          string
	ReleaseSummary string
	ReleaseURL     string

	InProgress bool
	Progress   float32
}

UpdateState is the firmware state reported to Home Assistant. An update is offered when LatestVersion differs from CurrentVersion.

type VoiceEvent

type VoiceEvent = api.VoiceAssistantEvent

type VoiceFeature

type VoiceFeature uint32

VoiceFeature flags go in DeviceInfoResponse.voice_assistant_feature_flags and tell Home Assistant what the satellite can do.

The enum lives in ESPHome's C++ (voice_assistant.h), not api.proto, so these are transcribed from there rather than generated.

const (
	FeatureVoiceAssistant    VoiceFeature = 1 << 0
	FeatureSpeaker           VoiceFeature = 1 << 1
	FeatureAPIAudio          VoiceFeature = 1 << 2
	FeatureTimers            VoiceFeature = 1 << 3
	FeatureAnnounce          VoiceFeature = 1 << 4
	FeatureStartConversation VoiceFeature = 1 << 5
	FeatureMultiChannelAudio VoiceFeature = 1 << 6
)

type VoiceSatellite

type VoiceSatellite struct {
	AvailableWakeWords []WakeWord
	ActiveWakeWords    []string
	MaxActiveWakeWords uint32

	// OnPipelineEvent reports stage transitions: STT text, TTS urls, errors.
	OnPipelineEvent func(PipelineEvent)

	// OnTTSAudio receives audio to play. Only used when Home Assistant negotiated
	// audio over the API, which it signals in SubscribeVoiceAssistantRequest.
	OnTTSAudio func(data []byte, end bool)

	// OnStartRequestAccepted fires with Home Assistant's answer to StartTurn. A
	// non-zero port means it wants the legacy UDP audio path instead.
	OnStartRequestAccepted func(port uint32, err bool)

	OnAnnounce func(Announce)
	OnTimer    func(TimerEvent)

	// OnSubscribed fires when Home Assistant starts or stops listening for voice turns. Until it
	// has subscribed, a wake word cannot be served at all, so this is what readiness means for a
	// satellite — and it is worth showing, since the device otherwise looks broken rather than
	// unattached.
	OnSubscribed func(subscribed bool)

	// OnSetActiveWakeWords fires when Home Assistant changes the selection.
	OnSetActiveWakeWords func(ids []string)

	// OnExternalWakeWords fires when Home Assistant offers models from its
	// custom_wake_words directory. Returning the ones the device has adopted adds them
	// to the advertised list; return nil to ignore the offer.
	//
	// It runs before the configuration reply is sent, so a slow download belongs in a
	// goroutine — adopt on the next request instead of blocking this one.
	OnExternalWakeWords func([]ExternalWakeWord) []WakeWord
	// contains filtered or unexported fields
}

VoiceSatellite implements the device half of the voice_assistant flow. The device initiates every turn, so nothing here is driven by Home Assistant polling — which is what makes a pre-flight arbitration check invisible to the protocol.

Use it as a Handler, usually via Chain alongside Entities.

func (*VoiceSatellite) APIAudio

func (v *VoiceSatellite) APIAudio() bool

APIAudio reports whether audio travels over the API connection rather than the legacy UDP path.

func (*VoiceSatellite) AnnounceFinished

func (v *VoiceSatellite) AnnounceFinished(success bool) error

AnnounceFinished reports the outcome of an Announce back to Home Assistant.

func (*VoiceSatellite) ConnClosed added in v0.0.15

func (v *VoiceSatellite) ConnClosed(c *Conn)

func (*VoiceSatellite) EndAudio

func (v *VoiceSatellite) EndAudio() error

EndAudio marks the end of captured audio for the turn.

func (*VoiceSatellite) Handle

func (v *VoiceSatellite) Handle(ctx context.Context, c *Conn, msg proto.Message) error

func (*VoiceSatellite) SendAudio

func (v *VoiceSatellite) SendAudio(data []byte) error

SendAudio streams captured audio for the active turn.

func (*VoiceSatellite) StartTurn

func (v *VoiceSatellite) StartTurn(wakeWordPhrase string, settings *api.VoiceAssistantAudioSettings) error

StartTurn tells Home Assistant a wake word fired and a turn is beginning. The device decides when to call this, so any pre-flight work — such as winning an arbitration claim — happens before it and is invisible to Home Assistant.

func (*VoiceSatellite) StopTurn

func (v *VoiceSatellite) StopTurn() error

StopTurn ends the current turn early.

func (*VoiceSatellite) Subscribed

func (v *VoiceSatellite) Subscribed() bool

Subscribed reports whether Home Assistant is listening for voice turns.

type VoiceTimerEvent

type VoiceTimerEvent = api.VoiceAssistantTimerEvent

type WakeWord

type WakeWord struct {
	ID               string
	Phrase           string
	TrainedLanguages []string
}

WakeWord is one model the device can listen for.

Directories

Path Synopsis
internal
wire
Package wire implements the ESPHome native API transport: frame codecs for the plaintext and Noise-encrypted variants, from the device (responder) side.
Package wire implements the ESPHome native API transport: frame codecs for the plaintext and Noise-encrypted variants, from the device (responder) side.
Package mdns advertises a device over mDNS so Home Assistant discovers it.
Package mdns advertises a device over mDNS so Home Assistant discovers it.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL