You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Advertise ESPHome API 1.18: IR/RF transmit completion, Sendspin, serial identity, device wizard - #44
Sync priv/protos to esphome/esphome@0a501b7ef and regenerate lib/espex/proto; register message ids 153-160
Answer every InfraredRFTransmitRawTimingsRequest with InfraredRFTransmitCompleteResponse (key and device_id echoed, requesting connection only). Success is the return of transmit_raw/3; no adapter, no timings or more than 30 s on air is refused with success: false
New optional Espex.Sendspin adapter (:sendspin): fills DeviceCapabilitiesResponse.sendspin per request and answers SendspinPairingTokenRequest, with the token sent only over Noise (ENCRYPTION_REQUIRED on plaintext) and never logged
Serial proxy identity: Espex.SerialProxy.Identity, optional identity/1 callback, Espex.push_serial_proxy_identity/2; SubscribeSerialProxyIdentityRequest answered per advertised port, then on each pushed change. SerialProxy.Info.port_type gains :usb_serial
New optional Espex.Wizard adapter (:wizard, OTP 28+ for :zstd): Espex.Wizard.Document builder validated against ESPHome's limits, JSON+zstd blob, Espex.Wizard.fnv1_hash/1 input keys, WizardInputSetRequest validated as api_wizard.cpp, wizard.configured capability
Espex.Supervisor and Espex.update_adapters/2 accept :sendspin and :wizard; a :wizard adapter is refused without :zstd
@api_version_minor 18, version 0.12.0; README, architecture and entity-types guides updated (missing_state on switch, climate, water heater)
Regenerates lib/espex/proto from esphome/esphome@0a501b7ef (API 1.18 on
the firmware side). Every change is additive.
New in the schema and now decodable:
- InfraredRFTransmitCompleteResponse (153)
- SubscribeSerialProxyIdentityRequest (154), SerialProxyIdentity (155),
UsbDeviceDescriptor, and the identity source and flag enums
- DeviceWizardRequest (156), DeviceWizardResponse (157),
WizardInputSetRequest (158), DeviceCapabilitiesResponse.wizard
- SendspinPairingTokenRequest (159), SendspinPairingTokenResponse (160),
DeviceCapabilitiesResponse.sendspin
- HelloRequest.outgoing_connection_target and
DeviceInfoResponse.api_outgoing_connection_supported
- SERIAL_PROXY_PORT_TYPE_USB_SERIAL
- missing_state on switch, climate and water heater state responses
The new progmem field option is C++-only; on the Elixir side it adds
the extension and a deprecated: false on the fields that carry it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aioesphomeapi paces IR/RF frames on this reply once a device reports
API 1.18, and waits 35 s past each frame's air time without it, so
espex must send it before advertising 1.18.
- The completion is the return of transmit_raw/3: :ok reports
success: true, {:error, _} success: false. It echoes key and
device_id and goes only to the requesting connection.
- A request with no infrared adapter or no timings is refused at once
with success: false, without calling the adapter.
- The :infrared_transmit action carries device_id, and its type names
InfraredProxy.transmit_opts() instead of SerialProxy.open_opts().
- InfraredProxy documents when transmit_raw/3 should return and that it
blocks the connection for the frame's air time.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A host running a Sendspin client reports it through a :sendspin
adapter. espex fills DeviceCapabilitiesResponse.sendspin and answers
SendspinPairingTokenRequest (API 1.18) from it.
- Espex.Sendspin: client_id/0, pairing_token/0, and an optional
feature_flags/0 that defaults to the PAIRING_TOKEN bit.
- The adapter is read on every request, because the client id and token
only exist once the Sendspin client has started.
- The token goes only over Noise. A plaintext connection, or one still
in the handshake, gets ENCRYPTION_REQUIRED and the adapter is not
asked.
- Without an adapter, capabilities carry no sendspin and the token
request is not answered, as firmware built without Sendspin.
- The token never reaches a log. A crashing adapter or an unexpected
return is logged by shape only and answered FAILED.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
At API 1.18 a client subscribes to the identity of the device behind
each serial proxy port, to tell identical devices apart.
- Espex.SerialProxy.Identity describes a port: source :none or :usb,
the connected and error flags, the descriptor strings and the USB
ids. The USB descriptor is sent only for :usb.
- An optional identity/1 callback on Espex.SerialProxy supplies it.
Without the callback, or when it returns nil, the port reports NONE.
- A subscribe is answered with one SerialProxyIdentity per advertised
port. Without a serial adapter it goes unanswered, as firmware
without the serial proxy.
- Espex.push_serial_proxy_identity/2 broadcasts a change. Each
connection forwards it only if it subscribed and advertises the
instance.
- SerialProxy.Info.port_type gains :usb_serial.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The device wizard is the pages Home Assistant shows when a device is
added (API 1.18). A page can show entities of the device and ask the
user to pick Home Assistant entities, which the client sends back.
- Espex.Wizard: wizard/0 returns an Espex.Wizard.Document, and
input_set/2 receives the picked entity ids by input key.
- Document.new/1 builds pages of Entity and Input structs, with
EntityFilter on inputs, and checks them against ESPHome's limits:
string lengths, filter fields, max_length, unique input keys, and a
compressed size that fits one API message.
- The wire blob is compact version 1 JSON in one zstd frame, with empty
values left out, as wizard.py builds it.
- Input keys are Espex.Wizard.fnv1_hash/1 of a host-chosen id, the same
FNV-1 over code points ESPHome uses.
- The document is captured when a connection is accepted. Capabilities
then report wizard.configured, and DeviceWizardRequest gets the blob.
- WizardInputSetRequest is validated as api_wizard.cpp does: unknown
keys and invalid values are ignored with a warning that leaves out
the value. Spaces around the ids of a multiple input are trimmed.
- Without a wizard adapter the capability is unset and both requests
are ignored.
- :zstd needs OTP 28. Espex.Supervisor refuses a :wizard adapter on an
older OTP, and so does Espex.update_adapters/2.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every 1.18 message a client relies on is now handled: IR/RF transmit
completion, serial proxy identity, and the optional Sendspin and device
wizard messages. Version 0.12.0.
- @api_version_minor 18, with a 1.18 entry in the api_version_minor/0
version history.
- README: API 1.18 features, Espex.Sendspin and Espex.Wizard.
- architecture guide: every adapter option in the configuration table.
- entity types guide: missing_state on switch, climate and water heater.
- @moduledoc since: "0.12.0" on the new modules.
- Document.invalid/1 is specced no_return for dialyzer.
- HANDOFF-sendspin.md is removed; the work it planned is done.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A Sendspin pairing token that is not valid UTF-8 is answered FAILED.
protobuf refuses such a string with the bytes in its error, which put
the token in the connection's crash log. An empty token is FAILED too.
- A crashing or ill-typed client_id/0 or feature_flags/0 reports an
empty client id instead of closing the connection, since clients read
capabilities on every connect.
- An IR transmit whose timings times repeats exceed 30 s on air is
refused with success: false, so one request cannot hold the
connection for hours.
- A WizardInputSetRequest value over 4095 bytes is refused before it is
split.
- The wizard blob limit is 65511 bytes: the Noise frame size also
covers the response's tag and length.
- Tests check that nothing more was sent with a ping round trip instead
of a 200 ms timeout, and check that the token stays out of the log on
the OK and plaintext paths.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A Sendspin pairing token over 127 bytes (api.proto max_data_length)
is answered FAILED, without logging it.
- A %Wizard.Document{} returned without Document.new/1 is checked and
normalised at accept. Its input keys were nil, so every input set for
it was ignored as an unknown key.
- Wizard filter lists reject entries that are not strings; nil passed
as optional text and encoded as null.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Protocol interoperability and secret-handling changes need human validation, and a confirmed response-size defect remains unresolved.
No open issues 🎉
1 previously missed issue
In code that hasn't changed since last review
Reject client IDs exceeding the 43-byte protocol limit
lib/espex/connection.ex:1335
This accepts arbitrarily large UTF-8 client IDs despite the 43-byte maximum in api.proto. A 65,536-byte adapter return makes the capabilities response exceed the 16-bit Noise frame lengths, producing a malformed encrypted response instead of the empty-ID fallback. Reject IDs over 43 bytes here, and add regression tests for the 43-byte boundary and oversized returns.
A client_id longer than api.proto's max_data_length is reported empty
with a warning, like any other unexpected return, instead of being
sent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
priv/protosto esphome/esphome@0a501b7ef and regeneratelib/espex/proto; register message ids 153-160InfraredRFTransmitRawTimingsRequestwithInfraredRFTransmitCompleteResponse(key and device_id echoed, requesting connection only). Success is the return oftransmit_raw/3; no adapter, no timings or more than 30 s on air is refused withsuccess: falseEspex.Sendspinadapter (:sendspin): fillsDeviceCapabilitiesResponse.sendspinper request and answersSendspinPairingTokenRequest, with the token sent only over Noise (ENCRYPTION_REQUIREDon plaintext) and never loggedEspex.SerialProxy.Identity, optionalidentity/1callback,Espex.push_serial_proxy_identity/2;SubscribeSerialProxyIdentityRequestanswered per advertised port, then on each pushed change.SerialProxy.Info.port_typegains:usb_serialEspex.Wizardadapter (:wizard, OTP 28+ for:zstd):Espex.Wizard.Documentbuilder validated against ESPHome's limits, JSON+zstd blob,Espex.Wizard.fnv1_hash/1input keys,WizardInputSetRequestvalidated asapi_wizard.cpp,wizard.configuredcapabilityEspex.SupervisorandEspex.update_adapters/2accept:sendspinand:wizard; a:wizardadapter is refused without:zstd@api_version_minor 18, version 0.12.0; README, architecture and entity-types guides updated (missing_stateon switch, climate, water heater)HANDOFF-sendspin.md🤖 Generated with Claude Code