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)
This commit is contained in:
enne2
2026-11-01 13:16:08 +01:00
parent a3b6bc66c9
commit db47640846
5 changed files with 154 additions and 2 deletions
+55
View File
@@ -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=="<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: è
+22 -2
View File
@@ -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()
+1
View File
@@ -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"
Executable
+61
View File
@@ -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 <<EOF
Ora rendi persistente il percorso nel demone:
ambilight-ctl set port /dev/ambilight
systemctl --user restart ambilight.service
ambilight-ctl state
Nota: se cambi porta USB alla scheda, il nome resta legato alla porta fisica:
aggiorna KERNELS nella regola oppure usa come alternativa il percorso
/dev/serial/by-path/... (vedi README).
EOF
+15
View File
@@ -0,0 +1,15 @@
# Nome stabile per la scheda Adalight (ESP8266 + CH340), indipendente dal numero ttyUSB.
#
# Perche' non /dev/serial/by-id:
# l'identita' by-id e' usb-1a86_USB_Serial-if00-port0 perche' i cloni CH340 NON
# espongono un iSerial (verificato: /sys/bus/usb/devices/1-2/serial = vuoto). Con due
# adattatori identici quel nome collide. Quindi si àncora il nome alla PORTA FISICA.
#
# Adatta KERNELS al percorso USB della tua scheda (vedi: udevadm info -a -n /dev/ttyUSB0
# | grep KERNELS, oppure ls /dev/serial/by-path/).
SUBSYSTEM=="tty", KERNEL=="ttyUSB*", KERNELS=="1-2", \
ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", \
SYMLINK+="ambilight", \
ENV{ID_MM_DEVICE_IGNORE}="1", \
TAG+="uaccess", MODE="0660", GROUP="dialout"