Skip to content

Commit c971eec

Browse files
committed
docs: add speaker setup, Spotify, and architecture guides
- speaker-setup.md: SSH on fw 27.x, data extraction, speaker redirect - spotify.md: Connect vs SoundTouch Spotify, preset kick-start fix - architecture.md: Bose server status, proxy modes, circuit breaker, data flows Closes #172, closes #173, closes #174
1 parent 672029a commit c971eec

File tree

3 files changed

+374
-0
lines changed

3 files changed

+374
-0
lines changed

docs/architecture.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Architecture
2+
3+
## The Problem
4+
5+
Bose SoundTouch speakers depend on 4 cloud servers for network features like TuneIn radio presets, account management, and firmware updates. Bose announced the shutdown of these servers on **May 6, 2026** (extended from the original February 18 date).
6+
7+
Reference: [Bose SoundTouch EOL page](https://www.bose.co.uk/en_gb/landing_pages/soundtouch-eol.html)
8+
9+
Bose has published official API documentation to support community developers: [SoundTouch Web API (PDF)](https://assets.bosecreative.com/m/496577402d128874/original/SoundTouch-Web-API.pdf)
10+
11+
## The Four Bose Servers
12+
13+
| Server | Internal Name | Purpose | Status (Feb 2026) |
14+
|--------|--------------|---------|-------------------|
15+
| `streaming.bose.com` | marge | Account data, presets, recents, software updates, streaming tokens | Alive (shutdown not until May 6) |
16+
| `content.api.bose.io` | bmx | TuneIn radio playback URLs, service registry, analytics | API removed early — server responds but returns 404 for all endpoints |
17+
| `worldwide.bose.com` | updates | Firmware update checks | API removed early — returns 404 |
18+
| `events.api.bosecm.com` | stats | Telemetry, device blacklist | Not proxied — telemetry only, safe to ignore |
19+
20+
Note: BMX and updates APIs are already returning 404s even though the official shutdown date hasn't passed. This suggests Bose is deprecating APIs incrementally.
21+
22+
## How SoundCork Replaces Them
23+
24+
```
25+
Speaker → soundcork → local handlers (XML data files)
26+
```
27+
28+
1. Edit the speaker's config file (`SoundTouchSdkPrivateCfg.xml`) to point all server URLs to your soundcork instance
29+
2. SoundCork serves all responses locally from your extracted XML data files
30+
3. No traffic reaches Bose servers
31+
32+
## Operating Modes
33+
34+
### Local Mode (Recommended)
35+
36+
`SOUNDCORK_MODE=local` (default)
37+
38+
All responses served from local data store. Zero traffic to Bose. This is recommended because:
39+
40+
- Complete independence from Bose servers
41+
- No risk of unwanted firmware updates (marge has a `/streaming/software/update/` endpoint)
42+
- No data sent to Bose
43+
- Works identically whether Bose servers are up or down
44+
45+
### Proxy Mode
46+
47+
`SOUNDCORK_MODE=proxy`
48+
49+
Tries upstream Bose servers first, falls back to local handlers on failure. Useful during initial setup to:
50+
51+
- Verify your speaker is correctly talking to soundcork
52+
- Capture real server responses for analysis
53+
- Compare local handler behavior against real servers
54+
55+
**Not recommended for production** — the marge server's software update endpoint could potentially trigger a firmware update.
56+
57+
## Circuit Breaker (Proxy Mode)
58+
59+
When in proxy mode, a circuit breaker tracks upstream health per-server:
60+
61+
- **Closed** (healthy): Requests forwarded to upstream normally
62+
- **Open** (down): Upstream failed recently — skip directly to local fallback. Opens on:
63+
- Connection errors or timeouts (10 second timeout)
64+
- HTTP 404 responses (API removed but server alive)
65+
- HTTP 5xx responses (server errors)
66+
- **Half-open** (probing): After 5-minute cooldown, allows one request through to probe upstream health
67+
68+
The circuit breaker is per-worker (gunicorn runs 2 workers), so at most 2 upstream probes per cooldown window per dead server.
69+
70+
## Data Flows
71+
72+
### Power-on Sequence
73+
74+
Speaker boots and calls (in order):
75+
76+
1. `POST /marge/streaming/support/power_on` — device registration
77+
2. `GET /marge/streaming/sourceproviders` — available source types
78+
3. `GET /marge/streaming/account/{id}/full` — full account with devices, presets, recents
79+
4. `GET /marge/streaming/account/{id}/device/{id}/presets` — preset list
80+
5. `GET /marge/streaming/software/update/account/{id}` — check for firmware updates (soundcork returns "no updates")
81+
82+
All served locally by soundcork's marge handlers.
83+
84+
### Playing a TuneIn Preset
85+
86+
1. Speaker sends preset button press event
87+
2. Speaker requests `GET /bmx/tunein/v1/playback/station/{stationId}`
88+
3. SoundCork returns the stream URL (e.g., `http://icecast.vrtcdn.be/mnm.aac`)
89+
4. Speaker connects directly to the radio stream — no further server involvement
90+
91+
### Spotify
92+
93+
Spotify playback does **not** go through soundcork or Bose servers. See [Spotify Guide](spotify.md) for details.
94+
95+
## Traffic Logging
96+
97+
In proxy mode, all traffic is logged to `SOUNDCORK_LOG_DIR/traffic.jsonl` in JSON Lines format. Each entry contains:
98+
99+
- Timestamp
100+
- Request: method, path, query, headers, body
101+
- Upstream URL (or "local" if handled locally)
102+
- Response: status, headers, body
103+
- Fallback reason (if applicable): `circuit_open`, `upstream_error`, `upstream_http_404`, etc.
104+
105+
Useful for debugging and understanding speaker behavior. Not active in local mode.

docs/speaker-setup.md

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
# Speaker Setup Guide
2+
3+
How to set up your Bose SoundTouch speaker to work with SoundCork. This guide
4+
covers enabling SSH access, extracting the data SoundCork needs, and redirecting
5+
your speaker's cloud traffic to your SoundCork server.
6+
7+
## Prerequisites
8+
9+
- Bose SoundTouch speaker (tested on SoundTouch 20, firmware 27.0.6)
10+
- Clean FAT32-formatted USB stick
11+
- Ethernet cable (recommended for initial setup)
12+
- Computer on the same network as the speaker
13+
14+
## Step 1: Enable SSH Access
15+
16+
### Firmware 27.x (Current)
17+
18+
The old `remote_services on` TAP command (port 17000) was **removed** in
19+
firmware 27.x. You must use the USB stick method instead:
20+
21+
1. Format a USB stick as FAT32.
22+
2. Create a single empty file called `remote_services` (no file extension).
23+
3. **Critical for macOS users** — remove the junk files that macOS creates
24+
automatically:
25+
```sh
26+
mdutil -i off /Volumes/YOUR_USB_NAME
27+
rm -rf /Volumes/YOUR_USB_NAME/.fseventsd
28+
rm -rf /Volumes/YOUR_USB_NAME/.Spotlight-V100
29+
rm -f /Volumes/YOUR_USB_NAME/._*
30+
```
31+
These hidden files can prevent the speaker from detecting the
32+
`remote_services` file.
33+
4. Power off the speaker completely.
34+
5. Insert the USB stick into the USB port on the back of the speaker.
35+
6. Power on the speaker.
36+
7. Wait approximately 60 seconds.
37+
38+
> **A note on connectivity**: During our testing, we initially failed with WiFi
39+
> and a USB stick containing macOS junk files. We succeeded after cleaning the
40+
> USB AND switching to Ethernet. We changed both variables simultaneously, so we
41+
> cannot confirm which was the actual fix. If WiFi doesn't work for you, try
42+
> connecting the speaker via Ethernet cable as well.
43+
44+
Then SSH in:
45+
46+
```sh
47+
ssh root@<speaker-ip>
48+
```
49+
50+
No password is required.
51+
52+
### Make SSH Persistent Across Reboots
53+
54+
By default, SSH access is lost when the speaker reboots. To make it permanent:
55+
56+
```sh
57+
ssh root@<speaker-ip>
58+
touch /mnt/nv/remote_services
59+
```
60+
61+
This creates a persistent flag file on the speaker's non-volatile storage. You
62+
can now remove the USB stick and SSH will survive reboots.
63+
64+
### Finding Your Speaker's IP Address
65+
66+
There are a few ways to find the speaker's IP:
67+
68+
- Check your router's DHCP client list for a device named "SoundTouch".
69+
- If you have the [Bose CLI](https://github.com/timvw/bose): `bose status`
70+
- The Bose SoundTouch app shows the speaker's IP in device settings.
71+
72+
## Step 2: Extract Speaker Data
73+
74+
SoundCork needs 4 XML files from your speaker. Some are available via the
75+
speaker's local web API (port 8090), others require SSH.
76+
77+
### From the speaker's web API (port 8090)
78+
79+
```sh
80+
curl http://<speaker-ip>:8090/presets > Presets.xml
81+
curl http://<speaker-ip>:8090/recents > Recents.xml
82+
curl http://<speaker-ip>:8090/info > DeviceInfo.xml
83+
```
84+
85+
### From SSH (requires root access)
86+
87+
`Sources.xml` contains authentication tokens that are not exposed via the web
88+
API. You must retrieve it over SSH:
89+
90+
```sh
91+
ssh root@<speaker-ip>
92+
cat /mnt/nv/BoseApp-Persistence/1/Sources.xml
93+
```
94+
95+
Copy the output and save it as `Sources.xml`.
96+
97+
### Get Your Account UUID
98+
99+
From the `DeviceInfo.xml` you just downloaded, find the `margeAccountUUID`
100+
field. Alternatively, via SSH:
101+
102+
```sh
103+
cat /opt/Bose/etc/SoundTouchSdkPrivateCfg.xml
104+
```
105+
106+
Look for the account UUID in the marge URL.
107+
108+
### Store Files in SoundCork's Data Directory
109+
110+
Place the extracted files in the following structure:
111+
112+
```
113+
data/
114+
<accountId>/
115+
Presets.xml
116+
Recents.xml
117+
Sources.xml
118+
devices/
119+
<deviceId>/
120+
DeviceInfo.xml
121+
```
122+
123+
Where:
124+
- `<accountId>` is your `margeAccountUUID`
125+
- `<deviceId>` is the `deviceID` attribute from `DeviceInfo.xml`
126+
127+
See the [`examples/`](../examples/) directory in this repository for the
128+
expected XML format.
129+
130+
## Step 3: Redirect Speaker to SoundCork
131+
132+
### Make the filesystem writable
133+
134+
The speaker's root filesystem is read-only by default. You must switch it to
135+
read-write mode before editing any files:
136+
137+
```sh
138+
ssh root@<speaker-ip>
139+
rw
140+
```
141+
142+
### Edit the server configuration
143+
144+
```sh
145+
vi /opt/Bose/etc/SoundTouchSdkPrivateCfg.xml
146+
```
147+
148+
Change all 4 server URLs to point to your SoundCork instance:
149+
150+
| Server | Before | After |
151+
|---------|-------------------------------------|-------------------------------|
152+
| marge | `https://streaming.bose.com` | `https://your-soundcork-server` |
153+
| bmx | `https://content.api.bose.io` | `https://your-soundcork-server` |
154+
| updates | `https://worldwide.bose.com` | `https://your-soundcork-server` |
155+
| stats | `https://events.api.bosecm.com` | `https://your-soundcork-server` |
156+
157+
Reboot the speaker for changes to take effect. The speaker will now send all
158+
cloud traffic to your SoundCork server.
159+
160+
## Warnings
161+
162+
> **Port 17000 (TAP Console)**: The speaker exposes a diagnostic console on
163+
> port 17000. On firmware 27.x, most commands have been removed. **Do NOT send
164+
> exploratory commands** — the `demo enter` command puts the speaker into
165+
> factory/demo mode which may be difficult to recover from.
166+
167+
> **Read-only filesystem**: The speaker's root filesystem is read-only by
168+
> default. Always run `rw` before editing files. The filesystem reverts to
169+
> read-only on reboot.

docs/spotify.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Spotify on SoundTouch
2+
3+
## Two Different Spotify Systems
4+
5+
There are two completely separate ways Spotify works on a SoundTouch speaker. This is a common source of confusion.
6+
7+
### 1. Spotify Connect (Works — Recommended)
8+
9+
- The speaker advertises itself as a Spotify Connect device on your local network
10+
- Open the Spotify app on your phone or computer
11+
- Tap the speaker/device icon and select your SoundTouch speaker (e.g., "Bose-Woonkamer")
12+
- Audio streams directly from Spotify's CDN to the speaker
13+
- **No Bose servers involved** — this is purely between the Spotify app, Spotify's servers, and the speaker
14+
- **No soundcork involvement** — Spotify Connect operates independently
15+
- Works before and after the Bose shutdown
16+
17+
### 2. SoundTouch Spotify Integration (Broken Without Workaround)
18+
19+
- This is what the SoundTouch app used for browsing Spotify and setting Spotify presets
20+
- Relies on Bose's own Spotify client ID for OAuth token management via the marge server
21+
- The SoundTouch app can **no longer reconnect or configure** Spotify accounts
22+
- Spotify presets may show as "unplayable" after redirecting to soundcork
23+
24+
## Fixing Spotify Presets
25+
26+
If your Spotify preset fails after setting up soundcork, you'll see these symptoms in the traffic logs:
27+
28+
- `type: "DO_NOT_RESUME"`, `location: "Unplayable location string"`, `playStatus: "STOP_STATE"`
29+
- `source: "INVALID_SOURCE"`, `playStatus: "BUFFERING_STATE"`
30+
31+
### The Fix: Kick-Start via Spotify Connect
32+
33+
1. Open the **Spotify app** on your phone (not the SoundTouch app)
34+
2. Play any song
35+
3. Tap the speaker/device icon at the bottom of the Now Playing screen
36+
4. Select your SoundTouch speaker
37+
5. Wait for the music to start playing (confirms Spotify Connect is active)
38+
6. Now press your Spotify preset button on the speaker — **it should work**
39+
40+
### Why This Works
41+
42+
When you cast via Spotify Connect, the Spotify app authenticates the speaker's **embedded Spotify client** directly over the local network using ZeroConf/mDNS. This gives the speaker a fresh Spotify session.
43+
44+
Key details:
45+
46+
- The Spotify session lives in the speaker's **RAM** — the OAuth token stored in `Sources.xml` doesn't change
47+
- The preset can reuse the active Spotify session established by Spotify Connect
48+
- **You may need to repeat this after a speaker reboot**, since the in-memory session is lost on restart
49+
- Spotify playback traffic never appears in soundcork's traffic logs because it doesn't go through soundcork
50+
51+
### What We Observed (Traffic Analysis)
52+
53+
From our real traffic logs:
54+
55+
**Before Spotify Connect** (speaker trying to play preset on its own):
56+
57+
```
58+
source-state: "SPOTIFY"
59+
contentItem type: "DO_NOT_RESUME"
60+
location: "Unplayable location string"
61+
playStatus: "STOP_STATE"
62+
```
63+
64+
**After casting one song via Spotify Connect** (from the Spotify app):
65+
66+
```
67+
track: "Beachball - Vocal Radio Edit"
68+
artist: "Nalin & Kane"
69+
source: "SPOTIFY"
70+
playStatus: "PLAY_STATE"
71+
```
72+
73+
**Then pressing Spotify preset** (Clouseau):
74+
75+
```
76+
artist: "Clouseau"
77+
album: "Hoezo?"
78+
source: "SPOTIFY"
79+
playStatus: "PLAY_STATE" ← works!
80+
```
81+
82+
## Managing Presets
83+
84+
The official SoundTouch app can no longer configure presets pointing to TuneIn stations. If you need to add or change presets, use the [Bose CLI](https://github.com/timvw/bose), which talks directly to the speaker's local API on port 8090:
85+
86+
```bash
87+
# Install via Homebrew
88+
brew install timvw/tap/bose
89+
90+
# View current presets
91+
bose preset
92+
93+
# Get a specific preset
94+
bose preset 1
95+
96+
# View speaker status
97+
bose status
98+
```
99+
100+
The speaker's local API (port 8090) is completely independent of the cloud servers and will continue to work indefinitely.

0 commit comments

Comments
 (0)