"""Service de chargement et sauvegarde de la configuration YAML.""" from __future__ import annotations import logging from dataclasses import dataclass, field from pathlib import Path from typing import Any import yaml logger = logging.getLogger(__name__) @dataclass class AppConfig: name: str = "JH Photomaton" host: str = "0.0.0.0" port: int = 8090 debug: bool = False secret_key: str = "" admin_password: str = "PhotoBooth2026!" @dataclass class PhotoboothConfig: base_url: str = "http://localhost:8083" data_dir: str = "/home/pi/photobooth-data" config_file: str = "/home/pi/.config/photobooth-app/config.json" media_dir: str = "/home/pi/photobooth-data/media/processed_full" userdata_dir: str = "/home/pi/photobooth-data/userdata" show_delete_button: bool = False @dataclass class ButtonConfig: pin: int = 23 relay_pin: int = 12 debounce_ms: int = 50 double_click_ms: int = 400 long_press_ms: int = 1500 max_clicks: int = 4 long_press_action: str = "print_last" print_enabled: bool = True @dataclass class LEDEffectConfig: color: list = field(default_factory=lambda: [0, 30, 80]) mode: str = "solid" speed: float = 0.05 flashes: int = 2 flash_duration: float = 0.1 duration: float = 2.0 @dataclass class LEDConfig: pin: int = 18 count: int = 35 brightness: int = 180 auto_brightness: bool = False freq_hz: int = 800000 dma: int = 10 strip_type: str = "WS2812" effects: dict = field(default_factory=dict) def get_effect(self, name: str) -> LEDEffectConfig: raw = self.effects.get(name, {}) return LEDEffectConfig( color=raw.get("color", [0, 30, 80]), mode=raw.get("mode", "solid"), speed=raw.get("speed", 0.05), flashes=raw.get("flashes", 2), flash_duration=raw.get("flash_duration", 0.1), duration=raw.get("duration", 2.0), ) @dataclass class PrintConfig: mode: str = "validation" script_path: str = "/home/pi/photobooth-data/script/script_print.sh" default_copies: int = 1 printers: list = field(default_factory=list) @dataclass class GalleryConfig: public_enabled: bool = True photos_per_page: int = 24 qr_base_url: str = "https://photomaton.lessapinsduweb.com" @dataclass class EventConfig: name: str = "Evenement" slug: str = "evenement" started_at: float = 0.0 @dataclass class Config: app: AppConfig = field(default_factory=AppConfig) photobooth: PhotoboothConfig = field(default_factory=PhotoboothConfig) button: ButtonConfig = field(default_factory=ButtonConfig) leds: LEDConfig = field(default_factory=LEDConfig) print: PrintConfig = field(default_factory=PrintConfig) gallery: GalleryConfig = field(default_factory=GalleryConfig) event: EventConfig = field(default_factory=EventConfig) button_actions: dict = field(default_factory=dict) class ConfigService: def __init__(self, path: Path): self.path = path self._config: Config | None = None def load(self) -> Config: if not self.path.exists(): logger.warning("Config introuvable: %s", self.path) self._config = Config() return self._config with open(self.path, encoding="utf-8") as f: raw: dict[str, Any] = yaml.safe_load(f) or {} cfg = Config() if "app" in raw: cfg.app = AppConfig(**{k: v for k, v in raw["app"].items() if hasattr(AppConfig, k)}) if "photobooth" in raw: cfg.photobooth = PhotoboothConfig(**{k: v for k, v in raw["photobooth"].items() if hasattr(PhotoboothConfig, k)}) if "button" in raw: cfg.button = ButtonConfig(**{k: v for k, v in raw["button"].items() if hasattr(ButtonConfig, k)}) if "leds" in raw: led_raw = raw["leds"] cfg.leds = LEDConfig( pin=led_raw.get("pin", 18), count=led_raw.get("count", 35), brightness=led_raw.get("brightness", 180), auto_brightness=led_raw.get("auto_brightness", False), freq_hz=led_raw.get("freq_hz", 800000), dma=led_raw.get("dma", 10), strip_type=led_raw.get("strip_type", "WS2812"), effects=led_raw.get("effects", {}), ) if "print" in raw: cfg.print = PrintConfig(**{k: v for k, v in raw["print"].items() if hasattr(PrintConfig, k)}) if "gallery" in raw: cfg.gallery = GalleryConfig(**{k: v for k, v in raw["gallery"].items() if hasattr(GalleryConfig, k)}) if "event" in raw: cfg.event = EventConfig(**{k: v for k, v in raw["event"].items() if hasattr(EventConfig, k)}) cfg.button_actions = raw.get("button_actions", { 1: {"label": "Photo normale", "photobooth_index": 0}, 2: {"label": "Photo etoile", "photobooth_index": 1}, 3: {"label": "Photo cailloux", "photobooth_index": 2}, 4: {"label": "Photo soiree", "photobooth_index": 3}, }) self._config = cfg logger.info("Configuration chargee depuis %s", self.path) return cfg def save_button_actions(self, button_actions: dict): with open(self.path, encoding="utf-8") as f: raw = yaml.safe_load(f) or {} raw["button_actions"] = button_actions with open(self.path, "w", encoding="utf-8") as f: yaml.dump(raw, f, allow_unicode=True, default_flow_style=False) if self._config: self._config.button_actions = button_actions def save_show_delete_button(self, visible: bool): with open(self.path, encoding="utf-8") as f: raw = yaml.safe_load(f) or {} raw.setdefault("photobooth", {})["show_delete_button"] = visible with open(self.path, "w", encoding="utf-8") as f: yaml.dump(raw, f, allow_unicode=True, default_flow_style=False) if self._config: self._config.photobooth.show_delete_button = visible def save_button_config(self, data: dict): with open(self.path, encoding="utf-8") as f: raw = yaml.safe_load(f) or {} raw.setdefault("button", {}).update(data) with open(self.path, "w", encoding="utf-8") as f: yaml.dump(raw, f, allow_unicode=True, default_flow_style=False) if self._config: for k, v in data.items(): if hasattr(self._config.button, k): setattr(self._config.button, k, v) def save_event_config(self, name: str, slug: str, started_at: float): with open(self.path, encoding="utf-8") as f: raw = yaml.safe_load(f) or {} raw["event"] = {"name": name, "slug": slug, "started_at": started_at} with open(self.path, "w", encoding="utf-8") as f: yaml.dump(raw, f, allow_unicode=True, default_flow_style=False) if self._config: self._config.event.name = name self._config.event.slug = slug self._config.event.started_at = started_at def save_print_mode(self, mode: str): with open(self.path, encoding="utf-8") as f: raw = yaml.safe_load(f) or {} raw.setdefault("print", {})["mode"] = mode with open(self.path, "w", encoding="utf-8") as f: yaml.dump(raw, f, allow_unicode=True, default_flow_style=False) if self._config: self._config.print.mode = mode @property def config(self) -> Config: if self._config is None: self.load() return self._config def save_led_brightness(self, brightness: int): with open(self.path, encoding="utf-8") as f: raw = yaml.safe_load(f) or {} raw.setdefault("leds", {})["brightness"] = brightness with open(self.path, "w", encoding="utf-8") as f: yaml.dump(raw, f, allow_unicode=True, default_flow_style=False) if self._config: self._config.leds.brightness = brightness def save_led_auto_brightness(self, enabled: bool): with open(self.path, encoding="utf-8") as f: raw = yaml.safe_load(f) or {} raw.setdefault("leds", {})["auto_brightness"] = enabled with open(self.path, "w", encoding="utf-8") as f: yaml.dump(raw, f, allow_unicode=True, default_flow_style=False) if self._config: self._config.leds.auto_brightness = enabled def save_led_effect(self, effect_name: str, effect_data: dict): with open(self.path, encoding="utf-8") as f: raw = yaml.safe_load(f) or {} raw.setdefault("leds", {}).setdefault("effects", {})[effect_name] = effect_data with open(self.path, "w", encoding="utf-8") as f: yaml.dump(raw, f, allow_unicode=True, default_flow_style=False) if self._config: self._config.leds.effects[effect_name] = effect_data