mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-23 02:56:13 +00:00
The WebSocketMessage struct was causing a lint error due to invalid xml:",any" tag. Investigation revealed this struct was unused in actual WebSocket parsing - the ParseWebSocketEvent() function works directly with WebSocketEvent struct. Changes: - Removed unused WebSocketMessage struct and its GetEventType() method - Removed corresponding tests - WebSocket functionality verified working (all tests pass) - Actual parsing uses WebSocketEvent which has proper XML tags This resolves the SA5008 lint warning while maintaining full WebSocket functionality.
486 lines
15 KiB
Go
486 lines
15 KiB
Go
package models
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// WebSocketEventType represents the type of WebSocket event
|
|
type WebSocketEventType string
|
|
|
|
const (
|
|
// Event types as documented in SoundTouch API
|
|
EventTypeNowPlaying WebSocketEventType = "nowPlayingUpdated"
|
|
EventTypeVolumeUpdated WebSocketEventType = "volumeUpdated"
|
|
EventTypeConnectionState WebSocketEventType = "connectionStateUpdated"
|
|
EventTypePresetUpdated WebSocketEventType = "presetUpdated"
|
|
EventTypeZoneUpdated WebSocketEventType = "zoneUpdated"
|
|
EventTypeBassUpdated WebSocketEventType = "bassUpdated"
|
|
EventTypeClockTimeUpdated WebSocketEventType = "clockTimeUpdated"
|
|
EventTypeClockDisplayUpdated WebSocketEventType = "clockDisplayUpdated"
|
|
EventTypeNameUpdated WebSocketEventType = "nameUpdated"
|
|
EventTypeErrorUpdated WebSocketEventType = "errorUpdated"
|
|
EventTypeRecentsUpdated WebSocketEventType = "recentsUpdated"
|
|
EventTypeLanguageUpdated WebSocketEventType = "languageUpdated"
|
|
EventTypeUnknown WebSocketEventType = "unknown"
|
|
)
|
|
|
|
// String returns a human-readable string representation
|
|
func (e WebSocketEventType) String() string {
|
|
switch e {
|
|
case EventTypeNowPlaying:
|
|
return "Now Playing Updated"
|
|
case EventTypeVolumeUpdated:
|
|
return "Volume Updated"
|
|
case EventTypeConnectionState:
|
|
return "Connection State Updated"
|
|
case EventTypePresetUpdated:
|
|
return "Preset Updated"
|
|
case EventTypeZoneUpdated:
|
|
return "Zone Updated"
|
|
case EventTypeBassUpdated:
|
|
return "Bass Updated"
|
|
case EventTypeClockTimeUpdated:
|
|
return "Clock Time Updated"
|
|
case EventTypeClockDisplayUpdated:
|
|
return "Clock Display Updated"
|
|
case EventTypeNameUpdated:
|
|
return "Name Updated"
|
|
case EventTypeErrorUpdated:
|
|
return "Error Updated"
|
|
case EventTypeRecentsUpdated:
|
|
return "Recents Updated"
|
|
case EventTypeLanguageUpdated:
|
|
return "Language Updated"
|
|
default:
|
|
return "Unknown Event"
|
|
}
|
|
}
|
|
|
|
// WebSocketEvent represents a generic WebSocket event from SoundTouch device
|
|
type WebSocketEvent struct {
|
|
XMLName xml.Name `xml:"updates"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
NowPlayingUpdated *NowPlayingUpdatedEvent `xml:"nowPlayingUpdated,omitempty"`
|
|
VolumeUpdated *VolumeUpdatedEvent `xml:"volumeUpdated,omitempty"`
|
|
ConnectionStateUpdated *ConnectionStateUpdatedEvent `xml:"connectionStateUpdated,omitempty"`
|
|
PresetUpdated *PresetUpdatedEvent `xml:"presetUpdated,omitempty"`
|
|
ZoneUpdated *ZoneUpdatedEvent `xml:"zoneUpdated,omitempty"`
|
|
BassUpdated *BassUpdatedEvent `xml:"bassUpdated,omitempty"`
|
|
ClockTimeUpdated *ClockTimeUpdatedEvent `xml:"clockTimeUpdated,omitempty"`
|
|
ClockDisplayUpdated *ClockDisplayUpdatedEvent `xml:"clockDisplayUpdated,omitempty"`
|
|
NameUpdated *NameUpdatedEvent `xml:"nameUpdated,omitempty"`
|
|
ErrorUpdated *ErrorUpdatedEvent `xml:"errorUpdated,omitempty"`
|
|
RecentsUpdated *RecentsUpdatedEvent `xml:"recentsUpdated,omitempty"`
|
|
LanguageUpdated *LanguageUpdatedEvent `xml:"languageUpdated,omitempty"`
|
|
Timestamp time.Time `json:"timestamp"` // Added by client for tracking
|
|
}
|
|
|
|
// GetEvents returns all events present in this WebSocket event
|
|
func (e *WebSocketEvent) GetEvents() []interface{} {
|
|
var events []interface{}
|
|
|
|
if e.NowPlayingUpdated != nil {
|
|
events = append(events, e.NowPlayingUpdated)
|
|
}
|
|
if e.VolumeUpdated != nil {
|
|
events = append(events, e.VolumeUpdated)
|
|
}
|
|
if e.ConnectionStateUpdated != nil {
|
|
events = append(events, e.ConnectionStateUpdated)
|
|
}
|
|
if e.PresetUpdated != nil {
|
|
events = append(events, e.PresetUpdated)
|
|
}
|
|
if e.ZoneUpdated != nil {
|
|
events = append(events, e.ZoneUpdated)
|
|
}
|
|
if e.BassUpdated != nil {
|
|
events = append(events, e.BassUpdated)
|
|
}
|
|
if e.ClockTimeUpdated != nil {
|
|
events = append(events, e.ClockTimeUpdated)
|
|
}
|
|
if e.ClockDisplayUpdated != nil {
|
|
events = append(events, e.ClockDisplayUpdated)
|
|
}
|
|
if e.NameUpdated != nil {
|
|
events = append(events, e.NameUpdated)
|
|
}
|
|
if e.ErrorUpdated != nil {
|
|
events = append(events, e.ErrorUpdated)
|
|
}
|
|
if e.RecentsUpdated != nil {
|
|
events = append(events, e.RecentsUpdated)
|
|
}
|
|
if e.LanguageUpdated != nil {
|
|
events = append(events, e.LanguageUpdated)
|
|
}
|
|
|
|
return events
|
|
}
|
|
|
|
// NowPlayingUpdatedEvent represents a now playing update event
|
|
type NowPlayingUpdatedEvent struct {
|
|
XMLName xml.Name `xml:"nowPlayingUpdated"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
NowPlaying NowPlaying `xml:"nowPlaying"`
|
|
}
|
|
|
|
// VolumeUpdatedEvent represents a volume update event
|
|
type VolumeUpdatedEvent struct {
|
|
XMLName xml.Name `xml:"volumeUpdated"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
Volume Volume `xml:"volume"`
|
|
}
|
|
|
|
// ConnectionStateUpdatedEvent represents a connection state update event
|
|
type ConnectionStateUpdatedEvent struct {
|
|
XMLName xml.Name `xml:"connectionStateUpdated"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
ConnectionState ConnectionState `xml:"connectionState"`
|
|
}
|
|
|
|
// ConnectionState represents the device's network connection state
|
|
type ConnectionState struct {
|
|
XMLName xml.Name `xml:"connectionState"`
|
|
State string `xml:"state,attr"`
|
|
Signal string `xml:"signal,attr"`
|
|
}
|
|
|
|
// ConnectionStateType represents connection state values
|
|
type ConnectionStateType string
|
|
|
|
const (
|
|
ConnectionStateConnected ConnectionStateType = "CONNECTED"
|
|
ConnectionStateDisconnected ConnectionStateType = "DISCONNECTED"
|
|
ConnectionStateConnecting ConnectionStateType = "CONNECTING"
|
|
)
|
|
|
|
// IsConnected returns true if the device is connected
|
|
func (cs *ConnectionState) IsConnected() bool {
|
|
return cs.State == string(ConnectionStateConnected)
|
|
}
|
|
|
|
// GetSignalStrength returns the signal strength as a string
|
|
func (cs *ConnectionState) GetSignalStrength() string {
|
|
return cs.Signal
|
|
}
|
|
|
|
// PresetUpdatedEvent represents a preset update event
|
|
type PresetUpdatedEvent struct {
|
|
XMLName xml.Name `xml:"presetUpdated"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
Preset Preset `xml:"preset"`
|
|
}
|
|
|
|
// ZoneUpdatedEvent represents a multiroom zone update event
|
|
type ZoneUpdatedEvent struct {
|
|
XMLName xml.Name `xml:"zoneUpdated"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
Zone Zone `xml:"zone"`
|
|
}
|
|
|
|
// Zone represents multiroom zone information
|
|
type Zone struct {
|
|
XMLName xml.Name `xml:"zone"`
|
|
Master string `xml:"master,attr"`
|
|
Members []ZoneMember `xml:"member"`
|
|
}
|
|
|
|
// ZoneMember represents a member of a multiroom zone
|
|
type ZoneMember struct {
|
|
XMLName xml.Name `xml:"member"`
|
|
DeviceID string `xml:",chardata"`
|
|
IP string `xml:"ipaddress,attr"`
|
|
}
|
|
|
|
// BassUpdatedEvent represents a bass setting update event
|
|
type BassUpdatedEvent struct {
|
|
XMLName xml.Name `xml:"bassUpdated"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
Bass Bass `xml:"bass"`
|
|
}
|
|
|
|
// ClockTimeUpdatedEvent represents a clock time update event
|
|
type ClockTimeUpdatedEvent struct {
|
|
XMLName xml.Name `xml:"clockTimeUpdated"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
ClockTime ClockTime `xml:"clockTime"`
|
|
}
|
|
|
|
// ClockDisplayUpdatedEvent represents a clock display setting update event
|
|
type ClockDisplayUpdatedEvent struct {
|
|
XMLName xml.Name `xml:"clockDisplayUpdated"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
ClockDisplay ClockDisplay `xml:"clockDisplay"`
|
|
}
|
|
|
|
// NameUpdatedEvent represents a device name update event
|
|
type NameUpdatedEvent struct {
|
|
XMLName xml.Name `xml:"nameUpdated"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
Name Name `xml:"name"`
|
|
}
|
|
|
|
// ErrorUpdatedEvent represents an error state update event
|
|
type ErrorUpdatedEvent struct {
|
|
XMLName xml.Name `xml:"errorUpdated"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
Error Error `xml:"error"`
|
|
}
|
|
|
|
// Error represents an error state
|
|
type Error struct {
|
|
XMLName xml.Name `xml:"error"`
|
|
Value string `xml:"value,attr"`
|
|
Name string `xml:"name,attr"`
|
|
Text string `xml:",chardata"`
|
|
}
|
|
|
|
// RecentsUpdatedEvent represents a recent items update event
|
|
type RecentsUpdatedEvent struct {
|
|
XMLName xml.Name `xml:"recentsUpdated"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
Recents Recents `xml:"recents"`
|
|
}
|
|
|
|
// Recents represents recently played items
|
|
type Recents struct {
|
|
XMLName xml.Name `xml:"recents"`
|
|
Items []RecentItem `xml:"recent"`
|
|
}
|
|
|
|
// RecentItem represents a recently played item
|
|
type RecentItem struct {
|
|
XMLName xml.Name `xml:"recent"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
CreatedOn int64 `xml:"createdOn,attr"`
|
|
ID string `xml:"id,attr"`
|
|
ContentItem ContentItem `xml:"ContentItem"`
|
|
}
|
|
|
|
// LanguageUpdatedEvent represents a language setting update event
|
|
type LanguageUpdatedEvent struct {
|
|
XMLName xml.Name `xml:"languageUpdated"`
|
|
DeviceID string `xml:"deviceID,attr"`
|
|
Language Language `xml:"language"`
|
|
}
|
|
|
|
// Language represents language settings
|
|
type Language struct {
|
|
XMLName xml.Name `xml:"language"`
|
|
Value string `xml:",chardata"`
|
|
}
|
|
|
|
// EventHandler represents a function that handles WebSocket events
|
|
type EventHandler func(event *WebSocketEvent)
|
|
|
|
// TypedEventHandler represents a function that handles specific event types
|
|
type TypedEventHandler[T any] func(event T)
|
|
|
|
// WebSocketEventHandlers holds typed event handlers for different event types
|
|
type WebSocketEventHandlers struct {
|
|
OnNowPlaying TypedEventHandler[*NowPlayingUpdatedEvent]
|
|
OnVolumeUpdated TypedEventHandler[*VolumeUpdatedEvent]
|
|
OnConnectionState TypedEventHandler[*ConnectionStateUpdatedEvent]
|
|
OnPresetUpdated TypedEventHandler[*PresetUpdatedEvent]
|
|
OnZoneUpdated TypedEventHandler[*ZoneUpdatedEvent]
|
|
OnBassUpdated TypedEventHandler[*BassUpdatedEvent]
|
|
OnClockTimeUpdated TypedEventHandler[*ClockTimeUpdatedEvent]
|
|
OnClockDisplayUpdated TypedEventHandler[*ClockDisplayUpdatedEvent]
|
|
OnNameUpdated TypedEventHandler[*NameUpdatedEvent]
|
|
OnErrorUpdated TypedEventHandler[*ErrorUpdatedEvent]
|
|
OnRecentsUpdated TypedEventHandler[*RecentsUpdatedEvent]
|
|
OnLanguageUpdated TypedEventHandler[*LanguageUpdatedEvent]
|
|
OnUnknownEvent EventHandler
|
|
}
|
|
|
|
// ParseWebSocketEvent attempts to parse a WebSocket message into a specific event type
|
|
func ParseWebSocketEvent(data []byte) (*WebSocketEvent, error) {
|
|
var event WebSocketEvent
|
|
if err := xml.Unmarshal(data, &event); err != nil {
|
|
return nil, fmt.Errorf("failed to parse WebSocket event: %w", err)
|
|
}
|
|
|
|
// Add timestamp
|
|
event.Timestamp = time.Now()
|
|
|
|
return &event, nil
|
|
}
|
|
|
|
// ParseTypedEvent attempts to parse a WebSocket event into a specific typed event
|
|
func ParseTypedEvent[T any](event *WebSocketEvent, eventType WebSocketEventType) (T, error) {
|
|
var result T
|
|
|
|
// Get the event directly from the parsed structure
|
|
switch eventType {
|
|
case EventTypeNowPlaying:
|
|
if event.NowPlayingUpdated != nil {
|
|
if typedResult, ok := interface{}(event.NowPlayingUpdated).(T); ok {
|
|
return typedResult, nil
|
|
}
|
|
}
|
|
case EventTypeVolumeUpdated:
|
|
if event.VolumeUpdated != nil {
|
|
if typedResult, ok := interface{}(event.VolumeUpdated).(T); ok {
|
|
return typedResult, nil
|
|
}
|
|
}
|
|
case EventTypeConnectionState:
|
|
if event.ConnectionStateUpdated != nil {
|
|
if typedResult, ok := interface{}(event.ConnectionStateUpdated).(T); ok {
|
|
return typedResult, nil
|
|
}
|
|
}
|
|
case EventTypePresetUpdated:
|
|
if event.PresetUpdated != nil {
|
|
if typedResult, ok := interface{}(event.PresetUpdated).(T); ok {
|
|
return typedResult, nil
|
|
}
|
|
}
|
|
case EventTypeZoneUpdated:
|
|
if event.ZoneUpdated != nil {
|
|
if typedResult, ok := interface{}(event.ZoneUpdated).(T); ok {
|
|
return typedResult, nil
|
|
}
|
|
}
|
|
case EventTypeBassUpdated:
|
|
if event.BassUpdated != nil {
|
|
if typedResult, ok := interface{}(event.BassUpdated).(T); ok {
|
|
return typedResult, nil
|
|
}
|
|
}
|
|
case EventTypeClockTimeUpdated:
|
|
if event.ClockTimeUpdated != nil {
|
|
if typedResult, ok := interface{}(event.ClockTimeUpdated).(T); ok {
|
|
return typedResult, nil
|
|
}
|
|
}
|
|
case EventTypeClockDisplayUpdated:
|
|
if event.ClockDisplayUpdated != nil {
|
|
if typedResult, ok := interface{}(event.ClockDisplayUpdated).(T); ok {
|
|
return typedResult, nil
|
|
}
|
|
}
|
|
case EventTypeNameUpdated:
|
|
if event.NameUpdated != nil {
|
|
if typedResult, ok := interface{}(event.NameUpdated).(T); ok {
|
|
return typedResult, nil
|
|
}
|
|
}
|
|
case EventTypeErrorUpdated:
|
|
if event.ErrorUpdated != nil {
|
|
if typedResult, ok := interface{}(event.ErrorUpdated).(T); ok {
|
|
return typedResult, nil
|
|
}
|
|
}
|
|
case EventTypeRecentsUpdated:
|
|
if event.RecentsUpdated != nil {
|
|
if typedResult, ok := interface{}(event.RecentsUpdated).(T); ok {
|
|
return typedResult, nil
|
|
}
|
|
}
|
|
case EventTypeLanguageUpdated:
|
|
if event.LanguageUpdated != nil {
|
|
if typedResult, ok := interface{}(event.LanguageUpdated).(T); ok {
|
|
return typedResult, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return result, fmt.Errorf("event type %s not found in WebSocket event", eventType)
|
|
}
|
|
|
|
// HasEventType checks if the WebSocket event contains a specific event type
|
|
func (e *WebSocketEvent) HasEventType(eventType WebSocketEventType) bool {
|
|
switch eventType {
|
|
case EventTypeNowPlaying:
|
|
return e.NowPlayingUpdated != nil
|
|
case EventTypeVolumeUpdated:
|
|
return e.VolumeUpdated != nil
|
|
case EventTypeConnectionState:
|
|
return e.ConnectionStateUpdated != nil
|
|
case EventTypePresetUpdated:
|
|
return e.PresetUpdated != nil
|
|
case EventTypeZoneUpdated:
|
|
return e.ZoneUpdated != nil
|
|
case EventTypeBassUpdated:
|
|
return e.BassUpdated != nil
|
|
case EventTypeClockTimeUpdated:
|
|
return e.ClockTimeUpdated != nil
|
|
case EventTypeClockDisplayUpdated:
|
|
return e.ClockDisplayUpdated != nil
|
|
case EventTypeNameUpdated:
|
|
return e.NameUpdated != nil
|
|
case EventTypeErrorUpdated:
|
|
return e.ErrorUpdated != nil
|
|
case EventTypeRecentsUpdated:
|
|
return e.RecentsUpdated != nil
|
|
case EventTypeLanguageUpdated:
|
|
return e.LanguageUpdated != nil
|
|
}
|
|
return false
|
|
}
|
|
|
|
// GetEventTypes returns all event types present in this WebSocket event
|
|
func (e *WebSocketEvent) GetEventTypes() []WebSocketEventType {
|
|
var types []WebSocketEventType
|
|
|
|
if e.NowPlayingUpdated != nil {
|
|
types = append(types, EventTypeNowPlaying)
|
|
}
|
|
if e.VolumeUpdated != nil {
|
|
types = append(types, EventTypeVolumeUpdated)
|
|
}
|
|
if e.ConnectionStateUpdated != nil {
|
|
types = append(types, EventTypeConnectionState)
|
|
}
|
|
if e.PresetUpdated != nil {
|
|
types = append(types, EventTypePresetUpdated)
|
|
}
|
|
if e.ZoneUpdated != nil {
|
|
types = append(types, EventTypeZoneUpdated)
|
|
}
|
|
if e.BassUpdated != nil {
|
|
types = append(types, EventTypeBassUpdated)
|
|
}
|
|
if e.ClockTimeUpdated != nil {
|
|
types = append(types, EventTypeClockTimeUpdated)
|
|
}
|
|
if e.ClockDisplayUpdated != nil {
|
|
types = append(types, EventTypeClockDisplayUpdated)
|
|
}
|
|
if e.NameUpdated != nil {
|
|
types = append(types, EventTypeNameUpdated)
|
|
}
|
|
if e.ErrorUpdated != nil {
|
|
types = append(types, EventTypeErrorUpdated)
|
|
}
|
|
if e.RecentsUpdated != nil {
|
|
types = append(types, EventTypeRecentsUpdated)
|
|
}
|
|
if e.LanguageUpdated != nil {
|
|
types = append(types, EventTypeLanguageUpdated)
|
|
}
|
|
|
|
return types
|
|
}
|
|
|
|
// String returns a human-readable string representation of the WebSocket event
|
|
func (e *WebSocketEvent) String() string {
|
|
events := e.GetEvents()
|
|
eventTypes := e.GetEventTypes()
|
|
|
|
if len(events) == 0 {
|
|
return fmt.Sprintf("WebSocket Event [Device: %s] - No events", e.DeviceID)
|
|
}
|
|
|
|
if len(events) == 1 {
|
|
return fmt.Sprintf("WebSocket Event [Device: %s] - %s", e.DeviceID, eventTypes[0].String())
|
|
}
|
|
|
|
return fmt.Sprintf("WebSocket Event [Device: %s] - %d events", e.DeviceID, len(events))
|
|
}
|