---
title: "WebSocket Events - Real-time SoundTouch Monitoring"
---
This document describes the WebSocket event functionality for real-time monitoring of Bose SoundTouch devices.

## Overview

The WebSocket client provides real-time event notifications for various device state changes including:

- **Now Playing Updates**: Track changes, playback status, shuffle/repeat settings
- **Volume Changes**: Volume level and mute status changes
- **Connection Status**: Network connectivity and signal strength
- **Preset Updates**: Preset configuration changes (read-only - creation not supported by API)
- **Multiroom Zone Changes**: Zone membership and master device changes
- **Bass Level Changes**: Bass equalizer adjustments
- **Clock/Display Updates**: Clock time and display setting changes
- **Device Name Changes**: Device name updates
- **Error States**: Device error notifications

## Quick Start

### Basic Usage

```go
package main

import (
    "log"
    "time"
    
    "github.com/gesellix/bose-soundtouch/pkg/client"
    "github.com/gesellix/bose-soundtouch/pkg/models"
)

func main() {
    // Create SoundTouch client
    soundTouchClient := client.NewClientFromHost("192.0.2.10")
    
    // Create WebSocket client with default configuration
    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", np.PlayStatus.String())
    })
    
    wsClient.OnVolumeUpdated(func(event *models.VolumeUpdatedEvent) {
        vol := &event.Volume
        if vol.IsMuted() {
            log.Println("Volume: Muted")
        } else {
            log.Printf("Volume: %d", vol.ActualVolume)
        }
    })
    
    // Connect to WebSocket
    if err := wsClient.Connect(); err != nil {
        log.Fatalf("Failed to connect: %v", err)
    }
    
    // Keep running
    wsClient.Wait()
}
```

### Using the CLI

The recommended way to monitor WebSocket events is through the built-in CLI command:

```bash
# Monitor all events from a specific device
soundtouch-cli --host 192.0.2.10 events subscribe

# Monitor only volume and now playing events
soundtouch-cli --host 192.0.2.10 events subscribe --filter volume,nowPlaying

# Monitor for 5 minutes with verbose output
soundtouch-cli --host 192.0.2.10 events subscribe --duration 5m --verbose

# Monitor zone events without automatic reconnection
soundtouch-cli --host 192.0.2.10 events subscribe --filter zone --no-reconnect
```

### Using the CLI Demo (Alternative)

For development or testing purposes, you can also use the standalone demo:

```bash
# Auto-discover device and monitor all events
go run ./cmd/websocket-demo -discover

# Connect to specific device
go run ./cmd/websocket-demo -host 192.0.2.10

# Monitor only volume changes for 5 minutes
go run ./cmd/websocket-demo -host 192.0.2.10 -filter volume -duration 5m

# Monitor multiple event types with verbose logging
go run ./cmd/websocket-demo -host 192.0.2.10 -filter nowPlaying,volume -verbose
```

## Configuration

### WebSocket Configuration Options

```go
config := &client.WebSocketConfig{
    // Reconnection settings
    ReconnectInterval:    5 * time.Second,  // Time between reconnection attempts
    MaxReconnectAttempts: 0,                // 0 = unlimited attempts
    
    // Keep-alive settings
    PingInterval:         30 * time.Second, // Ping frequency
    PongTimeout:          10 * time.Second, // Pong response timeout
    
    // Buffer sizes
    ReadBufferSize:       2048,             // WebSocket read buffer
    WriteBufferSize:      2048,             // WebSocket write buffer
    
    // Logging
    Logger:               customLogger,      // Custom logger implementation
}

wsClient := soundTouchClient.NewWebSocketClient(config)
```

### Default Configuration

If you pass `nil` to `NewWebSocketClient()`, these defaults are used:

- **ReconnectInterval**: 5 seconds
- **MaxReconnectAttempts**: 0 (unlimited)
- **PingInterval**: 30 seconds
- **PongTimeout**: 10 seconds
- **ReadBufferSize**: 1024 bytes
- **WriteBufferSize**: 1024 bytes
- **Logger**: Default logger using standard `log` package

## Event Types and Handlers

### 1. Now Playing Events

Triggered when playback state, track, or playback settings change.

```go
wsClient.OnNowPlaying(func(event *models.NowPlayingUpdatedEvent) {
    np := &event.NowPlaying
    
    // Basic info
    fmt.Printf("Track: %s\n", np.Track)
    fmt.Printf("Artist: %s\n", np.Artist)
    fmt.Printf("Album: %s\n", np.Album)
    fmt.Printf("Source: %s\n", np.Source)
    
    // Playback status
    fmt.Printf("Status: %s\n", np.PlayStatus.String())
    fmt.Printf("Is Playing: %t\n", np.PlayStatus.IsPlaying())
    
    // Settings
    fmt.Printf("Shuffle: %s\n", np.ShuffleSetting.String())
    fmt.Printf("Repeat: %s\n", np.RepeatSetting.String())
    
    // Time info (if available)
    if np.HasTimeInfo() {
        fmt.Printf("Duration: %s\n", np.FormatDuration())
        fmt.Printf("Position: %s\n", np.FormatPosition())
    }
    
    // Capabilities
    fmt.Printf("Can Skip: %t\n", np.CanSkip())
    fmt.Printf("Can Seek: %t\n", np.IsSeekSupported())
    fmt.Printf("Can Favorite: %t\n", np.CanFavorite())
})
```

### 2. Volume Events

Triggered when volume level or mute status changes.

```go
wsClient.OnVolumeUpdated(func(event *models.VolumeUpdatedEvent) {
    vol := &event.Volume
    
    if vol.IsMuted() {
        fmt.Println("Device is muted")
    } else {
        fmt.Printf("Volume: %d\n", vol.ActualVolume)
        fmt.Printf("Level: %s\n", models.GetVolumeLevelName(vol.ActualVolume))
    }
    
    // Check if volume is still transitioning
    if !vol.IsVolumeSync() {
        fmt.Printf("Target volume: %d\n", vol.TargetVolume)
    }
})
```

### 3. Connection State Events

Triggered when network connectivity changes.

```go
wsClient.OnConnectionState(func(event *models.ConnectionStateUpdatedEvent) {
    cs := &event.ConnectionState
    
    if cs.IsConnected() {
        fmt.Println("Device connected to network")
        fmt.Printf("Signal strength: %s\n", cs.GetSignalStrength())
    } else {
        fmt.Printf("Connection state: %s\n", cs.State)
    }
})
```

### 4. Preset Events

Triggered when presets are updated or selected. Note: Preset creation via API is officially not supported by SoundTouch - presets can only be created through the official app or device controls.

```go
wsClient.OnPresetUpdated(func(event *models.PresetUpdatedEvent) {
    preset := &event.Preset
    
    fmt.Printf("Preset %s updated\n", preset.ID)
    if preset.ContentItem != nil {
        fmt.Printf("Name: %s\n", preset.ContentItem.ItemName)
        fmt.Printf("Source: %s\n", preset.ContentItem.Source)
    }
})
```

### 5. Multiroom Zone Events

Triggered when multiroom configuration changes.

```go
wsClient.OnZoneUpdated(func(event *models.ZoneUpdatedEvent) {
    zone := &event.Zone
    
    fmt.Printf("Zone master: %s\n", zone.Master)
    fmt.Printf("Zone members: %d\n", len(zone.Members))
    
    for i, member := range zone.Members {
        fmt.Printf("  %d. %s (%s)\n", i+1, member.DeviceID, member.IP)
    }
})
```

### 6. Bass Level Events

Triggered when bass equalizer settings change.

```go
wsClient.OnBassUpdated(func(event *models.BassUpdatedEvent) {
    bass := &event.Bass
    
    fmt.Printf("Bass level: %d\n", bass.ActualBass)
    
    if bass.ActualBass > 0 {
        fmt.Println("Bass boosted")
    } else if bass.ActualBass < 0 {
        fmt.Println("Bass reduced")
    } else {
        fmt.Println("Bass neutral")
    }
})
```

### 7. Unknown Events Handler

Handle any events not explicitly supported:

```go
wsClient.OnUnknownEvent(func(event *models.WebSocketEvent) {
    fmt.Printf("Unknown event from device %s\n", event.DeviceID)
    for _, eventType := range event.GetEventTypes() {
        fmt.Printf("  Event type: %s\n", eventType)
    }
})
```

## Connection Management

### Connecting and Disconnecting

```go
// Connect with default configuration
err := wsClient.Connect()

// Connect with custom configuration
config := &client.WebSocketConfig{
    ReconnectInterval: 3 * time.Second,
    PingInterval:      15 * time.Second,
}
err := wsClient.ConnectWithConfig(config)

// Check connection status
if wsClient.IsConnected() {
    fmt.Println("WebSocket connected")
}

// Disconnect
err := wsClient.Disconnect()
```

### Automatic Reconnection

The WebSocket client automatically attempts to reconnect when the connection is lost:

```go
config := &client.WebSocketConfig{
    ReconnectInterval:    5 * time.Second,  // Wait 5 seconds between attempts
    MaxReconnectAttempts: 10,               // Try up to 10 times (0 = unlimited)
}

wsClient := soundTouchClient.NewWebSocketClient(config)
```

### Graceful Shutdown

```go
// Set up signal handling for graceful shutdown
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

go func() {
    <-sigChan
    fmt.Println("Shutting down...")
    wsClient.Disconnect()
}()

// Wait for shutdown
wsClient.Wait()
```

## Error Handling and Logging

### Custom Logger

Implement the `Logger` interface for custom logging:

```go
type CustomLogger struct{}

func (c *CustomLogger) Printf(format string, v ...interface{}) {
    // Custom logging implementation
    log.Printf("[WS] "+format, v...)
}

config := &client.WebSocketConfig{
    Logger: &CustomLogger{},
}
```

### Silent Logging

To disable logging completely:

```go
type SilentLogger struct{}

func (s *SilentLogger) Printf(format string, v ...interface{}) {
    // Do nothing
}

config := &client.WebSocketConfig{
    Logger: &SilentLogger{},
}
```

## Advanced Usage

### Event Filtering

Process only specific event types:

```go
wsClient.OnNowPlaying(func(event *models.NowPlayingUpdatedEvent) {
    // Only handle now playing events
})

// Don't set other handlers - they'll be ignored
```

### Multiple Event Handlers

You can set multiple handlers, but only the last one set will be used:

```go
// First handler - will be replaced
wsClient.OnNowPlaying(func(event *models.NowPlayingUpdatedEvent) {
    fmt.Println("Handler 1")
})

// Second handler - this one will be used
wsClient.OnNowPlaying(func(event *models.NowPlayingUpdatedEvent) {
    fmt.Println("Handler 2")
})
```

### Composite Event Handling

Handle multiple event types in a unified way:

```go
handlers := &models.WebSocketEventHandlers{
    OnNowPlaying: func(event *models.NowPlayingUpdatedEvent) {
        logEvent("NowPlaying", event.DeviceID)
    },
    OnVolumeUpdated: func(event *models.VolumeUpdatedEvent) {
        logEvent("Volume", event.DeviceID)
    },
    OnConnectionState: func(event *models.ConnectionStateUpdatedEvent) {
        logEvent("Connection", event.DeviceID)
    },
}

wsClient.SetHandlers(handlers)
```

## WebSocket Protocol Details

### Connection Endpoint

The WebSocket connects to:
- **Protocol**: `ws://`
- **Port**: `8080` (different from HTTP API port 8090)
- **Path**: `/`

Example: `ws://192.0.2.10:8080/`

### Message Format

Events are received as XML messages in this format:

```xml
<?xml version="1.0" encoding="UTF-8" ?>
<updates deviceID="689E19B8BB8A">
    <nowPlayingUpdated deviceID="689E19B8BB8A">
        <nowPlaying deviceID="689E19B8BB8A" source="SPOTIFY">
            <track>Song Title</track>
            <artist>Artist Name</artist>
            <album>Album Name</album>
            <playStatus>PLAY_STATE</playStatus>
        </nowPlaying>
    </nowPlayingUpdated>
</updates>
```

### Keep-Alive

The client automatically sends WebSocket ping frames to keep the connection alive. The server responds with pong frames.

## Troubleshooting

### Common Issues

1. **Connection Refused**
   - Ensure the device is on the network and reachable
   - Check that WebSocket port 8080 is not blocked by firewall
   - Verify the device supports WebSocket connections

2. **Frequent Disconnections**
   - Check network stability
   - Increase ping interval if network is slow
   - Enable verbose logging to see connection details

3. **Events Not Received**
   - Verify event handlers are set before connecting
   - Check if the device actually generates the expected events
   - Enable unknown event handler to see all incoming events

### Debugging

Enable verbose logging:

```go
config := &client.WebSocketConfig{
    Logger: &VerboseLogger{},
}

type VerboseLogger struct{}

func (v *VerboseLogger) Printf(format string, args ...interface{}) {
    timestamp := time.Now().Format("15:04:05.000")
    fmt.Printf("[%s] [WebSocket] %s\n", timestamp, fmt.Sprintf(format, args...))
}
```

### Testing

Use the CLI demo to test WebSocket functionality:

```bash
# Test with verbose output
go run ./cmd/websocket-demo -host 192.0.2.10 -verbose

# Test reconnection by temporarily disconnecting device
go run ./cmd/websocket-demo -host 192.0.2.10 -verbose -duration 5m
```

## Performance Considerations

- **Buffer Sizes**: Increase buffer sizes for high-frequency events
- **Handler Efficiency**: Keep event handlers lightweight to avoid blocking
- **Memory Usage**: The client maintains minimal state and shouldn't leak memory
- **CPU Usage**: XML parsing adds some CPU overhead but should be minimal

## Security Notes

- WebSocket connections are unencrypted (ws://, not wss://)
- Authentication is not required for WebSocket connections
- Only devices on the same network can connect
- No sensitive data is transmitted over WebSocket

## API Limitations

### Preset Management
The SoundTouch API officially does not support preset creation or modification via WebSocket or HTTP endpoints. Preset events are read-only notifications when presets are updated through:
- Official SoundTouch mobile app
- Physical device controls
- Voice assistants (Alexa integration)

This is an intentional API design decision to maintain user control over personal preset configurations.

## Integration Examples

### Home Automation

```go
wsClient.OnNowPlaying(func(event *models.NowPlayingUpdatedEvent) {
    if event.NowPlaying.PlayStatus.IsPlaying() {
        // Dim lights when music starts playing
        homeAutomation.DimLights()
    }
})

wsClient.OnVolumeUpdated(func(event *models.VolumeUpdatedEvent) {
    if event.Volume.ActualVolume > 80 {
        // Send notification for loud volume
        notification.Send("Volume is very loud!")
    }
})
```

### Music Dashboard

```go
type MusicDashboard struct {
    currentTrack string
    volume      int
    isPlaying   bool
}

dashboard := &MusicDashboard{}

wsClient.OnNowPlaying(func(event *models.NowPlayingUpdatedEvent) {
    dashboard.currentTrack = event.NowPlaying.GetDisplayTitle()
    dashboard.isPlaying = event.NowPlaying.PlayStatus.IsPlaying()
    dashboard.updateUI()
})

wsClient.OnVolumeUpdated(func(event *models.VolumeUpdatedEvent) {
    dashboard.volume = event.Volume.ActualVolume
    dashboard.updateUI()
})
```

## API Reference

See the generated Go documentation for complete API details:

```bash
go doc github.com/gesellix/bose-soundtouch/pkg/client.WebSocketClient
go doc github.com/gesellix/bose-soundtouch/pkg/models.WebSocketEvent
```

## Testing

Run the WebSocket tests:

```bash
# Run unit tests
go test ./pkg/client -v -run TestWebSocket

# Run model tests
go test ./pkg/models -v -run TestWebSocket

# Run benchmarks
go test ./pkg/client -bench=BenchmarkWebSocket
```
