mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-19 00:56:16 +00:00
Three files carried 192.168.123.x as placeholder IPs in examples and
fixtures. RFC-1918 private space — same reader-confusion concern as
the broader 192.168.1.* sweep in 136d24a. Switched to 192.0.2.x
preserving the last octet so the reader-side intent ("CLI host arg
example", "test fixture URL") stays clear.
- docs/analysis/FACTORY-RESET-PROTOCOL.md — 14 CLI --host examples + 1 log-fragment
- docs/analysis/TELNET-COMMAND-REFERENCE.md — 1 docker-run env example
- pkg/service/marge/recents_sourceproviderid_regression_test.go
— 2 XML location URLs (matched-pair within file)
docs/analysis/BOSE-LAB-RUNBOOK.md keeps its 192.168.10/24 subnet
unchanged — that's the documented Pi-as-AP network for the runbook,
not a placeholder.
go test ./pkg/service/marge/... clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
137 lines
8.4 KiB
Markdown
137 lines
8.4 KiB
Markdown
# 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.0.2.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.0.2.123 setup inspect
|
||
|
||
# Green/red status across every migration axis (SSH, telnet, CA, pairing, …).
|
||
go run ./cmd/soundtouch-cli --host 192.0.2.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.0.2.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.0.2.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.0.2.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.0.2.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.0.2.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.0.2.123 clock now
|
||
|
||
# Reboot to clear any half-initialized resolver / NTP state from the wifi-push flap.
|
||
go run ./cmd/soundtouch-cli --host 192.0.2.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.0.2.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.0.2.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.0.2.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.0.2.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.0.2.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.
|