Integra calibrazione RGB nel plasmoide e ripristina backend funzionante
This commit is contained in:
+42
-39
@@ -19,7 +19,6 @@ Config: ~/.config/ambilight/config.json
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
@@ -36,6 +35,9 @@ DEFAULTS = {
|
||||
"brightness": 1.0, # moltiplicatore 0.05 .. 2.0
|
||||
"contrast": 1.0, # 0.5 .. 2.0 (pivot sul grigio 128)
|
||||
"saturation": 1.0, # 0.0 .. 2.0
|
||||
"gain_r": 1.0, # bilanciamento del bianco per canale
|
||||
"gain_g": 1.0,
|
||||
"gain_b": 1.0,
|
||||
"band": 0.12, # frazione di altezza campionata sul bordo
|
||||
"reverse": True, # LED 0 sul lato destro dello schermo (misurato su frigate)
|
||||
"leds": 47,
|
||||
@@ -43,14 +45,15 @@ DEFAULTS = {
|
||||
"baud": 115200,
|
||||
"fps": 60,
|
||||
"side": "top", # top | bottom
|
||||
"capture": "portal", # portal (ScreenCast) | kmsgrab (DRM diretto, senza indicatore)
|
||||
"drm_device": "/dev/dri/card1",
|
||||
"auto_start": True, # streaming attivo all'avvio del demone
|
||||
}
|
||||
PARAM_RANGES = {
|
||||
"brightness": (0.05, 2.0),
|
||||
"contrast": (0.5, 2.0),
|
||||
"saturation": (0.0, 2.0),
|
||||
"gain_r": (0.0, 2.0),
|
||||
"gain_g": (0.0, 2.0),
|
||||
"gain_b": (0.0, 2.0),
|
||||
"band": (0.02, 0.5),
|
||||
}
|
||||
|
||||
@@ -163,6 +166,7 @@ class State:
|
||||
with self.lock:
|
||||
d = dict(self.cfg)
|
||||
d["running"] = self.running
|
||||
d["target_fps"] = d["fps"]
|
||||
d["fps"] = round(self.fps, 1)
|
||||
d["stream_error"] = self.stream_error
|
||||
d["last_error"] = self.last_error
|
||||
@@ -189,60 +193,40 @@ def apply_params(zones, cfg):
|
||||
b = cfg["brightness"]
|
||||
if b != 1.0:
|
||||
z = z * b
|
||||
z = z * np.array([cfg["gain_r"], cfg["gain_g"], cfg["gain_b"]])
|
||||
return np.clip(z, 0, 255)
|
||||
|
||||
|
||||
def capture_loop(state, strip):
|
||||
"""Ciclo di cattura: sceglie il backend (portal o kmsgrab) e invia i frame."""
|
||||
from ambicapture import ChildCapture, KmsgrabCapture
|
||||
from ambicapture import Capture, screencast_stream
|
||||
|
||||
cap = None
|
||||
key = None
|
||||
leds_cfg = None
|
||||
prev = None
|
||||
sent = 0
|
||||
t_win = time.time()
|
||||
|
||||
def build(cfg):
|
||||
"""Crea e avvia il backend; ritorna (cap, descrizione)."""
|
||||
if cfg["capture"] == "kmsgrab":
|
||||
c = KmsgrabCapture(cfg["leds"], fps=cfg["fps"], device=cfg["drm_device"]).start()
|
||||
return c, f"kmsgrab {c.binary} su {cfg['drm_device']}"
|
||||
helper = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ambicatcher.py")
|
||||
c = ChildCapture([sys.executable, helper,
|
||||
"--leds", str(cfg["leds"]), "--rows", "72", "--sub", "4",
|
||||
"--fps", str(int(cfg["fps"])), "--drain"],
|
||||
cfg["leds"]).start()
|
||||
return c, f"portal (processo separato) node={c.header.get('node')} " \
|
||||
f"sorgente {c.header.get('w')}x{c.header.get('h')}"
|
||||
|
||||
last_write = 0.0
|
||||
t_log = time.time()
|
||||
n_none = 0
|
||||
while True:
|
||||
cfg = state.snapshot()
|
||||
if not cfg["running"]:
|
||||
if cap is not None:
|
||||
cap.stop()
|
||||
cap = None
|
||||
key = None
|
||||
prev = None
|
||||
time.sleep(0.15)
|
||||
continue
|
||||
|
||||
want = (cfg["capture"], cfg["leds"], cfg["drm_device"], cfg["fps"])
|
||||
dead = cap is not None and hasattr(cap, "alive") and not cap.alive()
|
||||
if cap is None or want != key or dead:
|
||||
if cap is None or leds_cfg != cfg["leds"]:
|
||||
if cap is not None:
|
||||
err = cap.error_text() if hasattr(cap, "error_text") else ""
|
||||
cap.stop()
|
||||
cap = None
|
||||
if dead:
|
||||
with state.lock:
|
||||
state.stream_error = f"kmsgrab terminato: {err}"[:300]
|
||||
log("kmsgrab terminato:", err)
|
||||
time.sleep(3)
|
||||
continue
|
||||
try:
|
||||
cap, desc = build(cfg)
|
||||
key = want
|
||||
log(f"cattura avviata ({cfg['capture']}): {desc} led={cfg['leds']}")
|
||||
node, w, h = screencast_stream()
|
||||
cap = Capture(node, cfg["leds"], fps=cfg["target_fps"])
|
||||
cap.start()
|
||||
leds_cfg = cfg["leds"]
|
||||
log(f"cattura avviata: node={node} sorgente {w}x{h} led={cfg['leds']}")
|
||||
with state.lock:
|
||||
state.stream_error = ""
|
||||
state.last_error = ""
|
||||
@@ -255,11 +239,17 @@ def capture_loop(state, strip):
|
||||
|
||||
frame = cap.frame(200)
|
||||
if frame is None:
|
||||
n_none += 1
|
||||
time.sleep(0.005)
|
||||
if time.time() - t_log >= 10:
|
||||
log(f"diagnostica: 10 s senza frame (timeout={n_none}), "
|
||||
f"fps={state.fps} sent={sent}")
|
||||
t_log, n_none = time.time(), 0
|
||||
continue
|
||||
|
||||
rows = max(1, int(round(cap.gh * cfg["band"])))
|
||||
gh, gw = frame.shape[0], frame.shape[1]
|
||||
rows = min(max(1, int(round(gh * cfg["band"]))), gh)
|
||||
rows = min(rows, gh)
|
||||
band = frame[:rows] if cfg["side"] == "top" else frame[gh - rows:]
|
||||
sub = gw // cfg["leds"]
|
||||
if sub < 1:
|
||||
@@ -274,7 +264,18 @@ def capture_loop(state, strip):
|
||||
px.reverse()
|
||||
|
||||
state.strip_dev = strip.resolved
|
||||
if strip.write(adalight_frame(px)):
|
||||
# PACING: mai sopra ~60% della capacità della seriale. Il firmware Adalight fa
|
||||
# SERIAL_FLUSH alla chiusura del frame: se il frame successivo e' già nel buffer
|
||||
# ne perde l'header -> frame scartati e striscia nera, senza errori lato host.
|
||||
frame_bits = (6 + 3 * cfg["leds"]) * 10
|
||||
min_interval = max(1.0 / max(1, cfg["target_fps"]),
|
||||
1.7 * frame_bits / max(9600, cfg["baud"]))
|
||||
wait = min_interval - (time.time() - last_write)
|
||||
if wait > 0:
|
||||
time.sleep(wait)
|
||||
ok = strip.write(adalight_frame(px))
|
||||
if ok:
|
||||
last_write = time.time()
|
||||
sent += 1
|
||||
now = time.time()
|
||||
if now - t_win >= 1.0:
|
||||
@@ -384,12 +385,14 @@ class DBusService:
|
||||
value = bool(value)
|
||||
elif key == "leds":
|
||||
value = max(1, min(256, int(value)))
|
||||
elif key in ("port", "side", "baud", "fps", "capture", "drm_device"):
|
||||
elif key in ("port", "side", "baud", "fps"):
|
||||
value = type(DEFAULTS[key])(value)
|
||||
else:
|
||||
return False, f"parametro sconosciuto: {key}"
|
||||
self.state.update(**{key: value})
|
||||
save_config(self.state.snapshot())
|
||||
with self.state.lock:
|
||||
config = dict(self.state.cfg)
|
||||
save_config(config)
|
||||
return True, ""
|
||||
|
||||
# --- metodi
|
||||
|
||||
Reference in New Issue
Block a user