Files
Bose-SoundTouch/cmd/soundtouch-cli/main.go
T
Tobias Gesellchen 5e55ab22ae feat: Implement complete advanced audio endpoints (/audiodspcontrols, /audioproducttonecontrols, /audioproductlevelcontrols)
Completes the implementation of all official Bose SoundTouch Web API v1.0
endpoints, achieving 100% official API coverage.

## New Features

### DSP Audio Controls (/audiodspcontrols)
- GetAudioDSPControls() - Get current DSP settings and supported audio modes
- SetAudioDSPControls() - Set audio mode and video sync delay
- SetAudioMode() - Set audio mode only (NORMAL, DIALOG, MUSIC, MOVIE, etc.)
- SetVideoSyncAudioDelay() - Set video sync delay only

### Advanced Tone Controls (/audioproducttonecontrols)
- GetAudioProductToneControls() - Get advanced bass/treble settings with ranges
- SetAudioProductToneControls() - Set both bass and treble
- SetAdvancedBass() - Set advanced bass level only
- SetAdvancedTreble() - Set advanced treble level only

### Speaker Level Controls (/audioproductlevelcontrols)
- GetAudioProductLevelControls() - Get front-center and rear-surround levels
- SetAudioProductLevelControls() - Set both speaker levels
- SetFrontCenterSpeakerLevel() - Set front-center speaker level only
- SetRearSurroundSpeakersLevel() - Set rear-surround speakers level only

## Implementation Details

### Models & Validation
- Complete XML marshaling/unmarshaling with proper struct separation
- Comprehensive input validation with device capability checking
- Support for device-specific ranges and step values
- Proper error handling and constraint validation

### CLI Integration
- Full CLI command tree: audio -> {dsp,tone,level} -> {get,set,specific}
- Rich help text with device-specific guidance
- Flexible parameter handling (individual or combined operations)
- Professional usage examples and CLI command demonstrations

### Testing Coverage
- 748+ lines of comprehensive model tests
- 786+ lines of client integration tests
- XML marshaling/unmarshaling validation
- Error handling and edge case coverage
- Network error simulation and validation testing

## Device Compatibility

### Consumer Devices (SoundTouch 10, 20, 30)
-  Basic controls (bass, volume, balance)
-  Advanced audio controls (professional feature)

### Professional/High-end Devices
-  All basic controls
-  DSP audio modes and video sync
-  Advanced bass/treble controls
-  Speaker level controls (surround systems)

## Documentation & Examples

### Updated Coverage Documentation
- README.md: Updated to 100% complete (19/19 endpoints)
- API-Endpoints-Overview.md: Complete coverage analysis
- API-COVERAGE-ANALYSIS.md: Achievement of full API implementation

### Comprehensive Examples
- advanced-audio-controls.go: Complete usage demonstration
- CLI command examples and device compatibility guide
- Error handling and validation examples

## Final API Status

-  **19/19 Official Endpoints Implemented** (100%)
-  **18/19 Functional on Real Devices** (95%)
-  **1 Endpoint Non-functional** (/trackInfo times out on hardware)
- 🔍 **5 Extended Features** (beyond official API v1.0)

This completes the most comprehensive Bose SoundTouch API implementation
available, covering all documented endpoints plus extended functionality.
2026-01-11 00:28:14 +01:00

892 lines
21 KiB
Go

package main
import (
"log"
"os"
"github.com/urfave/cli/v2"
)
// Build-time variables injected via ldflags
var (
version = "dev"
commit = "unknown"
date = "unknown"
)
func main() {
app := &cli.App{
Name: "soundtouch-cli",
Usage: "Command-line interface for controlling Bose SoundTouch devices",
Description: `A comprehensive CLI tool for interacting with Bose SoundTouch devices.
Supports device discovery, playback control, volume/bass/balance adjustment,
source selection, zone management, and more.`,
Version: version,
Authors: []*cli.Author{
{
Name: "SoundTouch CLI Contributors",
Email: "info@example.com",
},
},
Flags: CommonFlags,
Commands: []*cli.Command{
// Version commands
{
Name: "version",
Aliases: []string{"v"},
Usage: "Show detailed version information",
Action: showVersionInfo,
},
// Discovery commands
{
Name: "discover",
Aliases: []string{"d"},
Usage: "Discover SoundTouch devices on the network",
Subcommands: []*cli.Command{
{
Name: "devices",
Usage: "Discover and list SoundTouch devices",
Action: discoverDevices,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "all",
Aliases: []string{"a"},
Usage: "Show detailed information for all devices",
},
},
},
},
},
// Device information commands
{
Name: "info",
Aliases: []string{"i"},
Usage: "Get device information",
Action: getDeviceInfo,
Before: RequireHost,
},
{
Name: "name",
Usage: "Get or set device name",
Before: RequireHost,
Subcommands: []*cli.Command{
{
Name: "get",
Usage: "Get device name",
Action: getDeviceName,
Before: RequireHost,
},
{
Name: "set",
Usage: "Set device name",
Action: setDeviceName,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "value",
Aliases: []string{"n"},
Usage: "New device name",
Required: true,
},
},
Before: RequireHost,
},
},
},
{
Name: "capabilities",
Usage: "Get device capabilities",
Action: getCapabilities,
Before: RequireHost,
},
{
Name: "presets",
Usage: "Get configured presets",
Action: getPresets,
Before: RequireHost,
},
// Playback commands
{
Name: "play",
Aliases: []string{"p"},
Usage: "Playback control commands",
Subcommands: []*cli.Command{
{
Name: "now",
Usage: "Get current playback status",
Action: getNowPlaying,
Before: RequireHost,
},
{
Name: "start",
Usage: "Start playback",
Action: playCommand,
Before: RequireHost,
},
{
Name: "pause",
Usage: "Pause playback",
Action: pauseCommand,
Before: RequireHost,
},
{
Name: "stop",
Usage: "Stop playback",
Action: stopCommand,
Before: RequireHost,
},
{
Name: "next",
Usage: "Next track",
Action: nextCommand,
Before: RequireHost,
},
{
Name: "prev",
Usage: "Previous track",
Action: prevCommand,
Before: RequireHost,
},
},
},
// Preset commands
{
Name: "preset",
Usage: "Select preset by number",
Action: selectPreset,
Flags: []cli.Flag{
&cli.IntFlag{
Name: "preset",
Usage: "Preset number (1-6)",
Required: true,
},
},
Before: RequireHost,
},
// Key commands
{
Name: "key",
Aliases: []string{"k"},
Usage: "Send key commands",
Subcommands: []*cli.Command{
{
Name: "send",
Usage: "Send generic key command",
Action: sendKey,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "key",
Aliases: []string{"k"},
Usage: "Key name (PLAY, PAUSE, STOP, POWER, MUTE, etc.)",
Required: true,
},
},
Before: RequireHost,
},
{
Name: "power",
Usage: "Send POWER key command",
Action: powerCommand,
Before: RequireHost,
},
{
Name: "mute",
Usage: "Send MUTE key command",
Action: muteCommand,
Before: RequireHost,
},
{
Name: "thumbs-up",
Usage: "Send THUMBS_UP key command",
Action: thumbsUpCommand,
Before: RequireHost,
},
{
Name: "thumbs-down",
Usage: "Send THUMBS_DOWN key command",
Action: thumbsDownCommand,
Before: RequireHost,
},
{
Name: "volume-up",
Usage: "Send VOLUME_UP key command",
Action: volumeUpKey,
Before: RequireHost,
},
{
Name: "volume-down",
Usage: "Send VOLUME_DOWN key command",
Action: volumeDownKey,
Before: RequireHost,
},
},
},
// Track info
{
Name: "track",
Usage: "Get track information (WARNING: times out on real devices, use playback 'now' command instead)",
Action: getTrackInfo,
Before: RequireHost,
},
// Volume commands
{
Name: "volume",
Aliases: []string{"vol"},
Usage: "Volume control commands",
Subcommands: []*cli.Command{
{
Name: "get",
Usage: "Get current volume level",
Action: getVolume,
Before: RequireHost,
},
{
Name: "set",
Usage: "Set volume level",
Action: setVolume,
Flags: []cli.Flag{
&cli.IntFlag{
Name: "level",
Aliases: []string{"l"},
Usage: "Volume level (0-100)",
Required: true,
},
},
Before: RequireHost,
},
{
Name: "up",
Usage: "Increase volume",
Action: volumeUp,
Flags: []cli.Flag{
&cli.IntFlag{
Name: "amount",
Aliases: []string{"a"},
Usage: "Amount to increase (1-10)",
Value: 2,
},
},
Before: RequireHost,
},
{
Name: "down",
Usage: "Decrease volume",
Action: volumeDown,
Flags: []cli.Flag{
&cli.IntFlag{
Name: "amount",
Aliases: []string{"a"},
Usage: "Amount to decrease (1-10)",
Value: 2,
},
},
Before: RequireHost,
},
},
},
// Source commands
{
Name: "source",
Aliases: []string{"src"},
Usage: "Audio source commands",
Subcommands: []*cli.Command{
{
Name: "list",
Usage: "List available audio sources",
Action: listSources,
Before: RequireHost,
},
{
Name: "select",
Usage: "Select an audio source",
Action: selectSource,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "source",
Aliases: []string{"s"},
Usage: "Source to select (SPOTIFY, BLUETOOTH, AUX, etc.)",
Required: true,
},
&cli.StringFlag{
Name: "account",
Aliases: []string{"a"},
Usage: "Source account for streaming services (optional)",
},
},
Before: RequireHost,
},
{
Name: "spotify",
Usage: "Select Spotify source",
Action: selectSpotify,
Before: RequireHost,
},
{
Name: "bluetooth",
Usage: "Select Bluetooth source",
Action: selectBluetooth,
Before: RequireHost,
},
{
Name: "aux",
Usage: "Select AUX input source",
Action: selectAux,
Before: RequireHost,
},
},
},
// Bass commands
{
Name: "bass",
Aliases: []string{"b"},
Usage: "Bass control commands",
Subcommands: []*cli.Command{
{
Name: "get",
Usage: "Get current bass level",
Action: getBass,
Before: RequireHost,
},
{
Name: "set",
Usage: "Set bass level",
Action: setBass,
Flags: []cli.Flag{
&cli.IntFlag{
Name: "level",
Aliases: []string{"l"},
Usage: "Bass level (-9 to 9)",
Required: true,
},
},
Before: RequireHost,
},
{
Name: "up",
Usage: "Increase bass",
Action: bassUp,
Flags: []cli.Flag{
&cli.IntFlag{
Name: "amount",
Aliases: []string{"a"},
Usage: "Amount to increase (1-5)",
Value: 1,
},
},
Before: RequireHost,
},
{
Name: "down",
Usage: "Decrease bass",
Action: bassDown,
Flags: []cli.Flag{
&cli.IntFlag{
Name: "amount",
Aliases: []string{"a"},
Usage: "Amount to decrease (1-5)",
Value: 1,
},
},
Before: RequireHost,
},
{
Name: "capabilities",
Usage: "Get bass capabilities",
Action: getBassCapabilities,
Before: RequireHost,
},
},
},
// Balance commands
{
Name: "balance",
Aliases: []string{"bal"},
Usage: "Balance control commands",
Subcommands: []*cli.Command{
{
Name: "get",
Usage: "Get current balance level",
Action: getBalance,
Before: RequireHost,
},
{
Name: "set",
Usage: "Set balance level",
Action: setBalance,
Flags: []cli.Flag{
&cli.IntFlag{
Name: "level",
Aliases: []string{"l"},
Usage: "Balance level (-50 to 50, negative=left, positive=right)",
Required: true,
},
},
Before: RequireHost,
},
{
Name: "left",
Usage: "Shift balance to the left",
Action: balanceLeft,
Flags: []cli.Flag{
&cli.IntFlag{
Name: "amount",
Aliases: []string{"a"},
Usage: "Amount to shift left (1-10, default: 5)",
Value: 5,
},
},
Before: RequireHost,
},
{
Name: "right",
Usage: "Shift balance to the right",
Action: balanceRight,
Flags: []cli.Flag{
&cli.IntFlag{
Name: "amount",
Aliases: []string{"a"},
Usage: "Amount to shift right (1-10, default: 5)",
Value: 5,
},
},
Before: RequireHost,
},
{
Name: "center",
Usage: "Center the balance",
Action: balanceCenter,
Before: RequireHost,
},
},
},
// Clock commands
{
Name: "clock",
Aliases: []string{"time"},
Usage: "Clock and time commands",
Subcommands: []*cli.Command{
{
Name: "get",
Usage: "Get current time",
Action: getClockTime,
Before: RequireHost,
},
{
Name: "set",
Usage: "Set clock time",
Action: setClockTime,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "time",
Aliases: []string{"t"},
Usage: "Time in HH:MM format or 'now' for current time",
Required: true,
},
},
Before: RequireHost,
},
{
Name: "now",
Usage: "Set clock to current system time",
Action: setClockTimeNow,
Before: RequireHost,
},
{
Name: "display",
Usage: "Clock display commands",
Subcommands: []*cli.Command{
{
Name: "get",
Usage: "Get display settings",
Action: getClockDisplay,
Before: RequireHost,
},
{
Name: "enable",
Usage: "Enable clock display",
Action: enableClockDisplay,
Before: RequireHost,
},
{
Name: "disable",
Usage: "Disable clock display",
Action: disableClockDisplay,
Before: RequireHost,
},
{
Name: "brightness",
Usage: "Set display brightness",
Action: setClockDisplayBrightness,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "brightness",
Aliases: []string{"b"},
Usage: "Brightness level (low, medium, high, off)",
Required: true,
},
},
Before: RequireHost,
},
{
Name: "format",
Usage: "Set display format",
Action: setClockDisplayFormat,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "format",
Aliases: []string{"f"},
Usage: "Time format (12 or 24)",
Required: true,
},
},
Before: RequireHost,
},
},
},
},
},
// Network commands
{
Name: "network",
Aliases: []string{"net"},
Usage: "Network information commands",
Subcommands: []*cli.Command{
{
Name: "info",
Usage: "Get network information",
Action: getNetworkInfo,
Before: RequireHost,
},
{
Name: "ping",
Usage: "Ping the device",
Action: pingDevice,
Before: RequireHost,
},
{
Name: "url",
Usage: "Get device base URL",
Action: getDeviceURL,
Before: RequireHost,
},
},
},
// Zone commands
{
Name: "zone",
Aliases: []string{"z"},
Usage: "Multi-room zone management commands",
Subcommands: []*cli.Command{
{
Name: "get",
Usage: "Get current zone configuration",
Action: getZone,
Before: RequireHost,
},
{
Name: "status",
Usage: "Get zone status",
Action: getZoneStatus,
Before: RequireHost,
},
{
Name: "members",
Usage: "List zone members",
Action: getZoneMembers,
Before: RequireHost,
},
{
Name: "create",
Usage: "Create a new zone",
Action: createZone,
Flags: []cli.Flag{
&cli.StringSliceFlag{
Name: "members",
Aliases: []string{"m"},
Usage: "Member IP addresses",
Required: true,
},
},
Before: RequireHost,
},
{
Name: "add",
Usage: "Add device to zone",
Action: addToZone,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "member",
Aliases: []string{"m"},
Usage: "Member IP address to add",
Required: true,
},
},
Before: RequireHost,
},
{
Name: "remove",
Usage: "Remove device from zone",
Action: removeFromZone,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "member",
Aliases: []string{"m"},
Usage: "Member IP address to remove",
Required: true,
},
},
Before: RequireHost,
},
{
Name: "dissolve",
Usage: "Dissolve the current zone",
Action: dissolveZone,
Before: RequireHost,
},
{
Name: "set",
Usage: "Set zone configuration",
Action: setZoneConfig,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "master",
Usage: "Master device IP address",
Required: true,
},
&cli.StringSliceFlag{
Name: "members",
Aliases: []string{"m"},
Usage: "Member IP addresses",
},
},
Before: RequireHost,
},
{
Name: "add-slave",
Usage: "Add slave to zone (official API)",
Action: addZoneSlave,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "master",
Usage: "Master device ID",
Required: true,
},
&cli.StringFlag{
Name: "slave",
Usage: "Slave device ID",
Required: true,
},
&cli.StringFlag{
Name: "slave-ip",
Usage: "Slave device IP address (optional)",
},
},
Before: RequireHost,
},
{
Name: "remove-slave",
Usage: "Remove slave from zone (official API)",
Action: removeZoneSlave,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "master",
Usage: "Master device ID",
Required: true,
},
&cli.StringFlag{
Name: "slave",
Usage: "Slave device ID",
Required: true,
},
&cli.StringFlag{
Name: "slave-ip",
Usage: "Slave device IP address (optional)",
},
},
Before: RequireHost,
},
},
},
// Advanced Audio commands
{
Name: "audio",
Aliases: []string{"a"},
Usage: "Advanced audio control commands",
Subcommands: []*cli.Command{
// DSP Controls
{
Name: "dsp",
Aliases: []string{"d"},
Usage: "DSP audio control commands",
Subcommands: []*cli.Command{
{
Name: "get",
Usage: "Get current DSP audio controls",
Action: getAudioDSPControls,
Before: RequireHost,
},
{
Name: "set",
Usage: "Set DSP audio controls",
Action: setAudioDSPControls,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "mode",
Usage: "Audio mode (NORMAL, DIALOG, SURROUND, MUSIC, MOVIE, etc.)",
},
&cli.IntFlag{
Name: "delay",
Usage: "Video sync audio delay in milliseconds",
},
},
Before: RequireHost,
},
{
Name: "mode",
Usage: "Set audio mode",
Action: setAudioMode,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "mode",
Usage: "Audio mode (NORMAL, DIALOG, SURROUND, MUSIC, MOVIE, etc.)",
Required: true,
},
},
Before: RequireHost,
},
{
Name: "delay",
Usage: "Set video sync audio delay",
Action: setVideoSyncDelay,
Flags: []cli.Flag{
&cli.IntFlag{
Name: "delay",
Usage: "Video sync audio delay in milliseconds",
Required: true,
},
},
Before: RequireHost,
},
},
},
// Tone Controls
{
Name: "tone",
Aliases: []string{"t"},
Usage: "Advanced tone control commands",
Subcommands: []*cli.Command{
{
Name: "get",
Usage: "Get current advanced tone controls",
Action: getAudioToneControls,
Before: RequireHost,
},
{
Name: "set",
Usage: "Set advanced tone controls",
Action: setAudioToneControls,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "bass",
Usage: "Bass level (range varies by device)",
},
&cli.StringFlag{
Name: "treble",
Usage: "Treble level (range varies by device)",
},
},
Before: RequireHost,
},
{
Name: "bass",
Usage: "Set advanced bass level",
Action: setAdvancedBass,
Flags: []cli.Flag{
&cli.IntFlag{
Name: "level",
Usage: "Bass level (range varies by device)",
Required: true,
},
},
Before: RequireHost,
},
{
Name: "treble",
Usage: "Set advanced treble level",
Action: setAdvancedTreble,
Flags: []cli.Flag{
&cli.IntFlag{
Name: "level",
Usage: "Treble level (range varies by device)",
Required: true,
},
},
Before: RequireHost,
},
},
},
// Level Controls
{
Name: "level",
Aliases: []string{"l"},
Usage: "Speaker level control commands",
Subcommands: []*cli.Command{
{
Name: "get",
Usage: "Get current speaker level controls",
Action: getAudioLevelControls,
Before: RequireHost,
},
{
Name: "set",
Usage: "Set speaker level controls",
Action: setAudioLevelControls,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "front-center",
Usage: "Front-center speaker level (range varies by device)",
},
&cli.StringFlag{
Name: "rear-surround",
Usage: "Rear-surround speakers level (range varies by device)",
},
},
Before: RequireHost,
},
{
Name: "front-center",
Usage: "Set front-center speaker level",
Action: setFrontCenterLevel,
Flags: []cli.Flag{
&cli.IntFlag{
Name: "level",
Usage: "Front-center speaker level (range varies by device)",
Required: true,
},
},
Before: RequireHost,
},
{
Name: "rear-surround",
Usage: "Set rear-surround speakers level",
Action: setRearSurroundLevel,
Flags: []cli.Flag{
&cli.IntFlag{
Name: "level",
Usage: "Rear-surround speakers level (range varies by device)",
Required: true,
},
},
Before: RequireHost,
},
},
},
},
},
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}