Bumps the actions-core group with 6 updates: | Package | From | To | | --- | --- | --- | | [actions/checkout](https://github.com/actions/checkout) | `4` | `6` | | [actions/setup-go](https://github.com/actions/setup-go) | `5` | `6` | | [actions/cache](https://github.com/actions/cache) | `4` | `5` | | [actions/upload-artifact](https://github.com/actions/upload-artifact) | `4` | `6` | | [actions/github-script](https://github.com/actions/github-script) | `7` | `8` | | [actions/download-artifact](https://github.com/actions/download-artifact) | `4` | `7` | Updates `actions/checkout` from 4 to 6 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v6) Updates `actions/setup-go` from 5 to 6 - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v5...v6) Updates `actions/cache` from 4 to 5 - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v4...v5) Updates `actions/upload-artifact` from 4 to 6 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v6) Updates `actions/github-script` from 7 to 8 - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/v7...v8) Updates `actions/download-artifact` from 4 to 7 - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v4...v7) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions-core - dependency-name: actions/setup-go dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions-core - dependency-name: actions/cache dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions-core - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions-core - dependency-name: actions/github-script dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions-core - dependency-name: actions/download-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions-core ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bose SoundTouch API Client
A modern Go library and CLI tool for interacting with Bose SoundTouch devices via their Web API.
Features
✅ Implemented (90% Complete - 18/20 endpoints)
- HTTP Client with XML Support: Complete client for SoundTouch Web API
- Device Information: Get detailed device info via
/infoendpoint - Device Name: Get device name via
/nameendpoint - Device Capabilities: Get device capabilities via
/capabilitiesendpoint - Configured Presets: Get preset configurations via
/presetsendpoint - Now Playing Status: Get current playback information via
/now_playingendpoint - Audio Sources: Get available sources via
/sourcesendpoint - Media Controls: Play, pause, stop, track navigation via
/keyendpoint - Volume Management: Get/set volume, incremental control via
/volumeendpoint - Bass Control: Get/set bass levels (-9 to +9 range) via
/bassendpoint - Balance Control: Get/set balance (-50 to +50 range) via
/balanceendpoint - Clock/Time Management: Get/set device time via
/clockTimeand/clockDisplayendpoints - Network Information: Get network details via
/networkInfoendpoint - Real-time WebSocket Events: Live monitoring of device state changes
- UPnP/SSDP Discovery: Automatic device discovery using Universal Plug and Play
- mDNS/Bonjour Discovery: Multicast DNS device discovery support
- Cross-Platform: Works on Windows, macOS, Linux, and WASM
- CLI Tool: Command-line interface for testing and control operations
- Flexible Configuration: Support for .env files and environment variables
- Unified Discovery: Combines UPnP, mDNS, and configured device lists
- Safety Features: Volume warnings, increment limits, error validation
✅ Recently Completed - Zone Management
- Multiroom Support: GET/POST /getZone, /setZone with comprehensive zone operations
- Zone Operations: Create, modify, add members, remove members, dissolve zones
- Zone Status: Query zone membership, master/slave status, device counting
✅ All High Priority Endpoints Complete
All originally planned high-priority endpoints have been implemented. The API client now has comprehensive coverage of all available SoundTouch functionality.
❌ Not Supported by API
- Preset Creation: POST /presets (officially not supported by SoundTouch API)
Recent Additions - WebSocket Events ⚡
NEW: Real-time WebSocket support has been added! Monitor device state changes in real-time with comprehensive event handling.
Key Features:
- 🎵 Live Now Playing Updates: Track changes, playback status, shuffle/repeat
- 🔊 Real-time Volume Changes: Volume levels and mute status
- 🌐 Connection Monitoring: Network connectivity and signal strength
- 📻 Preset Notifications: Preset updates and selections
- 🏠 Multiroom Events: Zone membership changes
- 🎚️ Audio Settings: Bass level adjustments
- 🔄 Auto-Reconnection: Robust connection management
- 🎛️ Event Filtering: Subscribe to specific event types
- 📊 Comprehensive Logging: Debug and monitoring capabilities
CLI Demo:
# Quick start - auto-discover and monitor all events
go run ./cmd/websocket-demo -discover
# Monitor specific device with event filtering
go run ./cmd/websocket-demo -host 192.168.1.10 -filter nowPlaying,volume -verbose
Installation
Using Go
go install github.com/user_account/bose-soundtouch/cmd/soundtouch-cli@latest
From Source
git clone https://github.com/user_account/bose-soundtouch.git
cd bose-soundtouch
make build
Quick Start
Configuration
Create a .env file in your working directory to configure preferred devices:
# Copy the example file
cp .env.example .env
Example .env configuration:
# Discovery Settings
DISCOVERY_TIMEOUT=5s
UPNP_ENABLED=true
MDNS_ENABLED=true
# Preferred Devices (alternative to UPnP)
# Format: name@host:port;name@host:port;...
PREFERRED_DEVICES="Living Room@192.168.1.10;Kitchen@192.168.1.11;192.168.1.12:8091"
# HTTP Client Settings
HTTP_TIMEOUT=10s
USER_AGENT="Bose-SoundTouch-Go-Client/1.0"
CLI Usage
Device Discovery
The library supports multiple discovery methods automatically:
- Configuration: Manually specified devices in
.envfile (fastest, most reliable) - UPnP/SSDP: Universal Plug and Play discovery (widely supported)
- mDNS/Bonjour: Multicast DNS discovery (Apple ecosystem friendly)
See docs/DISCOVERY.md for detailed information.
# Discover SoundTouch devices (combines UPnP, mDNS + configured devices)
soundtouch-cli -discover
# Discover and show detailed info for all devices
soundtouch-cli -discover-all
# Discover with custom timeout
soundtouch-cli -discover -timeout 10s
Real-time WebSocket Events
Monitor device state changes in real-time using WebSocket connections:
# Auto-discover device and monitor all events
go run ./cmd/websocket-demo -discover
# Connect to specific device and monitor all events
go run ./cmd/websocket-demo -host 192.168.1.10
# Monitor only volume and now playing events
go run ./cmd/websocket-demo -host 192.168.1.10 -filter volume,nowPlaying
# Monitor for 5 minutes with verbose output
go run ./cmd/websocket-demo -host 192.168.1.10 -duration 5m -verbose
# Available event types for filtering:
# nowPlaying, volume, connection, preset, zone, bass
Supported WebSocket Events:
- 🎵 Now Playing: Track changes, playback status, shuffle/repeat settings
- 🔊 Volume: Volume level and mute status changes
- 🌐 Connection: Network connectivity and signal strength
- 📻 Preset: Preset configuration updates
- 🏠 Zone: Multiroom zone membership changes
- 🎚️ Bass: Bass equalizer level adjustments
See docs/websocket-events.md for complete WebSocket documentation.
Device Information
# Get device information by IP address
soundtouch-cli -host 192.168.1.10 -info
# With custom port and timeout
soundtouch-cli -host 192.168.1.10 -port 8090 -timeout 15s -info
Now Playing Status
# Get current playback information
soundtouch-cli -host 192.168.1.10 -nowplaying
# Example output:
# Now Playing:
# Device ID: ABCD1234EFGH
# Source: SPOTIFY
# Status: Playing
# Title: In Between Breaths - Paris Unplugged
# Artist: SYML
# Album: Paris Unplugged
# Duration: 2:32 / 3:30
# Shuffle: Off
# Repeat: Off
# Artwork: https://i.scdn.co/image/...
# Capabilities: Skip, Skip Previous, Seek, Favorite
#### Audio Sources
```bash
# Get available audio sources
soundtouch-cli -host 192.168.1.10:8090 -sources
# Example output:
# Audio Sources:
# Device ID: ABCD1234EFGH
# Total Sources: 14
# Ready Sources: 5
#
# Ready Sources:
# • AUX IN [Local, Multiroom]
# • user+spotify@example.com (user) [Remote, Multiroom, Streaming]
# • Alexa [Remote, Multiroom]
# • Tunein [Remote, Multiroom, Streaming]
# • Local_internet_radio [Remote, Multiroom, Streaming]
#
# Categories:
# Spotify: 1 account(s) ready
# AUX Input: Ready
# Streaming Services: 3 ready
Media Controls
# Basic playback controls
soundtouch-cli -host 192.168.1.10:8090 -play
soundtouch-cli -host 192.168.1.10:8090 -pause
soundtouch-cli -host 192.168.1.10:8090 -stop
# Track navigation
soundtouch-cli -host 192.168.1.10:8090 -next
soundtouch-cli -host 192.168.1.10:8090 -prev
# Volume controls (key-based)
soundtouch-cli -host 192.168.1.10:8090 -volume-up
soundtouch-cli -host 192.168.1.10:8090 -volume-down
# Preset selection
soundtouch-cli -host 192.168.1.10:8090 -preset 1
soundtouch-cli -host 192.168.1.10:8090 -preset 6
# Generic key command
soundtouch-cli -host 192.168.1.10:8090 -key STOP
Volume Management
# Get current volume
soundtouch-cli -host 192.168.1.10:8090 -volume
# Example output:
# Current Volume:
# Device ID: ABCD1234EFGH
# Current Level: 50 (Medium)
# Target Level: 50
# Muted: false
# Set specific volume (0-100, shows warning for >30)
soundtouch-cli -host 192.168.1.10:8090 -set-volume 25
soundtouch-cli -host 192.168.1.10:8090 -set-volume 0 # Mute
# Incremental volume control
soundtouch-cli -host 192.168.1.10:8090 -inc-volume 3
soundtouch-cli -host 192.168.1.10:8090 -dec-volume 5
Device Name
# Get device name
soundtouch-cli -host 192.168.1.10 -name
# Example output:
# Device Name: My SoundTouch
Device Capabilities
# Get device capabilities
soundtouch-cli -host 192.168.1.10 -capabilities
# Example output:
# Device Capabilities:
# Device ID: ABCD1234EFGH
#
# System Features:
# • Power Saving Disabled
#
# Audio Features:
# • L/R Stereo
#
# Network Features:
# • Dual Mode
# • WSAPI Proxy
#
# Extended Capabilities:
# • systemtimeout (/systemtimeout)
# • rebroadcastlatencymode (/rebroadcastlatencymode)
Configured Presets
# Get configured presets
soundtouch-cli -host 192.168.1.10 -presets
# Example output:
# Configured Presets:
# Used Slots: 6/6
# Spotify Presets: 6
#
# Preset 1: My Playlist
# Source: SPOTIFY (user@example.com)
# Type: tracklisturl
# Created: 2024-06-23 09:40:36
# Updated: 2024-10-12 15:39:42
# Artwork: https://i.scdn.co/image/...
#
# Most Recent: Preset 4 (Movie Soundtrack)
System Information
# Get device clock time
soundtouch-cli -host 192.168.1.10 -clock-time
# Set device time to current system time
soundtouch-cli -host 192.168.1.10 -set-clock-time now
# Get clock display settings
soundtouch-cli -host 192.168.1.10 -clock-display
# Configure clock display
soundtouch-cli -host 192.168.1.10 -enable-clock
soundtouch-cli -host 192.168.1.10 -clock-format 24
soundtouch-cli -host 192.168.1.10 -clock-brightness 75
# Get network information
soundtouch-cli -host 192.168.1.10 -network-info
Go Library Usage
Basic HTTP Client Usage
package main
import (
"fmt"
"log"
"github.com/user_account/bose-soundtouch/pkg/client"
)
func main() {
// Create client
soundTouchClient := client.NewClientFromHost("192.168.1.10")
// Get device information
deviceInfo, err := soundTouchClient.GetDeviceInfo()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Device: %s (%s)\n", deviceInfo.Name, deviceInfo.Type)
// Get now playing
nowPlaying, err := soundTouchClient.GetNowPlaying()
if err != nil {
log.Fatal(err)
}
if !nowPlaying.IsEmpty() {
fmt.Printf("Now Playing: %s by %s\n", nowPlaying.Track, nowPlaying.Artist)
fmt.Printf("Status: %s\n", nowPlaying.PlayStatus.String())
}
// Volume control
volume, err := soundTouchClient.GetVolume()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Volume: %d\n", volume.ActualVolume)
// Set volume safely (with warnings)
err = soundTouchClient.SetVolumeSafe(25)
if err != nil {
log.Fatal(err)
}
// Media controls
soundTouchClient.Play()
soundTouchClient.Pause()
soundTouchClient.VolumeUp()
// Source selection
soundTouchClient.SelectSpotify()
soundTouchClient.SelectPreset(1)
}
Real-time WebSocket Events
package main
import (
"context"
"log"
"os"
"os/signal"
"syscall"
"github.com/user_account/bose-soundtouch/pkg/client"
"github.com/user_account/bose-soundtouch/pkg/models"
)
func main() {
// Create SoundTouch client
soundTouchClient := client.NewClientFromHost("192.168.1.10")
// Create WebSocket client
wsClient := soundTouchClient.NewWebSocketClient(nil)
// Set up event handlers
wsClient.OnNowPlaying(func(event *models.NowPlayingUpdatedEvent) {
np := &event.NowPlaying
log.Printf("🎵 Now Playing: %s by %s", np.Track, np.Artist)
log.Printf(" Status: %s, Source: %s", np.PlayStatus.String(), np.Source)
if np.HasTimeInfo() {
log.Printf(" Duration: %s", np.FormatDuration())
}
})
wsClient.OnVolumeUpdated(func(event *models.VolumeUpdatedEvent) {
vol := &event.Volume
if vol.IsMuted() {
log.Println("🔇 Volume: Muted")
} else {
log.Printf("🔊 Volume: %d (%s)", vol.ActualVolume,
models.GetVolumeLevelName(vol.ActualVolume))
}
})
wsClient.OnConnectionState(func(event *models.ConnectionStateUpdatedEvent) {
cs := &event.ConnectionState
if cs.IsConnected() {
log.Printf("✅ Connected (Signal: %s)", cs.GetSignalStrength())
} else {
log.Printf("❌ Connection: %s", cs.State)
}
})
wsClient.OnBassUpdated(func(event *models.BassUpdatedEvent) {
bass := &event.Bass
log.Printf("🎚️ Bass: %d", bass.ActualBass)
})
// Handle unknown events for debugging
wsClient.OnUnknownEvent(func(event *models.WebSocketEvent) {
log.Printf("❓ Unknown event types: %v", event.GetEventTypes())
})
// Connect to WebSocket
if err := wsClient.Connect(); err != nil {
log.Fatalf("Failed to connect: %v", err)
}
log.Println("Connected! Listening for events... (Press Ctrl+C to stop)")
// Set up graceful shutdown
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
// Wait for shutdown signal
<-sigChan
log.Println("Shutting down...")
// Disconnect
if err := wsClient.Disconnect(); err != nil {
log.Printf("Error during disconnect: %v", err)
}
log.Println("Disconnected successfully")
}
Advanced WebSocket Configuration
package main
import (
"log"
"time"
"github.com/user_account/bose-soundtouch/pkg/client"
"github.com/user_account/bose-soundtouch/pkg/models"
)
// Custom logger for WebSocket events
type CustomLogger struct{}
func (c *CustomLogger) Printf(format string, v ...interface{}) {
timestamp := time.Now().Format("15:04:05.000")
log.Printf("[%s] [WebSocket] %s", timestamp, fmt.Sprintf(format, v...))
}
func main() {
soundTouchClient := client.NewClientFromHost("192.168.1.10")
// Custom WebSocket configuration
config := &client.WebSocketConfig{
ReconnectInterval: 3 * time.Second, // Reconnect every 3 seconds
MaxReconnectAttempts: 5, // Try 5 times before giving up
PingInterval: 15 * time.Second, // Ping every 15 seconds
PongTimeout: 5 * time.Second, // Wait 5 seconds for pong
ReadBufferSize: 4096, // 4KB read buffer
WriteBufferSize: 4096, // 4KB write buffer
Logger: &CustomLogger{}, // Custom logger
}
wsClient := soundTouchClient.NewWebSocketClient(config)
// Set up handlers for specific events only
wsClient.OnNowPlaying(func(event *models.NowPlayingUpdatedEvent) {
// Handle only now playing events
log.Printf("Track changed: %s", event.NowPlaying.GetDisplayTitle())
})
// Connect with custom config
if err := wsClient.ConnectWithConfig(config); err != nil {
log.Fatal(err)
}
// Keep running
wsClient.Wait()
}
Device Discovery with WebSocket
package main
import (
"context"
"log"
"time"
"github.com/user_account/bose-soundtouch/pkg/client"
"github.com/user_account/bose-soundtouch/pkg/config"
"github.com/user_account/bose-soundtouch/pkg/discovery"
"github.com/user_account/bose-soundtouch/pkg/models"
)
func main() {
// Discover devices
cfg := &config.Config{
DiscoveryTimeout: 10 * time.Second,
CacheEnabled: false,
}
discoveryService := discovery.NewUnifiedDiscoveryService(cfg)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
devices, err := discoveryService.DiscoverDevices(ctx)
if err != nil {
log.Fatal(err)
}
if len(devices) == 0 {
log.Fatal("No devices found")
}
// Connect to first device found
device := devices[0]
log.Printf("Connecting to: %s (%s:%d)", device.Name, device.Host, device.Port)
clientConfig := client.ClientConfig{
Host: device.Host,
Port: device.Port,
}
soundTouchClient := client.NewClient(clientConfig)
// Test basic connectivity
deviceInfo, err := soundTouchClient.GetDeviceInfo()
if err != nil {
log.Fatal(err)
}
log.Printf("Connected to: %s", deviceInfo.Name)
// Set up WebSocket monitoring
wsClient := soundTouchClient.NewWebSocketClient(nil)
wsClient.OnNowPlaying(func(event *models.NowPlayingUpdatedEvent) {
log.Printf("[%s] Now Playing: %s",
deviceInfo.Name, event.NowPlaying.GetDisplayTitle())
})
if err := wsClient.Connect(); err != nil {
log.Fatal(err)
}
log.Println("Monitoring events...")
wsClient.Wait()
}
Project Structure
Bose-SoundTouch/
├── cmd/
│ ├── soundtouch-cli/ # Main CLI tool (fully functional)
│ ├── websocket-demo/ # WebSocket event monitoring demo
│ ├── example-upnp/ # UPnP discovery examples
│ ├── example-mdns/ # mDNS discovery examples
│ └── mdns-scanner/ # Network scanning utility
├── pkg/
│ ├── client/ # HTTP & WebSocket clients
│ │ ├── client.go # Main HTTP API client
│ │ ├── websocket.go # WebSocket event client
│ │ └── *_test.go # Comprehensive tests
│ ├── models/ # Typed XML models
│ │ ├── websocket.go # WebSocket event models
│ │ ├── nowplaying.go # Now playing models
│ │ ├── volume.go # Volume control models
│ │ ├── bass.go # Bass control models
│ │ ├── balance.go # Balance control models
│ │ └── *.go # Other endpoint models
│ ├── discovery/ # Device discovery
│ │ ├── unified.go # Unified discovery service
│ │ ├── upnp.go # UPnP/SSDP discovery
│ │ └── mdns.go # mDNS/Bonjour discovery
│ └── config/ # Configuration management
└── docs/ # Comprehensive documentation
├── websocket-events.md # WebSocket API documentation
├── DISCOVERY.md # Device discovery guide
└── API.md # HTTP API reference
API Coverage Status
| Endpoint | Method | Status | Description |
|---|---|---|---|
/info |
GET | ✅ Complete | Device information and capabilities |
/name |
GET | ✅ Complete | Device name |
/capabilities |
GET | ✅ Complete | Device feature capabilities |
/now_playing |
GET | ✅ Complete | Current playback status |
/sources |
GET | ✅ Complete | Available audio sources |
/sources |
POST | ✅ Complete | Select audio source |
/key |
POST | ✅ Complete | Send key commands (24 commands) |
/volume |
GET/POST | ✅ Complete | Volume control with safety features |
/bass |
GET/POST | ✅ Complete | Bass control (-9 to +9) |
/balance |
GET/POST | ✅ Complete | Balance control (-50 to +50) |
/presets |
GET | ✅ Complete | Preset configurations (read-only) |
/presets |
POST | ❌ Not Supported | Officially not supported by SoundTouch API |
/clockTime |
GET/POST | ✅ Complete | Device time management |
/clockDisplay |
GET/POST | ✅ Complete | Clock display settings |
/networkInfo |
GET | ✅ Complete | Network connectivity information |
| WebSocket | / |
✅ Complete | Real-time event monitoring |
| Discovery | UPnP/mDNS | ✅ Complete | Device discovery services |
/getZone |
GET | ✅ NEW | Multiroom zone information |
/setZone |
POST | ✅ NEW | Zone creation and management |
Zone Management Features ✅ NEW
| Feature | Status | Description |
|---|---|---|
| Zone Information | ✅ Complete | Get current zone configuration |
| Zone Status | ✅ Complete | Check master/slave status for device |
| Zone Members | ✅ Complete | List all devices in zone |
| Create Zone | ✅ Complete | Create multiroom zone with multiple devices |
| Add to Zone | ✅ Complete | Add device to existing zone |
| Remove from Zone | ✅ Complete | Remove device from zone |
| Dissolve Zone | ✅ Complete | Break up zone, make devices standalone |
| Zone Builder | ✅ Complete | Fluent API for zone construction |
| Validation | ✅ Complete | IP validation, duplicate detection |
| Error Handling | ✅ Complete | Comprehensive zone error types |
Testing Coverage
- Unit Tests: 200+ test cases covering all functionality
- Integration Tests: Real device testing scenarios
- Benchmark Tests: Performance validation
- WebSocket Tests: Comprehensive event handling tests
- Zone Management Tests: Complete multiroom functionality tests
- Discovery Tests: Multi-protocol device discovery tests
// Run all tests
go test ./... -v
# Run specific test suites
go test ./pkg/client -v -run TestWebSocket
go test ./pkg/models -v -run TestWebSocket
go test ./pkg/client -v -run TestZone
go test ./pkg/models -v -run TestZone
go test ./pkg/discovery -v
# Run benchmarks
go test ./pkg/client -bench=.
go test ./pkg/models -bench=.
Quick Start Examples
Basic HTTP Client
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/user_account/bose-soundtouch/pkg/client"
"github.com/user_account/bose-soundtouch/pkg/discovery"
)
func main() {
// Option 1: Connect to known device
soundtouchClient := client.NewClientFromHost("192.168.1.10")
deviceInfo, err := soundtouchClient.GetDeviceInfo()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Device: %s (%s)\n", deviceInfo.Name, deviceInfo.Type)
// Option 2: Discover devices automatically (unified: UPnP + mDNS + config)
cfg, _ := config.LoadFromEnv()
discoveryService := discovery.NewUnifiedDiscoveryService(cfg)
ctx := context.Background()
devices, err := discoveryService.DiscoverDevices(ctx)
if err != nil {
log.Fatal(err)
}
for _, device := range devices {
fmt.Printf("Found: %s at %s:%d\n", device.Name, device.Host, device.Port)
}
// Get current playback status
nowPlaying, err := soundtouchClient.GetNowPlaying()
if err != nil {
log.Fatal(err)
}
if !nowPlaying.IsEmpty() {
fmt.Printf("Now Playing: %s by %s\n",
nowPlaying.GetDisplayTitle(),
nowPlaying.GetDisplayArtist())
}
// Get available audio sources
sources, err := soundtouchClient.GetSources()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Ready Sources: %d/%d\n",
sources.GetReadySourceCount(),
sources.GetSourceCount())
if sources.HasSpotify() {
fmt.Println("Spotify is available")
}
// Get device name
name, err := soundtouchClient.GetName()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Device: %s\n", name.GetName())
// Get device capabilities
capabilities, err := soundtouchClient.GetCapabilities()
if err != nil {
log.Fatal(err)
}
fmt.Printf("L/R Stereo Support: %v\n", capabilities.HasLRStereoCapability())
// Get presets
presets, err := soundtouchClient.GetPresets()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Presets Used: %d/6\n", len(presets.GetUsedPresetSlots()))
// Media controls
fmt.Println("Playing music...")
if err := soundtouchClient.Play(); err != nil {
log.Printf("Failed to play: %v", err)
}
// Volume control
volume, err := soundtouchClient.GetVolume()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Current Volume: %d (%s)\n",
volume.GetLevel(),
volume.GetVolumeString())
// Set comfortable volume
if err := soundtouchClient.SetVolume(25); err != nil {
log.Printf("Failed to set volume: %v", err)
}
}
Project Structure
├── cmd/
│ └── soundtouch-cli/ # CLI application
├── pkg/
│ ├── client/ # HTTP client with XML support
│ ├── discovery/ # Device discovery (UPnP/SSDP + mDNS/Bonjour)
│ └── models/ # XML data models
├── docs/ # Documentation
└── build/ # Build artifacts
Development
Prerequisites
- Go 1.25.5 or later
- Make (optional, for convenience)
Building
# Build CLI tool
make build
# Build for all platforms
make build-all
# Build and run tests
make check
# Run tests with coverage
make test-coverage
Testing
# Run all tests
make test
# Run specific package tests
go test -v ./pkg/client
go test -v ./pkg/discovery
# Test with real devices
make dev-info HOST=192.168.1.10
# Test device discovery
make dev-discover
# Test mDNS discovery example
make dev-mdns
Development Commands
# Format code
make fmt
# Run linter (requires golangci-lint)
make lint
# Clean build artifacts
make clean
# Show help
make help
API Documentation
The SoundTouch Web API uses HTTP with XML payloads. Key endpoints include:
GET /info- Device information ✅ ImplementedGET /name- Device name ✅ ImplementedGET /capabilities- Device capabilities ✅ ImplementedGET /presets- Configured presets ✅ ImplementedGET /now_playing- Current playback status ✅ ImplementedGET /sources- Available audio sources ✅ ImplementedPOST /key- Send key commands (play, pause, etc.) ✅ ImplementedGET/POST /volume- Volume control ✅ ImplementedGET/POST /bass- Bass control ✅ ImplementedGET/POST /balance- Stereo balance control ✅ ImplementedPOST /select- Source selection ✅ ImplementedGET/POST /clockTime- Device time management ✅ ImplementedGET/POST /clockDisplay- Clock display settings ✅ ImplementedGET /networkInfo- Network information ✅ Implemented- WebSocket
/- Real-time event stream
For complete API documentation, see:
- API Endpoints Overview
- Key Controls Documentation
- Volume Controls Documentation
- Host:Port Parsing Feature
Configuration Options
The application supports configuration through .env files and environment variables:
| Variable | Default | Description |
|---|---|---|
DISCOVERY_TIMEOUT |
5s |
Timeout for device discovery |
UPNP_ENABLED |
true |
Enable/disable UPnP/SSDP discovery |
MDNS_ENABLED |
true |
Enable/disable mDNS/Bonjour discovery |
PREFERRED_DEVICES |
(empty) | Semicolon-separated list of devices |
HTTP_TIMEOUT |
10s |
HTTP client timeout |
CACHE_ENABLED |
true |
Enable device caching |
CACHE_TTL |
30s |
Cache time-to-live |
Device Configuration Format
The PREFERRED_DEVICES environment variable supports multiple formats:
# Host only (uses default port 8090)
PREFERRED_DEVICES="192.168.1.10"
# Host with port
PREFERRED_DEVICES="192.168.1.10:8091"
# Named device
PREFERRED_DEVICES="Living Room@192.168.1.10"
# Multiple devices
PREFERRED_DEVICES="Living Room@192.168.1.10;Kitchen@192.168.1.11:8091"
Supported Devices
Tested with:
- Bose SoundTouch 10
- Bose SoundTouch 20
Should work with all SoundTouch series devices that support the Web API.
Real Device Examples
SoundTouch 10 Response
<info deviceID="ABCD1234EFGH">
<name>My SoundTouch Device</name>
<type>SoundTouch 10</type>
<moduleType>sm2</moduleType>
<variant>rhino</variant>
<components>
<component>
<componentCategory>SCM</componentCategory>
<softwareVersion>27.0.6.46330.5043500</softwareVersion>
</component>
</components>
</info>
SoundTouch 20 Response
<info deviceID="ABCD1234EFGH">
<name>My SoundTouch Device</name>
<type>SoundTouch 20</type>
<moduleType>scm</moduleType>
<variant>spotty</variant>
<components>
<component>
<componentCategory>SCM</componentCategory>
<softwareVersion>27.0.6.46330.5043500</softwareVersion>
</component>
<component>
<componentCategory>Lightswitch</componentCategory>
</component>
</components>
</info>
Architecture
This project follows modern Go patterns:
- Clean Architecture: Separated concerns with pkg structure
- Interface-Based Design: Testable and mockable components
- Cross-Platform: Supports Windows, macOS, Linux, and WASM
- Test-Driven: Comprehensive unit and integration tests
- Real Device Integration: Tested with actual SoundTouch hardware
Contributing
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass:
make check - Submit a pull request
Development Guidelines
- Tests are mandatory: Every feature needs corresponding tests
- KISS principle: Keep implementations simple and readable
- Small iterations: Break large features into testable chunks
- Real device testing: Validate against actual SoundTouch devices
- Cross-platform compatibility: Test on multiple platforms
License
This project is licensed under the MIT License - see the LICENSE file for details.