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:
@@ -75,6 +75,61 @@ systemctl --user status ambilight.service
|
|||||||
- 47 LED, Adalight a 115200: ~**78 fps** (limite teorico della seriale per 147 byte/frame).
|
- 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.
|
- 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
|
## Note
|
||||||
|
|
||||||
- Il firmware Adalight manda `Ada\n` una volta al secondo quando non riceve frame: è
|
- Il firmware Adalight manda `Ada\n` una volta al secondo quando non riceve frame: è
|
||||||
|
|||||||
+22
-2
@@ -87,16 +87,32 @@ def adalight_frame(pixels):
|
|||||||
|
|
||||||
|
|
||||||
class Strip:
|
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):
|
def __init__(self, port, baud):
|
||||||
self.port, self.baud = port, baud
|
self.port, self.baud = port, baud
|
||||||
self.ser = None
|
self.ser = None
|
||||||
|
self.resolved = port
|
||||||
self.error = ""
|
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):
|
def _open(self):
|
||||||
import serial
|
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:
|
try:
|
||||||
self.ser.setDTR(False)
|
self.ser.setDTR(False)
|
||||||
self.ser.setRTS(False)
|
self.ser.setRTS(False)
|
||||||
@@ -134,6 +150,7 @@ class State:
|
|||||||
def __init__(self, cfg):
|
def __init__(self, cfg):
|
||||||
self.lock = threading.Lock()
|
self.lock = threading.Lock()
|
||||||
self.cfg = cfg
|
self.cfg = cfg
|
||||||
|
self.strip_dev = cfg.get("port", "")
|
||||||
self.running = bool(cfg.get("auto_start", True))
|
self.running = bool(cfg.get("auto_start", True))
|
||||||
self.fps = 0.0
|
self.fps = 0.0
|
||||||
self.stream_error = ""
|
self.stream_error = ""
|
||||||
@@ -146,6 +163,7 @@ class State:
|
|||||||
d["fps"] = round(self.fps, 1)
|
d["fps"] = round(self.fps, 1)
|
||||||
d["stream_error"] = self.stream_error
|
d["stream_error"] = self.stream_error
|
||||||
d["last_error"] = self.last_error
|
d["last_error"] = self.last_error
|
||||||
|
d["dev"] = getattr(self, "strip_dev", "")
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def update(self, **kw):
|
def update(self, **kw):
|
||||||
@@ -229,6 +247,7 @@ def capture_loop(state, strip):
|
|||||||
if cfg["reverse"]:
|
if cfg["reverse"]:
|
||||||
px.reverse()
|
px.reverse()
|
||||||
|
|
||||||
|
state.strip_dev = strip.resolved
|
||||||
if strip.write(adalight_frame(px)):
|
if strip.write(adalight_frame(px)):
|
||||||
sent += 1
|
sent += 1
|
||||||
now = time.time()
|
now = time.time()
|
||||||
@@ -400,6 +419,7 @@ def main():
|
|||||||
cfg = load_config()
|
cfg = load_config()
|
||||||
state = State(cfg)
|
state = State(cfg)
|
||||||
strip = Strip(cfg["port"], cfg["baud"])
|
strip = Strip(cfg["port"], cfg["baud"])
|
||||||
|
state.strip_dev = strip.resolved
|
||||||
svc = DBusService(state, strip)
|
svc = DBusService(state, strip)
|
||||||
th = threading.Thread(target=capture_loop, args=(state, strip), daemon=True)
|
th = threading.Thread(target=capture_loop, args=(state, strip), daemon=True)
|
||||||
th.start()
|
th.start()
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ install -d "$SYSDIR"
|
|||||||
install -m 644 "$HERE/backend/ambilight.service" "$SYSDIR/ambilight.service"
|
install -m 644 "$HERE/backend/ambilight.service" "$SYSDIR/ambilight.service"
|
||||||
systemctl --user daemon-reload
|
systemctl --user daemon-reload
|
||||||
systemctl --user enable --now ambilight.service
|
systemctl --user enable --now ambilight.service
|
||||||
|
systemctl --user restart ambilight.service # applica eventuali aggiornamenti del backend
|
||||||
|
|
||||||
# ---------------------------------------------------------------- plasmoide
|
# ---------------------------------------------------------------- plasmoide
|
||||||
say "Installo il plasmoide $APPLET_ID"
|
say "Installo il plasmoide $APPLET_ID"
|
||||||
|
|||||||
Executable
+61
@@ -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
|
||||||
@@ -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"
|
||||||
Reference in New Issue
Block a user