Skip to content

CLI and daemon to control Devialet Phantom volume over local IP API (CEC + keyboard), built with uv.

License

Notifications You must be signed in to change notification settings

clementperon/devialet-phantom-ctl

Repository files navigation

Devialet Phantom Control (IP Control + uv)

Coverage

Python CLI and daemon to control Devialet Phantom volume over local HTTP API.

Use cases:

  • scriptable local volume control
  • Raspberry Pi bridge
  • TV remote volume bridge over HDMI-CEC
  • foundation for IR / keyboard / Home Assistant adapters

Features

  • mDNS discovery (_whatsup._tcp.local) merged with UPnP discovery
  • volume commands: getvol, setvol, volup, voldown, mute
  • target selection with --system <name> (preferred for multi-device setups)
  • manual target override (--ip, --port)
  • long-running daemon mode (daemon --input cec)
  • keyboard test mode (daemon --input keyboard)
  • typed config via TOML + env overrides
  • packaged with uv

Requirements

For HDMI-CEC daemon mode:

  • Linux CEC framework device available (typically /dev/cec0)
  • CEC-capable adapter/device path (commonly Raspberry Pi HDMI or USB-CEC adapter)

Install

git clone <repo>
cd dvlt-volume
uv sync

CLI Usage

List discovered speakers:

uv run devialetctl list

Read current volume:

uv run devialetctl getvol

Relative commands are precise:

  • volup increases volume by +1
  • voldown decreases volume by -1

Set volume:

uv run devialetctl setvol 35

Use explicit target:

uv run devialetctl --ip 192.168.1.42 --port 80 getvol

Use system-name target selection (from tree output):

uv run devialetctl --system "TV" getvol

Daemon (CEC Input)

Run daemon with config:

uv run devialetctl daemon --input cec

Override daemon CEC settings for one run (global options stay before subcommand):

uv run devialetctl --system "TV" daemon --input cec --cec-vendor-compat samsung

The daemon:

  • consumes CEC key events from Linux CEC (/dev/cec0, ioctl backend)
  • normalizes to volume actions
  • answers GIVE_AUDIO_STATUS (0x71) with REPORT_AUDIO_STATUS (0x7A)
  • answers System Audio/ARC requests (0x70, 0x7D, 0xC3, 0xC4)
  • answers REQUEST_SHORT_AUDIO_DESCRIPTOR (0xA4) with REPORT_SHORT_AUDIO_DESCRIPTOR (0xA3)
  • by default, keeps standard CEC behavior (no vendor spoofing)
  • optionally announces DEVICE_VENDOR_ID (0x87) from Audio System when vendor compat requires spoofing
  • with cec_vendor_compat = "samsung", handles Samsung vendor command 0x89:
    • 0x95 (SYNC_TV_VOLUME) -> replies 50:89:95:01:XX
    • unknown/unsupported subcommands -> no response
  • with cec_vendor_compat = "samsung", consumes Samsung vendor command-with-id 0xA0 with no-response policy for unknown payloads
  • applies absolute volume from TV SET_AUDIO_VOLUME_LEVEL (0x73)
  • sends updated REPORT_AUDIO_STATUS (0x7A) after handled volume/mute events
  • applies dedupe/rate-limit policy
  • retries with backoff if adapter/network is temporarily unavailable

Run daemon in a container (CEC mode):

docker run --rm -it \
  --network host \
  --device /dev/cec0:/dev/cec0 \
  ghcr.io/clementperon/devialet-phantom-ctl:latest \
  --system "TV" daemon --input cec --cec-vendor-compat="samsung"

Notes:

  • --device /dev/cec0:/dev/cec0 passes the Linux CEC device into the container.
  • --network host is required for mDNS discovery (_whatsup._tcp.local) from the container.
  • --cec-vendor-compat samsung enables Samsung vendor compatibility mode for this run.
  • alternatively, set DEVIALETCTL_CEC_VENDOR_COMPAT=samsung to make it the environment default.
  • with a fixed target IP (DEVIALETCTL_IP), you can usually run without host networking.

Run daemon with keyboard input (no CEC hardware required):

uv run devialetctl daemon --input keyboard

Keyboard commands:

  • u, +, up -> volume up
  • d, -, down -> volume down
  • m, mute -> toggle mute
  • q, quit, exit -> stop daemon

In interactive terminal mode, single keys (u, d, m, q) work immediately without pressing Enter.

Config File

Default config path:

  • Linux/RPi: $XDG_CONFIG_HOME/devialetctl/config.toml or ~/.config/devialetctl/config.toml
  • macOS: ~/.config/devialetctl/config.toml

Example config.toml:

log_level = "INFO"
cec_device = "/dev/cec0"
cec_osd_name = "Devialet"
cec_vendor_compat = "none"
reconnect_delay_s = 2.0
dedupe_window_s = 0.08
min_interval_s = 0.12

[target]
ip = "192.168.1.42"
port = 80

Use log_level = "DEBUG" (or DEVIALETCTL_LOG_LEVEL=DEBUG) to log raw HDMI-CEC frames:

  • CEC RX frame: ... for received CEC frames from /dev/cec0
  • CEC TX: tx ... for transmitted frames
  • watcher-side external audio-state changed; notified TV ... when Devialet-side state changes are pushed to TV

Environment overrides:

  • DEVIALETCTL_IP
  • DEVIALETCTL_PORT
  • DEVIALETCTL_BASE_PATH
  • DEVIALETCTL_LOG_LEVEL
  • DEVIALETCTL_CEC_DEVICE

CLI target selection notes:

  • --ip and --system are mutually exclusive.
  • list and tree are discovery-only commands and reject --ip / --system.

Kernel CEC permissions note:

  • the daemon user must have read/write access to /dev/cec0 (typically via video group or udev rule)
  • if startup fails with ioctl/device access errors, verify ls -l /dev/cec* and group membership

Service Deployment

Raspberry Pi (systemd)

Create /etc/systemd/system/devialetctl-cec.service:

[Unit]
Description=Devialet CEC volume bridge
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=pi
WorkingDirectory=/home/pi/dvlt-volume
ExecStart=/home/pi/.local/bin/uv run devialetctl daemon --input cec
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

Enable it:

sudo systemctl daemon-reload
sudo systemctl enable --now devialetctl-cec.service
sudo systemctl status devialetctl-cec.service

macOS (LaunchAgent)

Create ~/Library/LaunchAgents/com.local.devialetctl.cec.plist and point it to:

  • your repo directory
  • your uv binary path
  • command uv run devialetctl daemon --input cec

Then:

launchctl unload ~/Library/LaunchAgents/com.local.devialetctl.cec.plist 2>/dev/null || true
launchctl load ~/Library/LaunchAgents/com.local.devialetctl.cec.plist
launchctl list | rg devialetctl

Development

Run tests:

uv run pytest

Architecture Notes

The package is organized in layers:

  • devialetctl.domain: events and policy
  • devialetctl.application: service, routing, daemon orchestration
  • devialetctl.infrastructure: HTTP, mDNS, CEC adapter, config
  • devialetctl.interfaces: CLI wiring

Legacy imports remain available:

  • devialetctl.api.DevialetClient
  • devialetctl.discovery.discover

About

CLI and daemon to control Devialet Phantom volume over local IP API (CEC + keyboard), built with uv.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors 3

  •  
  •  
  •