Bose SoundTouch Toolkit

Documentation for controlling and preserving Bose SoundTouch devices

View the Project on GitHub gesellix/Bose-SoundTouch

SoundTouch API Cookbook

Real-world patterns, recipes, and best practices for the SoundTouch Go client

This cookbook provides practical solutions to common SoundTouch integration challenges. Each recipe includes working code, error handling, and production considerations.

πŸ“‹ Table of Contents


Device Management

Recipe: Robust Device Discovery

Problem: Reliably find all SoundTouch devices on the network with fallback options.

package main

import (
    "context"
    "fmt"
    "log"
    "time"

    "github.com/gesellix/bose-soundtouch/pkg/client"
    "github.com/gesellix/bose-soundtouch/pkg/discovery"
)

func discoverAllDevices(timeout time.Duration) ([]*client.Client, error) {
    discoverer := discovery.NewDiscoverer(discovery.Config{
        Timeout: timeout,
    })

    fmt.Printf("πŸ” Discovering SoundTouch devices (timeout: %v)...\n", timeout)
    
    devices, err := discoverer.DiscoverDevices()
    if err != nil {
        return nil, fmt.Errorf("discovery failed: %w", err)
    }

    if len(devices) == 0 {
        return nil, fmt.Errorf("no devices found on network")
    }

    // Create clients for each device
    var clients []*client.Client
    for _, device := range devices {
        config := client.ClientConfig{
            Host:    device.Host,
            Port:    device.Port,
            Timeout: 5 * time.Second,
        }
        
        c := client.NewClient(config)
        
        // Verify connectivity
        if _, err := c.Ping(); err != nil {
            log.Printf("⚠️  Device at %s:%d not responding: %v", device.Host, device.Port, err)
            continue
        }
        
        clients = append(clients, c)
    }

    fmt.Printf("βœ… Found %d responsive device(s)\n", len(clients))
    return clients, nil
}

// Usage with fallback to known IPs
func getDevicesWithFallback() []*client.Client {
    // Try discovery first
    clients, err := discoverAllDevices(10 * time.Second)
    if err == nil && len(clients) > 0 {
        return clients
    }

    log.Printf("Discovery failed: %v, trying known IPs...", err)

    // Fallback to known IP addresses
    knownIPs := []string{"192.168.1.100", "192.168.1.101", "192.168.1.102"}
    
    var clients []*client.Client
    for _, ip := range knownIPs {
        c := client.NewClientFromHost(ip)
        if _, err := c.Ping(); err == nil {
            clients = append(clients, c)
            log.Printf("βœ… Connected to device at %s", ip)
        }
    }

    return clients
}

Recipe: Device Health Monitoring

Problem: Monitor device connectivity and automatically reconnect.

type DeviceMonitor struct {
    client      *client.Client
    deviceName  string
    healthy     bool
    lastSeen    time.Time
    retryCount  int
    maxRetries  int
    checkInterval time.Duration
    callbacks   DeviceCallbacks
}

type DeviceCallbacks struct {
    OnHealthy   func(deviceName string)
    OnUnhealthy func(deviceName string, err error)
    OnReconnect func(deviceName string)
}

func NewDeviceMonitor(c *client.Client, name string) *DeviceMonitor {
    return &DeviceMonitor{
        client:        c,
        deviceName:    name,
        maxRetries:    3,
        checkInterval: 30 * time.Second,
    }
}

func (dm *DeviceMonitor) Start(ctx context.Context) {
    ticker := time.NewTicker(dm.checkInterval)
    defer ticker.Stop()

    for {
        select {
        case <-ctx.Done():
            return
        case <-ticker.C:
            dm.checkHealth()
        }
    }
}

func (dm *DeviceMonitor) checkHealth() {
    err := dm.client.Ping()
    
    if err == nil {
        if !dm.healthy {
            dm.healthy = true
            dm.retryCount = 0
            dm.lastSeen = time.Now()
            if dm.callbacks.OnHealthy != nil {
                dm.callbacks.OnHealthy(dm.deviceName)
            }
            if dm.callbacks.OnReconnect != nil && dm.retryCount > 0 {
                dm.callbacks.OnReconnect(dm.deviceName)
            }
        }
        dm.lastSeen = time.Now()
        return
    }

    // Device is unhealthy
    dm.healthy = false
    dm.retryCount++
    
    if dm.callbacks.OnUnhealthy != nil {
        dm.callbacks.OnUnhealthy(dm.deviceName, err)
    }

    log.Printf("⚠️  Device %s unhealthy (attempt %d/%d): %v", 
        dm.deviceName, dm.retryCount, dm.maxRetries, err)
}

func (dm *DeviceMonitor) IsHealthy() bool {
    return dm.healthy && time.Since(dm.lastSeen) < 2*dm.checkInterval
}

Playback Control

Recipe: Smart Play/Pause Toggle

Problem: Implement intelligent play/pause that works regardless of current state.

func smartPlayPause(c *client.Client) error {
    // Get current status
    nowPlaying, err := c.GetNowPlaying()
    if err != nil {
        return fmt.Errorf("failed to get playback status: %w", err)
    }

    switch nowPlaying.PlayStatus {
    case "PLAY_STATE":
        fmt.Println("⏸️  Pausing playback...")
        return c.Pause()
    
    case "PAUSE_STATE", "STOP_STATE":
        fmt.Println("▢️  Resuming playback...")
        return c.Play()
    
    case "BUFFERING_STATE":
        fmt.Println("πŸ“‘ Device is buffering, waiting...")
        // Wait a bit and try again
        time.Sleep(2 * time.Second)
        return smartPlayPause(c)
    
    default:
        fmt.Printf("πŸ€” Unknown play status: %s, trying play...\n", nowPlaying.PlayStatus)
        return c.Play()
    }
}

Recipe: Playlist Navigation with Validation

Problem: Navigate playlists safely with boundary checking and retry logic.

type PlaylistNavigator struct {
    client      *client.Client
    maxRetries  int
    retryDelay  time.Duration
}

func NewPlaylistNavigator(c *client.Client) *PlaylistNavigator {
    return &PlaylistNavigator{
        client:     c,
        maxRetries: 3,
        retryDelay: time.Second,
    }
}

func (pn *PlaylistNavigator) NextTrack() error {
    return pn.executeWithRetry(func() error {
        return pn.client.NextTrack()
    }, "next track")
}

func (pn *PlaylistNavigator) PreviousTrack() error {
    return pn.executeWithRetry(func() error {
        return pn.client.PrevTrack()
    }, "previous track")
}

func (pn *PlaylistNavigator) executeWithRetry(operation func() error, operationName string) error {
    var lastErr error
    
    for attempt := 1; attempt <= pn.maxRetries; attempt++ {
        lastErr = operation()
        if lastErr == nil {
            return nil
        }
        
        log.Printf("⚠️  %s attempt %d failed: %v", operationName, attempt, lastErr)
        
        if attempt < pn.maxRetries {
            time.Sleep(pn.retryDelay)
        }
    }
    
    return fmt.Errorf("%s failed after %d attempts: %w", operationName, pn.maxRetries, lastErr)
}

func (pn *PlaylistNavigator) GetTrackInfo() (string, error) {
    nowPlaying, err := pn.client.GetNowPlaying()
    if err != nil {
        return "", err
    }
    
    info := "Unknown Track"
    if nowPlaying.Track != "" && nowPlaying.Artist != "" {
        info = fmt.Sprintf("%s - %s", nowPlaying.Artist, nowPlaying.Track)
    } else if nowPlaying.Track != "" {
        info = nowPlaying.Track
    } else if nowPlaying.StationName != "" {
        info = nowPlaying.StationName
    }
    
    return info, nil
}

Volume Audio

Recipe: Gradual Volume Transitions

Problem: Smoothly transition volume levels without jarring jumps.

type VolumeController struct {
    client        *client.Client
    stepSize      int
    stepDelay     time.Duration
    maxVolume     int
    warningLevel  int
}

func NewVolumeController(c *client.Client) *VolumeController {
    return &VolumeController{
        client:       c,
        stepSize:     2,
        stepDelay:    100 * time.Millisecond,
        maxVolume:    80, // Safety limit
        warningLevel: 70,
    }
}

func (vc *VolumeController) FadeIn(targetVolume int, duration time.Duration) error {
    if targetVolume > vc.maxVolume {
        return fmt.Errorf("target volume %d exceeds safety limit %d", targetVolume, vc.maxVolume)
    }

    current, err := vc.getCurrentVolume()
    if err != nil {
        return err
    }

    return vc.transitionVolume(current, targetVolume, duration)
}

func (vc *VolumeController) FadeOut(duration time.Duration) error {
    current, err := vc.getCurrentVolume()
    if err != nil {
        return err
    }

    return vc.transitionVolume(current, 0, duration)
}

func (vc *VolumeController) transitionVolume(from, to int, duration time.Duration) error {
    if from == to {
        return nil
    }

    steps := abs(to - from)
    if steps == 0 {
        return nil
    }

    stepDuration := duration / time.Duration(steps)
    direction := 1
    if to < from {
        direction = -1
    }

    current := from
    for current != to {
        if direction > 0 && current < to {
            current = min(current+vc.stepSize, to)
        } else if direction < 0 && current > to {
            current = max(current-vc.stepSize, to)
        }

        if err := vc.client.SetVolume(current); err != nil {
            return fmt.Errorf("failed to set volume to %d: %w", current, err)
        }

        if current >= vc.warningLevel {
            fmt.Printf("⚠️  High volume warning: %d\n", current)
        }

        if current != to {
            time.Sleep(stepDuration)
        }
    }

    return nil
}

func (vc *VolumeController) getCurrentVolume() (int, error) {
    volume, err := vc.client.GetVolume()
    if err != nil {
        return 0, err
    }
    return volume.TargetVolume, nil
}

// Helper functions
func abs(x int) int {
    if x < 0 { return -x }
    return x
}

func min(a, b int) int {
    if a < b { return a }
    return b
}

func max(a, b int) int {
    if a > b { return a }
    return b
}

Recipe: Audio Profile Management

Problem: Save and restore audio settings for different scenarios.

type AudioProfile struct {
    Name     string `json:"name"`
    Volume   int    `json:"volume"`
    Bass     int    `json:"bass"`
    Balance  int    `json:"balance"`
    Source   string `json:"source,omitempty"`
}

type ProfileManager struct {
    client   *client.Client
    profiles map[string]AudioProfile
}

func NewProfileManager(c *client.Client) *ProfileManager {
    return &ProfileManager{
        client:   c,
        profiles: make(map[string]AudioProfile),
    }
}

func (pm *ProfileManager) CreateProfile(name string) error {
    // Get current settings
    volume, err := pm.client.GetVolume()
    if err != nil {
        return fmt.Errorf("failed to get volume: %w", err)
    }

    bass, err := pm.client.GetBass()
    if err != nil {
        return fmt.Errorf("failed to get bass: %w", err)
    }

    balance, err := pm.client.GetBalance()
    if err != nil {
        // Balance might not be supported, use default
        balance = &models.Balance{TargetBalance: 0}
    }

    nowPlaying, err := pm.client.GetNowPlaying()
    source := ""
    if err == nil {
        source = nowPlaying.Source
    }

    profile := AudioProfile{
        Name:    name,
        Volume:  volume.TargetVolume,
        Bass:    bass.TargetBass,
        Balance: balance.TargetBalance,
        Source:  source,
    }

    pm.profiles[name] = profile
    fmt.Printf("βœ… Profile '%s' saved: Vol=%d, Bass=%d, Balance=%d\n", 
        name, profile.Volume, profile.Bass, profile.Balance)

    return nil
}

func (pm *ProfileManager) ApplyProfile(name string) error {
    profile, exists := pm.profiles[name]
    if !exists {
        return fmt.Errorf("profile '%s' not found", name)
    }

    fmt.Printf("πŸŽ›οΈ  Applying profile '%s'...\n", name)

    // Apply settings in order
    if err := pm.client.SetVolume(profile.Volume); err != nil {
        return fmt.Errorf("failed to set volume: %w", err)
    }

    if err := pm.client.SetBassSafe(profile.Bass); err != nil {
        log.Printf("Warning: failed to set bass: %v", err)
    }

    if err := pm.client.SetBalanceSafe(profile.Balance); err != nil {
        log.Printf("Warning: failed to set balance: %v", err)
    }

    if profile.Source != "" {
        if err := pm.client.SelectSource(profile.Source, ""); err != nil {
            log.Printf("Warning: failed to select source %s: %v", profile.Source, err)
        }
    }

    fmt.Printf("βœ… Profile '%s' applied successfully\n", name)
    return nil
}

func (pm *ProfileManager) ListProfiles() []string {
    var names []string
    for name := range pm.profiles {
        names = append(names, name)
    }
    return names
}

// Predefined profiles
func (pm *ProfileManager) CreateDefaultProfiles() {
    // Movie profile
    pm.profiles["movie"] = AudioProfile{
        Name:    "movie",
        Volume:  60,
        Bass:    3,
        Balance: 0,
    }

    // Music profile
    pm.profiles["music"] = AudioProfile{
        Name:    "music",
        Volume:  50,
        Bass:    1,
        Balance: 0,
    }

    // Night profile (low volume)
    pm.profiles["night"] = AudioProfile{
        Name:    "night",
        Volume:  20,
        Bass:    -1,
        Balance: 0,
    }

    fmt.Println("βœ… Default profiles created: movie, music, night")
}

Real-time Monitoring

Recipe: Event-Driven State Manager

Problem: Keep track of device state changes and trigger actions based on events.

type StateManager struct {
    client      *client.Client
    wsClient    *client.WebSocketClient
    state       DeviceState
    mutex       sync.RWMutex
    subscribers []StateSubscriber
}

type DeviceState struct {
    Volume      int
    Muted       bool
    PlayStatus  string
    CurrentTrack string
    CurrentArtist string
    Source      string
    LastUpdate  time.Time
}

type StateSubscriber interface {
    OnStateChange(oldState, newState DeviceState)
}

func NewStateManager(c *client.Client) *StateManager {
    sm := &StateManager{
        client: c,
        state:  DeviceState{LastUpdate: time.Now()},
    }

    // Initialize WebSocket client
    sm.wsClient = c.NewWebSocketClient(nil)
    sm.setupEventHandlers()

    return sm
}

func (sm *StateManager) setupEventHandlers() {
    sm.wsClient.OnVolumeUpdated(func(event *models.VolumeUpdatedEvent) {
        sm.mutex.Lock()
        oldState := sm.state
        sm.state.Volume = event.Volume.TargetVolume
        sm.state.Muted = event.Volume.Muted
        sm.state.LastUpdate = time.Now()
        newState := sm.state
        sm.mutex.Unlock()

        sm.notifySubscribers(oldState, newState)
    })

    sm.wsClient.OnNowPlaying(func(event *models.NowPlayingUpdatedEvent) {
        sm.mutex.Lock()
        oldState := sm.state
        sm.state.PlayStatus = event.NowPlaying.PlayStatus
        sm.state.CurrentTrack = event.NowPlaying.Track
        sm.state.CurrentArtist = event.NowPlaying.Artist
        sm.state.Source = event.NowPlaying.Source
        sm.state.LastUpdate = time.Now()
        newState := sm.state
        sm.mutex.Unlock()

        sm.notifySubscribers(oldState, newState)
    })
}

func (sm *StateManager) Start() error {
    return sm.wsClient.Connect()
}

func (sm *StateManager) Stop() error {
    return sm.wsClient.Disconnect()
}

func (sm *StateManager) GetState() DeviceState {
    sm.mutex.RLock()
    defer sm.mutex.RUnlock()
    return sm.state
}

func (sm *StateManager) Subscribe(subscriber StateSubscriber) {
    sm.mutex.Lock()
    defer sm.mutex.Unlock()
    sm.subscribers = append(sm.subscribers, subscriber)
}

func (sm *StateManager) notifySubscribers(oldState, newState DeviceState) {
    sm.mutex.RLock()
    subscribers := make([]StateSubscriber, len(sm.subscribers))
    copy(subscribers, sm.subscribers)
    sm.mutex.RUnlock()

    for _, subscriber := range subscribers {
        go subscriber.OnStateChange(oldState, newState)
    }
}

// Example subscriber: Auto-pause when volume muted
type AutoPauseSubscriber struct {
    client *client.Client
}

func (aps *AutoPauseSubscriber) OnStateChange(oldState, newState DeviceState) {
    if !oldState.Muted && newState.Muted && newState.PlayStatus == "PLAY_STATE" {
        log.Println("πŸ”‡ Volume muted, auto-pausing...")
        aps.client.Pause()
    } else if oldState.Muted && !newState.Muted && newState.PlayStatus == "PAUSE_STATE" {
        log.Println("πŸ”Š Volume unmuted, auto-resuming...")
        aps.client.Play()
    }
}

Multiroom Coordination

Recipe: Party Mode Controller

Problem: Synchronize multiple speakers for whole-house audio.

type PartyModeController struct {
    masterClient *client.Client
    allClients   []*client.Client
    zoneActive   bool
    masterID     string
}

func NewPartyModeController(clients []*client.Client) (*PartyModeController, error) {
    if len(clients) == 0 {
        return nil, fmt.Errorf("no clients provided")
    }

    // Use first client as master
    master := clients[0]
    info, err := master.GetDeviceInfo()
    if err != nil {
        return nil, fmt.Errorf("failed to get master device info: %w", err)
    }

    return &PartyModeController{
        masterClient: master,
        allClients:   clients,
        masterID:     info.DeviceID,
    }, nil
}

func (pmc *PartyModeController) StartPartyMode() error {
    fmt.Println("πŸŽ‰ Starting party mode...")

    // Get all device IDs
    var memberIDs []string
    for i, client := range pmc.allClients[1:] { // Skip master
        info, err := client.GetDeviceInfo()
        if err != nil {
            log.Printf("⚠️  Failed to get info for device %d: %v", i+1, err)
            continue
        }
        memberIDs = append(memberIDs, info.DeviceID)
    }

    if len(memberIDs) == 0 {
        return fmt.Errorf("no member devices available")
    }

    // Create zone
    err := pmc.masterClient.CreateZone(pmc.masterID, memberIDs)
    if err != nil {
        return fmt.Errorf("failed to create zone: %w", err)
    }

    pmc.zoneActive = true
    fmt.Printf("βœ… Party mode active with %d speakers\n", len(memberIDs)+1)

    // Set reasonable volume for all speakers
    return pmc.SetPartyVolume(40)
}

func (pmc *PartyModeController) StopPartyMode() error {
    if !pmc.zoneActive {
        return nil
    }

    fmt.Println("πŸ›‘ Stopping party mode...")
    
    err := pmc.masterClient.DissolveZone()
    if err != nil {
        return fmt.Errorf("failed to dissolve zone: %w", err)
    }

    pmc.zoneActive = false
    fmt.Println("βœ… Party mode stopped, speakers are now independent")
    return nil
}

func (pmc *PartyModeController) SetPartyVolume(level int) error {
    if !pmc.zoneActive {
        return fmt.Errorf("party mode not active")
    }

    // Only master controls volume in a zone
    return pmc.masterClient.SetVolume(level)
}

func (pmc *PartyModeController) PlayPartyPlaylist() error {
    if !pmc.zoneActive {
        return fmt.Errorf("party mode not active")
    }

    // Example: Select Spotify and play
    if err := pmc.masterClient.SelectSpotify(); err != nil {
        return fmt.Errorf("failed to select Spotify: %w", err)
    }

    time.Sleep(time.Second) // Give source change time to process

    return pmc.masterClient.Play()
}

func (pmc *PartyModeController) GetZoneStatus() (string, error) {
    zone, err := pmc.masterClient.GetZone()
    if err != nil {
        return "", err
    }

    if zone.IsStandalone() {
        return "No zone active", nil
    }

    return fmt.Sprintf("Zone active: master=%s, members=%d", 
        zone.Master, len(zone.Members)), nil
}

Error Handling

Recipe: Resilient Operation Wrapper

Problem: Handle network issues, temporary failures, and device state conflicts gracefully.

type ResilientClient struct {
    client       *client.Client
    maxRetries   int
    baseDelay    time.Duration
    maxDelay     time.Duration
    backoffRate  float64
}

func NewResilientClient(c *client.Client) *ResilientClient {
    return &ResilientClient{
        client:      c,
        maxRetries:  3,
        baseDelay:   time.Second,
        maxDelay:    10 * time.Second,
        backoffRate: 2.0,
    }
}

func (rc *ResilientClient) ExecuteWithRetry(operation func() error, operationName string) error {
    var lastErr error
    delay := rc.baseDelay

    for attempt := 1; attempt <= rc.maxRetries; attempt++ {
        lastErr = operation()
        if lastErr == nil {
            if attempt > 1 {
                log.Printf("βœ… %s succeeded on attempt %d", operationName, attempt)
            }
            return nil
        }

        if !rc.isRetryableError(lastErr) {
            return fmt.Errorf("%s failed (non-retryable): %w", operationName, lastErr)
        }

        log.Printf("⚠️  %s attempt %d/%d failed: %v", operationName, attempt, rc.maxRetries, lastErr)

        if attempt < rc.maxRetries {
            log.Printf("πŸ”„ Retrying in %v...", delay)
            time.Sleep(delay)
            delay = time.Duration(float64(delay) * rc.backoffRate)
            if delay > rc.maxDelay {
                delay = rc.maxDelay
            }
        }
    }

    return fmt.Errorf("%s failed after %d attempts: %w", operationName, rc.maxRetries, lastErr)
}

func (rc *ResilientClient) isRetryableError(err error) bool {
    errStr := err.Error()
    
    // Network-related errors
    retryablePatterns := []string{
        "connection refused",
        "timeout",
        "temporary failure",
        "network is unreachable",
        "no such host",
        "connection reset",
        "500", // Server errors
        "502", // Bad gateway
        "503", // Service unavailable
    }

    for _, pattern := range retryablePatterns {
        if strings.Contains(strings.ToLower(errStr), pattern) {
            return true
        }
    }

    return false
}

// Wrapper methods with automatic retry
func (rc *ResilientClient) SetVolume(level int) error {
    return rc.ExecuteWithRetry(func() error {
        return rc.client.SetVolume(level)
    }, "SetVolume")
}

func (rc *ResilientClient) Play() error {
    return rc.ExecuteWithRetry(func() error {
        return rc.client.Play()
    }, "Play")
}

func (rc *ResilientClient) SelectSource(source, account string) error {
    return rc.ExecuteWithRetry(func() error {
        return rc.client.SelectSource(source, account)
    }, "SelectSource")
}

Performance Optimization

Recipe: Connection Pool Manager

Problem: Efficiently manage connections to multiple devices without resource waste.

type ConnectionPool struct {
    clients    map[string]*client.Client
    mutex      sync.RWMutex
    maxIdle    int
    timeout    time.Duration
    lastUsed   map[string]time.Time
    cleanup    *time.Ticker
    stopChan   chan struct{}
}

func NewConnectionPool(maxIdle int, timeout time.Duration) *ConnectionPool {
    cp := &ConnectionPool{
        clients:  make(map[string]*client.Client),
        maxIdle:  maxIdle,
        timeout:  timeout,
        lastUsed: make(map[string]time.Time),
        stopChan: make(chan struct{}),
    }

    // Start cleanup goroutine
    cp.cleanup = time.NewTicker(timeout / 2)
    go cp.cleanupLoop()

    return cp
}

func (cp *ConnectionPool) GetClient(host string, port int) *client.Client {
    key := fmt.Sprintf("%s:%d", host, port)
    
    cp.mutex.RLock()
    if client, exists := cp.clients[key]; exists {
        cp.mutex.RUnlock()
        cp.mutex.Lock()
        cp.lastUsed[key] = time.Now()
        cp.mutex.Unlock()
        return client
    }
    cp.mutex.RUnlock()

    // Create new client
    config := client.ClientConfig{
        Host:    host,
        Port:    port,
        Timeout: 10 * time.Second,
    }
    
    newClient := client.NewClient(config)

    cp.mutex.Lock()
    cp.clients[key] = newClient
    cp.lastUsed[key] = time.Now()
    cp.mutex.Unlock()

    return newClient
}

func (cp *ConnectionPool) cleanupLoop() {
    for {
        select {
        case <-cp.cleanup.C:
            cp.cleanupIdleConnections()
        case <-cp.stopChan:
            return
        }
    }
}

func (cp *ConnectionPool) cleanupIdleConnections() {
    cp.mutex.Lock()
    defer cp.mutex.Unlock()

    now := time.Now()
    var toDelete []string

    for key, lastUsed := range cp.lastUsed {
        if now.Sub(lastUsed) > cp.timeout {
            toDelete = append(toDelete, key)
        }
    }

    for _, key := range toDelete {
        delete(cp.clients, key)
        delete(cp.lastUsed, key)
        log.Printf("πŸ—‘οΈ  Cleaned up idle connection: %s", key)
    }
}

func (cp *ConnectionPool) Close() {
    close(cp.stopChan)
    cp.cleanup.Stop()
    
    cp.mutex.Lock()
    defer cp.mutex.Unlock()
    
    // Clean up all connections
    for key := range cp.clients {
        delete(cp.clients, key)
        delete(cp.lastUsed, key)
    }
}

func (cp *ConnectionPool) Stats() (active int, idle int) {
    cp.mutex.RLock()
    defer cp.mutex.RUnlock()
    
    now := time.Now()
    active = 0
    idle = 0
    
    for _, lastUsed := range cp.lastUsed {
        if now.Sub(lastUsed) < time.Minute {
            active++
        } else {
            idle++
        }
    }
    
    return
}

Production Patterns

Recipe: Configuration Management

Problem: Manage different environments and settings cleanly.

```go type Config struct { DeviceHosts []string json:"device_hosts" DiscoveryTimeout time.Duration json:"discovery_timeout" RequestTimeout time.Duration json:"request_timeout" MaxRetries int json:"max_retries" LogLevel string json:"log_level" Features FeatureFlags json:"features" }

type FeatureFlags struct { AutoDiscovery bool json:"auto_discovery" HealthCheck bool json:"health_check" PartyMode bool json:"party_mode" Volu