Documentation
¶
Overview ¶
Package boxapi is a thin client for the Bose BoseApp REST API on port 8090. The box itself hosts it — we proxy it via the stick so the desktop app has a simple JSON interface.
Most endpoints are XML (format: <field>value</field>); we parse that into Go structs and return JSON to the outside.
Index ¶
- Constants
- func ForgetLevelControls(host string)
- type Balance
- type Bass
- type BoxError
- type Client
- func (c *Client) AddGroup(ctx context.Context, name, masterDeviceID string, members []ZoneMember) error
- func (c *Client) AddWirelessProfile(ctx context.Context, ssid, password string) error
- func (c *Client) AddZoneSlave(ctx context.Context, master ZoneMember, slaves []ZoneMember) error
- func (c *Client) BrowseMediaServer(ctx context.Context, account, location, name string, start, count int) ([]MediaItem, int, error)
- func (c *Client) GetActiveWirelessProfile(ctx context.Context) (string, error)
- func (c *Client) GetBalance(ctx context.Context) (Balance, error)
- func (c *Client) GetGroup(ctx context.Context) (Group, error)
- func (c *Client) GetInfo(ctx context.Context) (Info, error)
- func (c *Client) GetNetwork(ctx context.Context) (Network, error)
- func (c *Client) GetSetupStatus(ctx context.Context) (SetupStatus, error)
- func (c *Client) GetSources(ctx context.Context) ([]Source, error)
- func (c *Client) GetSpeakerLevels(ctx context.Context) (SpeakerLevels, error)
- func (c *Client) GetSysLanguage(ctx context.Context) (int, error)
- func (c *Client) GetVolume(ctx context.Context) (Volume, error)
- func (c *Client) GetZone(ctx context.Context) (Zone, error)
- func (c *Client) HasDisplay(ctx context.Context) (has, ok bool)
- func (c *Client) Key(ctx context.Context, key string) error
- func (c *Client) LeaveSetup(ctx context.Context) error
- func (c *Client) ListMediaServers(ctx context.Context) ([]MediaServer, error)
- func (c *Client) ListServiceStations(ctx context.Context, source, account string) ([]ServiceStation, error)
- func (c *Client) LoadSettings(ctx context.Context) (Settings, error)
- func (c *Client) PlayMediaItem(ctx context.Context, account, location, name string) error
- func (c *Client) RegisterMediaServer(ctx context.Context, m MediaServer) error
- func (c *Client) RegisteredMediaServerAccounts(ctx context.Context) (map[string]bool, error)
- func (c *Client) RemoveGroup(ctx context.Context) error
- func (c *Client) RemoveMusicServiceAccount(ctx context.Context, source, displayName, user string) error
- func (c *Client) RemoveZoneSlave(ctx context.Context, master ZoneMember, slaves []ZoneMember) error
- func (c *Client) SelectContentItem(ctx context.Context, item string) error
- func (c *Client) SetBass(ctx context.Context, b int) error
- func (c *Client) SetMusicServiceAccount(ctx context.Context, source, displayName, user, pass string) error
- func (c *Client) SetName(ctx context.Context, name string) error
- func (c *Client) SetSpeakerLevel(ctx context.Context, which string, value int) error
- func (c *Client) SetVolume(ctx context.Context, v int) error
- func (c *Client) SetZone(ctx context.Context, master ZoneMember, slaves []ZoneMember) error
- func (c *Client) SiteSurvey(ctx context.Context) ([]string, error)
- func (c *Client) Standby(ctx context.Context) error
- func (c *Client) UnregisterMediaServer(ctx context.Context, m MediaServer) error
- type Group
- type Info
- type MediaItem
- type MediaServer
- type Network
- type NetworkInterface
- type ServiceStation
- type Settings
- type SetupStatus
- type Source
- type SpeakerLevel
- type SpeakerLevels
- type Volume
- type Zone
- type ZoneMember
Constants ¶
const ( LevelFrontCenter = "frontCenter" LevelRearSurrounds = "rearSurrounds" )
Level names, as used by the HTTP layer and the XML attribute name minus the suffix the firmware spells out.
Variables ¶
This section is empty.
Functions ¶
func ForgetLevelControls ¶
func ForgetLevelControls(host string)
ForgetLevelControls drops the cached /capabilities verdict for one host. Only tests need it: httptest hands out ephemeral ports that a later server can be given again, and without this a test inherits the verdict cached by whichever earlier one happened to bind the same port.
Types ¶
type Balance ¶
type Balance struct {
Available bool `json:"available"`
Min int `json:"min"`
Max int `json:"max"`
Default int `json:"default"`
Target int `json:"target"`
Actual int `json:"actual"`
}
Balance is the left/right offset of a stereo pair. It exists only while two speakers are paired: an unpaired speaker reports Available == false.
Measured live on two SoundTouch 10s (2026-08-04): Min -7, Max +7, Default 0, negative to the left. Note that a widely-referenced community implementation assumes -50..+50; the firmware itself does not agree, so use the Min/Max the speaker reports rather than a constant.
This comment used to say that only the master carries a balance and that the RIGHT-hand member reports Available == false. Re-measured on 2026-09-10 with a live pair, that is wrong: BOTH members report the same document, including the same targetBalance, and either one accepts a write that both then report. The pair, not the speaker, owns the value. The desktop app still addresses the master (see groups.balanceSourceBox), because reading and writing one known end of the pair is one behaviour instead of two.
type Bass ¶
type Bass struct {
Target int `json:"target"`
Actual int `json:"actual"`
Min int `json:"min"`
Max int `json:"max"`
Default int `json:"default"`
// Step is the granularity the firmware imposes on a bass write. The
// classic /bass route reports none (0; the app's slider treats that as
// 1). The capability-gated tone-controls route of home-theater systems
// reports it explicitly and it can exceed 1, and a write off the grid is
// the firmware's to refuse, so the slider must honor it.
Step int `json:"step"`
Avail bool `json:"available"`
}
Bass + capabilities combined.
type BoxError ¶
type BoxError struct {
Path string // request path, e.g. "/addGroup"
Status int // HTTP status of the reply
Value string // numeric error code, e.g. "5510"; "" when untyped
Name string // e.g. "GROUP_ALREADY_EXISTS"
Severity string
Detail string // the error element's own text
Body string // raw reply body as read (callers cap it), verbatim
}
BoxError is a >=400 reply from the box, with the firmware's own error envelope parsed out of the body when it carried one.
Value/Name/Severity/Detail hold the first <error> element (the firmware has not been seen sending more than one) and stay empty when the body was not an envelope, so Value == "" means "untyped", never "success". Reach it with errors.As; the webui 5510/5580 helpers are the intended first consumers.
type Client ¶
type Client struct {
Host string // e.g. "127.0.0.1" or the box IP
HTTP *http.Client
// Log receives the client's diagnostics (currently the 2xx error-envelope
// detection). A field rather than a constructor argument so the many
// boxapi.New call sites stay as they are; nil falls back to the process
// default logger.
Log *slog.Logger
}
Client wraps http.Client + box host.
func New ¶
New creates a client with defaults.
The client deliberately carries NO http.Client Timeout: that field caps the whole request regardless of any context deadline, which silently defeated every deliberately longer budget a caller passed (the stereo /addGroup patience of 20 s died at exactly 6 s in every field bundle, 2026-08-18 — the firmware spends ~20 s on a pairing op before it answers). The deadline comes from the caller's context instead; callCtx supplies the old 6 s default for callers that set none, so nothing can hang forever.
func (*Client) AddGroup ¶
func (c *Client) AddGroup(ctx context.Context, name, masterDeviceID string, members []ZoneMember) error
AddGroup creates a real L/R stereo pair (POST /addGroup to the master). name is a display label; masterDeviceID the deviceID of the master; members MUST contain exactly two speakers, each with role "LEFT" or "RIGHT", deviceID and LAN IP. Only the ST10 actually pairs; on other models the firmware responds with an error, which the caller passes through to the app.
func (*Client) AddWirelessProfile ¶
AddWirelessProfile writes SSID/password into NetManager's OWN profile store, the list the firmware picks from at boot before STR's agent is even running.
This is the ONLY channel that can author an entry there. NetworkProfiles.xml keeps its passphrases AES-encrypted, so STR can read that file but can never create an entry in it, and the firmware exposes no per-profile delete at all (/supportedURLs carries addWirelessProfile, getActiveWirelessProfile, performWirelessSiteSurvey, setWiFiRadio and networkInfo, and nothing that removes one). So this call adds; it never replaces.
The body is the shape proven on live hardware by run.sh's M1 approach (usb-stick/run.sh:2515), timeout attribute included: that number is the firmware's own budget for the operation, not the caller's, which comes from ctx.
Known to be refused on the taigan chassis (HTTP 500 regardless of the body, see run.sh's M1 notes), so callers must treat an error as "this chassis cannot", never as fatal.
func (*Client) AddZoneSlave ¶
func (c *Client) AddZoneSlave(ctx context.Context, master ZoneMember, slaves []ZoneMember) error
AddZoneSlave adds slaves to the zone already led by master. The master must already lead a zone (call SetZone first); the firmware rejects an add to a box that is not yet a master.
func (*Client) BrowseMediaServer ¶
func (c *Client) BrowseMediaServer(ctx context.Context, account, location, name string, start, count int) ([]MediaItem, int, error)
BrowseMediaServer lists one container of a registered media server.
An empty location browses the root. To descend, pass an item's Location and Name back.
The request shape is NOT obvious and the firmware rejects every variation. Measured 2026-08-10: the container must be an <item> carrying <name> and <type> BEFORE its <ContentItem>. A bare <ContentItem> as a direct child of <navigate>, or a <mediaItemContainer>, both answer 500 "field not found on 'navigate'". None of this is in Bose's public API document; the shape comes from thlucas1/bosesoundtouchapi plus live probing.
func (*Client) GetActiveWirelessProfile ¶
GetActiveWirelessProfile reads /getActiveWirelessProfile and returns the SSID of the stored Wi-Fi profile ("" if none is set). Caution: a set profile does NOT mean the box is also associated (on BCO/taigan a profile can be persisted without the AP->STA switch ever being completed).
func (*Client) GetBalance ¶
GetBalance reads /balance.
CAUTION: this endpoint does NOT answer while the speaker is in deep standby. It does not refuse, it hangs until the caller gives up (12 s and counting, live). Never put it on a polled path, and always give it its own short budget. Reading it after a wake works normally.
func (*Client) GetGroup ¶
GetGroup reads /getGroup (stereo pair). For an unpaired box the response is empty and an empty Group is returned (no error).
Schema (derived live from the firmware docs, NOT the earlier guessed <groupMember>): the members are <roles><groupRole> with deviceId/role/ ipAddress as child elements, plus <masterDeviceId> at the group level.
func (*Client) GetInfo ¶
GetInfo reads /info and returns the static box description incl. margeAccountUUID (empty = not paired / after factory reset), moduleType/variant (taigan/scm/...) and the LAN IP from the networkInfo block (prefers a real address over "0.0.0.0").
func (*Client) GetNetwork ¶
GetNetwork reads /networkInfo (interface state, IP, profile count).
func (*Client) GetSetupStatus ¶
func (c *Client) GetSetupStatus(ctx context.Context) (SetupStatus, error)
GetSetupStatus reads /setup. On a box in factory state this returns e.g. state="SETUP_AP_OOB" systemstate="SETUP_LANG_NOT_SET".
func (*Client) GetSources ¶
GetSources reads /sources: every input and service slot the box has.
Remember that `status` is a CONNECTION indicator, not a capability. A SoundTouch 10's Bluetooth reports UNAVAILABLE simply because nothing is paired to it, and UPNP reports UNAVAILABLE even while it is the actively playing source. Never diagnose a source as unusable from this field alone.
func (*Client) GetSpeakerLevels ¶
func (c *Client) GetSpeakerLevels(ctx context.Context) (SpeakerLevels, error)
GetSpeakerLevels reads the centre and surround levels. Supported is false and the rest zero on any box that does not advertise the capability, which the caller shows as "this speaker has no separate levels" rather than as an error: not having surrounds is the normal case.
func (*Client) GetSysLanguage ¶
GetSysLanguage reads the speaker's display language as the firmware's own integer (2 = German, 3 = English, and so on; the full table lives in the desktop app's settings view).
It exists so the phone remote can speak the language the OWNER set on the speaker. Until now that page took its language from the browser alone, so a household that had set the speaker to German still got an English remote if the phone was English, with no way to change it (support mail 2026-08-16).
func (*Client) GetVolume ¶
GetVolume reads the box's current volume state (0-100 target/actual plus mute). Lets a caller read the level for a relative adjustment or a status display without loading the full /volume+bass+network settings blob.
func (*Client) GetZone ¶
GetZone reads /getZone and returns the current multiroom zone. For a standalone box the zone is empty (Master == "").
func (*Client) HasDisplay ¶ added in v1.0.4
HasDisplay reports whether the speaker has a screen that shows text, read from the firmware's own /capabilities answer: a box with a display advertises <clockDisplay>true</clockDisplay> (it can show the clock), and one without a screen says false. Measured on FW 27.0.6: a Portable (taigan) answers true, an ST10 (rhino, LEDs only) answers false. ok is false when the box did not give a definite answer, so the caller can fall back to the model name.
func (*Client) Key ¶
Key sends one Bose remote key to the speaker: a press followed by a release, which is what the firmware expects and what the physical remote does.
This is the only way to control playback the speaker runs ITSELF. STR's transport controls drive the UPnP renderer, which is correct while STR pushes the audio and does nothing at all once the box plays a native radio station on its own: that is the box's own player, not the UPnP one.
func (*Client) LeaveSetup ¶
LeaveSetup tells the firmware to leave its out-of-box SETUP source. A box that finished install but never completed Bose's app-driven onboarding keeps the SETUP source active: the display shows "follow the SoundTouch app instructions" and every play is refused, even though the box is otherwise configured and on the network (live: SoundTouch 300 and scm-ST30, 2026-07-09, now_playing source="SETUP"). A single POST /setup state="SETUP_LEAVE" clears it to INVALID_SOURCE, after which UPnP radio plays normally. Verified live on an scm-ST30. Harmless when the box is not in setup (the firmware just echoes the state back), so it is safe to call unconditionally as a repair.
func (*Client) ListMediaServers ¶
func (c *Client) ListMediaServers(ctx context.Context) ([]MediaServer, error)
ListMediaServers reads /listMediaServers: every DLNA/UPnP media server the speaker can currently see. Discovery is the firmware's own, so this works on a box that has never had a music source registered.
func (*Client) ListServiceStations ¶ added in v1.0.13
func (c *Client) ListServiceStations(ctx context.Context, source, account string) ([]ServiceStation, error)
ListServiceStations reads every station of a linked service account (source "PANDORA") from the speaker, paging through /navigate until the firmware's totalItems is reached.
func (*Client) LoadSettings ¶
LoadSettings fetches all relevant states in parallel with one call. On errors from individual endpoints the fields are left empty.
func (*Client) PlayMediaItem ¶
PlayMediaItem points the speaker at one item of a media server. The location is the one BrowseMediaServer returned; the firmware activates it itself, so nothing is streamed through STR.
func (*Client) RegisterMediaServer ¶
func (c *Client) RegisterMediaServer(ctx context.Context, m MediaServer) error
RegisterMediaServer registers a media server as a native STORED_MUSIC source.
POST, not PUT: the firmware answers 405 to a PUT here even though this reads like a write of a single setting.
The box answers 200 immediately, but the source does NOT appear at once. The speaker then calls out to its marge (STR) with an addSource callback, STR answers it and serves the account's source list, and only then does /sources gain the entry as READY. Measured live, that round trip took minutes rather than seconds, so a caller must not treat "not READY yet" as failure.
func (*Client) RegisteredMediaServerAccounts ¶
RegisteredMediaServerAccounts returns the sourceAccount of every STORED_MUSIC entry currently in /sources, whatever its status.
Status is deliberately NOT filtered on: `status` in /sources is a connection indicator rather than a capability, and treating UNAVAILABLE as "not registered" would make a caller re-register a source that is already there.
func (*Client) RemoveGroup ¶
RemoveGroup dissolves the stereo pair of this box. The firmware documents this as a GET (response is the now-empty Group); directed at the master.
func (*Client) RemoveMusicServiceAccount ¶ added in v1.0.3
func (c *Client) RemoveMusicServiceAccount(ctx context.Context, source, displayName, user string) error
RemoveMusicServiceAccount removes such an account again (empty password).
func (*Client) RemoveZoneSlave ¶
func (c *Client) RemoveZoneSlave(ctx context.Context, master ZoneMember, slaves []ZoneMember) error
RemoveZoneSlave drops slaves from the zone led by master. Removing the last remaining slave dissolves the zone; re-form it with SetZone.
func (*Client) SelectContentItem ¶ added in v1.0.13
SelectContentItem hands a ContentItem to the speaker's /select. The speaker can answer 200 with an error document, so the body decides, not only the status.
func (*Client) SetBass ¶
SetBass sets the bass value (range from bassCapabilities — typical ST10 range -9..0).
On a box whose /capabilities probe advertised audioproducttonecontrols (see toneControlsBass) the value goes through that route's POST instead; omitting <treble> there leaves the treble untouched, which is that route's partial-update contract. The verdict comes from the per-host cache because this Client is constructed fresh for every webui request and must not probe on the hot slider path; a cache miss means the classic route, exactly today's behavior.
func (*Client) SetMusicServiceAccount ¶ added in v1.0.3
func (c *Client) SetMusicServiceAccount(ctx context.Context, source, displayName, user, pass string) error
SetMusicServiceAccount adds a music-service account through the speaker's local API, e.g. PANDORA: the firmware's own client logs in with it and then registers the source with its cloud (STR's stand-in).
func (*Client) SetName ¶
SetName changes the display name of the box. Caution: Bose also resets the margeURL in the box state to the default update server in the process. Our autoPair re-attaches it on the next tick.
func (*Client) SetSpeakerLevel ¶
SetSpeakerLevel writes one level. which is LevelFrontCenter or LevelRearSurrounds.
Only the named element is sent: the route's documented partial-update contract is that a level left out of the POST is not changed, which is the same contract SetBass relies on for treble. Writing both every time would make a stale cached value in the caller overwrite a change made meanwhile on the Bose remote.
func (*Client) SetZone ¶
func (c *Client) SetZone(ctx context.Context, master ZoneMember, slaves []ZoneMember) error
SetZone creates (or replaces) the multiroom zone led by master with the given slaves. POST it to the master box; any existing zone on the master is replaced. The master must be actively playing a source for the slaves to produce sound (see #70 design notes), so callers should start playback on the master first.
func (*Client) SiteSurvey ¶
SiteSurvey kicks the box radio into a ~5 s scan via /performWirelessSiteSurvey and returns the SSIDs the box can actually SEE. A network the box's own radio cannot pick up never appears here. STR uses this as a pre-flight before a Wi-Fi change so it never strands the box on a network it cannot join (the box would otherwise leave its current network and fail to join the new one, forcing a Bose-app re-pair).
func (*Client) Standby ¶
Standby puts the speaker into Bose standby, the real power-off rather than a transport stop.
GET, not POST: the firmware answers /standby with 400 on a POST. That is the same call the webui's power-off makes, lifted here so it can also be aimed at ANOTHER speaker, which is what a sleep timer covering a whole group needs.
Idempotent from the caller's side: a speaker already in standby answers 200 and stays where it is. The caller still has to check first, because on some chassis a request into a sleeping box is what wakes it.
func (*Client) UnregisterMediaServer ¶
func (c *Client) UnregisterMediaServer(ctx context.Context, m MediaServer) error
UnregisterMediaServer removes the STORED_MUSIC account again. The display name must match the one the source was registered under, which is why callers pass the source's own display name back rather than a fresh guess.
type Group ¶
type Group struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
MasterDeviceID string `json:"masterDeviceID,omitempty"`
Members []ZoneMember `json:"members"`
// Status is the firmware's own verdict on the pair, e.g. GROUP_OK or
// GROUP_ERROR. A pair whose other half let go keeps the document on the
// half that still holds it, with GROUP_ERROR here (measured 2026-08-10 on
// two SoundTouch 10s). That half refuses every source activation, so this
// field is what lets the app and the play path name the real reason.
Status string `json:"status,omitempty"`
}
Group is the state of the stereo-pair group concept (two ST10 as an L/R pair). For a single, unpaired box the firmware returns an empty body (live verified: NOT `<group />`); getXML catches that and then returns an empty Group without error.
Only the SoundTouch 10 supports real stereo pairs. All models list /addGroup in /supportedURLs (live verified on taigan), so that does NOT work as a gate; the box firmware is the final authority.
type Info ¶
type Info struct {
DeviceID string `json:"deviceID"`
Name string `json:"name"`
Type string `json:"type"`
Version string `json:"version"`
Variant string `json:"variant"`
ModuleType string `json:"moduleType"`
MargeAccountUUID string `json:"margeAccountUUID"`
// MargeURL is the cloud host the firmware ITSELF says it is using for the
// marge account/preset service. STR redirects only the stock hostname to
// its own listeners via /etc/hosts, so a box reporting anything else here
// can never reach STR's stub: #986's ST30 reported an OpenCloudTouch host
// and answered 1036 for every preset for three days while looking healthy
// everywhere else.
MargeURL string `json:"margeURL,omitempty"`
IP string `json:"ipAddress"`
CountryCode string `json:"countryCode"`
}
Info contains the static description of the box.
type MediaItem ¶
type MediaItem struct {
Name string `json:"name"`
// Type is the firmware's own word: "dir" for a container, "track" for
// something playable. Anything else is passed through untouched.
Type string `json:"type"`
// Location addresses the item inside the server ("4:cont1:20:0:0:" for a
// folder, "5:audio5:part11:11:5 TRACK" for a track). Opaque: build nothing
// from it, hand it back as-is.
Location string `json:"location"`
Playable bool `json:"playable"`
}
MediaItem is one entry from a media server: a folder to descend into or a track to play.
type MediaServer ¶
type MediaServer struct {
// ID is the server's UPnP UDN WITHOUT the "uuid:" prefix, exactly as the
// box reports it. It is what the source account is built from, and it must
// match case-sensitively.
ID string `json:"id"`
IP string `json:"ip"`
Manufacturer string `json:"manufacturer"`
ModelName string `json:"modelName"`
FriendlyName string `json:"friendlyName"`
// Location is the device-description URL the firmware's own discovery
// recorded (e.g. "http://192.0.2.131:9000/dev0/desc.xml"). It is the
// address a server that never answers M-SEARCH (Twonky, #733) can still be
// described at directly: the firmware keeps hearing its NOTIFY
// announcements, so this URL stays fresh while STR's own probes see
// nothing.
Location string `json:"location"`
// Registered is filled in by callers that also read /sources; the box's own
// media-server list says nothing about whether a server is usable yet.
Registered bool `json:"registered"`
}
MediaServer is one DLNA/UPnP server the speaker has discovered.
func (MediaServer) SourceAccount ¶
func (m MediaServer) SourceAccount() string
SourceAccount is the sourceAccount value that identifies this server as a music source. The trailing "/0" selects the server's first (and, on every server measured, only) account.
type Network ¶
type Network struct {
WifiProfileCount int `json:"wifiProfileCount"`
Interfaces []NetworkInterface `json:"interfaces"`
}
Network is the active Wi-Fi state.
type NetworkInterface ¶
type NetworkInterface struct {
Type string `json:"type"`
Name string `json:"name"`
MAC string `json:"macAddress"`
IP string `json:"ipAddress"`
SSID string `json:"ssid"`
Frequency int `json:"frequencyKHz"`
State string `json:"state"`
Signal string `json:"signal"`
Mode string `json:"mode"`
}
NetworkInterface is a Wi-Fi adapter.
type ServiceStation ¶ added in v1.0.13
type ServiceStation struct {
Name string `json:"name"`
Source string `json:"source"`
SourceAccount string `json:"-"`
Location string `json:"location"`
Type string `json:"type"`
ItemName string `json:"itemName,omitempty"`
ContainerArt string `json:"art,omitempty"`
Presetable bool `json:"presetable"`
// ContentItem is the station's own <ContentItem> element as the speaker
// sent it: every attribute and the inner XML unchanged. /select takes it
// back as it is, which is the form confirmed on hardware.
ContentItem string `json:"-"`
}
ServiceStation is one station from a service's station list.
type Settings ¶
type Settings struct {
Info Info `json:"info"`
Volume Volume `json:"volume"`
Bass Bass `json:"bass"`
Network Network `json:"network"`
Sources []Source `json:"sources"`
}
Settings is the combined response for the box settings tab.
type SetupStatus ¶
SetupStatus is the response from /setup. State is e.g. "SETUP_AP_OOB" (box opens the Bose setup AP) or "SETUP_INACTIVE" (setup finished); SystemState e.g. "SETUP_LANG_NOT_SET" / "SETUP_LANG_SET".
type Source ¶
type Source struct {
Source string `json:"source"`
SourceAccount string `json:"sourceAccount"`
Status string `json:"status"`
IsLocal bool `json:"isLocal"`
Multiroom bool `json:"multiroomallowed"`
DisplayName string `json:"displayName"`
}
Source is an entry from /sources (Spotify, AirPlay etc).
type SpeakerLevel ¶
type SpeakerLevel struct {
Value int `json:"value"`
Min int `json:"min"`
Max int `json:"max"`
Step int `json:"step"`
Avail bool `json:"available"`
}
SpeakerLevel is one adjustable level with the range the firmware imposes. Avail is false when the system does not offer that particular speaker, which happens independently per level: a bar with surrounds but no separate centre reports only the rear one.
type SpeakerLevels ¶
type SpeakerLevels struct {
Supported bool `json:"supported"`
FrontCenter SpeakerLevel `json:"frontCenter"`
RearSurrounds SpeakerLevel `json:"rearSurrounds"`
}
SpeakerLevels is the whole /audioproductlevelcontrols state. Supported is false when the box does not advertise the capability at all, which is every speaker that is not a home theater system.
type Volume ¶
type Volume struct {
Target int `json:"target"`
Actual int `json:"actual"`
Muted bool `json:"muted"`
}
Volume is the current volume state.
type Zone ¶
type Zone struct {
Master string `json:"master,omitempty"`
SenderIP string `json:"senderIP,omitempty"`
Members []ZoneMember `json:"members"`
}
Zone is the state of the classic SoundTouch multiroom group. Master is the deviceID of the box that broadcasts the stream; Members are all boxes that follow the stream. SenderIP is the LAN IP of the master. For a standalone box the firmware returns an empty `<zone />` element — then Master and Members are empty.
type ZoneMember ¶
type ZoneMember struct {
DeviceID string `json:"deviceID"`
IP string `json:"ip"`
Role string `json:"role,omitempty"`
}
ZoneMember describes a slave in a SoundTouch zone. The Bose firmware returns per member the deviceID as element text and the LAN IP as an attribute.