{"id":50379187,"url":"https://github.com/tostmann/sixback","last_synced_at":"2026-06-10T00:00:18.974Z","repository":{"id":358344885,"uuid":"1240850998","full_name":"tostmann/SixBack","owner":"tostmann","description":"The Bose SoundTouch cloud, reborn on a ~$5 ESP32 — like a Docker container, just without the host. Brings your six preset buttons back: no server, no Docker, no Pi.","archived":false,"fork":false,"pushed_at":"2026-06-06T23:42:41.000Z","size":7697,"stargazers_count":54,"open_issues_count":1,"forks_count":3,"subscribers_count":3,"default_branch":"master","last_synced_at":"2026-06-07T01:18:07.258Z","etag":null,"topics":["bmx","bose","busware","cloud-replacement","esp32","esp32-c6","esp32-s3","home-automation","smart-home","soundtouch"],"latest_commit_sha":null,"homepage":"https://sixback.io","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tostmann.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"custom":["https://paypal.me/busware"]}},"created_at":"2026-05-16T16:43:01.000Z","updated_at":"2026-06-06T23:42:44.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/tostmann/SixBack","commit_stats":null,"previous_names":["tostmann/bosefix32","tostmann/sixback"],"tags_count":32,"template":false,"template_full_name":null,"purl":"pkg:github/tostmann/SixBack","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tostmann%2FSixBack","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tostmann%2FSixBack/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tostmann%2FSixBack/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tostmann%2FSixBack/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tostmann","download_url":"https://codeload.github.com/tostmann/SixBack/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tostmann%2FSixBack/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34130642,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-09T02:00:06.510Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bmx","bose","busware","cloud-replacement","esp32","esp32-c6","esp32-s3","home-automation","smart-home","soundtouch"],"created_at":"2026-05-30T11:00:35.414Z","updated_at":"2026-06-10T00:00:18.903Z","avatar_url":"https://github.com/tostmann.png","language":"C++","funding_links":["https://paypal.me/busware"],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"images/sixback-logo-crop-text.png\" alt=\"SixBack — local SoundTouch cloud replacement\" width=\"480\"\u003e\n\u003c/p\u003e\n\n# SixBack\n\n\u003e *Bring your six back.*\n\nA tiny ESP32 stick that brings back the six Internet-radio preset buttons on\n**Bose SoundTouch** speakers after Bose shut down their cloud\n(2026-05-06).  It speaks just enough of the BMX cloud protocol that the\nspeaker firmware — which can no longer be updated — happily keeps working.\n\nNo subscription, no account, no Bose servers.  One USB stick on your LAN.\n\n\u003e SixBack was formerly developed and published as *BoseFix32*.  All\n\u003e functionality is preserved; the rename reflects the project's identity\n\u003e independent of any Bose trademark.\n\n## Status (v0.8.18)\n\n| Component                                                            | State                                                                                                              |\n| -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |\n| Cloud replacement (`/bmx/registry`, `/streaming/…`, `/updates/…`)    | working — 22 / 30 ueberboese-spec endpoints served                                                                 |\n| **ESP32-S3 8 MB variant — Seeed XIAO ESP32S3 \u0026 similar** (v0.8.18)  | working — dedicated `s3-8mb` build target with its own partition table (`partitions-8mb.csv`, same 3 MB A/B OTA app slots as the 16 MB layout, 1.9 MB LittleFS), its own web-flasher button pair and its own OTA artifact channel (`sixback-s3-8mb-*`), so over-the-air updates always match the flashed layout. Spotify stays enabled (8 MB PSRAM). The OTA puller also gained a **pre-flight size check** on all targets: an image larger than its target partition is refused before anything is unmounted or written (\"wrong build variant for this board?\") (#23) |\n| **Rename speakers from the WebUI** (v0.8.18)                         | working — ✏ next to the speaker name sends the new name device-direct (`POST /name`), so it persists on the speaker across reboots, app-free. Two firmware quirks discovered on-device and handled: the speaker silently ignores the request unless the content type is `text/xml`, and it applies **only one rename per power-on** — further renames answer OK while doing nothing. SixBack verifies the speaker's response instead of trusting the status code and tells you when a reboot is needed first. The System tab now also shows the flash size (16 MB / 8 MB) so you can tell the S3 variants apart (#25) |\n| **Spotify — Library + slot trigger** (v0.7.7 → v0.7.11)              | working — connect once via OAuth in the 🎵 Spotify sidebar tab, save tracks / albums / playlists as reusable **Library tiles** (device-side NVS, `GET/POST/DELETE /api/spotify/library`), then drag a tile onto a preset slot. A physical button press fires the Spotify Web-API `/play` to the speaker as a Connect device, with per-slot **shuffle** + **repeat** (one track / full album-playlist) and a live trigger log with 🎵-badges |\n| **Media sidebar — search \u0026 drag onto slots**                        | working — a 4-tab Media panel (📻 Radio · 🔗 Stream · 🎵 Spotify · 💿 DLNA): search TuneIn / RadioBrowser stations, keep custom stream URLs and Spotify Library tiles, or browse DLNA servers, then drag any result straight onto one of a speaker's 6 preset slots |\n| **Marge keep-alive** (v0.7.7)                                        | working — 60s background ping of `/setMargeAccount` to every known speaker; prevents the scmudc event-stream from going silent after hours of idle |\n| **Marge pair-bootstrap** (`/setMargeAccount` round-trip)             | working — `/streaming/account/{a}/device/` echoes deviceid with Bearer-credentials                                 |\n| **scmudc telemetry** — per-device NowPlaying + event trace           | working — body-captured `/v1/scmudc/{deviceId}` JSON parsed into per-speaker store                                 |\n| TuneIn preset resolver (`Tune.ashx` + `Describe.ashx`)               | working — stations show with correct name \u0026 artwork. **AAC-only stations now play (v0.8.9):** without an explicit `formats=` filter TuneIn hands back a `notcompatible.enUS.mp3` placeholder for AAC-only stations (the speaker played a ~12 s \"station not compatible\" message, then stopped). The resolver now requests `formats=mp3` first (keeps the most-compatible stream for dual-format stations) and falls back to `formats=aac`, so AAC-only stations resolve to their real stream — the SoundTouch decodes AAC-LC and HE-AAC v1/v2 fine; HLS (`.m3u8`) variants are skipped. `POST /api/tunein/cache/clear` flushes stale cached resolutions after updating. |\n| Preset push to speaker — serialized FreeRTOS queue (v0.6.0)          | working — single persistent worker drains pushes one-by-one; depth 16, 503 when full; refuses with an actionable HTTP 409 (\"migrate this speaker first\") when the speaker isn't migrated yet, instead of a confusing \"didn't save\" (v0.8.7); waits for the speaker to actually reach *playing* state before the long-press (up to ~18 s) so a slow stream-start no longer drops the preset (v0.8.8) |\n| **Source self-healing** (v0.8.8)                                     | working — a migrated speaker whose SixBack account never bound (empty `margeAccountUUID` → no TuneIn/Spotify/DLNA sources registered, so every push failed with `/select=500`) is detected on the periodic status check and **auto-re-bound** (synthetic per-device account id + `/setMargeAccount`), re-registering its sources with no user action; a `⚠ sources not synced` badge + a **Re-Sync Sources** button surface it in the WebUI too (#10) |\n| **Captive portal** — WiFi setup AP                                   | working — fixed the `ERR_TOO_MANY_REDIRECTS` redirect loop that broke the setup page (the root route was a regex pattern handed to a non-regex router); the portal now loads cleanly (v0.8.8, #12) |\n| **Compressed NVS stores** (v0.8.17)                                  | working — the JSON stores (presets, inventory, stream/Spotify libraries) are now heatshrink-compressed in NVS (vendored [atomicobject/heatshrink](https://github.com/atomicobject/heatshrink) v0.4.1, 1.6 KB encoder state, measured factor ~2-4 on real store data), raising the per-stick ceiling from ~7 fully-loaded speakers to 15-20+. Values under 512 B stay plaintext; legacy stores migrate in place on first save; every decode path is fail-safe (fuzz-tested host-side, 2700 cases under ASan/UBSan) and a corrupt frame can never hang the boot. **Note:** firmware older than v0.8.17 cannot read compressed stores — after a manual downgrade the preset store re-seeds from the speakers, but stream/Spotify library tiles would need a re-import |\n| **Preset / inventory store — NVS blob storage** (v0.8.15)           | working — both per-stick stores (preset assignments, speaker inventory) were single NVS *strings*, which hit ESP-IDF's hard 4000-byte `nvs_set_str` limit at roughly five speakers with full presets: from then on **every** save failed regardless of free space (the \"partition full\" error was misleading — the partition was two-thirds empty), and a cleanup pass could destroy the last persisted state, so presets vanished on the next reboot or OTA update. Both stores are now NVS **blobs** (page-chunked, limit = partition size), the cleanup backs up and restores the previous value instead of sacrificing it, and a genuine out-of-space reports an accurate error. Existing data migrates in place on first save. Practical ceiling on the 24 KB NVS partition is ~7 fully-loaded speakers per stick — beyond that, writes fail loudly but never destroy data |\n| **Preset-loss defense** (Defense-in-Depth)                           | working — `handleMigrate` pre-imports; `/presets` and `account/full` return 404 when store empty; TUNEIN source-block carries `username=TuneIn` so `sourceAccount` survives every sync |\n| **Opaque-source passthrough** — DLNA / UPnP / Bluetooth presets      | working — original `\u003cContentItem\u003e` captured at import and replayed 1:1; `STORED_MUSIC` and `STORED_MUSIC_MEDIA_RENDERER` declared in `accountSources`; serialized as Bosman-schema `\u003cpreset\u003e` blocks with `\u003clocation\u003e` + `\u003csource\u003e` reference (v0.6.537) |\n| **DLNA preset workflow** end-to-end                                  | working — verified on SoundTouch 30 with 6/6 OPAQUE slots reboot-persistent (2026-05-21)                           |\n| **DLNA browse** in the WebUI (v0.8.0)                                | working — sidebar tab with speaker + server pickers, breadcrumb, drag-track-onto-slot; UPnP ContentDirectory:Browse SOAP runs in a small Pi5/Apache-fronted proxy so the firmware stays thin; tested against MiniDLNA, Fritz!Mediaserver |\n| **DLNA preset recording** via drag-to-slot (v0.8.0)                  | working — `POST /api/speaker/{id}/dlna/preset/{slot}` emulates long-press (`/select` STORED_MUSIC ContentItem → 8 s settle → `/key` press+release) then re-imports `/presets` so the new OPAQUE slot is captured into the store with its `rawContentItem`; peer-aware refuse (HTTP 409) when the speaker is owned by another SixBack |\n| **Migrate / Reboot progress modal** (v0.8.0)                         | working — both speaker actions open the same step-by-step progress dialog used by Refresh; status transitions are tracked by polling `/api/speakers`, with explicit timeout + last-status surfacing if the speaker never returns |\n| Speaker telnet bootstrap (`sys configuration …` via TCP 17000)       | working                                                                                                            |\n| **Migrate verify post-boot** (v0.7.632)                              | working — second `getpdo` after `waitForSpeakerBack_`; mismatch → `MIGRATE_FAILED` instead of silent `MIGRATED`    |\n| Auto-import existing presets via BMX `/presets`                      | working                                                                                                            |\n| **Stereo-Pair / Multi-Room group API**                               | working — POST/PUT/DELETE on `/streaming/account/{a}/group/`, NVS-persistent                                       |\n| **Stereo pair — ST10 left/right pairing in the WebUI** (v0.8.16)    | working — SoundTouch 10 cards get a stereo-pair row: pick the right-channel speaker and the two ST10 join into one left/right stereo image that presents as a single device (`POST /addGroup` to the master, device-direct — the speaker itself registers the pair with the SixBack cloud store, so it survives ESP reboots). Un-pair works from either card (`/removeGroup` is routed to the master). ST10-only per the protocol (`supportedURLs` is *not* a reliable gate — an ST30 advertises `/addGroup` too — so the UI gates by model). Stale pair entries left behind by the Bose app's own un-pair path are now pruned when a new pair is registered (#22) |\n| **Device-direct multiroom** (ZoneManager, v0.8.7)                    | working — group speakers straight through the speaker's own `/setZone` / `/getZone` on port 8090 (master + slaves); stateless, live truth read from the master's `/getZone` — a separate layer from the cloud group-store above; WebUI group-picker / badge / ungroup |\n| **Auto-Mode** — discover + migrate + preserve presets on first boot  | working — gated by NVS flag, default on                                                                            |\n| **Auto-Mode cron** — periodic re-check every 30 min when enabled     | working — light discovery + auto-claim/release + migrate newcomers; since v0.8.13 a speaker is only *released* to a **verified** foreign owner (a live SixBack peer, or an explicit revert to the Bose cloud) — a speaker pointing at a dead URL stays owned and is **re-claimed** automatically (covers stale bases after an IP change and retired second sticks; the re-claim path skips the model/firmware whitelist because the speaker has already been migrated successfully before) |\n| **Peer-aware Auto-Mode** (v0.7.5)                                    | working — HTTP-probes other SixBack sticks in the LAN; skips speakers already claimed by a peer; UI shows `claimed by peer @ \u003cip\u003e` |\n| **Source-Normalizer** — TuneIn / Local / RadioBrowser → playable     | working — RadioBrowser UUID resolved via radio-browser.info                                                        |\n| **IP-Failsafe** — auto-remigrate on ESP-IP change, with pre-probe    | working — every migrated speaker stores the SixBack base URL as a fixed IP, so a DHCP change would strand them; SixBack detects its own IP change **at boot and at runtime** (WiFi reconnect event, v0.8.13) and re-points every speaker it owns, skipping those already on the new base. If a speaker is offline during the run (router swap — speakers boot slower than the ESP), the run retries every 60 s for up to 20 min instead of giving up (v0.8.13). A DHCP reservation for the SixBack MAC avoids the situation entirely and is still the recommended setup |\n| **SETTLING status** (v0.6.541)                                       | working — backend reports `settling` instead of `offline` when only Telnet:17000 is down but BMX:8090 still answers |\n| Preset UI — drag\u0026drop, dual-row (HW vs Store), per-slot revert       | working — modal progress, per-speaker export/import, refresh discards unsaved (v0.7.3)                             |\n| **Custom stream library — device-side** (v0.8.5)                     | working — Stream-tab tiles persist in device NVS instead of per-browser localStorage; `GET/POST/DELETE /api/streams` + bulk import, one-time localStorage→device migration, Export/Import; survives USB-erase and browser change |\n| **Speaker reordering** (v0.8.6)                                      | working — drag the ⠿ grip on a speaker card header to reorder the list; order is stored device-side (`POST /api/speakers/order`, persisted in NVS in the speaker-vector order), so it's identical in every browser and survives reboot; newly discovered speakers append at the end |\n| Diagnostic snapshot (v0.6.0)                                         | working — `GET /api/speaker/{id}/diagnostic-snapshot` + one-shot pre-migrate snapshot persisted to `/snapshots/{deviceId}.json`; WebUI download or \"Send to maintainer\" upload to `sixback.io/snapshots/bosefix/snapshot` |\n| OTA — app \u0026 LittleFS                                                 | working — `UPDATE_SIZE_UNKNOWN` + stream-to-EOF + 90% sanity-abort (v0.7.0 fix for HTTPS Content-Length truncation) |\n| **OTA install — self-validating + clear status** (v0.8.3)            | working — the *Install* action re-checks the manifest itself instead of gating on a stale prior check, so a legitimate update is never blocked by a misleading \"no update available\"; distinct messages for *server unreachable* (retry) vs *already up-to-date* (use Force re-install); the WebUI panel always reflects the real state, so an error can no longer sit next to a stale \"available\" |\n| **Manual \"Flash web UI\" — full-size S3 image** (v0.8.4)              | working — the WebUI upload guard rejected the ~9.9 MB S3 LittleFS image against a leftover 1 MB cap; raised to 11 MB so a manual FS upload matches the S3 spiffs partition. Verified end-to-end on S3 test hardware (~9.9 MB upload written, rebooted, FS intact). Also in v0.8.4: larger at-a-glance speaker status dots, and a GitHub project link in the WebUI + landing-page footer |\n| **Tag-based release versioning** (v0.7.6)                            | working — `RELEASE_TAG` env bakes the same version string into all four target firmwares; eliminates multi-target build-drift |\n| **Build size-gate** (v0.7.5)                                         | working — `build_release.sh` aborts if any firmware or LittleFS image exceeds its partition slot                   |\n| **A/B-OTA partition layout**                                         | working — C3 / C6 / classic use symmetric `partitions-4mb.csv`: two 1.90 MB app slots (app0/app1) + 256 KB spiffs, so OTA flips between slots (no USB needed for updates). S3 uses two 3 MB app slots + 10 MB spiffs (`partitions.csv`). The size-gate refuses any image that won't fit its slot |\n| WiFi provisioning — Improv-Serial (idle-window) + Captive AP         | working — both armed in parallel on cold boot                                                                      |\n| **ESP32-C6 WPA2 reliability**                                        | working — `WiFi.setSleep(WIFI_PS_NONE)` + `setAutoReconnect(true)` applied **before** `WiFi.begin()`; closes 4-Way-Handshake-Timeout on WPA2-Mixed APs |\n| System health — Task-WDT, WiFi / heap watchdog, crash counter, self-ping | working                                                                                                        |\n| **Discovery stack-safety** (v0.8.5)                                  | working — the background discovery worker no longer overruns its task stack on setups with many speakers: SSDP responder collection and per-speaker probing now run in separate stack frames and the worker stack was enlarged. Fixes a stack-canary crash that rebooted the device mid-scan and left discovery finding 0 speakers (manual add still worked). Verified across S3 / C6 / C3 |\n| Builds for **ESP32-S3 ★ / ESP32-C3 / ESP32-C6 / ESP32-classic**      | working — S3 is the recommended target; ESP32-classic re-enabled (`scripts/fs_exclude_esp32.py` trims the Spotify-only `silence.mp3` from its LittleFS image so the Web UI fits the 256 KB spiffs slot of `partitions-4mb.csv`) |\n| ESP-Web-Tools landing page (auto-detects chip)                       | working — \u003chttps://sixback.io/\u003e                                                                                    |\n\n## Install (recommended)\n\nOpen the **web flasher** in Chrome or Edge desktop and click *Connect*:\n\n\u003e 🔗 **\u003chttps://sixback.io/\u003e**\n\nThe page reads [`webflasher/manifest.json`](webflasher/manifest.json),\ndetects the chip family of the connected board, and writes the matching\nfactory image — bootloader + partition table + firmware + Web UI — in a\nsingle shot.  Right after the flash, esp-web-tools also offers to hand\nover WiFi credentials via Improv-Serial.\n\nIf Web Serial is unavailable, every target also ships an\n`*-firmware.bin` (for OTA over WiFi) and `*-littlefs.bin` (for FS-OTA).\n\n### ⚠ Auto-migration runs by default\n\nA freshly-flashed device boots with **`auto_migrate_on_boot = true`** in NVS.\nOnce it is on your WiFi, it will:\n\n1. Discover all SoundTouch speakers on the LAN (SSDP + ARP-probe).\n2. For every eligible speaker (model whitelist `SoundTouch 10/20/30`,\n   firmware whitelist `27.0.6.x` and `27.0.3.x`):\n   - Read its current presets via the BMX API.\n   - Normalize each preset (TuneIn passthrough; RADIO_BROWSER converted\n     to a direct stream URL; DLNA / Bluetooth captured as opaque\n     `\u003cContentItem\u003e` and replayed 1:1).\n   - Rewrite the speaker's cloud URLs via Telnet `:17000`.\n   - Reboot the speaker; presets survive without long-press because the\n     normalized list is embedded in the speaker's `account/full` sync.\n\nIf you'd rather drive each migration by hand, **turn the switch off at\nthe very top of `http://sixback.local/`** *before* the device finds your\nspeakers — or pre-disable it via `PUT /api/auto-mode` (Body:\n`{\"enabled\":false}`).  The default is \"on\" because the typical install\npath is *flash → provision → presets work*, and the foot-gun guards\n(eligibility whitelists, `max_per_boot=4`) are tight enough that nothing\nunrelated on your LAN gets touched.\n\nAfter the initial boot pass, SixBack keeps the auto-mode pipeline alive\nas a **periodic cron** (default every 30 minutes, configurable via\n`cron_interval_s`).  Each tick does a light discovery (SSDP + known-IP\nprobe, no full `/24` sweep), runs Auto-Claim/Release on the inventory,\nand migrates any newcomer that matches the eligibility whitelist.  A\nspeaker is only *released* when its new owner is verified — a live\nSixBack peer answering on that URL, or an explicit revert to the Bose\ncloud.  A speaker that points at a dead URL (a stale SixBack base after\nan IP change, or a second stick that was retired) stays owned and is\nautomatically re-claimed on the next tick.  The countdown to the next\ntick is visible at the top of the Web UI.\n\nIf multiple SixBack sticks coexist on the same LAN, the peer-aware\nauto-mode (v0.7.5+) keeps them from fighting over the same speakers:\neach stick HTTP-probes any foreign cloud URL it sees, and if the response\nlooks like another SixBack instance the speaker is left to its current\nowner.  The UI labels such speakers as *claimed by peer @ \u0026lt;ip\u0026gt;*.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"images/WebUIRadioSelector.png\" alt=\"SixBack Web UI — radio/media selector with speaker preset slots\" width=\"720\"\u003e\n\u003c/p\u003e\n\nThe top of `http://sixback.local/` is where the **Auto-Migrate at Boot**\nswitch lives.  Below it every discovered speaker gets a card with its\ncurrent state (migrated / settling / original / foreign-cloud / offline),\nits 6 preset slots, and per-speaker actions (migrate, revert, reboot,\nedit presets, group sync).\n\n## WiFi provisioning — two paths in parallel\n\nOn every cold boot the device opens **two** parallel provisioning\nwindows.  Whichever finishes first wins; the other is torn down.\nSame pattern as the sister project [ip4knx / TUL KNX-Gateway](https://github.com/tostmann/ip4knx).\n\n| Path           | When                                         | Window                                        |\n| -------------- | -------------------------------------------- | --------------------------------------------- |\n| Improv-Serial  | always                                       | 30 s idle (with creds) / 120 s idle (without) |\n| Captive AP     | no NVS creds **or** STA-connect timeout      | 5 min idle                                    |\n\nThe **Improv** path is what esp-web-tools uses right after flashing.\nThe **Captive Portal** opens an **open** AP called `SixBack-XXYYZZ`\n(no password) with a DNS hijack so any phone connecting to it gets the\nWiFi-setup form automatically; after the user submits, the success\npage auto-redirects to the device's freshly assigned LAN IP via\n`\u003cmeta http-equiv=\"refresh\"\u003e`.\n\n## Supported hardware\n\n| Chip          | Board reference                  | Flash  | Notes                                                            |\n| ------------- | -------------------------------- | ------ | ---------------------------------------------------------------- |\n| **ESP32-S3 ★**| `esp32-s3-devkitc-1` **with PSRAM** (any \"R8\" variant, e.g. N16R8 / N8R8) | ≥ 8 MB | **recommended** — **PSRAM is required** (TLS/HTTPS path for Spotify + OTA). The exact SKU is not important; clones are fine. 16 MB is the tested config and uses the default web-flasher button. **8 MB+PSRAM boards (e.g. Seeed XIAO ESP32S3) use the dedicated \"S3 8 MB\" button** on the web flasher (`s3-8mb` build: own partition table + own OTA channel) — do *not* use the standard S3 button on them, the 16 MB image does not fit the flash |\n| ESP32         | `esp32dev` (DevKitC-1)           | 4 MB  | classic — **shipped again** (v0.8.x); `scripts/fs_exclude_esp32.py` trims the Spotify-only `silence.mp3` from its LittleFS image so the gzipped Web UI fits the 256 KB spiffs slot |\n| ESP32-C3      | `esp32-c3-devkitm-1`             | 4 MB  | flashes over the chip's built-in USB-Serial-JTAG                 |\n| ESP32-C6      | `esp32-c6-devkitc-1`             | 4 MB  | WiFi 6 — works, but cold-start discovery occasionally drops SSDP-multicast packets and rare HTTP-server hangs need a reset |\n\n**S3 is the recommended target for distribution.** During the 4-phase\nend-to-end test (S3 ↔ C6 ping-pong with full erase/flash/provision each\nround) the S3 hit 3/3 speakers discovered + migrated in every single\nauto-mode run, while the C6 needed a second boot in one cold-start case\nand produced one HTTP-server hang that recovered only after a hardware\nreset.  The extra ~5 € for an S3-DevKitC-1 (with PSRAM) buys noticeable\nrobustness and plenty of free flash for future features.  Any S3 board\n**with PSRAM** works — the specific flash size is not critical (the app is\n~1.6 MB and the web UI ~160 KB), but a board *without* PSRAM will struggle\non the TLS/HTTPS path and is not supported.\n\nC3, C6 and ESP32-classic are fully functional and stay built/published on\nevery release.  ESP32-classic is published again: `scripts/fs_exclude_esp32.py`\nstrips the Spotify-only `silence.mp3` stub from its LittleFS image so the\ngzipped Web UI fits the 256 KB spiffs slot of `partitions-4mb.csv`.\n\nAll four targets share the same source tree and the same Web UI; the\nPlatformIO `extends = common` mechanism keeps the per-target diff small\n([`platformio.ini`](platformio.ini)).\n\n## What it does on the speaker\n\nAfter clicking *Migrate* in the Web UI, SixBack talks to the Bose\nDiagnostic Shell on **TCP\u0026nbsp;:17000** of the speaker and rewrites the\ncloud URLs the firmware caches in NVS:\n\n```\nsys configuration bmxRegistryUrl http://\u003csixback-ip\u003e:8000/bmx/registry/v1/services\nsys configuration statsServerUrl http://\u003csixback-ip\u003e:8000\nsys configuration margeServerUrl http://\u003csixback-ip\u003e:8000\nsys configuration swUpdateUrl    http://\u003csixback-ip\u003e:8000/updates/soundtouch\nenvswitch boseurls set http://\u003csixback-ip\u003e:8000 http://\u003csixback-ip\u003e:8000/updates/soundtouch\nsys reboot\n```\n\nNo SSH, no firmware mod, no Bose login.  The change is fully reversible\nvia *Revert to original Bose* — the speaker returns to its factory URL\nset even though the original cloud is offline.\n\n## Build locally\n\nRequires PlatformIO and a Linux/macOS host.\n\n```bash\n# build everything (all four targets) + LittleFS images\npio run -e esp32 -e s3 -e c3 -e c6\npio run -e esp32 -e s3 -e c3 -e c6 -t buildfs\n\n# produce tagged factory images + manifest for the web flasher\n./scripts/build_release.sh v0.7.6     # tag arg bakes the version into all 4 firmwares\n\n# flash a single target via USB\npio run -e s3 -t upload\npio run -e s3 -t uploadfs\n```\n\nVersioning + build snapshots are automatic\n(see [`scripts/version_bump.py`](scripts/version_bump.py)): every local\nbuild snapshots the working tree before bumping `build_number.txt`, so\nyou can always roll back to the exact state a given binary was built\nfrom.  Those snapshot commits stay **local** — only tagged releases are\npushed to the public repo.\n\n## Repository layout\n\n```\nsrc/                  Firmware (Arduino + ESP-IDF mix)\nweb-src/              Web UI source (index.html, gzipped at build time\n                      into data/ for LittleFS)\nwebflasher/           esp-web-tools landing page + manifest (binaries\n                      are .gitignored — rebuild via build_release.sh)\nimages/               README assets — title PNG + Web-UI screenshot\nscripts/              version_bump pre-build hook + build_release.sh\npartitions.csv        16 MB partition table  (ESP32-S3 16-MB modules)\npartitions-8mb.csv    8 MB partition table   (ESP32-S3 8-MB modules, e.g. Seeed XIAO)\npartitions-4mb.csv    4 MB partition table   (ESP32 / C3 / C6)\nplatformio.ini        Multi-env config, see `[common]` + `[env:*]`\n```\n\n## Support\n\nSixBack is free and open source. If it kept your speakers out of the\nlandfill and you'd like to say thanks, there's a tip jar via\n[PayPal](https://paypal.me/busware) — entirely optional, and it helps keep\nthe lab stocked with test hardware. A ⭐ on the repo is just as welcome.\n\n## Acknowledgements\n\n- **[atomicobject/heatshrink](https://github.com/atomicobject/heatshrink)** (v0.4.1, ISC) —\n  embedded LZSS compressor vendored under `src/heatshrink/`; SixBack uses it\n  to compress the NVS-persisted JSON stores (presets, inventory, libraries)\n  with a 1.6 KB encoder state, raising the per-stick speaker ceiling.\n- **[julius-d/ueberboese-api](https://github.com/julius-d/ueberboese-api)** —\n  OpenAPI specification of the legacy Bose SoundTouch streaming cloud,\n  reconstructed from observed traffic. It is SixBack's verifiable\n  ground-truth for endpoint shapes, header semantics, and event-body\n  formats (scmudc envelope, NowPlaying structure, kebab-case event\n  types, group/preset XML).  Thanks to **julius-d** for publishing it.\n\n- **[tostmann/ip4knx](https://github.com/tostmann/ip4knx)** — sister\n  project. The dual-path WiFi provisioning (Improv + Captive in\n  parallel) and the system-health / self-ping watchdog pattern are\n  carried over from there.\n\n## Disclaimer\n\nSixBack is an independent open-source project.  It is **not** affiliated\nwith, endorsed by, or sponsored by Bose Corporation.  All references to\nBose products and protocols are nominative, for interoperability with\nhardware their owners have already paid for.  Use at your own risk.\n\n## Licence\n\n[PolyForm Noncommercial 1.0.0](https://polyformproject.org/licenses/noncommercial/1.0.0).\nSee [LICENSE](LICENSE) for the full text and\n[THIRD-PARTY-LICENSES.md](THIRD-PARTY-LICENSES.md) for upstream\ncomponent licences.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftostmann%2Fsixback","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftostmann%2Fsixback","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftostmann%2Fsixback/lists"}