Files
boWave/README.md

289 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Bordanlage Boat Onboard System
Multiroom audio + navigation dashboard for boats.
Full Docker stack — works on any machine, no hardware required in dev mode.
---
## Stack
| Service | Image / Build | Purpose |
|---|---|---|
| SignalK | `signalk/signalk-server` | NMEA 0183/2000 gateway, WebSocket delta stream |
| Snapserver | custom build (v0.35.0) | Multiroom audio server, JSON-RPC on :1705 |
| zone-salon/cockpit/bug/heck | custom build (v0.35.0) | Audio zones (snapclient) |
| Mopidy + Iris | custom Dockerfile | Local music, TuneIn radio, HTTP control on :6680 |
| librespot | custom build (v0.5.0) | Spotify Connect receiver, pipe backend |
| shairport-sync | `mikebrady/shairport-sync` | AirPlay 2 receiver, pipe backend |
| Jellyfin | `jellyfin/jellyfin` | Media library, :8096 |
| SignalK | `signalk/signalk-server` | Navigation data, demo NMEA in dev mode |
| Portainer | `portainer/portainer-ce` | Docker management UI, :9000 |
| Dashboard | React 18 + Vite | Touch UI, built with Vite (dev: HMR on :8090) |
Audio flow: `librespot / shairport / mopidy` → named pipe (PCM) → `snapserver``snapclient` zones
---
## Quick Start (Dev)
```bash
make dev
# or without make:
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
```
**Dashboard with hot-reload:** http://localhost:8090
**Everything runs in the browser - no additional software needed!**
On first run, Docker builds the custom images (snapserver, snapclient, mopidy, librespot stub). Subsequent starts are instant.
### Browser-Only Testing (Windows/Mac)
All services are accessible via browser:
- **Dashboard (Main UI):** http://localhost:8090 - Full boat control interface
- **Snapcast Web:** http://localhost:1780 - Audio zones + browser playback via Web Audio API
- **Mopidy/Iris:** http://localhost:6680/iris/ - Music player
- **SignalK:** http://localhost:3000 - Navigation backend (admin/bordanlage)
**Audio in browser:** Open Snapcast Web UI → Click 🔊 icon → Enable Web Audio playback!
### Native audio output
**Mac:**
```bash
make mac-audio # runs snapclient natively via Homebrew → Mac speakers
make spotify # runs librespot natively via Homebrew → Spotify Connect on Mac
```
Both commands require `brew install snapcast` / `brew install librespot` (auto-installed if missing).
**Windows:**
```powershell
make windows-audio # runs snapclient natively → Windows speakers (WASAPI)
```
Requires manual install: Download from https://github.com/badaix/snapcast/releases
See [WINDOWS_AUDIO_SETUP.md](WINDOWS_AUDIO_SETUP.md) for detailed Windows setup instructions.
---
## Service URLs
| Service | URL | Notes |
|---|---|---|
| Dashboard (dev) | http://localhost:8090 | Vite HMR, live reload |
| Dashboard (prod) | http://localhost:8080 | nginx-served built bundle |
| SignalK | http://localhost:3000 | Admin: `admin` / `bordanlage` |
| Mopidy/Iris | http://localhost:6680/iris/ | Music player UI |
| Snapcast Web | http://localhost:1780 | Zone control (Snapweb) |
| Jellyfin | http://localhost:8096 | Media library |
| Portainer | http://localhost:9000 | Docker management |
---
## Make Targets
```
make dev # start dev stack (Vite HMR, null audio, SignalK demo data)
make boot # start production stack (full audio pipeline)
make stop # stop dev stack
make stop-boot # stop production stack
make logs # tail logs (dev stack)
make rebuild # force rebuild all images (dev stack)
make status # show container status
make pipes # create audio named pipes in /tmp/audio (for boot mode)
make mac-audio # run snapclient natively on Mac (Homebrew)
make spotify # run librespot natively on Mac (Homebrew, Spotify Connect)
```
---
## Spotify Connect
**Dev mode (Mac):** run `make spotify` — shows as "Bordanlage" in Spotify app.
**Boot mode (boat):** librespot runs inside Docker, pipe backend, writes PCM to `/tmp/audio/spotify.pcm`.
> On Linux (boat), set `network_mode: host` for `librespot` and `shairport` in `docker-compose.yml` for reliable mDNS/Bonjour discovery.
---
## AirPlay
The `shairport` container runs `shairport-sync` with pipe output.
1. On iPhone/Mac: Control Center → AirPlay → select **"Bordanlage AirPlay"**
2. Audio goes through the snapserver multiroom pipeline
Config: `config/shairport.conf`
---
## Adding Music (Mopidy)
Drop files into `./music/`. Trigger a library scan:
```bash
curl -s http://localhost:6680/mopidy/rpc \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"local.scan"}'
```
---
## Zones
Four zones are pre-configured:
| Zone | hostID | Production use |
|---|---|---|
| Salon | zone-salon | Below-deck salon speakers |
| Cockpit | zone-cockpit | Cockpit/helm area |
| Bug | zone-bug | Forward cabin (Bug = bow in German) |
| Heck | zone-heck | Stern (Heck = stern in German) |
Each zone runs `snapclient` connecting to `snapserver` on the Docker internal network.
In dev mode the zones use `--player file:filename=null` (audio discarded, no hardware needed).
In production, replace with `--player alsa --soundcard hw:N,0` and expose `/dev/snd`.
---
## Environment Variables
Copy `.env.example` to `.env` and adjust:
```
SPOTIFY_NAME=Bordanlage
SPOTIFY_BITRATE=320
MUSIC_PATH=./music
VITE_USE_MOCK=false # true = force mock data in dashboard, false = real APIs
```
---
## Dashboard API Configuration
The dashboard reads hosts from environment at build time:
| Variable | Default | Description |
|---|---|---|
| `VITE_SNAPCAST_HOST` | `localhost` | Snapserver hostname |
| `VITE_SIGNALK_HOST` | `localhost` | SignalK hostname |
| `VITE_MOPIDY_HOST` | `localhost` | Mopidy hostname |
| `VITE_JELLYFIN_HOST` | `localhost` | Jellyfin hostname |
| `VITE_USE_MOCK` | `false` | Force mock data (no real APIs) |
---
## Architecture: Named Pipes
In **boot mode**, audio flows through named pipes on a tmpfs volume:
```
spotify.pcm ← librespot (Spotify Connect)
airplay.pcm ← shairport (AirPlay 2)
mopidy.pcm ← mopidy (local files, radio)
```
Snapserver reads all three as streams and routes them to zones.
Run `make pipes` once before `make boot` to create the pipes (or they are created automatically).
In **dev mode**, pipes are not used (no audio hardware crosses the Docker VM boundary on Mac).
---
## Deployment to Boat (Ubuntu Server)
This system is designed to run on a boat with an Ubuntu server in kiosk mode.
**See detailed guide:** [KIOSK_SETUP.md](KIOSK_SETUP.md)
### Quick Summary
Changes in `docker-compose.yml` for production:
1. **Zone audio output** — replace `--player file:filename=null` with `--player alsa --soundcard hw:N,0`, uncomment `/dev/snd` device
2. **Spotify / AirPlay discovery** — set `network_mode: host` for `librespot` and `shairport`
3. **NMEA hardware** — uncomment the `devices` block under `signalk`, configure the connection at http://[boat-ip]:3000
4. **Video decoding** (optional) — uncomment `/dev/dri` under `jellyfin`
5. Use `make boot` instead of `make dev`
### Kiosk Mode (Chromium Fullscreen)
```bash
chromium-browser --kiosk http://localhost:8080
```
See [KIOSK_SETUP.md](KIOSK_SETUP.md) for complete Ubuntu server setup with autostart.
---
## Troubleshooting
**Zones stuck restarting:**
- Check `docker compose logs zone-salon`. With snapclient v0.35+, the server URI must be `tcp://snapserver` (not `--host snapserver`).
- If "No audio player support": snapclient needs `--player file:filename=null` in dev (null player not compiled in the bookworm .deb).
**Dashboard crashes with exit 127 / npm not found:**
- The local `node:20-alpine` image was overwritten by the production dashboard build. Fix: `docker rmi node:20-alpine && make dev`.
- Root cause prevented by `dashboard/Dockerfile.dev` — dev uses its own build context.
**Shairport config syntax error:**
- Boolean values in `config/shairport.conf` must be quoted: `"yes"` not `yes`.
**Spotify device not visible:**
- On Mac, Docker Desktop bridges UDP poorly. Use `make spotify` (native librespot on Mac) for dev.
- On boat: use `network_mode: host` in `docker-compose.yml`.
**Librespot production build fails:**
- `cargo install librespot` (latest = v0.8.0) has a vergen_lib dependency conflict.
- Fixed by pinning to `--version "=0.5.0"` in `docker/librespot/Dockerfile`.
**Snapcast arm64 .deb not found:**
- v0.27.0 has no arm64 release. Use v0.35.0 which ships `arm64_bookworm.deb`.
**Port conflicts (e.g. 8080 taken by Supabase):**
- Dev dashboard is on **8090**, production on 8080. Change port mappings in `docker-compose.dev.yml` as needed.
---
## Project Layout
```
.
├── Makefile
├── docker-compose.yml # production stack
├── docker-compose.dev.yml # dev overrides (Vite HMR, null zones, SignalK demo)
├── config/
│ ├── snapserver.conf # pipe sources, HTTP API config
│ ├── mopidy.conf # music backends, HTTP server
│ └── shairport.conf # AirPlay name, pipe output
├── docker/
│ ├── snapserver/Dockerfile # snapcast v0.35.0 from GitHub releases
│ ├── snapclient/Dockerfile # snapcast v0.35.0 from GitHub releases
│ ├── mopidy/Dockerfile # mopidy + iris + local + tunein
│ └── librespot/
│ ├── Dockerfile # production: cargo install librespot v0.5.0
│ └── Dockerfile.dev # dev stub: alpine + sleep
├── dashboard/
│ ├── Dockerfile # production: Vite build → nginx
│ ├── Dockerfile.dev # dev: node:20-alpine base (avoid image overwrite)
│ ├── nginx.conf
│ └── src/
│ ├── api/ # real API clients (snapcast, signalk, mopidy, jellyfin)
│ ├── mock/ # mock data (VITE_USE_MOCK=true)
│ ├── hooks/ # useNMEA, useZones, usePlayer, useDocker
│ └── components/
│ ├── layout/ # TopBar, TabNav
│ ├── audio/ # ZoneCard, ZoneGrid, NowPlaying, SourcePicker
│ └── nav/ # InstrumentPanel, ChartPlaceholder
├── scripts/
│ ├── init-pipes.sh # create named pipes in /tmp/audio
│ ├── setup-dev.sh
│ └── setup-boot.sh
└── music/ # drop audio files here (Mopidy + Jellyfin)
```