feat: add all-off led action
This commit is contained in:
@@ -18,7 +18,8 @@ blue:wan green:wlan mmc0:: red:power sim:en sim:en2 sim:sel sim:sel2
|
|||||||
|
|
||||||
- 列出 `/sys/class/leds/` 下所有 LED
|
- 列出 `/sys/class/leds/` 下所有 LED
|
||||||
- 查看 brightness / max_brightness / 当前 trigger
|
- 查看 brightness / max_brightness / 当前 trigger
|
||||||
- 打开 / 关闭 LED
|
- 打开 / 关闭单个 LED
|
||||||
|
- 页面顶部一键关闭全部 LED
|
||||||
- 调整亮度
|
- 调整亮度
|
||||||
- 切换 trigger,例如 `none`、`timer`、`netdev`、`mmc0` 等,具体取决于内核暴露的能力
|
- 切换 trigger,例如 `none`、`timer`、`netdev`、`mmc0` 等,具体取决于内核暴露的能力
|
||||||
- 设置 `timer` trigger 的 `delay_on` / `delay_off`
|
- 设置 `timer` trigger 的 `delay_on` / `delay_off`
|
||||||
|
|||||||
+32
-2
@@ -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 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 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 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 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 esc(s){ return String(s).replace(/[&<>'"]/g, c=>({'&':'&','<':'<','>':'>',"'":''','"':'"'}[c])); }
|
||||||
function attr(s){ return esc(s); }
|
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)]})
|
results.append({"name": name, "ok": False, "errors": [str(e)]})
|
||||||
return results
|
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]]:
|
def list_leds() -> List[Dict[str, object]]:
|
||||||
leds = []
|
leds = []
|
||||||
saved_config = load_config()
|
saved_config = load_config()
|
||||||
@@ -317,7 +344,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
parsed = urllib.parse.urlparse(self.path)
|
parsed = urllib.parse.urlparse(self.path)
|
||||||
if parsed.path == "/":
|
if parsed.path == "/":
|
||||||
body = f"""<!doctype html><html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'><title>LED Manager</title><style>{CSS}</style></head><body><div class='container'><div class='header'><div><h1>LED Manager Panel</h1><div class='sub'>管理 <code>{html.escape(str(LED_ROOT))}</code> 下的 LED;配置保存到 <code>{html.escape(str(CONFIG_PATH))}</code>;当前 <span id='count'>-</span> 个。页面每 5 秒自动刷新。</div></div><div class='row' style='margin-top:0'><button onclick='applySaved()'>应用已保存配置</button><button onclick='load()'>刷新</button></div></div><div id='root' class='grid'></div></div><div id='toast' class='toast'></div><script>{JS}</script></body></html>"""
|
body = f"""<!doctype html><html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'><title>LED Manager</title><style>{CSS}</style></head><body><div class='container'><div class='header'><div><h1>LED Manager Panel</h1><div class='sub'>管理 <code>{html.escape(str(LED_ROOT))}</code> 下的 LED;配置保存到 <code>{html.escape(str(CONFIG_PATH))}</code>;当前 <span id='count'>-</span> 个。页面每 5 秒自动刷新。</div></div><div class='row' style='margin-top:0'><button class='off' onclick='turnOffAll()'>关闭全部灯</button><button onclick='applySaved()'>应用已保存配置</button><button onclick='load()'>刷新</button></div></div><div id='root' class='grid'></div></div><div id='toast' class='toast'></div><script>{JS}</script></body></html>"""
|
||||||
data = body.encode("utf-8")
|
data = body.encode("utf-8")
|
||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
self.send_header("Content-Type", "text/html; charset=utf-8")
|
self.send_header("Content-Type", "text/html; charset=utf-8")
|
||||||
@@ -335,7 +362,7 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
data = self.read_body()
|
data = self.read_body()
|
||||||
name = None
|
name = None
|
||||||
p = 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", "")))
|
name = safe_name(str(data.get("name", "")))
|
||||||
p = led_path(name)
|
p = led_path(name)
|
||||||
if parsed.path == "/api/brightness":
|
if parsed.path == "/api/brightness":
|
||||||
@@ -380,6 +407,9 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
elif parsed.path == "/api/apply-saved":
|
elif parsed.path == "/api/apply-saved":
|
||||||
results = apply_saved_config()
|
results = apply_saved_config()
|
||||||
self.send_json({"ok": True, "applied": sum(1 for r in results if r.get("ok")), "results": results})
|
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":
|
elif parsed.path == "/api/identify":
|
||||||
old_trigger = read_text(p / "trigger")
|
old_trigger = read_text(p / "trigger")
|
||||||
old_brightness = read_text(p / "brightness")
|
old_brightness = read_text(p / "brightness")
|
||||||
|
|||||||
+12
-1
@@ -29,6 +29,11 @@ def main():
|
|||||||
(d / 'trigger').write_text('[none] timer')
|
(d / 'trigger').write_text('[none] timer')
|
||||||
(d / 'delay_on').write_text('500')
|
(d / 'delay_on').write_text('500')
|
||||||
(d / 'delay_off').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'
|
config_path = root / 'config.json'
|
||||||
led_panel.LED_ROOT = root
|
led_panel.LED_ROOT = root
|
||||||
led_panel.CONFIG_PATH = config_path
|
led_panel.CONFIG_PATH = config_path
|
||||||
@@ -58,7 +63,13 @@ def main():
|
|||||||
assert applied['ok'] and applied['applied'] == 1
|
assert applied['ok'] and applied['applied'] == 1
|
||||||
assert (d / 'brightness').read_text() == '1'
|
assert (d / 'brightness').read_text() == '1'
|
||||||
assert (d / 'delay_on').read_text() == '123'
|
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:
|
finally:
|
||||||
httpd.shutdown()
|
httpd.shutdown()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user