Skip to content
Get started

Sendspin

The sendspin component adds support for Sendspin, a multi-room synchronized audio protocol. The hub manages the connection to a Sendspin server and distributes state updates to any Sendspin child components.

The device uses mDNS to discover Sendspin servers on the network, so mDNS traffic must not be blocked on your network. TCP port 8928 must also be reachable between the device and the server.

On its own, the hub does not expose any user-facing entities. Once a Sendspin server is present on the network, it will connect to the device and begin sending group state updates, but audio playback, control, and metadata require the corresponding child components to be configured as well. The hub can also stream a microphone to the server; see Source. The Sendspin Switch can stop the client so no server connects.

Every connection to a Sendspin server is encrypted. By default any server on the network may play on the device. Turn off unpaired access to allow only servers that have paired with it.

This platform only works on ESP32-based chips, with the ESP-IDF framework.

IMPORTANT

The device needs a Sendspin 1.0.0-rc1 server, such as Music Assistant 2.11.0b4 or newer. Servers using an earlier protocol version cannot connect.

WARNING

The Sendspin protocol is not yet finalized and this component is considered experimental. Breaking changes may occur in future ESPHome releases as the protocol and component evolve.

# Example configuration entry
sendspin:
  • id (Optional, ID): Manually specify the ID for this hub. Required if you want to reference this hub explicitly from child components.

  • task_stack_in_psram (Optional, boolean): Place the internal HTTP server and protocol task stacks in PSRAM to reduce internal RAM usage. Requires the Psram component to be configured. Defaults to false.

  • manufacturer (Optional, string): The manufacturer the device reports to the Sendspin server. Defaults to the author part of the project name when one is set, otherwise ESPHome.

  • model (Optional, string): The model the device reports to the Sendspin server. Defaults to the project part of the project name when one is set, otherwise the device’s name.

  • firmware_version (Optional, string): The firmware version the device reports to the Sendspin server. Defaults to the project version when one is set, otherwise the ESPHome version.

  • static_pairing_code (Optional, string): A fixed pairing code of exactly 8 decimal digits. Always quote the value, including in secrets.yaml when using !secret, for example "01234567", since an unquoted number is rejected. Cannot be combined with a dynamic pairing code. See Static Pairing Code.

  • unpaired_access (Optional, boolean): Whether servers that have not paired with the device may play on it. Set to false once a server is paired so only paired servers can play. Defaults to true. To change it at runtime, use a Sendspin Switch of type unpaired_access instead; the two cannot be combined.

  • source (Optional, mapping): Streams a microphone to the Sendspin server. See Source.

    • microphone (Required, Microphone Source Configuration): The microphone to stream. The source’s bits_per_sample (default 16) must be 16, 24 or 32, and at most two channels can be selected. The microphone must have a fixed sample rate, which the stream uses as is. Audio is sent as uncompressed PCM.
    • task_stack_in_psram (Optional, boolean): Place the source task’s stack in PSRAM to reduce internal RAM usage. Only use this if the device runs out of internal memory. Requires the PSRAM component. Defaults to false.
    • on_streaming_started (Optional, Automation): An automation to perform when the server starts the stream.
    • on_streaming_stopped (Optional, Automation): An automation to perform when the stream stops.

A server pairs with the device once, and the device stores the result. A paired server can always play, even when unpaired access is off. The device can offer these ways to pair:

Set static_pairing_code to a fixed 8-digit code, then enter that code on the server. Pick a random code that is unique to the device. An attempt must also be confirmed on the device with the sendspin.confirm_pairing_window action, for example from a button press. on_open_pairing_window runs when an attempt is waiting for that confirmation.

Confirming opens a pairing window of 5 minutes. It closes early when pairing succeeds, after 5 wrong codes, when the server disconnects, or when sendspin.cancel_pairing_window runs. After it closes, the next attempt has to be confirmed again.

The device shows a new 6-digit code for each attempt, which you enter on the server. Configure on_display_pairing_code to show the code, or publish it with a Sendspin Text Sensor of type pairing_code. Attempts do not need to be confirmed on the device, and there is no pairing window. After 20 wrong codes, though, further attempts wait until sendspin.confirm_pairing_window runs, which resets the count. Give the device a way to run it, such as a button. on_open_pairing_window runs when an attempt is waiting.

Only one pairing code method can be offered. The device offers a dynamic pairing code when on_display_pairing_code or a pairing_code text sensor is configured, so neither can be combined with static_pairing_code.

While unpaired_access is true, any server on the network may play on the device without pairing. When it is false, servers that have not paired can still connect to pair, but cannot play. A Sendspin Switch of type unpaired_access can turn it on and off at runtime instead.

WARNING

With unpaired_access: false, make sure the device offers a static or dynamic pairing code. Otherwise no new server can pair, so only servers that already paired can play.

With source configured, the device can stream a microphone to the Sendspin server, for example to share a line input or a room microphone. The server decides when the stream runs. The device starts the microphone when the server opens the stream and stops it when the stream closes. on_streaming_started and on_streaming_stopped run at those moments.

Each chunk carries the time its audio was captured, so the server can line it up with other audio.

The audio is sent as uncompressed PCM, which needs a stable network connection: 256 kbit/s for 16 kHz mono 16-bit audio, and about 1.5 Mbit/s for 48 kHz stereo 16-bit audio. 24 and 32 bits per sample need 1.5 and 2 times as much. While sending stalls, the device keeps about half a second of audio in a buffer, placed in PSRAM when available. If a stall lasts longer, the delayed audio is dropped and the stream continues from live audio.

To tell the server whether the device has audio to offer, use the sendspin.source.set_signal action. The server can start the stream when this changes to present and stop it when it changes to absent. Set it from a button or a switch, or for a line input, from whether the connected device is playing. A microphone should use a button or a switch rather than listening for sound.

  • on_pairing_succeeded (Optional, Automation): An automation to perform when a server finishes pairing with the device. The variable server_id (std::string) holds the server’s ID.
  • on_pairing_failed (Optional, Automation): An automation to perform when a pairing attempt fails. The variables server_id (std::string) and reason (StringRef) are available. reason is one of attempt_timeout, concurrent_attempt, method_not_supported, pairing_code_mismatch, user_cancelled or unknown. Compare it directly, for example reason == "user_cancelled", and use reason.c_str() to log it.
  • on_open_pairing_window (Optional, Automation): An automation to perform when a pairing attempt is waiting to be confirmed on the device: a static pairing code attempt while no pairing window is open, or a dynamic pairing code attempt after 20 wrong codes. Run sendspin.confirm_pairing_window to allow it.
  • on_close_pairing_window (Optional, Automation): An automation to perform when the attempt that ran on_open_pairing_window ends, whatever the outcome. Use it to dismiss a prompt.
  • on_display_pairing_code (Optional, Automation): An automation to perform when a server asks the device to show a dynamic pairing code. The variable code (std::string) holds the 6-digit code.
  • on_clear_pairing_code (Optional, Automation): An automation to perform when the dynamic pairing code is no longer needed.

This action switches the Sendspin client to the next active group.

on_...:
- sendspin.switch:

This action confirms a pairing attempt on the device. Run it in response to a user action such as a button press.

  • With a static pairing code, it opens the pairing window. If no attempt is waiting yet, the window still opens, so you can press the button before starting pairing on the server.
  • With a dynamic pairing code, it resets the count of wrong codes, so a waiting attempt can continue.
on_...:
- sendspin.confirm_pairing_window:

This action closes the pairing window, so a waiting pairing attempt is not confirmed.

on_...:
- sendspin.cancel_pairing_window:

This action tells the server whether the source has audio to offer. The device reports that it has none until the action first runs. Requires a source block.

on_...:
- sendspin.source.set_signal:
present: true
  • present (Required, boolean, templatable): true when the source has audio to offer, false when it does not.

This condition checks whether the source is streaming to the server. Requires a source block.

on_...:
- if:
condition:
sendspin.source.is_streaming:
then:
- logger.log: "Streaming to the server"

Pair with a static code that is confirmed with a button press:

sendspin:
static_pairing_code: !secret sendspin_pairing_code
on_open_pairing_window:
- logger.log: "Press the button to pair"
binary_sensor:
- platform: gpio
pin: GPIOXX
name: "Pair Button"
on_press:
- sendspin.confirm_pairing_window:

Stream a stereo line input to the server, with a switch that offers it to the server. line_in is a microphone with a fixed sample rate:

sendspin:
source:
microphone:
microphone: line_in
channels: [0, 1]
switch:
- platform: template
name: "Share Line In"
optimistic: true
turn_on_action:
- sendspin.source.set_signal:
present: true
turn_off_action:
- sendspin.source.set_signal:
present: false

Report the line input as present while something plays on it, using a sound level sensor. This keeps the input capturing all the time, so use it for a line input, not a microphone. Set the threshold between the input’s level when nothing plays and its level for quiet music. delayed_off keeps short pauses, such as between tracks, from reporting the input as absent:

sendspin:
source:
microphone:
microphone: line_in
channels: [0, 1]
sensor:
- platform: sound_level
microphone:
microphone: line_in
channels: [0, 1]
passive: false
measurement_duration: 500ms
rms:
id: line_in_level
internal: true
binary_sensor:
- platform: template
name: "Line In Playing"
lambda: return id(line_in_level).state > -60;
filters:
- delayed_off: 10s
on_press:
- sendspin.source.set_signal:
present: true
on_release:
- sendspin.source.set_signal:
present: false

Pair with a dynamic code that is shown as a text sensor, with a button for when attempts need confirming:

sendspin:
text_sensor:
- platform: sendspin
name: "Pairing Code"
type: pairing_code
binary_sensor:
- platform: gpio
pin: GPIOXX
name: "Pair Button"
on_press:
- sendspin.confirm_pairing_window:
  • Sendspin Image: Displays album and artist artwork streamed from the group on a connected display.
  • Sendspin Group Media Player: Exposes group-wide playback and volume controls as a Home Assistant media player entity.
  • Sendspin Media Source: Plays synchronized audio from a Sendspin group through a Speaker Source media player.
  • Sendspin Sensor: Exposes numeric metadata about the currently playing audio (track progress, track duration, etc.).
  • Sendspin Switch: Enables and disables the Sendspin client, and turns unpaired access on and off.
  • Sendspin Text Sensor: Exposes metadata about the currently playing audio (title, artist, album, etc.), and the pairing code.