- udev/99-ambilight.rules: symlink /dev/ambilight ancorato alla porta USB fisica (il CH340 clone non ha iSerial, quindi by-id non e' univoco), ID_MM_DEVICE_IGNORE, TAG+=uaccess - setup-udev.sh: rileva VID/PID/porta, installa la regola, verifica il symlink - backend: fallback di porta (config -> /dev/ambilight -> /dev/ttyUSB0) e campo 'dev' nello stato D-Bus - README: tabella dei percorsi, prove sull'indicatore di condivisione schermo e le tre alternative (portal, KWin ScreenShot2, kmsgrab)
144 lines
6.5 KiB
Markdown
144 lines
6.5 KiB
Markdown
# ambilight-plasmoid
|
||
|
||
Ambilight per KDE Plasma 6: cattura il bordo superiore dello schermo su **Wayland**
|
||
(portal ScreenCast + PipeWire), calcola il colore medio di 47 zone e pilota una
|
||
striscia **WS2812B** collegata a un **ESP8266 (Wemos D1 mini)** con firmware
|
||
**Adalight** via seriale. Il controllo sta in un **plasmoide nel vassoio di sistema**,
|
||
con popup per luminosità, contrasto, saturazione e banda campionata.
|
||
|
||
```
|
||
schermo ──portal──▶ ambilightd ──Adalight/seriale──▶ ESP8266 ──▶ striscia
|
||
▲
|
||
│ D-Bus org.enne2.Ambilight
|
||
│
|
||
ambilight-ctl ◀── plasmoide (Plasma 6, QML)
|
||
```
|
||
|
||
## Componenti
|
||
|
||
| File | Ruolo |
|
||
|---|---|
|
||
| `backend/ambicapture.py` | negoziazione ScreenCast portal + pipeline GStreamer (`pipewiresrc`) |
|
||
| `backend/ambilightd.py` | demone: cattura, calcolo zone, luminosità/contrasto/saturazione, invio Adalight, servizio D-Bus |
|
||
| `backend/ambilight-ctl` | CLI che parla col demone e stampa JSON (usata dal plasmoide) |
|
||
| `backend/ambilight.service` | unit `systemd --user` (avvio automatico al login) |
|
||
| `package/org.enne2.ambilight/` | plasmoide Plasma 6 (icona nel vassoio + popup con slider) |
|
||
| `install.sh` / `uninstall.sh` | installazione e rimozione, senza root |
|
||
|
||
## Hardware
|
||
|
||
- ESP8266 Wemos D1 mini (clone), firmware **Adalight-FastLED** con `PIN_DATA 5` (= D1/GPIO5),
|
||
`Num_Leds` = dimensione buffer (256): il numero reale di LED lo decide il software che invia i frame.
|
||
- Striscia WS2812B **47 LED** sul solo bordo superiore.
|
||
- Cablaggio: `D1 → 470 Ω → DIN`, alimentatore 5 V separato (~60 mA/LED), **massa comune**,
|
||
1000 µF sull'alimentazione; level shifter 74AHCT125 se il filo dati è lungo.
|
||
|
||
## Installazione
|
||
|
||
```sh
|
||
./install.sh # backend + servizio utente + plasmoide
|
||
```
|
||
|
||
Al primo avvio il desktop chiede il permesso di **condividere lo schermo**: va concesso
|
||
una volta (viene memorizzato in `~/.cache/ambilight/restore_token`).
|
||
|
||
Il plasmoide appare nel vassoio come **Ambilight** (icona TV); clic per il popup.
|
||
|
||
## Uso da terminale
|
||
|
||
```sh
|
||
ambilight-ctl state # JSON con tutto lo stato
|
||
ambilight-ctl start | stop | toggle
|
||
ambilight-ctl set brightness 0.8 # brightness|contrast|saturation|band|reverse|leds
|
||
ambilight-ctl get fps
|
||
systemctl --user status ambilight.service
|
||
```
|
||
|
||
## Configurazione
|
||
|
||
`~/.config/ambilight/config.json` (scritto dal demone a ogni modifica):
|
||
|
||
| chiave | range | default | note |
|
||
|---|---|---|---|
|
||
| `brightness` | 0.05 – 2.0 | 1.0 | moltiplicatore |
|
||
| `contrast` | 0.5 – 2.0 | 1.0 | pivot sul grigio 128 |
|
||
| `saturation` | 0.0 – 2.0 | 1.0 | 0 = bianco e nero |
|
||
| `band` | 0.02 – 0.5 | 0.12 | frazione di altezza campionata |
|
||
| `reverse` | bool | true | LED 0 sul lato destro (verificato a schermo) |
|
||
| `leds` | 1 – 256 | 47 | LED della striscia |
|
||
| `port`, `baud` | – | `/dev/ttyUSB0`, 115200 | seriale |
|
||
| `side` | top/bottom | top | bordo campionato |
|
||
| `auto_start` | bool | true | streaming attivo all'avvio del demone |
|
||
|
||
## Prestazioni misurate
|
||
|
||
- 47 LED, Adalight a 115200: ~**78 fps** (limite teorico della seriale per 147 byte/frame).
|
||
- Cattura: portal + PipeWire, pipeline ridotta a `47×4 × 72` px RGB → costo CPU trascurabile.
|
||
|
||
|
||
## Nome stabile del dispositivo seriale
|
||
|
||
La scheda (CH340, `1a86:7523`) **non espone un iSerial**: verificato con
|
||
`cat /sys/bus/usb/devices/1-2/serial` → vuoto. Di conseguenza:
|
||
|
||
| percorso | valore su questa macchina | unico? |
|
||
|---|---|---|
|
||
| `/dev/ttyUSB0` | assegnato in ordine di enumerazione | no |
|
||
| `/dev/serial/by-id/usb-1a86_USB_Serial-if00-port0` | derivato da `ID_SERIAL=1a86_USB_Serial` | **no** (due cloni identici collidono) |
|
||
| `/dev/serial/by-path/pci-0000:65:00.3-usb-0:2:1.0-port0` | legato alla porta USB fisica | sì |
|
||
| `/dev/ambilight` (regola udev) | symlink creato da noi | sì |
|
||
|
||
Installa la regola (richiede sudo, una volta):
|
||
|
||
```sh
|
||
sudo ./setup-udev.sh /dev/ttyUSB0 # rileva la porta fisica e scrive /etc/udev/rules.d/99-ambilight.rules
|
||
ambilight-ctl set port /dev/ambilight
|
||
systemctl --user restart ambilight.service
|
||
```
|
||
|
||
La regola aggancia `KERNELS=="<porta>"` + VID/PID (non il numero `ttyUSB`), crea
|
||
`/dev/ambilight`, aggiunge `ID_MM_DEVICE_IGNORE` (ModemManager smette di sondare la
|
||
porta) e `TAG+="uaccess"` (accesso ACL all'utente attivo). Se sposti la scheda su
|
||
un'altra porta USB il nome non segue: rilancia `setup-udev.sh`.
|
||
|
||
Il demone comunque ripiega su `/dev/ambilight` e poi `/dev/ttyUSB0` se il percorso
|
||
configurato non esiste, e riporta il dispositivo realmente aperto nel campo `dev`
|
||
di `ambilight-ctl state`.
|
||
|
||
## L'indicatore di condivisione schermo nel vassoio
|
||
|
||
Con la cattura via **portal ScreenCast**, KDE mostra nel vassoio un **pallino rosso**
|
||
finché la sessione di condivisione è attiva. Verificato su questa macchina:
|
||
|
||
- compare/scompare insieme allo streaming (diff di due screenshot: cambia solo quella
|
||
regione del vassoio, ~1.500 px in 172x22);
|
||
- **non** è un plasmoide di sistema (`/usr/share/plasma/plasmoids` non contiene nulla
|
||
di simile) e **non** compare in `knownItems`/`extraItems` del vassoio, quindi non
|
||
esiste la voce "Configure System Tray → Entries" per nasconderlo: è un indicatore
|
||
di stato del shell, per scelta di KDE.
|
||
|
||
Le alternative per non averlo:
|
||
|
||
1. **KWin ScreenShot2** (nessun portal, nessun indicatore): KWin autorizza solo i
|
||
processi con un `.desktop` che dichiara `X-KDE-DBUS-Restricted-Interfaces=org.kde.KWin.ScreenShot2`.
|
||
Provato con un `.desktop` utente (`Exec=/usr/bin/python3` e `python3.14`) + `kbuildsycoca6`:
|
||
risponde ancora `org.kde.KWin.ScreenShot2.Error.NoAuthorized`. Non provato l'entry
|
||
in `/usr/local/share/applications` (richiede sudo). Se funziona, la cattura è un
|
||
`CaptureArea` della sola fascia (nessun indicatore, ma costo CPU per frame).
|
||
2. **kmsgrab** (`ffmpeg -f kmsgrab`) da un servizio privilegiato con `CAP_SYS_ADMIN`:
|
||
nessun portal e nessun indicatore, ma cattura DRM grezza (piano/format legati alla
|
||
GPU) e un servizio di sistema: più fragile.
|
||
3. Tenere il portal: costo GPU minimo, ~78 fps, indicatore visibile.
|
||
|
||
## Note
|
||
|
||
- Il firmware Adalight manda `Ada\n` una volta al secondo quando non riceve frame: è
|
||
l'heartbeat del protocollo, non un errore.
|
||
- Il demone tiene la seriale in esclusiva: Prismatik (o altri programmi Adalight) vanno chiusi.
|
||
- `ModemManager` su Fedora può contendere `/dev/ttyUSB0`: se la porta risulta occupata
|
||
serve una regola udev con `ID_MM_DEVICE_IGNORE`.
|
||
|
||
## Licenza
|
||
|
||
GPL-3.0-or-later (come i componenti KDE con cui interagisce).
|