# 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ì | > **Su questa macchina la regola NON è installata**: scelta esplicita dell'utente di > lasciare `ttyUSB0` come assegnato dal kernel. I file restano nel repo come opzione. 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==""` + 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. **Il pallino è uno StatusNotifierItem pubblicato da `xdg-desktop-portal-kde`** (titolo "Trasmissione dello schermo"): quindi è un elemento del vassoio con visibilità configurabile da *Configura il vassoio di sistema → Voci* (impostandolo su "Sempre nascosto" sparisce dal pannello e resta nel popup espanso). Le alternative per non averlo: 1. **KWin ScreenShot2** (nessun portal, nessun indicatore) — **rifiutato su questa macchina**: testato sia con `.desktop` utente sia con entry system-wide in `/usr/local/share/applications` (`Exec=/usr/bin/python3`, `X-KDE-DBUS-Restricted-Interfaces=org.kde.KWin.ScreenShot2`, `kbuildsycoca6`): la risposta resta `org.kde.KWin.ScreenShot2.Error.NoAuthorized`. 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`) con `CAP_SYS_ADMIN`: **provato e non praticabile su questa macchina** (Radeon 780M + KWin). Il piano primario è `plane-3` (id 206), `crtc-0`, `fb=463`, `format=AR30` (`0x30335241`) con `modifier=0x200000010437b04` (AMD DCC/tiled): ffmpeg risponde `Framebuffer pixel format 30335241 is not a known supported format` e fallisce **prima** di applicare `-format`/`-format_modifier` (provati `bgr0`, `rgb0`, `0rgb`, `bgra`, `x2rgb10le`, `-format_modifier 0`, `-plane_id 206`). Anche superando il controllo, il layout tiled/DCC richiederebbe un import GBM/EGL o VAAPI, che kmsgrab non fa. 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).