From 23570b0dcef935d465e7c9ea5f50e94f836ad4af Mon Sep 17 00:00:00 2001 From: Bas Grolleman Date: Sat, 23 May 2026 10:03:35 +0200 Subject: [PATCH] Adding loop function --- arduino/cosplay_lights/cosplay_lights.ino | 19 +++++++--- arduino/cosplay_lights/lightshow_format.h | 8 +++- arduino/cosplay_lights/show_blue_breath.h | 15 ++++++++ arduino/cosplay_lights/show_blue_pulse.h | 13 ------- arduino/cosplay_lights/shows.h | 12 +++--- converter/convert_all.py | 45 ++++++++++++++++------- converter/shows/blue_breath.txt | 12 ++++++ converter/shows/blue_pulse.txt | 9 ----- 8 files changed, 86 insertions(+), 47 deletions(-) create mode 100644 arduino/cosplay_lights/show_blue_breath.h delete mode 100644 arduino/cosplay_lights/show_blue_pulse.h create mode 100644 converter/shows/blue_breath.txt delete mode 100644 converter/shows/blue_pulse.txt diff --git a/arduino/cosplay_lights/cosplay_lights.ino b/arduino/cosplay_lights/cosplay_lights.ino index 7d91dc4..f3e8914 100644 --- a/arduino/cosplay_lights/cosplay_lights.ino +++ b/arduino/cosplay_lights/cosplay_lights.ino @@ -5,7 +5,7 @@ * A single button navigates between shows: * 1 tap — next show * 2 taps — previous show - * hold — reset to show 0 (slow blue pulse) + * hold — reset to show 0 (blue breath) * * To add or update shows: * 1. Add or edit a .txt file in converter/shows/ @@ -66,10 +66,19 @@ static float step_progress(const Step& step) { return (t < 1.0f) ? t : 1.0f; } -// Complete the current step and advance to the next (loops at end of show). +// Complete the current step and advance to the next. +// SHOW_LOOP wraps back to step 0; SHOW_SINGLE loads the next show when the last step ends. static void advance_step(CRGB reached_color) { s_from_color = reached_color; - s_step_index = (s_step_index + 1) % s_show.length; + uint16_t next = s_step_index + 1; + if (next >= s_show.length) { + if (s_show.mode == SHOW_SINGLE) { + load_show((s_show_index + 1) % SHOW_COUNT); + return; + } + next = 0; + } + s_step_index = next; s_step_start = millis(); } @@ -78,7 +87,7 @@ static void advance_step(CRGB reached_color) { void setup() { leds_begin(); button_begin(BUTTON_PIN); - load_show(0); // start on show 0 — slow blue pulse + load_show(0); // start on show 0 — blue breath } void loop() { @@ -93,7 +102,7 @@ void loop() { load_show((s_show_index + SHOW_COUNT - 1) % SHOW_COUNT); } if (evt == BTN_HOLD) { - // Reset to show 0 (blue pulse) from any position. + // Reset to show 0 (blue breath) from any position. load_show(0); } diff --git a/arduino/cosplay_lights/lightshow_format.h b/arduino/cosplay_lights/lightshow_format.h index 5685eda..c40337a 100644 --- a/arduino/cosplay_lights/lightshow_format.h +++ b/arduino/cosplay_lights/lightshow_format.h @@ -12,16 +12,21 @@ #include #include +// Show playback modes. +#define SHOW_LOOP 0 // repeat the show indefinitely +#define SHOW_SINGLE 1 // play once, then auto-advance to the next show + // One step in a lightshow: a target color and the time to reach it. struct Step { uint8_t r, g, b; // Target RGB color (0–255 each) uint16_t duration_ms; // Milliseconds to fade from the previous color (0 = instant) }; -// Descriptor for one complete show: a pointer to its PROGMEM Step array and its length. +// Descriptor for one complete show. struct ShowDef { const Step* steps; // Pointer to a PROGMEM Step array uint16_t length; // Number of steps in the array + uint8_t mode; // SHOW_LOOP or SHOW_SINGLE }; // Read one Step from PROGMEM into a regular struct. @@ -41,5 +46,6 @@ inline ShowDef read_show_def(const ShowDef* ptr) { ShowDef sd; sd.steps = (const Step*)pgm_read_word(&ptr->steps); sd.length = pgm_read_word(&ptr->length); + sd.mode = pgm_read_byte(&ptr->mode); return sd; } diff --git a/arduino/cosplay_lights/show_blue_breath.h b/arduino/cosplay_lights/show_blue_breath.h new file mode 100644 index 0000000..fb03f87 --- /dev/null +++ b/arduino/cosplay_lights/show_blue_breath.h @@ -0,0 +1,15 @@ +// Generated by convert_all.py from: blue_breath.txt +// Do not edit manually — edit the .txt file and run: make shows +// SPDX-License-Identifier: BSD-2-Clause + +#pragma once +#include "lightshow_format.h" + +const Step SHOW_BLUE_BREATH[] PROGMEM = { + { 5, 0, 24, 0}, // #050018, 0ms + { 32, 32, 255, 2500}, // #2020FF, 2500ms + { 32, 32, 255, 400}, // #2020FF, 400ms + { 5, 0, 24, 2500}, // #050018, 2500ms + { 5, 0, 24, 800}, // #050018, 800ms +}; +const uint16_t SHOW_BLUE_BREATH_LENGTH = 5; diff --git a/arduino/cosplay_lights/show_blue_pulse.h b/arduino/cosplay_lights/show_blue_pulse.h deleted file mode 100644 index aa26973..0000000 --- a/arduino/cosplay_lights/show_blue_pulse.h +++ /dev/null @@ -1,13 +0,0 @@ -// Generated by convert_all.py from: blue_pulse.txt -// Do not edit manually — edit the .txt file and run: make shows -// SPDX-License-Identifier: BSD-2-Clause - -#pragma once -#include "lightshow_format.h" - -const Step SHOW_BLUE_PULSE[] PROGMEM = { - { 5, 0, 37, 0}, // #050025, 0ms - { 40, 40, 255, 3000}, // #2828FF, 3000ms - { 5, 0, 37, 3000}, // #050025, 3000ms -}; -const uint16_t SHOW_BLUE_PULSE_LENGTH = 3; diff --git a/arduino/cosplay_lights/shows.h b/arduino/cosplay_lights/shows.h index 753a489..d0bb1af 100644 --- a/arduino/cosplay_lights/shows.h +++ b/arduino/cosplay_lights/shows.h @@ -4,7 +4,7 @@ // Do not edit manually — add .txt files to converter/shows/ instead. // ===================================================================== // -// Show 0 is always the home/reset show (slow blue pulse). +// Show 0 is always the home/reset show (blue breath). // Holding the button resets back to show 0. // // SPDX-License-Identifier: BSD-2-Clause @@ -13,17 +13,17 @@ #include "lightshow_format.h" // ---- Individual show data ---------------------------------------------- -#include "show_blue_pulse.h" +#include "show_blue_breath.h" #include "show_example_fade.h" #include "show_example_party.h" #include "show_example_pulse.h" // ---- Show index (PROGMEM) ---------------------------------------------- const ShowDef SHOWS[] PROGMEM = { - {SHOW_BLUE_PULSE, SHOW_BLUE_PULSE_LENGTH}, // 0 — home show - {SHOW_EXAMPLE_FADE, SHOW_EXAMPLE_FADE_LENGTH}, // 1 - {SHOW_EXAMPLE_PARTY, SHOW_EXAMPLE_PARTY_LENGTH}, // 2 - {SHOW_EXAMPLE_PULSE, SHOW_EXAMPLE_PULSE_LENGTH}, // 3 + {SHOW_BLUE_BREATH, SHOW_BLUE_BREATH_LENGTH, SHOW_LOOP}, // 0 — home show + {SHOW_EXAMPLE_FADE, SHOW_EXAMPLE_FADE_LENGTH, SHOW_LOOP}, // 1 + {SHOW_EXAMPLE_PARTY, SHOW_EXAMPLE_PARTY_LENGTH, SHOW_LOOP}, // 2 + {SHOW_EXAMPLE_PULSE, SHOW_EXAMPLE_PULSE_LENGTH, SHOW_LOOP}, // 3 }; const uint8_t SHOW_COUNT = 4; diff --git a/converter/convert_all.py b/converter/convert_all.py index 05bc07a..1949362 100644 --- a/converter/convert_all.py +++ b/converter/convert_all.py @@ -28,7 +28,7 @@ SHOWS_DIR = SCRIPT_DIR / "shows" SKETCH_DIR = ROOT_DIR / "arduino" / "cosplay_lights" # The special first show — always placed at index 0. -HOME_SHOW = "blue_pulse" +HOME_SHOW = "blue_breath" # Regex to match a valid step line: #RRGGBB, duration_ms STEP_PATTERN = re.compile(r'^\s*#([0-9A-Fa-f]{6})\s*,\s*(\d+)') @@ -36,6 +36,16 @@ STEP_PATTERN = re.compile(r'^\s*#([0-9A-Fa-f]{6})\s*,\s*(\d+)') # ---- Helpers ----------------------------------------------------------- +def parse_mode(filepath: Path) -> str: + """Return the C mode constant for this show. Defaults to SHOW_LOOP.""" + with open(filepath) as f: + for line in f: + m = re.match(r'^\s*//\s*mode:\s*(\w+)', line, re.IGNORECASE) + if m: + return "SHOW_SINGLE" if m.group(1).lower() == "single" else "SHOW_LOOP" + return "SHOW_LOOP" + + def filename_to_symbol(stem: str) -> str: """Convert a filename stem to an uppercase C array symbol. e.g. 'example_fade' → 'SHOW_EXAMPLE_FADE'""" clean = re.sub(r'[^a-zA-Z0-9]', '_', stem) @@ -94,15 +104,15 @@ def render_show_header(steps: list, source_name: str, symbol: str) -> str: return "\n".join(lines) -def render_shows_index(ordered_stems: list[str]) -> str: - """Render the master shows.h index file.""" - includes = "\n".join(f'#include "show_{stem}.h"' for stem in ordered_stems) +def render_shows_index(ordered: list[tuple[str, str]]) -> str: + """Render the master shows.h index file. ordered = [(stem, mode_constant), ...]""" + includes = "\n".join(f'#include "show_{stem}.h"' for stem, _ in ordered) entries = "\n".join( - f" {{{filename_to_symbol(s)}, {filename_to_symbol(s)}_LENGTH}}," + f" {{{filename_to_symbol(s)}, {filename_to_symbol(s)}_LENGTH, {mode}}}," + (" // 0 — home show" if i == 0 else f" // {i}") - for i, s in enumerate(ordered_stems) + for i, (s, mode) in enumerate(ordered) ) - count = len(ordered_stems) + count = len(ordered) return f"""\ // ===================================================================== // shows.h — Master show index. @@ -110,7 +120,7 @@ def render_shows_index(ordered_stems: list[str]) -> str: // Do not edit manually — add .txt files to converter/shows/ instead. // ===================================================================== // -// Show 0 is always the home/reset show (slow blue pulse). +// Show 0 is always the home/reset show (blue breath). // Holding the button resets back to show 0. // // SPDX-License-Identifier: BSD-2-Clause @@ -133,7 +143,7 @@ const uint8_t SHOW_COUNT = {count}; # ---- Main -------------------------------------------------------------- def main() -> None: - txt_files = sorted(SHOWS_DIR.glob("*.txt")) + txt_files = sorted(SHOWS_DIR.glob("*.txt"), key=lambda p: p.stem.lower()) if not txt_files: print(f"No .txt files found in {SHOWS_DIR}") sys.exit(1) @@ -150,19 +160,20 @@ def main() -> None: SKETCH_DIR.mkdir(parents=True, exist_ok=True) - converted = [] + converted = [] # list of (stem, mode_constant) errors = [] for txt_path in ordered_files: stem = txt_path.stem symbol = filename_to_symbol(stem) + mode = parse_mode(txt_path) try: steps = parse_show(txt_path) header = render_show_header(steps, txt_path.name, symbol) out = SKETCH_DIR / f"show_{stem}.h" out.write_text(header, encoding="utf-8") - print(f" OK {txt_path.name} → show_{stem}.h ({len(steps)} steps)") - converted.append(stem) + print(f" OK {txt_path.name} → show_{stem}.h ({len(steps)} steps, {mode})") + converted.append((stem, mode)) except ValueError as e: print(f" ERR {e}") errors.append(stem) @@ -174,10 +185,18 @@ def main() -> None: print("No shows converted — shows.h not updated.") sys.exit(1) + # Remove stale show_*.h files no longer in the converted list. + expected = {SKETCH_DIR / f"show_{stem}.h" for stem, _ in converted} + for stale in SKETCH_DIR.glob("show_*.h"): + if stale not in expected: + stale.unlink() + print(f" RM {stale.name} (removed — no matching .txt file)") + # Regenerate shows.h index_path = SKETCH_DIR / "shows.h" index_path.write_text(render_shows_index(converted), encoding="utf-8") - print(f"\n OK shows.h updated ({len(converted)} shows, show 0 = {converted[0]})") + stems = [s for s, _ in converted] + print(f"\n OK shows.h updated ({len(converted)} shows, show 0 = {stems[0]})") print( " Run 'make upload' to compile and send to the Arduino.") diff --git a/converter/shows/blue_breath.txt b/converter/shows/blue_breath.txt new file mode 100644 index 0000000..55d8617 --- /dev/null +++ b/converter/shows/blue_breath.txt @@ -0,0 +1,12 @@ +// Blue breath — home / reset show (always index 0) +// A slow, natural breathing effect in deep blue. +// Loops continuously as the default show. +// +// mode: loop +// Format: #RRGGBB, duration_ms + +#050018, 0 // snap to near-black blue — sets the starting point +#2020FF, 2500 // breathe in — slow rise to bright blue +#2020FF, 400 // hold at peak +#050018, 2500 // breathe out — slow fall to near-black +#050018, 800 // pause before next breath diff --git a/converter/shows/blue_pulse.txt b/converter/shows/blue_pulse.txt deleted file mode 100644 index c709820..0000000 --- a/converter/shows/blue_pulse.txt +++ /dev/null @@ -1,9 +0,0 @@ -// Blue pulse — home / reset show (always index 0) -// A slow, gentle breathing effect in deep blue. -// Loops continuously as the default show. -// -// Format: #RRGGBB, duration_ms - -#050025, 0 // snap to dim blue — sets the starting point -#2828FF, 3000 // slow breathe up to bright blue -#050025, 3000 // slow breathe back down to dim