feat: add lightweight API and web console

This commit is contained in:
way
2026-09-27 10:21:12 +08:00
parent fd4e2bf0f4
commit f42ea9d4bd
9 changed files with 382 additions and 36 deletions
+40 -3
View File
@@ -1,13 +1,14 @@
#!/usr/bin/env python3
import time,socket,subprocess,configparser,signal,re,errno,threading,argparse
import time,socket,subprocess,configparser,signal,re,errno,threading,argparse,json
from pathlib import Path
APP_VERSION="3.5.0"
APP_VERSION="3.6.0"
from smbus2 import SMBus
from PIL import Image
from shutil import disk_usage
OLED=0x3C; MCU=0x0D; W,H=128,32
CFG="/etc/pigway-pi-control.conf"
STATUS_PATH="/run/pigway-pi-control/status.json"
running=True
def stop(*_):
@@ -616,6 +617,17 @@ class DisplayController:
def owner_snapshot(self):
with self.lock: return dict(self.requests[self.current_owner_id])
def status_snapshot(self):
with self.lock:
alerts=self._ordered_alerts_locked()
return {
"owner":self.current_owner_id,
"oled_page":self.current_oled_page_id,
"rgb_mode":self.current_rgb_mode,
"alerts":[{k:a[k] for k in ("id","priority","severity","title","l2","l3","l4","first_seen","last_changed")}
for a in alerts],
}
def _rgb_worker(self):
previous_error=None; applied_mode="OFF"; applied_flash=False; animation_started=time.monotonic()
while not self.stop_event.is_set():
@@ -660,6 +672,29 @@ def cleanup():
for name,action in (("fan",lambda:mcu(0x08,0x00)),("bus_close",bus.close)):
try: action()
except Exception as e: log("ERROR","SHUTDOWN_CLEANUP",step=name,error=repr(e))
try: Path(STATUS_PATH).unlink(missing_ok=True)
except Exception as e: log("ERROR","SHUTDOWN_CLEANUP",step="status_file",error=repr(e))
def write_status(cpu,temp,mem,disk):
display_state=display.status_snapshot()
state={
"version":APP_VERSION,"timestamp":int(time.time()),"uptime_seconds":uptime_seconds(),
"system":{"load_average":load_average(),"cpu_percent":round(cpu,1),
"cpu_freq_mhz":round(cpu_freq_mhz()),"temperature_c":round(temp,1),
"memory_percent":round(mem,1),"disk_percent":round(disk,1),
"root_readonly":bool(fs_readonly),"fan_level":fan_level,"fan_name":FAN_NAME[fan_level]},
"power":health,
"network":{"interface":net_iface,"kind":net_kind,"metric":net_metric,
"ip_label":ip_label,"ip":ip,"link":bool(link_ok),"internet":bool(net_ok)},
"watches":[{"type":w[0],"name":w[1],"target":w[2],"up":bool(ok)}
for w,ok in zip(watches,watch_state)],
"display":display_state,
"agent":{"loop_errors":agent["loop_errors"],"i2c_errors":agent["i2c_errors"]},
}
path=Path(STATUS_PATH); path.parent.mkdir(mode=0o755,parents=True,exist_ok=True)
temporary=path.with_suffix(".tmp")
temporary.write_text(json.dumps(state,ensure_ascii=False,separators=(",",":")))
temporary.chmod(0o644); temporary.replace(path)
# Dispatch only after all shared checks and configuration have been initialized.
parser=argparse.ArgumentParser()
@@ -668,7 +703,7 @@ args=parser.parse_args()
if args.diagnose: raise SystemExit(diagnose())
watch_state=[]
last_sensor=last_render=0.0
last_sensor=last_render=last_status=0.0
last_net=last_watch=last_health=0.0
health={}; fs_readonly=False
agent={"last_sensor_ok":0.0,"last_network_ok":0.0,"last_watch_ok":0.0,"last_health_ok":0.0,"loop_errors":0,"i2c_errors":0}
@@ -731,6 +766,8 @@ try:
if now-last_render >= OLED_REFRESH:
display.refresh()
last_render=now
if now-last_status >= 1.0:
write_status(c,t,m,d); last_status=now
time.sleep(0.20)
except Exception as e: