mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 16:46:17 +00:00
Implements the speaker-side group API surface (path 1 of the two approaches gmuth outlined in issue #252): clients form, rename, and dissolve stereo pairs directly on the device, and the resulting GroupService.xml persists on disk in the same shape the device emits over /getGroup. What landed: - pkg/models/group.go: Status field + IsEmpty() helper, matching the GET /getGroup response shape (id-attr, masterDeviceId, roles, senderIPAddress). - pkg/client/client.go: GetGroup, AddGroup, UpdateGroup, RemoveGroup. The endpoint name is /getGroup (not /group, despite some wiki docs) — confirmed against a real ST-10's /supportedURLs. RemoveGroup uses GET per the wire spec. - cmd/soundtouch-cli/cmd_group.go + main.go: new `group` subcommand with status / create --left --right [--name] / rename / remove, mirroring gmuth's group.sh recipe. WebSocket notifications: - pkg/models/websocket.go: EventTypeGroupUpdated + GroupUpdatedEvent + dispatch helpers. The device fans this out to both LEFT and RIGHT speakers on every group mutation, including empty-group teardowns; the parse test covers both shapes. - pkg/client/websocket.go: OnGroupUpdated registration and dispatch. - cmd/soundtouch-cli/cmd_events.go: `group` filter + handleGroupEvent formatter. WebSocket observability (came up while validating the above against a real device): - New RawMessageHandler type + OnRawMessage hook that fires for every incoming frame before parsing, with the parse error alongside. - New --debug flag on `events subscribe` with modes all / unknown / errors. Raw output goes to stderr so it composes cleanly with shell redirects. The pkg/client refactor in this commit also adopts speaker.HTTPPort (introduced in the previous refactor) — the unexported defaultSoundTouchPort and three hard-coded 8090 literals are gone. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
35 lines
1.1 KiB
Go
35 lines
1.1 KiB
Go
package models
|
|
|
|
import "encoding/xml"
|
|
|
|
// Group represents a stereo pair of two ST10 SoundTouch speakers.
|
|
type Group struct {
|
|
XMLName xml.Name `xml:"group"`
|
|
ID string `xml:"id,attr,omitempty"`
|
|
Name string `xml:"name"`
|
|
MasterDeviceID string `xml:"masterDeviceId"`
|
|
Roles GroupRoles `xml:"roles"`
|
|
SenderIPAddress string `xml:"senderIPAddress,omitempty"`
|
|
// Status is populated by the device on GET /group (e.g. "GROUP_OK")
|
|
// and omitted from requests we send back.
|
|
Status string `xml:"status,omitempty"`
|
|
}
|
|
|
|
// IsEmpty reports whether the device returned an empty <group/> element,
|
|
// which is the speaker's way of saying "no stereo pair configured".
|
|
func (g *Group) IsEmpty() bool {
|
|
return g.ID == "" && g.MasterDeviceID == "" && len(g.Roles.Roles) == 0
|
|
}
|
|
|
|
// GroupRoles contains the role assignments for devices in a group.
|
|
type GroupRoles struct {
|
|
Roles []GroupRole `xml:"groupRole"`
|
|
}
|
|
|
|
// GroupRole describes the role (LEFT or RIGHT) of a single device in a group.
|
|
type GroupRole struct {
|
|
DeviceID string `xml:"deviceId"`
|
|
Role string `xml:"role"`
|
|
IPAddress string `xml:"ipAddress,omitempty"`
|
|
}
|