Files
photoBooth/backend/api/system_api.py
T
admin 16bf9ddfb3
🚀 Deploy — JH Photomaton / 🔍 Vérification (push) Has been cancelled
🚀 Deploy — JH Photomaton / 🍓 Deploy sur le Pi (push) Has been cancelled
fix: restart photobooth-app après changement du bouton Supprimer
2026-07-17 12:23:09 +02:00

178 lines
5.9 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 puis redémarre photobooth-app pour que le CSS
soit pris en compte.
"""
import asyncio, subprocess
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)
# Redémarre photobooth-app en arrière-plan pour recharger le CSS
asyncio.create_task(_restart_photobooth())
return {"ok": ok, "visible": visible}
async def _restart_photobooth():
import asyncio
await asyncio.sleep(0.5)
proc = await asyncio.create_subprocess_exec(
"sudo", "systemctl", "restart", "photobooth-app.service",
stdout=asyncio.subprocess.DEVNULL,
stderr=asyncio.subprocess.DEVNULL,
)
await proc.wait()
logger.info("photobooth-app redémarré après changement CSS")
@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,
}
}