feat: add lightweight API and web console
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user