all
🚀 Deploy — JH Photomaton / 🔍 Vérification (push) Has been cancelled
🚀 Deploy — JH Photomaton / 🍓 Deploy sur le Pi (push) Has been cancelled

This commit is contained in:
2026-07-17 00:44:22 +02:00
parent 209f81aa3d
commit 9a006f1457
23 changed files with 3712 additions and 484 deletions
+22 -51
View File
@@ -1,16 +1,4 @@
"""
JH Photomaton — Interface de gestion
Raspberry Pi 4 / Les Sapins Du Web (LSDW)
Remplace Node-RED pour :
- Contrôle bouton GPIO23 (multi-clic + long press) + relay GPIO12
- LEDs WS2812b GPIO18 (35 LEDs) — effets selon état
- Galerie publique et galerie admin
- File d'attente d'impression (SQLite)
- Dashboard admin avec monitoring système
Port : 8090 (configurable dans config/settings.yaml)
"""
"""JH Photomaton -- Interface de gestion (Les Sapins Du Web)."""
import asyncio
import logging
@@ -22,6 +10,7 @@ from fastapi.staticfiles import StaticFiles
from starlette.middleware.sessions import SessionMiddleware
from backend.services.config_service import ConfigService
from backend.services.event_service import EventService
from backend.services.led_service import LEDService
from backend.services.button_service import ButtonService
from backend.services.photobooth_service import PhotoboothService
@@ -37,6 +26,7 @@ from backend.api import (
print_api,
actions_api,
webhooks,
event_api,
)
logging.basicConfig(
@@ -50,23 +40,27 @@ BASE_DIR = Path(__file__).parent
@asynccontextmanager
async def lifespan(app: FastAPI):
# ── Chargement config ───────────────────────────────────────────────────
config_svc = ConfigService(BASE_DIR / "config" / "settings.yaml")
config = config_svc.load()
# ── Services ────────────────────────────────────────────────────────────
ws_manager = WSManager()
photobooth_svc = PhotoboothService(config)
system_svc = SystemService()
printer_svc = PrinterService(config)
event_svc = EventService()
await printer_svc.init_db(BASE_DIR / "data" / "print_queue.db")
await event_svc.init_db(BASE_DIR / "data" / "events.db")
from datetime import datetime as _dt
ev = config.event
if not ev.started_at:
ev.started_at = _dt.now().timestamp()
await event_svc.ensure_event(ev.slug, ev.name, ev.started_at)
# LEDs
led_svc = LEDService(config)
led_svc.start()
# Bouton GPIO — passe le loop asyncio pour les callbacks
loop = asyncio.get_event_loop()
led_svc.set_loop(loop)
led_svc.set_on_change(ws_manager.broadcast)
@@ -74,7 +68,6 @@ async def lifespan(app: FastAPI):
button_svc = ButtonService(config, led_svc, photobooth_svc, ws_manager, loop)
button_svc.start()
# ── État partagé dans l'app ──────────────────────────────────────────────
app.state.config = config
app.state.config_service = config_svc
app.state.ws_manager = ws_manager
@@ -83,26 +76,25 @@ async def lifespan(app: FastAPI):
app.state.photobooth_service = photobooth_svc
app.state.printer_service = printer_svc
app.state.system_service = system_svc
app.state.event_service = event_svc
# ── Tâche de fond : broadcast stats système ──────────────────────────────
stats_task = asyncio.create_task(_system_stats_loop(system_svc, ws_manager))
logger.info("JH Photomaton démarré — port %d", config.app.port)
logger.info("JH Photomaton demarre -- port %d", config.app.port)
led_svc.play("idle")
yield # ← l'app tourne ici
yield
# ── Arrêt propre ─────────────────────────────────────────────────────────
stats_task.cancel()
button_svc.stop()
led_svc.stop()
await photobooth_svc.close()
await printer_svc.close()
logger.info("JH Photomaton arrêté")
await event_svc.close()
logger.info("JH Photomaton arrete")
async def _system_stats_loop(sys_svc: SystemService, ws: WSManager):
"""Broadcast des stats système toutes les 5 secondes."""
while True:
try:
stats = sys_svc.get_stats()
@@ -112,29 +104,21 @@ async def _system_stats_loop(sys_svc: SystemService, ws: WSManager):
await asyncio.sleep(5)
# ── Application FastAPI ──────────────────────────────────────────────────────
app = FastAPI(
title="JH Photomaton",
description="Interface de gestion du photomaton Les Sapins Du Web",
description="Interface de gestion du photomaton -- Les Sapins Du Web",
version="1.0.0",
lifespan=lifespan,
)
app.add_middleware(
SessionMiddleware,
secret_key="jh-photomaton-session-2026", # remplacé par config au démarrage
max_age=86400, # 24h
secret_key="jh-photomaton-session-2026",
max_age=86400,
)
# Fichiers statiques
app.mount(
"/static",
StaticFiles(directory=BASE_DIR / "frontend" / "static"),
name="static",
)
app.mount("/static", StaticFiles(directory=BASE_DIR / "frontend" / "static"), name="static")
# Routes
app.include_router(gallery.router)
app.include_router(admin_api.router)
app.include_router(admin_gallery_api.router)
@@ -143,16 +127,13 @@ app.include_router(system_api.router, prefix="/api")
app.include_router(print_api.router, prefix="/api")
app.include_router(actions_api.router, prefix="/api")
app.include_router(webhooks.router, prefix="/api")
app.include_router(event_api.router, prefix="/api")
# ── WebSocket ────────────────────────────────────────────────────────────────
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
ws_manager: WSManager = websocket.app.state.ws_manager
await ws_manager.connect(websocket)
# Envoie l'état initial à la connexion
try:
led = websocket.app.state.led_service
sys_svc = websocket.app.state.system_service
@@ -160,11 +141,9 @@ async def websocket_endpoint(websocket: WebSocket):
await ws_manager.send(websocket, {"type": "system_stats", "data": sys_svc.get_stats()})
except Exception:
pass
try:
while True:
data = await websocket.receive_text()
# Commandes entrantes du client (optionnel)
import json
try:
msg = json.loads(data)
@@ -176,15 +155,7 @@ async def websocket_endpoint(websocket: WebSocket):
ws_manager.disconnect(websocket)
# ── Point d'entrée ───────────────────────────────────────────────────────────
if __name__ == "__main__":
import uvicorn
cfg = ConfigService(BASE_DIR / "config" / "settings.yaml").load()
uvicorn.run(
"main:app",
host=cfg.app.host,
port=cfg.app.port,
reload=cfg.app.debug,
log_level="info",
)
uvicorn.run("main:app", host=cfg.app.host, port=cfg.app.port, reload=cfg.app.debug, log_level="info")