163 lines
5.4 KiB
Python
163 lines
5.4 KiB
Python
"""API de surveillance des ressources système."""
|
|
|
|
import logging
|
|
from typing import Optional
|
|
from fastapi import APIRouter, Request, Body
|
|
from fastapi.responses import JSONResponse
|
|
from pydantic import BaseModel
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/system/stats")
|
|
async def system_stats(request: Request):
|
|
"""Ressources système en temps réel."""
|
|
sys_svc = request.app.state.system_service
|
|
return sys_svc.get_stats()
|
|
|
|
|
|
@router.get("/system/services")
|
|
async def system_services(request: Request):
|
|
"""Statut des services systemd."""
|
|
sys_svc = request.app.state.system_service
|
|
return sys_svc.get_services_status()
|
|
|
|
|
|
@router.get("/system/photobooth")
|
|
async def photobooth_status(request: Request):
|
|
"""Vérifie si photobooth-app répond."""
|
|
pb = request.app.state.photobooth_service
|
|
alive = await pb.is_alive()
|
|
return {"alive": alive, "url": request.app.state.config.photobooth.base_url}
|
|
|
|
|
|
@router.post("/system/button/simulate")
|
|
async def simulate_button(request: Request, clicks: int = 1):
|
|
"""Simule un appui bouton (dev/test uniquement)."""
|
|
btn = request.app.state.button_service
|
|
if clicks == 0:
|
|
btn.simulate_long_press()
|
|
else:
|
|
btn.simulate_click(clicks)
|
|
return {"ok": True, "simulated_clicks": clicks}
|
|
|
|
|
|
@router.post("/system/relay")
|
|
async def control_relay(request: Request, state: str = "on"):
|
|
"""Force le relay ON/OFF."""
|
|
btn = request.app.state.button_service
|
|
if state == "on":
|
|
btn.relay_on()
|
|
else:
|
|
btn.relay_off()
|
|
return {"ok": True, "relay": state}
|
|
|
|
|
|
@router.get("/system/ui/delete-button")
|
|
async def get_delete_button(request: Request):
|
|
"""Retourne l'état actuel du bouton Supprimer de photobooth-app."""
|
|
visible = request.app.state.config.photobooth.show_delete_button
|
|
return {"visible": visible}
|
|
|
|
|
|
@router.post("/system/ui/delete-button")
|
|
async def set_delete_button(request: Request, visible: bool):
|
|
"""Active ou désactive le bouton Supprimer dans l'UI de review photobooth-app.
|
|
|
|
Écrit userdata/private.css en temps réel — effectif à la prochaine capture,
|
|
sans redémarrage.
|
|
"""
|
|
pb = request.app.state.photobooth_service
|
|
config_svc = request.app.state.config_service
|
|
|
|
ok = await pb.set_delete_button_visible(visible)
|
|
if ok:
|
|
config_svc.save_show_delete_button(visible)
|
|
|
|
return {"ok": ok, "visible": visible}
|
|
|
|
|
|
@router.post("/system/screen/refresh")
|
|
async def screen_refresh(request: Request):
|
|
"""Envoie F5 à l'écran HDMI connecté (Chromium kiosque Wayland/X11)."""
|
|
import subprocess, os, asyncio
|
|
|
|
# Cherche le display Wayland du user pi (UID 1000)
|
|
# Essaie wayland-0 puis wayland-1
|
|
xdg_runtime = "/run/user/1000"
|
|
cmd_wtype = f"WAYLAND_DISPLAY=wayland-1 XDG_RUNTIME_DIR={xdg_runtime} wtype -k F5"
|
|
cmd_x11 = f"DISPLAY=:0 XAUTHORITY=/home/pi/.Xauthority xdotool key F5"
|
|
|
|
for cmd in [cmd_wtype, cmd_x11]:
|
|
try:
|
|
proc = await asyncio.create_subprocess_shell(
|
|
f"su -s /bin/bash pi -c '{cmd}'",
|
|
stdout=asyncio.subprocess.PIPE,
|
|
stderr=asyncio.subprocess.PIPE,
|
|
)
|
|
_, stderr = await asyncio.wait_for(proc.communicate(), timeout=3)
|
|
if proc.returncode == 0:
|
|
logger.info("Screen refresh OK via: %s", cmd.split()[0])
|
|
return {"ok": True, "method": cmd.split()[0]}
|
|
except Exception as e:
|
|
logger.debug("Screen refresh cmd failed (%s): %s", cmd.split()[0], e)
|
|
|
|
return JSONResponse({"ok": False, "error": "Aucune méthode disponible (wtype/xdotool)"}, status_code=500)
|
|
|
|
|
|
class ButtonConfigUpdate(BaseModel):
|
|
double_click_ms: Optional[int] = None
|
|
long_press_ms: Optional[int] = None
|
|
debounce_ms: Optional[int] = None
|
|
max_clicks: Optional[int] = None
|
|
print_enabled: Optional[bool] = None
|
|
save: bool = False # True = persiste dans settings.yaml
|
|
|
|
|
|
@router.get("/system/button/config")
|
|
async def get_button_config(request: Request):
|
|
"""Retourne la configuration actuelle du bouton (timings)."""
|
|
cfg = request.app.state.config.button
|
|
return {
|
|
"double_click_ms": cfg.double_click_ms,
|
|
"long_press_ms": cfg.long_press_ms,
|
|
"debounce_ms": cfg.debounce_ms,
|
|
"max_clicks": cfg.max_clicks,
|
|
"print_enabled": cfg.print_enabled,
|
|
}
|
|
|
|
|
|
@router.put("/system/button/config")
|
|
async def update_button_config(request: Request, payload: ButtonConfigUpdate):
|
|
"""Met à jour les timings du bouton (à chaud + optionnellement sauvegardé)."""
|
|
btn = request.app.state.button_service
|
|
config_svc = request.app.state.config_service
|
|
|
|
update = {k: v for k, v in payload.model_dump(exclude={"save"}).items() if v is not None}
|
|
|
|
# Application immédiate (sans redémarrage)
|
|
btn.update_timings(
|
|
double_click_ms=payload.double_click_ms,
|
|
long_press_ms=payload.long_press_ms,
|
|
debounce_ms=payload.debounce_ms,
|
|
max_clicks=payload.max_clicks,
|
|
)
|
|
|
|
# Persistance optionnelle
|
|
if payload.save and update:
|
|
config_svc.save_button_config(update)
|
|
|
|
cfg = request.app.state.config.button
|
|
return {
|
|
"ok": True,
|
|
"saved": payload.save,
|
|
"current": {
|
|
"double_click_ms": cfg.double_click_ms,
|
|
"long_press_ms": cfg.long_press_ms,
|
|
"debounce_ms": cfg.debounce_ms,
|
|
"max_clicks": cfg.max_clicks,
|
|
"print_enabled": cfg.print_enabled,
|
|
}
|
|
}
|