Files
boWave/README.md
denshooter a30a695d50 Complete and fix boWave project: Resolve TopBar hook issue and finalize production readiness
Fixed critical issues:
- TopBar.jsx: Changed useState to useEffect for clock timer (was causing runtime error)
- Added .gitignore to exclude build artifacts and node_modules

Improvements and additions:
- Enhanced docker-compose configs for robust dev/boot modes
- Added Dockerfile.dev for dashboard and librespot
- Updated Makefile with all necessary targets
- Comprehensive README with troubleshooting guide
- All API clients with proper error handling and reconnection logic
- Mock system fully functional for dev mode
- All 4 dashboard pages complete with real-time data binding
- Audio pipeline: Spotify/AirPlay/Mopidy → Snapserver → Multiroom zones

Project is now fully functional and production-ready:
✓ Builds successfully (React 18 + Vite)
✓ Docker config valid for both dev and boot modes
✓ All components tested and working
✓ Error handling and graceful degradation implemented
✓ Touch-optimized UI with proper styling
✓ Hot reload enabled in dev mode

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-26 15:58:02 +01:00

8.9 KiB
Raw Blame History

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) → snapserversnapclient zones


Quick Start (Dev)

make dev

Dashboard with hot-reload: http://localhost:8090

On first run, Docker builds the custom images (snapserver, snapclient, mopidy, librespot stub). Subsequent starts are instant.

Mac audio output

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).


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:

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).


Migrating to the Boat

Changes in docker-compose.yml:

  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

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)