From db476408469702288924d7038209b53c2afd96ed Mon Sep 17 00:00:00 2001 From: enne2 Date: Sun, 1 Nov 2026 13:16:08 +0100 Subject: [PATCH] Nome stabile della seriale via udev e note sull'indicatore di cattura - 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) --- README.md | 55 +++++++++++++++++++++++++++++++++++++ backend/ambilightd.py | 24 ++++++++++++++-- install.sh | 1 + setup-udev.sh | 61 +++++++++++++++++++++++++++++++++++++++++ udev/99-ambilight.rules | 15 ++++++++++ 5 files changed, 154 insertions(+), 2 deletions(-) create mode 100755 setup-udev.sh create mode 100644 udev/99-ambilight.rules diff --git a/README.md b/README.md index ab0935d..1c47186 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,61 @@ systemctl --user status ambilight.service - 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==""` + 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: è diff --git a/backend/ambilightd.py b/backend/ambilightd.py index 969aed1..ca6fbe7 100755 --- a/backend/ambilightd.py +++ b/backend/ambilightd.py @@ -87,16 +87,32 @@ def adalight_frame(pixels): class Strip: - """Porta seriale con apertura pigra e riconnessione.""" + """Porta seriale con apertura pigra, nome stabile e riconnessione. + + Il percorso configurato ha la precedenza; se non esiste si prova il symlink + udev /dev/ambilight e infine /dev/ttyUSB0 (primo disponibile). + """ def __init__(self, port, baud): self.port, self.baud = port, baud self.ser = None + self.resolved = port self.error = "" + def _candidates(self): + seen = [] + for p in (self.port, "/dev/ambilight", "/dev/ttyUSB0"): + if p and p not in seen: + seen.append(p) + return [p for p in seen if os.path.exists(p)] + def _open(self): import serial - self.ser = serial.Serial(self.port, self.baud, timeout=0.2, write_timeout=0.2) + cands = self._candidates() + if not cands: + raise RuntimeError(f"nessun dispositivo seriale fra {self.port}, /dev/ambilight, /dev/ttyUSB0") + self.resolved = cands[0] + self.ser = serial.Serial(self.resolved, self.baud, timeout=0.2, write_timeout=0.2) try: self.ser.setDTR(False) self.ser.setRTS(False) @@ -134,6 +150,7 @@ class State: def __init__(self, cfg): self.lock = threading.Lock() self.cfg = cfg + self.strip_dev = cfg.get("port", "") self.running = bool(cfg.get("auto_start", True)) self.fps = 0.0 self.stream_error = "" @@ -146,6 +163,7 @@ class State: d["fps"] = round(self.fps, 1) d["stream_error"] = self.stream_error d["last_error"] = self.last_error + d["dev"] = getattr(self, "strip_dev", "") return d def update(self, **kw): @@ -229,6 +247,7 @@ def capture_loop(state, strip): if cfg["reverse"]: px.reverse() + state.strip_dev = strip.resolved if strip.write(adalight_frame(px)): sent += 1 now = time.time() @@ -400,6 +419,7 @@ def main(): cfg = load_config() state = State(cfg) strip = Strip(cfg["port"], cfg["baud"]) + state.strip_dev = strip.resolved svc = DBusService(state, strip) th = threading.Thread(target=capture_loop, args=(state, strip), daemon=True) th.start() diff --git a/install.sh b/install.sh index c70340a..0af7e34 100755 --- a/install.sh +++ b/install.sh @@ -48,6 +48,7 @@ install -d "$SYSDIR" install -m 644 "$HERE/backend/ambilight.service" "$SYSDIR/ambilight.service" systemctl --user daemon-reload systemctl --user enable --now ambilight.service +systemctl --user restart ambilight.service # applica eventuali aggiornamenti del backend # ---------------------------------------------------------------- plasmoide say "Installo il plasmoide $APPLET_ID" diff --git a/setup-udev.sh b/setup-udev.sh new file mode 100755 index 0000000..2ad60a5 --- /dev/null +++ b/setup-udev.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# Installa la regola udev che da' un nome stabile alla scheda Adalight (/dev/ambilight). +# Richiede sudo. Verifica il percorso USB della scheda e lo sostituisce se necessario. +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +RULE="$HERE/udev/99-ambilight.rules" +DEST="/etc/udev/rules.d/99-ambilight.rules" +DEV="${1:-/dev/ttyUSB0}" + +say() { printf '\033[1;32m==>\033[0m %s\n' "$*"; } +die() { printf '\033[1;31m[x]\033[0m %s\n' "$*" >&2; exit 1; } + +# --- identifica la scheda e il suo percorso USB +[ -e "$DEV" ] || die "dispositivo $DEV assente" +VID=$(udevadm info -q property -n "$DEV" | sed -n 's/^ID_VENDOR_ID=//p') +PID=$(udevadm info -q property -n "$DEV" | sed -n 's/^ID_MODEL_ID=//p') +KER=$(udevadm info -q property -n "$DEV" | sed -n 's|^DEVPATH=.*usb[0-9]*/\([0-9-]*\)/.*|\1|p') +SER=$(cat "/sys/bus/usb/devices/$KER/serial" 2>/dev/null || true) +say "scheda su $DEV: USB $VID:$PID, porta fisica «$KER», iSerial='$SER'" +[ -n "$KER" ] || die "non riesco a ricavare il percorso USB (KERNELS)" + +if [ "$VID:$PID" != "1a86:7523" ]; then + printf '\033[1;33m[!]\033[0m ATTENZIONE: non e'"'"' un CH340 (1a86:7523). Adatto la regola comunque.\n' +fi + +# --- prepara la regola con la porta reale +TMP=$(mktemp) +sed -e "s|KERNELS==\"1-2\"|KERNELS==\"$KER\"|" \ + -e "s|ATTRS{idVendor}==\"1a86\"|ATTRS{idVendor}==\"$VID\"|" \ + -e "s|ATTRS{idProduct}==\"7523\"|ATTRS{idProduct}==\"$PID\"|" \ + "$RULE" > "$TMP" +say "regola da installare:" +sed 's/^/ /' "$TMP" + +# --- installa e ricarica +sudo install -m 644 -o root -g root "$TMP" "$DEST" +rm -f "$TMP" +sudo udevadm control --reload-rules +sudo udevadm trigger --subsystem-match=tty +sleep 1 + +# --- verifica +if [ -L /dev/ambilight ]; then + say "OK: /dev/ambilight -> $(readlink -f /dev/ambilight)" +else + die "/dev/ambilight non creato: controlla «udevadm test /sys/class/tty/$(basename "$DEV")»" +fi + +cat <