From 4ff64b31d9c652691a29a15049ef7fe76fe840f2 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Fri, 15 May 2026 15:57:10 +0800 Subject: [PATCH] feat: add all-off led action --- README.md | 3 ++- led_panel.py | 34 ++++++++++++++++++++++++++++++++-- test_led_panel.py | 13 ++++++++++++- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c3e4641..c228834 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,8 @@ blue:wan green:wlan mmc0:: red:power sim:en sim:en2 sim:sel sim:sel2 - 列出 `/sys/class/leds/` 下所有 LED - 查看 brightness / max_brightness / 当前 trigger -- 打开 / 关闭 LED +- 打开 / 关闭单个 LED +- 页面顶部一键关闭全部 LED - 调整亮度 - 切换 trigger,例如 `none`、`timer`、`netdev`、`mmc0` 等,具体取决于内核暴露的能力 - 设置 `timer` trigger 的 `delay_on` / `delay_off` diff --git a/led_panel.py b/led_panel.py index ed43008..ffd3b97 100755 --- a/led_panel.py +++ b/led_panel.py @@ -69,6 +69,7 @@ async function identify(name){ try{ await api('/api/identify',{name}); toast('Bl async function saveLed(name){ try{ await api('/api/save',{name}); toast('已保存 '+name+',重启后自动恢复'); setTimeout(load,300);}catch(e){toast(e.message,true);} } async function deleteSaved(name){ try{ await api('/api/delete-saved',{name}); toast('已取消持久化 '+name); setTimeout(load,300);}catch(e){toast(e.message,true);} } async function applySaved(){ try{ const r=await api('/api/apply-saved',{}); toast('已应用持久化配置:'+(r.applied||0)+' 个'); setTimeout(load,300);}catch(e){toast(e.message,true);} } +async function turnOffAll(){ try{ const r=await api('/api/all-off',{}); const failed=(r.results||[]).filter(x=>!x.ok); toast(failed.length ? ('已关闭 '+(r.changed||0)+' 个,失败 '+failed.length+' 个') : ('已关闭全部 LED:'+(r.changed||0)+' 个'), failed.length>0); setTimeout(load,300);}catch(e){toast(e.message,true);} } function colorOf(n,b){ const s=n.toLowerCase(); if(Number(b)<=0) return '#475569'; if(s.includes('red')) return '#ef4444'; if(s.includes('green')) return '#22c55e'; if(s.includes('blue')) return '#3b82f6'; if(s.includes('wlan')) return '#22c55e'; if(s.includes('wan')) return '#3b82f6'; if(s.includes('power')) return '#ef4444'; if(s.includes('sim')) return '#f59e0b'; return '#5eead4'; } function esc(s){ return String(s).replace(/[&<>'"]/g, c=>({'&':'&','<':'<','>':'>',"'":''','"':'"'}[c])); } function attr(s){ return esc(s); } @@ -253,6 +254,32 @@ def apply_saved_config() -> List[Dict[str, object]]: results.append({"name": name, "ok": False, "errors": [str(e)]}) return results + +def turn_off_all_leds() -> List[Dict[str, object]]: + results = [] + for led in list_leds(): + name = str(led["name"]) + result: Dict[str, object] = {"name": name, "ok": True, "errors": []} + try: + p = led_path(name) + if (p / "trigger").exists(): + try: + set_trigger(p, "none") + except Exception as e: + result["ok"] = False + result["errors"].append(f"trigger: {e}") + write_text(p / "brightness", "0") + try: + result["brightness"] = int(read_text(p / "brightness") or 0) + except ValueError: + result["brightness"] = read_text(p / "brightness") + except Exception as e: + result["ok"] = False + result["errors"].append(str(e)) + results.append(result) + return results + + def list_leds() -> List[Dict[str, object]]: leds = [] saved_config = load_config() @@ -317,7 +344,7 @@ class Handler(BaseHTTPRequestHandler): def do_GET(self): parsed = urllib.parse.urlparse(self.path) if parsed.path == "/": - body = f"""LED Manager

LED Manager Panel

管理 {html.escape(str(LED_ROOT))} 下的 LED;配置保存到 {html.escape(str(CONFIG_PATH))};当前 - 个。页面每 5 秒自动刷新。
""" + body = f"""LED Manager

LED Manager Panel

管理 {html.escape(str(LED_ROOT))} 下的 LED;配置保存到 {html.escape(str(CONFIG_PATH))};当前 - 个。页面每 5 秒自动刷新。
""" data = body.encode("utf-8") self.send_response(200) self.send_header("Content-Type", "text/html; charset=utf-8") @@ -335,7 +362,7 @@ class Handler(BaseHTTPRequestHandler): data = self.read_body() name = None p = None - if parsed.path not in ("/api/apply-saved",): + if parsed.path not in ("/api/apply-saved", "/api/all-off"): name = safe_name(str(data.get("name", ""))) p = led_path(name) if parsed.path == "/api/brightness": @@ -380,6 +407,9 @@ class Handler(BaseHTTPRequestHandler): elif parsed.path == "/api/apply-saved": results = apply_saved_config() self.send_json({"ok": True, "applied": sum(1 for r in results if r.get("ok")), "results": results}) + elif parsed.path == "/api/all-off": + results = turn_off_all_leds() + self.send_json({"ok": all(r.get("ok") for r in results), "changed": sum(1 for r in results if r.get("brightness") == 0), "results": results}) elif parsed.path == "/api/identify": old_trigger = read_text(p / "trigger") old_brightness = read_text(p / "brightness") diff --git a/test_led_panel.py b/test_led_panel.py index 228586b..926b7e6 100644 --- a/test_led_panel.py +++ b/test_led_panel.py @@ -29,6 +29,11 @@ def main(): (d / 'trigger').write_text('[none] timer') (d / 'delay_on').write_text('500') (d / 'delay_off').write_text('500') + d2 = root / 'red:power' + d2.mkdir() + (d2 / 'brightness').write_text('1') + (d2 / 'max_brightness').write_text('1') + (d2 / 'trigger').write_text('[none] timer') config_path = root / 'config.json' led_panel.LED_ROOT = root led_panel.CONFIG_PATH = config_path @@ -58,7 +63,13 @@ def main(): assert applied['ok'] and applied['applied'] == 1 assert (d / 'brightness').read_text() == '1' assert (d / 'delay_on').read_text() == '123' - print('OK: API write + persistence regression passed') + all_off = req(port, '/api/all-off', {}) + assert all_off['ok'] and all_off['changed'] == 2 + assert (d / 'brightness').read_text() == '0' + assert (d2 / 'brightness').read_text() == '0' + assert '[none]' in (d / 'trigger').read_text() or (d / 'trigger').read_text() == 'none' + assert '[none]' in (d2 / 'trigger').read_text() or (d2 / 'trigger').read_text() == 'none' + print('OK: API write + persistence + all-off regression passed') finally: httpd.shutdown()