mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-24 14:47:23 +00:00
Major additions: - Fixed WebSocketMessage XMLName struct tag issue - Implemented remaining official API endpoints: * POST /name (SetName) - Set device name * GET /bassCapabilities (GetBassCapabilities) - Check bass support * GET /trackInfo (GetTrackInfo) - Track information (duplicate of now_playing) New features: - BassCapabilities model with validation and helper methods - SetName method for device naming - GetTrackInfo method for track information - Full CLI support for all new endpoints - Comprehensive test coverage (503 lines of tests) Testing results: - All unit tests pass ✅ - bassCapabilities works on SoundTouch 10 ✅ - trackInfo may not be supported on all models (timeout on SoundTouch 10) - SetName not tested on real hardware (to avoid changing device name) API Coverage: Now 100% of official Bose SoundTouch Web API v1.0 - 19/19 endpoints implemented - All documented features supported - Additional undocumented endpoints working (clock, network, balance)
210 lines
5.1 KiB
Go
210 lines
5.1 KiB
Go
package models
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"fmt"
|
|
)
|
|
|
|
// Bass represents the response from /bass endpoint
|
|
type Bass struct {
|
|
XMLName xml.Name `xml:"bass"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
TargetBass int `xml:"targetbass"`
|
|
ActualBass int `xml:"actualbass"`
|
|
}
|
|
|
|
// BassRequest represents the request for POST /bass endpoint
|
|
type BassRequest struct {
|
|
XMLName xml.Name `xml:"bass"`
|
|
Level int `xml:",chardata"`
|
|
}
|
|
|
|
// Bass level constants
|
|
const (
|
|
BassLevelMin = -9
|
|
BassLevelMax = 9
|
|
BassLevelDefault = 0
|
|
)
|
|
|
|
// NewBassRequest creates a new bass request with validation
|
|
func NewBassRequest(level int) (*BassRequest, error) {
|
|
if !ValidateBassLevel(level) {
|
|
return nil, fmt.Errorf("invalid bass level: %d (must be between %d and %d)", level, BassLevelMin, BassLevelMax)
|
|
}
|
|
|
|
return &BassRequest{
|
|
Level: level,
|
|
}, nil
|
|
}
|
|
|
|
// ValidateBassLevel validates that a bass level is within the allowed range
|
|
func ValidateBassLevel(level int) bool {
|
|
return level >= BassLevelMin && level <= BassLevelMax
|
|
}
|
|
|
|
// ClampBassLevel clamps a bass level to the valid range
|
|
func ClampBassLevel(level int) int {
|
|
if level < BassLevelMin {
|
|
return BassLevelMin
|
|
}
|
|
if level > BassLevelMax {
|
|
return BassLevelMax
|
|
}
|
|
return level
|
|
}
|
|
|
|
// GetLevel returns the target bass level
|
|
func (b *Bass) GetLevel() int {
|
|
return b.TargetBass
|
|
}
|
|
|
|
// GetActualLevel returns the actual bass level
|
|
func (b *Bass) GetActualLevel() int {
|
|
return b.ActualBass
|
|
}
|
|
|
|
// IsAtTarget returns true if actual bass matches target bass
|
|
func (b *Bass) IsAtTarget() bool {
|
|
return b.TargetBass == b.ActualBass
|
|
}
|
|
|
|
// GetLevelName returns a descriptive name for the bass level
|
|
func GetBassLevelName(level int) string {
|
|
switch {
|
|
case level < -6:
|
|
return "Very Low"
|
|
case level < -3:
|
|
return "Low"
|
|
case level < 0:
|
|
return "Slightly Low"
|
|
case level == 0:
|
|
return "Neutral"
|
|
case level <= 3:
|
|
return "Slightly High"
|
|
case level <= 6:
|
|
return "High"
|
|
default:
|
|
return "Very High"
|
|
}
|
|
}
|
|
|
|
// GetBassLevelCategory returns the bass category
|
|
func GetBassLevelCategory(level int) string {
|
|
switch {
|
|
case level < 0:
|
|
return "Bass Cut"
|
|
case level == 0:
|
|
return "Flat"
|
|
default:
|
|
return "Bass Boost"
|
|
}
|
|
}
|
|
|
|
// String returns a human-readable string representation
|
|
func (b *Bass) String() string {
|
|
return fmt.Sprintf("Bass: %d (%s)", b.GetLevel(), GetBassLevelName(b.GetLevel()))
|
|
}
|
|
|
|
// UnmarshalXML implements custom XML unmarshaling with validation
|
|
func (b *Bass) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|
// Use a temporary struct to avoid infinite recursion
|
|
type TempBass Bass
|
|
temp := (*TempBass)(b)
|
|
|
|
if err := d.DecodeElement(temp, &start); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Validate bass levels are within acceptable range
|
|
if !ValidateBassLevel(b.TargetBass) {
|
|
return fmt.Errorf("invalid target bass level: %d", b.TargetBass)
|
|
}
|
|
|
|
if !ValidateBassLevel(b.ActualBass) {
|
|
return fmt.Errorf("invalid actual bass level: %d", b.ActualBass)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// MarshalXML implements custom XML marshaling
|
|
func (b *Bass) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
|
type TempBass Bass
|
|
temp := (*TempBass)(b)
|
|
return e.EncodeElement(temp, start)
|
|
}
|
|
|
|
// IsBassBoost returns true if bass is boosted (positive level)
|
|
func (b *Bass) IsBassBoost() bool {
|
|
return b.GetLevel() > 0
|
|
}
|
|
|
|
// IsBassCut returns true if bass is cut (negative level)
|
|
func (b *Bass) IsBassCut() bool {
|
|
return b.GetLevel() < 0
|
|
}
|
|
|
|
// IsFlat returns true if bass is neutral (zero level)
|
|
func (b *Bass) IsFlat() bool {
|
|
return b.GetLevel() == 0
|
|
}
|
|
|
|
// GetBassChangeNeeded returns the amount of change needed to reach target from actual
|
|
func (b *Bass) GetBassChangeNeeded() int {
|
|
return b.TargetBass - b.ActualBass
|
|
}
|
|
|
|
// BassCapabilities represents the response from /bassCapabilities endpoint
|
|
type BassCapabilities struct {
|
|
XMLName xml.Name `xml:"bassCapabilities"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
BassAvailable bool `xml:"bassAvailable"`
|
|
BassMin int `xml:"bassMin"`
|
|
BassMax int `xml:"bassMax"`
|
|
BassDefault int `xml:"bassDefault"`
|
|
}
|
|
|
|
// IsBassSupported returns true if bass control is supported
|
|
func (bc *BassCapabilities) IsBassSupported() bool {
|
|
return bc.BassAvailable
|
|
}
|
|
|
|
// GetMinLevel returns the minimum bass level
|
|
func (bc *BassCapabilities) GetMinLevel() int {
|
|
return bc.BassMin
|
|
}
|
|
|
|
// GetMaxLevel returns the maximum bass level
|
|
func (bc *BassCapabilities) GetMaxLevel() int {
|
|
return bc.BassMax
|
|
}
|
|
|
|
// GetDefaultLevel returns the default bass level
|
|
func (bc *BassCapabilities) GetDefaultLevel() int {
|
|
return bc.BassDefault
|
|
}
|
|
|
|
// ValidateLevel returns true if the level is within supported range
|
|
func (bc *BassCapabilities) ValidateLevel(level int) bool {
|
|
return level >= bc.BassMin && level <= bc.BassMax
|
|
}
|
|
|
|
// ClampLevel clamps a level to the supported range
|
|
func (bc *BassCapabilities) ClampLevel(level int) int {
|
|
if level < bc.BassMin {
|
|
return bc.BassMin
|
|
}
|
|
if level > bc.BassMax {
|
|
return bc.BassMax
|
|
}
|
|
return level
|
|
}
|
|
|
|
// String returns a human-readable string representation
|
|
func (bc *BassCapabilities) String() string {
|
|
if !bc.BassAvailable {
|
|
return "Bass control not supported"
|
|
}
|
|
return fmt.Sprintf("Bass: %d to %d (default: %d)", bc.BassMin, bc.BassMax, bc.BassDefault)
|
|
}
|