mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-09-07 15:07:17 +00:00
317 lines
9.4 KiB
Go
317 lines
9.4 KiB
Go
package models
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// SystemTimeout is the power-saving setting returned by /systemtimeout.
|
|
type SystemTimeout struct {
|
|
XMLName xml.Name `xml:"systemtimeout"`
|
|
PowerSavingEnabled bool `xml:"powersaving_enabled"`
|
|
}
|
|
|
|
// Validate checks whether the update model is present.
|
|
func (s *SystemTimeout) Validate() error {
|
|
if s == nil {
|
|
return fmt.Errorf("system timeout is nil")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// UnmarshalXML rejects responses that omit the required power-saving value.
|
|
func (s *SystemTimeout) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|
if start.Name.Local != "systemtimeout" {
|
|
return fmt.Errorf("expected systemtimeout element, got %s", start.Name.Local)
|
|
}
|
|
|
|
var wire struct {
|
|
PowerSavingEnabled *bool `xml:"powersaving_enabled"`
|
|
}
|
|
if err := d.DecodeElement(&wire, &start); err != nil {
|
|
return err
|
|
}
|
|
|
|
if wire.PowerSavingEnabled == nil {
|
|
return fmt.Errorf("systemtimeout is missing powersaving_enabled")
|
|
}
|
|
|
|
s.XMLName = start.Name
|
|
s.PowerSavingEnabled = *wire.PowerSavingEnabled
|
|
|
|
return nil
|
|
}
|
|
|
|
// RebroadcastLatencyModeValue is a firmware-supported rebroadcast timing mode.
|
|
type RebroadcastLatencyModeValue string
|
|
|
|
const (
|
|
// RebroadcastLatencySyncToRoom prioritizes the selected room for video sync.
|
|
RebroadcastLatencySyncToRoom RebroadcastLatencyModeValue = "SYNC_TO_ROOM"
|
|
// RebroadcastLatencySyncToZone prioritizes synchronization across the zone.
|
|
RebroadcastLatencySyncToZone RebroadcastLatencyModeValue = "SYNC_TO_ZONE"
|
|
)
|
|
|
|
// Validate rejects values that the SoundTouch firmware does not understand.
|
|
func (m RebroadcastLatencyModeValue) Validate() error {
|
|
switch m {
|
|
case RebroadcastLatencySyncToRoom, RebroadcastLatencySyncToZone:
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("unknown rebroadcast latency mode %q", m)
|
|
}
|
|
}
|
|
|
|
// RebroadcastLatencyMode is the setting returned by /rebroadcastlatencymode.
|
|
// Controllable is response metadata and is not included in update requests.
|
|
type RebroadcastLatencyMode struct {
|
|
XMLName xml.Name `xml:"rebroadcastlatencymode"`
|
|
Mode RebroadcastLatencyModeValue `xml:"mode,attr"`
|
|
Controllable bool `xml:"controllable,attr"`
|
|
}
|
|
|
|
// Validate checks whether the reported mode is supported.
|
|
func (r *RebroadcastLatencyMode) Validate() error {
|
|
if r == nil {
|
|
return fmt.Errorf("rebroadcast latency mode is nil")
|
|
}
|
|
|
|
return r.Mode.Validate()
|
|
}
|
|
|
|
// UnmarshalXML rejects responses that omit either required attribute.
|
|
func (r *RebroadcastLatencyMode) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|
if start.Name.Local != "rebroadcastlatencymode" {
|
|
return fmt.Errorf("expected rebroadcastlatencymode element, got %s", start.Name.Local)
|
|
}
|
|
|
|
var wire struct {
|
|
Mode *RebroadcastLatencyModeValue `xml:"mode,attr"`
|
|
Controllable *bool `xml:"controllable,attr"`
|
|
}
|
|
if err := d.DecodeElement(&wire, &start); err != nil {
|
|
return err
|
|
}
|
|
|
|
if wire.Mode == nil {
|
|
return fmt.Errorf("rebroadcastlatencymode is missing mode")
|
|
}
|
|
|
|
if wire.Controllable == nil {
|
|
return fmt.Errorf("rebroadcastlatencymode is missing controllable")
|
|
}
|
|
|
|
if err := wire.Mode.Validate(); err != nil {
|
|
return err
|
|
}
|
|
|
|
r.XMLName = start.Name
|
|
r.Mode = *wire.Mode
|
|
r.Controllable = *wire.Controllable
|
|
|
|
return nil
|
|
}
|
|
|
|
// RebroadcastLatencyModeRequest is the update body accepted by the firmware.
|
|
type RebroadcastLatencyModeRequest struct {
|
|
XMLName xml.Name `xml:"rebroadcastlatencymode"`
|
|
Mode RebroadcastLatencyModeValue `xml:"mode,attr"`
|
|
}
|
|
|
|
// Validate checks whether the requested latency mode is known.
|
|
func (r *RebroadcastLatencyModeRequest) Validate() error {
|
|
if r == nil {
|
|
return fmt.Errorf("rebroadcast latency mode request is nil")
|
|
}
|
|
|
|
return r.Mode.Validate()
|
|
}
|
|
|
|
// LanguageCode is a SoundTouch system-language identifier.
|
|
type LanguageCode int
|
|
|
|
// Supported system language codes match the set exposed by Stockholm.
|
|
const (
|
|
LanguageDanish LanguageCode = 1
|
|
LanguageGerman LanguageCode = 2
|
|
LanguageEnglish LanguageCode = 3
|
|
LanguageSpanish LanguageCode = 4
|
|
LanguageFrench LanguageCode = 5
|
|
LanguageItalian LanguageCode = 6
|
|
LanguageDutch LanguageCode = 7
|
|
LanguageSwedish LanguageCode = 8
|
|
LanguageJapanese LanguageCode = 9
|
|
LanguageSimplifiedChinese LanguageCode = 10
|
|
LanguageTraditionalChinese LanguageCode = 11
|
|
LanguageKorean LanguageCode = 12
|
|
LanguageThai LanguageCode = 13
|
|
LanguageCzech LanguageCode = 15
|
|
LanguageFinnish LanguageCode = 16
|
|
LanguageGreek LanguageCode = 17
|
|
LanguageNorwegian LanguageCode = 18
|
|
LanguagePolish LanguageCode = 19
|
|
LanguagePortuguese LanguageCode = 20
|
|
LanguageRomanian LanguageCode = 21
|
|
LanguageRussian LanguageCode = 22
|
|
LanguageSlovenian LanguageCode = 23
|
|
LanguageTurkish LanguageCode = 24
|
|
LanguageHungarian LanguageCode = 25
|
|
)
|
|
|
|
var knownSystemLanguageNames = map[LanguageCode]string{
|
|
LanguageDanish: "Dansk",
|
|
LanguageGerman: "Deutsch",
|
|
LanguageEnglish: "English",
|
|
LanguageSpanish: "Español",
|
|
LanguageFrench: "Français",
|
|
LanguageItalian: "Italiano",
|
|
LanguageDutch: "Nederlands",
|
|
LanguageSwedish: "Svenska",
|
|
LanguageJapanese: "日本語",
|
|
LanguageSimplifiedChinese: "简体中文",
|
|
LanguageTraditionalChinese: "繁體中文",
|
|
LanguageKorean: "한국어",
|
|
LanguageThai: "ไทย",
|
|
LanguageCzech: "Čeština",
|
|
LanguageFinnish: "Suomi",
|
|
LanguageGreek: "Ελληνικά",
|
|
LanguageNorwegian: "Norsk",
|
|
LanguagePolish: "Polski",
|
|
LanguagePortuguese: "Português",
|
|
LanguageRomanian: "Română",
|
|
LanguageRussian: "Русский",
|
|
LanguageSlovenian: "Slovenščina",
|
|
LanguageTurkish: "Türkçe",
|
|
LanguageHungarian: "Magyar",
|
|
}
|
|
|
|
// SystemLanguageNames returns the language labels and codes used by Stockholm.
|
|
// Each call returns a copy so callers cannot mutate shared validation state.
|
|
func SystemLanguageNames() map[LanguageCode]string {
|
|
names := make(map[LanguageCode]string, len(knownSystemLanguageNames))
|
|
for code, name := range knownSystemLanguageNames {
|
|
names[code] = name
|
|
}
|
|
|
|
return names
|
|
}
|
|
|
|
// Validate rejects language codes that Stockholm does not offer for writes.
|
|
func (l LanguageCode) Validate() error {
|
|
if _, ok := knownSystemLanguageNames[l]; !ok {
|
|
return fmt.Errorf("unknown system language code %d", l)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SystemLanguage is the integer value read from or written to /language.
|
|
// Unknown values are retained when reading so newer firmware remains usable.
|
|
type SystemLanguage struct {
|
|
XMLName xml.Name `xml:"sysLanguage"`
|
|
Code LanguageCode `xml:",chardata"`
|
|
}
|
|
|
|
// UnmarshalXML retains unknown integer codes for forward compatibility while
|
|
// rejecting a response that omits the language value entirely.
|
|
func (l *SystemLanguage) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|
if start.Name.Local != "sysLanguage" {
|
|
return fmt.Errorf("expected sysLanguage element, got %s", start.Name.Local)
|
|
}
|
|
|
|
var raw string
|
|
if err := d.DecodeElement(&raw, &start); err != nil {
|
|
return err
|
|
}
|
|
|
|
raw = strings.TrimSpace(raw)
|
|
if raw == "" {
|
|
return fmt.Errorf("sysLanguage is missing its language code")
|
|
}
|
|
|
|
code, err := strconv.Atoi(raw)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid sysLanguage code %q: %w", raw, err)
|
|
}
|
|
|
|
l.XMLName = start.Name
|
|
l.Code = LanguageCode(code)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Validate checks whether this language can be sent to the speaker.
|
|
func (l *SystemLanguage) Validate() error {
|
|
if l == nil {
|
|
return fmt.Errorf("system language is nil")
|
|
}
|
|
|
|
return l.Code.Validate()
|
|
}
|
|
|
|
// BluetoothInfo is the speaker Bluetooth adapter information.
|
|
type BluetoothInfo struct {
|
|
XMLName xml.Name `xml:"BluetoothInfo"`
|
|
BluetoothMACAddress string `xml:"BluetoothMACAddress,attr"`
|
|
}
|
|
|
|
// Validate requires the adapter address returned by the firmware.
|
|
func (b *BluetoothInfo) Validate() error {
|
|
if b == nil {
|
|
return fmt.Errorf("bluetooth info is nil")
|
|
}
|
|
|
|
if b.BluetoothMACAddress == "" {
|
|
return fmt.Errorf("bluetooth info is missing BluetoothMACAddress")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// UnmarshalXML rejects responses without the adapter address.
|
|
func (b *BluetoothInfo) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|
if start.Name.Local != "BluetoothInfo" {
|
|
return fmt.Errorf("expected BluetoothInfo element, got %s", start.Name.Local)
|
|
}
|
|
|
|
var wire struct {
|
|
BluetoothMACAddress string `xml:"BluetoothMACAddress,attr"`
|
|
}
|
|
if err := d.DecodeElement(&wire, &start); err != nil {
|
|
return err
|
|
}
|
|
|
|
b.XMLName = start.Name
|
|
b.BluetoothMACAddress = wire.BluetoothMACAddress
|
|
|
|
return b.Validate()
|
|
}
|
|
|
|
// SourceRenameRequest is the exact update body accepted by /nameSource.
|
|
type SourceRenameRequest struct {
|
|
XMLName xml.Name `xml:"ContentItem"`
|
|
Source string `xml:"source,attr"`
|
|
SourceAccount string `xml:"sourceAccount,attr,omitempty"`
|
|
ItemName string `xml:"itemName"`
|
|
}
|
|
|
|
// Validate requires the source identity and replacement display name.
|
|
func (r *SourceRenameRequest) Validate() error {
|
|
if r == nil {
|
|
return fmt.Errorf("source rename request is nil")
|
|
}
|
|
|
|
if r.Source == "" {
|
|
return fmt.Errorf("source is required")
|
|
}
|
|
|
|
if r.ItemName == "" {
|
|
return fmt.Errorf("item name is required")
|
|
}
|
|
|
|
return nil
|
|
}
|