Skip to main content

Sonos (hc-sonos)

The hc-sonos plugin discovers Sonos speakers on your network, registers them as HomeCore media_player devices, and keeps transport state in sync through MQTT.

It also keeps its own standalone HTTP API. That is intentional. You can use the plugin directly if you want, but once a speaker is registered in HomeCore the preferred automation path is through the HomeCore device ID.

Its screens​

All of these come with the plugin itself — update the plugin, and they update.

  • Its page (Settings › Plugins › Sonos): how many speakers there are and when they were found; speakers that are in no room yet, with a way to place them; the speakers as the groups they play in — a group is one card with the music it plays, one volume for the whole group and each speaker's own volume under it, and Change the group / Ungroup; and your Sonos favourites with their artwork: pick one, tick the speakers it should play on, and press Play.
  • Play together (Group speakers… on the page, or Change the group): tick the speakers that should play the same music. The one marked playing from is where the music comes from; Play from here moves it. All speakers and Each on its own are shortcuts. The plugin makes the whole group in one go and says, speaker by speaker, what happened — joined, left, or no answer for a speaker that is off or asleep, with a button to try it again.
  • A speaker's panel: what it plays — a track with its progress (tap the bar to jump), or a radio station, live, with nothing to skip — the transport, mute and volume (and the group's volume, when it is in one); then three tabs: Favourites to play one here, Group to tick the speakers that play this one's music, and Sound for bass, treble and loudness.
  • A speaker's card on Home and the rooms: its artwork, what it plays, the track's progress, play/pause and its volume; compact, one row with a play button. A speaker in a group shows the group's music.
  • Sonos speakers, a card you add yourself (Edit → Add a card): every speaker, or the ones its settings tick — what each plays, its volume, play/pause.
  • Sonos favourites, a card you add yourself: the favourites with their artwork; a tap plays one on the speaker its settings choose (Play on), or on whichever speaker is playing.
  • In automations, a step on a speaker is Play a favourite (optionally at a volume), Volume, Pause or Group, and says so in words: "play Piano for Sleeping on Bathroom at volume 20", "play Bathroom's music on Office-1 and Office-2 too".

What the screens send​

CommandDoes
{"action": "play_favorite", "favorite": "Piano for Sleeping", "volume": 20}sets this speaker's volume, then plays the favourite (on its group, if it is in one)
{"volume": 20}this speaker's own volume
{"action": "set_group_volume", "volume": 24}the whole group's volume, keeping each speaker's balance
{"action": "pause"}, {"action": "play"}to the group's coordinator, whichever speaker it is sent to
{"action": "group", "members": ["sonos_office_1", "sonos_office_2"]}makes this speaker's group exactly these — see below

The page and Play together use the plugin's own group_speakers action, which does the same as group and answers speaker by speaker, and its read-only status action for when the speakers were last looked for.

Groups​

A Sonos group plays the music of one speaker, its coordinator; the others are members.

  • A new group shows at once. The plugin listens for Sonos's group-change events and reads the groups again when one arrives, and straight after it groups or ungroups speakers itself.
  • Play, pause and favourites go to the group. Sonos refuses transport commands sent to a member, so the plugin sends them to the coordinator: pause any speaker's card in a group, and the group pauses.
  • A member shows the group's music: its title, artist, artwork and progress are the coordinator's. Its volume, mute and sound stay its own.
  • group_volume is the whole group's volume, on every speaker in a group of two or more.

{"action": "group", "members": [...]}, sent to the speaker that should lead, makes its group exactly the speakers listed: they join, everyone else in its group leaves, and if the leader was itself in another group it leaves that one first. An empty list plays it on its own. Each speaker gets four seconds to answer; one that doesn't is skipped and the rest still go ahead.

SDK adoption​

hc-sonos is built on the official Rust plugin SDK (plugin-sdk-rs) and supports the full management protocol: heartbeat monitoring, remote configuration, and dynamic log level.

What the plugin provides​

Each discovered speaker is registered automatically as a media_player device. You do not need to pre-create the device in HomeCore.

For each player, HomeCore can see:

  • playback state such as playing and paused
  • volume and mute state, and the group's volume
  • current track title, artist, album and artwork — or the radio station
  • group coordinator and group members
  • available Sonos favorites
  • available Sonos playlists

This means HomeCore rules can target sonos_living_room or sonos_kitchen directly instead of calling a hardcoded speaker URL.

Discovery and automatic registration​

hc-sonos uses SSDP discovery and registers speakers as they are found.

Multicast routing on multi-NIC hosts​

SSDP relies on UDP multicast (group 239.255.255.250:1900). On a host with multiple network interfaces — common in dev setups with separate management and IoT networks — the kernel may route multicast out the wrong interface and discovery silently fails.

If hc-sonos finds zero speakers but the Sonos app on the same network sees them, check your routing table:

# Confirm where multicast is going
ip route get 239.255.255.250

Force the IoT-side interface as the multicast egress (replace enp12s0 and the IoT subnet with yours):

sudo ip route add 239.255.255.250/32 dev enp12s0

For a more permanent fix, configure your network manager (NetworkManager, systemd-networkd, etc.) to set route metrics so the IoT interface wins for multicast destinations.

As a fallback when multicast is genuinely unroutable (firewalled VLAN, container with restricted networking, etc.), [sonos] manual_hosts = ["192.168.10.5", ...] lists speaker IPs the plugin should HTTP-probe directly. The probe path bypasses multicast entirely; the trade-off is that adding a new speaker to the network requires a config edit + plugin restart. An address may carry a port ("192.168.10.5:1400"); 1400 otherwise.

Turning SSDP off​

[sonos] ssdp = false stops the plugin sending anything to the multicast group: it contacts only the manual_hosts. Use it where multicast is no use anyway, or for a test homeCore that must never find — and so never command — the real speakers in the house.

If you have no manual overrides configured, the plugin derives a HomeCore device ID from the room name:

Sonos roomHomeCore device ID
Living Roomsonos_living_room
Kitchensonos_kitchen
Officesonos_office

Manual configuration is still useful when you want to:

  • assign a stable custom HomeCore device ID
  • override the display name
  • assign an area
  • pin a specific speaker UUID to specific metadata

Manual configuration is not required for baseline discovery and use.

Configuration​

# config/config.toml

[homecore]
broker_host = "127.0.0.1"
broker_port = 1883
plugin_id = "plugin.sonos"
password = ""

[sonos]
ssdp = true # false: only the manual_hosts are contacted
discovery_interval_secs = 300
discovery_timeout_secs = 5
manual_hosts = [] # "192.168.1.50" or "192.168.1.50:1400"

[api]
enabled = true
host = "0.0.0.0"
port = 5005
callback_host = "192.168.1.10"

Optional speaker overrides​

If you want a speaker to use a specific HomeCore ID, set it explicitly by Sonos UUID:

[[devices]]
uuid = "RINCON_347E5C3D12E401400"
hc_id = "sonos_main_living_room"
name = "Living Room"
area = "living_room"

Without an override, the plugin will still discover and register the speaker automatically.

HomeCore device behavior​

Once registered, a Sonos player appears like any other HomeCore device:

curl -s "http://localhost:8080/api/v1/devices?device_type=media_player" \
-H "Authorization: Bearer $TOKEN" | jq

You can also inspect one player directly:

curl -s http://localhost:8080/api/v1/devices/sonos_living_room \
-H "Authorization: Bearer $TOKEN" | jq

Typical state attributes include:

AttributeMeaning
stateplaying, paused, stopped or buffering
volumeCurrent volume 0-100
mutedMute state
media_titleCurrent track title
media_artistCurrent artist
media_albumCurrent album
media_image_urlArtwork — the track's, or the station's logo
media_durationTrack duration in seconds
media_positionCurrent playback position in seconds
media_kindradio for a live stream, track for anything else, null when nothing is queued
group_coordinatorHomeCore device ID of the coordinator
group_membersArray of grouped player IDs
group_volumeThe group's volume 0-100, while it is in a group
available_favoritesArray of Sonos favorite names
available_playlistsArray of Sonos playlist names

Controlling Sonos through HomeCore​

For normal automation, use the HomeCore device command path:

# Play
curl -s -X PATCH http://localhost:8080/api/v1/devices/sonos_living_room/state \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"action": "play"}'

# Pause
curl -s -X PATCH http://localhost:8080/api/v1/devices/sonos_living_room/state \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"action": "pause"}'

# Set volume (a write to the writable `volume` attribute, 0–100)
curl -s -X PATCH http://localhost:8080/api/v1/devices/sonos_living_room/state \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"volume": 30}'

Named content commands​

The Sonos plugin also exposes named content commands through HomeCore. This is the important part for rules.

# Play a Sonos favorite by name
curl -s -X PATCH http://localhost:8080/api/v1/devices/sonos_living_room/state \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"action": "play_favorite", "favorite": "Dinner Jazz"}'

# Play a Sonos playlist by name
curl -s -X PATCH http://localhost:8080/api/v1/devices/sonos_living_room/state \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"action": "play_playlist", "playlist": "Dinner"}'

# Generic media command form
curl -s -X PATCH http://localhost:8080/api/v1/devices/sonos_living_room/state \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"action": "play_media", "media_type": "favorite", "name": "Dinner Jazz"}'

The plugin resolves the named favorite or playlist internally. Your automation does not need to know the transport URI.

Rule example​

id       = ""
name = "Dinner music"
enabled = true
priority = 10

[trigger]
type = "device_state_changed"
device = "dining_room.music_switch"
attribute = "on"
to = true

[[actions]]
type = "set_device_state"
device = "living_room.sonos"
state = { action = "play_favorite", favorite = "Dinner Jazz" }

This is preferable to an HTTP CallService action because:

  • the rule references a stable HomeCore canonical device name
  • the plugin remains free to rediscover speakers and change IPs
  • Sonos favorites and playlists stay named content, not embedded transport details

Plugin actions​

hc-sonos declares four capability actions, exposed in the homeCore admin UI and surfaced by hc-mcp's list_plugin_actions.

group_speakers (sync, user)​

{"leader": "sonos_office_1", "members": ["sonos_office_2", "sonos_bathroom"]} — the group device action, answered speaker by speaker:

{"status": "partial", "leader": "sonos_office_1",
"results": [{"device_id": "sonos_office_2", "name": "Office-2", "result": "joined"},
{"device_id": "sonos_living_room", "name": "Living Room", "result": "no_answer"}]}

Results are joined, left, already, no_answer or failed.

status (sync, read-only)​

How many speakers are known, when discovery last looked (last_sweep), and whether it uses SSDP.

discover_speakers (streaming, user)​

Re-runs SSDP discovery and emits each found speaker as a stream item as it's discovered. Streaming so the UI updates progressively rather than waiting for the whole discovery window to close.

Each item_add event carries {uuid, room_name, host_port, status}, keyed on uuid. Found speakers are also forwarded into the bridge's discovery channel — the registration / state-publish path runs exactly as if the speaker had been picked up by the periodic discovery cycle.

Manifest: concurrency: single, requires_role: user, timeout_ms: 60_000, item_key: "uuid". Cancelable.

Use this from the admin UI when a speaker isn't appearing — the live item list tells you whether it was found at all (network / multicast issue) versus discovered but failing some downstream step.

rediscover_speakers (sync, user)​

Fire-and-forget kick that runs the same discovery scan with no event stream. Returns once the scan completes with a brief summary ({count, elapsed_ms}). Use this from rules or scripts where you want a quick "force a discovery now" without the streaming UX.

Both actions reuse the same discovery::run_once core — they're two front doors on the same path.

Standalone HTTP API​

The plugin's own HTTP API remains available and supported. This is useful for:

  • standalone operation outside HomeCore
  • manual testing and debugging
  • advanced Sonos-specific workflows not yet modeled as HomeCore device commands

Examples:

# Direct plugin endpoint
curl http://localhost:5005/Living%20Room/play

# Browse favorites
curl http://localhost:5005/favorites | jq

# Browse playlists
curl http://localhost:5005/playlists | jq

For end-user automation inside HomeCore, prefer the HomeCore device ID path first and use the plugin HTTP API as the escape hatch.

Troubleshooting​

ProblemWhat to check
Speaker never appears in HomeCoreConfirm the plugin is running and the speaker is visible on the same LAN
Speaker appears but commands do nothingCheck available = true on the device and verify the plugin can reach the speaker
Favorites or playlists are emptyWait for discovery/content refresh, then inspect available_favorites and available_playlists on the device
Rules still use Sonos URLsReplace call_service actions with set_device_state targeting the HomeCore device ID
GENA callbacks failVerify api.callback_host is reachable from the Sonos speakers
A new group takes minutes to showGroup-change events arrive on the same callback address — check api.callback_host
A speaker says "no answer" when groupingIt did not reply within four seconds: off, asleep or unreachable. Try again from Play together