Concepts

Architecture

Architecture

sonoscli is a small Go binary that speaks Sonos's UPnP/SOAP dialect over the LAN. It is built from a handful of packages that you can read in one sitting.

#Package layout

cmd/sonos/                 # main entrypoint (just calls into internal/cli)
internal/cli/              # Cobra commands, flag plumbing, output formatting
internal/sonos/            # SOAP client, SSDP, topology, AVTransport, ContentDirectory, RenderingControl
internal/spotify/          # Optional Spotify Web API client (client credentials only)
internal/scenes/           # Scene save/apply (grouping + per-room volume/mute)
internal/appconfig/        # Local config file (~/.config/sonoscli/config.json)

#Command flow

Every command follows the same path:

  1. Parse flags with Cobra. Global flags: --name, --ip, --format, --debug, --timeout.
  2. Resolve target — when a command needs a speaker:
  • --ip is used directly.
  • Otherwise --name is matched against the cached topology (and discovery is run if the cache is empty).
  1. Resolve coordinator — transport-affecting commands (play, pause, next, queue, etc.) walk the topology and replace the target with its group coordinator. This is why a pause aimed at a satellite still pauses the whole group.
  2. Issue SOAP — a tiny SOAP client posts to http://<coordinator-ip>:1400/MediaRenderer/AVTransport/Control (or whichever service is needed) with the right SOAPAction header.
  3. Format output--format plain|json|tsv runs through one rendering layer so every command prints consistently.

#Topology is the source of truth

Sonos exposes the real grouping state via ZoneGroupTopology.GetZoneGroupState, which returns an XML blob that lists every zone, its coordinator, members, and bonded satellites. sonoscli:

  • Treats topology — not SSDP — as canonical for the room list.
  • Filters bonded satellites and stereo-pair secondaries from default output (use --all to include them).
  • Caches the parsed topology for a short TTL so subsequent commands skip discovery.

See Discovery for the SSDP details.

#SOAP client

internal/sonos ships a minimal SOAP client that:

  • Formats request envelopes for urn:schemas-upnp-org:service:* actions.
  • Sends them with the right SOAPAction header.
  • Parses response envelopes into Go structs per action.
  • Surfaces UPnP fault codes as Go errors.

Only the actions actually needed by the CLI are wired up. Adding a new action is mostly schema plumbing.

#Eventing

sonos watch uses UPnP eventing (GENA): it starts a small HTTP server, sends SUBSCRIBE requests for AVTransport and RenderingControl, and re-renders state changes as they stream in. The callback URL must be reachable from the speaker, which is why your firewall may prompt on first run.

#Output

Three machine modes plus a human mode:

  • plain — the default; tuned for terminals.
  • json — stable shape; suitable for jq and dashboards.
  • tsv — easy cut/awk-able row format.

Errors always go to stderr with a non-zero exit code. --debug adds a structured trace including SOAP requests and responses, redacted where appropriate.

#Configuration

Local defaults live at ~/.config/sonoscli/config.json (or the platform equivalent). The file is small on purpose — sonos config get|set|unset|path is the only supported way to write it.

#Scenes

Scenes are stored as JSON next to the config:

  • Capture: walk the topology, snapshot each visible zone's coordinator, members, volume, and mute.
  • Apply: dissolve current groups, re-create the saved groups, then re-apply per-room volume/mute.

Apply is best-effort and idempotent — running scene apply evening twice is safe.