diff --git a/backend/services/led_service.py b/backend/services/led_service.py index 125061a..b60456c 100644 --- a/backend/services/led_service.py +++ b/backend/services/led_service.py @@ -190,25 +190,60 @@ class LEDService: step += 1 def _effect_countdown(self, duration: float = 5.0): - cfg = self._cfg.get_effect("countdown") - c = cfg.color n = self._cfg.count blank = WS_Color(0, 0, 0) - color = WS_Color(c[0], c[1], c[2]) - self._fill(blank) + # Mode sombre : rampe progressive avec couleur flash pour stabiliser AWB caméra + lux = self._ambient_lux + dark_mode = self._cfg.auto_brightness and lux >= 0 and lux < 50 + + if dark_mode: + flash_cfg = self._cfg.get_effect("capture") + fc = flash_cfg.color + color = WS_Color(fc[0], fc[1], fc[2]) + # Brightness cible du flash + target_br = 255 if lux < 50 else (int(255 - (lux - 50) / 130 * 135) if lux < 180 else 80) + # Rampe : atteindre target_br quand il reste pre_flash_advance secondes + ramp_duration = max(0.1, duration - self._cfg.pre_flash_advance) + orig_br = self._cfg.brightness + logger.info("Countdown mode sombre: rampe 0→%d sur %.1fs puis stable", target_br, ramp_duration) + if HAS_WS281X and self._strip: + self._strip.setBrightness(0) + self._fill(color) + else: + cfg = self._cfg.get_effect("countdown") + c = cfg.color + color = WS_Color(c[0], c[1], c[2]) + self._fill(blank) + t_start = time.time() while not self._cmd_event.is_set() and not self._stop_event.is_set(): elapsed = time.time() - t_start ratio = min(elapsed / duration, 1.0) - leds_on = int(ratio * n) - for i in range(n): - self._strip.setPixelColor(i, color if i < leds_on else blank) - self._strip.show() + + if dark_mode: + # Rampe de 0 → target_br jusqu'à ramp_duration, puis stable à target_br + if elapsed < ramp_duration: + br = int(target_br * elapsed / ramp_duration) + else: + br = target_br + if HAS_WS281X and self._strip: + self._strip.setBrightness(br) + self._strip.show() + else: + leds_on = int(ratio * n) + for i in range(n): + self._strip.setPixelColor(i, color if i < leds_on else blank) + self._strip.show() + if ratio >= 1.0: break time.sleep(0.04) + # En mode sombre, restaurer brightness normale (capture le fera aussi, mais par sécurité) + if dark_mode and HAS_WS281X and self._strip: + self._strip.setBrightness(orig_br) + def _effect_capture(self): """Flash photo. @@ -241,15 +276,22 @@ class LEDService: if HAS_WS281X and self._strip: self._strip.setBrightness(flash_brightness) + # Le flash démarre pre_flash_advance secondes AVANT la photo. + # La durée ON doit couvrir ce délai + la durée d'exposition. + # Sinon les LEDs s'éteignent avant que la photo soit prise. + effective_on_time = self._cfg.pre_flash_advance + cfg.flash_duration + try: - for _ in range(cfg.flashes): + for i in range(cfg.flashes): if self._cmd_event.is_set(): break self._fill(flash_color) - time.sleep(cfg.flash_duration) + # Premier flash : durée étendue pour couvrir le délai + expo + # Flashs suivants : durée normale (décoratif post-capture) + on_time = effective_on_time if i == 0 else cfg.flash_duration + time.sleep(on_time) self._fill(off) - # Pause inter-flash plus courte que le flash lui-meme - if _ < cfg.flashes - 1: + if i < cfg.flashes - 1: time.sleep(cfg.flash_duration * 0.4) finally: # Toujours restaurer la luminosite normale meme en cas d'erreur