Documentation for controlling and preserving Bose SoundTouch devices
This document describes the implementation of the GET/POST /volume endpoints for volume management in the Bose SoundTouch API client.
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.
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 managementRetrieves 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>
Sets the volume level on the device.
Request Format:
<volume>50</volume>
Response Format:
<?xml version="1.0" encoding="UTF-8" ?>
<status>/volume</status>
// 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 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 100
// 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, _ := 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"
// 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"
# Get current volume information
soundtouch-cli -host 192.168.1.100:8090 -volume
Output:
Current Volume:
Device ID: ABCD1234EFGH
Current Level: 50 (Medium)
Target Level: 50
Muted: false
# Set volume to specific level (0-100)
soundtouch-cli -host 192.168.1.100:8090 -set-volume 25
soundtouch-cli -host 192.168.1.100:8090 -set-volume 0 # Mute
Safety Features:
# Increase volume by amount (1-10, default: 2)
soundtouch-cli -host 192.168.1.100:8090 -inc-volume 3
soundtouch-cli -host 192.168.1.100:8090 -inc-volume # Uses default: 2
# Decrease volume by amount (1-20, default: 2)
soundtouch-cli -host 192.168.1.100:8090 -dec-volume 5
soundtouch-cli -host 192.168.1.100:8090 -dec-volume # Uses default: 2
The implementation includes comprehensive unit tests in pkg/models/volume_test.go:
Run tests:
go test ./pkg/models/volume*
Tested with real SoundTouch devices:
All volume operations successfully tested on both devices.
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.168.1.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")
}
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)
}
}
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
}
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() and DecreaseVolume() methods return the updated volume level for immediate feedback.
Error Handling: All volume operations include comprehensive error handling with descriptive messages.
Volume can be controlled in two ways:
client.SetVolume(50) // Set exact level
client.IncreaseVolume(5) // Increase by exact amount
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.
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.
Potential areas for future development: