Skip to content

nbr23/jacadi

Repository files navigation

jacadi

jacadi is a simple Go HTTP API server that plays WAV audio files through a USB speaker. It is designed to run on a Raspberry Pi or similar with a USB speaker attached, and allow clients to play sounds via HTTP requests. Supports both single-file playback and folder mode for looping ambient audio.

jacadi's main ambition is to serve as a voice proxy to command voice controlled devices (like alexa, ok google, etc).

Quick Start

1. Verify USB Speaker

On your Raspberry Pi, check that the USB speaker is detected:

aplay -l

You should see your USB speaker listed. Note the card number.

2. Build and Run

# Build the Docker image (slim - pre-generated audio only)
docker build --target slim -t jacadi:slim .

# Or build full image with TTS support
docker build --target full -t jacadi:full .

# Build with a specific routeset (defaults to dreame)
docker build --target slim --build-arg ROUTES=mydevice -t jacadi:mydevice .

# Start the service
docker-compose up -d

3. Test

# Health check
curl http://localhost:8080/health

# Play audio (format: /play/{device}/{command})
curl -X POST http://localhost:8080/play/dreame/ok-dream
curl -X POST http://localhost:8080/play/dreame/clean-kitchen

# Start folder (loops infinitely)
curl -X POST http://localhost:8080/play/dreame/ambient

# Stop folder
curl -X POST http://localhost:8080/stop

# Set volume (0-100)
curl -X POST http://localhost:8080/volume \
  -H "Content-Type: application/json" \
  -d '{"volume": 50}'

# Get current volume
curl http://localhost:8080/volume

The full image embeds piper, allowing on the fly TTS through the API:

curl -X POST http://localhost:8080/play/tts \
  -H "Content-Type: application/json" \
  -d '{"text": "Hello world"}'

curl -X POST http://localhost:8080/play/tts \
  -H "Content-Type: application/json" \
  -d '{"text": "Hello world", "voice": "en_US-amy-low"}'

Configuration

Environment Variables

  • EXTRA_ROUTES_PATH: Path to extra routes file for runtime merging (optional)
  • EXTRA_ROUTES_JSON: Inline JSON string of extra routes for runtime merging (optional, merged after EXTRA_ROUTES_PATH)
  • HOST: Listen address (default: 0.0.0.0)
  • PORT: Listen port (default: 8080)
  • AUDIODEV: ALSA device for audio output (e.g., hw:3,0)
  • ALSA_CONTROL: ALSA mixer control name for volume (default: PCM, use Master for internal sound cards)
  • {DEVICE}_VOLUME_OVERRIDE: Force volume for a specific device, ignoring the route config value (e.g., DREAME_VOLUME_OVERRIDE=20). Device name is uppercased.
  • VOICE: Default piper voice model (default: en_US-amy-low)

Route Files

Routes are stored in routes/{name}.json. Use the ROUTES build arg to select a routeset (defaults to dreame).

{
  "dreame": {
    "volume": 80,
    "commands": {
      "ok-dream": { "text": "Okay dream" },
      "clean-kitchen": { "text": "Clean the kitchen" },
      "ambient": { "text": "Ambient music", "type": "folder" },
      "music": { "text": "Background music", "type": "folder", "path": "/music/library" },
      "jingle": { "text": "Jingle loop", "type": "folder", "restart": true }
    }
  }
}
  • Top-level keys are device names (creates /play/{device}/... endpoints)
  • volume: Optional device volume (0-100). When set, playback saves current volume, sets device volume, plays audio, then restores original volume. For folders, volume is set but not restored (folder runs indefinitely).
  • commands: Map of command names to metadata
    • text: Description of the command
    • type: Playback type (omit for single file, "folder" for directory loop)
    • path: Optional custom path for folder directory (overrides default location)
    • restart: Optional boolean for folder commands. When true, always starts from the beginning instead of resuming from last position
  • Audio locations:
    • Single file: assets/audio/{device}/{command}.wav (copied to /audio/ at build time)
    • Folder: assets/audio/{device}/{command}/ directory containing audio files (or custom path)

Extra Routes

Add routes at runtime without rebuilding. Two options:

File mount (EXTRA_ROUTES_PATH) — useful for Docker Compose:

volumes:
  - "./extra_routes.json:/app/extra_routes.json:ro"
  - "./extra_audio:/audio/extra"
environment:
  - EXTRA_ROUTES_PATH=/app/extra_routes.json

Inline JSON (EXTRA_ROUTES_JSON) — useful for Kubernetes ConfigMaps/Secrets (no volume mount required):

environment:
  - EXTRA_ROUTES_JSON={"mydevice":{"commands":{"hello":{"text":"Hello"}}}}

Both can be used simultaneously; EXTRA_ROUTES_PATH is merged first, then EXTRA_ROUTES_JSON. Same device/command keys in EXTRA_ROUTES_JSON override those from EXTRA_ROUTES_PATH. Invalid JSON in EXTRA_ROUTES_JSON logs a warning and is skipped.

Audio files go in /audio/extra/{device}/{command}.wav. For folders, create a directory /audio/extra/{device}/{command}/ containing audio files.

  • Full image: Missing audio files are auto-generated at startup using piper TTS (works for both EXTRA_ROUTES_PATH and EXTRA_ROUTES_JSON)
  • Slim image: You must provide the audio files manually

In docker, ensure /audio/extra is a volume so the generated audio files persist.

Custom Audio Files

Audio files must be WAV format: 44100 Hz, 16-bit, mono.

Convert with ffmpeg:

ffmpeg -i input.wav -ar 44100 -ac 1 -acodec pcm_s16le output.wav

Extras

Home Assistant Config Generator

Generate rest_command configuration for Home Assistant from your routes:

go run cmd/generate-homeassistant/main.go -base-url="http://jacadi.local:8080"

With TTS support (full image only):

go run cmd/generate-homeassistant/main.go -base-url="http://jacadi.local:8080" -tts

Output goes to ha-config/. Include in your Home Assistant configuration.yaml:

rest_command: !include homeassistant_rest.yml
script: !include homeassistant_scripts.yml

Ansible Role

An Ansible role is available at ansible-role-jacadi for deploying the container.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors