Volume Control Implementation
Volume Control Implementation
This document describes the implementation of the GET/POST /volume endpoints for volume management in the Bose SoundTouch API client.
Overview
The volume control functionality allows getting current volume levels and setting new volume levels on SoundTouch devices. It provides both direct volume setting and incremental adjustments with safety features.
Implementation Files
pkg/models/volume.go- XML model and validation for volume endpointspkg/models/volume_test.go- Comprehensive tests for volume functionalitypkg/client/client.go- Client methods for volume controlcmd/soundtouch-cli/main.go- CLI commands for volume management
API Specification
GET /volume
Retrieves the current volume level and mute status from the device.
Response Format:
<volume deviceID="ABCD1234EFGH">
<targetvolume>50</targetvolume>
<actualvolume>50</actualvolume>
<muteenabled>false</muteenabled>
</volume>POST /volume
Sets the volume level on the device.
Request Format:
<volume>50</volume>Response Format:
<?xml version="1.0" encoding="UTF-8" ?>
<status>/volume</status>Volume Levels
Valid Range
- Minimum: 0 (mute/silent)
- Maximum: 100 (loudest)
- Validation: All values must be within 0-100 range
Volume Categories
- Mute: 0
- Very Quiet: 1-10
- Quiet: 11-25
- Medium: 26-50
- High: 51-75
- Loud: 76-100
Client API
Basic Volume Operations
// Get current volume information
volume, err := client.GetVolume()
if err != nil {
log.Printf("Failed to get volume: %v", err)
}
fmt.Printf("Current volume: %d (%s)\n", volume.GetLevel(), volume.GetVolumeString())
fmt.Printf("Target volume: %d\n", volume.GetTargetLevel())
fmt.Printf("Muted: %v\n", volume.IsMuted())Set Volume
// Set specific volume level (0-100)
err := client.SetVolume(50)
// Set volume with automatic clamping for invalid values
err := client.SetVolumeSafe(150) // Will be clamped to 100Incremental Volume Control
// Increase volume by specified amount
newVolume, err := client.IncreaseVolume(5)
if err != nil {
log.Printf("Failed to increase volume: %v", err)
} else {
fmt.Printf("Volume increased to: %d\n", newVolume.GetLevel())
}
// Decrease volume by specified amount
newVolume, err := client.DecreaseVolume(3)
if err != nil {
log.Printf("Failed to decrease volume: %v", err)
} else {
fmt.Printf("Volume decreased to: %d\n", newVolume.GetLevel())
}Volume Information Methods
volume, _ := client.GetVolume()
// Get current actual volume level
level := volume.GetLevel()
// Get target volume level
targetLevel := volume.GetTargetLevel()
// Check if device is muted
isMuted := volume.IsMuted()
// Check if volume is synchronized (target == actual)
isSync := volume.IsVolumeSync()
// Get formatted volume string
volumeStr := volume.GetVolumeString() // "Muted" or "50"Volume Validation
// Validate volume level
if models.ValidateVolumeLevel(75) {
fmt.Println("Volume level is valid")
}
// Clamp volume to valid range
safeLevel := models.ClampVolumeLevel(150) // Returns 100
// Get descriptive name for volume level
name := models.GetVolumeLevelName(25) // Returns "Quiet"CLI Usage
Get Current Volume
# Get current volume information
soundtouch-cli -host 192.0.2.100:8090 -volumeOutput:
Current Volume:
Device ID: ABCD1234EFGH
Current Level: 50 (Medium)
Target Level: 50
Muted: falseSet Specific Volume Level
# Set volume to specific level (0-100)
soundtouch-cli -host 192.0.2.100:8090 -set-volume 25
soundtouch-cli -host 192.0.2.100:8090 -set-volume 0 # MuteSafety Features:
- Volumes above 30 show a warning and 2-second delay
- Invalid volumes are rejected with error message
Incremental Volume Control
# Increase volume by amount (1-10, default: 2)
soundtouch-cli -host 192.0.2.100:8090 -inc-volume 3
soundtouch-cli -host 192.0.2.100:8090 -inc-volume # Uses default: 2
# Decrease volume by amount (1-20, default: 2)
soundtouch-cli -host 192.0.2.100:8090 -dec-volume 5
soundtouch-cli -host 192.0.2.100:8090 -dec-volume # Uses default: 2CLI Safety Features
- Volume Warnings: High volume settings (>30) show warnings
- Increment Limits: Volume increases limited to 10 per command
- Decrement Limits: Volume decreases limited to 20 per command
- Automatic Clamping: All values automatically clamped to 0-100 range
- Error Handling: Clear error messages for invalid operations
Testing
Unit Tests
The implementation includes comprehensive unit tests in pkg/models/volume_test.go:
- XML marshaling/unmarshaling for requests and responses
- Volume validation and clamping functions
- Volume level categorization
- Helper method functionality
- Constants validation
- Benchmark tests for performance
Run tests:
go test ./pkg/models/volume*Integration Testing
Tested with real SoundTouch devices:
- SoundTouch 10 (192.0.2.10:8090) ✅
- SoundTouch 20 (192.0.2.11:8090) ✅
All volume operations successfully tested on both devices.
Code Examples
Basic Volume Control
package main
import (
"fmt"
"log"
"github.com/gesellix/bose-soundtouch/pkg/client"
"github.com/gesellix/bose-soundtouch/pkg/models"
)
func main() {
// Create client
soundtouchClient := client.NewClientFromHost("192.0.2.100")
// Get current volume
volume, err := soundtouchClient.GetVolume()
if err != nil {
log.Fatalf("Failed to get volume: %v", err)
}
fmt.Printf("Current volume: %d (%s)\n",
volume.GetLevel(),
models.GetVolumeLevelName(volume.GetLevel()))
// Set to comfortable listening level
if err := soundtouchClient.SetVolume(35); err != nil {
log.Fatalf("Failed to set volume: %v", err)
}
fmt.Println("Volume set to comfortable level")
}Volume Monitoring
func monitorVolume(client *client.Client) {
for {
volume, err := client.GetVolume()
if err != nil {
log.Printf("Error getting volume: %v", err)
continue
}
fmt.Printf("Volume: %d", volume.GetLevel())
if volume.IsMuted() {
fmt.Print(" (MUTED)")
}
if !volume.IsVolumeSync() {
fmt.Printf(" -> %d (adjusting)", volume.GetTargetLevel())
}
fmt.Println()
time.Sleep(2 * time.Second)
}
}Safe Volume Management
func safeVolumeControl(client *client.Client, newLevel int) error {
// Get current volume first
currentVolume, err := client.GetVolume()
if err != nil {
return fmt.Errorf("failed to get current volume: %w", err)
}
// Don't allow large jumps in volume
currentLevel := currentVolume.GetLevel()
if abs(newLevel - currentLevel) > 20 {
return fmt.Errorf("volume change too large: %d -> %d", currentLevel, newLevel)
}
// Validate and clamp
if !models.ValidateVolumeLevel(newLevel) {
newLevel = models.ClampVolumeLevel(newLevel)
fmt.Printf("Volume clamped to safe range: %d\n", newLevel)
}
// Set volume
return client.SetVolume(newLevel)
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}Implementation Notes
Target vs Actual Volume: The API returns both target and actual volume levels. During volume changes, these may differ temporarily.
Volume Synchronization: Use
IsVolumeSync()to check if the device has finished adjusting to the target volume.Mute Handling: Mute is a separate boolean field, not just volume level 0.
Safety Features: The CLI implementation includes several safety features to prevent accidental loud volume settings.
Incremental Control: The
IncreaseVolume()andDecreaseVolume()methods return the updated volume level for immediate feedback.Error Handling: All volume operations include comprehensive error handling with descriptive messages.
Relationship to Key Controls
Volume can be controlled in two ways:
Via Volume API (Precise)
client.SetVolume(50) // Set exact level
client.IncreaseVolume(5) // Increase by exact amountVia Key Commands (Step-based)
client.VolumeUp() // Single step up
client.VolumeDown() // Single step down
client.SendKey(models.KeyVolumeUp) // Same as VolumeUp()Note: Key commands now properly send both press and release states as per API documentation.
Known Issues and Workarounds
Stuck Volume Keys: If volume appears to be continuously adjusting, there may be a stuck key press. Send the same key command to ensure proper press+release cycle.
External Volume Control: Some sources (like Spotify apps) may override volume settings. Check the source and consider switching sources if needed.
Volume Jumps: If volume jumps unexpectedly during increment/decrement operations, check for external volume control interference.
Future Enhancements
Potential areas for future development:
- Volume Profiles: Predefined volume profiles (quiet, normal, party)
- Time-based Volume: Automatic volume adjustment based on time of day
- Source-specific Volume: Remember volume levels per audio source
- Volume Limits: Configurable maximum volume limits for safety
- Volume Fade: Gradual volume transitions for smooth experience
- Volume Monitoring: Real-time volume change notifications via WebSocket
Reference
- Official API: Based on Bose SoundTouch Web API documentation
- Test Devices: Validated with SoundTouch 10 and SoundTouch 20
- Standards: Follows existing project patterns and conventions
- Safety: Implements multiple safety features for user protection