mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 08:36:13 +00:00
feat(setup): align <PairDeviceWithAccount> with the official Bose app shape
The Stockholm app (stockholm/setup/js/workflow_add_devices.js:23,77) and Zimbo88's OpenCloudTouch USB-less script (https://github.com/scheilch/opencloudtouch/discussions/201) both send <boseServer>, <updateServer>, and <accountEmail> alongside the <accountId>/<userAuthToken> pair. AfterTouch's setMargeAccount historically sent only the latter two. Adds: - MargePairingExtras struct on SessionConfig, opt-in via BoseServer (UpdateServer + AccountEmail default-derived when empty). - DefaultMargeAuthToken constant ("Bearer AfterTouch") and DefaultMargePairingEmail constant ("local@aftertouch.invalid", RFC 2606 reserved .invalid TLD). - buildPairDeviceWithAccountXML helper extracted so tests can pin both the minimal-payload and extended-payload shapes without driving a full WebSocket session. - --token flag on `soundtouch-cli setup pair` so we can override the placeholder for token-shape experiments. - runPairBare threads --service-url through to PairingExtras so `--mode=bare --service-url=...` ships the extended payload too; runPairFull already used it via applyInitPlanDefaults. The speaker accepts any non-empty Bearer string (verified during #195 investigation: "Bearer AfterTouch" passes and the speaker re-derives its post-pair state from the marge endpoints regardless of token content). The Stockholm-app payload shape is purely documentation alignment; it did NOT fix the post-pair AUX/preset breakage that turned out to be the cloud /full source list (see the preceding marge commit). Keeping the wiring so the switches are ready when we want to experiment further. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
332c7b87d0
commit
74007c7cb2
@@ -1353,10 +1353,11 @@ func setupPairCmd() *cli.Command {
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{Name: "account", Usage: "7-digit account ID (empty = generate)"},
|
||||
&cli.StringFlag{Name: "mode", Value: "full", Usage: "full (state machine) or bare (setMargeAccount only — experimental)"},
|
||||
&cli.StringFlag{Name: "service-url", Value: "http://aftertouch.local:8000", Usage: "AfterTouch base URL (used by mode=full for defaults)"},
|
||||
&cli.StringFlag{Name: "service-url", Value: "http://aftertouch.local:8000", Usage: "AfterTouch base URL (also populates <boseServer>/<updateServer> in setMargeAccount)"},
|
||||
&cli.StringFlag{Name: "name", Usage: "Speaker name to set during pairing (empty = keep current)"},
|
||||
&cli.IntFlag{Name: "language", Value: setup.LanguageEnglish, Usage: "sysLanguage code (2 = English)"},
|
||||
&cli.DurationFlag{Name: "step-timeout", Value: 8 * time.Second},
|
||||
&cli.StringFlag{Name: "token", Usage: "userAuthToken value (empty = use built-in placeholder matching the Bose app token shape)"},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
cfg := GetClientConfig(c)
|
||||
@@ -1401,8 +1402,20 @@ func runPairBare(c *cli.Context, deviceIP, accountID string) error {
|
||||
fmt.Printf("pre /info deviceID=%s margeAccountUUID=%q margeURL=%q\n",
|
||||
info.DeviceID, info.MargeAccountUUID, info.MargeURL)
|
||||
|
||||
// Service URL drives the extended <PairDeviceWithAccount> payload
|
||||
// (boseServer/updateServer/accountEmail). When empty, the session
|
||||
// falls back to the minimal historical shape (accountId +
|
||||
// userAuthToken only).
|
||||
serviceURL := c.String("service-url")
|
||||
|
||||
var extras setup.MargePairingExtras
|
||||
if serviceURL != "" {
|
||||
extras = setup.MargePairingExtras{BoseServer: serviceURL}
|
||||
}
|
||||
|
||||
session, err := setup.DialSession(deviceIP, info.DeviceID, setup.SessionConfig{
|
||||
StepTimeout: c.Duration("step-timeout"),
|
||||
StepTimeout: c.Duration("step-timeout"),
|
||||
PairingExtras: extras,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("dial WS: %w", err)
|
||||
@@ -1413,9 +1426,13 @@ func runPairBare(c *cli.Context, deviceIP, accountID string) error {
|
||||
ctx, cancel := context.WithTimeout(c.Context, c.Duration("step-timeout")+2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
fmt.Printf("→ setMargeAccount accountID=%s (no SETUP bracket)\n", accountID)
|
||||
if serviceURL != "" {
|
||||
fmt.Printf("→ setMargeAccount accountID=%s (extended: boseServer=%s)\n", accountID, serviceURL)
|
||||
} else {
|
||||
fmt.Printf("→ setMargeAccount accountID=%s (minimal payload, no SETUP bracket)\n", accountID)
|
||||
}
|
||||
|
||||
if pairErr := session.SetMargeAccount(ctx, accountID, ""); pairErr != nil {
|
||||
if pairErr := session.SetMargeAccount(ctx, accountID, c.String("token")); pairErr != nil {
|
||||
PrintError(fmt.Sprintf("setMargeAccount: %v", pairErr))
|
||||
return pairErr
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user