Sonos Audio Vertical Architecture
The Sonos audio integration in homectl is the pilot implementation of our Agent Plugin & Go Module Vertical strategy. It bridges high-level natural language agent commands with low-level UPnP SOAP actions and asynchronous GENA push notifications.
Architectural Diagram
Section titled “Architectural Diagram”
Layer-by-Layer Breakdown
Section titled “Layer-by-Layer Breakdown”1. Agent Skill & Token Economics (skills/sonos-soundscape)
Section titled “1. Agent Skill & Token Economics (skills/sonos-soundscape)”Interacting with Sonos devices via raw SOAP produces verbose XML payloads (DIDL-Lite blocks) that consume 1,200–2,000 prompt tokens per query. The sonos-soundscape skill solves this via the Skill with Code pattern:
- Compact JSON Transformation: Delegates XML parsing to
summarize_metadata.py, transforming a multi-kilobyte DIDL-Lite fragment into a 40-byte JSON summary (title,artist,album). This yields a ~94% token reduction per status check. - Acoustic Safety Boundaries: Clamps standard listening levels between 15% and 40%. Mutating calls exceeding 60% require explicit user confirmation; volumes over 80% are blocked.
- Error 701 Queue Recovery: Codifies the recovery workflow: if playback fails with UPnP Error
701 (Transition Not Available), the agent restores the speaker’s local queue (x-rincon-queue:<RINCON>#0) or falls back to ambient Sonos Radio before retrying.
2. Standalone MCP Server (cmd/mcp-sonos)
Section titled “2. Standalone MCP Server (cmd/mcp-sonos)”The MCP server communicates with AI agents over standard I/O using the official @modelcontextprotocol/go-sdk:
| Tool | Mode | Schema Wrapper | Description |
|---|---|---|---|
sonos_list_speakers |
🔒 Read-Only | ListSpeakersResult{Count, Speakers} |
Discovers or lists cached speakers on LAN with generation (S1/S2) and renderer status. |
sonos_get_now_playing |
🔒 Read-Only | NowPlayingResult |
Compact playback state, progress, play mode, shuffle, repeat, crossfade, and track metadata. |
sonos_get_topology |
🔒 Read-Only | TopologyResult{Count, Groups} |
Exposes zone groups and stereo-pair coordinator/follower relationships. |
sonos_list_favorites |
🔒 Read-Only | ListFavoritesResult{Count, Favorites} |
Lists pinned cloud tracks/playlists from Spotify, Apple Music, and Sonos Radio. |
sonos_control |
⚡ Mutating | {"status": "ok", "action": ...} |
Sends playback and grouping actions: play, pause, stop, next, previous, seek_track, seek_time, join, leave, unjoin. |
sonos_set_volume |
⚡ Mutating | {"status": "ok", "volume": ...} |
Adjusts absolute volume (0–100) or applies relative step deltas (+5, -10). |
sonos_play_favorite |
⚡ Mutating | {"status": "ok", "favorite_id": ...} |
Initiates playback of a pinned cloud favorite. |
sonos_play_stream |
⚡ Mutating | {"status": "ok", "url": ...} |
Streams an arbitrary HTTP/HTTPS audio URL (radio, podcast, or TTS). |
sonos_add_to_queue |
⚡ Mutating | {"status": "ok", "track_position": ...} |
Enqueues track or container URI (with optional metadata and as-next flag). |
sonos_get_queue |
🔒 Read-Only | QueueResult{Items, TotalMatches} |
Inspects playback queue tracks with pagination support. |
sonos_queue_edit |
⚡ Mutating | {"status": "ok", "action": ...} |
Edits queue: removes tracks, clears queue, reorders tracks, or configures shuffle/repeat/crossfade. |
sonos_list_services |
🔒 Read-Only | ListServicesResult{Count, Services} |
Lists registered music services and configured default provider. |
All output schemas conform to MCP SEP-2106 and OpenCode validation rules by returning Go records (JSON objects), never bare arrays.
3. Go Engine (modules/sonos)
Section titled “3. Go Engine (modules/sonos)”The core domain logic is isolated in modules/sonos:
- S1/S2 Hardware Classification & Bridge Detection: Legacy devices running S1 firmware (
swGen: "1"or hardwareZP80-120,S5,ZB100,BR100,CR100-200,WD100) are classified intoGeneration: "S1", while modern devices reportGeneration: "S2". Bridges and docks withoutAVTransportendpoints are flagged withIsRenderer: false. The engine and MCP layer reject playback/volume mutations on non-renderers and validate grouping boundaries to prevent joining S1 and S2 speakers into incompatible mixed households. - Coordinator / Follower Resolution: In Sonos stereo pairs or groups, secondary speakers report their transport as
PLAYINGwith anx-rincon:<coord>track URI but empty metadata.modules/sonosautomatically detects follower nodes, resolves the group coordinator viaGetCoordinatorIP(), and routes playback commands to the authoritative master speaker. - Container vs. Item Favorite Playback Protocol: Sonos handles single tracks/radio streams differently from multi-track containers (Spotify/Apple Music playlists, albums, YouTube Music Liked Music).
modules/sonosdetects container favorites viaisContainerFavorite():- Clears current queue (
RemoveAllTracksFromQueue). - Enqueues the container with stored DIDL-Lite metadata (
AddURIToQueue). - Points transport to local queue (
x-rincon-queue:<rincon>#0). - Begins playback via the group coordinator. Single-item streams bypass queueing and use direct
SetAVTransportURI.
- Clears current queue (
- Zero Global State: Does not rely on global
init()loggers or hardcoded file paths. All dependencies (core.Logger,core.Storage,core.Settings) are injected via functional options:client := sonos.NewClient(ip,sonos.WithLogger(logger),sonos.WithStorage(storage),sonos.WithHTTPClient(customHTTP),) - Comprehensive Mock Testing: Tested via
modules/sonos/client_test.gousinghttptest.Server, covering DIDL-Lite parsing, SOAP error handling, and group topology without requiring physical hardware.
4. Physical Protocol Layer
Section titled “4. Physical Protocol Layer”- Outbound SOAP (Port 1400): Executes commands against standard UPnP services:
/MediaRenderer/AVTransport/Control/MediaRenderer/RenderingControl/Control/MediaServer/ContentDirectory/Control/ZoneGroupTopology/Control
- SSDP M-SEARCH (UDP 1900): Sends multicast probes to
239.255.255.250:1900(ST: urn:schemas-upnp-org:device:ZonePlayer:1). Bypasses macOS port 5353 multicast conflicts to deliver sub-500ms discovery across all network nodes. - Inbound GENA Push Events:
GENAListener(events.go) launches a background HTTP listener to receive real-time UPnPNOTIFYrequests containingLastChangeXML event fragments for instant volume and track synchronization.
5. Jibo Social Robot Voice Capability (jiboplugins/sonos)
Section titled “5. Jibo Social Robot Voice Capability (jiboplugins/sonos)”The Sonos module also powers conversational smart home audio on the Jibo Social Robot:
- Shared Discovery Cache: Jibo Cloud reads
~/.config/homectl/sonos_cache.jsonon the LAN to resolve room names with zero runtime mDNS latency during speech turns. - Auto-Resolution of Active Zone: When an owner asks “What song is this?” without specifying a room,
ResolveActiveSpeaker()polls zones to find which speaker is activelyPLAYING. - Queue Seeking by Song/Artist: Allows owners to jump to any track up to 100 positions ahead in the queue by speaking song titles or artist names (“Play Such Great Heights in the office”).
- Embodied Performance: Jibo smiles, sways rhythmically (
cat="dance" filter="music, slowdance"), and renders circular album cover art on its face while announcing tracks.
Prior Art & Acknowledgments
Section titled “Prior Art & Acknowledgments”- S1/S2 Detection Heuristics: The model identification rules,
<swGen>UPnP descriptor inspection, and non-renderer bridge filtering build upon prior art and research fromfrankensonosby @dickelstoneworth.