73 lines
4.0 KiB
Markdown
73 lines
4.0 KiB
Markdown
# Sonos Web Controller
|
|
|
|
A local, responsive web interface for Sonos with automatic device discovery through [SoCo](https://github.com/SoCo/SoCo). It controls playback and volume, manages groups, and plays Sonos favorites, items from the current queue, or direct HTTP(S) streams. The application stores no state and does not require a database.
|
|
|
|

|
|
|
|
## Security boundary
|
|
|
|
**This application intentionally has no authentication. Run it exclusively on a trusted LAN and never expose it to the internet through port forwarding, a reverse proxy, or a public IP address.** Any client with access to port 5000 can control every discovered Sonos device. Firewall rules should restrict access to the local management network.
|
|
|
|
## Requirements
|
|
|
|
- Linux x86_64 with Docker Engine and Docker Compose v2
|
|
- Sonos devices and the Docker host on the same LAN/VLAN
|
|
- Multicast/SSDP (UDP 1900) and UPnP traffic allowed between the host and Sonos devices
|
|
|
|
## Start with Docker Compose
|
|
|
|
```bash
|
|
git clone https://git.citr.one/tom/Sonos-Web.git
|
|
cd Sonos-Web
|
|
docker compose up -d --build
|
|
docker compose ps
|
|
```
|
|
|
|
Open `http://<docker-host-ip>:5000`. View logs with `docker compose logs -f`. Stop the application with `docker compose down`.
|
|
|
|
### Why `network_mode: host` is required
|
|
|
|
SoCo discovers devices through SSDP multicast and communicates with them directly through UPnP. Docker's standard bridge network does not reliably forward multicast traffic between the LAN and a container. `network_mode: host` gives the container access to the Linux host's network stack, enabling discovery and device communication. As a result, the Compose configuration does not contain a `ports:` mapping. Host networking in this configuration is supported reliably only on Linux.
|
|
|
|
## Usage
|
|
|
|
- **Rooms:** Select "Search rooms" to refresh device discovery. Playback, pause, stop, and volume controls are available on every room card. Status and current track information refresh every ten seconds.
|
|
- **Groups:** Select a coordinator, choose one or more members, and select "Create group." Use "Leave group" to remove a zone from its group.
|
|
- **Music:** Select a room and play a favorite or queue item. Under "Stream URL," enter a complete `http://` or `https://` URL. Whether a stream can be played depends on its format and whether the Sonos player can reach it.
|
|
|
|
## Configuration
|
|
|
|
| Variable | Default | Description |
|
|
|---|---:|---|
|
|
| `SONOS_DISCOVERY_TIMEOUT` | `5` | Discovery timeout in seconds |
|
|
| `SONOS_OPERATION_TIMEOUT` | `8` | SoCo HTTP/SOAP request timeout in seconds |
|
|
| `SONOS_MAX_JSON_BYTES` | `16384` | Maximum JSON request body size in bytes |
|
|
| `LOG_LEVEL` | `INFO` | Python log level |
|
|
|
|
Timeout values must be positive and finite. `SONOS_OPERATION_TIMEOUT` configures the SoCo HTTP/SOAP request timeout. A high-level operation may contain several bounded requests and can therefore take longer overall. Device calls are not left running in the background after an artificial future timeout. The discovery cache is locked only during short in-memory operations. Status requests run independently for each device in a short-lived pool limited to eight threads, so an unreachable room does not prevent status data from being returned for other rooms. Error responses contain a machine-readable `error` field and a German `message` intended for the current user interface.
|
|
|
|
The `GET /health` endpoint intentionally checks only the web process. Missing or unreachable speakers do not mark the container as unhealthy.
|
|
|
|
## Local development and testing
|
|
|
|
Use Python 3.12:
|
|
|
|
```bash
|
|
python -m venv .venv
|
|
. .venv/bin/activate
|
|
pip install -r requirements.txt -r requirements-dev.txt
|
|
pytest -q
|
|
python -m compileall -q app tests
|
|
```
|
|
|
|
The tests use mocks exclusively. They do not run discovery or contact real speakers.
|
|
|
|
## API overview
|
|
|
|
- `GET /api/zones`
|
|
- `POST /api/zones/<uid>/transport` (`play`, `pause`, `stop`)
|
|
- `PUT /api/zones/<uid>/volume`
|
|
- `POST /api/groups`, `DELETE /api/groups/<uid>`
|
|
- `GET /api/zones/<uid>/favorites`, `GET /api/zones/<uid>/queue`
|
|
- `POST /api/zones/<uid>/play/favorite|queue|stream`
|