"""Hold a real-driver visual pattern until systemd or the operator stops it.""" from __future__ import annotations import argparse import json from pathlib import Path import signal import time from PIL import Image from app.display.matrix_driver import MatrixDisplayDriver from app.display.ota_indicator import render_ota_indicator from app.display.transforms import apply_orientation, apply_row_bit_order, get_row_bit_order from app.display.wifi_indicator import ( WIFI_SCROLL_FPS, WIFI_SCROLL_GAP_PIXELS, WIFI_SCROLL_PIXELS_PER_SECOND, render_wifi_indicator, text_width, ) def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--brightness", type=int, default=50) parser.add_argument("--mode", choices=("wifi", "complex", "ota"), default="wifi") parser.add_argument( "--message", default="SSID: DRIVER-TEST IP: http://192.168.1.2:8080/", ) parser.add_argument("--progress-file") parser.add_argument("--orientation", type=int, choices=(0, 90, 180, 270), default=0) args = parser.parse_args() if not 1 <= args.brightness <= 100: parser.error("brightness must be in 1..100") stopping = False def request_stop(_signum: int, _frame: object) -> None: nonlocal stopping stopping = True signal.signal(signal.SIGINT, request_stop) signal.signal(signal.SIGTERM, request_stop) driver = MatrixDisplayDriver(args.brightness, limit_refresh_rate_hz=100) cycle_pixels = ( 64 + text_width(args.message) + WIFI_SCROLL_GAP_PIXELS if args.mode == "wifi" else 192 ) started = time.monotonic() next_frame = started try: while not stopping: now = time.monotonic() offset = ((now - started) * WIFI_SCROLL_PIXELS_PER_SECOND) % cycle_pixels if args.mode == "wifi": frame = render_wifi_indicator( args.message, offset=offset, connected=True, ) elif args.mode == "complex": frame = render_complex_pattern(offset) else: percent, stage = read_ota_progress(args.progress_file) frame = render_ota_indicator(percent, f"{stage}:{int(offset) % 4}") transformed = apply_row_bit_order( apply_orientation(frame, args.orientation), get_row_bit_order(), ) driver.set_image(transformed) next_frame += 1.0 / WIFI_SCROLL_FPS time.sleep(max(0.0, next_frame - time.monotonic())) finally: driver.clear() driver.close() return 0 def read_ota_progress(filename: str | None) -> tuple[int, str]: if not filename: return 0, "准备更新" try: document = json.loads(Path(filename).read_text(encoding="utf-8")) job = document.get("job") if isinstance(document, dict) else None if not isinstance(job, dict): return 0, "准备更新" return max(0, min(100, int(job.get("percent", 0)))), str(job.get("message") or "更新中")[:80] except (OSError, UnicodeError, json.JSONDecodeError, TypeError, ValueError): return 0, "准备更新" def render_complex_pattern(offset: float) -> Image.Image: """Render a dense, high-contrast 192-pixel panorama through a 64px window.""" phase = int(offset) % 192 pixels: list[tuple[int, int, int]] = [] palette = ( (255, 32, 32), (255, 176, 24), (48, 255, 64), (32, 224, 255), (48, 64, 255), (224, 48, 255), ) for y in range(64): for x in range(64): source_x = (x + phase) % 192 base = palette[(source_x // 16) % len(palette)] checker = ((source_x // 4) ^ (y // 4)) & 1 diagonal = ((source_x + y * 2) // 8) & 1 gain = 1.0 if checker else 0.48 if diagonal: gain *= 0.78 radial = ((source_x % 32) - 16) ** 2 + (y - 32) ** 2 if 70 <= radial <= 150: color = (255, 255, 255) elif radial < 36: color = (8, 8, 8) else: color = tuple(max(8, min(255, int(channel * gain))) for channel in base) pixels.append(color) image = Image.new("RGB", (64, 64)) image.putdata(pixels) return image if __name__ == "__main__": raise SystemExit(main())