diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..f1fac95 Binary files /dev/null and b/.DS_Store differ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..61d8379 --- /dev/null +++ b/LICENSE @@ -0,0 +1,83 @@ +Monada Dominion Non-Commercial No-Derivatives Source License (MD-NC-ND) v1.0 + +IMPORTANT: THIS IS NOT AN OPEN SOURCE LICENSE. IT DOES NOT GRANT PERMISSION FOR COMMERCIAL USE OR FOR DISTRIBUTION OF MODIFIED VERSIONS. BY USING ANY PORTION OF THE LICENSED MATERIAL YOU ACCEPT THESE TERMS. + +0. Definitions +"Licensor" means the copyright holder(s) listed in the copyright notice(s). +"Licensed Material" means the source code, build scripts, asset files, and accompanying documentation in this repository (the "Project"). + +"Source Form" means the preferred form for making modifications, including all associated build or configuration files. + +"Verbatim Copy" means an exact, unmodified copy of the Licensed Material, preserving all file contents and notices. + +"Non-Commercial Use" means any use that (a) is not conducted for or directed toward commercial advantage or monetary compensation; and (b) is not incorporated into, or used to provision, a product or service offered for a fee, subscription, advertising revenue, or other consideration. Academic research, personal study, internal evaluation by a non-profit organization, and classroom teaching are Non-Commercial Uses provided no fee is charged specifically for access to, or use of, the Licensed Material beyond cost-recovery for media or hosting. + +"Commercial Use" means any use that is not Non-Commercial Use. Operating the Licensed Material (modified or unmodified) as part of a hosted or managed service for third parties is Commercial Use. + +1. Grant of Limited Rights +Subject to your full compliance with this License, the Licensor grants you a personal, worldwide, revocable, non-exclusive, non-transferable, royalty-free license to: + (a) Use and execute the Licensed Material in Source Form or compiled form strictly for Non-Commercial Use; and + (b) Create temporary, incidental copies (e.g., in object code, build artifacts, caches) necessary for such Non-Commercial Use; and + (c) Distribute Verbatim Copies for Non-Commercial Use, provided Section 4 (Attribution & Notice) is satisfied. + +2. No Derivatives / Modification Restriction +You may not modify, adapt, translate, reverse engineer (except to the extent required by applicable law), or create derivative works of the Licensed Material, except that you may make purely ephemeral modifications strictly necessary to (i) configure build parameters (e.g., editing a .env or config file), or (ii) apply security patches you authored for internal Non-Commercial Use. ANY distribution of a modified or derivative version is expressly prohibited. + +3. Prohibited Activities (Without a Separate Commercial / Derivative License) +Without prior written permission from the Licensor you must NOT: + (a) Engage in Commercial Use of the Licensed Material (modified or unmodified); + (b) Offer, sell, rent, lease, sublicense, or otherwise commercialize the Licensed Material; + (c) Provide the Licensed Material (modified or unmodified) as part of a service, SaaS, hosted, or managed offering; + (d) Distribute modified, patched, combined, or derivative versions; + (e) Remove, alter, or obscure copyright, license, trademark, or attribution notices; + (f) Circumvent or remove any technical protection or usage limitations applied by the Licensor. + +4. Attribution & Notice +All Verbatim Copies you distribute must retain in all files (or in a top-level NOTICE file if present): + - Original copyright notice(s); + - This License text in full; + - A notice substantially in the form: "This distribution contains the Monada Dominion project licensed for NON-COMMERCIAL, NO-DERIVATIVES use under the MD-NC-ND License v1.0. See LICENSE."; + - A URL or repository reference to the canonical upstream source (if provided by Licensor). + +5. Patent Notice +NO PATENT LICENSE IS GRANTED OR IMPLIED BY THIS LICENSE. If you initiate or threaten patent litigation (including cross-claim or counterclaim) alleging that the Licensed Material or a contribution thereto infringes any patent, your rights under this License terminate immediately. + +6. Feedback +You may (but are not required to) submit ideas, feedback, or suggestions ("Feedback"). By submitting Feedback you grant Licensor a perpetual, irrevocable, worldwide, transferable, sublicensable, royalty-free license to use, incorporate, and distribute the Feedback without obligation or attribution. + +7. Termination +This License terminates automatically without notice if you breach any term. Upon termination you must cease all use and distribution, destroy local copies (excluding automatically generated backup archives retained unintentionally), and certify compliance upon request. Sections 4–13 survive termination. + +Licensor may, at its sole discretion, offer you a different (e.g., commercial or derivative) license under separate written terms. + +8. Disclaimer of Warranty +THE LICENSED MATERIAL IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, AND NON-INFRINGEMENT. YOUR USE IS AT YOUR SOLE RISK. + +9. Limitation of Liability +TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT WILL LICENSOR OR CONTRIBUTORS BE LIABLE FOR ANY INDIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL, EXEMPLARY, OR PUNITIVE DAMAGES; OR FOR ANY LOSS OF PROFITS, REVENUE, DATA, GOODWILL, OR BUSINESS INTERRUPTION; OR FOR SUBSTITUTE GOODS OR SERVICES; ARISING OUT OF OR RELATED TO THIS LICENSE OR USE OF THE LICENSED MATERIAL, HOWEVER CAUSED AND UNDER ANY THEORY OF LIABILITY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. TOTAL AGGREGATE LIABILITY SHALL NOT EXCEED ONE HUNDRED (100) USD OR THE AMOUNT (IF ANY) PAID TO LICENSOR FOR THE LICENSED MATERIAL DURING THE SIX (6) MONTHS PRIOR TO THE CLAIM. + +10. Compliance with Law +You are solely responsible for compliance with all applicable laws, regulations, and third-party rights in connection with your use of the Licensed Material. + +11. Severability +If any provision of this License is held unenforceable, that provision will be reformed only to the extent necessary to make it enforceable. The remaining provisions remain in full force and effect. + +12. No Waiver +Failure of Licensor to enforce any right or provision shall not constitute a waiver of future enforcement of that right or provision. + +13. Governing Law (Optional) +If the Licensor designates a jurisdiction in the copyright notice (e.g., "Governing Law: State / Country"), this License will be governed by the laws of that jurisdiction excluding its conflict-of-law principles. Otherwise, governing law is determined by applicable conflict-of-law rules. + +14. Entire Agreement +This License constitutes the entire agreement between you and the Licensor concerning the Licensed Material and supersedes all prior or contemporaneous understandings regarding its subject matter. + +15. Versioning +The Licensor may publish new versions of this License with a unique version number. Unless explicitly stated otherwise, you may continue to use the Licensed Material under the version originally applied. + +16. Contact for Commercial / Derivative Licensing +To request commercial, derivative, or broader usage rights, contact: [make an issue]. Granting such rights is at Licensor's sole discretion. + +Copyright (c) 2025 Monada Dominion and/or individual contributors as noted. +All rights reserved except as expressly granted herein. + +END OF LICENSE diff --git a/README.md b/README.md deleted file mode 100644 index 4b352a6..0000000 --- a/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# Echoes of Tomorrow - Toppiece Firmware - -This repository contains the embedded firmware for the microcontrollers that operate the toppiece in the **Echoes of Tomorrow** GLOW 2025 project. - -## Overview -- Written in C++ -- Compiled and uploaded to microcontrollers (ESP32 S3) -- Receives when the climax starts -- Receives when the climax is over - -## Project Structure -``` -arm/ -├── firmware/ # C++ firmware source -├── tests/ # Diagnostic sketches/tests -└── documentation/ # Schematics and setup guides -``` - -## Setup Instructions -1. Open `firmware/top.ino` in the Arduino IDE -2. Select the correct board (ESP32 doit devkit) -3. Upload to the connected board - -## Testing -Diagnostic sketches are in `tests/` - -## Documentation -See `documentation/` or the [Wiki](https://github.com/GLOW-Delta-2025/master/wiki) for details on architecture, function descriptions, and setup. - -## Branches -- `main`: Production-ready code -- `develop`: Active development -- `feature/`, `bugfix/`, `hotfix/`: Use Git Flow - -## Commit Convention -```text -: -``` -Example: `fix: LED flickering on pin 6` diff --git a/big_lamps_prototype/README.md b/big_lamps_prototype/README.md new file mode 100644 index 0000000..8ebc2f2 --- /dev/null +++ b/big_lamps_prototype/README.md @@ -0,0 +1,77 @@ +GLOW 2025 — Big Lamps Prototype (Teensy + Dual DMX Shields) + +Overview +- Teensy 4.1 drives two TinkerKit T040060 DMX shields as RS‑485 transmitters. +- Two DMX universes: Serial1 (pin 1 TX) for 3 lamps, Serial2 (pin 8 TX) for 2 lamps. +- All 5 lamps behave the same: channels 1–108 are mirrored from U1 to U2. +- Primary code: `top/big_lamps_prototype/main_updated.cpp` (paste into a `.ino` for Arduino IDE if needed). The older `main.cpp` can be ignored. + +Hardware setup +- Shields act only as RS‑485 line drivers; DMX is controller‑transmit only. +- Wiring (Teensy 4.1): + - Shield #1 DI ← Teensy pin 1 (Serial1 TX) → Universe 1 (3 fixtures in chain) + - Shield #2 DI ← Teensy pin 8 (Serial2 TX) → Universe 2 (2 fixtures in chain) + - Optional RX lines are not used. + - Common GND between Teensy, shields, and lamp PSU. + - DMX/XLR pins: 1=GND, 2=Data− (B), 3=Data+ (A). + - Termination: last fixture in each chain ON (120 Ω). + - DE/RE on shields: tie for transmit enable per shield docs (controller is TX‑only). + +DMX addressing (assumes 108 channels/fixture) +- Universe 1 chain: A1=1, A2=109, A3=217. +- Universe 2 chain: A4=1, A5=109. +- All fixtures use the same 108‑channel personality. Set via menu/DIP. + +Code architecture (main_updated.cpp) +- Libraries: TeensyDMX, Arduino. +- Two Senders: `dmx1(Serial1)` and `dmx2(Serial2)`. +- Drive channels 1..108 each frame from a generated pattern (moving gradient). +- Mirroring: by default the same value `v` is written to U1 and U2 for each channel. +- Validation mode (optional): define `VALIDATE_DIFFERENT_UNIS` to drive U2 with a different pattern for hardware path checks. +- Diagnostics: periodic `logFrame(frame, phase, U1_ch1, U2_ch1)` via Serial. +- Timing: simple `elapsedMillis` tick with `delay(5)` pacing (logic‑level simulation of ~100–200 Hz update cadence). + +Build and upload (Arduino IDE) +1) Open Arduino IDE (with Teensyduino installed). +2) Create a new sketch and paste `main_updated.cpp` content (Arduino requires `.ino`). +3) Tools → Board: Teensy 4.1; USB Type: Serial; CPU Speed: 600 MHz. +4) Upload and open Serial Monitor at 115200 baud. + +Expected behavior on hardware +- Both universes output identical data on channels 1–108 (mirrored), yielding identical looks on the 3‑lamp chain and the 2‑lamp chain. +- Serial prints every ~50 frames: `frame=... phase=... ch1=U1/U2`. +- If `VALIDATE_DIFFERENT_UNIS` is defined: U2 intentionally differs (inverse/faster modulation) for visual validation. + +No‑hardware host simulator +- Location: `top/big_lamps_prototype/host_sim`. +- What it does: stubs Arduino and TeensyDMX APIs and runs `main_updated.cpp` on macOS. This validates logic and logging without real hardware. +- Build and run (macOS): +```zsh +cd ~/Documents/GLOW_2025/top/big_lamps_prototype/host_sim +clang++ -std=c++17 -Iinclude run.cpp -o sim +./sim | head -50 +``` +- Expected output: the same startup and periodic frame logs you’d see on Teensy, then “Host sim finished.” +- Notes: this does not simulate DMX timing or the physical bus; it exercises the code flow only. + +Planned command protocol (USB Serial and later a spare UART) +- During development: send commands over USB Serial; for production also accept via a spare UART (e.g., Serial3). +- Minimal commands (to be added): + - `FILL ` — set ch 1–108 to value 0..255 + - `BLACKOUT` — set all to 0 + - `GRAD ` — gradient animation with speed 1..10 + - `SET ` — set channel + - `PING` — respond `PONG` + +Troubleshooting +- No light: verify fixture start address=1 and correct personality; confirm termination; check DE/RE enable; swap XLR pins 2/3 if needed. +- One universe dark: check correct Teensy pin (1 vs 8), shield power/GND, and cable. +- Serial.printf issues: the code uses a safe `logFrame` helper that falls back to Serial.print when printf isn’t available. +- Editor include squiggles: harmless for Arduino builds; use Arduino IDE or PlatformIO to compile/upload. + +Optional: PlatformIO +If you prefer PlatformIO, create a `platformio.ini` (Teensy 4.1, Arduino framework, TeensyDMX lib), then `pio run -t upload` and `pio device monitor -b 115200`. + +License +- See repository root for license information (if applicable). + diff --git a/big_lamps_prototype/docs/18x18W_6in1_c79c0ccf-e4d0-460a-aa4f-dc78af9a8be5.pdf b/big_lamps_prototype/docs/18x18W_6in1_c79c0ccf-e4d0-460a-aa4f-dc78af9a8be5.pdf new file mode 100644 index 0000000..6b64801 Binary files /dev/null and b/big_lamps_prototype/docs/18x18W_6in1_c79c0ccf-e4d0-460a-aa4f-dc78af9a8be5.pdf differ diff --git a/big_lamps_prototype/docs/DMX_States_and_Order.md b/big_lamps_prototype/docs/DMX_States_and_Order.md new file mode 100644 index 0000000..8be307a --- /dev/null +++ b/big_lamps_prototype/docs/DMX_States_and_Order.md @@ -0,0 +1,82 @@ +# DMX Multi‑Universe: States and Execution Order + +Applies to: /top/big_lamps_prototype/main.cpp + +Overview +- Libraries: TeensyDMX (Sender on Serial1/2/3), Arduino core. +- Universes: U1, U2, U3 (U3 initialized but not used in loop). +- Behavior: Brightness sweeps 0→255→0; U1 RGB follow brightness; U2 uses inverse red, zero green, blue follows brightness. + +Key Variables +- brightness (uint8_t): 0..255, the current fade level. +- direction (int): +1 when increasing, −1 when decreasing. +- baseAddr (int): 1, base DMX address for the first fixture in U1. + +High-Level States +1) Setup + - Serial.begin(115200) + - dmxX.begin() for U1, U2, U3 + - For channels 1..512 on each universe: set value = 0 + - Log: "DMX Multi-Universe Started" +2) Running + - Every loop tick (~10 ms): + - Write U1 channels: + - Ch 1 (R) = brightness + - Ch 2 (G) = brightness + - Ch 3 (B) = brightness + - Write U2 channels: + - Ch 1 (R) = 255 - brightness + - Ch 2 (G) = 0 + - Ch 3 (B) = brightness + - Update brightness: brightness += direction + - Edge handling: + - If brightness == 255 → direction = −1 + - If brightness == 0 → direction = +1 + - delay(10) + +Execution Order Per Loop Iteration +1) Compute baseAddr = 1 (constant in this sketch). +2) Set DMX for U1 (Ch 1..3) and U2 (Ch 1..3) based on current brightness. +3) Increment brightness by direction. +4) If at bounds (0 or 255), flip direction. +5) Wait 10 ms. + +Timing Notes +- Update rate: ~100 Hz. +- One full triangle wave cycle: 256 steps up + 256 down = 512 steps ≈ 5.12 s. + +DMX Universe/Channel Mapping Used +- Universe 1 + - Ch 1: R = brightness + - Ch 2: G = brightness + - Ch 3: B = brightness +- Universe 2 + - Ch 1: R = 255 - brightness + - Ch 2: G = 0 + - Ch 3: B = brightness +- Universe 3 + - Initialized, not updated in loop. + +Notes +- DMX channels are 1-based (1..512). +- FIXTURE_CHANNELS and NUM_FIXTURES_U1/U2 are defined for context but not used in loop. +- Direction flip happens exactly at 0 and 255, avoiding under/overflow artifacts. + +State Diagram + +~~~mermaid +stateDiagram-v2 + [*] --> Setup + Setup: Serial + DMX init\nZero all channels + Setup --> Running + + state Running { + [*] --> Increasing + Increasing: direction = +1 + Increasing --> Decreasing: brightness == 255 + Decreasing: direction = -1 + Decreasing --> Increasing: brightness == 0 + } + + Running --> Running: Each ~10 ms tick\n- Set U1/U2 channels\n- brightness += direction +~~~ \ No newline at end of file diff --git a/big_lamps_prototype/docs/IMG_0252.png b/big_lamps_prototype/docs/IMG_0252.png new file mode 100644 index 0000000..67696a8 Binary files /dev/null and b/big_lamps_prototype/docs/IMG_0252.png differ diff --git a/big_lamps_prototype/docs/IMG_0253.png b/big_lamps_prototype/docs/IMG_0253.png new file mode 100644 index 0000000..52795a1 Binary files /dev/null and b/big_lamps_prototype/docs/IMG_0253.png differ diff --git a/big_lamps_prototype/docs/dev plan b/big_lamps_prototype/docs/dev plan new file mode 100644 index 0000000..0e4845f --- /dev/null +++ b/big_lamps_prototype/docs/dev plan @@ -0,0 +1,241 @@ +# GLOW 2025 — DMX Lamps Dev Plan (Corrected) + +Summary +- 5 DMX lamps total (18 LEDs each assumed), all behave the same. +- Split 3 lamps on one TinkerKit T040060 shield (Universe 1) and 2 lamps on the other (Universe 2). +- Teensy 4.1 synchronizes both universes and mirrors the same channel data (ch 1–108) to both. +- Control input: during development via USB Serial; production also able to receive the same commands via a spare UART ("tx_rx" pins). +- Physical limitation: one DMX output reliably drives up to ~3 fixtures chained. +- Controller: Teensy 4.1 with TeensyDMX. TinkerKit shields used only as RS‑485 line drivers (no DmxMaster library). + +Decision +- Standardize on Teensy 4.1 + TeensyDMX (2 universes via Serial1 and Serial2). +- TinkerKit T040060 shields are used as RS‑485 transceivers only. +- Do NOT use the DmxMaster library. + +Open items to confirm +- Fixture model(s) and DMX personality (assume 108-ch until confirmed). +- Exact RGB channel indices for each LED block (or master dimmer layout). +- Whether 3‑pin or 5‑pin XLR mapping (Pin 1 GND, Pin 2 Data−, Pin 3 Data+). + +--- + +## Hardware Architecture + +Universes +- Universe 1 (Serial1, Teensy pin 1 TX): 3 lamps in chain (A1, A2, A3). +- Universe 2 (Serial2, Teensy pin 8 TX): 2 lamps in chain (A4, A5). + +Wiring (Teensy 4.1) +- Serial1 TX: pin 1 → RS‑485 DI on Shield #1 (Universe 1) +- Serial2 TX: pin 8 → RS‑485 DI on Shield #2 (Universe 2) +- Optional RX lines not used for send‑only DMX. +- GND: common between Teensy, shields, and lamp PSU +- XLR to lamps: Pin 1 GND, Pin 2 Data− (B), Pin 3 Data+ (A) +- Termination: last fixture in each chain ON (120 Ω) +- DE/RE on shields: tie to enable transmit (or follow shield docs). DMX is transmit‑only for controller. + +Control UART (commands) +- Use a free UART on Teensy for external commands (e.g., Serial3). Wire RX/TX to your controller’s TX/RX per Teensy 4.1 pinout. Share GND. +- During development, the same commands can be sent over USB Serial. + +Power +- Lamps powered from mains with their PSUs. +- Teensy powered via USB or regulated 5V; do not power lamps from Teensy. + +--- + +## DMX Addressing Plan (assumes 108 channels/fixture) + +Universe 1 (Serial1) +- A1 start: 1 +- A2 start: 1 + 108 = 109 +- A3 start: 217 + +Universe 2 (Serial2) +- A4 start: 1 +- A5 start: 109 + +Set these via fixture menu/DIP and select the same 108‑ch personality on all lamps. + +--- + +## Software Architecture + +Libraries +- qindesign/TeensyDMX (already working in your main_updated.cpp). +- No DmxMaster. + +Structure +- Sender dmx1(Serial1), dmx2(Serial2). +- Address arrays for each universe. +- Frame buffer or generator drives channels 1–108 identically on both universes (mirrored output). +- Optional validation mode can send different patterns to U1 vs U2 for hardware checks. + +Command Input +- USB Serial (Serial) and a free UART (default Serial3) accept simple ASCII commands terminated by \n. +- Minimal protocol: + - `FILL `: set ch 1–108 to value 0..255 + - `GRAD `: enable gradient animation; `speed` 1..10 + - `SET `: set channel 1..108 to value + - `BLACKOUT`: set all to 0 + - `PING`: respond `PONG` + +State Machine (simple) +- INIT → RUN (mirrored output) → optional VALIDATE (different U1/U2 patterns) → RUN + +Timing +- Frame loop at ~100 Hz max; DMX universe refresh is ~44 Hz. Use delay(5–15 ms) or elapsedMillis for pacing. + +Diagnostics +- Periodic Serial prints: which state, frame counter, brightness, last send tick. + +--- + +## Milestones and Tasks + +Phase 0 — Repo/Build Hygiene +- [ ] Keep Teensy code under top/big_lamps_prototype. +- [ ] Optional: add PlatformIO for repeatable builds. + +Phase 1 — Single Lamp Bring‑Up +- [ ] Universe 1 only, one lamp at address 1. +- [ ] Send static test values to 3 primary channels (R/G/B) and verify color. +- [ ] Add Serial prints of set values. + +Phase 2 — Chain of 3 on Universe 1 +- [ ] Set A1=1, A2=109, A3=217. +- [ ] Sweep a simple effect across all three; verify addressing boundaries (A1 doesn’t bleed into A2). + +Phase 3 — Add Universe 2 Hardware +- [ ] Wire Shield #2 to Serial2 TX (pin 8). +- [ ] Add dmx2.begin(), clear(), and a distinct test color so you can visually separate universes. + +Phase 4 — Five Lamps Layout +- [ ] Address A4=1, A5=109 on Universe 2. +- [ ] Implement mirrored write that updates A1..A5 across both universes identically (ch 1–108). + +Phase 4.5 — Dual‑Universe Validation (Optional) +- [ ] Ensure both shields are connected: Shield #1 on pin 1 (U1), Shield #2 on pin 8 (U2). +- [ ] Firmware sends different patterns to channels 1–108 on each universe: + - U1: gradient sweep (0..255) across ch 1–108. + - U2: inverse/pulsing pattern across ch 1–108. +- [ ] Verify each universe updates independently even with only one lamp attached. + +Phase 5 — Command Protocol + Timings +- [ ] Implement USB Serial + Serial3 command parser. +- [ ] Add parameters for animation speed and frame pacing. + +Phase 6 — Polish +- [ ] Add a “blackout” and “all‑on” command for safety. +- [ ] Add a heartbeat LED on Teensy to indicate running. + +Phase 7 — Polish +- [ ] Add a “blackout” and “all‑on” command for safety. +- [ ] Add a heartbeat LED on Teensy to indicate running. + +--- + +## Debug and Verification Steps + +General +- If no light: check termination, swap Data+/- (2/3) if colors are inverted or dead, ensure same DMX mode on fixtures. +- Ensure Teensy GND is tied to shield/XLR pin 1 GND. +- Confirm TeensyDMX is sending: try a single channel at 255 and others 0. + +Per Phase +1) Single Lamp +- [ ] Set channel 1=255; verify expected color or master dimmer. +- [ ] Increment channels 1..10 to map what they do. Log findings. + +2) Three Lamps, One Universe +- [ ] Write ch 108 boundary checks: set last channel of A1=255 and first of A2=0; then flip; ensure isolation. +- [ ] Disconnect middle lamp to test line loading and termination. + +3) Add Universe 2 +- [ ] Use drastically different colors on U1 vs U2 to confirm independent control. +- [ ] Print which Sender is updated each loop. + - [ ] Validation mode: check that ch 1–108 visibly differ between U1 and U2 (e.g., gradient vs inverse pulse). If one universe is dark, recheck pin mapping (pin 1 vs pin 8) and DE/RE enable. + +4) Mirrored Output +- [ ] Verify A1..A5 move together across both universes (same look on both chains). +- [ ] Measure drift visually; adjust loop pacing if needed. + +5) Command Protocol +- [ ] Send `FILL 128`, `BLACKOUT`, `GRAD 4`, `SET 10 255` via USB Serial and via Serial3; confirm behavior. + +Electrical Checks +- [ ] If intermittent flicker: add/balance bias resistors if shield lacks them; ensure last fixture termination ON. +- [ ] Keep DMX cable lengths within spec; avoid USB ground loops. + +--- + +## Risks and Mitigations + +- Unknown fixture channel maps + - Mitigation: quick mapping pass in Phase 1; capture per‑fixture DMX map in a fixtures.md. +- Channel overlap by wrong start addresses + - Mitigation: enforce computed addresses from config; print them at boot. +- Shield bus driver direction control (DE/RE) + - Mitigation: tie DE high/RE low per shield docs; confirm continuous TX. + +--- + +## Deliverables + +- Two‑universe Teensy firmware with mirrored output and simple command protocol. +- fixtures.md with channel map (ground truth from Phase 1). +- Wiring diagram and address table. +- Runbook with test steps and known-good parameters. + +--- + +## Optional: PlatformIO Setup + +Create platformio.ini: +```ini +[env:teensy41] +platform = teensy +board = teensy41 +framework = arduino +lib_deps = qindesign/TeensyDMX@^5 +monitor_speed = 115200 +upload_protocol = teensy-cli +``` + +Commands (macOS): +- Build/Upload: pio run -t upload +- Monitor: pio device monitor -b 115200 + +--- + +## Next Actions (Checklist) + +- [ ] Confirm fixture model and DMX personality (is it truly 108‑ch?). +- [ ] Wire Serial2 → Shield #2 and add dmx2 to code. +- [ ] Set start addresses per plan and test Phases 1–3. +- [ ] Add state machine (SYNC_SHOW → SPECIAL_SHOW) and dial durations. + +--- + +## Quick Test Now (Arduino IDE) + +Goal: verify Teensy drives two DMX shields independently with different patterns on channels 1–108. + +Steps +1. Hardware: connect Shield #1 to Teensy pin 1 (Serial1 TX) and Shield #2 to pin 8 (Serial2 TX). Share GND. Terminate last fixture in each chain. +2. Fixture(s): set a lamp’s start address to 1 and 108‑ch mode; connect it to Universe 1, then Universe 2 (or use two lamps simultaneously). +3. Sketch: open `top/big_lamps_prototype/main_updated.cpp` in Arduino IDE by creating a new sketch and pasting the file contents into a `.ino` (Arduino requires `.ino`). +4. Board: Tools → Board → Teensy 4.1; USB Type: Serial; CPU Speed: 600 MHz. +5. Upload and open Serial Monitor at 115200 baud. + +Expected Result (Mirrored) +- Universe 1 (pin 1) and Universe 2 (pin 8): both show the same pattern when in normal RUN mode (e.g., gradient or fill). +- In optional VALIDATE mode, U1 and U2 differ so you can verify hardware paths. +- Serial prints every ~50 frames: `frame=... state=... ch1=...`. + +If Not Working +- Confirm the lamp is on address 1; try setting channel 1 to 255 temporarily to see any response. +- Swap Data+/- on XLR pins 2/3 if the chain is dark. +- Ensure DE/RE on the shield is enabled for transmit; tie DE high and RE low per shield docs. +- Verify you’re connected to the right universe (pin 1 vs pin 8). \ No newline at end of file diff --git a/big_lamps_prototype/host_sim/README.md b/big_lamps_prototype/host_sim/README.md new file mode 100644 index 0000000..9c6f14a --- /dev/null +++ b/big_lamps_prototype/host_sim/README.md @@ -0,0 +1,15 @@ +Host Simulator (no hardware) + +What it does +- Stubs Arduino and TeensyDMX APIs and runs `main_updated.cpp` on your Mac. +- Prints the same diagnostics your sketch emits. Useful to catch syntax/runtime issues in logic. + +Build (macOS) +``` +clang++ -std=c++17 -Iinclude run.cpp -o sim +./sim | head -50 +``` + +Notes +- This does not simulate DMX timings or shield behavior; it only exercises code flow and prints. +- If you change header paths in the sketch, update the includes in `run.cpp` accordingly. diff --git a/big_lamps_prototype/host_sim/include/Arduino.h b/big_lamps_prototype/host_sim/include/Arduino.h new file mode 100644 index 0000000..b7a3a0d --- /dev/null +++ b/big_lamps_prototype/host_sim/include/Arduino.h @@ -0,0 +1,48 @@ +#pragma once +#include +#include +#include +#include + +using uint8_t = std::uint8_t; +using uint16_t = std::uint16_t; +using uint32_t = std::uint32_t; + +static inline uint32_t millis(); + +// Simulated clock +static inline uint32_t &__sim_millis_ref() { + static uint32_t g = 0; + return g; +} + +static inline uint32_t millis() { return __sim_millis_ref(); } + +static inline void delay(uint32_t ms) { __sim_millis_ref() += ms; } + +struct SerialClass { + void begin(unsigned long) {} + void print(const char *s) { std::cout << s; } + void print(uint32_t v) { std::cout << v; } + void print(int v) { std::cout << v; } + void print(uint8_t v) { std::cout << (unsigned)v; } + void println(const char *s) { std::cout << s << '\n'; } + void println(uint32_t v) { std::cout << v << '\n'; } + void println(int v) { std::cout << v << '\n'; } + void println(uint8_t v) { std::cout << (unsigned)v << '\n'; } + void printf(const char *fmt, ...) { + va_list args; va_start(args, fmt); vprintf(fmt, args); va_end(args); + } +} ; + +static SerialClass Serial; + +// Minimal elapsedMillis compatible with Teensy semantics +class elapsedMillis { +public: + elapsedMillis() : start_(millis()) {} + operator unsigned long() const { return millis() - start_; } + elapsedMillis &operator=(unsigned long val) { start_ = millis() - val; return *this; } +private: + unsigned long start_; +}; diff --git a/big_lamps_prototype/host_sim/include/TeensyDMX.h b/big_lamps_prototype/host_sim/include/TeensyDMX.h new file mode 100644 index 0000000..f3e299c --- /dev/null +++ b/big_lamps_prototype/host_sim/include/TeensyDMX.h @@ -0,0 +1,22 @@ +#pragma once +#include +#include + +namespace qindesign { namespace teensydmx { + +class Sender { +public: + Sender(int) {} + void begin() { } + void clear() { buffer.fill(0); } + void set(int ch, uint8_t val) { if (ch>=1 && ch<=512) buffer[ch-1]=val; } + uint8_t get(int ch) const { return (ch>=1 && ch<=512)? buffer[ch-1]:0; } +private: + std::array buffer{}; +}; + +}} + +// Simulate Serial1/Serial2 identifiers +static const int Serial1 = 1; +static const int Serial2 = 2; diff --git a/big_lamps_prototype/host_sim/run.cpp b/big_lamps_prototype/host_sim/run.cpp new file mode 100644 index 0000000..75d24d2 --- /dev/null +++ b/big_lamps_prototype/host_sim/run.cpp @@ -0,0 +1,24 @@ +#include "include/Arduino.h" +#include "include/TeensyDMX.h" + +// Trick: include the firmware after our stubs so it uses them +#define ARDUINO_TEENSY41 1 +#define CORE_TEENSY 1 + +// Adjust the include path expectation by placing headers in include/ +// and including the original file here. +#include "../main_updated.cpp" + +int main() { + // Run setup once + setup(); + // Execute a few hundred frames + for (int i=0; i<300; ++i) { + loop(); + } + // Print a simple verification for mirroring + uint8_t u1 = qindesign::teensydmx::Sender(0).get(1); // Not accessible; rely on our logging output instead + (void)u1; + Serial.println("Host sim finished."); + return 0; +} diff --git a/big_lamps_prototype/host_sim/sim b/big_lamps_prototype/host_sim/sim new file mode 100755 index 0000000..126f0ee Binary files /dev/null and b/big_lamps_prototype/host_sim/sim differ diff --git a/big_lamps_prototype/main.cpp b/big_lamps_prototype/main.cpp new file mode 100644 index 0000000..c182df1 --- /dev/null +++ b/big_lamps_prototype/main.cpp @@ -0,0 +1,54 @@ +//Initial code + +#include +#include +using namespace qindesign::teensydmx; + +// Create DMX output universes +Sender dmx1(Serial1); // Universe 1 +Sender dmx2(Serial2); // Universe 2 +Sender dmx3(Serial3); // Universe 3 +// Add more if needed + +// Fixture info +const int FIXTURE_CHANNELS = 108; +const int NUM_FIXTURES_U1 = 4; // example: 4 fixtures * 108 = 432 channels +const int NUM_FIXTURES_U2 = 2; // example for universe 2 + +void setup() { + Serial.begin(115200); + dmx1.begin(); + dmx2.begin(); + dmx3.begin(); + + // Initialize all channels to 0 + for (int i = 1; i <= 512; i++) { + dmx1.set(i, 0); + dmx2.set(i, 0); + dmx3.set(i, 0); + } + + Serial.println("DMX Multi-Universe Started"); +} + +void loop() { + // Example: sweep brightness on fixture 1 (Universe 1) + static uint8_t brightness = 0; + static int direction = 1; + + // Update first fixture (red, green, blue channels) + int baseAddr = 1; + dmx1.set(baseAddr + 0, brightness); // Red + dmx1.set(baseAddr + 1, brightness); // Green + dmx1.set(baseAddr + 2, brightness); // Blue + + // Example for Universe 2 (fixture starting at address 1) + dmx2.set(1, 255 - brightness); // Red inverse + dmx2.set(2, 0); + dmx2.set(3, brightness); + + brightness += direction; + if (brightness == 0 || brightness == 255) direction = -direction; + + delay(10); // smooth fade +} diff --git a/big_lamps_prototype/main_updated.cpp b/big_lamps_prototype/main_updated.cpp new file mode 100644 index 0000000..c35f62c --- /dev/null +++ b/big_lamps_prototype/main_updated.cpp @@ -0,0 +1,82 @@ + +// Teensy: drive two DMX shields (mirrored output) +// - Serial1 (pin 1 TX) -> Universe 1 (3 lamps) +// - Serial2 (pin 8 TX) -> Universe 2 (2 lamps) + +#include +#include +using namespace qindesign::teensydmx; + +// Two DMX output universes +Sender dmx1(Serial1); // Universe 1 (pin 1 TX) +Sender dmx2(Serial2); // Universe 2 (pin 8 TX) + +// We actively drive channels 1..108 on each universe +static const int DMX_ACTIVE_CH = 108; +// Define to send different patterns to U1/U2 for validation +//#define VALIDATE_DIFFERENT_UNIS 1 + +// Simple time base for animations +elapsedMillis tick; +uint8_t phase = 0; + +void setup() { + Serial.begin(115200); + + dmx1.begin(); + dmx2.begin(); + + // Initialize all 512 channels to 0 on both universes + for (int ch = 1; ch <= 512; ++ch) { + dmx1.set(ch, 0); + dmx2.set(ch, 0); + } + + Serial.println("DMX dual-universe started: U1=Serial1(pin1), U2=Serial2(pin8). Mirroring ch 1..108 across both universes."); +} + +static inline uint8_t wrap8(int v) { return (uint8_t)(v & 0xFF); } + +// Safe logging helper: uses printf on Teensy, falls back to Serial.print elsewhere +static inline void logFrame(uint32_t frame, uint8_t phase, uint8_t u1, uint8_t u2) { +#if defined(ARDUINO_TEENSY41) || defined(CORE_TEENSY) + Serial.printf("frame=%lu phase=%u ch1=%u/%u\n", (unsigned long)frame, phase, u1, u2); +#else + Serial.print("frame="); Serial.print(frame); + Serial.print(" phase="); Serial.print(phase); + Serial.print(" ch1="); Serial.print(u1); + Serial.print("/"); Serial.println(u2); +#endif +} + +void loop() { + // Advance animation phase ~every 20 ms + if (tick >= 20) { + tick = 0; + phase++; + } + + // Generate one pattern buffer for channels 1..108 + // Example: moving gradient + for (int ch = 1; ch <= DMX_ACTIVE_CH; ++ch) { + uint8_t v = wrap8((ch * 2) + phase); + dmx1.set(ch, v); +#ifdef VALIDATE_DIFFERENT_UNIS + // Optional: different look on U2 to validate hardware paths + uint8_t v2 = 255 - wrap8((ch * 3) + (phase << 1)); + dmx2.set(ch, v2); +#else + // Mirrored output + dmx2.set(ch, v); +#endif + } + + // Periodic diagnostics + static uint32_t frame = 0; + if ((frame++ % 50) == 0) { + logFrame(frame, phase, dmx1.get(1), dmx2.get(1)); + } + + // Pace updates; TeensyDMX will stream continuously + delay(5); +} diff --git a/development/esp32_2_led_strips_prototype/README.md b/development/esp32_2_led_strips_prototype/README.md new file mode 100644 index 0000000..18a059b --- /dev/null +++ b/development/esp32_2_led_strips_prototype/README.md @@ -0,0 +1,266 @@ +# Echoes of Tomorrow - Toppiece Firmware + +This repository contains the embedded firmware for the microcontrollers that operate the toppiece in the **Echoes of Tomorrow** GLOW 2025 project. + +## Overview +- Written in C++ for Arduino IDE +- Runs on ESP32 WROVER board +- Controls addressable RGB LED strips (WS2812B) via FastLED library +- Receives serial commands to trigger light sequences +- Sends confirmation responses and status messages +- Two-tier communication: Serial Monitor (debugging) and TX/RX pins (external control) + +## Main Firmware: `led_strip_control.ino` + +### How It Works + +#### Hardware Configuration +- **Board**: ESP32 WROVER +- **LED Library**: FastLED 3.10.3+ +- **LED Type**: WS2812B addressable RGB strips +- **Default Configuration**: 2 strips enabled (strip 1 and strip 6) + - Strip 1 (GPIO 2): Represents the main 5-strip array (charging effect) + - Strip 6 (GPIO 14): Independent element (intense flow effect) +- **Serial Ports**: + - **Serial**: USB/UART0 at 115200 baud (Arduino IDE Serial Monitor) + - **Serial2**: Hardware UART1 at 115200 baud (TX/RX pins: GPIO17/GPIO16) + +#### Sequence Flow + +**State Machine**: Idle → Active Sequence → Complete → Idle + +**Active Sequence (60 seconds total)**: + +1. **Phase 1 - Charging (30 seconds)** + - Strip 1 progressively fills with LEDs + - Color gradient: Blue → White (simulating power buildup) + - Update rate: Every 100ms + - Effect: Represents energy accumulation + +2. **Phase 2 - Intense Flow (30 seconds)** + - Strip 6 activates with a flowing stream effect + - 15-LED stream moves continuously around the strip + - Gradient: Bright white (head) → Dim blue (tail) + - Random white sparks (30% chance per frame) + - Update rate: Every 50ms (faster for intensity) + - Effect: Represents energy release/climax + +#### Serial Command Protocol + +**All commands end with `##` delimiter** + +##### Incoming Commands (ESP Receives) + +| Command | Format | Response | +|---------|--------|----------| +| Start Sequence | `!!TOP:REQUEST:START_CLIMAX_TOP{TIME}##` | `!MASTER:CONFIRM:START_CLIMAX_TOP##` | +| Stop Sequence | `!!TOP:REQUEST:STOP_CLIMAX_TOP##` | `!!MASTER:CONFIRM:STOP_CLIMAX_TOP##` | + +**Example**: +``` +Send: !!TOP:REQUEST:START_CLIMAX_TOP{1000}## +Receive: !MASTER:CONFIRM:START_CLIMAX_TOP## +[Sequence runs for 60 seconds] +Receive: !!MASTER:CONFIRM:STOP_CLIMAX_TOP## +``` + +##### Outgoing Confirmations (ESP Sends to Both Serial & Serial2) + +- **On START received**: `!MASTER:CONFIRM:START_CLIMAX_TOP##` +- **On completion or manual STOP**: `!!MASTER:CONFIRM:STOP_CLIMAX_TOP##` + +--- + +## Setup Instructions + +### Prerequisites +- Arduino IDE 1.8.13 or later +- FastLED library 3.10.3+ (Install via Sketch → Include Library → Manage Libraries) +- ESP32 board package (Install via Tools → Board Manager, search "esp32") + +### Installation Steps + +1. **Install Libraries**: + - Open Arduino IDE + - Go to **Sketch → Include Library → Manage Libraries** + - Search for "FastLED" and install the latest version + - Search for "esp32" and install the latest ESP32 board package + +2. **Open the Sketch**: + - Open `led_strip_control.ino` in Arduino IDE + +3. **Configure Board**: + - **Tools → Board**: Select `esp32:esp32:esp32wrover` + - **Tools → Upload Speed**: 921600 + - **Tools → Flash Freq**: 80 MHz + - **Tools → Partition Scheme**: Default + +4. **Connect Hardware**: + - USB cable to the ESP32 WROVER + - LED strip(s) data pin to GPIO 2 (strip 1) and/or GPIO 14 (strip 6) + - LED strip ground to ESP32 ground + - LED strip power to external 5V supply + +5. **Upload**: + - Select the COM port (**Tools → Port**) + - Click **Upload** + - Serial output will appear at 115200 baud + +--- + +## Debugging Guide + +### Method 1: Serial Monitor (USB Debug) + +1. **Open Serial Monitor**: + - Tools → Serial Monitor (or Ctrl+Shift+M) + - Set baud rate to **115200** + +2. **Send Commands**: + - Type into the input field at the bottom and press **Send** + - Example: `!!TOP:REQUEST:START_CLIMAX_TOP{0}##` + +3. **Monitor Output**: + - Watch for initialization messages during boot + - Observe state transitions and timing updates + - Check for responses and confirmations + +4. **Typical Boot Output**: + ``` + Serial2 initialized on RX=16, TX=17 + Strip 1 initialized on pin 2 + Strip 6 initialized on pin 14 + LED Strip Installation Started + ``` + +5. **Sequence Output Example**: + ``` + Received start climax command with time: 0 + !MASTER:CONFIRM:START_CLIMAX_TOP## + Starting climax sequence + Starting strip 1 charging + Starting strip 6 intense flow + Climax sequence completed + !!MASTER:CONFIRM:STOP_CLIMAX_TOP## + ``` + +### Method 2: TX/RX External Control (Hardware Debug) + +1. **Wire External Device**: + - Connect external device RX to ESP32 GPIO 17 (TX2) + - Connect external device TX to ESP32 GPIO 16 (RX2) + - Share ground + +2. **Send Commands**: + - Send via the external serial device at 115200 baud + - Monitor responses on the same TX/RX line + +3. **Responses Appear On**: + - Both Serial Monitor (USB) + - TX/RX pins (external device) + +### Troubleshooting + +#### LEDs Don't Light Up +- [ ] Check GPIO pins match your wiring (default: 2 and 14) +- [ ] Verify external 5V power connected to strips +- [ ] Confirm ground is shared between ESP32 and LED power supply +- [ ] Check that `#define ENABLE_STRIP1` and `#define ENABLE_STRIP6` are not commented out +- [ ] Verify LED strip is WS2812B (addressable RGB) not simple single-color strip + +#### No Serial Output +- [ ] Confirm baud rate is 115200 +- [ ] Check USB cable is connected and drivers installed +- [ ] Look at COM port number in Device Manager (Windows) or /dev (Mac/Linux) +- [ ] Try a different USB port + +#### Commands Not Recognized +- [ ] Ensure commands end with `##` (two hashes) +- [ ] Check command format matches exactly (case-sensitive) +- [ ] Verify "Line ending" is set to "Newline" in Serial Monitor +- [ ] Monitor output should show parsing in checkSerialCommands() + +#### Sequence Timing Off +- [ ] Edit `flashDuration` (default 30000 ms for charging) +- [ ] Edit `burstDuration` (default 30000 ms for flow) +- [ ] Recompile and upload after changes + +### Configurable Parameters + +Edit these values in `led_strip_control.ino` to customize: + +```cpp +// Line 5: LED count per strip (adjust if strips are different length) +#define NUM_LEDS_PER_STRIP 60 + +// Line 6: Global brightness (0-255) +#define BRIGHTNESS 64 + +// Line 15-16: Enable/disable strips (comment out to disable) +#define ENABLE_STRIP1 +#define ENABLE_STRIP6 + +// Line 42-43: Duration of each phase (milliseconds) +unsigned long flashDuration = 30000; // Charging phase +unsigned long burstDuration = 30000; // Flow phase + +// Line 210, 233: Update rates +if (millis() - lastUpdate > 100) // Charging update: 100ms +if (millis() - lastUpdate > 50) // Flow update: 50ms +``` + +--- + +## File Structure + +``` +top/ +├── led_strip_control.ino # Main firmware +├── led_strip_plan.md # Detailed hardware/software plan +├── README.md # This file +├── development/ +│ ├── example.ino # Original blink example +│ └── single_strip_demo.ino # Single strip test sketch +└── .git/ # Version control +``` + +--- + +## Development Hints + +### Testing Individual Strips +- Use `development/single_strip_demo.ino` to test one strip independently +- Modify GPIO and LED count, upload, observe behavior + +### Gradual Strip Connection +- Comment out unwanted `#define ENABLE_STRIPx` lines +- Code will skip initialization and control of disabled strips +- Allows testing with partial hardware + +### Adding More Strips +- Define new pin and CRGB array in the global section +- Add `#define ENABLE_STRIPn` flag +- Add FastLED initialization in `setup()` +- Include control logic in `chargingEffect()` or `intenseFlow()` + +--- + +## References + +- **FastLED Documentation**: https://fastled.io/ +- **ESP32 Arduino Core**: https://github.com/espressif/arduino-esp32 +- **WS2812B Datasheet**: Search online for timing requirements +- **Project Planning**: See `led_strip_plan.md` + +--- + +## Branches +- `main`: Production-ready code +- `develop`: Active development +- `feature/`, `bugfix/`, `hotfix/`: Use Git Flow + +## Commit Convention +```text +: +``` +Example: `feat: add LED strip control with serial commands` diff --git a/development/esp32_2_led_strips_prototype/led_strip_control.ino b/development/esp32_2_led_strips_prototype/led_strip_control.ino new file mode 100644 index 0000000..d2335c5 --- /dev/null +++ b/development/esp32_2_led_strips_prototype/led_strip_control.ino @@ -0,0 +1,460 @@ +/* + GLOW 2025 - Echoes of Tomorrow Toppiece LED Control + + This firmware controls 6 addressable LED strips (WS2812B) for the toppiece light installation. + The system uses a dual-communication architecture: + - Serial (USB): Debug messages and commands + - Serial2 (TX/RX pins): External control and responses + + CURRENT CONFIGURATION: + - Only strips 1 and 6 are enabled for testing + - Strip 1: Represents the 5-strip main array (charging effect) + - Strip 6: Independent climax element (intense flow effect) + + PING and DEBUG STATEMENTS: + - It is sending IDLE state Ping to ensure the device is actively waiting and responsive. + - It also informs what strips are initialized at startup for verification. + - These are needed for development and can be removed in production. + + ENABLING ALL 6 STRIPS FOR PRODUCTION: + 1. Uncomment all #define ENABLE_STRIPx lines (lines 20-25) + 2. Wire each strip to its assigned GPIO pin + 3. Supply external 5V power to each strip (separate from ESP32) + 4. Share ground between ESP32 and LED power supply + 5. Recompile and upload + + SYNCHRONIZED CONTROL: + The first 5 strips (1-5) use the same chargingEffect() function. + They all: + - Start charging simultaneously when sequence begins + - Use identical blue-to-white gradient fill pattern + - Complete charging at the same time + + Strip 6 is independent and uses intenseFlow() for the climax phase. + + ARCHITECTURE: + - chargingEffect(): Fills strips 1-5 progressively (0% to 100% over 30s) + - intenseFlow(): Animated stream on strip 6 with random sparks (30s) + - checkSerialCommands(): Parses commands from both Serial and Serial2 + - Command protocol: !!TOP:REQUEST:START_CLIMAX_TOP{TIME}## / !!TOP:REQUEST:STOP_CLIMAX_TOP## + - Responses: !MASTER:CONFIRM:START_CLIMAX_TOP## / !!MASTER:CONFIRM:STOP_CLIMAX_TOP## +*/ + +#include + +// LED Strip Configuration +#define NUM_LEDS_PER_STRIP 60 // Adjust based on your strip length +#define BRIGHTNESS 64 + +// Pin assignments for each strip +// To enable all 6 strips, uncomment the respective #define ENABLE_STRIPx lines below +#define STRIP1_PIN 2 // Strip 1 (part of main 5-array) +#define STRIP2_PIN 4 // Strip 2 (part of main 5-array) +#define STRIP3_PIN 5 // Strip 3 (part of main 5-array) +#define STRIP4_PIN 12 // Strip 4 (part of main 5-array) +#define STRIP5_PIN 13 // Strip 5 (part of main 5-array) +#define STRIP6_PIN 14 // Strip 6 (independent climax element) + +// Enable/disable strips (comment out to disable) +// For PRODUCTION with all 6 strips, uncomment all lines below: + +// TESTING CONFIGURATION (currently active): +// Only strips 1 and 6 enabled. Strips 1-5 will be synchronized when all are enabled. +#define ENABLE_STRIP1 // Main 5-strip array (charging effect) +#define ENABLE_STRIP2 +//#define ENABLE_STRIP3 +//#define ENABLE_STRIP4 +//#define ENABLE_STRIP5 +#define ENABLE_STRIP6 // Independent climax element (intense flow) + +// Serial command +// External TX/RX communication allows the toppiece to receive commands from a host controller +// and send back status confirmations on the same physical TX/RX header. +#define UART_RX_PIN 16 // RX pin for external commands (Serial2) +#define UART_TX_PIN 17 // TX pin for external responses (Serial2) + +// LED array declarations: only enabled strips are instantiated +// This allows gradual hardware connection without code changes +#ifdef ENABLE_STRIP1 +CRGB strip1[NUM_LEDS_PER_STRIP]; +#endif +#ifdef ENABLE_STRIP2 +CRGB strip2[NUM_LEDS_PER_STRIP]; +#endif +#ifdef ENABLE_STRIP3 +CRGB strip3[NUM_LEDS_PER_STRIP]; +#endif +#ifdef ENABLE_STRIP4 +CRGB strip4[NUM_LEDS_PER_STRIP]; +#endif +#ifdef ENABLE_STRIP5 +CRGB strip5[NUM_LEDS_PER_STRIP]; +#endif +#ifdef ENABLE_STRIP6 +CRGB strip6[NUM_LEDS_PER_STRIP]; +#endif + +// Lookup table of strips participating in the charging phase (strips 1-5). +// Each pointer appears only if the corresponding strip is enabled, allowing +// chargingEffect() to iterate across all active strips with identical logic. +static CRGB* const chargingStrips[] = { +#ifdef ENABLE_STRIP1 + strip1, +#endif +#ifdef ENABLE_STRIP2 + strip2, +#endif +#ifdef ENABLE_STRIP3 + strip3, +#endif +#ifdef ENABLE_STRIP4 + strip4, +#endif +#ifdef ENABLE_STRIP5 + strip5, +#endif +}; + +static const size_t chargingStripCount = sizeof(chargingStrips) / sizeof(chargingStrips[0]); + +// State machine management +enum DeviceState { IDLE, RUNNING }; +DeviceState currentState = IDLE; + +// Timing variables for the climax sequence +// Sequence phases: +// 1. Charging phase (flashDuration): Strips 1-5 progressively fill with blue→white gradient +// 2. Flow phase (burstDuration): Strip 6 activates with intense animated stream +unsigned long startTime; +unsigned long flashDuration = 30000; // 30 seconds charging for strips 1-5 +unsigned long burstDuration = 30000; // 30 seconds for intense flow on strip 6 +bool strips1to5Filled = false; +bool strip6Active = false; +bool strip1Flashing = false; +bool strip6Bursting = false; + +// Serial command buffer +String serialBuffer = ""; +String serial2Buffer = ""; // buffer for Serial2 (TX/RX pins) + +void setup() { + // Initialize serial for debugging + Serial.begin(115200); + // Initialize Serial2 for external TX/RX communication + // This allows a host controller (master) to send commands and receive responses + // on the physical TX/RX header pins, independent of USB Serial. + Serial2.begin(115200, SERIAL_8N1, UART_RX_PIN, UART_TX_PIN); + Serial.println("Serial2 initialized on RX=" + String(UART_RX_PIN) + ", TX=" + String(UART_TX_PIN)); + + // Initialize FastLED for each enabled strip + // When a strip's #define ENABLE_STRIPx is active, FastLED manages that GPIO and LED array. + // When disabled (commented), the code is skipped entirely, avoiding errors on unconnected pins. +#ifdef ENABLE_STRIP1 + FastLED.addLeds(strip1, NUM_LEDS_PER_STRIP); + Serial.println("Strip 1 initialized on pin " + String(STRIP1_PIN)); +#endif +#ifdef ENABLE_STRIP2 + FastLED.addLeds(strip2, NUM_LEDS_PER_STRIP); + Serial.println("Strip 2 initialized on pin " + String(STRIP2_PIN)); +#endif +#ifdef ENABLE_STRIP3 + FastLED.addLeds(strip3, NUM_LEDS_PER_STRIP); + Serial.println("Strip 3 initialized on pin " + String(STRIP3_PIN)); +#endif +#ifdef ENABLE_STRIP4 + FastLED.addLeds(strip4, NUM_LEDS_PER_STRIP); + Serial.println("Strip 4 initialized on pin " + String(STRIP4_PIN)); +#endif +#ifdef ENABLE_STRIP5 + FastLED.addLeds(strip5, NUM_LEDS_PER_STRIP); + Serial.println("Strip 5 initialized on pin " + String(STRIP5_PIN)); +#endif +#ifdef ENABLE_STRIP6 + FastLED.addLeds(strip6, NUM_LEDS_PER_STRIP); + Serial.println("Strip 6 initialized on pin " + String(STRIP6_PIN)); +#endif + + FastLED.setBrightness(BRIGHTNESS); + + // Start timing + startTime = millis(); + + Serial.println("LED Strip Installation Started"); +} + +// Helper: turn off all enabled strips +static void turnOffAllStrips() { +#ifdef ENABLE_STRIP1 + fill_solid(strip1, NUM_LEDS_PER_STRIP, CRGB::Black); +#endif +#ifdef ENABLE_STRIP2 + fill_solid(strip2, NUM_LEDS_PER_STRIP, CRGB::Black); +#endif +#ifdef ENABLE_STRIP3 + fill_solid(strip3, NUM_LEDS_PER_STRIP, CRGB::Black); +#endif +#ifdef ENABLE_STRIP4 + fill_solid(strip4, NUM_LEDS_PER_STRIP, CRGB::Black); +#endif +#ifdef ENABLE_STRIP5 + fill_solid(strip5, NUM_LEDS_PER_STRIP, CRGB::Black); +#endif +#ifdef ENABLE_STRIP6 + fill_solid(strip6, NUM_LEDS_PER_STRIP, CRGB::Black); +#endif +} + +void loop() { + // Check for serial commands from both USB (Serial) and external TX/RX (Serial2) + checkSerialCommands(); + + unsigned long currentTime = millis(); + + // Main state machine: handles RUNNING and IDLE states + if (currentState == RUNNING) { + unsigned long elapsedTime = currentTime - startTime; + + // PHASE 1: CHARGING (0-30 seconds) + // Strips 1-5: Progressive fill from blue to white + if (!strip1Flashing && elapsedTime < flashDuration) { + strip1Flashing = true; + Serial.println("Starting strip 1 charging"); + } + + if (strip1Flashing && elapsedTime < flashDuration) { + chargingEffect(); + } + + // PHASE 2: INTENSE FLOW (30-60 seconds) + // Transition point: switch from charging to climax + if (elapsedTime >= flashDuration && !strip6Bursting) { + strip6Bursting = true; + strip1Flashing = false; + Serial.println("Starting strip 6 intense flow"); + } + + // Strip 6: Animated stream with energy sparks (independent climax element) + if (strip6Bursting && elapsedTime < flashDuration + burstDuration) { + intenseFlow(); + } + + // End sequence: transition to IDLE, turn off strips, and signal completion + if (elapsedTime >= flashDuration + burstDuration) { + currentState = IDLE; + strip1Flashing = false; + strip6Bursting = false; + // Turn off all strips +#ifdef ENABLE_STRIP1 + fill_solid(strip1, NUM_LEDS_PER_STRIP, CRGB::Black); +#endif +#ifdef ENABLE_STRIP2 + fill_solid(strip2, NUM_LEDS_PER_STRIP, CRGB::Black); +#endif +#ifdef ENABLE_STRIP3 + fill_solid(strip3, NUM_LEDS_PER_STRIP, CRGB::Black); +#endif +#ifdef ENABLE_STRIP4 + fill_solid(strip4, NUM_LEDS_PER_STRIP, CRGB::Black); +#endif +#ifdef ENABLE_STRIP5 + fill_solid(strip5, NUM_LEDS_PER_STRIP, CRGB::Black); +#endif +#ifdef ENABLE_STRIP6 + fill_solid(strip6, NUM_LEDS_PER_STRIP, CRGB::Black); +#endif + FastLED.show(); + Serial.println("Climax sequence completed. Returning to IDLE."); + // Send completion confirmation to both Serial and Serial2 + Serial.println("!!MASTER:CONFIRM:STOP_CLIMAX_TOP##"); + Serial2.println("!!MASTER:CONFIRM:STOP_CLIMAX_TOP##"); + } + } else { + // IDLE state: Keep strips off and wait for a command. + turnOffAllStrips(); + // Periodic status ping to show we're alive and ready + static unsigned long lastPing = 0; + if (currentTime - lastPing > 10000) { // Ping every 10 seconds + lastPing = currentTime; + Serial.println("!!TOP:STATUS:IDLE##"); + Serial2.println("!!TOP:STATUS:IDLE##"); + } + } + + FastLED.show(); + delay(50); +} + +// Function to check for serial commands +void checkSerialCommands() { + // Local lambda to process complete commands within a buffer + auto processBuffer = [&](String &buf, const char* sourceTag) { + int sepIdx; + while ((sepIdx = buf.indexOf("##")) != -1) { + String cmd = buf.substring(0, sepIdx + 2); + buf.remove(0, sepIdx + 2); + // Trim any CR/LF that might immediately follow + while (buf.length() > 0 && (buf[0] == '\\r' || buf[0] == '\\n' || buf[0] == ' ')) { + buf.remove(0, 1); + } + + cmd.trim(); + + if (cmd.startsWith("!!TOP:REQUEST:START_CLIMAX_TOP")) { + // Extract time if present + int startIdx = cmd.indexOf('{'); + int endIdx = cmd.indexOf('}'); + if (startIdx != -1 && endIdx != -1 && endIdx > startIdx) { + String timeStr = cmd.substring(startIdx + 1, endIdx); + Serial.println(String("(") + sourceTag + ") Start with time: " + timeStr); + } + + // Confirm and (re)start + Serial.println("!MASTER:CONFIRM:START_CLIMAX_TOP##"); + Serial2.println("!MASTER:CONFIRM:START_CLIMAX_TOP##"); + + currentState = RUNNING; + startTime = millis(); + strip1Flashing = false; // Reset phase flags + strip6Bursting = false; + Serial.println(String("(") + sourceTag + ") Starting (or restarting) climax sequence"); + } else if (cmd.startsWith("!!TOP:REQUEST:STOP_CLIMAX_TOP")) { + // Stop and go idle + if (currentState == RUNNING) { + currentState = IDLE; + strip1Flashing = false; + strip6Bursting = false; + turnOffAllStrips(); + FastLED.show(); + Serial.println(String("(") + sourceTag + ") Stopped sequence. Returning to IDLE."); + Serial.println("!!MASTER:CONFIRM:STOP_CLIMAX_TOP##"); + Serial2.println("!!MASTER:CONFIRM:STOP_CLIMAX_TOP##"); + } + } else { + // Unknown command: ignore silently to avoid clogging + } + } + }; + + // Read from USB Serial + while (Serial.available()) { + char c = Serial.read(); + serialBuffer += c; + } + processBuffer(serialBuffer, "USB"); + + // Read from Serial2 (external TX/RX) + while (Serial2.available()) { + char c2 = Serial2.read(); + serial2Buffer += c2; + } + processBuffer(serial2Buffer, "RX2"); +} + +// Function for charging effect on strips 1-5 +// This function is called during the first 30 seconds of the climax sequence. +// When all 6 strips are enabled, this effect runs simultaneously on strips 1-5, +// creating a synchronized "power accumulation" visual effect. +// +// Effect: +// - Progressively fills the strip from position 0 to the end +// - Color gradient: Blue (start, 0%) → White (end, 100%) +// - All enabled strips receive identical fill progress +// - Update rate: 100ms (smooth but not too fast) +// +// If you want to modify this effect for all 5 strips at once, +// simply edit the CRGB color mapping here. +void chargingEffect() { + static unsigned long lastUpdate = 0; + + // Faster cadence for a snappier look + if (millis() - lastUpdate <= 60) { + return; // ~16 FPS + } + lastUpdate = millis(); + + if (chargingStripCount == 0) { + return; // No strips are enabled for the charging phase + } + + const unsigned long elapsedTime = millis() - startTime; + + // Fast repeating ping-pong fill across the strip + const uint16_t cycleMs = 2000; // 2 seconds there-and-back + uint16_t phase = elapsedTime % cycleMs; + bool descending = phase >= (cycleMs / 2); + int half = cycleMs / 2; + int fillProgress; + if (!descending) { + fillProgress = map(phase, 0, half, 0, NUM_LEDS_PER_STRIP); + } else { + int phase2 = phase - half; + fillProgress = map(phase2, 0, half, NUM_LEDS_PER_STRIP, 0); + } + fillProgress = constrain(fillProgress, 0, NUM_LEDS_PER_STRIP); + + // Apply identical gradient and clearing logic to every charging strip + for (size_t idx = 0; idx < chargingStripCount; ++idx) { + CRGB* leds = chargingStrips[idx]; + + // Clear first for crisp motion + fill_solid(leds, NUM_LEDS_PER_STRIP, CRGB::Black); + + // Draw the current fill with a blue->white gradient + for (int i = 0; i < fillProgress; ++i) { + uint8_t blue = map(i, 0, NUM_LEDS_PER_STRIP - 1, 255, 0); + uint8_t white = map(i, 0, NUM_LEDS_PER_STRIP - 1, 0, 255); + leds[i] = CRGB(white, white, blue); + } + } +} + +// Function for intense flow on strip 6 (independent climax element) +// This function runs during the second 30 seconds of the climax sequence, +// after all strips 1-5 have completed charging. +// Strip 6 uses a different effect: a flowing stream with random energy sparks. +// +// Effect: +// - 15-LED stream continuously flows around the strip +// - Color: Bright white (head) → Dim blue (tail) with smooth gradient +// - Random white sparks (30% chance per update) for intensity +// - Update rate: 50ms (fast, creates sense of urgency) +// +// This strip is independent and does NOT use the chargingEffect(). +// It represents the "climax" - the peak energy release after the 5-strip buildup. (independent climax element) +void intenseFlow() { + static unsigned long lastUpdate = 0; + static int flowPosition = 0; + static uint8_t hue = 0; + + if (millis() - lastUpdate > 30) { // Very fast, intense look + lastUpdate = millis(); + +#ifdef ENABLE_STRIP6 + // Base yellow glow across the entire strip (never off while active) + const CRGB baseYellow = CRGB(90, 90, 0); + fill_solid(strip6, NUM_LEDS_PER_STRIP, baseYellow); + + // Create a multi-color flowing stream overlay + int streamLength = 15; // Length of the flowing stream + for (int i = 0; i < streamLength; i++) { + int ledIndex = (flowPosition - i + NUM_LEDS_PER_STRIP) % NUM_LEDS_PER_STRIP; + // Gradient from bright colorful head to dimmer tail + uint8_t v = map(i, 0, streamLength - 1, 255, 80); + uint8_t localHue = hue + i * 6; // color variation along the stream + CRGB c; c.setHSV(localHue, 255, v); + // Add on top of base yellow (saturating add) + strip6[ledIndex] += c; + } + + // Add frequent colorful sparks + if (random(10) < 4) { // 40% chance per frame + int sparkIndex = random(NUM_LEDS_PER_STRIP); + CRGB spark; spark.setHSV(hue + random8(), 200, 255); + strip6[sparkIndex] += spark; // overlay spark + } + + flowPosition = (flowPosition + 1) % NUM_LEDS_PER_STRIP; + hue += 3; // slowly shift hue for diversity +#endif + } +} \ No newline at end of file diff --git a/development/esp32_2_led_strips_prototype/led_strip_plan.md b/development/esp32_2_led_strips_prototype/led_strip_plan.md new file mode 100644 index 0000000..1b6cf64 --- /dev/null +++ b/development/esp32_2_led_strips_prototype/led_strip_plan.md @@ -0,0 +1,108 @@ +# ESP LED Strip Installation Plan + +## Overview +This plan outlines the setup for controlling 6 LED strips using an ESP32 board: +- Strips 1-5: Work together as the main installation +- Strip 6: Activates after strips 1-5 have been running for a set time and are "filled" (fully lit) + +## Hardware Setup + +### Recommended Approach: Separate Pins for Independent Control +Using separate GPIO pins for each strip allows for maximum flexibility and independent control. This is better than connecting strips in series or parallel on the same pin, as it enables: +- Individual strip control +- Easier synchronization through code +- Ability to handle different strip lengths +- Better fault isolation + +### Pin Assignments +- Strip 1: GPIO 2 +- Strip 2: GPIO 4 +- Strip 3: GPIO 5 +- Strip 4: GPIO 12 +- Strip 5: GPIO 13 +- Strip 6: GPIO 14 + +### Why Not Connect All to Same Pin? +- **Series Connection**: Would treat all strips as one continuous strip, losing individual control +- **Parallel Connection**: Not recommended for addressable LEDs as data signals would interfere +- **Separate Pins**: Allows each strip to be controlled independently while still coordinating them via software + +### Power Considerations +- Each LED strip requires significant power (typically 60mA per meter at full white) +- Use a separate power supply for the strips (5V or 12V depending on strip type) +- Connect ESP32 ground to strip power supply ground +- Consider power injection for longer strips to prevent voltage drop + +### Strip Specifications +- Assume WS2812B or similar addressable RGB LED strips +- Each strip: 1 meter, 60 LEDs/meter (adjust in code as needed) +- Data connection: 3-pin JST or direct wire to ESP GPIO + +## Software Implementation + +### Library +Use FastLED library for ESP32 Arduino environment. + +### Conditional Strip Control +The code includes conditional compilation flags to enable/disable individual strips: +```cpp +#define ENABLE_STRIP1 // Comment out to disable +#define ENABLE_STRIP6 +``` +This allows you to gradually connect strips without modifying the main logic. + +### Serial Commands +The ESP accepts these commands over TX/RX (serial): + +**Start Command:** +``` +!!TOP:REQUEST:START_CLIMAX_TOP{[TIME]}## +``` + +**Stop Command:** +``` +!!TOP:REQUEST:STOP_CLIMAX_TOP## +``` + +### Confirmations (Emitted by ESP) +- On receiving START command: + ``` + !MASTER:CONFIRM:START_CLIMAX_TOP## + ``` +- On sequence completion or manual stop: + ``` + !!MASTER:CONFIRM:STOP_CLIMAX_TOP## + ``` + +### Command Responses +- **Start**: Begins the climax sequence +- **Stop**: Immediately stops the sequence and turns off strips +- **Completion**: When sequence ends naturally, ESP sends: + ``` + !!TOP:REQUEST:STOP_CLIMAX_TOP## + ``` + +This allows for debugging and external control of the light installation. + +### Control Logic +1. **Idle State**: Strips remain off until command received +2. **Strip 1 (30 seconds)**: Charging effect - LEDs fill progressively from blue to white +3. **Strip 6 (30 seconds)**: Intense flow - flowing stream of LEDs with random sparks +4. **Completion**: All strips turn off, ready for next command + +### Timing +- Charging duration: 30 seconds +- Flow duration: 30 seconds +- Charging update: 100ms +- Flow update: 50ms (faster for intensity) + +## Files +- `led_strip_control.ino`: Main Arduino sketch +- `led_strip_plan.md`: This plan document + +## Next Steps +1. Gather hardware components +2. Install FastLED library in Arduino IDE +3. Test individual strips +4. Implement and test code +5. Adjust timing and patterns as needed \ No newline at end of file diff --git a/development/initial_debugging/README.md b/development/initial_debugging/README.md new file mode 100644 index 0000000..5fbb143 --- /dev/null +++ b/development/initial_debugging/README.md @@ -0,0 +1 @@ +This folder contains code for testing ESP and a single LED strip demo for testing individual strips. \ No newline at end of file diff --git a/development/initial_debugging/example.ino b/development/initial_debugging/example.ino new file mode 100644 index 0000000..a371414 --- /dev/null +++ b/development/initial_debugging/example.ino @@ -0,0 +1,53 @@ +/* + Blink All LEDs + + Demonstrates usage of multiple LEDs on ESP board, including RGB LED. + + This code controls multiple GPIO LEDs and the onboard RGB LED to blink in a rhythm. +*/ + +// Define LED pins (adjust based on your ESP board) +const int ledPins[] = {2, 4, 5, 12, 13, 14, 15}; // GPIO pins with LEDs +const int numLeds = sizeof(ledPins) / sizeof(ledPins[0]); + +#define RGB_BRIGHTNESS 64 // Change white brightness (max 255) + +// the setup function runs once when you press reset or power the board +void setup() { + // Initialize GPIO LED pins as outputs + for (int i = 0; i < numLeds; i++) { + pinMode(ledPins[i], OUTPUT); + digitalWrite(ledPins[i], LOW); // Start with LEDs off + } + // No need to initialize the RGB LED +} + +// the loop function runs over and over again forever +void loop() { + // Blink GPIO LEDs in sequence (chase effect) + for (int i = 0; i < numLeds; i++) { + digitalWrite(ledPins[i], HIGH); + delay(200); + digitalWrite(ledPins[i], LOW); + } + + // Control RGB LED if available +#ifdef RGB_BUILTIN + digitalWrite(RGB_BUILTIN, HIGH); // Turn the RGB LED white + delay(500); + digitalWrite(RGB_BUILTIN, LOW); // Turn the RGB LED off + delay(500); + + rgbLedWrite(RGB_BUILTIN, RGB_BRIGHTNESS, 0, 0); // Red + delay(500); + rgbLedWrite(RGB_BUILTIN, 0, RGB_BRIGHTNESS, 0); // Green + delay(500); + rgbLedWrite(RGB_BUILTIN, 0, 0, RGB_BRIGHTNESS); // Blue + delay(500); + rgbLedWrite(RGB_BUILTIN, 0, 0, 0); // Off / black + delay(500); +#endif + + // All LEDs off for a moment + delay(1000); +} diff --git a/development/initial_debugging/single_strip_demo.ino b/development/initial_debugging/single_strip_demo.ino new file mode 100644 index 0000000..37d1753 --- /dev/null +++ b/development/initial_debugging/single_strip_demo.ino @@ -0,0 +1,74 @@ +/* + Single LED Strip Demo + + This demo controls one WS2812B LED strip using 3 wires: + - 5V (power) + - GND (ground) + - Data (GPIO pin) + + You CANNOT control addressable LEDs with just power and ground. + The data line is required to send signals that determine which LEDs light up + and in what colors. + + For non-addressable strips, you could control them with just power/ground, + but those are typically single-color and controlled by switching power on/off. +*/ + +#include + +#define LED_PIN 2 // GPIO pin for data line +#define NUM_LEDS 10 // Number of LEDs in your strip (adjust as needed) +#define BRIGHTNESS 64 // Brightness level (0-255) + +CRGB leds[NUM_LEDS]; + +void setup() { + Serial.begin(115200); + + // Initialize FastLED + FastLED.addLeds(leds, NUM_LEDS); + FastLED.setBrightness(BRIGHTNESS); + + Serial.println("Single LED Strip Demo Started"); + Serial.println("Make sure you have 3 connections:"); + Serial.println("- 5V to strip power"); + Serial.println("- GND to strip ground"); + Serial.println("- GPIO 2 to strip data input"); +} + +void loop() { + // Demo 1: Fill strip with red + fill_solid(leds, NUM_LEDS, CRGB::Red); + FastLED.show(); + delay(2000); + + // Demo 2: Fill strip with green + fill_solid(leds, NUM_LEDS, CRGB::Green); + FastLED.show(); + delay(2000); + + // Demo 3: Fill strip with blue + fill_solid(leds, NUM_LEDS, CRGB::Blue); + FastLED.show(); + delay(2000); + + // Demo 4: Rainbow effect + for (int i = 0; i < NUM_LEDS; i++) { + leds[i] = CHSV(i * 255 / NUM_LEDS, 255, 255); + } + FastLED.show(); + delay(2000); + + // Demo 5: Chase effect + for (int i = 0; i < NUM_LEDS; i++) { + leds[i] = CRGB::White; + FastLED.show(); + delay(100); + leds[i] = CRGB::Black; + } + + // Demo 6: All off + fill_solid(leds, NUM_LEDS, CRGB::Black); + FastLED.show(); + delay(1000); +} \ No newline at end of file