#!/usr/bin/env python3 """ convert.py — Convert a single .txt show file to an Arduino header. Use this to preview or test a single show. For the full workflow (all shows + regenerate shows.h), use convert_all.py. Usage: python converter/convert.py path/to/show.txt Output: show_.h written to the current directory. Move it to arduino/cosplay_lights/ then run: make upload Show file format: Each step is one line: #RRGGBB, duration_ms Lines starting with // are comments and are ignored. Blank lines are ignored. duration_ms controls how long the fade from the PREVIOUS color takes: 0 = instant switch (no fade) 500 = half-second fade 5000 = five-second slow fade To hold a color, add a second step with the same color and the hold time as duration: #FF0000, 0 // snap to red #FF0000, 2000 // hold red for 2 seconds #0000FF, 3000 // fade to blue over 3 seconds SPDX-License-Identifier: BSD-2-Clause """ import sys import re from pathlib import Path STEP_PATTERN = re.compile(r'^\s*#([0-9A-Fa-f]{6})\s*,\s*(\d+)') def filename_to_symbol(stem: str) -> str: """Convert a filename stem to an uppercase C array symbol.""" clean = re.sub(r'[^a-zA-Z0-9]', '_', stem) return "SHOW_" + clean.upper() def hex_to_rgb(hex_str: str) -> tuple[int, int, int]: return int(hex_str[0:2], 16), int(hex_str[2:4], 16), int(hex_str[4:6], 16) def parse_show(filepath: Path) -> list[tuple[int, int, int, int]]: """ Parse a .txt show file. Returns list of (r, g, b, duration_ms) tuples. Raises ValueError on malformed lines. """ steps = [] with open(filepath) as f: for lineno, raw_line in enumerate(f, 1): line = raw_line.split("//")[0].strip() if not line: continue match = STEP_PATTERN.match(line) if not match: raise ValueError( f"Line {lineno}: expected '#RRGGBB, duration_ms' " f"but got: {raw_line.rstrip()!r}" ) r, g, b = hex_to_rgb(match.group(1)) duration = int(match.group(2)) steps.append((r, g, b, duration)) if not steps: raise ValueError("Show file contains no steps.") return steps def render_header(steps: list, source_name: str, symbol: str) -> str: """Render parsed steps as a C++ header file string.""" lines = [ f"// Generated by convert.py from: {source_name}", "// Do not edit manually — edit the .txt file instead.", "// SPDX-License-Identifier: BSD-2-Clause", "", "#pragma once", '#include "lightshow_format.h"', "", f"const Step {symbol}[] PROGMEM = {{", ] for r, g, b, dur in steps: hex_color = f"#{r:02X}{g:02X}{b:02X}" lines.append(f" {{{r:3d}, {g:3d}, {b:3d}, {dur:5d}}}, // {hex_color}, {dur}ms") lines += [ "};", f"const uint16_t {symbol}_LENGTH = {len(steps)};", "", ] return "\n".join(lines) def main() -> None: if len(sys.argv) != 2: print(__doc__) sys.exit(1) source_path = Path(sys.argv[1]) if not source_path.exists(): print(f"Error: file not found: {source_path}") sys.exit(1) try: steps = parse_show(source_path) except ValueError as e: print(f"Error: {e}") sys.exit(1) symbol = filename_to_symbol(source_path.stem) header_text = render_header(steps, source_path.name, symbol) output_path = Path(f"show_{source_path.stem}.h") output_path.write_text(header_text, encoding="utf-8") print(f"OK {len(steps)} steps → {output_path}") print(f" Move to 'arduino/cosplay_lights/' then run 'make upload'.") print(f" Or run 'make shows' to convert all shows at once.") if __name__ == "__main__": main()