OverviewFilesCommitsIssuesDocsReleasesPeers

cthuloid commits / 1619a37

Add project documentation pass
- Rewrite top-level README.md with full repo map, hardware stack overview, software stack overview, quick-start, repo-deployment notes, and doc index. - Add PROTOCOL.md documenting the studiolight wire protocol (pwm, pwm2, freq, gamma, hostname, reboot, verbose, plevel, s, unrecognized), channel semantics, BGR-per-light ordering, OTA workflow, long-run stability features, and diagnostic flow. - Add HARDWARE.md documenting pin assignments per light, ESP32 strapping-pin caveats (GPIO 2/12), MOSFET gate-drive bandwidth ceiling (~1.2 kHz with direct ESP32-GPIO drive), camera-banding chart for the Nikon D7000, and a power-on checklist. - Add esp32/README.md cataloguing legacy + experimental sketches (32wifi12frak variants, esp32-arnet-fastled, esp32-rotary-encoder, sc_test, sketch_colortest). - Add esp8266/README.md documenting the historical single-light controllers. - Add Pcb/README.md indexing the Fritzing schematic + PCB layout files, noting the future gate-driver-IC upgrade path. - Add Arduino/README.md indexing the Atmel sketches by category (light controllers, motion/servo, DMX/RDM, misc) and noting the libraries/ subdir. Existing lowercase readme files (Arduino/readme, LightArm/readme, cuemgr/readme, ghettopticon/README) are left in place — the new uppercase README.md files at sibling locations supplement rather than replace them. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1619a3747b40f75e9c118b3bffff40b5d5d2be24
by plankatron <fleeky@gmail.com> · 2026-05-23T00:25:58.000Z

Changed files (7) +986 -16

Arduino/README.md +67 -0
……@@ -0,0 +1,67 @@
1# Arduino/
2
3Atmel-based Arduino firmware (Mega, Uno, Nano). Most predate the ESP32
4sketches in `../esp32/` and `../esp324xlight/`. Many sketches require
5built-in Arduino libraries for PWM and networking, plus the bundled
6modules in `libraries/` here.
7
8## Sketch index
9
10### Light controllers
11
12| Sketch | Purpose | Status |
13|---|---|---|
14| [`LightArm/`](LightArm/) | Mega-based spotlight robot firmware with Dynamixel servos + 100 W LED. Listens on TCP/1337. See [`../LightArm/readme`](../LightArm/readme) for the full subproject docs. | Active |
15| [`LEDController/`](LEDController/) | Ethernet-enabled high-resolution LED controller — pre-ESP32 path. | Reference |
16| [`DMX_LED_Controller/`](DMX_LED_Controller/) | DMX-driven LED controller | Reference |
17| [`DMX_LED_Controller2/`](DMX_LED_Controller2/) | Revision 2 of DMX LED controller | Reference |
18| [`NanoLEDController/`](NanoLEDController/) | Arduino Nano variant of LED controller | Reference |
19| [`RGB_LEDController/`](RGB_LEDController/) | RGB-specific LED controller | Reference |
20
21### Motion / servo
22
23| Sketch | Purpose |
24|---|---|
25| [`Blenderbot/`](Blenderbot/) | Blender-driven robot (likely earlier ghettopticon-style) |
26| [`PrinbooLimbs/`](PrinbooLimbs/) | "Prinboo" robot limb control |
27| [`ServoShieldKeyboard/`](ServoShieldKeyboard/) | Adafruit Servo Shield + keyboard input demo |
28| [`ServoShieldSerial/`](ServoShieldSerial/) | Adafruit Servo Shield + serial control |
29| [`servotest/`](servotest/) | Servo test sketch |
30| [`TestDynamixel/`](TestDynamixel/) | Dynamixel servo test |
31| [`test_dynamixel_torque/`](test_dynamixel_torque/) | Dynamixel torque test |
32| [`TestSteppers/`](TestSteppers/) | Stepper motor test |
33| [`sonartest/`](sonartest/) | Sonar sensor test |
34
35### DMX / RDM
36
37| Sketch | Purpose |
38|---|---|
39| [`RDM_Slave/`](RDM_Slave/) | RDM slave (Remote Device Management for DMX) |
40| [`RDM_Slave_Eeprom/`](RDM_Slave_Eeprom/) | RDM slave with EEPROM storage |
41
42### Misc
43
44| File | Purpose |
45|---|---|
46| [`CodeGenCommands.h`](CodeGenCommands.h) | Code-generated command list header |
47
48## libraries/
49
50The `libraries/` subdir contains library copies pinned to known-working
51versions for these sketches:
52
53- `Adafruit_PWM_Servo_Driver_Library/` — Adafruit PCA9685 PWM driver
54- `Bioloid-fixed/`, `Bioloid-fixed-UART0/` — Dynamixel/Bioloid driver, patched
55- `Conceptinetics/` — DMX library
56- More
57
58When building these sketches, point the Arduino IDE's sketchbook
59location at this directory (or symlink) so it finds these specific
60versions. The main `~/Arduino/libraries/` may have newer versions that
61break these older sketches.
62
63## See also
64
65- [`../LightArm/readme`](../LightArm/readme) — full LightArm subproject docs
66- [`../esp32/README.md`](../esp32/README.md) — newer ESP32-based sketches
67- [`../esp324xlight/README.md`](../esp324xlight/README.md) — current production firmware
HARDWARE.md +249 -0
……@@ -0,0 +1,249 @@
1# cthuloid hardware notes
2
3Pin assignments, electrical conventions, and gotchas across the hardware
4in this repo. Two distinct hardware paths are documented here:
5
61. **Studiolight** — 4-light × RGB rig driven by ESP32 + MOSFETs
72. **LightArm** — Mega-based spotlight robot with Dynamixel servos and a high-current LED
8
9(Plus a brief note on the `ghettopticon` cardboard robot and the historical
10ESP8266 rigs.)
11
12---
13
14## Studiolight (current production rig)
15
16### Controller
17
18| Item | Value |
19|---|---|
20| Board | ESP32 Wrover Module |
21| MCU | ESP32-D0WDQ6, dual-core 240 MHz, with hardware LEDC peripheral |
22| PSRAM | Not used (the daemon's PSRAM probe always fails on the boards we have, harmless) |
23| USB-UART | CP2102 (Silicon Labs) — `/dev/ttyUSBn` on Linux |
24| Power input to MCU | USB 5 V *or* board's 5 V rail (don't have both at once — back-feeds the regulator) |
25
26### Firmware build target
27
28The board is selected as **"ESP32 Wrover Module"** in the Arduino IDE.
29Full fqbn:
30
31```
32esp32:esp32:esp32wrover:FlashFreq=80,UploadSpeed=921600,PartitionScheme=default,DebugLevel=none
33```
34
35The Wrover board profile pins PSRAM on (`-DBOARD_HAS_PSRAM`), but the
36PSRAM chip on our boards isn't present/accessible — at boot you'll see:
37
38```
39E (312) quad_psram: PSRAM ID read error: 0xffffffff, PSRAM chip not found or not supported
40```
41
42This is harmless; the rest of boot proceeds. The Wrover profile is still
43the correct pick because of its flash size / partition layout.
44
45### PWM pin map (12 channels = 4 lights × RGB)
46
47The 12 GPIO pins are grouped into RGB triples, sent over the wire in
48**BGR** order:
49
50| Light | GPIO position 0 (Blue) | GPIO position 1 (Green) | GPIO position 2 (Red) |
51|---|---|---|---|
52| 1 | **32** | **33** | **25** |
53| 2 | **26** | **27** | **14** |
54| 3 | **12** | **13** | **2** |
55| 4 | **23** | **22** | **21** |
56
57Defined in firmware as:
58
59```c
60const int PWMPins[] = {32, 33, 25, 26, 27, 14, 12, 13, 2, 23, 22, 21};
61```
62
63### ESP32 strapping-pin caveats
64
65Three of the GPIOs used are strapping pins that read special values at boot:
66
67- **GPIO 2** — connected to onboard LED on some dev boards. Used for light 3 red here. Must be low (or floating) at boot.
68- **GPIO 12** — strapping pin for flash voltage. **Must be low at boot** or the chip refuses to start. If your light 3 green channel has any external pull-up, the board will brick at boot. Make sure the MOSFET gate has a strong pull-down resistor that wins at power-on.
69- **GPIO 15** — not in our pin list, no issue.
70
71If a board mysteriously fails to boot after wiring it into the rig, GPIO
7212 is the first suspect. Disconnect the rig from the ESP32 (just the
73GPIO 12 line) and try again to confirm.
74
75### PWM electrical chain
76
77```
78ESP32 GPIO (3.3 V logic)
79 │
80 └─→ MOSFET gate (per channel)
81 │
82 └─→ LED ←── 600 W power supply
83```
84
85#### Power supply
86
87- 600 W bench/server PSU
88- Rail voltage chosen to match each LED string:
89 - Blue/green LED strings → ~32 V rail
90 - Red LED strings → ~26 V rail (lower forward voltage)
91- Common ground tied to ESP32 ground
92
93#### MOSFETs
94
95Currently using one MOSFET per channel (12 total), driven directly from
96the ESP32 GPIO. **This is the known weakest link in the rig** — see the
97"Driver bandwidth" section below.
98
99### Known LED-side conventions
100
101- **LEDs are wired active-low** (common-anode through the MOSFET). GPIO HIGH = LED off; GPIO LOW = LED on.
102- The firmware compensates with `#define INVERT_HIGH_AND_LOW`, which inverts the PWM duty before writing to LEDC. The user-facing protocol then has the intuitive convention: input `0` = off, input `65534` = full on.
103- **Red diodes are voltage-limited.** With the 600 W supply rail set for blue/green, the red diodes can only safely be driven to ~31% duty before exceeding their voltage rating. Firmware enforces this via `#define RED_MAX_PWM 45000`: any value above 45000 in a red position-2 channel is rejected at parse. The Blender controller respects a tighter `LIGHT_PWM_RED_MAX = 30000` as the normal-operation max; 45000 is the hardware safety redline.
104
105### Driver bandwidth — the dominant electrical caveat
106
107ESP32 GPIO sources only ~20–40 mA. High-power MOSFET gates have several
108nF of capacitance, so direct-drive transitions take 1–10 µs.
109
110Empirical bandwidth ceiling on the current rig (ESP32 GPIO direct-drive,
111no gate-driver IC):
112
113| Freq | Brightness | Notes |
114|---|---|---|
115| ≤ 1000 Hz | Full | Sweet spot. Use this. |
116| 1200 Hz | Just-perceptible dimming | First hint of slew limit |
117| 2000 Hz | Clearly dimmer | Driver only reaches ~85–90% target |
118| 5000 Hz | Noticeable drop | Driver topology bandwidth limit |
119| 20000 Hz | Red nearly off, all dim | Way past bandwidth — output is averaged ripple |
120
121Red drops out first because the lower-voltage red string carries higher
122current, amplifying gate-transition `I²R` losses and `di/dt` interactions
123with parasitic wire inductance.
124
125**This is a hardware ceiling, not a firmware bug.** No software fix
126recovers full brightness at 20 kHz — the energy literally isn't getting
127to the LED through an underdriven MOSFET gate.
128
129### Recommended upgrade path (if you need >1 kHz)
130
131Add a dedicated MOSFET gate driver IC between each ESP32 GPIO and the
132MOSFET:
133
134| Part | Drive | Cost | Notes |
135|---|---|---|---|
136| **TC4427** | 1.5 A push-pull | ~$1 | Two channels per chip; very common |
137| **UCC27517** | 4 A | ~$1.50 | Single channel; faster |
138| **MIC4422** | 9 A | ~$2 | Overkill for this rig but options exist |
139
140All of these level-shift 3.3 V logic to 12 V (or whatever rail you
141supply) gate drive and switch in tens of nanoseconds. Add a 10–100 Ω
142gate resistor to damp ringing.
143
144With gate drivers in the loop, the bandwidth ceiling moves from ~1.2 kHz
145into the tens of kHz, unlocking 20 kHz / 11-bit camera-safe operation.
146
147### Camera-banding considerations
148
149Used at 1 kHz, the PWM rate is below most camera shutter speeds. Banding
150is visible at ~1/2000 shutter and faster. For a Nikon D7000:
151
152| Shutter | Risk at 1 kHz | Risk at 5 kHz | Risk at 20 kHz |
153|---|---|---|---|
154| 1/60 | None | None | None |
155| 1/250 | None | None | None |
156| 1/1000 | Slight | None | None |
157| 1/2000 | Yes | Slight | None |
158| 1/4000 | Yes | Yes | Slight |
159
160If you're shooting video or fast stills, you want gate drivers in the
161chain so you can push to 20 kHz cleanly. If you're shooting normal-shutter
162stills only, 1 kHz / 16-bit (current state) is fine.
163
164---
165
166## LightArm (Mega + Dynamixel spotlight robot)
167
168Documented in [`LightArm/readme`](LightArm/readme). Summary:
169
170### Controller
171
172| Item | Value |
173|---|---|
174| Board | Arduino Mega |
175| Network | Ethernet shield |
176| Default IPv4 | 10.0.2.255 (set in firmware EEPROM) |
177| TCP port | 1337 |
178
179### Components
180
181- 2 × Dynamixel servos (USART)
182- 100 W LED with heatsink + fan
183- Custom LED driver board (schematic: `LightArm/LED MOSFET board.fzz`)
184- 12 V supply + step-up converter to 33 V for the LED
185- The same `pwm`/`plevel`/`speed`/`p`/`r` command protocol family as studiolight (defined in `Arduino/LightArm/LightArm.ino`)
186
187### Mac address derivation
188
189MAC address is generated from the IP address — change the IP via the `ip`
190command and the MAC follows. DHCP isn't supported.
191
192---
193
194## Ghettopticon (cardboard wheelchair robot)
195
196Documented in [`ghettopticon/README`](ghettopticon/README). Summary:
197
198- Arduino + Adafruit Servo Shield drives up to 9 servos
199- Python (from Blender or standalone scripts) talks to Arduino over **USB serial** (not network)
200- Protocol: control byte `0xFF` followed by N bytes (one per servo, range 0–180)
201- BUFSIZE in the Arduino firmware must match the number of servos used
202
203This subproject is electrically independent from the studiolight rig and
204the original README notes it should probably live in its own repo.
205
206---
207
208## ESP8266 historical rigs
209
210`esp8266/esp8266-frak` and `esp8266/esp8266-rgb` were earlier-generation
211single-light controllers. ESP8266 has fewer PWM channels and lower clock
212than ESP32, which is why the project moved to ESP32 + the 12-channel
213`32wifi12frak` (and then to `esp324xlight`). Kept for reference;
214unlikely to be actively maintained.
215
216---
217
218## Pcb/ — PCB and breakout schematics
219
220[`Pcb/`](Pcb/) contains Fritzing files (`.fzz`) and rendered images for
221the various boards we use. See [`Pcb/README.md`](Pcb/README.md) for the
222index.
223
224Highlights:
225- `12v5v-breakout.fzz` — dual-rail breakout
226- `esp32-breakout.fzz` (and `.1`, `.2`, `.3x` variants) — ESP32 carrier
227- `sm-msft*.fzz` — MOSFET driver boards for the studiolight rig
228- `together-test.fzz` — assembly test
229
230---
231
232## Power-on checklist
233
234For a fresh studiolight unit:
235
2361. **GPIO 12 pull-down verified** — measure between GPIO 12 and ground
237 on the carrier. Should be < 1 kΩ pull-down. If not, the ESP32 won't boot.
2382. **Common ground** between ESP32 and 600 W PSU.
2393. **USB and 5 V rail not both connected.** One or the other.
2404. **MOSFET gates pulled down** (gate resistor or driver IC) so all 12
241 channels are OFF on power-on (not floating).
2425. Power on the 600 W PSU and the ESP32 USB (or 5 V).
2436. Wait for the boot fade self-test (10 s off → on → off across all 12
244 channels). If you see this, the chip is alive, PWM is configured, and
245 all 12 GPIOs are at least functional.
2467. From a dev box: `ping studiolight.local` (or the unit's IP) — should
247 respond.
2488. `telnet studiolight.local 1337` — should print `<hostname> ready`.
2499. Send `pwm 65534 65534 30000 65534 65534 30000 65534 65534 30000 65534 65534 30000` for the warm-white reference.
PROTOCOL.md +304 -0
……@@ -0,0 +1,304 @@
1# cthuloid wire protocol
2
3Plain-text, line-based, ASCII commands over TCP. Used by:
4
5- **studiolight units** (4-light × RGB rigs) running [`esp324xlight`](esp324xlight/) (modern) or [`esp32/32wifi12frak`](esp32/) (legacy) — port **1337**
6- **LightArm robots** (spotlight arms with Dynamixel servos) running [`Arduino/LightArm`](Arduino/) — port **1337**
7
8The studiolight and LightArm command sets overlap heavily (both inherit
9PWM + plevel + freq commands) but diverge where the underlying hardware
10differs (LightArm adds servo commands; studiolight adds gamma, OTA-related
11commands).
12
13This document covers the **studiolight protocol** as implemented by
14`esp324xlight`. For LightArm-specific commands see
15[`LightArm/readme`](LightArm/readme) and
16[`Arduino/LightArm/LightArm.ino`](Arduino/).
17
18## Connection
19
20```bash
21telnet studiolight.local 1337 # mDNS, all units claim studiolight.local — disambiguate by IP if multiple
22telnet 192.168.50.154 1337 # exact IP
23```
24
25On connect the unit sends a one-line greeting:
26
27```
28<hostname> ready
29```
30
31Each command is terminated by `\n` (or `\r\n` — `\r` is tolerated).
32Whitespace between tokens is a single space.
33
34## Channel value semantics
35
36Each PWM channel takes an integer 0..65535. With `INVERT_HIGH_AND_LOW`
37defined and an active-low LED driver, the mapping is:
38
39| Input | Output duty | LED state |
40|---|---|---|
41| `0` | 100% high (pin always HIGH) | **off** (active-low LED) |
42| `30000` | ~54% high | ~46% bright |
43| `45000` | ~31% high | ~69% bright — red's protective ceiling |
44| `65534` | ~0% high (pin always LOW) | **full on** |
45
46Higher value = brighter LED. **Red channels (position 2 of each RGB
47triple) are capped at 45000** to keep average voltage below the red
48diodes' ~26 V rating; values above that are rejected at parse time.
49
50## Per-light channel order
51
52The 12 channels are grouped into **4 RGB triples** (one per light), and
53each triple is sent in **BGR order**:
54
55| Light | Pin pos 0 (Blue) | Pin pos 1 (Green) | Pin pos 2 (Red) |
56|---|---|---|---|
57| 1 | GPIO 32 | GPIO 33 | GPIO 25 |
58| 2 | GPIO 26 | GPIO 27 | GPIO 14 |
59| 3 | GPIO 12 | GPIO 13 | GPIO 2 |
60| 4 | GPIO 23 | GPIO 22 | GPIO 21 |
61
62The Blender-side controller in
63[`LightArm/blender/lightarm_panel.py`](LightArm/blender/) reads
64`obj.data.color[0]` (red), `[1]` (green), `[2]` (blue), and emits them as
65`pwm_b pwm_g pwm_r` — so the on-wire order matches the firmware's BGR
66position layout.
67
68## Commands
69
70### `pwm <v1> <v2> ... <vN>`
71
72Set channel values immediately. `N` ≤ 12. Values follow the BGR-per-light
73order above. Channels beyond `N` are left at their previous value.
74
75```
76pwm 0 0 0 0 0 0 0 0 0 0 0 0 # all 12 channels to off
77pwm 65534 65534 30000 65534 65534 30000 65534 65534 30000 65534 65534 30000
78 # all 4 lights to "warm white" reference
79pwm 65534 65534 0 # only light 1's red to full bright
80```
81
82Brief 3-step interpolation from previous → target with no inter-frame
83delay, so visually it snaps.
84
85**Range:** 0–65535 per channel (red position capped at 45000).
86**Response:** `OK pwm <count>` on success, `ERR pwm <reason>` on failure.
87
88### `pwm2 <numPeriods> <periodMs> <v1> ... <vN>`
89
90Fade from each channel's current value to its target over
91`numPeriods × periodMs` milliseconds.
92
93| Arg | Range | Meaning |
94|---|---|---|
95| `numPeriods` | 1–1000 | number of interpolation steps |
96| `periodMs` | 1–1000 | milliseconds between steps |
97| `v1..vN` | 0–65535 | target value per channel (BGR order, ≤ 12) |
98
99Interpolation is **gamma-corrected** per-channel using the `gamma` setting.
100
101```
102pwm2 100 10 0 0 0 0 0 0 0 0 0 0 0 0 # 1 s fade to "off" on all 12
103pwm2 1000 30 65534 65534 30000 ... (×4) # 30 s smooth fade to white reference
104pwm2 500 1 65534 65534 30000 ... (×4) # 0.5 s smooth fade (max smoothness)
105```
106
107For maximum smoothness over any duration: max out `numPeriods` (1000) and set
108`periodMs = total_ms / 1000`. Minimum honoured `periodMs` is 1 (the
109FreeRTOS tick floor).
110
111**Response:** `OK pwm2 <count>`.
112
113### `freq [hz]`
114
115Get or set the PWM frequency for all 12 channels.
116
117With no argument: print current freq + auto-selected resolution.
118With one argument: set freq (range 1 to `maxfreq` = 40000) and reconfigure
119the LEDC peripheral.
120
121Resolution is auto-selected: the firmware picks the highest bit-depth
122(up to 16) that fits in the LEDC peripheral's 80 MHz clock at the
123requested freq.
124
125```
126freq # report current — e.g. "OK freq 1000 Hz at 16-bit (dutyMax=65535)"
127freq 1000 # 1 kHz; full 16-bit res
128freq 5000 # 5 kHz; auto-drops to 13-bit
129freq 20000 # 20 kHz; auto-drops to 11-bit
130```
131
132**Hardware caveat:** when ESP32 GPIO directly drives a MOSFET gate (no
133gate-driver IC), bandwidth limits cause LED brightness to droop at freqs
134above ~1.2 kHz. Sweet spot for the current rig: `freq 1000` (full bit-depth
135+ full LED brightness). See [`HARDWARE.md`](HARDWARE.md) for details.
136
137### `gamma [b] [g] [r]`
138
139Get or set per-channel gamma exponents used by `pwm2`'s perceptual
140interpolation. Three floats in **BGR order**, range 0.5–5.0.
141
142| Gamma | Behaviour |
143|---|---|
144| `1.0` | linear in input value (default, no perceptual correction) |
145| `1.5–1.8` | gentle perceptual correction |
146| `2.2` | full perceptual correction (slow start, fast end) |
147| `> 2.5` | aggressively slow this channel's ramp |
148
149Higher gamma slows that channel's *perceived* ramp during fades. Useful
150when red appears to lead green/blue because red LEDs are more
151luminous-per-duty.
152
153```
154gamma # report current
155gamma 1.0 1.0 1.5 # red slowed mildly
156gamma 1.2 1.2 1.8 # all corrected, red corrected most
157```
158
159**Response:** `gamma B=<b> G=<g> R=<r>`.
160
161### `hostname [name]`
162
163Get or set the unit's mDNS / OTA / greeting hostname. Set is persisted to
164NVS and takes effect after a `reboot`. Valid: 1–63 ASCII chars
165(letters / digits / hyphens).
166
167```
168hostname # → "hostname: studiolight"
169hostname studiolight-stage # stored; reboot to activate
170reboot # apply now
171```
172
173### `reboot`
174
175Soft-restart the chip via `ESP.restart()`. Useful after `hostname` change
176or to clear a stuck state.
177
178```
179reboot # → "Rebooting..." then connection drops
180```
181
182### `verbose 0 | 1`
183
184Toggle the `GOT <cmd>` / `OK <cmd>` / `ERR <cmd>` markers that are echoed
185to telnet for every command. Default ON for debugging. Turn OFF when a
186tight-loop controller (like the Blender script) is driving the unit and
187doesn't want ack chatter.
188
189```
190verbose 0 # quiet for production
191verbose 1 # noisy for debugging
192```
193
194### `plevel [level]`
195
196Set the verbosity of the underlying `PrintLevel`-tagged Serial output
197(printInfo / printAck / printError calls scattered through the code).
198Affects Serial, not telnet. Levels:
199
200| Level | Shows |
201|---|---|
202| `silent` | nothing |
203| `error` | errors only |
204| `ack` | + success confirmations |
205| `info` | + per-pin info lines (default) |
206
207```
208plevel ack # quieter Serial during fades
209```
210
211### `s ...`
212
213Servo command. Reserved for future per-light tilt/pan servo control;
214currently a stub that just prints "Got Servo shit" to Serial.
215
216### Unrecognized commands
217
218The dispatch's default handler emits:
219
220```
221ERR unrecognized: <whatever you typed>
222```
223
224## Reference Blender integration
225
226The Blender add-on at `LightArm/blender/lightarm_panel.py` (or
227equivalent) registers a `frame_change_post` handler that walks scene
228objects whose names start with `bgr-<index>-<ip>` and emits one `pwm`
229command per IP per frame:
230
231```python
232# in handler:
233ip = obj.name.split('-')[2]
234pwm_r = pwmFromLuminosity(obj.data.color[0], LIGHT_PWM_RED_MAX) # 0..30000 cap for red
235pwm_g = pwmFromLuminosity(obj.data.color[1]) # 0..65534 for G
236pwm_b = pwmFromLuminosity(obj.data.color[2]) # 0..65534 for B
237ss = f"{pwm_b} {pwm_g} {pwm_r} " # BGR order
238# aggregate 4 triples into one `pwm B1 G1 R1 B2 G2 R2 B3 G3 R3 B4 G4 R4` command
239sendString(ip, f"pwm {bgr_for_4_lights}\n")
240```
241
242Two practical conventions to know:
243
244- **Red is capped at 30000 client-side** even though the firmware would
245 accept up to 45000. The 30000 is normal-operation max; 45000 is the
246 hardware-safety redline.
247- **Same value re-sent is a no-op visually** because `pwm` snaps. To
248 observe a change, send a different value or use `pwm2` (which
249 interpolates from the current state).
250
251## Reference colour points (empirical)
252
253For light 1 with the others off:
254
255| Command | Result |
256|---|---|
257| `pwm 65534 65534 25000 65534 65534 65534 65534 65534 65534 65534 65534 65534` | Neutral white from light 1 |
258| `pwm 65534 65534 20000 65534 65534 65534 65534 65534 65534 65534 65534 65534` | Cooler white |
259| `pwm 65534 65534 30000 65534 65534 65534 65534 65534 65534 65534 65534 65534` | Warmer white |
260
261## Long-run stability
262
263The firmware handles the following automatically so a unit stays usable
264across days/weeks:
265
266- **WiFi reconnect** after AP drops (every 10 s health check, calls `WiFi.reconnect()`)
267- **Idle TCP timeout** drops stale telnet sessions after 5 min (works around the arduino-esp32 `connected()` bug that can keep dead sockets alive for tens of minutes)
268- **Old client `stop()`** on every new accept so a fresh telnet displaces a hung one
269- **`WiFi.setSleep(false)`** for persistent-server stability
270- **Watchdog feeding** during long fades (vTaskDelay yields properly)
271
272If a unit appears unresponsive: try a fresh `telnet`. If still dead, send
273`reboot` from another telnet. If that fails too, the unit is hung at a
274layer below `loop()` (rare).
275
276## OTA firmware push
277
278Once a unit is on the network, push new firmware without USB:
279
280```bash
281arduino-cli upload \
282 --fqbn esp32:esp32:esp32wrover:FlashFreq=80,UploadSpeed=921600,PartitionScheme=default,DebugLevel=none \
283 --port studiolight.local \
284 --protocol network \
285 --upload-field password=studiolight-ota \
286 esp324xlight/esp324xlight.ino
287```
288
289Password is set in the firmware via `#define OTA_PASSWORD` (default
290`studiolight-ota`). During the ~5-second upload, the main loop pauses
291and incoming commands are ignored; the chip auto-reboots into the new
292firmware on completion.
293
294## Diagnostic flow when commands "do nothing"
295
2961. Can you `telnet studiolight.local 1337` and see the greeting? If no:
297 network / mDNS issue. Try the IP directly.
2982. After connecting, type a known-noop like `gamma`. Do you see
299 `gamma B=1.00 G=1.00 R=1.00`? If no: command parser hung. `reboot`.
3003. Send `pwm 65534 65534 65534 65534 65534 65534 65534 65534 65534 65534 65534 65534`. Do all
301 LEDs go full off? If yes: protocol fine, your earlier command was
302 probably a no-op (sending the same value that was already set).
3034. If some channels are stuck regardless: likely hardware (MOSFET
304 gate-drive issue, wiring break). See [`HARDWARE.md`](HARDWARE.md).
Pcb/README.md +71 -0
……@@ -0,0 +1,71 @@
1# Pcb/
2
3Fritzing schematic + PCB layout files for the various breakouts and
4driver boards used by the cthuloid hardware. All files are
5[Fritzing](https://fritzing.org/) `.fzz` format unless noted.
6
7## File index
8
9### Power breakouts
10
11| File | Purpose |
12|---|---|
13| `12v5v-breakout.fzz` | Dual-rail 12 V / 5 V breakout |
14
15### ESP32 carriers
16
17| File | Purpose |
18|---|---|
19| `esp32-breakout.fzz` | Original ESP32 carrier board |
20| `esp32-breakout1.fzz`, `esp32-breakout (1).fzz` | Revision 1 |
21| `esp32-breakout2.fzz` | Revision 2 |
22| `esp32-breakout3x.fzz` | 3x variant (likely 3-channel) |
23| `esp32-breakout/` | Working dir / export for one of the above |
24| `breakout.fzz`, `breakout-export/`, `breakout_pcb.jpg` | Generic breakout (likely current studiolight carrier) |
25
26### MOSFET driver boards (studiolight rig)
27
28| File | Purpose |
29|---|---|
30| `sm-msft1.6.fzz` | Single-MOSFET driver board, rev 1.6 |
31| `sm-msft1.6/` | Export dir for 1.6 |
32| `sm-msft1.6_pcb.jpg` | Rendered PCB image for 1.6 |
33| `sm-msft1.7.fzz` | Revision 1.7 |
34| `sm-msft1.7/` | Export dir for 1.7 |
35| `sm-msft3-0.1.fzz` | Revision 3.0 alpha |
36| `sm-msft3-0.1_pcb.jpg` | Rendered image for 3.0 alpha |
37| `sm-3xmsft.fzz` | 3-channel MOSFET driver board |
38
39These drive the 12 channels of the studiolight rig (one MOSFET per
40channel). The "sm-msft" boards are the per-channel driver boards used
41in the current rig. The "3xmsft" variant integrates three drivers on one
42board (one RGB triple).
43
44### Misc
45
46| File | Purpose |
47|---|---|
48| `mb-test.fzz` | Test board |
49| `together-test.fzz`, `together-test/` | Assembly test (probably combines breakout + MOSFET boards) |
50
51## Hardware notes
52
53The current MOSFET driver boards drive the gates **directly from ESP32
54GPIO** (no dedicated gate-driver IC). This works at ≤1 kHz PWM but the
55output droops at higher freqs due to slow gate-charge transitions. See
56[`../HARDWARE.md`](../HARDWARE.md#driver-bandwidth--the-dominant-electrical-caveat)
57for the bandwidth analysis and the recommended gate-driver upgrade path.
58
59The MOSFETs used in the field are likely common N-channel logic-level
60parts (e.g., IRLZ44N, IRF3205, AOD508). Exact part numbers should be
61read off the boards or from the Fritzing files. A future PCB revision
62should add a TC4427-class gate driver between the ESP32 and the
63MOSFET to unlock higher-freq camera-safe operation.
64
65## Opening the files
66
67[Fritzing](https://fritzing.org/) is the original tool. KiCad can import
68Fritzing files via plugins but the schematic capture may need cleanup.
69
70Rendered PCB images (`*_pcb.jpg`) give a quick visual reference without
71needing Fritzing installed.
README.md +115 -16
……@@ -1,18 +1,117 @@
11# cthuloid
22
3Cthuloid is an experimental autonomous theater umbrella project by Circus Automatic.
4
5build a theater system that integrates robotics and performance into a hackable physical space where the performer can dictate lighting design to follow their every movement. Future plans include sound integration, shape changing scenery and more.
6
7Inspired largely by video game level design, it will use blender and other programs for its internal representation. low cost electronics / sensors will be crafted for its real world implementation. a big goal is to form the feedback loop which will allow for more dynamic performance possibilities.
8---
9
10Folders of note:
11- ### LightArm
12*-ethernet-enabled spotlight robot project*
13- ### Arduino/LightArm
14*-firware for the spotlight robot*
15- ### Arduino/LEDController
16*-firmware for ethernet-enabled controller for LEDs at high resolution*
17- ### ghettopticon
18*-humanoid robot mounted on a motorized wheelchair (should be moved to its own repo)*
3Cthuloid is an experimental autonomous theatre umbrella project by Circus Automatic.
4
5The goal: a theatre system that integrates robotics and performance into a
6hackable physical space where the performer can dictate lighting design to
7follow their every movement. Future plans include sound integration,
8shape-changing scenery, and richer feedback loops between performer and stage.
9
10Inspired largely by video-game level design — Blender is used as the
11modelling and simulation environment, and low-cost electronics/sensors are
12crafted for the real-world implementation.
13
14## Repository map
15
16| Folder | What lives here | Status |
17|---|---|---|
18| [`esp324xlight/`](esp324xlight/) | **Modern** 4-light × RGB controller firmware for ESP32 (Arduino core 3.x). Auto-resolution PWM, gamma-corrected fades, OTA, NVS-persisted hostname, TCP stale-client mitigations, runtime command surface. **Current production firmware** for studiolight units. | Active |
19| [`esp32/`](esp32/) | Older ESP32 sketches including the legacy `32wifi12frak` that `esp324xlight` evolved from, plus FastLED/Artnet variants, color tests, and a rotary-encoder lightswitch POC. | Mixed — `32wifi12frak` still works against Arduino core 2.0.17; others are experimental |
20| [`esp8266/`](esp8266/) | ESP8266 sketches (`esp8266-frak`, `esp8266-rgb`) — earlier-generation single-light controllers before the ESP32 12-channel design. | Historical |
21| [`Arduino/`](Arduino/) | Older Atmel-based Arduino firmware: `LightArm` (Mega-based spotlight robot), `LEDController`, `DMX_LED_Controller`, `Blenderbot`, `PrinbooLimbs`, `RDM_Slave`, ServoShield demos, etc. | Mixed |
22| [`LightArm/`](LightArm/) | Ethernet-enabled spotlight-robot project. Contains the Android app, Blender models + Python bridge, and standalone scripts. Firmware lives in `Arduino/LightArm/`. | Active |
23| [`cuemgr/`](cuemgr/) | Console-based cueing program for running theatre productions. Speaks both to LightArm robots (TCP) and DMX fixtures (via OLA). | Active |
24| [`ghettopticon/`](ghettopticon/) | Cardboard humanoid robot mounted on a motorized wheelchair, driven by Adafruit Servo Shield + Python from Blender. The original README notes this should probably move to its own repo. | Active (separate concern) |
25| [`Pcb/`](Pcb/) | Fritzing schematic + PCB files for the various breakout boards (12 V / 5 V breakout, ESP32 breakouts, MOSFET driver boards). | Reference |
26| [`rgb-strip.py`](rgb-strip.py) | Standalone Python script (top-level) for testing RGB strip behaviour over the network protocol. | Utility |
27| [`chrisclean.sh`](chrisclean.sh) | Maintenance script. | Utility |
28
29## Hardware stack at a glance
30
31The current 4-light rig (driven by `esp324xlight`):
32
33- **Controller**: ESP32 Wrover Module
34- **PWM**: 12 channels (4 lights × RGB) via ESP32 LEDC peripheral. Default: 1 kHz @ 16-bit (camera-safe; runtime-tunable with the `freq` command)
35- **Drivers**: 600 W power supply → MOSFET per channel → LED. The ESP32 GPIO currently drives the MOSFET gate directly (3.3 V); this works at ≤1 kHz but droops at higher freqs. A dedicated gate-driver IC is the upgrade path — see [`HARDWARE.md`](HARDWARE.md).
36- **LEDs**: Common-anode RGB at higher rail voltage. Blue/green at ~32 V, red at ~26 V. Red gets a protective input cap (`RED_MAX_PWM=45000`) so it can't be over-driven.
37- **Network**: WiFi → mDNS (`studiolight.local`) → TCP server on port 1337 + ArduinoOTA on port 3232.
38
39The LightArm sub-project uses an Arduino Mega with Dynamixel servos for a fully separate spotlight-robot hardware path.
40
41Full pin assignments, voltages, and electrical caveats: [`HARDWARE.md`](HARDWARE.md).
42
43## Software stack at a glance
44
45- **Firmware** — Arduino IDE / arduino-cli targeting ESP32 (Arduino core 3.x for `esp324xlight`, 2.0.17 for legacy `32wifi12frak`). See [`esp324xlight/README.md`](esp324xlight/README.md) for the build matrix.
46- **Show control** — Python (`cuemgr/`) speaks to multiple light units over TCP and to DMX hardware via the [OLA](https://www.openlighting.org/) (Open Lighting Architecture) daemon.
47- **Modelling / authoring** — Blender, with Python add-ons in `LightArm/blender/` (and `ghettopticon/blender/`) that translate Blender object positions/lighting into wire-protocol commands sent at every frame change.
48- **Network protocol** — Plain-text line-based commands over TCP/1337. Same command grammar (`pwm`, `pwm2`, `freq`, `gamma`, …) on both the LightArm robots and the studiolight units. Full grammar: [`PROTOCOL.md`](PROTOCOL.md).
49
50## Quick start for a new developer
51
52```bash
53# 1. Talk to a running studiolight from your workstation
54telnet studiolight.local 1337
55> pwm 65534 65534 30000 65534 65534 30000 65534 65534 30000 65534 65534 30000
56# (all 4 lights to a "warm white" reference per the Blender script convention)
57
58# 2. Build & flash the modern firmware (with the ESP32 core switcher installed)
59use-esp32-core 3.3.8
60arduino-cli compile --fqbn esp32:esp32:esp32wrover:FlashFreq=80,UploadSpeed=921600,PartitionScheme=default,DebugLevel=none esp324xlight/esp324xlight.ino
61arduino-cli upload --fqbn esp32:esp32:esp32wrover:FlashFreq=80,UploadSpeed=921600,PartitionScheme=default,DebugLevel=none --port /dev/ttyUSB0 esp324xlight/esp324xlight.ino
62
63# 3. Or push firmware over the air (no USB), once the unit is on the network:
64arduino-cli upload --port studiolight.local --protocol network --upload-field password=studiolight-ota \
65 --fqbn esp32:esp32:esp32wrover:FlashFreq=80,UploadSpeed=921600,PartitionScheme=default,DebugLevel=none \
66 esp324xlight/esp324xlight.ino
67
68# 4. Run a show from a cuesheet
69cd cuemgr
70python cuemgr.py
71```
72
73## Repo + deployment
74
75cthuloid lives in a [pear-git-v3](https://github.com/holepunchto) bare repo
76at `~/git-repos/cthuloid.git` and is replicated peer-to-peer via Hyperswarm.
77The former GitHub origin is retained as `git remote github` for fallback.
78
79```bash
80git push # → pear-git-v3 (origin)
81git push github master # → GitHub (legacy)
82```
83
84## ESP32 core switcher
85
86`esp324xlight` builds against ESP32 Arduino core **3.3.8**, and the legacy
87`esp32/32wifi12frak` requires core **2.0.17**. Both are installed
88side-by-side at `~/.arduino15/packages/esp32/hardware/esp32/`. The
89`use-esp32-core` script at `~/.local/bin/use-esp32-core` flips which one is
90active for the IDE / arduino-cli:
91
92```bash
93use-esp32-core 3.3.8 # modern (default; for esp324xlight and anything new)
94use-esp32-core 2.0.17 # legacy (only when rebuilding 32wifi12frak)
95use-esp32-core list # show current state
96```
97
98## Documentation map
99
100| File | Scope |
101|---|---|
102| [README.md](README.md) | This overview |
103| [PROTOCOL.md](PROTOCOL.md) | Wire protocol shared by all light/robot units |
104| [HARDWARE.md](HARDWARE.md) | Pin assignments, voltages, MOSFET driving notes, hardware-side gotchas |
105| [esp324xlight/README.md](esp324xlight/README.md) | Current production firmware — full command reference, OTA workflow, stability audit |
106| [esp32/README.md](esp32/README.md) | Legacy + experimental ESP32 sketches |
107| [esp8266/README.md](esp8266/README.md) | ESP8266 single-light sketches |
108| [Arduino/readme](Arduino/readme) | Short index of Atmel sketches |
109| [LightArm/readme](LightArm/readme) | Spotlight-robot subproject |
110| [cuemgr/readme](cuemgr/readme) | Cueing program |
111| [ghettopticon/README](ghettopticon/README) | Cardboard wheelchair robot |
112| [Pcb/README.md](Pcb/README.md) | Schematic + PCB index |
113
114## Credits
115
116Circus Automatic. Contributors include Jacob Fenwick, Fleeky Flanco,
117Francesco Capodieci, Vrav, and others through the years.
esp32/README.md +131 -0
……@@ -0,0 +1,131 @@
1# esp32/
2
3Legacy + experimental ESP32 sketches. The **production firmware** for the
44-light studiolight rig is one directory up at
5[`../esp324xlight/`](../esp324xlight/) — start there if you're looking at
6the current system.
7
8Most sketches here predate the `esp324xlight` rewrite. They are kept for
9historical reference and because some still run on hardware that hasn't
10been migrated to the modern firmware.
11
12## Build targets
13
14| Sketch | Requires ESP32 core | Notes |
15|---|---|---|
16| `32wifi12frak/` | **2.0.17** (legacy) | The direct predecessor of `esp324xlight`. Still works, with current fixes baked in. |
17| `32wifi12frakadafruit/` | unknown | Variant using Adafruit PWM library |
18| `32wifi12frakfastled/` | unknown | Variant using FastLED |
19| `32wifi12frakfled/` | unknown | Variant — unclear how it differs from `32wifi12frakfastled` |
20| `esp32-arnet-fastled/` | unknown | ArtNet → FastLED bridge |
21| `esp32-rotary-encoder/` | any | Rotary encoder lightswitch POC |
22| `esp8266-pwm-net/` | n/a (ESP8266) | Old single-light controller, lives here for grouping reasons |
23| `sc_test/` | any | SerialCommand library test |
24| `sketch_colortest/` | any | Standalone colour-test sketch |
25
26Use the `use-esp32-core` script to switch between cores when building
27against the legacy 2.0.17 vs the modern 3.x:
28
29```bash
30use-esp32-core 2.0.17 # for 32wifi12frak
31use-esp32-core 3.3.8 # for everything else / esp324xlight
32```
33
34## `32wifi12frak/` — the original 12-channel studiolight firmware
35
36This is the firmware that drove the studiolight rig before
37`esp324xlight` was written. It implements the same 12-channel PWM
38controller and the same plain-text TCP protocol (port 1337), but:
39
40- Uses the **old LEDC API** (`ledcSetup` + `ledcAttachPin` + channel-based `ledcWrite`) which was removed in ESP32 Arduino core 3.x. Builds only against **core 2.0.17**.
41- Uses `WiFiServer::available()` (the deprecated form of `accept()`).
42- The 12 `ledChannel0..11` globals are explicit; channels aren't auto-allocated.
43- Has been patched in-tree with the same fixes we added to `esp324xlight`:
44 - `freq = 1000` (was 20000, which is impossible at 16-bit on the LEDC peripheral and silently fails)
45 - GOT/OK/ERR telnet diagnostic markers
46 - TCP stale-client mitigations (`#1527` / `#4773` workarounds, `WiFi.setSleep(false)`, 5-min idle timeout)
47 - `MAX_PWM 65535` (was 65534 → an off-by-one that rejected legitimate full-off commands)
48 - `verbose 0|1` runtime toggle for the diag markers
49
50If a deployed unit is still on `32wifi12frak` and you want to keep it
51there, build with:
52
53```bash
54use-esp32-core 2.0.17
55arduino-cli compile --fqbn esp32:esp32:esp32wrover:FlashFreq=80,UploadSpeed=921600,PartitionScheme=default,DebugLevel=none 32wifi12frak/32wifi12frak.ino
56```
57
58Otherwise, **flash `esp324xlight` instead** — same hardware, same
59protocol, plus OTA, gamma correction, auto-resolution PWM, NVS-persisted
60hostname, etc.
61
62## `32wifi12frakadafruit/`, `32wifi12frakfastled/`, `32wifi12frakfled/`
63
64Forks of `32wifi12frak` that experiment with alternative driver
65libraries:
66
67- **Adafruit** — uses the Adafruit PWM Servo Driver Library (off-board PCA9685)
68- **FastLED** — uses the FastLED library (typically for WS2811-class addressable strips)
69- **fled** — unclear, possibly a different FastLED variant
70
71These were experiments; only `32wifi12frak` (and its successor `esp324xlight`) made it to the active rig. Build status of these forks isn't currently verified.
72
73## `esp32-arnet-fastled/`
74
75ArtNet receiver bridged to a FastLED-driven strip. Useful for plugging
76an ESP32-based light into a lighting console that speaks ArtNet/sACN
77instead of our custom TCP protocol. Untested in the current rig.
78
79## `esp32-rotary-encoder/`
80
81Two sketches:
82
83- `lightswitch_poc/` — proof-of-concept "lightswitch" UI that uses a
84 rotary encoder to control brightness on a single channel
85- `rotary-encoder/` — generic rotary encoder driver
86
87Added more recently (2025-ish) as exploratory work on physical-UI inputs
88to complement the network-driven rig.
89
90## `esp8266-pwm-net/`
91
92ESP8266 (not ESP32) sketch for a single-light controller. Lives here for
93historical filing reasons; functionally belongs with `../esp8266/`. The
94ESP8266 path was abandoned in favour of ESP32 once channel count
95requirements grew past what ESP8266 could provide.
96
97## `sc_test/`
98
99Bare-minimum sketch that uses just the SerialCommand (`SC`) library —
100the same dispatcher all the WiFi sketches use. Useful for debugging the
101command parser in isolation if a `pwm`/`pwm2`/etc. handler stops
102responding.
103
104## `sketch_colortest/`
105
106Standalone colour-test sketch. Cycles through hard-coded colour
107combinations to verify that the wiring on a new rig is correct (which
108GPIO drives which colour on each light). Useful when bringing up a new
109unit.
110
111## Build with arduino-cli (any sketch here)
112
113```bash
114# Pick the right core
115use-esp32-core 2.0.17 # for 32wifi12frak only
116# OR
117use-esp32-core 3.3.8 # for most other sketches here
118
119# Compile
120arduino-cli compile \
121 --fqbn esp32:esp32:esp32wrover:FlashFreq=80,UploadSpeed=921600,PartitionScheme=default,DebugLevel=none \
122 <sketch-dir>/<sketch>.ino
123
124# Upload over USB
125arduino-cli upload \
126 --fqbn esp32:esp32:esp32wrover:FlashFreq=80,UploadSpeed=921600,PartitionScheme=default,DebugLevel=none \
127 --port /dev/ttyUSB0 \
128 <sketch-dir>/<sketch>.ino
129```
130
131`esp324xlight` is the only sketch in this repo that supports OTA (over-WiFi) uploads.
esp8266/README.md +49 -0
……@@ -0,0 +1,49 @@
1# esp8266/
2
3Historical ESP8266 single-light controllers. The project moved to ESP32
4+ 12-channel control (now [`esp324xlight/`](../esp324xlight/)) because
5ESP8266 doesn't have enough hardware PWM channels for 4 lights × RGB.
6
7These sketches are kept for reference / for resurrecting an ESP8266
8single-light if you have one in a drawer.
9
10## `esp8266-frak/`
11
12ESP8266 controller for a single light, using the same `pwm` / `plevel`
13command grammar as the larger ESP32 rigs (subset thereof). Connects to
14WiFi and listens on TCP/1337.
15
16## `esp8266-rgb/`
17
18ESP8266 RGB controller — like `esp8266-frak` but with three channels
19(R, G, B) instead of one.
20
21## Building
22
23ESP8266 sketches require the **ESP8266 Arduino core**, which is a
24separate package from the ESP32 cores managed by the `use-esp32-core`
25script:
26
27```bash
28arduino-cli core install esp8266:esp8266
29arduino-cli compile --fqbn esp8266:esp8266:nodemcuv2 <sketch>.ino
30arduino-cli upload --fqbn esp8266:esp8266:nodemcuv2 --port /dev/ttyUSB0 <sketch>.ino
31```
32
33(Board fqbn depends on your specific ESP8266 board — `nodemcuv2`,
34`d1_mini`, `huzzah`, etc.)
35
36## Why this isn't the current path
37
38ESP8266 has:
39- Only 1 hardware PWM channel (the rest are software-PWM, jittery)
40- Less RAM and Flash
41- Slower CPU (80 MHz vs ESP32's 240 MHz)
42
43For a 12-channel RGB rig the ESP32's LEDC peripheral (16 hardware PWM
44channels, dedicated timer hardware) is decisively the right answer.
45The ESP8266 controllers worked for a single-light/single-channel
46configuration but didn't scale to the studiolight rig.
47
48See [`../esp324xlight/`](../esp324xlight/) for the current 12-channel
49firmware.