150 lines
3.0 KiB
Markdown
150 lines
3.0 KiB
Markdown
# turnup
|
||
|
||
A lightweight daemon that bridges a USB serial device (physical knobs and
|
||
buttons) to PipeWire/PulseAudio on Linux. Map each knob to per-sink,
|
||
per-source, or per-application volume; assign buttons to mute toggles or
|
||
arbitrary shell commands.
|
||
|
||
## Requirements
|
||
|
||
- Python 3.10+
|
||
- [pyserial](https://pypi.org/project/pyserial/)
|
||
- [pulsectl](https://pypi.org/project/pulsectl/)
|
||
- PipeWire (with `pipewire-pulse`) or PulseAudio
|
||
- `playerctl`
|
||
|
||
## Installation
|
||
|
||
### Arch Linux (AUR)
|
||
|
||
```sh
|
||
yay -S turn-up-arch
|
||
```
|
||
|
||
Or manually:
|
||
|
||
```sh
|
||
git clone https://aur.archlinux.org/turn-up-arch.git
|
||
cd turn-up-arch
|
||
makepkg -si
|
||
```
|
||
|
||
### From source
|
||
|
||
```sh
|
||
git clone https://github.com/sean351/turn-up-arch.git
|
||
cd turn-up-arch
|
||
pip install .
|
||
```
|
||
|
||
## Configuration
|
||
|
||
On first run `turnupd` writes a default config to
|
||
`~/.config/turnup/config.toml`. Edit it to match your device layout.
|
||
See [`contrib/config.example.toml`](contrib/config.example.toml) for a
|
||
fully annotated example.
|
||
|
||
```toml
|
||
port = "/dev/ttyACM0"
|
||
baud = 115200
|
||
|
||
[leds]
|
||
mode = "volume"
|
||
low_color = [255, 0, 0] # red at 0 %
|
||
high_color = [0, 255, 0] # green at 100 %
|
||
|
||
[knobs.0]
|
||
action = "sink_volume"
|
||
target = "default"
|
||
|
||
[knobs.1]
|
||
action = "group_volume"
|
||
targets = ["vlc", "spotify"]
|
||
|
||
[knobs.2]
|
||
action = "app_volume"
|
||
target = "Brave"
|
||
|
||
[knobs.3]
|
||
action = "source_volume"
|
||
target = "default"
|
||
|
||
[buttons.0]
|
||
action = "mute_sink"
|
||
target = "default"
|
||
|
||
[buttons.1]
|
||
action = "command"
|
||
target = "playerctl previous"
|
||
|
||
[buttons.2]
|
||
action = "command"
|
||
target = "playerctl play-pause"
|
||
|
||
[buttons.3]
|
||
action = "command"
|
||
target = "playerctl next"
|
||
|
||
[buttons.4]
|
||
action = "mute_source"
|
||
target = "default"
|
||
```
|
||
|
||
### Knob actions
|
||
|
||
| Action | Description |
|
||
|---|---|
|
||
| `sink_volume` | Output device volume (0–150 %) |
|
||
| `source_volume` | Mic / input volume (0–100 %) |
|
||
| `app_volume` | Single application volume, matched by name or binary |
|
||
| `group_volume` | Multiple applications at once — use `"targets": [...]` |
|
||
|
||
### Button actions
|
||
|
||
| Action | Description |
|
||
|---|---|
|
||
| `mute_sink` | Toggle output mute |
|
||
| `mute_source` | Toggle mic mute |
|
||
| `command` | Run an arbitrary shell command |
|
||
|
||
## Running as a service
|
||
|
||
A systemd user service unit is included:
|
||
|
||
```sh
|
||
# After install via AUR or pip:
|
||
systemctl --user enable --now turnupd.service
|
||
```
|
||
|
||
To view logs:
|
||
|
||
```sh
|
||
journalctl --user -u turnupd -f
|
||
```
|
||
|
||
## Running manually
|
||
|
||
```sh
|
||
turnupd
|
||
```
|
||
|
||
Pass a custom config path with the `TURNUP_CONFIG` environment variable
|
||
*(planned — currently edit `~/.config/turnup/config.json` directly)*.
|
||
|
||
## Hardware
|
||
|
||
The daemon expects a USB-serial device speaking a simple binary protocol:
|
||
|
||
| Frame | Bytes | Description |
|
||
|---|---|---|
|
||
| Heartbeat | `FE 02 FF` | Keepalive |
|
||
| Button | `FE 06/07 <id> FF` | `06` = press, `07` = release |
|
||
| Knob | `FE 03 <id> <hi> <lo> FF` | 10-bit ADC value, big-endian |
|
||
|
||
Tested with an RP2040-based board. Any microcontroller that enumerates as a
|
||
USB-CDC serial port and speaks the protocol above will work.
|
||
|
||
## License
|
||
|
||
MIT — see [LICENSE](LICENSE).
|