diff --git a/src/display_manager.py b/src/display_manager.py index 43a53d98..7ad48550 100644 --- a/src/display_manager.py +++ b/src/display_manager.py @@ -25,6 +25,10 @@ class DisplayManager: start_time = time.time() self.config = config or {} self._force_fallback = force_fallback + # Snapshot settings for web preview integration (service writes, web reads) + self._snapshot_path = "/tmp/led_matrix_preview.png" + self._snapshot_min_interval_sec = 0.2 # max ~5 fps + self._last_snapshot_ts = 0.0 self._setup_matrix() logger.info("Matrix setup completed in %.3f seconds", time.time() - start_time) @@ -186,6 +190,8 @@ class DisplayManager: if self.matrix is None: # Fallback mode - no actual hardware to update logger.debug("Update display called in fallback mode (no hardware)") + # Still write a snapshot so the web UI can preview + self._write_snapshot_if_due() return # Copy the current image to the offscreen canvas @@ -196,6 +202,9 @@ class DisplayManager: # Swap our canvas references self.offscreen_canvas, self.current_canvas = self.current_canvas, self.offscreen_canvas + + # Write a snapshot for the web preview (throttled) + self._write_snapshot_if_due() except Exception as e: logger.error(f"Error updating display: {e}") @@ -615,4 +624,21 @@ class DisplayManager: else: suffix = {1: 'st', 2: 'nd', 3: 'rd'}.get(day % 10, 'th') - return dt.strftime(f"%b %-d{suffix}") \ No newline at end of file + return dt.strftime(f"%b %-d{suffix}") + + def _write_snapshot_if_due(self) -> None: + """Write the current image to a PNG snapshot file at a limited frequency.""" + try: + now = time.time() + if (now - self._last_snapshot_ts) < self._snapshot_min_interval_sec: + return + # Ensure directory exists + snapshot_dir = os.path.dirname(self._snapshot_path) + if snapshot_dir and not os.path.exists(snapshot_dir): + os.makedirs(snapshot_dir, exist_ok=True) + # Write PNG snapshot + self.image.save(self._snapshot_path, format='PNG') + self._last_snapshot_ts = now + except Exception as e: + # Snapshot failures should never break display; log at debug to avoid noise + logger.debug(f"Snapshot write skipped: {e}") \ No newline at end of file diff --git a/web_interface_v2.py b/web_interface_v2.py index 08ebb2e9..f4f7b0b2 100644 --- a/web_interface_v2.py +++ b/web_interface_v2.py @@ -57,32 +57,45 @@ class DisplayMonitor: def _monitor_loop(self): global display_manager, current_display_data + snapshot_path = "/tmp/led_matrix_preview.png" while self.running: try: - if display_manager and hasattr(display_manager, 'image'): - # Convert raw image to base64 (no server-side scaling; client scales visually) + # Prefer service-provided snapshot if available (works when ledmatrix service is running) + if os.path.exists(snapshot_path): + with open(snapshot_path, 'rb') as f: + img_bytes = f.read() + img_str = base64.b64encode(img_bytes).decode() + # If we can infer dimensions from display_manager, include them; else leave 0 + width = display_manager.width if display_manager else 0 + height = display_manager.height if display_manager else 0 + current_display_data = { + 'image': img_str, + 'width': width, + 'height': height, + 'timestamp': time.time() + } + socketio.emit('display_update', current_display_data) + elif display_manager and hasattr(display_manager, 'image'): + # Fallback to in-process manager image img_buffer = io.BytesIO() display_manager.image.save(img_buffer, format='PNG') img_str = base64.b64encode(img_buffer.getvalue()).decode() - current_display_data = { 'image': img_str, 'width': display_manager.width, 'height': display_manager.height, 'timestamp': time.time() } - - # Emit to all connected clients socketio.emit('display_update', current_display_data) except Exception as e: logger.error(f"Display monitor error: {e}", exc_info=True) - # Yield to the async loop; target ~10 FPS to reduce load + # Yield to the async loop; target ~5-10 FPS try: - socketio.sleep(0.1) + socketio.sleep(0.2) except Exception: - time.sleep(0.1) + time.sleep(0.2) display_monitor = DisplayMonitor()