Sonos
GEM driver sonos auto-discovers and controls Sonos speakers (S2 generation) on the LAN over UPnP, using the @svrooij/sonos SSDP library. Each discovered speaker becomes a GEM zone and an AV zone, so the speakers participate in AV routing alongside the rest of the system. The driver covers playback, volume, mute, grouping, queue management, line-in pass-through, music-library browsing and search, and music-service (SMAPI) account linking.
What it does
- One driver instance discovers every Sonos device on the subnet and creates one zone per speaker automatically (
zone.address= the Sonos device UUID, e.g.RINCON_xxxxxx). - Auto-creates an AV zone (
av_type=audio) per speaker so it shows up in AV routing. - Live state via real-time UPnP event subscriptions (volume, mute, transport), with a polling fallback (
status_interval, default 3000 ms) to backfill state when an event is dropped. - Per-zone volume, mute, transport (play/pause/stop/next/previous), shuffle, repeat, crossfade, and sleep timer.
- Speaker grouping (join/leave) using the coordinator looked up from the Sonos zone-group topology.
- Music-library and Sonos-favorites browsing, prefix search, and queue management.
- Music-service (SMAPI) account linking and catalog browse/search — Pandora, SiriusXM, third-party radio, etc. — once the service is authorized on a speaker.
- A dedicated Sonos source view for end users (browse → play/queue, now-playing, presets/favorites). Set the AV source's Component to
sonosto get it — see AV Sources. - Designated sources: set a
source_labelattribute on a speaker's zone (e.g.Jim's Sonos) and the driver publishes that speaker as a selectable AV source automatically — see Designated sources. - Notification clips:
play_notificationplays a chime or a spoken announcement from a URL over whatever is playing and restores the previous source and volume afterwards — this is what the macro Announce step uses on a Sonos zone.
Prerequisites
- Sonos speakers already set up through the Sonos app (S2 generation).
- The GEM server on the same VLAN/subnet as the speakers — SSDP multicast does not cross routers. If discovery is blocked, a single seed IP is enough to bootstrap.
Setup steps
Open Devices
- Go to System → Devices → Add Device and create a device with driver
sonos. Leave Seed IP blank to use SSDP multicast discovery, or enter the IP of any one Sonos speaker to seed discovery when multicast is blocked. - Save. The driver discovers the speakers and creates one zone per device automatically, placed under the AV subsystem.
zone.addressis populated with the device UUID — do not set it by hand. (The AV subsystem ships with every install; if it has been deleted, recreate it first or no zones can be created.) - AV zones (
av_type= audio) are auto-created for the speakers so they appear in AV routing right away. - To expose a speaker as a selectable source with the rich browse UI, set a
source_labelattribute on its zone — see Designated sources. (You can also hand-build an AV Source whose Device is the Sonos device and whose Component issonos; the attribute route automates exactly that.)
Attribute reference
Device
| Name | Required | Description |
|---|---|---|
ip | no | Seed IP of any Sonos speaker. Leave blank for SSDP multicast discovery. |
status_interval | no | Status poll interval in ms. Default 3000, min 1000. Backs up the real-time event subscriptions. |
volume_increment | no | Step size for volume_up / volume_down when the zone has no override. Default 2. |
Zone
No required zone attributes — address (the device UUID) is filled in during discovery, never by hand.
One optional integrator-set attribute:
| Name | Required | Description |
|---|---|---|
source_label | no | Designates this speaker as a selectable AV source with this label (e.g. Jim's Sonos). See Designated sources. |
The driver writes live speaker state back onto each zone as it changes (from the real-time event subscriptions, backfilled by the status poll). These attribute names are stable, so you can build attribute triggers and bind widgets to them:
| Attribute | Type | Meaning |
|---|---|---|
volume | int | Current volume, 0-100. |
mute_state | string | on / off. |
play_state / playback_state | string | Transport state — PLAYING, PAUSED_PLAYBACK, STOPPED, TRANSITIONING. Both carry the same value. |
input | string | Empty when playing from the local queue; set to the line-in source's UUID when a line input is routed in. |
input_device / input_device_id | string / int | The GEM device feeding the line-in, when the source is another Sonos speaker's line input. |
shuffle | bool | Shuffle on/off. |
repeat_mode | string | none / all / one. |
crossfade | bool | Crossfade on/off. |
route_source / route_mode / route_source_name | string | Set by the input routing command — which speaker is feeding this zone and whether it was joined as line_in or current (grouped). |
is_group_coordinator | bool | True when this speaker leads its group. |
group_coordinator_uuid / group_coordinator_name | string | The coordinator this speaker follows (empty when it is the coordinator / ungrouped). |
group_members | json | Names of the speakers in this speaker's group. |
group_member_count | int | Number of speakers in the group. |
Track/artist/album metadata is not written to the zone — it lives on the AV source's device (see Now-playing metadata). Older builds wrote it to the zone; the driver clears any leftover zone copies on connect.
Designated sources (source_label)
A Sonos speaker is both an output (its zone / AV zone) and an origination point — it plays its own queue and stream. GEM models origination the same way it does for every multi-stream player (compare the Autonomic Mirage's one-device-per-player-instance convention): one device row per origination point, referenced by an AV source. Designation automates that wiring:
- Set a
source_labelattribute on the speaker's zone — the label is the source's name, e.g.Jim's SonosorJanet's Sonos. - Reload the Sonos device (or wait for the next connect). The driver ensures:
- a child device — driver
sonos,address= the speaker's RINCON,controller= the main Sonos device. It opens no connection of its own; commands sent to it forward through the controller, addressed to its speaker. - a global AV source — Component
sonos, Device = the child device,av_typeaudio.
- a child device — driver
Only labeled zones become sources, so the source grid stays curated — outputs-only speakers never clutter it.
Adoption, not duplication. Existing rows are matched by address (device) and device (AV source) and reused, so hand-built setups are adopted by simply setting the attribute to the existing label. The attribute stays authoritative for the label — renaming it renames the device and source on the next connect. Everything else is fill-empty: macros, icons, or a component an integrator set are never overwritten.
Ensure-only. Clearing the attribute deletes nothing — remove the device and AV source rows by hand to un-designate. A row you disabled is skipped, not revived.
Grouping = selecting another room's source
Selecting Jim's Sonos in another room should join that room's speaker to Jim's stream. Wire the AV source's On Macro to a shared join macro — one macro serves every designated source because the AV dispatch fills the argument tokens per room:
- Join (source on-macro): device command
inputwithmode=current,source_address=[@device.address](the designated speaker),address=[@av_zone.address](the selecting room's speaker). - Leave/off (source off-macro or AV zone off-macro): device command
zone_offwithaddress=[@av_zone.address]— the room's speaker leaves the group and stops.
Selecting a designated source in its own room is detected by the driver (a speaker cannot group to itself) and switches the speaker to its own queue standalone instead.
What targets what
- Transport and browse always target the designated source. The source view and the media bar act on the origination speaker — play/pause/next and queue edits change what the whole group hears. A room grouped to
Jim's Sonosbrowses Jim's stream, not its own follower speaker. - Volume and mute stay room-scoped. They dispatch through the AV zone's volume routing, so each room keeps its own level.
- Now-playing follows the source. Track metadata is written to the child device, so every room on the source shows the same correct now-playing.
The Sonos source view
When a user selects a source whose Component is sonos, GEM draws a two-sided panel: the Browse side walks the library, Sonos favorites and any linked music service, and the other side shows now-playing (cover art, transport, volume, shuffle / repeat) or, after a drill-down, the resulting track list.
The button bars follow the side they act on. Browse home and back move the whole browse tree, so they sit under the Browse side; now-playing info, search and clear search act only on the other side and sit under it. On phones, where one side shows at a time, home and back appear on both bars so you can always get out of a drilled-in list.
Now-playing metadata
Now-playing fields (track, artist, album, album_art, repeat_mode, shuffle, plus play_state / playback_state) are written to the AV source's device, not to the zone — this is how the source view and any now-playing widgets pick up metadata. The link is the AV Source's Device field; without it, the source view shows transport controls but no track info. A grouped (follower) speaker originates nothing, so its now-playing metadata is cleared while it follows a coordinator. (Earlier versions wrote these to the zone; the driver clears any leftover zone copies on connect.)
Music services (SMAPI)
Streaming services that the Sonos household already has accounts for can be browsed and played through the speaker's SMAPI bridge:
list_service_accountslists the services available on the speaker with their auth policy and linked status.link_music_servicestarts account linking and returns an authorization URL. Open it, sign in to the service, and approve.complete_service_linkpolls once for completion and, when authorized, saves the account tokens onto the speaker.browse_serviceandsearch_servicethen return the service catalog;play_service_item/queue_service_itemplay or enqueue an item.unlink_music_serviceremoves the saved tokens.
The Sonos source view drives this flow for end users; the commands are also available to macros and the AI assistant.
Some third-party radio services reject the native x-sonosapi-stream URI (UPnP 402) unless the household carries an account serial for them. The driver detects this and falls back to resolving the direct stream URL via SMAPI and playing it as plain internet radio, so the station still plays.
Grouping
join_group (with the target zone_id) makes a speaker join another speaker's group; the driver resolves the coordinator from the zone-group topology, so you pass the GEM zone, not a raw Sonos coordinator name. leave_group removes a speaker from its group.
Commands
| Name | Args | Notes |
|---|---|---|
get_devices | — | List discovered speakers with model/UUID/host. |
volume | address, volume | Set absolute volume 0-100. |
volume_up / volume_down | address | Step volume by volume_increment. |
mute_on / mute_off | address | Mute control. |
get_volume / get_mute / get_input / get_state | address | Refresh a single facet of zone state. |
play / pause / stop / next / previous | address | Transport. |
zone_off | address | Stop and clear the zone. |
input | address, source_address, mode | Route another speaker into this zone. mode is line_in (default — stream that speaker's physical line input) or current (join its current playback). source_address is the source speaker's UUID. When source_address equals the target's own UUID with mode = current, the speaker goes standalone on its own queue instead (a speaker cannot group to itself). |
line_in | address | Switch the zone to its line-in input. |
switch_to_queue | address | Switch playback back to the local queue. |
join_group / leave_group | address (+ zone_id to join) | Group speakers. |
set_shuffle / set_repeat | address, shuffle / repeat | Set play modes. |
shuffle_toggle | address | Toggle shuffle, preserving repeat. |
repeat_toggle | address | Cycle repeat none/all/one, preserving shuffle. |
set_crossfade | address, crossfade | Enable/disable crossfade. |
set_sleep_timer | address, duration | Sleep timer (HH:MM:SS). |
get_queue / clear_queue | address | Read or clear the queue. |
play_queue_track | address, track_nr | Jump to a 1-based queue position and play. |
get_position | address | Current track position and duration. |
browse_content | address, object_id, start_index, count | Browse the library with custom parameters. |
browse_artists / browse_albums / browse_genres / browse_playlists / browse_favorites | address | Browse a library category or Sonos favorites. |
find_prefix | address, container_id, prefix | Jump to entries starting with a prefix. |
refresh_library | address | Re-index the music library. |
get_search_capabilities / get_sort_capabilities | address | Report what the library supports. |
play_item / queue_item | address, uri (+ next to queue) | Play or enqueue a browsed item. Pass the browsed object as args.item for correct metadata. |
play_favorite | address, item_id | Play a Sonos favorite by id (FV:2/n). |
play_notification | address, url, volume, timeout, only_when_playing | Play an audio clip from a URL (a chime, a text-to-speech announcement), then return to what was playing at the previous volume. volume sets the clip level, timeout reverts regardless (seconds, default 30, capped at 600), only_when_playing skips a zone that is silent. |
list_music_services / update_music_services | address | List/refresh the services on the speaker. |
get_music_session | address, service_id, username | Return the speaker's session id for a music service (low-level SMAPI session lookup). |
list_service_accounts | address | Services with auth policy and linked status. |
link_music_service | address, service_id | Begin account linking; returns the authorization URL. |
complete_service_link | address, service_id | Poll once for link completion; saves tokens. |
unlink_music_service | address, service_id | Remove saved account tokens. |
browse_service | address, service_id, item_id, start_index, count | Browse a service catalog (item_id defaults to root). |
search_service | address, service_id, term, category | Search a service catalog. |
play_service_item / queue_service_item | address, service_id, item_id (+ next) | Play or enqueue a service item. |
Known limitations
- Discovery requires multicast on the local subnet; firewalls or VLAN isolation hide speakers. Provide a seed IP if discovery fails.
- Zones are addressed by Sonos device UUID, populated during discovery — never enter the address manually.
- Music-service browsing depends on the account already existing in the Sonos household; GEM links the speaker to it, it does not create the streaming account.
Troubleshooting
| Symptom | Check |
|---|---|
| No zones created on connect. | SSDP multicast is being blocked. Enter a Seed IP of any known Sonos speaker on the device row. |
| Group commands fail with "invalid zone". | The target zone is in a different fabric or was removed from the Sonos system; reload the device or re-run discovery. |
| Volume/mute not updating in real time. | The UPnP event subscription dropped; the polling fallback (status_interval) catches up, and a full re-subscribe happens on the next driver reload. |
| Source view shows controls but no track info. | The AV Source has no Device set, or the device is not this Sonos driver — now-playing metadata is written to the source's device. |
A source_label was set but no source appeared. | The ensure runs on connect — reload the Sonos device. If a device row with that speaker's RINCON address exists but is disabled, the driver skips it (disabled rows are never revived); re-enable or delete it. |
| A radio station won't start. | The service may need an account serial; the driver falls back to plain internet radio, but a station with no resolvable stream URL cannot play. |
| An Announce says it played but nothing was heard. | The speaker fetches the clip from the controller itself, with no session. Set the tts_base_url system attribute to an address the speakers can reach when the detected LAN address is wrong, or when the controller only answers over https with a self-signed certificate. A speaker whose firmware has no notification support answers notifications are not supported on this speaker instead. |