Skip to main content

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 sonos to get it — see AV Sources.
  • Designated sources: set a source_label attribute 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_notification plays 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

  1. 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.
  2. Save. The driver discovers the speakers and creates one zone per device automatically, placed under the AV subsystem. zone.address is 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.)
  3. AV zones (av_type = audio) are auto-created for the speakers so they appear in AV routing right away.
  4. To expose a speaker as a selectable source with the rich browse UI, set a source_label attribute on its zone — see Designated sources. (You can also hand-build an AV Source whose Device is the Sonos device and whose Component is sonos; the attribute route automates exactly that.)

Attribute reference

Device

NameRequiredDescription
ipnoSeed IP of any Sonos speaker. Leave blank for SSDP multicast discovery.
status_intervalnoStatus poll interval in ms. Default 3000, min 1000. Backs up the real-time event subscriptions.
volume_incrementnoStep 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:

NameRequiredDescription
source_labelnoDesignates 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:

AttributeTypeMeaning
volumeintCurrent volume, 0-100.
mute_statestringon / off.
play_state / playback_statestringTransport state — PLAYING, PAUSED_PLAYBACK, STOPPED, TRANSITIONING. Both carry the same value.
inputstringEmpty 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_idstring / intThe GEM device feeding the line-in, when the source is another Sonos speaker's line input.
shuffleboolShuffle on/off.
repeat_modestringnone / all / one.
crossfadeboolCrossfade on/off.
route_source / route_mode / route_source_namestringSet by the input routing command — which speaker is feeding this zone and whether it was joined as line_in or current (grouped).
is_group_coordinatorboolTrue when this speaker leads its group.
group_coordinator_uuid / group_coordinator_namestringThe coordinator this speaker follows (empty when it is the coordinator / ungrouped).
group_membersjsonNames of the speakers in this speaker's group.
group_member_countintNumber 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:

  1. Set a source_label attribute on the speaker's zone — the label is the source's name, e.g. Jim's Sonos or Janet's Sonos.
  2. 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 sourceComponent sonos, Device = the child device, av_type audio.

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 input with mode = 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_off with address = [@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 Sonos browses 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:

  1. list_service_accounts lists the services available on the speaker with their auth policy and linked status.
  2. link_music_service starts account linking and returns an authorization URL. Open it, sign in to the service, and approve.
  3. complete_service_link polls once for completion and, when authorized, saves the account tokens onto the speaker.
  4. browse_service and search_service then return the service catalog; play_service_item / queue_service_item play or enqueue an item.
  5. unlink_music_service removes the saved tokens.

The Sonos source view drives this flow for end users; the commands are also available to macros and the AI assistant.

Radio stream fallback

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

NameArgsNotes
get_devicesList discovered speakers with model/UUID/host.
volumeaddress, volumeSet absolute volume 0-100.
volume_up / volume_downaddressStep volume by volume_increment.
mute_on / mute_offaddressMute control.
get_volume / get_mute / get_input / get_stateaddressRefresh a single facet of zone state.
play / pause / stop / next / previousaddressTransport.
zone_offaddressStop and clear the zone.
inputaddress, source_address, modeRoute 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_inaddressSwitch the zone to its line-in input.
switch_to_queueaddressSwitch playback back to the local queue.
join_group / leave_groupaddress (+ zone_id to join)Group speakers.
set_shuffle / set_repeataddress, shuffle / repeatSet play modes.
shuffle_toggleaddressToggle shuffle, preserving repeat.
repeat_toggleaddressCycle repeat none/all/one, preserving shuffle.
set_crossfadeaddress, crossfadeEnable/disable crossfade.
set_sleep_timeraddress, durationSleep timer (HH:MM:SS).
get_queue / clear_queueaddressRead or clear the queue.
play_queue_trackaddress, track_nrJump to a 1-based queue position and play.
get_positionaddressCurrent track position and duration.
browse_contentaddress, object_id, start_index, countBrowse the library with custom parameters.
browse_artists / browse_albums / browse_genres / browse_playlists / browse_favoritesaddressBrowse a library category or Sonos favorites.
find_prefixaddress, container_id, prefixJump to entries starting with a prefix.
refresh_libraryaddressRe-index the music library.
get_search_capabilities / get_sort_capabilitiesaddressReport what the library supports.
play_item / queue_itemaddress, uri (+ next to queue)Play or enqueue a browsed item. Pass the browsed object as args.item for correct metadata.
play_favoriteaddress, item_idPlay a Sonos favorite by id (FV:2/n).
play_notificationaddress, url, volume, timeout, only_when_playingPlay 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_servicesaddressList/refresh the services on the speaker.
get_music_sessionaddress, service_id, usernameReturn the speaker's session id for a music service (low-level SMAPI session lookup).
list_service_accountsaddressServices with auth policy and linked status.
link_music_serviceaddress, service_idBegin account linking; returns the authorization URL.
complete_service_linkaddress, service_idPoll once for link completion; saves tokens.
unlink_music_serviceaddress, service_idRemove saved account tokens.
browse_serviceaddress, service_id, item_id, start_index, countBrowse a service catalog (item_id defaults to root).
search_serviceaddress, service_id, term, categorySearch a service catalog.
play_service_item / queue_service_itemaddress, 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

SymptomCheck
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.