mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 16:46:17 +00:00
feat(setup): add CLI setup command group for end-to-end speaker provisioning
Add `soundtouch-cli setup` subcommand group covering the full reset →
re-provision → pair lifecycle as a scriptable alternative to the web UI:
inspect, verify, plan, factory-reset, wait-ap, wifi-push, wait-online,
ssh-check, install-ca, migrate, reboot, pair (bare | full state machine)
Supporting library code lives in pkg/service/setup: factory_reset.go,
wifi_provision.go, inspect.go, init_plan.go, setup_session.go.
Confirmed against ST10 firmware 27.0.6 that bare setMargeAccount over
WebSocket — no SETUP_START/SETUP_ENTER/SETUP_LEAVE bracket — is
sufficient to pair a factory-reset speaker; the firmware materializes
SystemConfigurationDB.xml and Sources.xml itself and the pairing
survives reboot. Result and field-by-field SystemConfigurationDB
comparison documented in docs/analysis/SETUP-WEBSOCKET-EXPERIMENT.md.
Captures the device's pre-reset DELETE-to-marge plus its LAN peer
notification flow in docs/analysis/FACTORY-RESET-PROTOCOL.md.
Perf: batch GetMigrationSummary's SSH probes into one Run() call via
ssh_probe.go / ssh_probe_apply.go — was ~8 sequential dials at
500-1000 ms each on FW 27 crypto, now one round-trip. Same data shape,
same MigrationSummary fields populated.
Fixes /clockTime and /clockDisplay wire formats — firmware 27 rejects
the legacy flat XML ("Error parsing request"). ClockTimeRequest now
uses utcTime attribute; ClockDisplayRequest emits the nested
<clockConfig> envelope with timezoneInfo/timeFormat/brightnessLevel.
Removes cmd/example-init-speaker (superseded by setup pair).
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
1ab4295653
commit
29a462da2b
@@ -63,6 +63,8 @@
|
||||
* [Device Redirect Methods](analysis/DEVICE-REDIRECT-METHODS.md)
|
||||
* [Telnet (Port 17000) Migration Method](analysis/TELNET-MIGRATION-METHOD.md)
|
||||
* [Telnet Command Reference](analysis/TELNET-COMMAND-REFERENCE.md)
|
||||
* [Setup WebSocket Experiment](analysis/SETUP-WEBSOCKET-EXPERIMENT.md)
|
||||
* [Factory Reset Protocol](analysis/FACTORY-RESET-PROTOCOL.md)
|
||||
* [Wiki API Comparison](analysis/WIKI-COMPARISON.md)
|
||||
* [IoT Config Summary](analysis/IOT-CONFIG-SUMMARY.md)
|
||||
* [IoT Configuration Analysis](analysis/IOT-CONFIGURATION-ANALYSIS.md)
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
# What a SoundTouch speaker does during factory reset
|
||||
|
||||
Observed live on ST10 firmware `27.0.6.46330.5043500` (build `epdbuild.trunk.hepdswbld04.2022-08-04`) on 2026-05-12, by running `soundtouch-cli setup factory-reset` and tailing the speaker's `logread` over SSH. The trace is preserved at `_/logs/factory-reset.txt` for reference.
|
||||
|
||||
## Sequence
|
||||
|
||||
1. **Telnet receives `sys factorydefault`.** The diagnostic shell on port 17000 accepts the command and acknowledges. Some firmwares close the socket as part of the reboot — our CLI's `setup factory-reset` tolerates that as success.
|
||||
|
||||
2. **Speaker DELETEs itself from its marge account.** Before wiping anything, the firmware does:
|
||||
```
|
||||
[MargeStateAssociated] HandleRemoveDeviceRequest - Removing this device from the user's Marge account
|
||||
[MargeClient] RemoveDevice calling Marge Server with https://streaming.bose.com/streaming/account/{accountId}/device/{deviceId}
|
||||
[MargeClient] RemoveDeviceCB - Device removed from the user's Marge account
|
||||
[MargeStateAssociated] HandleRemoveDeviceRequestSuccessCB, Marge returned: {"ok": true}
|
||||
```
|
||||
AfterTouch already handles this — `HandleMargeRemoveDevice` (`pkg/service/handlers/handlers_marge.go:633`) routed via `r.Delete("/device/{device}", …)` in `cmd/soundtouch-service/main.go:955`. The handler calls `marge.RemoveDeviceFromAccount(s.ds, account, device)` and prunes the device from the datastore.
|
||||
|
||||
3. **Speaker notifies its LAN peers.** Two HTTP POSTs to each known peer at `:8090/notification`:
|
||||
```
|
||||
[NotificationSender] SendNotifyLisas_: URL: >>http://192.168.123.122:8090/notification<<, m_msgdata.size(58)
|
||||
[SimpleURLFetcher] multipart/form-data text/xml
|
||||
```
|
||||
~58 bytes of `multipart/form-data` carrying `text/xml`. "Lisas" is the firmware's internal term for LAN peers (devices on the same account on the same network segment). AfterTouch is **not** on this path — it's pure peer-to-peer over the LAN. Peers presumably refresh their account info as a result.
|
||||
|
||||
4. **Local state teardown.** Bluetooth pairings cleared (`BTRemoteDeviceAccess::ClearPrevPairedList`), zone/group state torn down, all source proxies disconnected (`STSAccountProxy::Disconnect Requested` × many).
|
||||
|
||||
5. **Persistence cleanup.** Logs, core dumps wiped (`FactoryDefault: Clearing the CoreDump and BoseLogs … rm -rf /mnt/nv/BoseLog/*`). Notably **NOT wiped**: `/mnt/nv/aftertouch.resolv.conf`, `/mnt/nv/rc.local`'s Aftertouch hook, and `/mnt/nv/BoseApp-Persistence/1/SystemConfigurationDB.xml`. The reset only touches log directories and account-specific persistence under the same `/mnt/nv/BoseApp-Persistence/1/` tree.
|
||||
|
||||
6. **Reboot into setup mode.** Speaker drops Wi-Fi, comes back as its own AP `Bose SoundTouch XXXX` on 192.0.2.1.
|
||||
|
||||
## Implications for migration ordering
|
||||
|
||||
The DELETE in step 2 only reaches AfterTouch if the speaker's `margeURL` already points at AfterTouch *at the moment of reset*. A speaker still pointing at `streaming.bose.com` sends it into the void → AfterTouch keeps a stale `account/{id}/device/{id}` entry until someone manually prunes it.
|
||||
|
||||
Therefore for a clean datastore lifecycle on an already-Bose-paired speaker:
|
||||
|
||||
1. Migrate URLs first (`setup migrate --method=resolv` or `--method=telnet`).
|
||||
2. Reboot to apply.
|
||||
3. Factory reset.
|
||||
4. Re-provision.
|
||||
|
||||
`soundtouch-cli setup plan --reset` currently runs factory-reset first (optimal for already-on-AfterTouch speakers); both `setup plan --reset` and `setup factory-reset` print a one-line note explaining the ordering tradeoff so users can pick the right sequence for their starting state.
|
||||
|
||||
## Implications for AfterTouch behaviour
|
||||
|
||||
- The DELETE handler is already correct; no changes needed.
|
||||
- AfterTouch is invisible to the LAN-peer notification step — that's just LAN HTTP between speakers.
|
||||
- If you build a "consolidate account" / "migrate fleet" feature later, the peer-notification channel is the propagation path the firmware uses internally; AfterTouch doesn't need to do anything analogous.
|
||||
- The persistence layer at `/mnt/nv/` is **factory-reset-resistant**. Our DNS-redirect migration (`setup migrate --method=resolv`) writes there specifically so AfterTouch routing survives a reset. This is intentional — the user can factory-reset a speaker freely without re-running migration.
|
||||
|
||||
## Open questions
|
||||
|
||||
- Are there other peer endpoints the firmware POSTs to besides `:8090/notification`? Worth checking on a 3-speaker LAN.
|
||||
- Does the `:8090/notification` payload format match the format used for play-as-notification audio pushes, or is it a distinct message shape? The "size(58)" byte count is too small for an audio URL but big enough for an XML envelope with an event type.
|
||||
|
||||
If you want either of these answered, capture two synchronised `logread -f` streams from two LAN speakers while one is being reset.
|
||||
|
||||
## Runbook — reset & re-provision an ST10 on AfterTouch
|
||||
|
||||
End-to-end command sequence used during the 2026-05-12 bare-pairing experiment, recorded verbatim from the test session. Replace IPs, SSID, password, service URL, and account ID with your own. Two manual Wi-Fi switches happen between `factory-reset` and `wifi-push` (host joins the speaker's AP) and again between `wifi-push` and `wait-online` (host re-joins home Wi-Fi).
|
||||
|
||||
```bash
|
||||
# === 1. Reconnaissance — confirm what state the speaker is in before touching it. ===
|
||||
|
||||
# Identity, network, sources, presets.
|
||||
go run ./cmd/soundtouch-cli --host 192.168.123.123 setup inspect
|
||||
|
||||
# Green/red status across every migration axis (SSH, telnet, CA, pairing, …).
|
||||
go run ./cmd/soundtouch-cli --host 192.168.123.123 setup verify \
|
||||
--service-url=https://soundtouch.fritz.box
|
||||
|
||||
# What `setup plan --reset` would recommend, so you can preview the sequence.
|
||||
go run ./cmd/soundtouch-cli --host 192.168.123.123 setup plan \
|
||||
--service-url=https://soundtouch.fritz.box --reset
|
||||
|
||||
|
||||
# === 2. Reset and Wi-Fi re-provisioning. ===
|
||||
|
||||
# Tell the speaker to wipe itself. Speaker drops Wi-Fi and reboots into AP mode.
|
||||
go run ./cmd/soundtouch-cli --host 192.168.123.123 setup factory-reset
|
||||
|
||||
# Manual: switch this host to the speaker's setup AP.
|
||||
# macOS: networksetup -setairportnetwork en0 "Bose SoundTouch XXXX"
|
||||
|
||||
# Poll 192.0.2.1:8090/info until the speaker answers (interval=2s, timeout=5m).
|
||||
go run ./cmd/soundtouch-cli --host 192.168.123.123 setup wait-ap
|
||||
|
||||
# Push home Wi-Fi credentials. NOTE the single-quoted password: zsh expands `!`
|
||||
# inside double quotes as history-expansion and will refuse the command.
|
||||
go run ./cmd/soundtouch-cli --host 192.168.123.123 setup wifi-push \
|
||||
--ssid="wifi-name" --pass='a.secure!password'
|
||||
|
||||
# Manual: switch host back to home Wi-Fi.
|
||||
# macOS: networksetup -setairportnetwork en0 "wifi-name" 'a.secure!password'
|
||||
|
||||
# mDNS-poll for the speaker on the home network, matched by deviceID suffix
|
||||
# (which survives the reset since it's the MAC). Returns the new IP.
|
||||
go run ./cmd/soundtouch-cli --host 192.168.123.123 setup wait-online --match=536A98
|
||||
|
||||
|
||||
# === 3. Clock, migrate, pair. From here on use the new IP wait-online reported. ===
|
||||
|
||||
# Set the speaker's wall-clock. `clock set --time=now` fails on FW 27;
|
||||
# `clock now` is the working subcommand.
|
||||
go run ./cmd/soundtouch-cli --host 192.168.123.123 clock now
|
||||
|
||||
# Reboot to clear any half-initialized resolver / NTP state from the wifi-push flap.
|
||||
go run ./cmd/soundtouch-cli --host 192.168.123.123 setup reboot
|
||||
|
||||
# Apply DNS-redirect migration: routes *.bose.com to AfterTouch and installs its CA.
|
||||
# Idempotent; safe to re-run.
|
||||
go run ./cmd/soundtouch-cli --host 192.168.123.123 setup migrate \
|
||||
--service-url=https://soundtouch.fritz.box --method=resolv
|
||||
|
||||
# Reboot again so the envswitch parallel-persistence layer and the resolv hook
|
||||
# both take effect on the next boot.
|
||||
go run ./cmd/soundtouch-cli --host 192.168.123.123 setup reboot
|
||||
|
||||
# Pair the device with an AfterTouch account — bare experiment variant.
|
||||
# Drop --mode=bare and add --name=… / --language=… for the full state-machine variant.
|
||||
go run ./cmd/soundtouch-cli --host 192.168.123.123 setup pair \
|
||||
--mode=bare --account=1111111 --service-url='https://soundtouch.fritz.box'
|
||||
|
||||
|
||||
# === 4. Verify. ===
|
||||
|
||||
# Reboot to verify persistence survives.
|
||||
go run ./cmd/soundtouch-cli --host 192.168.123.123 setup reboot
|
||||
|
||||
# Snapshot the result. margeAccountUUID should still equal --account, and Sources
|
||||
# should list ~14 entries (TUNEIN, RADIO_BROWSER, LOCAL_INTERNET_RADIO,
|
||||
# SPOTIFY slots, AIRPLAY, etc.) materialized by the firmware.
|
||||
go run ./cmd/soundtouch-cli --host 192.168.123.123 setup inspect
|
||||
```
|
||||
|
||||
Total wall-clock for the above on this hardware: roughly 5 minutes including the two manual Wi-Fi switches and three reboots.
|
||||
@@ -0,0 +1,215 @@
|
||||
# Experiment: Does bare `setMargeAccount` work outside the SETUP bracket?
|
||||
|
||||
## Why we are doing this
|
||||
|
||||
Our captured pairing flow (`docs/reference/DEVICE-PAIRING-FLOW.md`) shows the official Bose app always sends `setMargeAccount` *inside* a `SETUP_START` → `SETUP_ENTER` → `SETUP_LEAVE` state-machine bracket over WebSocket. The question this experiment answers:
|
||||
|
||||
> If we open a WebSocket to a factory-reset speaker and send **only** `setMargeAccount` — no surrounding setupState messages — does the device honor it and write its persistence files (`SystemConfigurationDB.xml`, `Sources.xml`) cleanly?
|
||||
|
||||
The answer determines the shape of `PairAccount`:
|
||||
|
||||
- **If YES:** `PairAccount` becomes uniform: WebSocket-first, HTTP `/setMargeAccount` second, telnet `envswitch accountid set` third. One function, one ordering, all callers.
|
||||
- **If NO:** WebSocket pairing is only meaningful inside the full state machine. Factory-reset path uses the state machine; re-pair path keeps today's HTTP→telnet ordering.
|
||||
|
||||
## Preconditions
|
||||
|
||||
- A SoundTouch speaker that has been **factory-reset** and joined to the test Wi-Fi.
|
||||
- Speaker reachable on `:8090` (HTTP API) and `:8080` (WebSocket).
|
||||
- Speaker's runtime marge URL already points at AfterTouch (run the existing telnet URL rewrite first — otherwise the device's downstream POST will land on the dead Bose cloud and we will not be able to distinguish "WS message refused" from "downstream cloud failed").
|
||||
- A free 7-digit account ID — for example, generated via `setup.GenerateAccountID(nil)`.
|
||||
|
||||
## Step 0 — Baseline
|
||||
|
||||
```bash
|
||||
DEVICE=192.168.x.x
|
||||
curl -s http://$DEVICE:8090/info | xmllint --format -
|
||||
curl -s http://$DEVICE:8090/sources | xmllint --format -
|
||||
curl -s http://$DEVICE:8090/presets | xmllint --format -
|
||||
```
|
||||
|
||||
Record:
|
||||
|
||||
- `<margeAccountUUID>` — expect empty on a factory-reset device.
|
||||
- `<margeURL>` — expect the AfterTouch URL (preflight already applied).
|
||||
- `<sources>` — expect a minimal list.
|
||||
- `<presets>` — expect `<presets/>`.
|
||||
|
||||
## Step 1 — Send bare `setMargeAccount` over WebSocket
|
||||
|
||||
Build the CLI once:
|
||||
|
||||
```bash
|
||||
make build
|
||||
```
|
||||
|
||||
Then run the bare path against the speaker:
|
||||
|
||||
```bash
|
||||
DEVICE=192.168.x.x
|
||||
./build/soundtouch-cli setup pair --host=$DEVICE --account=1234567 --mode=bare
|
||||
```
|
||||
|
||||
What it does:
|
||||
|
||||
1. Reads `/info` to discover `deviceID`, logs the pre-state.
|
||||
2. Opens a WebSocket to `$DEVICE:8080` with the `gabbo` subprotocol.
|
||||
3. Sends exactly one frame — the `setMargeAccount` envelope — **without** any preceding `SETUP_START`/`SETUP_ENTER`.
|
||||
4. Reads frames for up to `--step-timeout=8s` (configurable), looking for an ack referencing our `requestID`.
|
||||
5. Closes the WebSocket, waits 2 s, re-reads `/info`, prints whether `margeAccountUUID` now equals our supplied ID.
|
||||
|
||||
The exact frame sent (built by `setup.SetupSession.SetMargeAccount`):
|
||||
|
||||
```xml
|
||||
<msg><header deviceID="DEVICE_ID" url="setMargeAccount" method="POST"><request requestID="1"/></header><body>
|
||||
<PairDeviceWithAccount>
|
||||
<accountId>1234567</accountId>
|
||||
<userAuthToken>Bearer aftertouch</userAuthToken>
|
||||
</PairDeviceWithAccount>
|
||||
</body></msg>
|
||||
```
|
||||
|
||||
Outcomes the CLI will surface:
|
||||
|
||||
- `Device accepted bare pairing.` (post-`/info` shows our ID) → **bare path works**.
|
||||
- `setMargeAccount: device rejected setMargeAccount: …` → device returned an `<error>` body → **bare path refused explicitly**.
|
||||
- `setMargeAccount: await ack for setMargeAccount: …` (timeout or EOF) → **bare path refused silently**.
|
||||
- `Device did NOT persist the pairing — bare path likely refused silently.` → ack received but persistence didn't follow.
|
||||
|
||||
## Step 2 — Record outcome
|
||||
|
||||
After step 1 (regardless of which branch happened):
|
||||
|
||||
```bash
|
||||
sleep 2
|
||||
curl -s http://$DEVICE:8090/info | grep margeAccountUUID
|
||||
```
|
||||
|
||||
| Observed result | Verdict |
|
||||
|------------------------------------------------------------------------------|-----------------------------|
|
||||
| `<margeAccountUUID>1234567</margeAccountUUID>` appears | **YES** — Option 1 wins |
|
||||
| `<margeAccountUUID></margeAccountUUID>` still empty, no error frame received | Refused silently → **NO** |
|
||||
| Error frame returned (e.g. `<error name="UNSUPPORTED_STATE"/>`) | Refused explicitly → **NO** |
|
||||
| Device drops the WebSocket connection without replying | Refused → **NO** |
|
||||
|
||||
If verdict is YES, also verify the device wrote persistence cleanly. Reboot the device, then:
|
||||
|
||||
```bash
|
||||
ssh root@$DEVICE 'cat /mnt/nv/BoseApp-Persistence/1/SystemConfigurationDB.xml'
|
||||
ssh root@$DEVICE 'cat /mnt/nv/BoseApp-Persistence/1/Sources.xml'
|
||||
curl -s http://$DEVICE:8090/info | grep margeAccountUUID
|
||||
```
|
||||
|
||||
The UUID must still be present after reboot, and `SystemConfigurationDB.xml` must contain `<AccountUUID>1234567</AccountUUID>`. If it survives reboot, **YES** is confirmed.
|
||||
|
||||
## Step 3 — Control: full state machine
|
||||
|
||||
Factory-reset the same speaker again and run the full state machine — the same CLI, `--mode=full`:
|
||||
|
||||
```bash
|
||||
./build/soundtouch-cli setup pair --host=$DEVICE --account=1234567 --mode=full
|
||||
```
|
||||
|
||||
This drives `setup.Manager.ExecuteInitPlan` with `SkipURLRewrite=true`, which runs:
|
||||
|
||||
```
|
||||
SETUP_START
|
||||
SETUP_IDENTIFY_DEVICE_ENTER
|
||||
language sysLanguage=2
|
||||
SETUP_ENTER
|
||||
SETUP_IDENTIFY_DEVICE_LEAVE
|
||||
setMargeAccount …
|
||||
SETUP_LEAVE
|
||||
pushCustomerSupportInfoToMarge
|
||||
```
|
||||
|
||||
The CLI logs every step with status. Confirm `/info`, persistence, and reboot-survival checks pass. If the bare path failed but the full path succeeds, the SETUP bracket is load-bearing — a follow-up bisect (e.g. `SETUP_START + setMargeAccount + SETUP_LEAVE` only) tells us *which* surrounding messages the firmware actually requires.
|
||||
|
||||
## Full reset-and-rebuild loop
|
||||
|
||||
Once the bare/full question is decided, the loop for repeated experiments is:
|
||||
|
||||
```bash
|
||||
# 0. Speaker is currently on home Wi-Fi at $DEVICE.
|
||||
# Capture deviceID-suffix + current SSID first so wait-online and
|
||||
# wifi-push have the right inputs.
|
||||
./build/soundtouch-cli setup inspect --host=$DEVICE
|
||||
./build/soundtouch-cli setup factory-reset --host=$DEVICE
|
||||
|
||||
# 1. Manually switch this host to the speaker's AP (Bose SoundTouch XXXX).
|
||||
# macOS: networksetup -setairportnetwork en0 "Bose SoundTouch XXXX"
|
||||
|
||||
./build/soundtouch-cli setup wait-ap
|
||||
./build/soundtouch-cli setup wifi-push --ssid="$HOME_SSID" --pass="$HOME_PASS"
|
||||
|
||||
# 2. Manually switch this host back to home Wi-Fi.
|
||||
|
||||
./build/soundtouch-cli setup wait-online --match=DE4803 # deviceID suffix from /info before reset
|
||||
# (note the new IP from the "Speaker discovered" line)
|
||||
|
||||
NEW_IP=192.168.x.y
|
||||
./build/soundtouch-cli setup migrate --host=$NEW_IP --service-url=http://aftertouch.local:8000 # default --method=telnet
|
||||
|
||||
# Optional, if you want the DNS-redirect path instead of (or alongside) telnet envswitch:
|
||||
# 1. ./build/soundtouch-cli setup ssh-check --host=$NEW_IP # USB-stick procedure if 22 is closed
|
||||
# 2. ./build/soundtouch-cli setup install-ca --host=$NEW_IP --service-url=http://aftertouch.local:8000
|
||||
# 3. ./build/soundtouch-cli setup migrate --host=$NEW_IP --service-url=http://aftertouch.local:8000 --method=resolv
|
||||
./build/soundtouch-cli setup pair --host=$NEW_IP --mode=bare # or --mode=full
|
||||
```
|
||||
|
||||
The two manual lines are user-side Wi-Fi switches that can't be automated portably. The `wait-ap` and `wait-online` subcommands poll for the corresponding network state, so timing them is hands-off.
|
||||
|
||||
## Recording the result
|
||||
|
||||
Append to this file under `## Results`:
|
||||
|
||||
```
|
||||
- Date: YYYY-MM-DD
|
||||
- Firmware: 27.x.x
|
||||
- Model: ST10 / ST20 / ST30 / ST300
|
||||
- Bare setMargeAccount accepted: yes/no
|
||||
- Persistence written: yes/no
|
||||
- Survives reboot: yes/no
|
||||
- Notes: ...
|
||||
```
|
||||
|
||||
One row per device tested. Once two devices on different firmware confirm the same verdict, we treat it as decided.
|
||||
|
||||
## Results
|
||||
|
||||
- Date: 2026-05-13
|
||||
- Firmware: 27.0.6.46330.5043500 (build epdbuild.trunk.hepdswbld04.2022-08-04T11:20:29)
|
||||
- Model: SoundTouch 10 (deviceID A81B6A536A98)
|
||||
- Bare setMargeAccount accepted: **yes** — pre-/info margeAccountUUID="" → post-/info margeAccountUUID="1111111"
|
||||
- Persistence written: **yes** — device materialized 14-entry Sources.xml on its own
|
||||
- Survives reboot: **yes** — `setup inspect` after `setup reboot` shows margeAccountUUID still 1111111
|
||||
- Notes: After bare pairing, the speaker did the full post-pairing handshake against AfterTouch (POST /streaming/support/power_on, GET /streaming/sourceproviders, GET /streaming/account/{id}/full, group/, provider_settings). No SETUP_START/SETUP_ENTER/SETUP_LEAVE was ever sent. Verdict: bare path is functionally equivalent to the full state machine on this firmware.
|
||||
|
||||
### Implication for the codebase
|
||||
|
||||
- `pkg/service/setup/setup_session.go` keeps the full state machine for completeness, but
|
||||
- `pkg/service/setup/init_plan.go`'s default could be simplified to "send setMargeAccount only" once we have one more confirming run on a different model.
|
||||
- The OCT issue-167 SSH-XML seeding workaround is **not required**.
|
||||
|
||||
### Appendix — SystemConfigurationDB.xml comparison
|
||||
|
||||
Post-experiment we compared the device-written `/mnt/nv/BoseApp-Persistence/1/SystemConfigurationDB.xml` from the bare-paired speaker against two SSH backups taken from speakers originally paired by the official Bose app (account 3230304, devices `A_Sound_Machine` and `Sound_Machinechen`). The diff is much smaller than expected — only two fields differ, and neither is set by the pairing protocol itself:
|
||||
|
||||
| Field | Bare-paired (1111111) | Real-Bose-paired (3230304) | Set by |
|
||||
|--------------------------|--------------------------------------------|----------------------------|-----------------------------------------------------------------------------------------------------------|
|
||||
| `DeviceName` | `Bose SoundTouch 536A98` (factory default) | `Sound Machinechen` | `name` WS message — only sent in `--mode=full` |
|
||||
| `AccountAssociatedEMail` | empty | **empty** | Never populated, even by real Bose |
|
||||
| `AccountUUID` | `1111111` | `3230304` | `setMargeAccount` — both paths set it |
|
||||
| `Locale` | empty | **empty** | Never populated, even by real Bose |
|
||||
| `acctMode` | `global` | `global` | Firmware-default; no protocol path observed to change it |
|
||||
| `isMultiDeviceAccount` | `false` | `true` | Derived from the cloud's `/streaming/account/{id}/full` response — count of `<devices>` > 1 flips it true |
|
||||
| `margeAuthServerToken` | empty | **empty** | Never populated, even by real Bose |
|
||||
| `Password` | (encrypted blob) | (encrypted blob) | Device-local key; expected to differ |
|
||||
|
||||
Three of the seven informational fields are empty even after a real-Bose pairing — the firmware simply doesn't populate `AccountAssociatedEMail`, `Locale`, or `margeAuthServerToken` from the pairing flow. So bare pairing isn't missing any field that real pairing fills.
|
||||
|
||||
The two genuinely different fields:
|
||||
|
||||
- **`DeviceName`** — pure UX. Settable any time post-pair via `name` POST (`soundtouch-cli name set --value=…`) or by sending the `name` WS message during `--mode=full` pairing.
|
||||
- **`isMultiDeviceAccount`** — not a pairing concern. It's derived from the account's device count on AfterTouch's side; flips to `true` automatically the next time the speaker refreshes account state if a second speaker has been paired to the same account.
|
||||
|
||||
So the experiment's YES verdict stands unqualified: bare `setMargeAccount` produces a `SystemConfigurationDB.xml` functionally equivalent to one written by the official pairing flow.
|
||||
Reference in New Issue
Block a user