Reference

sonoscli – Design & Specification

sonoscli – Design & Specification

This document describes the overall architecture, command surface, and key implementation details of sonoscli.

#Goals

  • Discover all speakers reliably and present room names consistent with the Sonos app.
  • Provide fast, scriptable playback control from the terminal.
  • Be coordinator-aware so commands behave like the Sonos controller apps.
  • Support Spotify enqueue/play without requiring Spotify credentials (using Sonos-linked Spotify).
  • Support Sonos-side music-service search (SMAPI) when services are linked in the Sonos app.
  • Optionally support Spotify search via Spotify Web API (requires credentials).
  • Keep the implementation small, modern Go, and easy to extend.

Non-goals (for now):

  • Full music-service browsing trees (Sonos SMAPI catalog browsing is large/complex and service-dependent).
  • Full credential management (keychain/encryption, profiles) beyond the current local token store.

#High-level Architecture

cmd/sonos/                 # main entrypoint
internal/cli/              # Cobra commands and output formatting
internal/sonos/            # Sonos UPnP/SOAP, SSDP discovery, topology parsing
internal/spotify/          # Spotify Web API (client credentials) search helper
docs/spec.md               # this document

#Data flow

  • Discovery
  • Primary: SSDP M-SEARCH → find any Sonos responder → query topology (ZoneGroupTopology.GetZoneGroupState) for full room list.
  • Fallback: local subnet scan for TCP 1400 + device_description.xml → then topology query.
  • Output is based on topology members, which match the Sonos app’s room list.
  • Control
  • Commands resolve to a group coordinator when required (transport controls must go to the coordinator).
  • Commands call UPnP SOAP actions on port 1400 using a minimal SOAP client.

#Sonos Protocols Used

#SSDP (discovery)

  • Multicast: 239.255.255.250:1900
  • Query: M-SEARCH for urn:schemas-upnp-org:device:ZonePlayer:1
  • Result: device LOCATION pointing at http://<ip>:1400/xml/device_description.xml

SSDP can be unreliable on some networks (multicast blocked, flaky Wi‑Fi), so we do not depend on it for the final device list.

#UPnP SOAP (control and topology)

All calls are HTTP POST SOAP requests to http://<speaker-ip>:1400/.../Control.

Key services/actions:

  • ZoneGroupTopology:
  • GetZoneGroupState → returns a ZoneGroupState XML payload which describes groups and members.
  • AVTransport:
  • Play, Pause, Stop, Next, Previous
  • SetAVTransportURI (used for grouping join, and queue management)
  • AddURIToQueue (enqueue Spotify items)
  • BecomeCoordinatorOfStandaloneGroup (ungroup)
  • RenderingControl:
  • GetVolume, SetVolume, GetMute, SetMute (plus group volume where supported)

#Command Surface

#Discovery

  • sonos discover – list speakers (room name, IP, UDN)
  • --format json supported.

#Status

  • sonos status --name "<Room>" (or sonos now) – show playback status, current URI, time, volume/mute, and parsed now-playing metadata when available (Title/Artist/Album/AlbumArt).
  • --format json supported.

#Transport

  • sonos play|pause|stop|next|prev --name "<Room>"

#Watch (events)

  • sonos watch --name "<Room>" [--duration 30s]
  • Subscribes to AVTransport and RenderingControl UPnP events and prints changes as they arrive.
  • --format json prints one JSON object per line (stream-friendly).

#Volume / mute

  • sonos volume get|set --name "<Room>" <0-100>
  • sonos mute get|on|off|toggle --name "<Room>"

#Queue

  • sonos queue list --name "<Room>" [--start N] [--limit N] (and --format json|tsv)
  • sonos queue play --name "<Room>" <pos> (1-based)
  • sonos queue remove --name "<Room>" <pos> (1-based)
  • sonos queue clear --name "<Room>"

#Favorites

  • sonos favorites list --name "<Room>" [--start N] [--limit N] (and --format json|tsv)
  • sonos favorites open --name "<Room>" --index <N>
  • sonos favorites open --name "<Room>" "<title>"

#Other sources

  • sonos play-uri --name "<Room>" "<uri>" [--title "..."] [--radio]
  • sonos linein --name "<Room>" [--from "<RoomWithLineIn>"]
  • sonos tv --name "<Room>"

#Scenes

  • sonos scene save <name> – capture grouping + per-room volume/mute
  • sonos scene apply <name> – restore grouping + per-room volume/mute
  • sonos scene list – list saved scenes (--format json|tsv supported)
  • sonos scene delete <name> – delete a scene

#Spotify (no Spotify credentials required)

Spotify must already be linked in the Sonos app.

  • sonos open --name "<Room>" <spotify-uri-or-share-link>
  • Adds to queue and starts playback.
  • sonos enqueue --name "<Room>" <spotify-uri-or-share-link>
  • Adds to queue without playing.

Accepted Spotify refs:

  • spotify:track:<id>, spotify:album:<id>, spotify:playlist:<id>, spotify:show:<id>, spotify:episode:<id>
  • https://open.spotify.com/... share links

Implementation detail: we generate Sonos-compatible DIDL metadata similar to SoCo’s ShareLink logic and try common Spotify Sonos service numbers (2311, 3079).

#Spotify search (requires Spotify Web API credentials)

  • sonos search spotify "<query>" [--type track|album|playlist|show|episode]
  • Requires SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET (or --client-id/--client-secret).
  • Prints spotify:<type>:<id> URIs usable with sonos open / sonos enqueue.
  • --open / --enqueue optionally play/enqueue the selected result (--index).

#Sonos-side music-service search (SMAPI; no Spotify Web API credentials)

Spotify must be linked in the Sonos app. Some services also require a one-time DeviceLink/AppLink flow.

  • sonos smapi services – list available services and auth types.
  • sonos smapi categories --service "Spotify" – list available search categories for a service.
  • sonos auth smapi begin|complete --service "Spotify" – link your account for SMAPI access.
  • sonos smapi search --service "Spotify" --category tracks "<query>" – prints canonical Spotify URIs usable with sonos open / sonos enqueue.
  • sonos smapi browse --service "Spotify" --id root – browse containers via SMAPI getMetadata (drill down by passing returned ids).

#Grouping

  • sonos group status – show all groups, coordinators, and members
  • --format json|tsv supported.
  • sonos group join --name "<Room>" --to "<OtherRoomOrIP>"
  • Sends AVTransport.SetAVTransportURI to the joining speaker with x-rincon:<COORDINATOR_UUID>.
  • Room selection supports fuzzy substring matching; ambiguous matches return suggestions.
  • sonos group unjoin --name "<Room>"
  • Sends AVTransport.BecomeCoordinatorOfStandaloneGroup to the target speaker.
  • sonos group party --to "<RoomOrIP>"
  • Joins all visible speakers to the target group.
  • sonos group dissolve --name "<Room>"
  • Ungroups every member of the target group (leaves members first, coordinator last).
  • sonos group volume get|set --name "<Room>" <0-100>
  • sonos group mute get|on|off|toggle --name "<Room>"

#Coordinator Awareness

For transport-like actions (play/pause/stop/next/prev, queue operations, Spotify enqueue/open), the effective target should be the group coordinator. sonoscli resolves the coordinator via topology and sends commands to that device.

Grouping actions are different:

  • group join: sent to the joining speaker.
  • group unjoin: sent to the target speaker.

#Output Formats

  • Human-readable output is tab/line oriented and intended for terminal use.
  • --format plain|json|tsv controls output formatting where applicable.
  • --json is retained as a deprecated alias for --format json.

#Testing Strategy

  • Pure parsing and transformation logic has unit tests:
  • SSDP parsing
  • SOAP response/error parsing
  • Topology parsing (ZoneGroupState)
  • Spotify ref parsing and Spotify Web API search parsing
  • CLI commands with external dependencies are tested using dependency injection:
  • Spotify search CLI tests stub a searcher and a Sonos enqueuer.
  • Grouping CLI tests stub a topology getter and a grouping client.

Integration tests (real speakers) are intentionally not part of CI.

#Tooling / CI

  • Formatting: gofmt
  • Lint: golangci-lint (configured in .golangci.yml)
  • Tests: go test ./...
  • CI: GitHub Actions runs format check, go vet, tests, and lint.

#Inspiration

SoCo (Python) is a major reference for Sonos protocol patterns and music-service mechanics:

https://github.com/SoCo/SoCo