Mercury

"swift messenger"

BLE client daemon written in go. scans for janus, connects over bluetooth low energy, reads GATT characteristics, and pushes data to redis. the invisible bridge between your phone and the car thing. fast, resilient, reliable.

data flow

    ┌─────────────┐          BLE           ┌──────────────┐
    │    Janus    │◄──────────────────────►│   Mercury    │
    │ (Android)   │      GATT Protocol      │  (Go daemon) │
    │             │                         │              │
    │ Advertises: │                         │ Scans for:   │
    │  "Janus"    │                         │  "Janus"     │
    │             │                         │              │
    │ Exposes:    │        Reads:           │              │
    │  0x2001 ────┼────────► Track Title    │              │
    │  0x2002 ────┼────────► Artist         │              │
    │  0x2003 ────┼────────► Album          │              │
    │  0x2004 ────┼────────► State/Position │              │
    │  0x2005 ────┼────────► Album Art      │              │
    │  0x2006 ────┼────────► Lyrics         │              │
    └─────────────┘                         └──────┬───────┘
                                                   │
                                                   │ Redis Protocol
                                                   │ (localhost:6379)
                                                   ▼
                                            ┌──────────────┐
                                            │    Redis     │
                                            │  Data Store  │
                                            │              │
                                            │ Keys:        │
                                            │  media:title │
                                            │  media:artist│
                                            │  media:album │
                                            │  media:state │
                                            │  media:art   │
                                            │  media:lyrics│
                                            └──────────────┘

mercury acts as a translator between the BLE world and the redis world. it continuously polls janus for updates and pushes changes to redis where llizard plugins can read them.

features

intelligent scanning

scans for BLE devices advertising as "Janus". caches the MAC address for faster reconnection. stops scanning once connected to save battery. resumes scanning if connection drops.

chunked album art

BLE has a 512-byte MTU limit. janus splits album art into chunks. mercury reads all chunks sequentially, reassembles the base64 string, and stores it in redis as a complete image.

automatic reconnection

handles bluetooth signal loss gracefully. retries connection with exponential backoff. clears stale data from redis when disconnected. notifies llizard plugins of connection state changes.

implementation

go libraries

  • tinygo-org/bluetooth - BLE stack for Linux
  • go-redis/redis - Redis client
  • sirupsen/logrus - Structured logging

polling interval

mercury polls janus characteristics every 500ms. this balances responsiveness with battery usage. characteristics that change frequently (playback position) are cached with a 250ms invalidation window to reduce BLE traffic.

redis keys

Redis KeyValue TypeExample
media:titlestring"Bohemian Rhapsody"
media:artiststring"Queen"
media:albumstring"A Night at the Opera"
media:stateJSON{playing: true, pos: 124}
media:albumartbase64iVBORw0KGgoAAAA...
media:lyricsLRC[00:12.00]Is this...

building mercury

mercury can be built for desktop (development) or cross-compiled for the car thing (ARMv7).

desktop build

bashbuild-desktop.sh
# Install dependencies (Linux)
sudo apt install bluez libbluetooth-dev

# Build
go build -o mercury ./cmd/mercury

# Run
./mercury

cross-compile for car thing

bashcross-compile.sh
# Set environment for ARMv7
export GOOS=linux
export GOARCH=arm
export GOARM=7
export CC=arm-linux-musleabihf-gcc
export CGO_ENABLED=1

# Build
go build -o mercury-arm ./cmd/mercury

# Copy to device
scp mercury-arm root@172.16.42.2:/usr/local/bin/mercury
on llizardOS, mercury runs as a runit service and starts automatically at boot. it requires bluetooth to be enabled and the bluez daemon to be running.

configuration

mercury reads configuration from /etc/mercury.conf

bash/etc/mercury.conf
# BLE settings
SCAN_TIMEOUT=30s
POLL_INTERVAL=500ms
RECONNECT_BACKOFF=5s

# Redis settings
REDIS_HOST=localhost:6379
REDIS_PASSWORD=
REDIS_DB=0

# Logging
LOG_LEVEL=info
LOG_FILE=/var/log/mercury/current

troubleshooting

mercury can't find janus

  • • ensure janus app is running on your android device
  • • check that bluetooth is enabled on both devices
  • • verify janus has notification listener permission
  • • try restarting bluetooth: sv restart bluetooth

connection keeps dropping

  • • BLE has limited range (~10 meters). move devices closer
  • • check for bluetooth interference from other devices
  • • verify phone isn't in aggressive battery saving mode

no data in redis

  • • check mercury logs: tail -f /var/log/mercury/current
  • • verify redis is running: sv status redis
  • • test redis connection: redis-cli ping
view on githublearn about janus