board
RITHESH LED Display
Public Unreviewedby Rithesh03
A 300 x 70 mm PCB that spells RITHESH with 107 WS2812B RGB LEDs, controlled over Wi-Fi.
← Commit history
Update 4: improved README (V1 overview, images, LED order), Version 2 proposal files
22 files changed
+2093−38
README.md+159−38docs/images/led-order-v1.pngdocs/images/led-order-v1.svg+117docs/tools/make_led_order_diagram.py+55v2/README.md+21v2/firmware/README.md+49v2/firmware/led-order.csv+108v2/firmware/ledmap.json+1v2/firmware/ledmap1.json+1v2/hardware/layout-plan/board-plan-v2.pngv2/hardware/layout-plan/board-plan-v2.svg+189v2/hardware/layout-plan/electronics-area-v2.pngv2/hardware/layout-plan/electronics-area-v2.svg+100v2/hardware/layout-plan/footprint-LED_XL-2020RGBC-2812B.pngv2/hardware/layout-plan/footprint-LED_XL-2020RGBC-2812B.svg+28v2/hardware/layout-plan/led-pattern-v2.pngv2/hardware/layout-plan/led-pattern-v2.svg+297v2/hardware/rithesh_v2_fp.pretty/LED_XL-2020RGBC-2812B.kicad_mod+29v2/hardware/tools/make_v2_footprint.py+94v2/hardware/tools/make_v2_ledmaps.py+80v2/hardware/tools/make_v2_plan.py+432v2/requirements.md+333README.md+159−38@@ -1,45 +1,166 @@ # RITHESH LED Display -A custom PCB (printed circuit board) that spells out the name **"RITHESH"** with 107 addressable RGB LEDs,-controlled over Wi-Fi with **WLED** on an ESP32-C3.+A custom circuit board that spells the name **RITHESH** with **107 colour LEDs**. You control it from a phone or+computer over **Wi-Fi**, using the free **WLED** app. It is powered from an ordinary **USB-C phone charger**. -> **Status (2026-09-24): PCB design complete and approved for a prototype.**-> **Do not order yet**: the XL LED timing pre-check and JLCPCB's manufacturing review must pass first.-> See [FINAL-REVIEW-CHECKLIST.md](FINAL-REVIEW-CHECKLIST.md).+> **Status (2026-09-25): Version 1 design complete and approved for a prototype.**+> **Not ordered yet.** Two things must pass first: a timing test with a few real LEDs, and JLCPCB's manufacturing+> review. A smaller **Version 2** is being planned (see the last section). -## The board+++## Project overview++- **What it is:** a 300 × 70 mm board with the letters R-I-T-H-E-S-H made of small addressable RGB LEDs. Every LED+ can show any colour.+- **Why:** a personal name sign that can show colours, patterns and animations, controlled from a phone.+- **How it was made:** requirements, schematic, PCB layout and every check were done step by step and saved in this+ page's files. The board is designed for **JLCPCB** to make and assemble (all parts soldered by the factory).++## Final Version 1 specifications | | | |--|--|-| Size | 300 × 70 mm, 2 layers, 1.6 mm FR-4, 1 oz copper, four M3 mounting holes |-| LEDs | 107 × XL-5050RGBC-2812B (WS2812B-style), 7 mm grid, each with its own 100 nF capacitor |-| Controller | ESP32-C3-MINI-1-N4 (Wi-Fi), running standard WLED |-| Power | USB-C 5 V up to 3 A → SMAJ5.0A surge diode → TPS259531 eFuse (soft start, current limit 2.49–2.90 A, 5.7 V clamp) → 5 V copper fill |-| Other | USBLC6 USB protection, TLV76733 3.3 V regulator, 74AHCT1G125 LED data level shifter, RESET / ON/OFF / BOOT buttons, 10 test pads |-| Assembly | All parts on the front, assembled by JLCPCB (250 placed parts) |--## Project status--- [x] Requirements agreed ([requirements.md](requirements.md))-- [x] Parts chosen (JLCPCB/LCSC numbers in the schematic and BOM)-- [x] Schematic drawn, reviewed and approved ([hardware/schematic.pdf](hardware/schematic.pdf))-- [x] PCB placement and routing done and approved: DRC, ERC and all checks clean ([hardware/pcb-layout-notes.md](hardware/pcb-layout-notes.md))-- [x] Manufacturing package generated **for review** ([hardware/manufacturing/RITHESH-v1-PROTOTYPE/](hardware/manufacturing/RITHESH-v1-PROTOTYPE/))-- [x] WLED setup guide and LED maps ([firmware/](firmware/))-- [ ] **LED timing pre-check** with 5–10 XL LEDs (must pass before ordering)-- [ ] **JLCPCB DFM check and placement preview** (must pass before ordering)-- [ ] Order a small prototype batch, then run the prototype tests--## Files--| Path | What it is |-|------|------------|-| `requirements.md` | Every design decision, power budget, accepted risks and the prototype plan |-| `UPDATES.md` | Progress notes, newest at the bottom |-| `FINAL-REVIEW-CHECKLIST.md` | What has passed and what still needs physical or JLCPCB testing |-| `hardware/` | KiCad project (schematic, PCB, footprints), `schematic.pdf`, `schematic-review.md`, `pcb-layout-notes.md`, check reports |-| `hardware/layout-review/` | Review images: routed board, copper layers, close-ups, 3D views |-| `hardware/manufacturing/RITHESH-v1-PROTOTYPE/` | Gerbers + drills (ZIP), BOM, CPL, assembly drawings, fabrication notes: **prototype, do not order yet** |-| `hardware/tools/` | Python scripts that generate and check the design (placement, routing, checks, images, manufacturing files) |-| `firmware/` | WLED installation and setup guide, `ledmap.json`, `ledmap1.json`, LED order |-| `HYDROGEN-FEEDBACK.md` | Feedback on the tools used while making this project |+| Size | 300 × 70 mm, 2 layers, 1.6 mm thick, 1 oz copper, four M3 mounting holes |+| LEDs | 107 × XL-5050RGBC-2812B (WS2812B-style), 5 × 5 mm, 7 mm apart, each with its own 100 nF capacitor |+| Letters | 5 × 7 dot pattern, each letter about 33 × 47 mm |+| Brain | ESP32-C3-MINI-1 Wi-Fi module running standard **WLED** |+| Power | USB-C, 5 V, up to 3 A charger. WLED limits the whole board to 2 A |+| Protection | surge diode, USB protection chip, and an electronic fuse (eFuse) with soft start, current limit and over-voltage clamp |+| Buttons | RESET, ON/OFF, BOOT |+| Test pads | 10 (power, 3.3 V, ground, LED data, eFuse status and current, serial RX/TX) |+| Assembly | 250 parts, all on the front |++++## How the board works++1. The **USB-C charger** supplies 5 V.+2. A **surge diode** and a **USB protection chip** absorb voltage spikes, and the **eFuse** switches the power on+ gently and cuts it off if too much current flows (about 2.5–2.9 A) or the voltage is too high.+3. A **3.3 V regulator** powers the **ESP32-C3** Wi-Fi chip.+4. The ESP32 runs **WLED**. It sends colour data out of one pin (GPIO10).+5. A **level shifter** turns the 3.3 V data signal into a clean 5 V signal for the LEDs.+6. The data enters **LED1** (top-left of the "R") and passes from LED to LED, all the way to **LED107**.++++++## Major components++| Part | What it does | JLCPCB part |+|------|--------------|-------------|+| XL-5050RGBC-2812B × 107 | the colour LEDs (WS2812B-style, each with a tiny built-in controller) | C2843785 |+| ESP32-C3-MINI-1-N4 | Wi-Fi chip that runs WLED; also programmed over the USB-C port | C2838502 |+| TPS259531 eFuse | soft start, 2.5–2.9 A current limit, over-voltage clamp, automatic restart after a fault | C2155674 |+| SMAJ5.0A | surge (TVS) diode on the 5 V input | C2925443 |+| USBLC6-2SC6 | protects the USB data lines from static | C7519 |+| TLV76733 | 3.3 V regulator for the ESP32 | C2848334 |+| SN74AHCT1G125 | 3.3 V to 5 V level shifter for the LED data | C7484 |+| TYPE-C-31-M-12 | USB-C connector for power and programming | C165948 |+| 220 µF × 2 (+ 2 spare spots) | bulk capacitors that steady the LED power | C72496 |++## Power design++- **Budget:** the charger gives up to 3 A. WLED keeps the whole board at or under **2 A** (about 1.88 A for the LEDs+ plus the ESP32).+- **Full white:** all LEDs at full white would need about 3.85 A, so WLED dims white to about 30 %. Single+ colours can be much brighter.+- **Supply copper:** the back of the board is a solid ground plane. The front has a 5 V copper area under all the+ LEDs.+- **Voltage drop:** the worst voltage loss to the farthest LED is calculated at only **9 mV** at 1.88 A.+- **eFuse:** stays below the charger's 3 A and above the board's 2.2 A worst case (including Wi-Fi peaks).++++++## PCB design and checks++- **Checks that all pass:**+ - design-rule check: 0 errors, 0 unconnected, 0 footprint errors+ - electrical-rule check: 0 errors, 0 warnings+ - every chip pin checked against its datasheet+ - netlist checker, placement checker and a voltage-drop calculation+- **Antenna:** the ESP32 antenna sits at the bottom edge with no copper under or beside it.+- **USB:** the data pair has protection right at the connector and ground vias wherever it changes layer.+- **Vias:** all are covered by solder mask, and none are inside a pad.++++++++++Design notes: [hardware/pcb-layout-notes.md](hardware/pcb-layout-notes.md) ·+schematic: [hardware/schematic.pdf](hardware/schematic.pdf) · all decisions: [requirements.md](requirements.md)++## Firmware and WLED++- **Firmware:** standard WLED, installed from a web browser at install.wled.me. No custom firmware is needed.+- **Settings:**+ - LED output on **GPIO10**, **107** LEDs+ - ON/OFF button on **GPIO5**+ - **Maximum PSU Current 2000 mA**+- **Letter effects:** the files `firmware/ledmap.json` and `firmware/ledmap1.json` make effects follow the letters.+- **Full guide:** [firmware/README.md](firmware/README.md) covers installing, Wi-Fi setup, testing and USB recovery.++## Manufacturing package++[hardware/manufacturing/RITHESH-v1-PROTOTYPE/](hardware/manufacturing/RITHESH-v1-PROTOTYPE/) contains:+- the Gerber and drill ZIP+- the parts list (BOM) and placement file (CPL) for JLCPCB+- assembly drawings and fabrication notes++It is marked **"PROTOTYPE – DO NOT ORDER"** until the tests below pass. Nothing has been uploaded to JLCPCB or+ordered.++## Remaining physical tests++**Before ordering:**+1. **LED timing test:** 5–10 real LEDs on an ESP32-C3 running WLED for 30 minutes, plus an oscilloscope check of+ the data timing.+2. **JLCPCB review:**+ - manufacturing check+ - placement preview (every part's rotation and pin 1)+ - stock+ - board size accepted+ - final price++**On the prototype:**+- **Power:** starts cleanly; 5 V stays at or above 4.7 V at full load.+- **LEDs:** all 107 light in the right order.+- **WLED:** the 2 A limit holds.+- **Connections:** Wi-Fi range, USB programming and the RESET/BOOT recovery, and the ON/OFF button all work.+- **Power ripple:** decides whether to fit the two spare capacitors.++The full list is in [FINAL-REVIEW-CHECKLIST.md](FINAL-REVIEW-CHECKLIST.md).++## Version 2 optimization plan++A smaller Version 2 is being planned in [v2/](v2/). It is a **proposal only**; Version 1 stays unchanged.++| | Version 1 | Version 2 (plan) |+|---|---|---|+| Board | 300 × 70 mm | **250 × 40 mm** (about half the area) |+| LEDs | 107 × 5 mm LEDs, 7 mm apart | 107 × **2 mm** LEDs, 5 mm apart |+| Letters | 33 × 47 mm | 22 × 32 mm |+| Full white | about 3.85 A (dimmed by WLED) | about 1.66 A (full brightness) |+| Electronics | strip under the letters | at the right end |++In Version 2 the LED data enters at the right end, next to the electronics. The letters look the same, but the+**LED numbering is reversed**: V2 LED1 is where V1 LED107 is. Version 2 therefore has its own WLED maps in+[v2/firmware/](v2/firmware/).++The new LED stays **provisional** until its own timing test passes. Details are in+[v2/requirements.md](v2/requirements.md).++++---++Files: [README](README.md) · [requirements](requirements.md) · [updates](UPDATES.md) ·+[final checklist](FINAL-REVIEW-CHECKLIST.md) · [Hydrogen feedback](HYDROGEN-FEEDBACK.md)
docs/images/led-order-v1.pngadded⋯ 1 unchanged line ⋯
docs/images/led-order-v1.svgadded+117@@ -0,0 +1,117 @@+<svg xmlns="http://www.w3.org/2000/svg" width="2528" height="736" viewBox="-8 -8 316.0 92.0" font-family="DejaVu Sans, Arial, sans-serif">+<rect x="-8" y="-8" width="316.0" height="92.0" fill="#ffffff"/>+<rect x="0" y="0" width="300.0" height="70.0" rx="2" fill="#1f5f3a" stroke="#333" stroke-width="0.3"/>+<polyline points="10.00,7.80 17.00,7.80 24.00,7.80 31.00,7.80 38.00,14.80 10.00,14.80 10.00,21.80 38.00,21.80 31.00,28.80 24.00,28.80 17.00,28.80 10.00,28.80 10.00,35.80 24.00,35.80 31.00,42.80 10.00,42.80 10.00,49.80 38.00,49.80 59.00,49.80 66.00,49.80 73.00,49.80 66.00,42.80 66.00,35.80 66.00,28.80 66.00,21.80 66.00,14.80 59.00,7.80 66.00,7.80 73.00,7.80 94.00,7.80 101.00,7.80 108.00,7.80 115.00,7.80 122.00,7.80 108.00,14.80 108.00,21.80 108.00,28.80 108.00,35.80 108.00,42.80 108.00,49.80 136.00,49.80 164.00,49.80 164.00,42.80 136.00,42.80 136.00,35.80 164.00,35.80 164.00,28.80 157.00,28.80 150.00,28.80 143.00,28.80 136.00,28.80 136.00,21.80 164.00,21.80 164.00,14.80 136.00,14.80 136.00,7.80 164.00,7.80 178.00,7.80 185.00,7.80 192.00,7.80 199.00,7.80 206.00,7.80 178.00,14.80 178.00,21.80 199.00,28.80 192.00,28.80 185.00,28.80 178.00,28.80 178.00,35.80 178.00,42.80 178.00,49.80 185.00,49.80 192.00,49.80 199.00,49.80 206.00,49.80 220.00,49.80 227.00,49.80 234.00,49.80 241.00,49.80 248.00,42.80 248.00,35.80 241.00,28.80 234.00,28.80 227.00,28.80 220.00,21.80 220.00,14.80 227.00,7.80 234.00,7.80 241.00,7.80 248.00,7.80 262.00,7.80 290.00,7.80 290.00,14.80 262.00,14.80 262.00,21.80 290.00,21.80 290.00,28.80 283.00,28.80 276.00,28.80 269.00,28.80 262.00,28.80 262.00,35.80 290.00,35.80 290.00,42.80 262.00,42.80 262.00,49.80 290.00,49.80" fill="none" stroke="#ffb000" stroke-width="0.6" stroke-linejoin="round"/>+<rect x="7.5" y="5.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="10.00" y="8.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">1</text>+<rect x="14.5" y="5.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="17.00" y="8.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">2</text>+<rect x="21.5" y="5.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="24.00" y="8.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">3</text>+<rect x="28.5" y="5.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="31.00" y="8.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">4</text>+<rect x="35.5" y="12.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="38.00" y="15.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">5</text>+<rect x="7.5" y="12.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="10.00" y="15.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">6</text>+<rect x="7.5" y="19.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="10.00" y="22.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">7</text>+<rect x="35.5" y="19.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="38.00" y="22.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">8</text>+<rect x="28.5" y="26.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="31.00" y="29.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">9</text>+<rect x="21.5" y="26.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="24.00" y="29.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">10</text>+<rect x="14.5" y="26.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="17.00" y="29.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">11</text>+<rect x="7.5" y="26.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="10.00" y="29.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">12</text>+<rect x="7.5" y="33.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="10.00" y="36.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">13</text>+<rect x="21.5" y="33.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="24.00" y="36.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">14</text>+<rect x="28.5" y="40.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="31.00" y="43.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">15</text>+<rect x="7.5" y="40.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="10.00" y="43.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">16</text>+<rect x="7.5" y="47.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="10.00" y="50.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">17</text>+<rect x="35.5" y="47.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="38.00" y="50.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">18</text>+<rect x="56.5" y="47.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="59.00" y="50.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">19</text>+<rect x="63.5" y="47.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="66.00" y="50.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">20</text>+<rect x="70.5" y="47.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="73.00" y="50.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">21</text>+<rect x="63.5" y="40.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="66.00" y="43.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">22</text>+<rect x="63.5" y="33.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="66.00" y="36.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">23</text>+<rect x="63.5" y="26.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="66.00" y="29.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">24</text>+<rect x="63.5" y="19.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="66.00" y="22.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">25</text>+<rect x="63.5" y="12.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="66.00" y="15.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">26</text>+<rect x="56.5" y="5.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="59.00" y="8.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">27</text>+<rect x="63.5" y="5.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="66.00" y="8.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">28</text>+<rect x="70.5" y="5.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="73.00" y="8.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">29</text>+<rect x="91.5" y="5.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="94.00" y="8.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">30</text>+<rect x="98.5" y="5.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="101.00" y="8.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">31</text>+<rect x="105.5" y="5.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="108.00" y="8.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">32</text>+<rect x="112.5" y="5.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="115.00" y="8.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">33</text>+<rect x="119.5" y="5.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="122.00" y="8.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">34</text>+<rect x="105.5" y="12.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="108.00" y="15.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">35</text>+<rect x="105.5" y="19.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="108.00" y="22.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">36</text>+<rect x="105.5" y="26.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="108.00" y="29.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">37</text>+<rect x="105.5" y="33.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="108.00" y="36.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">38</text>+<rect x="105.5" y="40.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="108.00" y="43.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">39</text>+<rect x="105.5" y="47.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="108.00" y="50.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">40</text>+<rect x="133.5" y="47.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="136.00" y="50.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">41</text>+<rect x="161.5" y="47.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="164.00" y="50.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">42</text>+<rect x="161.5" y="40.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="164.00" y="43.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">43</text>+<rect x="133.5" y="40.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="136.00" y="43.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">44</text>+<rect x="133.5" y="33.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="136.00" y="36.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">45</text>+<rect x="161.5" y="33.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="164.00" y="36.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">46</text>+<rect x="161.5" y="26.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="164.00" y="29.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">47</text>+<rect x="154.5" y="26.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="157.00" y="29.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">48</text>+<rect x="147.5" y="26.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="150.00" y="29.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">49</text>+<rect x="140.5" y="26.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="143.00" y="29.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">50</text>+<rect x="133.5" y="26.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="136.00" y="29.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">51</text>+<rect x="133.5" y="19.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="136.00" y="22.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">52</text>+<rect x="161.5" y="19.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="164.00" y="22.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">53</text>+<rect x="161.5" y="12.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="164.00" y="15.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">54</text>+<rect x="133.5" y="12.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="136.00" y="15.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">55</text>+<rect x="133.5" y="5.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="136.00" y="8.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">56</text>+<rect x="161.5" y="5.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="164.00" y="8.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">57</text>+<rect x="175.5" y="5.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="178.00" y="8.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">58</text>+<rect x="182.5" y="5.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="185.00" y="8.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">59</text>+<rect x="189.5" y="5.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="192.00" y="8.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">60</text>+<rect x="196.5" y="5.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="199.00" y="8.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">61</text>+<rect x="203.5" y="5.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="206.00" y="8.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">62</text>+<rect x="175.5" y="12.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="178.00" y="15.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">63</text>+<rect x="175.5" y="19.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="178.00" y="22.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">64</text>+<rect x="196.5" y="26.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="199.00" y="29.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">65</text>+<rect x="189.5" y="26.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="192.00" y="29.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">66</text>+<rect x="182.5" y="26.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="185.00" y="29.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">67</text>+<rect x="175.5" y="26.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="178.00" y="29.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">68</text>+<rect x="175.5" y="33.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="178.00" y="36.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">69</text>+<rect x="175.5" y="40.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="178.00" y="43.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">70</text>+<rect x="175.5" y="47.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="178.00" y="50.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">71</text>+<rect x="182.5" y="47.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="185.00" y="50.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">72</text>+<rect x="189.5" y="47.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="192.00" y="50.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">73</text>+<rect x="196.5" y="47.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="199.00" y="50.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">74</text>+<rect x="203.5" y="47.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="206.00" y="50.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">75</text>+<rect x="217.5" y="47.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="220.00" y="50.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">76</text>+<rect x="224.5" y="47.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="227.00" y="50.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">77</text>+<rect x="231.5" y="47.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="234.00" y="50.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">78</text>+<rect x="238.5" y="47.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="241.00" y="50.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">79</text>+<rect x="245.5" y="40.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="248.00" y="43.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">80</text>+<rect x="245.5" y="33.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="248.00" y="36.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">81</text>+<rect x="238.5" y="26.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="241.00" y="29.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">82</text>+<rect x="231.5" y="26.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="234.00" y="29.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">83</text>+<rect x="224.5" y="26.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="227.00" y="29.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">84</text>+<rect x="217.5" y="19.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="220.00" y="22.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">85</text>+<rect x="217.5" y="12.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="220.00" y="15.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">86</text>+<rect x="224.5" y="5.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="227.00" y="8.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">87</text>+<rect x="231.5" y="5.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="234.00" y="8.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">88</text>+<rect x="238.5" y="5.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="241.00" y="8.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">89</text>+<rect x="245.5" y="5.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="248.00" y="8.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">90</text>+<rect x="259.5" y="5.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="262.00" y="8.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">91</text>+<rect x="287.5" y="5.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="290.00" y="8.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">92</text>+<rect x="287.5" y="12.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="290.00" y="15.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">93</text>+<rect x="259.5" y="12.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="262.00" y="15.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">94</text>+<rect x="259.5" y="19.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="262.00" y="22.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">95</text>+<rect x="287.5" y="19.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="290.00" y="22.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">96</text>+<rect x="287.5" y="26.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="290.00" y="29.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">97</text>+<rect x="280.5" y="26.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="283.00" y="29.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">98</text>+<rect x="273.5" y="26.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="276.00" y="29.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">99</text>+<rect x="266.5" y="26.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="269.00" y="29.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">100</text>+<rect x="259.5" y="26.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="262.00" y="29.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">101</text>+<rect x="259.5" y="33.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="262.00" y="36.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">102</text>+<rect x="287.5" y="33.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="290.00" y="36.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">103</text>+<rect x="287.5" y="40.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="290.00" y="43.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">104</text>+<rect x="259.5" y="40.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="262.00" y="43.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">105</text>+<rect x="259.5" y="47.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="262.00" y="50.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">106</text>+<rect x="287.5" y="47.3" width="5.0" height="5.0" rx="0.5" fill="#f4f4f4" stroke="#111" stroke-width="0.15"/><text x="290.00" y="50.55" font-size="2.1" fill="#111" text-anchor="middle" font-weight="bold">107</text>+<text x="10.00" y="3.50" font-size="2.6" fill="#ffd84d" text-anchor="middle" font-weight="bold">LED1 (data in)</text>+<text x="290.00" y="56.00" font-size="2.6" fill="#ffd84d" text-anchor="middle" font-weight="bold">LED107</text>+<rect x="33.4" y="53.4" width="13.2" height="16.6" rx="0.8" fill="none" stroke="#9fe39f" stroke-width="0.4" stroke-dasharray="1.5 1"/><text x="40.00" y="62.70" font-size="2.4" fill="#9fe39f" text-anchor="middle" font-weight="normal">ESP32</text>+<rect x="85.5" y="62.7" width="9.0" height="7.3" rx="0.8" fill="none" stroke="#9fe39f" stroke-width="0.4" stroke-dasharray="1.5 1"/><text x="90.00" y="67.35" font-size="2.4" fill="#9fe39f" text-anchor="middle" font-weight="normal">USB-C</text>+<text x="150.00" y="75.50" font-size="2.5" fill="#222" text-anchor="middle" font-weight="normal">RITHESH Version 1: the 107 LEDs are one chain, LED1 to LED107. Numbers = order on the data line (WLED index = number - 1). The chain zig-zags inside each letter.</text>+</svg>
docs/tools/make_led_order_diagram.pyadded+55@@ -0,0 +1,55 @@+"""LED-order diagram of the finished Version 1 board, for the Wiki README.++Reads firmware/led-order.csv (LED1..LED107 positions) and writes docs/images/led-order-v1.svg / .png:+the 300 x 70 mm outline, every LED with its chain number, the data path, and the LED1 start point.++Usage: python3 docs/tools/make_led_order_diagram.py+"""+import csv+import os+import subprocess++ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))+OUT = os.path.join(ROOT, "docs", "images")+W, H = 300.0, 70.0+LED = 5.0+++def text(x, y, s, size, color, anchor="middle", weight="normal"):+ return (f'<text x="{x:.2f}" y="{y:.2f}" font-size="{size}" fill="{color}" text-anchor="{anchor}" '+ f'font-weight="{weight}">{s}</text>\n')+++def main():+ rows = list(csv.DictReader(open(os.path.join(ROOT, "firmware", "led-order.csv"))))+ pts = [(int(r["LED"][3:]), r["letter"], float(r["x mm"]), float(r["y mm"])) for r in rows]+ pts.sort()+ assert [p[0] for p in pts] == list(range(1, 108))+ m = 8+ s = (f'<svg xmlns="http://www.w3.org/2000/svg" width="{(W + 2 * m) * 8:.0f}" height="{(H + 2 * m + 6) * 8:.0f}" '+ f'viewBox="{-m} {-m} {W + 2 * m} {H + 2 * m + 6}" font-family="DejaVu Sans, Arial, sans-serif">\n'+ f'<rect x="{-m}" y="{-m}" width="{W + 2 * m}" height="{H + 2 * m + 6}" fill="#ffffff"/>\n'+ f'<rect x="0" y="0" width="{W}" height="{H}" rx="2" fill="#1f5f3a" stroke="#333" stroke-width="0.3"/>\n')+ s += (f'<polyline points="{" ".join(f"{x:.2f},{y:.2f}" for _, _, x, y in pts)}" fill="none" '+ f'stroke="#ffb000" stroke-width="0.6" stroke-linejoin="round"/>\n')+ for n, letter, x, y in pts:+ s += (f'<rect x="{x - LED / 2}" y="{y - LED / 2}" width="{LED}" height="{LED}" rx="0.5" fill="#f4f4f4" '+ f'stroke="#111" stroke-width="0.15"/>' + text(x, y + 0.75, n, 2.1, "#111", weight="bold"))+ first, last = pts[0], pts[-1]+ s += text(first[2], first[3] - 4.3, "LED1 (data in)", 2.6, "#ffd84d", weight="bold")+ s += text(last[2], last[3] + 6.2, "LED107", 2.6, "#ffd84d", weight="bold")+ # ESP32 (x 40 mm, antenna at the bottom edge) and USB-C (x 90 mm), as placed on the V1 board+ for (x, y, w, h, label) in [(33.4, 53.4, 13.2, 16.6, "ESP32"), (85.5, 62.7, 9.0, 7.3, "USB-C")]:+ s += (f'<rect x="{x}" y="{y}" width="{w}" height="{h}" rx="0.8" fill="none" stroke="#9fe39f" '+ f'stroke-width="0.4" stroke-dasharray="1.5 1"/>' + text(x + w / 2, y + h / 2 + 1, label, 2.4, "#9fe39f"))+ s += text(W / 2, H + 5.5, "RITHESH Version 1: the 107 LEDs are one chain, LED1 to LED107. Numbers = order on the data "+ "line (WLED index = number - 1). The chain zig-zags inside each letter.", 2.5, "#222")+ os.makedirs(OUT, exist_ok=True)+ p = os.path.join(OUT, "led-order-v1.svg")+ open(p, "w").write(s + "</svg>\n")+ subprocess.run(["rsvg-convert", "-o", p[:-4] + ".png", p], check=True)+ print("wrote", p, "and", p[:-4] + ".png")+++if __name__ == "__main__":+ main()
v2/README.mdadded+21@@ -0,0 +1,21 @@+# RITHESH LED Display: Version 2 (rev 0.2, proposal)++> **Proposal only. Do not order.** Version 1 (the project root) is the approved prototype and is not changed here.+> The Version 2 LED (XL-2020RGBC-2812B) is **provisional** until its own WLED timing test passes.++Version 2 is a smaller board:+- **250 × 40 mm** instead of 300 × 70 mm+- 107 small **2 × 2 mm** LEDs at 5 mm spacing+- the electronics at the right end of the word++The ESP32, WLED, protection chips, buttons, test pads and mounting holes stay the same.++| File | What it is |+|------|------------|+| `requirements.md` | All Version 2 decisions, LED and timing checks, power numbers, risks, parts changes and the V1 vs V2 comparison |+| `hardware/rithesh_v2_fp.pretty/` | New LED footprint `LED_XL-2020RGBC-2812B` (from the datasheet's recommended pattern) |+| `hardware/layout-plan/` | Proposed LED pattern (1:1 SVG), board plan, electronics-area plan, footprint drawing |+| `hardware/tools/` | Scripts that generate the footprint, the plan drawings and the WLED maps (`make_v2_footprint.py`, `make_v2_plan.py`, `make_v2_ledmaps.py`) |+| `firmware/` | Version 2 WLED maps (`ledmap.json`, `ledmap1.json`) and `led-order.csv`. **The LED numbering changed**: V2 LED1 is at the right end (see `firmware/README.md`) |++Not created yet (waiting for approval): schematic, PCB placement and routing, manufacturing files, full V2 setup guide.
v2/firmware/README.mdadded+49@@ -0,0 +1,49 @@+# Version 2 WLED files (proposal)++> These files are for the **Version 2** board only. For Version 1 use the files in the project's root+> `firmware/` folder. **Do not mix them.**++## The LED numbering changed (the letters did not)++The word looks exactly the same: the same 107 LED positions in the same letter pattern. What changed is the+**order in which the data travels through the LEDs**:++| | Version 1 | Version 2 |+|---|---|---|+| Where the data enters | LED1 = top-left of the "R" | **LED1 = bottom-right of the last "H"**, next to the electronics |+| Where the chain ends | LED107 = bottom-right of the last "H" | LED107 = top-left of the "R" |+| Rule | – | **V2 LED n is where V1 LED (108 − n) was** (WLED index: V2 = 106 − V1) |+| LEDs per letter (V2 numbers) | – | H 1–17, S 18–32, E 33–50, H 51–67, T 68–78, I 79–89, R 90–107 |++Why: the electronics are at the right end of the V2 board. Starting the chain there keeps the LED data wire a few+millimetres long instead of about 21 cm.++What it means in WLED:+- **With the maps below:** 2D effects and left-to-right effects look the same as on Version 1.+- **Without the maps:** plain 1D effects (for example **Chase**) run **from the right end ("H") to the left+ ("R")**. Tick **Reverse** in the segment settings if you want them to start at the "R".++## Files++| File | What it is |+|------|------------|+| `ledmap.json` | 2D map, 41 × 7 grid, named "RITHESH V2 41x7" |+| `ledmap1.json` | 1D map, left to right, named "RITHESH V2 left-to-right" |+| `led-order.csv` | LED1–LED107 with letter, row, column, position on the V2 board, and the V1 LED at the same spot |++WLED only loads a map with the exact names `ledmap.json` / `ledmap1.json`, so the files keep those names. The+`v2/` folder keeps them apart from Version 1's. Upload them the same way as in the Version 1 guide+(`firmware/README.md`, section 5).++All three files are generated by `v2/hardware/tools/make_v2_ledmaps.py`. It builds each map twice (from the V2+positions, and by converting V1's map) and stops if the two disagree.++## Settings that differ from Version 1++| Setting | Version 2 |+|---------|-----------|+| LED current (LED Preferences) | **Custom: 16 mA** per LED. The default 55 mA would dim full white for no reason |+| Maximum PSU Current | 2000 mA (same as V1). With 16 mA per LED, WLED does not dim full white |+| Data GPIO, length, button | same as V1: GPIO10, 107 LEDs, button on GPIO5 |++The Version 2 LED (XL-2020RGBC-2812B) is **provisional** until its own timing test passes (`v2/requirements.md`).
v2/firmware/led-order.csvadded+108@@ -0,0 +1,108 @@+WLED index (0-based),LED,letter,row (0=top),column (0=left),x mm,y mm,V1 LED at the same spot +0,LED1,H,6,40,209.1,35.0,LED107 +1,LED2,H,6,36,189.1,35.0,LED106 +2,LED3,H,5,36,189.1,30.0,LED105 +3,LED4,H,5,40,209.1,30.0,LED104 +4,LED5,H,4,40,209.1,25.0,LED103 +5,LED6,H,4,36,189.1,25.0,LED102 +6,LED7,H,3,36,189.1,20.0,LED101 +7,LED8,H,3,37,194.1,20.0,LED100 +8,LED9,H,3,38,199.1,20.0,LED99 +9,LED10,H,3,39,204.1,20.0,LED98 +10,LED11,H,3,40,209.1,20.0,LED97 +11,LED12,H,2,40,209.1,15.0,LED96 +12,LED13,H,2,36,189.1,15.0,LED95 +13,LED14,H,1,36,189.1,10.0,LED94 +14,LED15,H,1,40,209.1,10.0,LED93 +15,LED16,H,0,40,209.1,5.0,LED92 +16,LED17,H,0,36,189.1,5.0,LED91 +17,LED18,S,0,34,179.1,5.0,LED90 +18,LED19,S,0,33,174.1,5.0,LED89 +19,LED20,S,0,32,169.1,5.0,LED88 +20,LED21,S,0,31,164.1,5.0,LED87 +21,LED22,S,1,30,159.1,10.0,LED86 +22,LED23,S,2,30,159.1,15.0,LED85 +23,LED24,S,3,31,164.1,20.0,LED84 +24,LED25,S,3,32,169.1,20.0,LED83 +25,LED26,S,3,33,174.1,20.0,LED82 +26,LED27,S,4,34,179.1,25.0,LED81 +27,LED28,S,5,34,179.1,30.0,LED80 +28,LED29,S,6,33,174.1,35.0,LED79 +29,LED30,S,6,32,169.1,35.0,LED78 +30,LED31,S,6,31,164.1,35.0,LED77 +31,LED32,S,6,30,159.1,35.0,LED76 +32,LED33,E,6,28,149.1,35.0,LED75 +33,LED34,E,6,27,144.1,35.0,LED74 +34,LED35,E,6,26,139.1,35.0,LED73 +35,LED36,E,6,25,134.1,35.0,LED72 +36,LED37,E,6,24,129.1,35.0,LED71 +37,LED38,E,5,24,129.1,30.0,LED70 +38,LED39,E,4,24,129.1,25.0,LED69 +39,LED40,E,3,24,129.1,20.0,LED68 +40,LED41,E,3,25,134.1,20.0,LED67 +41,LED42,E,3,26,139.1,20.0,LED66 +42,LED43,E,3,27,144.1,20.0,LED65 +43,LED44,E,2,24,129.1,15.0,LED64 +44,LED45,E,1,24,129.1,10.0,LED63 +45,LED46,E,0,28,149.1,5.0,LED62 +46,LED47,E,0,27,144.1,5.0,LED61 +47,LED48,E,0,26,139.1,5.0,LED60 +48,LED49,E,0,25,134.1,5.0,LED59 +49,LED50,E,0,24,129.1,5.0,LED58 +50,LED51,H,0,22,119.1,5.0,LED57 +51,LED52,H,0,18,99.1,5.0,LED56 +52,LED53,H,1,18,99.1,10.0,LED55 +53,LED54,H,1,22,119.1,10.0,LED54 +54,LED55,H,2,22,119.1,15.0,LED53 +55,LED56,H,2,18,99.1,15.0,LED52 +56,LED57,H,3,18,99.1,20.0,LED51 +57,LED58,H,3,19,104.1,20.0,LED50 +58,LED59,H,3,20,109.1,20.0,LED49 +59,LED60,H,3,21,114.1,20.0,LED48 +60,LED61,H,3,22,119.1,20.0,LED47 +61,LED62,H,4,22,119.1,25.0,LED46 +62,LED63,H,4,18,99.1,25.0,LED45 +63,LED64,H,5,18,99.1,30.0,LED44 +64,LED65,H,5,22,119.1,30.0,LED43 +65,LED66,H,6,22,119.1,35.0,LED42 +66,LED67,H,6,18,99.1,35.0,LED41 +67,LED68,T,6,14,79.1,35.0,LED40 +68,LED69,T,5,14,79.1,30.0,LED39 +69,LED70,T,4,14,79.1,25.0,LED38 +70,LED71,T,3,14,79.1,20.0,LED37 +71,LED72,T,2,14,79.1,15.0,LED36 +72,LED73,T,1,14,79.1,10.0,LED35 +73,LED74,T,0,16,89.1,5.0,LED34 +74,LED75,T,0,15,84.1,5.0,LED33 +75,LED76,T,0,14,79.1,5.0,LED32 +76,LED77,T,0,13,74.1,5.0,LED31 +77,LED78,T,0,12,69.1,5.0,LED30 +78,LED79,I,0,9,54.1,5.0,LED29 +79,LED80,I,0,8,49.1,5.0,LED28 +80,LED81,I,0,7,44.1,5.0,LED27 +81,LED82,I,1,8,49.1,10.0,LED26 +82,LED83,I,2,8,49.1,15.0,LED25 +83,LED84,I,3,8,49.1,20.0,LED24 +84,LED85,I,4,8,49.1,25.0,LED23 +85,LED86,I,5,8,49.1,30.0,LED22 +86,LED87,I,6,9,54.1,35.0,LED21 +87,LED88,I,6,8,49.1,35.0,LED20 +88,LED89,I,6,7,44.1,35.0,LED19 +89,LED90,R,6,4,29.1,35.0,LED18 +90,LED91,R,6,0,9.1,35.0,LED17 +91,LED92,R,5,0,9.1,30.0,LED16 +92,LED93,R,5,3,24.1,30.0,LED15 +93,LED94,R,4,2,19.1,25.0,LED14 +94,LED95,R,4,0,9.1,25.0,LED13 +95,LED96,R,3,0,9.1,20.0,LED12 +96,LED97,R,3,1,14.1,20.0,LED11 +97,LED98,R,3,2,19.1,20.0,LED10 +98,LED99,R,3,3,24.1,20.0,LED9 +99,LED100,R,2,4,29.1,15.0,LED8 +100,LED101,R,2,0,9.1,15.0,LED7 +101,LED102,R,1,0,9.1,10.0,LED6 +102,LED103,R,1,4,29.1,10.0,LED5 +103,LED104,R,0,3,24.1,5.0,LED4 +104,LED105,R,0,2,19.1,5.0,LED3 +105,LED106,R,0,1,14.1,5.0,LED2 +106,LED107,R,0,0,9.1,5.0,LED1
v2/firmware/ledmap.jsonadded+1@@ -0,0 +1 @@+{"n":"RITHESH V2 41x7","width":41,"height":7,"map":[106,105,104,103,-1,-1,-1,80,79,78,-1,-1,77,76,75,74,73,-1,51,-1,-1,-1,50,-1,49,48,47,46,45,-1,-1,20,19,18,17,-1,16,-1,-1,-1,15,101,-1,-1,-1,102,-1,-1,-1,81,-1,-1,-1,-1,-1,72,-1,-1,-1,52,-1,-1,-1,53,-1,44,-1,-1,-1,-1,-1,21,-1,-1,-1,-1,-1,13,-1,-1,-1,14,100,-1,-1,-1,99,-1,-1,-1,82,-1,-1,-1,-1,-1,71,-1,-1,-1,55,-1,-1,-1,54,-1,43,-1,-1,-1,-1,-1,22,-1,-1,-1,-1,-1,12,-1,-1,-1,11,95,96,97,98,-1,-1,-1,-1,83,-1,-1,-1,-1,-1,70,-1,-1,-1,56,57,58,59,60,-1,39,40,41,42,-1,-1,-1,23,24,25,-1,-1,6,7,8,9,10,94,-1,93,-1,-1,-1,-1,-1,84,-1,-1,-1,-1,-1,69,-1,-1,-1,62,-1,-1,-1,61,-1,38,-1,-1,-1,-1,-1,-1,-1,-1,-1,26,-1,5,-1,-1,-1,4,91,-1,-1,92,-1,-1,-1,-1,85,-1,-1,-1,-1,-1,68,-1,-1,-1,63,-1,-1,-1,64,-1,37,-1,-1,-1,-1,-1,-1,-1,-1,-1,27,-1,2,-1,-1,-1,3,90,-1,-1,-1,89,-1,-1,88,87,86,-1,-1,-1,-1,67,-1,-1,-1,66,-1,-1,-1,65,-1,36,35,34,33,32,-1,31,30,29,28,-1,-1,1,-1,-1,-1,0]}
v2/firmware/ledmap1.jsonadded+1@@ -0,0 +1 @@+{"n":"RITHESH V2 left-to-right","map":[106,101,100,95,94,91,90,105,96,104,97,93,103,98,92,102,99,89,80,88,79,81,82,83,84,85,87,78,86,77,76,75,72,71,70,69,68,67,74,73,51,52,55,56,62,63,66,57,58,59,50,53,54,60,61,64,65,49,44,43,39,38,37,36,48,40,35,47,41,34,46,42,33,45,32,21,22,31,20,23,30,19,24,29,18,25,28,17,26,27,16,13,12,6,5,2,1,7,8,9,15,14,11,10,4,3,0]}
v2/hardware/layout-plan/board-plan-v2.pngadded⋯ 1 unchanged line ⋯
v2/hardware/layout-plan/board-plan-v2.svgadded+189@@ -0,0 +1,189 @@+<svg xmlns="http://www.w3.org/2000/svg" width="262.0mm" height="58.0mm" viewBox="-6 -6 262.0 58.0" font-family="DejaVu Sans, Arial, sans-serif">+<rect x="-6" y="-6" width="262.0" height="58.0" fill="#0d1117"/>+<defs><clipPath id="board"><rect x="0" y="0" width="250.0" height="40.0" rx="2.0"/></clipPath><marker id="arr" markerWidth="4" markerHeight="4" refX="3" refY="2" orient="auto"><path d="M0,0 L4,2 L0,4 z" fill="#ff4040"/></marker><marker id="arr2" markerWidth="4" markerHeight="4" refX="3" refY="2" orient="auto"><path d="M0,0 L4,2 L0,4 z" fill="#ffb000"/></marker></defs>+<rect x="0" y="0" width="250.0" height="40.0" rx="2.0" fill="#1f5f3a" stroke="#e8e2c8" stroke-width="0.25"/>+<circle cx="3.2" cy="3.2" r="3.5" fill="none" stroke="#ff6b6b" stroke-width="0.15" stroke-dasharray="0.6 0.4"/><circle cx="3.2" cy="3.2" r="1.6" fill="#0d1117" stroke="#b33" stroke-width="0.2"/>+<circle cx="3.2" cy="36.8" r="3.5" fill="none" stroke="#ff6b6b" stroke-width="0.15" stroke-dasharray="0.6 0.4"/><circle cx="3.2" cy="36.8" r="1.6" fill="#0d1117" stroke="#b33" stroke-width="0.2"/>+<circle cx="215.0" cy="3.2" r="3.5" fill="none" stroke="#ff6b6b" stroke-width="0.15" stroke-dasharray="0.6 0.4"/><circle cx="215.0" cy="3.2" r="1.6" fill="#0d1117" stroke="#b33" stroke-width="0.2"/>+<circle cx="215.0" cy="36.8" r="3.5" fill="none" stroke="#ff6b6b" stroke-width="0.15" stroke-dasharray="0.6 0.4"/><circle cx="215.0" cy="36.8" r="1.6" fill="#0d1117" stroke="#b33" stroke-width="0.2"/>+<rect x="7.75" y="3.75" width="202.70" height="32.50" fill="#2d7a4f" fill-opacity="0.6" stroke="#9fe39f" stroke-width="0.12" stroke-dasharray="0.8 0.5"/>+<rect x="208.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="188.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="188.1" y="29.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="208.1" y="29.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="208.1" y="24.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="188.1" y="24.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="188.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="193.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="198.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="203.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="208.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="208.1" y="14.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="188.1" y="14.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="188.1" y="9.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="208.1" y="9.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="208.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="188.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="178.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="173.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="168.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="163.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="158.1" y="9.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="158.1" y="14.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="163.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="168.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="173.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="178.1" y="24.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="178.1" y="29.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="173.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="168.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="163.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="158.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="148.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="143.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="138.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="133.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="128.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="128.1" y="29.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="128.1" y="24.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="128.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="133.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="138.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="143.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="128.1" y="14.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="128.1" y="9.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="148.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="143.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="138.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="133.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="128.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="118.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="98.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="98.1" y="9.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="118.1" y="9.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="118.1" y="14.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="98.1" y="14.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="98.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="103.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="108.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="113.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="118.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="118.1" y="24.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="98.1" y="24.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="98.1" y="29.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="118.1" y="29.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="118.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="98.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="78.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="78.1" y="29.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="78.1" y="24.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="78.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="78.1" y="14.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="78.1" y="9.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="88.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="83.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="78.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="73.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="68.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="53.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="48.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="43.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="48.1" y="9.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="48.1" y="14.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="48.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="48.1" y="24.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="48.1" y="29.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="53.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="48.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="43.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="28.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="8.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="8.1" y="29.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="23.1" y="29.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="18.1" y="24.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="8.1" y="24.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="8.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="13.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="18.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="23.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="28.1" y="14.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="8.1" y="14.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="8.1" y="9.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="28.1" y="9.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="23.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="18.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="13.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<rect x="8.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/>+<circle cx="36.0" cy="20.0" r="3.15" fill="#c89bff" stroke="#000" stroke-width="0.12"/><text x="36.00" y="20.40" font-size="1.1" fill="#111" text-anchor="middle" font-weight="normal">C302</text>+<text x="36.00" y="25.20" font-size="1.0" fill="#fff" text-anchor="middle" font-weight="normal">220uF (fitted, far end)</text>+<circle cx="93.0" cy="22.0" r="3.15" fill="#c89bff" stroke="#000" stroke-width="0.12"/><text x="93.00" y="22.40" font-size="1.1" fill="#111" text-anchor="middle" font-weight="normal">C303</text>+<text x="93.00" y="27.20" font-size="1.0" fill="#fff" text-anchor="middle" font-weight="normal">220uF spare (DNP)</text>+<rect x="222.40" y="0.80" width="10.60" height="9.60" fill="#9fe39f" fill-opacity="0.35" stroke="#000" stroke-width="0.12" stroke-dasharray="0.8 0.5"/>+<rect x="222.40" y="27.20" width="5.20" height="5.60" fill="#9fe39f" fill-opacity="0.35" stroke="#000" stroke-width="0.12" stroke-dasharray="0.8 0.5"/>+<rect x="215.50" y="25.20" width="2.20" height="3.40" fill="#9fe39f" fill-opacity="0.35" stroke="#000" stroke-width="0.12" stroke-dasharray="0.8 0.5"/>+<rect x="233.40" y="2.00" width="11.20" height="13.20" fill="#d9d9d9" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="239.00" y="8.38" font-size="1.0" fill="#111" text-anchor="middle" font-weight="normal">ESP32-C3</text>+<text x="239.00" y="9.52" font-size="1.0" fill="#111" text-anchor="middle" font-weight="normal">MINI-1 (U4)</text>+<rect x="244.60" y="2.00" width="5.40" height="13.20" fill="#d9d9d9" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<rect x="232.68" y="31.64" width="10.64" height="9.42" fill="#d9d9d9" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="238.00" y="36.70" font-size="1.0" fill="#111" text-anchor="middle" font-weight="normal">USB-C (J1)</text>+<rect x="232.50" y="27.45" width="7.00" height="3.50" fill="#d9d9d9" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="236.00" y="29.07" font-size="0.6" fill="#111" text-anchor="middle" font-weight="normal">D1</text>+<text x="236.00" y="29.75" font-size="0.6" fill="#111" text-anchor="middle" font-weight="normal">SMAJ5.0A</text>+<rect x="239.70" y="27.20" width="3.40" height="3.40" fill="#d9d9d9" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="241.40" y="28.77" font-size="0.6" fill="#111" text-anchor="middle" font-weight="normal">U1</text>+<text x="241.40" y="29.45" font-size="0.6" fill="#111" text-anchor="middle" font-weight="normal">USBLC6</text>+<rect x="227.90" y="27.75" width="3.20" height="2.50" fill="#d9d9d9" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="229.50" y="28.87" font-size="0.6" fill="#111" text-anchor="middle" font-weight="normal">U2</text>+<text x="229.50" y="29.55" font-size="0.6" fill="#111" text-anchor="middle" font-weight="normal">eFuse</text>+<circle cx="227.05" cy="22.2" r="3.15" fill="#c89bff" stroke="#000" stroke-width="0.12"/><text x="227.05" y="22.60" font-size="1.0" fill="#111" text-anchor="middle" font-weight="normal">C301</text>+<text x="227.05" y="23.70" font-size="0.8" fill="#111" text-anchor="middle" font-weight="normal">220uF</text>+<rect x="225.20" y="10.75" width="2.66" height="2.50" fill="#d9d9d9" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="226.53" y="11.87" font-size="0.6" fill="#111" text-anchor="middle" font-weight="normal">U3</text>+<text x="226.53" y="12.56" font-size="0.6" fill="#111" text-anchor="middle" font-weight="normal">LDO</text>+<rect x="211.90" y="27.20" width="3.40" height="4.10" fill="#d9d9d9" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="213.60" y="29.12" font-size="0.6" fill="#111" text-anchor="middle" font-weight="normal">U5</text>+<text x="213.60" y="29.80" font-size="0.6" fill="#111" text-anchor="middle" font-weight="normal">shifter</text>+<rect x="211.40" y="8.25" width="6.00" height="3.50" fill="#8fd0ff" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="214.40" y="10.31" font-size="0.9" fill="#111" text-anchor="middle" font-weight="normal">SW1</text>+<text x="217.90" y="10.45" font-size="1.2" fill="#fff" text-anchor="start" font-weight="bold">RESET</text>+<rect x="211.40" y="14.75" width="6.00" height="3.50" fill="#8fd0ff" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="214.40" y="16.82" font-size="0.9" fill="#111" text-anchor="middle" font-weight="normal">SW2</text>+<text x="217.90" y="16.95" font-size="1.2" fill="#fff" text-anchor="start" font-weight="bold">ON/OFF</text>+<rect x="211.40" y="21.25" width="6.00" height="3.50" fill="#8fd0ff" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="214.40" y="23.32" font-size="0.9" fill="#111" text-anchor="middle" font-weight="normal">SW3</text>+<text x="217.90" y="23.45" font-size="1.2" fill="#fff" text-anchor="start" font-weight="bold">BOOT</text>+<circle cx="216.6" cy="30.0" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="216.60" y="31.50" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">LED_DIN</text>++<circle cx="220.3" cy="34.6" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="220.30" y="33.55" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">5V_IN</text>++<circle cx="223.9" cy="34.6" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="223.90" y="33.55" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">5V_OUT</text>++<circle cx="227.5" cy="34.6" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="227.50" y="33.55" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">FLT</text>++<circle cx="231.10000000000002" cy="34.6" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="231.10" y="33.55" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">ILM</text>++<circle cx="220.3" cy="38.0" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="220.30" y="39.50" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">3V3</text>++<circle cx="223.9" cy="38.0" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="223.90" y="39.50" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">GND</text>++<circle cx="227.5" cy="38.0" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="227.50" y="39.50" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">RX</text>++<circle cx="231.10000000000002" cy="38.0" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="231.10" y="39.50" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">TX</text>++<circle cx="246.8" cy="35.5" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="246.80" y="37.00" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">GND</text>++<rect x="244.60" y="2.00" width="5.40" height="13.20" fill="#4aa3ff" fill-opacity="0.55" stroke="#000" stroke-width="0.12"/>+<text x="247.30" y="8.42" font-size="0.8" fill="#111" text-anchor="middle" font-weight="normal">antenna</text>+<text x="247.30" y="9.34" font-size="0.8" fill="#111" text-anchor="middle" font-weight="normal">keep-out</text>+<rect x="244.00" y="15.20" width="6.00" height="15.50" fill="#4aa3ff" fill-opacity="0.3" stroke="#000" stroke-width="0.12" stroke-dasharray="0.8 0.5"/>+<text x="247.00" y="21.49" font-size="0.75" fill="#111" text-anchor="middle" font-weight="normal">antenna</text>+<text x="247.00" y="22.35" font-size="0.75" fill="#111" text-anchor="middle" font-weight="normal">clearance</text>+<text x="247.00" y="23.21" font-size="0.75" fill="#111" text-anchor="middle" font-weight="normal">15.5 mm</text>+<text x="247.00" y="24.07" font-size="0.75" fill="#111" text-anchor="middle" font-weight="normal">no copper</text>+<text x="247.00" y="24.94" font-size="0.75" fill="#111" text-anchor="middle" font-weight="normal">both layers</text>+<g clip-path="url(#board)"><rect x="244.00" y="0.00" width="6.00" height="2.00" fill="#4aa3ff" fill-opacity="0.3" stroke="#000" stroke-width="0.12" stroke-dasharray="0.8 0.5"/>+</g>+<path d="M 223.5 25.5 L 210.5 25.5" stroke="#ff4040" stroke-width="0.6" fill="none" marker-end="url(#arr)"/><text x="216.70" y="26.90" font-size="0.7" fill="#ff8080" text-anchor="middle" font-weight="normal">5 V feed to LEDs</text>+<path d="M 211.9 31.3 L 210.2 34.2" stroke="#ffb000" stroke-width="0.35" fill="none" marker-end="url(#arr2)"/><text x="125.00" y="44.20" font-size="1.4" fill="#ccc" text-anchor="middle" font-weight="normal">V2 board plan (proposal): 250 x 40 mm, 2 layers - LED area (dashed green), 4 x M3 holes with 7 mm keep-outs (red), electronics at the right end, antenna at the top-right corner</text>+</svg>
v2/hardware/layout-plan/electronics-area-v2.pngadded⋯ 1 unchanged line ⋯
v2/hardware/layout-plan/electronics-area-v2.svgadded+100@@ -0,0 +1,100 @@+<svg xmlns="http://www.w3.org/2000/svg" width="50.0mm" height="49.0mm" viewBox="203.0 -3 50.0 49.0" font-family="DejaVu Sans, Arial, sans-serif">+<rect x="203.0" y="-3" width="50.0" height="49.0" fill="#0d1117"/>+<defs><clipPath id="board"><rect x="0" y="0" width="250.0" height="40.0" rx="2.0"/></clipPath><marker id="arr" markerWidth="4" markerHeight="4" refX="3" refY="2" orient="auto"><path d="M0,0 L4,2 L0,4 z" fill="#ff4040"/></marker><marker id="arr2" markerWidth="4" markerHeight="4" refX="3" refY="2" orient="auto"><path d="M0,0 L4,2 L0,4 z" fill="#ffb000"/></marker></defs>+<rect x="0" y="0" width="250.0" height="40.0" rx="2.0" fill="#1f5f3a" stroke="#e8e2c8" stroke-width="0.25"/>+<circle cx="3.2" cy="3.2" r="3.5" fill="none" stroke="#ff6b6b" stroke-width="0.15" stroke-dasharray="0.6 0.4"/><circle cx="3.2" cy="3.2" r="1.6" fill="#0d1117" stroke="#b33" stroke-width="0.2"/>+<circle cx="3.2" cy="36.8" r="3.5" fill="none" stroke="#ff6b6b" stroke-width="0.15" stroke-dasharray="0.6 0.4"/><circle cx="3.2" cy="36.8" r="1.6" fill="#0d1117" stroke="#b33" stroke-width="0.2"/>+<circle cx="215.0" cy="3.2" r="3.5" fill="none" stroke="#ff6b6b" stroke-width="0.15" stroke-dasharray="0.6 0.4"/><circle cx="215.0" cy="3.2" r="1.6" fill="#0d1117" stroke="#b33" stroke-width="0.2"/>+<circle cx="215.0" cy="36.8" r="3.5" fill="none" stroke="#ff6b6b" stroke-width="0.15" stroke-dasharray="0.6 0.4"/><circle cx="215.0" cy="36.8" r="1.6" fill="#0d1117" stroke="#b33" stroke-width="0.2"/>+<polyline points="209.10,35.00 209.10,30.00 209.10,25.00 194.10,20.00 199.10,20.00 204.10,20.00 209.10,20.00 209.10,15.00 209.10,10.00 209.10,5.00" fill="none" stroke="#ffb000" stroke-width="0.25" stroke-opacity="0.8"/>+<rect x="208.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="209.10" y="35.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">1</text>++<rect x="208.1" y="29.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="209.10" y="30.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">4</text>++<rect x="208.1" y="24.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="209.10" y="25.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">5</text>++<rect x="193.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="194.10" y="20.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">8</text>++<rect x="198.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="199.10" y="20.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">9</text>++<rect x="203.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="204.10" y="20.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">10</text>++<rect x="208.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="209.10" y="20.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">11</text>++<rect x="208.1" y="14.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="209.10" y="15.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">12</text>++<rect x="208.1" y="9.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="209.10" y="10.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">15</text>++<rect x="208.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="209.10" y="5.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">16</text>++<rect x="222.40" y="0.80" width="10.60" height="9.60" fill="#9fe39f" fill-opacity="0.35" stroke="#000" stroke-width="0.12" stroke-dasharray="0.8 0.5"/>+<rect x="222.40" y="27.20" width="5.20" height="5.60" fill="#9fe39f" fill-opacity="0.35" stroke="#000" stroke-width="0.12" stroke-dasharray="0.8 0.5"/>+<rect x="215.50" y="25.20" width="2.20" height="3.40" fill="#9fe39f" fill-opacity="0.35" stroke="#000" stroke-width="0.12" stroke-dasharray="0.8 0.5"/>+<rect x="233.40" y="2.00" width="11.20" height="13.20" fill="#d9d9d9" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="239.00" y="8.38" font-size="1.0" fill="#111" text-anchor="middle" font-weight="normal">ESP32-C3</text>+<text x="239.00" y="9.52" font-size="1.0" fill="#111" text-anchor="middle" font-weight="normal">MINI-1 (U4)</text>+<rect x="244.60" y="2.00" width="5.40" height="13.20" fill="#d9d9d9" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<rect x="232.68" y="31.64" width="10.64" height="9.42" fill="#d9d9d9" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="238.00" y="36.70" font-size="1.0" fill="#111" text-anchor="middle" font-weight="normal">USB-C (J1)</text>+<rect x="232.50" y="27.45" width="7.00" height="3.50" fill="#d9d9d9" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="236.00" y="29.07" font-size="0.6" fill="#111" text-anchor="middle" font-weight="normal">D1</text>+<text x="236.00" y="29.75" font-size="0.6" fill="#111" text-anchor="middle" font-weight="normal">SMAJ5.0A</text>+<rect x="239.70" y="27.20" width="3.40" height="3.40" fill="#d9d9d9" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="241.40" y="28.77" font-size="0.6" fill="#111" text-anchor="middle" font-weight="normal">U1</text>+<text x="241.40" y="29.45" font-size="0.6" fill="#111" text-anchor="middle" font-weight="normal">USBLC6</text>+<rect x="227.90" y="27.75" width="3.20" height="2.50" fill="#d9d9d9" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="229.50" y="28.87" font-size="0.6" fill="#111" text-anchor="middle" font-weight="normal">U2</text>+<text x="229.50" y="29.55" font-size="0.6" fill="#111" text-anchor="middle" font-weight="normal">eFuse</text>+<circle cx="227.05" cy="22.2" r="3.15" fill="#c89bff" stroke="#000" stroke-width="0.12"/><text x="227.05" y="22.60" font-size="1.0" fill="#111" text-anchor="middle" font-weight="normal">C301</text>+<text x="227.05" y="23.70" font-size="0.8" fill="#111" text-anchor="middle" font-weight="normal">220uF</text>+<rect x="225.20" y="10.75" width="2.66" height="2.50" fill="#d9d9d9" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="226.53" y="11.87" font-size="0.6" fill="#111" text-anchor="middle" font-weight="normal">U3</text>+<text x="226.53" y="12.56" font-size="0.6" fill="#111" text-anchor="middle" font-weight="normal">LDO</text>+<rect x="211.90" y="27.20" width="3.40" height="4.10" fill="#d9d9d9" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="213.60" y="29.12" font-size="0.6" fill="#111" text-anchor="middle" font-weight="normal">U5</text>+<text x="213.60" y="29.80" font-size="0.6" fill="#111" text-anchor="middle" font-weight="normal">shifter</text>+<rect x="211.40" y="8.25" width="6.00" height="3.50" fill="#8fd0ff" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="214.40" y="10.31" font-size="0.9" fill="#111" text-anchor="middle" font-weight="normal">SW1</text>+<text x="217.90" y="10.45" font-size="1.2" fill="#fff" text-anchor="start" font-weight="bold">RESET</text>+<rect x="211.40" y="14.75" width="6.00" height="3.50" fill="#8fd0ff" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="214.40" y="16.82" font-size="0.9" fill="#111" text-anchor="middle" font-weight="normal">SW2</text>+<text x="217.90" y="16.95" font-size="1.2" fill="#fff" text-anchor="start" font-weight="bold">ON/OFF</text>+<rect x="211.40" y="21.25" width="6.00" height="3.50" fill="#8fd0ff" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="214.40" y="23.32" font-size="0.9" fill="#111" text-anchor="middle" font-weight="normal">SW3</text>+<text x="217.90" y="23.45" font-size="1.2" fill="#fff" text-anchor="start" font-weight="bold">BOOT</text>+<circle cx="216.6" cy="30.0" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="216.60" y="31.50" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">LED_DIN</text>++<circle cx="220.3" cy="34.6" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="220.30" y="33.55" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">5V_IN</text>++<circle cx="223.9" cy="34.6" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="223.90" y="33.55" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">5V_OUT</text>++<circle cx="227.5" cy="34.6" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="227.50" y="33.55" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">FLT</text>++<circle cx="231.10000000000002" cy="34.6" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="231.10" y="33.55" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">ILM</text>++<circle cx="220.3" cy="38.0" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="220.30" y="39.50" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">3V3</text>++<circle cx="223.9" cy="38.0" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="223.90" y="39.50" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">GND</text>++<circle cx="227.5" cy="38.0" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="227.50" y="39.50" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">RX</text>++<circle cx="231.10000000000002" cy="38.0" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="231.10" y="39.50" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">TX</text>++<circle cx="246.8" cy="35.5" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="246.80" y="37.00" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">GND</text>++<rect x="244.60" y="2.00" width="5.40" height="13.20" fill="#4aa3ff" fill-opacity="0.55" stroke="#000" stroke-width="0.12"/>+<text x="247.30" y="8.42" font-size="0.8" fill="#111" text-anchor="middle" font-weight="normal">antenna</text>+<text x="247.30" y="9.34" font-size="0.8" fill="#111" text-anchor="middle" font-weight="normal">keep-out</text>+<rect x="244.00" y="15.20" width="6.00" height="15.50" fill="#4aa3ff" fill-opacity="0.3" stroke="#000" stroke-width="0.12" stroke-dasharray="0.8 0.5"/>+<text x="247.00" y="21.49" font-size="0.75" fill="#111" text-anchor="middle" font-weight="normal">antenna</text>+<text x="247.00" y="22.35" font-size="0.75" fill="#111" text-anchor="middle" font-weight="normal">clearance</text>+<text x="247.00" y="23.21" font-size="0.75" fill="#111" text-anchor="middle" font-weight="normal">15.5 mm</text>+<text x="247.00" y="24.07" font-size="0.75" fill="#111" text-anchor="middle" font-weight="normal">no copper</text>+<text x="247.00" y="24.94" font-size="0.75" fill="#111" text-anchor="middle" font-weight="normal">both layers</text>+<g clip-path="url(#board)"><rect x="244.00" y="0.00" width="6.00" height="2.00" fill="#4aa3ff" fill-opacity="0.3" stroke="#000" stroke-width="0.12" stroke-dasharray="0.8 0.5"/>+</g>+<path d="M 223.5 25.5 L 210.5 25.5" stroke="#ff4040" stroke-width="0.6" fill="none" marker-end="url(#arr)"/><text x="216.70" y="26.90" font-size="0.7" fill="#ff8080" text-anchor="middle" font-weight="normal">5 V feed to LEDs</text>+<path d="M 211.9 31.3 L 210.2 34.2" stroke="#ffb000" stroke-width="0.35" fill="none" marker-end="url(#arr2)"/><g stroke="#ffd84d" stroke-width="0.15"><line x1="211.0" y1="43.2" x2="250.0" y2="43.2"/><line x1="211.0" y1="42.2" x2="211.0" y2="44.2"/><line x1="250.0" y1="42.2" x2="250.0" y2="44.2"/></g>+<text x="230.50" y="42.60" font-size="1.2" fill="#ffd84d" text-anchor="middle" font-weight="normal">electronics area 39.0 mm</text>+<text x="228.00" y="45.40" font-size="0.95" fill="#ccc" text-anchor="middle" font-weight="normal">Plan only: part outlines are courtyards; small passives fit in the dashed green zones. Final positions come at placement.</text>+</svg>
v2/hardware/layout-plan/footprint-LED_XL-2020RGBC-2812B.pngadded⋯ 1 unchanged line ⋯
v2/hardware/layout-plan/footprint-LED_XL-2020RGBC-2812B.svgadded+28@@ -0,0 +1,28 @@+<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="760" viewBox="-25 -19 50 38" font-family="DejaVu Sans, Arial, sans-serif">+<rect x="-25" y="-19" width="50" height="38" fill="#0d1117"/>+<rect x="-10.0" y="-10.0" width="20.0" height="20.0" fill="none" stroke="#888" stroke-width="0.12" stroke-dasharray="0.6 0.4"/><rect x="-13.5" y="-12.5" width="27.0" height="25.0" fill="none" stroke="#ff66cc" stroke-width="0.1"/><rect x="2.5000000000000004" y="2.5" width="8.5" height="7.5" fill="#c8323c"/><text x="6.75" y="7.15" font-size="2.6" fill="#fff" text-anchor="middle" font-weight="bold">1</text>+<text x="13.84" y="6.85" font-size="1.9" fill="#ffd84d" text-anchor="middle" font-weight="bold">DO</text>+<rect x="2.5000000000000004" y="-10.0" width="8.5" height="7.5" fill="#c8323c"/><text x="6.75" y="-5.35" font-size="2.6" fill="#fff" text-anchor="middle" font-weight="bold">2</text>+<text x="13.84" y="-5.65" font-size="1.9" fill="#ffd84d" text-anchor="middle" font-weight="bold">GND</text>+<rect x="-11.0" y="-10.0" width="8.5" height="7.5" fill="#c8323c"/><text x="-6.75" y="-5.35" font-size="2.6" fill="#fff" text-anchor="middle" font-weight="bold">3</text>+<text x="-13.84" y="-5.65" font-size="1.9" fill="#ffd84d" text-anchor="middle" font-weight="bold">DI</text>+<rect x="-11.0" y="2.5" width="8.5" height="7.5" fill="#c8323c"/><text x="-6.75" y="7.15" font-size="2.6" fill="#fff" text-anchor="middle" font-weight="bold">4</text>+<text x="-13.84" y="6.85" font-size="1.9" fill="#ffd84d" text-anchor="middle" font-weight="bold">VDD</text>+<circle cx="5.0" cy="7.0" r="1.4000000000000001" fill="none" stroke="#aaa" stroke-width="0.15"/><circle cx="16.0" cy="12.5" r="1.5" fill="#fff"/><line x1="-12.5" y1="-12.5" x2="12.5" y2="-12.5" stroke="#fff" stroke-width="1.5" stroke-opacity="0.8"/><line x1="-12.5" y1="12.5" x2="12.5" y2="12.5" stroke="#fff" stroke-width="1.5" stroke-opacity="0.8"/><g stroke="#ffd84d" stroke-width="0.15"><line x1="-11.0" y1="-15.0" x2="11.0" y2="-15.0"/><line x1="-11.0" y1="-16.0" x2="-11.0" y2="-14.0"/><line x1="11.0" y1="-16.0" x2="11.0" y2="-14.0"/></g>+<text x="0.00" y="-15.60" font-size="1.3" fill="#ffd84d" text-anchor="middle" font-weight="normal">2.20 (pattern)</text>+<g stroke="#ffd84d" stroke-width="0.15"><line x1="-10.0" y1="15.5" x2="10.0" y2="15.5"/><line x1="-10.0" y1="14.5" x2="-10.0" y2="16.5"/><line x1="10.0" y1="14.5" x2="10.0" y2="16.5"/></g>+<text x="0.00" y="14.90" font-size="1.3" fill="#ffd84d" text-anchor="middle" font-weight="normal">2.00 body</text>+<g stroke="#ffd84d" stroke-width="0.15"><line x1="2.5000000000000004" y1="-13.0" x2="11.0" y2="-13.0"/><line x1="2.5000000000000004" y1="-14.0" x2="2.5000000000000004" y2="-12.0"/><line x1="11.0" y1="-14.0" x2="11.0" y2="-12.0"/></g>+<text x="6.75" y="-13.60" font-size="1.2" fill="#ffd84d" text-anchor="middle" font-weight="normal">0.85</text>+<g stroke="#ffd84d" stroke-width="0.15"><line x1="-2.5000000000000004" y1="-13.0" x2="2.5000000000000004" y2="-13.0"/><line x1="-2.5000000000000004" y1="-14.0" x2="-2.5000000000000004" y2="-12.0"/><line x1="2.5000000000000004" y1="-14.0" x2="2.5000000000000004" y2="-12.0"/></g>+<text x="0.00" y="-13.60" font-size="1.2" fill="#ffd84d" text-anchor="middle" font-weight="normal">0.5</text>+<g stroke="#ffd84d" stroke-width="0.15"><line x1="20.5" y1="2.5" x2="20.5" y2="10.0"/><line x1="19.5" y1="2.5" x2="21.5" y2="2.5"/><line x1="19.5" y1="10.0" x2="21.5" y2="10.0"/></g>+<text x="19.70" y="6.25" font-size="1.2" fill="#ffd84d" text-anchor="middle" font-weight="normal" transform="rotate(-90 19.7 6.25)">0.75</text>+<g stroke="#ffd84d" stroke-width="0.15"><line x1="20.5" y1="-2.5" x2="20.5" y2="2.5"/><line x1="19.5" y1="-2.5" x2="21.5" y2="-2.5"/><line x1="19.5" y1="2.5" x2="21.5" y2="2.5"/></g>+<text x="19.70" y="0.00" font-size="1.2" fill="#ffd84d" text-anchor="middle" font-weight="normal" transform="rotate(-90 19.7 0.0)">0.5</text>+<g stroke="#ffd84d" stroke-width="0.15"><line x1="-18.5" y1="-10.0" x2="-18.5" y2="10.0"/><line x1="-19.5" y1="-10.0" x2="-17.5" y2="-10.0"/><line x1="-19.5" y1="10.0" x2="-17.5" y2="10.0"/></g>+<text x="-19.30" y="0.00" font-size="1.3" fill="#ffd84d" text-anchor="middle" font-weight="normal" transform="rotate(-90 -19.3 0.0)">2.00 (pattern)</text>+<text x="0.00" y="-17.20" font-size="1.3" fill="#ccc" text-anchor="middle" font-weight="normal">LED_XL-2020RGBC-2812B (top view, 10x) - pin 1 = DO at the marked corner</text>+<text x="0.00" y="17.00" font-size="1.05" fill="#ccc" text-anchor="middle" font-weight="normal">pads 0.85 x 0.75 mm, centres +/-0.675 x +/-0.625 mm, gaps 0.5 mm</text>+<text x="0.00" y="18.50" font-size="0.95" fill="#aaa" text-anchor="middle" font-weight="normal">grey dashed = 2.0 mm body, grey circle = top mark, white = silkscreen, pink = courtyard</text>+</svg>
v2/hardware/layout-plan/led-pattern-v2.pngadded⋯ 1 unchanged line ⋯
v2/hardware/layout-plan/led-pattern-v2.svgadded+297@@ -0,0 +1,297 @@+<svg xmlns="http://www.w3.org/2000/svg" width="278.0mm" height="72.0mm" viewBox="-14 -12 278.0 72.0" font-family="DejaVu Sans, Arial, sans-serif">+<rect x="-14" y="-12" width="278.0" height="72.0" fill="#0d1117"/>+<defs><clipPath id="board"><rect x="0" y="0" width="250.0" height="40.0" rx="2.0"/></clipPath><marker id="arr" markerWidth="4" markerHeight="4" refX="3" refY="2" orient="auto"><path d="M0,0 L4,2 L0,4 z" fill="#ff4040"/></marker><marker id="arr2" markerWidth="4" markerHeight="4" refX="3" refY="2" orient="auto"><path d="M0,0 L4,2 L0,4 z" fill="#ffb000"/></marker></defs>+<rect x="0" y="0" width="250.0" height="40.0" rx="2.0" fill="#1f5f3a" stroke="#e8e2c8" stroke-width="0.25"/>+<circle cx="3.2" cy="3.2" r="3.5" fill="none" stroke="#ff6b6b" stroke-width="0.15" stroke-dasharray="0.6 0.4"/><circle cx="3.2" cy="3.2" r="1.6" fill="#0d1117" stroke="#b33" stroke-width="0.2"/>+<circle cx="3.2" cy="36.8" r="3.5" fill="none" stroke="#ff6b6b" stroke-width="0.15" stroke-dasharray="0.6 0.4"/><circle cx="3.2" cy="36.8" r="1.6" fill="#0d1117" stroke="#b33" stroke-width="0.2"/>+<circle cx="215.0" cy="3.2" r="3.5" fill="none" stroke="#ff6b6b" stroke-width="0.15" stroke-dasharray="0.6 0.4"/><circle cx="215.0" cy="3.2" r="1.6" fill="#0d1117" stroke="#b33" stroke-width="0.2"/>+<circle cx="215.0" cy="36.8" r="3.5" fill="none" stroke="#ff6b6b" stroke-width="0.15" stroke-dasharray="0.6 0.4"/><circle cx="215.0" cy="36.8" r="1.6" fill="#0d1117" stroke="#b33" stroke-width="0.2"/>+<polyline points="209.10,35.00 189.10,35.00 189.10,30.00 209.10,30.00 209.10,25.00 189.10,25.00 189.10,20.00 194.10,20.00 199.10,20.00 204.10,20.00 209.10,20.00 209.10,15.00 189.10,15.00 189.10,10.00 209.10,10.00 209.10,5.00 189.10,5.00 179.10,5.00 174.10,5.00 169.10,5.00 164.10,5.00 159.10,10.00 159.10,15.00 164.10,20.00 169.10,20.00 174.10,20.00 179.10,25.00 179.10,30.00 174.10,35.00 169.10,35.00 164.10,35.00 159.10,35.00 149.10,35.00 144.10,35.00 139.10,35.00 134.10,35.00 129.10,35.00 129.10,30.00 129.10,25.00 129.10,20.00 134.10,20.00 139.10,20.00 144.10,20.00 129.10,15.00 129.10,10.00 149.10,5.00 144.10,5.00 139.10,5.00 134.10,5.00 129.10,5.00 119.10,5.00 99.10,5.00 99.10,10.00 119.10,10.00 119.10,15.00 99.10,15.00 99.10,20.00 104.10,20.00 109.10,20.00 114.10,20.00 119.10,20.00 119.10,25.00 99.10,25.00 99.10,30.00 119.10,30.00 119.10,35.00 99.10,35.00 79.10,35.00 79.10,30.00 79.10,25.00 79.10,20.00 79.10,15.00 79.10,10.00 89.10,5.00 84.10,5.00 79.10,5.00 74.10,5.00 69.10,5.00 54.10,5.00 49.10,5.00 44.10,5.00 49.10,10.00 49.10,15.00 49.10,20.00 49.10,25.00 49.10,30.00 54.10,35.00 49.10,35.00 44.10,35.00 29.10,35.00 9.10,35.00 9.10,30.00 24.10,30.00 19.10,25.00 9.10,25.00 9.10,20.00 14.10,20.00 19.10,20.00 24.10,20.00 29.10,15.00 9.10,15.00 9.10,10.00 29.10,10.00 24.10,5.00 19.10,5.00 14.10,5.00 9.10,5.00" fill="none" stroke="#ffb000" stroke-width="0.25" stroke-opacity="0.8"/>+<rect x="208.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="209.10" y="35.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">1</text>++<rect x="188.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="189.10" y="35.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">2</text>++<rect x="188.1" y="29.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="189.10" y="30.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">3</text>++<rect x="208.1" y="29.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="209.10" y="30.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">4</text>++<rect x="208.1" y="24.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="209.10" y="25.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">5</text>++<rect x="188.1" y="24.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="189.10" y="25.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">6</text>++<rect x="188.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="189.10" y="20.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">7</text>++<rect x="193.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="194.10" y="20.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">8</text>++<rect x="198.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="199.10" y="20.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">9</text>++<rect x="203.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="204.10" y="20.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">10</text>++<rect x="208.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="209.10" y="20.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">11</text>++<rect x="208.1" y="14.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="209.10" y="15.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">12</text>++<rect x="188.1" y="14.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="189.10" y="15.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">13</text>++<rect x="188.1" y="9.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="189.10" y="10.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">14</text>++<rect x="208.1" y="9.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="209.10" y="10.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">15</text>++<rect x="208.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="209.10" y="5.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">16</text>++<rect x="188.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="189.10" y="5.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">17</text>++<rect x="178.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="179.10" y="5.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">18</text>++<rect x="173.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="174.10" y="5.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">19</text>++<rect x="168.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="169.10" y="5.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">20</text>++<rect x="163.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="164.10" y="5.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">21</text>++<rect x="158.1" y="9.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="159.10" y="10.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">22</text>++<rect x="158.1" y="14.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="159.10" y="15.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">23</text>++<rect x="163.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="164.10" y="20.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">24</text>++<rect x="168.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="169.10" y="20.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">25</text>++<rect x="173.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="174.10" y="20.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">26</text>++<rect x="178.1" y="24.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="179.10" y="25.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">27</text>++<rect x="178.1" y="29.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="179.10" y="30.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">28</text>++<rect x="173.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="174.10" y="35.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">29</text>++<rect x="168.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="169.10" y="35.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">30</text>++<rect x="163.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="164.10" y="35.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">31</text>++<rect x="158.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="159.10" y="35.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">32</text>++<rect x="148.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="149.10" y="35.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">33</text>++<rect x="143.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="144.10" y="35.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">34</text>++<rect x="138.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="139.10" y="35.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">35</text>++<rect x="133.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="134.10" y="35.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">36</text>++<rect x="128.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="129.10" y="35.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">37</text>++<rect x="128.1" y="29.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="129.10" y="30.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">38</text>++<rect x="128.1" y="24.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="129.10" y="25.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">39</text>++<rect x="128.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="129.10" y="20.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">40</text>++<rect x="133.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="134.10" y="20.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">41</text>++<rect x="138.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="139.10" y="20.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">42</text>++<rect x="143.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="144.10" y="20.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">43</text>++<rect x="128.1" y="14.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="129.10" y="15.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">44</text>++<rect x="128.1" y="9.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="129.10" y="10.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">45</text>++<rect x="148.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="149.10" y="5.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">46</text>++<rect x="143.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="144.10" y="5.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">47</text>++<rect x="138.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="139.10" y="5.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">48</text>++<rect x="133.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="134.10" y="5.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">49</text>++<rect x="128.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="129.10" y="5.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">50</text>++<rect x="118.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="119.10" y="5.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">51</text>++<rect x="98.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="99.10" y="5.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">52</text>++<rect x="98.1" y="9.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="99.10" y="10.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">53</text>++<rect x="118.1" y="9.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="119.10" y="10.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">54</text>++<rect x="118.1" y="14.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="119.10" y="15.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">55</text>++<rect x="98.1" y="14.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="99.10" y="15.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">56</text>++<rect x="98.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="99.10" y="20.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">57</text>++<rect x="103.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="104.10" y="20.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">58</text>++<rect x="108.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="109.10" y="20.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">59</text>++<rect x="113.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="114.10" y="20.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">60</text>++<rect x="118.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="119.10" y="20.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">61</text>++<rect x="118.1" y="24.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="119.10" y="25.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">62</text>++<rect x="98.1" y="24.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="99.10" y="25.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">63</text>++<rect x="98.1" y="29.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="99.10" y="30.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">64</text>++<rect x="118.1" y="29.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="119.10" y="30.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">65</text>++<rect x="118.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="119.10" y="35.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">66</text>++<rect x="98.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="99.10" y="35.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">67</text>++<rect x="78.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="79.10" y="35.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">68</text>++<rect x="78.1" y="29.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="79.10" y="30.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">69</text>++<rect x="78.1" y="24.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="79.10" y="25.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">70</text>++<rect x="78.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="79.10" y="20.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">71</text>++<rect x="78.1" y="14.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="79.10" y="15.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">72</text>++<rect x="78.1" y="9.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="79.10" y="10.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">73</text>++<rect x="88.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="89.10" y="5.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">74</text>++<rect x="83.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="84.10" y="5.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">75</text>++<rect x="78.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="79.10" y="5.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">76</text>++<rect x="73.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="74.10" y="5.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">77</text>++<rect x="68.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="69.10" y="5.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">78</text>++<rect x="53.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="54.10" y="5.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">79</text>++<rect x="48.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="49.10" y="5.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">80</text>++<rect x="43.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="44.10" y="5.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">81</text>++<rect x="48.1" y="9.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="49.10" y="10.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">82</text>++<rect x="48.1" y="14.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="49.10" y="15.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">83</text>++<rect x="48.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="49.10" y="20.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">84</text>++<rect x="48.1" y="24.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="49.10" y="25.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">85</text>++<rect x="48.1" y="29.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="49.10" y="30.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">86</text>++<rect x="53.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="54.10" y="35.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">87</text>++<rect x="48.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="49.10" y="35.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">88</text>++<rect x="43.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="44.10" y="35.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">89</text>++<rect x="28.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="29.10" y="35.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">90</text>++<rect x="8.1" y="34.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="9.10" y="35.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">91</text>++<rect x="8.1" y="29.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="9.10" y="30.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">92</text>++<rect x="23.1" y="29.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="24.10" y="30.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">93</text>++<rect x="18.1" y="24.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="19.10" y="25.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">94</text>++<rect x="8.1" y="24.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="9.10" y="25.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">95</text>++<rect x="8.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="9.10" y="20.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">96</text>++<rect x="13.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="14.10" y="20.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">97</text>++<rect x="18.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="19.10" y="20.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">98</text>++<rect x="23.1" y="19.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="24.10" y="20.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">99</text>++<rect x="28.1" y="14.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="29.10" y="15.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">100</text>++<rect x="8.1" y="14.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="9.10" y="15.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">101</text>++<rect x="8.1" y="9.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="9.10" y="10.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">102</text>++<rect x="28.1" y="9.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="29.10" y="10.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">103</text>++<rect x="23.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="24.10" y="5.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">104</text>++<rect x="18.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="19.10" y="5.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">105</text>++<rect x="13.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="14.10" y="5.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">106</text>++<rect x="8.1" y="4.0" width="2.0" height="2.0" rx="0.2" fill="#f4f4f4" stroke="#111" stroke-width="0.08"/><text x="9.10" y="5.45" font-size="1.05" fill="#111" text-anchor="middle" font-weight="bold">107</text>++<rect x="222.40" y="0.80" width="10.60" height="9.60" fill="#9fe39f" fill-opacity="0.35" stroke="#000" stroke-width="0.12" stroke-dasharray="0.8 0.5"/>+<rect x="222.40" y="27.20" width="5.20" height="5.60" fill="#9fe39f" fill-opacity="0.35" stroke="#000" stroke-width="0.12" stroke-dasharray="0.8 0.5"/>+<rect x="215.50" y="25.20" width="2.20" height="3.40" fill="#9fe39f" fill-opacity="0.35" stroke="#000" stroke-width="0.12" stroke-dasharray="0.8 0.5"/>+<rect x="233.40" y="2.00" width="11.20" height="13.20" fill="#d9d9d9" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="239.00" y="8.95" font-size="1.0" fill="#111" text-anchor="middle" font-weight="normal">U4</text>+<rect x="244.60" y="2.00" width="5.40" height="13.20" fill="#d9d9d9" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<rect x="232.68" y="31.64" width="10.64" height="9.42" fill="#d9d9d9" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="238.00" y="36.70" font-size="1.0" fill="#111" text-anchor="middle" font-weight="normal">USB-C (J1)</text>+<rect x="232.50" y="27.45" width="7.00" height="3.50" fill="#d9d9d9" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="236.00" y="29.41" font-size="0.6" fill="#111" text-anchor="middle" font-weight="normal">D1</text>+<rect x="239.70" y="27.20" width="3.40" height="3.40" fill="#d9d9d9" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="241.40" y="29.11" font-size="0.6" fill="#111" text-anchor="middle" font-weight="normal">U1</text>+<rect x="227.90" y="27.75" width="3.20" height="2.50" fill="#d9d9d9" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="229.50" y="29.21" font-size="0.6" fill="#111" text-anchor="middle" font-weight="normal">U2</text>+<circle cx="227.05" cy="22.2" r="3.15" fill="#c89bff" stroke="#000" stroke-width="0.12"/><text x="227.05" y="22.60" font-size="1.0" fill="#111" text-anchor="middle" font-weight="normal">C301</text>+<text x="227.05" y="23.70" font-size="0.8" fill="#111" text-anchor="middle" font-weight="normal">220uF</text>+<rect x="225.20" y="10.75" width="2.66" height="2.50" fill="#d9d9d9" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="226.53" y="12.21" font-size="0.6" fill="#111" text-anchor="middle" font-weight="normal">U3</text>+<rect x="211.90" y="27.20" width="3.40" height="4.10" fill="#d9d9d9" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="213.60" y="29.46" font-size="0.6" fill="#111" text-anchor="middle" font-weight="normal">U5</text>+<rect x="211.40" y="8.25" width="6.00" height="3.50" fill="#8fd0ff" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="214.40" y="10.31" font-size="0.9" fill="#111" text-anchor="middle" font-weight="normal">SW1</text>+<text x="217.90" y="10.45" font-size="1.2" fill="#fff" text-anchor="start" font-weight="bold">RESET</text>+<rect x="211.40" y="14.75" width="6.00" height="3.50" fill="#8fd0ff" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="214.40" y="16.82" font-size="0.9" fill="#111" text-anchor="middle" font-weight="normal">SW2</text>+<text x="217.90" y="16.95" font-size="1.2" fill="#fff" text-anchor="start" font-weight="bold">ON/OFF</text>+<rect x="211.40" y="21.25" width="6.00" height="3.50" fill="#8fd0ff" fill-opacity="0.9" stroke="#000" stroke-width="0.12"/>+<text x="214.40" y="23.32" font-size="0.9" fill="#111" text-anchor="middle" font-weight="normal">SW3</text>+<text x="217.90" y="23.45" font-size="1.2" fill="#fff" text-anchor="start" font-weight="bold">BOOT</text>+<circle cx="216.6" cy="30.0" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="216.60" y="31.50" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">LED_DIN</text>++<circle cx="220.3" cy="34.6" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="220.30" y="33.55" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">5V_IN</text>++<circle cx="223.9" cy="34.6" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="223.90" y="33.55" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">5V_OUT</text>++<circle cx="227.5" cy="34.6" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="227.50" y="33.55" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">FLT</text>++<circle cx="231.10000000000002" cy="34.6" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="231.10" y="33.55" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">ILM</text>++<circle cx="220.3" cy="38.0" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="220.30" y="39.50" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">3V3</text>++<circle cx="223.9" cy="38.0" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="223.90" y="39.50" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">GND</text>++<circle cx="227.5" cy="38.0" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="227.50" y="39.50" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">RX</text>++<circle cx="231.10000000000002" cy="38.0" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="231.10" y="39.50" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">TX</text>++<circle cx="246.8" cy="35.5" r="0.75" fill="#ffcc66" stroke="#000" stroke-width="0.1"/><text x="246.80" y="37.00" font-size="0.6" fill="#fff" text-anchor="middle" font-weight="normal">GND</text>++<rect x="244.60" y="2.00" width="5.40" height="13.20" fill="#4aa3ff" fill-opacity="0.55" stroke="#000" stroke-width="0.12"/>+<rect x="244.00" y="15.20" width="6.00" height="15.50" fill="#4aa3ff" fill-opacity="0.3" stroke="#000" stroke-width="0.12" stroke-dasharray="0.8 0.5"/>+<g clip-path="url(#board)"><rect x="244.00" y="0.00" width="6.00" height="2.00" fill="#4aa3ff" fill-opacity="0.3" stroke="#000" stroke-width="0.12" stroke-dasharray="0.8 0.5"/>+</g>+<text x="19.10" y="-1.20" font-size="2.2" fill="#ffd84d" text-anchor="middle" font-weight="bold">R</text>+<text x="49.10" y="-1.20" font-size="2.2" fill="#ffd84d" text-anchor="middle" font-weight="bold">I</text>+<text x="79.10" y="-1.20" font-size="2.2" fill="#ffd84d" text-anchor="middle" font-weight="bold">T</text>+<text x="109.10" y="-1.20" font-size="2.2" fill="#ffd84d" text-anchor="middle" font-weight="bold">H</text>+<text x="139.10" y="-1.20" font-size="2.2" fill="#ffd84d" text-anchor="middle" font-weight="bold">E</text>+<text x="169.10" y="-1.20" font-size="2.2" fill="#ffd84d" text-anchor="middle" font-weight="bold">S</text>+<text x="199.10" y="-1.20" font-size="2.2" fill="#ffd84d" text-anchor="middle" font-weight="bold">H</text>+<g stroke="#ffd84d" stroke-width="0.15"><line x1="0" y1="44.0" x2="250.0" y2="44.0"/><line x1="0" y1="43.0" x2="0" y2="45.0"/><line x1="250.0" y1="43.0" x2="250.0" y2="45.0"/></g>+<text x="125.00" y="43.40" font-size="1.5" fill="#ffd84d" text-anchor="middle" font-weight="normal">250 mm</text>+<g stroke="#ffd84d" stroke-width="0.15"><line x1="-5" y1="0" x2="-5" y2="40.0"/><line x1="-6" y1="0" x2="-4" y2="0"/><line x1="-6" y1="40.0" x2="-4" y2="40.0"/></g>+<text x="-5.80" y="20.00" font-size="1.5" fill="#ffd84d" text-anchor="middle" font-weight="normal" transform="rotate(-90 -5.8 20.0)">40 mm</text>+<g stroke="#ffd84d" stroke-width="0.15"><line x1="8.1" y1="-6.5" x2="210.1" y2="-6.5"/><line x1="8.1" y1="-7.5" x2="8.1" y2="-5.5"/><line x1="210.1" y1="-7.5" x2="210.1" y2="-5.5"/></g>+<text x="109.10" y="-7.10" font-size="1.5" fill="#ffd84d" text-anchor="middle" font-weight="normal">word: 202 mm (41 columns x 5 mm)</text>+<g stroke="#ffd84d" stroke-width="0.15"><line x1="8.1" y1="48.5" x2="30.1" y2="48.5"/><line x1="8.1" y1="47.5" x2="8.1" y2="49.5"/><line x1="30.1" y1="47.5" x2="30.1" y2="49.5"/></g>+<text x="19.10" y="47.90" font-size="1.5" fill="#ffd84d" text-anchor="middle" font-weight="normal">letter 22 mm</text>+<g stroke="#ffd84d" stroke-width="0.15"><line x1="30.1" y1="48.5" x2="38.1" y2="48.5"/><line x1="30.1" y1="47.5" x2="30.1" y2="49.5"/><line x1="38.1" y1="47.5" x2="38.1" y2="49.5"/></g>+<text x="34.10" y="47.90" font-size="1.2" fill="#ffd84d" text-anchor="middle" font-weight="normal">gap 8</text>+<g stroke="#ffd84d" stroke-width="0.15"><line x1="257.0" y1="4.0" x2="257.0" y2="36.0"/><line x1="256.0" y1="4.0" x2="258.0" y2="4.0"/><line x1="256.0" y1="36.0" x2="258.0" y2="36.0"/></g>+<text x="256.20" y="20.00" font-size="1.5" fill="#ffd84d" text-anchor="middle" font-weight="normal" transform="rotate(-90 256.2 20.0)">letter 32 mm</text>+<text x="125.00" y="53.50" font-size="1.4" fill="#ccc" text-anchor="middle" font-weight="normal">RITHESH V2 (proposal) - 107 x XL-2020RGBC-2812B, 5 mm pitch - numbers = chain order (LED1 at the right end, next to the level shifter) - scale 1:1 when printed at 100 %</text>+</svg>
v2/hardware/rithesh_v2_fp.pretty/LED_XL-2020RGBC-2812B.kicad_modadded+29@@ -0,0 +1,29 @@+(footprint "LED_XL-2020RGBC-2812B" (version 20221018) (generator rithesh_gen)+ (layer "F.Cu")+ (descr "XINGLIGHT XL-2020RGBC-WS2812B addressable RGB LED, 2.0x2.0mm (LCSC C5349955). Manufacturer recommended soldering pattern (datasheet p.11): 4 pads 0.85x0.75mm, 0.5mm apart across and down (centres +/-0.675 x +/-0.625mm). Pin 1 DO (bottom-right, marked corner), 2 GND, 3 DI, 4 VDD.")+ (tags "LED RGB addressable WS2812 2020 XL-2020RGBC-2812B")+ (attr smd)+ (fp_text reference "REF**" (at 0 -2.0) (layer "F.SilkS")+ (effects (font (size 0.8 0.8) (thickness 0.15))))+ (fp_text value "XL-2020RGBC-2812B" (at 0 2.1) (layer "F.Fab")+ (effects (font (size 0.5 0.5) (thickness 0.08))))+ (fp_text user "${REFERENCE}" (at 0 0) (layer "F.Fab")+ (effects (font (size 0.4 0.4) (thickness 0.06))))+ (fp_line (start -1.000 -1.000) (end 1.000 -1.000) (stroke (width 0.05) (type solid)) (layer "F.Fab"))+ (fp_line (start 1.000 -1.000) (end 1.000 0.700) (stroke (width 0.05) (type solid)) (layer "F.Fab"))+ (fp_line (start 1.000 0.700) (end 0.700 1.000) (stroke (width 0.05) (type solid)) (layer "F.Fab"))+ (fp_line (start 0.700 1.000) (end -1.000 1.000) (stroke (width 0.05) (type solid)) (layer "F.Fab"))+ (fp_line (start -1.000 1.000) (end -1.000 -1.000) (stroke (width 0.05) (type solid)) (layer "F.Fab"))+ (fp_circle (center 0.5 0.7) (end 0.64 0.7) (stroke (width 0.05) (type solid)) (fill none) (layer "F.Fab"))+ (fp_line (start -1.250 -1.250) (end 1.250 -1.250) (stroke (width 0.15) (type solid)) (layer "F.SilkS"))+ (fp_line (start -1.250 1.250) (end 1.250 1.250) (stroke (width 0.15) (type solid)) (layer "F.SilkS"))+ (fp_circle (center 1.6 1.25) (end 1.75 1.25) (stroke (width 0.1) (type solid)) (fill solid) (layer "F.SilkS"))+ (fp_line (start -1.350 -1.250) (end 1.350 -1.250) (stroke (width 0.05) (type solid)) (layer "F.CrtYd"))+ (fp_line (start 1.350 -1.250) (end 1.350 1.250) (stroke (width 0.05) (type solid)) (layer "F.CrtYd"))+ (fp_line (start 1.350 1.250) (end -1.350 1.250) (stroke (width 0.05) (type solid)) (layer "F.CrtYd"))+ (fp_line (start -1.350 1.250) (end -1.350 -1.250) (stroke (width 0.05) (type solid)) (layer "F.CrtYd"))+ (pad "1" smd rect (at 0.675 0.625) (size 0.85 0.75) (layers "F.Cu" "F.Paste" "F.Mask") (pinfunction "DO"))+ (pad "2" smd rect (at 0.675 -0.625) (size 0.85 0.75) (layers "F.Cu" "F.Paste" "F.Mask") (pinfunction "GND"))+ (pad "3" smd rect (at -0.675 -0.625) (size 0.85 0.75) (layers "F.Cu" "F.Paste" "F.Mask") (pinfunction "DI"))+ (pad "4" smd rect (at -0.675 0.625) (size 0.85 0.75) (layers "F.Cu" "F.Paste" "F.Mask") (pinfunction "VDD"))+)
v2/hardware/tools/make_v2_footprint.pyadded+94@@ -0,0 +1,94 @@+"""Create the Version 2 LED footprint: v2/hardware/rithesh_v2_fp.pretty/LED_XL-2020RGBC-2812B.kicad_mod++Drawn from the XINGLIGHT XL-2020RGBC-WS2812B datasheet, page 11 (LCSC C5349955):+- Body 2.0 x 2.0 mm, 0.65 mm tall.+- "Recommended Soldering Pattern": four 0.85 (across) x 0.75 (down) mm pads, 0.5 mm apart across+ and 0.5 mm apart down -> pad centres +/-0.675 x +/-0.625 mm.+- Pins (top view): DI top-left, GND top-right, VDD bottom-left, DO bottom-right.+ Pin table: 1 DO, 2 GND, 3 DI, 4 VDD. The round mark on the top of the LED sits at the DO corner (pin 1);+ the bottom-view triangle points to the same side.++NOTE: the pin order is different from the Version 1 LED (XL-5050: 1 VDD, 2 DO, 3 GND, 4 DI).++Usage: python3 make_v2_footprint.py+"""+import os++V2HW = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))+LIB = os.path.join(V2HW, "rithesh_v2_fp.pretty")++PAD_W, PAD_H = 0.85, 0.75 # pad size, across x down (datasheet recommended pattern)+GAP_X, GAP_Y = 0.5, 0.5 # gaps between pads+PX = GAP_X / 2 + PAD_W / 2 # 0.675: pad centre, across+PY = GAP_Y / 2 + PAD_H / 2 # 0.625: pad centre, down+BODY = 1.0 # half of the 2.0 mm body+SILK_W = 0.15 # JLCPCB minimum silkscreen line width+SILK_Y = PY + PAD_H / 2 + 0.25 # 1.25: silkscreen lines above/below the pads (0.175 mm clear of pads)+SILK_X = PX + PAD_W / 2 + 0.15 # 1.25: line length+CY_X = PX + PAD_W / 2 + 0.25 # courtyard: pads + 0.25 mm -> 1.35+CY_Y = BODY + 0.25 # body + 0.25 mm -> 1.25++# (number, name, x, y) in KiCad coordinates (y down), top view+PADS = [("1", "DO", PX, PY), ("2", "GND", PX, -PY), ("3", "DI", -PX, -PY), ("4", "VDD", -PX, PY)]+++def line(x1, y1, x2, y2, layer, w):+ return (f' (fp_line (start {x1:.3f} {y1:.3f}) (end {x2:.3f} {y2:.3f}) '+ f'(stroke (width {w}) (type solid)) (layer "{layer}"))\n')+++def led_footprint():+ s = ('(footprint "LED_XL-2020RGBC-2812B" (version 20221018) (generator rithesh_gen)\n'+ ' (layer "F.Cu")\n'+ ' (descr "XINGLIGHT XL-2020RGBC-WS2812B addressable RGB LED, 2.0x2.0mm (LCSC C5349955). Manufacturer '+ 'recommended soldering pattern (datasheet p.11): 4 pads 0.85x0.75mm, 0.5mm apart across and down '+ '(centres +/-0.675 x +/-0.625mm). Pin 1 DO (bottom-right, marked corner), 2 GND, 3 DI, 4 VDD.")\n'+ ' (tags "LED RGB addressable WS2812 2020 XL-2020RGBC-2812B")\n'+ ' (attr smd)\n'+ ' (fp_text reference "REF**" (at 0 -2.0) (layer "F.SilkS")\n'+ ' (effects (font (size 0.8 0.8) (thickness 0.15))))\n'+ ' (fp_text value "XL-2020RGBC-2812B" (at 0 2.1) (layer "F.Fab")\n'+ ' (effects (font (size 0.5 0.5) (thickness 0.08))))\n'+ ' (fp_text user "${REFERENCE}" (at 0 0) (layer "F.Fab")\n'+ ' (effects (font (size 0.4 0.4) (thickness 0.06))))\n')+ b = BODY+ # Fab: 2.0 x 2.0 body, corner chamfer at pin 1 (DO, bottom-right) and the round top mark+ for (x1, y1, x2, y2) in [(-b, -b, b, -b), (b, -b, b, b - 0.3), (b, b - 0.3, b - 0.3, b),+ (b - 0.3, b, -b, b), (-b, b, -b, -b)]:+ s += line(x1, y1, x2, y2, "F.Fab", 0.05)+ s += (' (fp_circle (center 0.5 0.7) (end 0.64 0.7) (stroke (width 0.05) (type solid)) '+ '(fill none) (layer "F.Fab"))\n')+ # Silkscreen: lines above and below the pads, pin-1 dot outside the DO corner+ s += line(-SILK_X, -SILK_Y, SILK_X, -SILK_Y, "F.SilkS", SILK_W)+ s += line(-SILK_X, SILK_Y, SILK_X, SILK_Y, "F.SilkS", SILK_W)+ s += (' (fp_circle (center 1.6 1.25) (end 1.75 1.25) (stroke (width 0.1) (type solid)) '+ '(fill solid) (layer "F.SilkS"))\n')+ for (x1, y1, x2, y2) in [(-CY_X, -CY_Y, CY_X, -CY_Y), (CY_X, -CY_Y, CY_X, CY_Y),+ (CY_X, CY_Y, -CY_X, CY_Y), (-CY_X, CY_Y, -CY_X, -CY_Y)]:+ s += line(x1, y1, x2, y2, "F.CrtYd", 0.05)+ for num, name, x, y in PADS:+ s += (f' (pad "{num}" smd rect (at {x:.3f} {y:.3f}) (size {PAD_W} {PAD_H}) '+ f'(layers "F.Cu" "F.Paste" "F.Mask") (pinfunction "{name}"))\n')+ return s + ")\n"+++def main():+ os.makedirs(LIB, exist_ok=True)+ path = os.path.join(LIB, "LED_XL-2020RGBC-2812B.kicad_mod")+ with open(path, "w") as f:+ f.write(led_footprint())+ # self-checks against the datasheet numbers+ xs = sorted({round(x, 3) for _, _, x, _ in PADS})+ ys = sorted({round(y, 3) for _, _, _, y in PADS})+ assert abs((xs[1] - xs[0]) - PAD_W - GAP_X) < 1e-9 and abs((ys[1] - ys[0]) - PAD_H - GAP_Y) < 1e-9+ assert SILK_Y - SILK_W / 2 - (PY + PAD_H / 2) >= 0.15 # silkscreen >= 0.15 mm from pads+ print("wrote", path)+ print(f"pads {PAD_W} x {PAD_H} mm, centres +/-{PX:.3f} x +/-{PY:.3f} mm, "+ f"gaps {GAP_X} / {GAP_Y} mm, pattern {2 * PX + PAD_W:.2f} x {2 * PY + PAD_H:.2f} mm, "+ f"courtyard {2 * CY_X:.2f} x {2 * CY_Y:.2f} mm")+ for num, name, x, y in PADS:+ print(f" pad {num} {name:3} at ({x:+.3f}, {y:+.3f})")+++if __name__ == "__main__":+ main()
v2/hardware/tools/make_v2_ledmaps.pyadded+80@@ -0,0 +1,80 @@+"""Version 2 LED order and WLED maps -> v2/firmware/ (Version 1's firmware/ files are only read, never written).++Version 2's chain is Version 1's chain reversed: the data now enters at the right end, next to the electronics.+ V2 LED n sits where V1 LED (108 - n) sits (WLED index: v2 = 106 - v1)+The letter pattern and the visible positions are the same; only the physical numbering (data order) changed.++Writes:+ v2/firmware/led-order.csv LED1..LED107 with letter, row, column, V2 position (mm) and the V1 LED at that spot+ v2/firmware/ledmap.json 2D map, 41 x 7 grid ("RITHESH V2 41x7")+ v2/firmware/ledmap1.json 1D map, left to right, column by column ("RITHESH V2 left-to-right")+The WLED file names must stay ledmap.json / ledmap1.json on the board; the v2/ folder keeps them apart from V1's.++Each map is built twice (from the V2 positions, and by converting V1's map) and both must agree.++Usage: python3 make_v2_ledmaps.py+"""+import csv+import json+import os++V2HW = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))+V2 = os.path.dirname(V2HW)+ROOT = os.path.dirname(V2)+OUT = os.path.join(V2, "firmware")+V1_FW = os.path.join(ROOT, "firmware")+COLS, ROWS = 41, 7+X0, Y0, PITCH = 9.1, 5.0, 5.0 # must match make_v2_plan.py+V1_X0, V1_PITCH = 10.0, 7.0+++def main():+ v1 = list(csv.DictReader(open(os.path.join(V1_FW, "led-order.csv"))))+ leds = [] # (v2 index, letter, row, col, v1 index)+ for r in v1:+ i1 = int(r["WLED index (0-based)"])+ col = round((float(r["x mm"]) - V1_X0) / V1_PITCH)+ leds.append((106 - i1, r["letter"], int(r["row (0=top)"]), col, i1))+ leds.sort()+ assert [l[0] for l in leds] == list(range(107))++ # 2D map from the V2 positions+ grid = [-1] * (COLS * ROWS)+ for i2, _, row, col, _ in leds:+ grid[row * COLS + col] = i2+ # ... and by converting V1's map; both must match+ m1 = json.load(open(os.path.join(V1_FW, "ledmap.json")))+ conv = [-1 if v < 0 else 106 - v for v in m1["map"]]+ assert conv == grid, "2D map mismatch"+ # 1D map: columns left to right, top to bottom inside a column+ order = [i2 for i2, _, row, col, _ in sorted(leds, key=lambda l: (l[3], l[2]))]+ m1d = json.load(open(os.path.join(V1_FW, "ledmap1.json")))+ assert [106 - v for v in m1d["map"]] == order, "1D map mismatch"+ assert sorted(order) == list(range(107))++ os.makedirs(OUT, exist_ok=True)+ with open(os.path.join(OUT, "ledmap.json"), "w") as f:+ json.dump({"n": "RITHESH V2 41x7", "width": COLS, "height": ROWS, "map": grid}, f, separators=(",", ":"))+ with open(os.path.join(OUT, "ledmap1.json"), "w") as f:+ json.dump({"n": "RITHESH V2 left-to-right", "map": order}, f, separators=(",", ":"))+ with open(os.path.join(OUT, "led-order.csv"), "w", newline="") as f:+ w = csv.writer(f)+ w.writerow(["WLED index (0-based)", "LED", "letter", "row (0=top)", "column (0=left)", "x mm", "y mm",+ "V1 LED at the same spot"])+ for i2, letter, row, col, i1 in leds:+ w.writerow([i2, f"LED{i2 + 1}", letter, row, col, f"{X0 + col * PITCH:.1f}", f"{Y0 + row * PITCH:.1f}",+ f"LED{i1 + 1}"])+ print("wrote", OUT, "(ledmap.json, ledmap1.json, led-order.csv); both map methods agree")+ first, last = leds[0], leds[-1]+ print(f"V2 LED1 = {first[1]} row {first[2]} col {first[3]} (V1 LED{first[4] + 1}); "+ f"V2 LED107 = {last[1]} row {last[2]} col {last[3]} (V1 LED{last[4] + 1})")+ starts = {}+ for i2, letter, row, col, _ in leds:+ key = (letter, col // 6)+ starts.setdefault(key, [i2 + 1, i2 + 1])[1] = i2 + 1+ print("LEDs per letter (V2 numbers):", ", ".join(f"{k[0]}{'' if k[1] != 6 else '(2nd)'} {a}-{b}"+ for k, (a, b) in sorted(starts.items(), key=lambda kv: kv[1][0])))+++if __name__ == "__main__":+ main()
v2/hardware/tools/make_v2_plan.pyadded+432@@ -0,0 +1,432 @@+"""Version 2 preliminary plan drawings (proposal only - no schematic or PCB yet).++Writes to v2/hardware/layout-plan/:+ led-pattern-v2.svg / .png full board at 1:1 scale (print the SVG at 100 %): 107 LEDs, chain order, dimensions+ board-plan-v2.svg / .png outline, mounting holes, keep-outs, LED area, bulk capacitors, electronics-area plan+ electronics-area-v2.svg / .png close-up of the right-hand electronics area+ footprint-LED_XL-2020RGBC-2812B.svg / .png the new LED footprint with dimensions and pin names+(The LED order file and WLED maps are written by make_v2_ledmaps.py into v2/firmware/.)++Coordinates: mm, origin at the board's top-left corner, y down (like KiCad).+The LED chain is Version 1's chain reversed: V2 LED n sits where V1 LED (108 - n) sits, so LED1 is next to+the electronics at the right end and the data line stays short. WLED maps for V2 = 106 - (V1 index).++Usage: python3 make_v2_plan.py+"""+import csv+import math+import os+import subprocess++V2HW = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))+ROOT = os.path.dirname(os.path.dirname(V2HW)) # project root (Version 1 lives here)+OUT = os.path.join(V2HW, "layout-plan")+V1_ORDER = os.path.join(ROOT, "firmware", "led-order.csv")++# ---- board -----------------------------------------------------------------------------+W, H, R_CORNER = 250.0, 40.0, 2.0+PITCH, LED = 5.0, 2.0 # LED spacing and body size+X0, Y0 = 9.1, 5.0 # centre of column 0 / row 0 (9.1: >= 1 mm from the hole keep-outs)+V1_PITCH, V1_X0 = 7.0, 10.0 # Version 1 grid, to convert led-order.csv+LED_CY = (2.70, 2.50) # LED courtyard (from the footprint)+HOLE_R_KEEP = 3.5 # 7 mm screw/washer keep-out (same as V1)+HOLES = [(3.2, 3.2), (3.2, H - 3.2), (215.0, 3.2), (215.0, H - 3.2)]+SCREW_HEAD_D = 5.7 # largest common M3 head (ISO 7380 button head; socket cap 5.5, pan head 5.6)+WASHER_D = 6.0 # 6 mm washer (the 7 mm keep-out also fits a DIN 125 7 mm washer)+LED_PAD_BOX = (2.2, 2.0) # outer edges of the four LED pads (footprint)+MIN_HOLE_LED = 0.5 # required: LED courtyard >= 0.5 mm outside the 7 mm keep-out (target ~1 mm)+EDGE_MIN = 0.3 # parts at least this far inside the board edge (except USB-C / antenna)++# ---- electronics area (courtyard rectangles: name, label, x, y, w, h, kind) ---------------+ESP = (233.4, 2.0, 16.6, 13.2) # module body, antenna end flush with the right edge+ANT_KEEP = (W - 5.4, 2.0, 5.4, 13.2) # module antenna keep-out, all copper layers (Espressif footprint)+# Espressif Hardware Design Guidelines Fig. 21: clearance >= 15 mm on each side of the antenna along the board edge,+# as deep as the antenna area (~6 mm). Below the antenna: a 15.5 mm x 6.0 mm no-copper zone (0.5 mm margin over 15).+# Above the antenna the board ends 2 mm later (free air); that 2 mm strip is also kept free of copper.+ANT_SIDE = (W - 6.0, 15.2, 6.0, 15.5)+ANT_TOP = (W - 6.0, 0.0, 6.0, 2.0)+ANT_REQ = 15.0 # required clearance along the edge+ZONE_MARGIN = 0.5 # every part stays at least this far outside the antenna zones+PARTS = [+ ("U4", "ESP32-C3-MINI-1", *ESP, "major"),+ ("J1", "USB-C", 238.0 - 5.32, H - 3.65 - 4.71, 10.64, 9.42, "edge"),+ ("D1", "SMAJ5.0A", 232.5, 27.45, 7.0, 3.5, "major"),+ ("U1", "USBLC6", 239.7, 27.2, 3.4, 3.4, "major"),+ ("U2", "eFuse", 227.9, 27.75, 3.2, 2.5, "major"),+ ("C301", "220uF", 223.5, 17.5, 7.1, 9.4, "major"),+ ("U3", "LDO", 225.2, 10.75, 2.66, 2.5, "major"),+ ("U5", "shifter", 211.9, 27.2, 3.4, 4.1, "major"),+ ("SW1", "RESET", 211.4, 8.25, 6.0, 3.5, "button"),+ ("SW2", "ON/OFF", 211.4, 14.75, 6.0, 3.5, "button"),+ ("SW3", "BOOT", 211.4, 21.25, 6.0, 3.5, "button"),+]+TPS = [("TP6", "LED_DIN", 216.6, 30.0)] + [+ (f"TP{n}", name, 220.3 + 3.6 * (i % 4), 34.6 + 3.4 * (i // 4))+ for i, (n, name) in enumerate([(1, "5V_IN"), (2, "5V_OUT"), (7, "FLT"), (8, "ILM"),+ (3, "3V3"), (4, "GND"), (9, "RX"), (10, "TX")])+] + [("TP5", "GND", 246.8, 35.5)]+TP_CY = 2.5+ZONES = [ # dashed allowance areas for small passives+ ("ESP32 support: EN RC, pull-ups, 22R x2, CC 5.1k x2, 470R, 10uF x2", 222.4, 0.8, 10.6, 9.6),+ ("eFuse: 4.7uF, 100nF, 100k/39k, 100k, 1.5k || 1.5k", 222.4, 27.2, 5.2, 5.6),+ ("LED data: 33R, 100nF, 10k", 215.5, 25.2, 2.2, 3.4),+]+BULK_FAR = ("C302", "220uF (fitted, far end)", 36.0, 20.0)+BULK_MID = ("C303", "220uF spare (DNP)", 93.0, 22.0)+BULK_CY = (7.1, 9.4)+++def leds():+ """[(n, letter, row, col, x, y)] for V2, LED1..LED107 (V1 chain reversed)."""+ rows = list(csv.DictReader(open(V1_ORDER)))+ out = []+ for r in rows:+ col = round((float(r["x mm"]) - V1_X0) / V1_PITCH)+ row = int(r["row (0=top)"])+ n = 108 - int(r["LED"][3:])+ out.append((n, r["letter"], row, col, X0 + col * PITCH, Y0 + row * PITCH))+ return sorted(out)+++# ---- checks ------------------------------------------------------------------------------+def rect_overlap(a, b, gap=0.0):+ ax, ay, aw, ah = a+ bx, by, bw, bh = b+ return ax < bx + bw + gap and bx < ax + aw + gap and ay < by + bh + gap and by < ay + ah + gap+++def rect_circle_dist(r, c):+ x, y, w, h = r+ cx, cy = c+ dx = max(x - cx, 0, cx - (x + w))+ dy = max(y - cy, 0, cy - (y + h))+ return math.hypot(dx, dy)+++HOLE_REPORT, HOLE_EDGE, ANT_REPORT = {}, {}, {}+++def checks(L):+ problems, notes = [], []+ led_r = [(x - LED_CY[0] / 2, y - LED_CY[1] / 2, *LED_CY) for *_, x, y in L]+ rects = [(p[0], p[2:6], p[6]) for p in PARTS]+ rects += [(t[0], (t[2] - TP_CY / 2, t[3] - TP_CY / 2, TP_CY, TP_CY), "tp") for t in TPS]+ for ref, _, cx, cy in (BULK_FAR, BULK_MID):+ rects.append((ref, (cx - BULK_CY[0] / 2, cy - BULK_CY[1] / 2, *BULK_CY), "bulk"))+ # parts vs parts+ for i in range(len(rects)):+ for j in range(i + 1, len(rects)):+ if rect_overlap(rects[i][1], rects[j][1]):+ problems.append(f"{rects[i][0]} overlaps {rects[j][0]}")+ # parts vs LEDs, holes, antenna, edge+ for ref, r, kind in rects:+ for k, lr in enumerate(led_r):+ if rect_overlap(r, lr, 0.2):+ problems.append(f"{ref} within 0.2 mm of LED{k + 1}")+ for h in HOLES:+ if rect_circle_dist(r, h) < HOLE_R_KEEP + 0.3:+ problems.append(f"{ref} closer than 0.3 mm to the keep-out of hole at {h}")+ if ref != "U4":+ for name, a in (("antenna keep-out", ANT_KEEP), ("antenna clearance zone", ANT_SIDE),+ ("strip above the antenna", ANT_TOP)):+ if rect_overlap(r, a, ZONE_MARGIN):+ problems.append(f"{ref} closer than {ZONE_MARGIN} mm to the {name}")+ x, y, w, h = r+ if kind != "edge" and ref != "U4" and (x < EDGE_MIN or y < EDGE_MIN or x + w > W - EDGE_MIN or y + h > H - EDGE_MIN):+ problems.append(f"{ref} closer than {EDGE_MIN} mm to the board edge")+ # LEDs vs holes+ for k, lr in enumerate(led_r):+ for h in HOLES:+ d = rect_circle_dist(lr, h)+ if d < HOLE_R_KEEP:+ problems.append(f"LED{k + 1} inside a hole keep-out")+ # mounting-hole report: LED courtyard (both rotations), body and pads vs keep-out, screw head, washer+ def boxes(x, y):+ out = {}+ for name, (w, h) in (("courtyard", LED_CY), ("body", (LED, LED)), ("pads", LED_PAD_BOX)):+ out[name] = [(x - w / 2, y - h / 2, w, h), (x - h / 2, y - w / 2, h, w)] # 0 and 90 degrees+ return out+ worst = {}+ for *_, x, y in L:+ for name, bl in boxes(x, y).items():+ for h in HOLES:+ d = min(rect_circle_dist(b, h) for b in bl)+ if name not in worst or d < worst[name][0]:+ worst[name] = (d, h)+ HOLE_REPORT.clear()+ for name in ("courtyard", "body", "pads"):+ d, h = worst[name]+ HOLE_REPORT[name] = {"keep-out (7.0 mm)": d - HOLE_R_KEEP, f"washer ({WASHER_D} mm)": d - WASHER_D / 2,+ f"screw head ({SCREW_HEAD_D} mm)": d - SCREW_HEAD_D / 2, "hole edge (3.2 mm)": d - 1.6}+ if worst["courtyard"][0] - HOLE_R_KEEP < MIN_HOLE_LED:+ problems.append(f"LED courtyard only {worst['courtyard'][0] - HOLE_R_KEEP:.2f} mm from a hole keep-out")+ for (hx, hy) in HOLES:+ edge = min(hx, hy, W - hx, H - hy)+ HOLE_EDGE[(hx, hy)] = {"screw head inside board by": edge - SCREW_HEAD_D / 2, "washer inside board by": edge - WASHER_D / 2}+ # antenna report: nearest part to the antenna keep-out, measured along the edge strip and in a straight line+ ax0, ay0, aw, ah = ANT_KEEP+ ANT_REPORT.clear()+ ANT_REPORT["clearance zone below the antenna"] = ANT_SIDE[3]+ strip = [r for ref, r, k in rects if ref != "U4" and r[0] + r[2] > ANT_SIDE[0]]+ ANT_REPORT["nearest part in the edge strip below the antenna"] = min(r[1] - (ay0 + ah) for r in strip if r[1] > ay0)+ ANT_REPORT["nearest part to the zone boundary"] = min(+ min(max(z[0] - (r[0] + r[2]), r[0] - (z[0] + z[2]), z[1] - (r[1] + r[3]), r[1] - (z[1] + z[3]))+ for z in (ANT_KEEP, ANT_SIDE, ANT_TOP)) for ref, r, k in rects if ref != "U4")+ if ANT_SIDE[3] < ANT_REQ:+ problems.append("antenna clearance zone shorter than 15 mm")+ # rounded corner at the ESP32+ ex, ey, ew, eh = ESP+ corner = (W - R_CORNER, R_CORNER)+ d = math.hypot(ex + ew - corner[0], ey - corner[1])+ if ex + ew > W - R_CORNER and ey < R_CORNER and d > R_CORNER + 1e-6:+ problems.append("ESP32 corner outside the rounded board corner")+ # free area estimate in the electronics section+ area = (W - 211.0) * H+ used = sum(r[2] * r[3] for ref, r, k in rects if r[0] > 205) + sum(z[3] * z[4] for z in ZONES)+ used += (ANT_KEEP[2] * ANT_KEEP[3] + ANT_SIDE[2] * ANT_SIDE[3] + ANT_TOP[2] * ANT_TOP[3]+ + 2 * math.pi * HOLE_R_KEEP ** 2 / 2)+ notes.append(f"electronics area x 211.0-{W:.0f} mm: {area:.0f} mm2, parts + keep-outs + passive zones "+ f"{used:.0f} mm2 ({100 * used / area:.0f} %), free for routing {area - used:.0f} mm2")+ return problems, notes+++# ---- SVG helpers ---------------------------------------------------------------------------+C = {"board": "#1f5f3a", "edge": "#e8e2c8", "led": "#f4f4f4", "ledline": "#111", "chain": "#ffb000",+ "hole": "#b33", "keep": "#ff6b6b", "ant": "#4aa3ff", "part": "#d9d9d9", "text": "#fff", "dim": "#ffd84d",+ "bulk": "#c89bff", "tp": "#ffcc66", "zone": "#9fe39f", "btn": "#8fd0ff"}+++def svg_open(vx, vy, vw, vh, scale_mm=True):+ unit = "mm" if scale_mm else ""+ return (f'<svg xmlns="http://www.w3.org/2000/svg" width="{vw}{unit}" height="{vh}{unit}" '+ f'viewBox="{vx} {vy} {vw} {vh}" font-family="DejaVu Sans, Arial, sans-serif">\n'+ f'<rect x="{vx}" y="{vy}" width="{vw}" height="{vh}" fill="#0d1117"/>\n')+++def board_outline():+ return (f'<rect x="0" y="0" width="{W}" height="{H}" rx="{R_CORNER}" fill="{C["board"]}" '+ f'stroke="{C["edge"]}" stroke-width="0.25"/>\n')+++def text(x, y, s, size=1.6, color=None, anchor="middle", weight="normal", rot=0):+ t = f' transform="rotate({rot} {x} {y})"' if rot else ""+ s = s.replace("&", "&").replace("<", "<")+ return (f'<text x="{x:.2f}" y="{y:.2f}" font-size="{size}" fill="{color or C["text"]}" '+ f'text-anchor="{anchor}" font-weight="{weight}"{t}>{s}</text>\n')+++def hdim(x1, x2, y, label, size=1.5):+ s = f'<g stroke="{C["dim"]}" stroke-width="0.15">'+ s += f'<line x1="{x1}" y1="{y}" x2="{x2}" y2="{y}"/><line x1="{x1}" y1="{y - 1}" x2="{x1}" y2="{y + 1}"/>'+ s += f'<line x1="{x2}" y1="{y - 1}" x2="{x2}" y2="{y + 1}"/></g>\n'+ return s + text((x1 + x2) / 2, y - 0.6, label, size, C["dim"])+++def vdim(y1, y2, x, label, size=1.5):+ s = f'<g stroke="{C["dim"]}" stroke-width="0.15">'+ s += f'<line x1="{x}" y1="{y1}" x2="{x}" y2="{y2}"/><line x1="{x - 1}" y1="{y1}" x2="{x + 1}" y2="{y1}"/>'+ s += f'<line x1="{x - 1}" y1="{y2}" x2="{x + 1}" y2="{y2}"/></g>\n'+ return s + text(x - 0.8, (y1 + y2) / 2, label, size, C["dim"], rot=-90)+++def holes_svg():+ s = ""+ for (x, y) in HOLES:+ s += (f'<circle cx="{x}" cy="{y}" r="{HOLE_R_KEEP}" fill="none" stroke="{C["keep"]}" '+ f'stroke-width="0.15" stroke-dasharray="0.6 0.4"/>')+ s += f'<circle cx="{x}" cy="{y}" r="1.6" fill="#0d1117" stroke="{C["hole"]}" stroke-width="0.2"/>\n'+ return s+++def leds_svg(L, numbers=True, chain=True):+ s = ""+ if chain:+ pts = " ".join(f"{x:.2f},{y:.2f}" for *_, x, y in L)+ s += (f'<polyline points="{pts}" fill="none" stroke="{C["chain"]}" stroke-width="0.25" '+ f'stroke-opacity="0.8"/>\n')+ for n, letter, row, col, x, y in L:+ s += (f'<rect x="{x - LED / 2}" y="{y - LED / 2}" width="{LED}" height="{LED}" rx="0.2" '+ f'fill="{C["led"]}" stroke="{C["ledline"]}" stroke-width="0.08"/>')+ if numbers:+ s += text(x, y + 0.45, str(n), 1.05, "#111", weight="bold")+ s += "\n"+ return s+++def rect_svg(x, y, w, h, fill, label="", size=1.2, dash=False, opacity=0.9, stroke="#000"):+ d = ' stroke-dasharray="0.8 0.5"' if dash else ""+ s = (f'<rect x="{x:.2f}" y="{y:.2f}" width="{w:.2f}" height="{h:.2f}" fill="{fill}" fill-opacity="{opacity}" '+ f'stroke="{stroke}" stroke-width="0.12"{d}/>\n')+ if label:+ lines = label.split("\n")+ for i, ln in enumerate(lines):+ s += text(x + w / 2, y + h / 2 + (i - (len(lines) - 1) / 2) * size * 1.15 + size * 0.35, ln, size, "#111")+ return s+++def electronics_svg(detail=True):+ s = ""+ for z in ZONES:+ s += rect_svg(*z[1:], C["zone"], "", dash=True, opacity=0.35)+ for ref, label, x, y, w, h, kind in PARTS:+ fill = C["btn"] if kind == "button" else C["part"]+ if ref == "U4":+ s += rect_svg(x, y, w - 5.4, h, fill, "ESP32-C3\nMINI-1 (U4)" if detail else "U4", 1.0)+ s += rect_svg(x + w - 5.4, y, 5.4, h, fill, "")+ elif ref == "J1":+ s += rect_svg(x, y, w, h, fill, "USB-C (J1)", 1.0)+ elif ref == "C301":+ s += f'<circle cx="{x + w / 2}" cy="{y + h / 2}" r="3.15" fill="{C["bulk"]}" stroke="#000" stroke-width="0.12"/>'+ s += text(x + w / 2, y + h / 2 + 0.4, "C301", 1.0, "#111")+ s += text(x + w / 2, y + h / 2 + 1.5, "220uF", 0.8, "#111")+ elif kind == "button":+ s += rect_svg(x, y, w, h, fill, ref, 0.9)+ s += text(x + w + 0.5, y + h / 2 + 0.45, label, 1.2, C["text"], anchor="start", weight="bold")+ else:+ s += rect_svg(x, y, w, h, fill, f"{ref}\n{label}" if detail else ref, 0.6)+ for ref, name, x, y in TPS:+ ly = y - 1.05 if 34 < y < 35 else y + 1.5 # label above the top test-pad row+ s += (f'<circle cx="{x}" cy="{y}" r="0.75" fill="{C["tp"]}" stroke="#000" stroke-width="0.1"/>'+ + text(x, ly, name, 0.6, C["text"]) + "\n")+ # antenna areas drawn last so they stay visible over the module+ s += rect_svg(*ANT_KEEP, C["ant"], "antenna\nkeep-out" if detail else "", 0.8, opacity=0.55)+ s += rect_svg(*ANT_SIDE, C["ant"], "antenna\nclearance\n15.5 mm\nno copper\nboth layers" if detail else "",+ 0.75, dash=True, opacity=0.3)+ s += '<g clip-path="url(#board)">' + rect_svg(*ANT_TOP, C["ant"], "", dash=True, opacity=0.3) + "</g>\n"+ if detail:+ # 5 V feed and data arrows+ s += (f'<path d="M 223.5 25.5 L 210.5 25.5" stroke="#ff4040" stroke-width="0.6" fill="none" '+ f'marker-end="url(#arr)"/>' + text(216.7, 26.9, "5 V feed to LEDs", 0.7, "#ff8080"))+ s += (f'<path d="M 211.9 31.3 L 210.2 34.2" stroke="{C["chain"]}" stroke-width="0.35" fill="none" '+ f'marker-end="url(#arr2)"/>')+ return s+++DEFS = (f'<defs><clipPath id="board"><rect x="0" y="0" width="{W}" height="{H}" rx="{R_CORNER}"/></clipPath>'+ '<marker id="arr" markerWidth="4" markerHeight="4" refX="3" refY="2" orient="auto">'+ '<path d="M0,0 L4,2 L0,4 z" fill="#ff4040"/></marker>'+ '<marker id="arr2" markerWidth="4" markerHeight="4" refX="3" refY="2" orient="auto">'+ '<path d="M0,0 L4,2 L0,4 z" fill="#ffb000"/></marker></defs>\n')+++def write(name, svg, dpi):+ p = os.path.join(OUT, name + ".svg")+ open(p, "w").write(svg + "</svg>\n")+ subprocess.run(["rsvg-convert", "-d", str(dpi), "-p", str(dpi), "-o", os.path.join(OUT, name + ".png"), p],+ check=True)+++def pattern_svg(L):+ m = 14 # margin around the board for dimensions+ s = svg_open(-m, -m + 2, W + 2 * m, H + 2 * m + 4) + DEFS+ s += board_outline() + holes_svg() + leds_svg(L)+ s += electronics_svg(detail=False)+ # letter boxes and names+ for i, letter in enumerate("RITHESH"):+ x1 = X0 + i * 6 * PITCH - LED / 2+ s += text(x1 + 11, -1.2, letter, 2.2, C["dim"], weight="bold")+ s += hdim(0, W, H + 4, f"{W:.0f} mm")+ s += vdim(0, H, -5, f"{H:.0f} mm")+ s += hdim(X0 - LED / 2, X0 + 40 * PITCH + LED / 2, -6.5, "word: 202 mm (41 columns x 5 mm)")+ s += hdim(X0 - LED / 2, X0 + 4 * PITCH + LED / 2, H + 8.5, "letter 22 mm")+ s += hdim(X0 + 4 * PITCH + LED / 2, X0 + 6 * PITCH - LED / 2, H + 8.5, "gap 8", 1.2)+ s += vdim(Y0 - LED / 2, Y0 + 6 * PITCH + LED / 2, W + 7, "letter 32 mm")+ s += text(W / 2, H + 13.5, "RITHESH V2 (proposal) - 107 x XL-2020RGBC-2812B, 5 mm pitch - numbers = chain order "+ "(LED1 at the right end, next to the level shifter) - scale 1:1 when printed at 100 %", 1.4, "#ccc")+ return s+++def plan_svg(L):+ m = 6+ s = svg_open(-m, -m, W + 2 * m, H + 2 * m + 6) + DEFS+ s += board_outline() + holes_svg()+ s += rect_svg(X0 - 1.35, Y0 - 1.25, 40 * PITCH + 2.7, 6 * PITCH + 2.5, "#2d7a4f", "", opacity=0.6, dash=True,+ stroke="#9fe39f")+ s += leds_svg(L, numbers=False, chain=False)+ for ref, label, cx, cy in (BULK_FAR, BULK_MID):+ s += (f'<circle cx="{cx}" cy="{cy}" r="3.15" fill="{C["bulk"]}" stroke="#000" stroke-width="0.12"/>'+ + text(cx, cy + 0.4, ref, 1.1, "#111") + text(cx, cy + 5.2, label, 1.0, C["text"]))+ s += electronics_svg(detail=True)+ s += text(W / 2, H + 4.2, "V2 board plan (proposal): 250 x 40 mm, 2 layers - LED area (dashed green), 4 x M3 "+ "holes with 7 mm keep-outs (red), electronics at the right end, antenna at the top-right corner", 1.4, "#ccc")+ return s+++def electronics_close_svg(L):+ vx, vw = 203.0, 50.0+ s = svg_open(vx, -3, vw, H + 9) + DEFS+ s += board_outline() + holes_svg() + leds_svg([l for l in L if l[4] > 190], numbers=True, chain=True)+ s += electronics_svg(detail=True)+ s += hdim(211.0, W, H + 3.2, f"electronics area {W - 211.0:.1f} mm", 1.2)+ s += text(vx + vw / 2, H + 5.4, "Plan only: part outlines are courtyards; small passives fit in the dashed "+ "green zones. Final positions come at placement.", 0.95, "#ccc")+ return s+++def footprint_svg():+ import importlib.util+ spec = importlib.util.spec_from_file_location("fp", os.path.join(V2HW, "tools", "make_v2_footprint.py"))+ fp = importlib.util.module_from_spec(spec)+ spec.loader.exec_module(fp)+ k = 10 # drawing scale (10 x)+ s = ('<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="760" viewBox="-25 -19 50 38" '+ 'font-family="DejaVu Sans, Arial, sans-serif">\n<rect x="-25" y="-19" width="50" height="38" fill="#0d1117"/>\n')+ b = fp.BODY * k+ s += (f'<rect x="{-b}" y="{-b}" width="{2 * b}" height="{2 * b}" fill="none" stroke="#888" stroke-width="0.12" '+ f'stroke-dasharray="0.6 0.4"/>')+ s += (f'<rect x="{-fp.CY_X * k}" y="{-fp.CY_Y * k}" width="{2 * fp.CY_X * k}" height="{2 * fp.CY_Y * k}" '+ f'fill="none" stroke="#ff66cc" stroke-width="0.1"/>')+ for num, name, x, y in fp.PADS:+ s += (f'<rect x="{(x - fp.PAD_W / 2) * k}" y="{(y - fp.PAD_H / 2) * k}" width="{fp.PAD_W * k}" '+ f'height="{fp.PAD_H * k}" fill="#c8323c"/>')+ s += text(x * k, y * k + 0.9, num, 2.6, "#fff", weight="bold")+ s += text(x * k * 2.05, y * k + 0.6, name, 1.9, "#ffd84d", weight="bold")+ s += f'<circle cx="{0.5 * k}" cy="{0.7 * k}" r="{0.14 * k}" fill="none" stroke="#aaa" stroke-width="0.15"/>'+ s += f'<circle cx="{1.6 * k}" cy="{1.25 * k}" r="{0.15 * k}" fill="#fff"/>'+ for yy in (-fp.SILK_Y, fp.SILK_Y):+ s += (f'<line x1="{-fp.SILK_X * k}" y1="{yy * k}" x2="{fp.SILK_X * k}" y2="{yy * k}" stroke="#fff" '+ f'stroke-width="{fp.SILK_W * k}" stroke-opacity="0.8"/>')+ px = (fp.PX + fp.PAD_W / 2) * k+ py = (fp.PY + fp.PAD_H / 2) * k+ s += hdim(-px, px, -py - 5.0, "2.20 (pattern)", 1.3)+ s += hdim(-b, b, py + 5.5, "2.00 body", 1.3)+ s += hdim((fp.PX - fp.PAD_W / 2) * k, px, -py - 3, "0.85", 1.2)+ s += hdim(-(fp.PX - fp.PAD_W / 2) * k, (fp.PX - fp.PAD_W / 2) * k, -py - 3, "0.5", 1.2)+ s += vdim((fp.PY - fp.PAD_H / 2) * k, py, px + 9.5, "0.75", 1.2)+ s += vdim(-(fp.PY - fp.PAD_H / 2) * k, (fp.PY - fp.PAD_H / 2) * k, px + 9.5, "0.5", 1.2)+ s += vdim(-py, py, -px - 7.5, "2.00 (pattern)", 1.3)+ s += text(0, -17.2, "LED_XL-2020RGBC-2812B (top view, 10x) - pin 1 = DO at the marked corner", 1.3, "#ccc")+ s += text(0, 17.0, "pads 0.85 x 0.75 mm, centres +/-0.675 x +/-0.625 mm, gaps 0.5 mm", 1.05, "#ccc")+ s += text(0, 18.5, "grey dashed = 2.0 mm body, grey circle = top mark, white = silkscreen, pink = courtyard",+ 0.95, "#aaa")+ return s+++def main():+ os.makedirs(OUT, exist_ok=True)+ L = leds()+ assert len(L) == 107 and [l[0] for l in L] == list(range(1, 108))+ problems, notes = checks(L)+ write("led-pattern-v2", pattern_svg(L), 254) # 10 px per mm+ write("board-plan-v2", plan_svg(L), 254)+ write("electronics-area-v2", electronics_close_svg(L), 762) # 30 px per mm+ svg = footprint_svg()+ p = os.path.join(OUT, "footprint-LED_XL-2020RGBC-2812B.svg")+ open(p, "w").write(svg + "</svg>\n")+ subprocess.run(["rsvg-convert", "-w", "1500", "-o", p[:-4] + ".png", p], check=True)+ for n in notes:+ print("note:", n)+ print("mounting holes: clearance of the closest LED (worst of 0 / 90 degree rotation), mm")+ for name, d in HOLE_REPORT.items():+ print(f" LED {name:9}: " + ", ".join(f"{k} {v:.2f}" for k, v in d.items()))+ for h, d in HOLE_EDGE.items():+ print(f" hole {h}: " + ", ".join(f"{k} {v:.2f}" for k, v in d.items()))+ print("antenna (mm):", ", ".join(f"{k} {v:.2f}" for k, v in ANT_REPORT.items()))+ print("CHECKS:", "all passed" if not problems else f"{len(problems)} problem(s)")+ for p in problems:+ print(" -", p)+ print("LED1 at", L[0][4:], "LED107 at", L[-1][4:])+++if __name__ == "__main__":+ main()
v2/requirements.mdadded+333@@ -0,0 +1,333 @@+# Version 2 requirements (rev 0.2, proposal)++> **Status (2026-09-25): proposal only, waiting for approval.** No schematic, placement or routing yet.+> Version 1 (the project root) is the approved prototype design and is **not changed** by anything in `v2/`.+> The Version 2 LED is **provisional** until its own 5–10 LED WLED timing test passes.+> **Do not order anything from this folder.**++## Goal++A smaller, lower-power RITHESH board with smaller LEDs. It still spells "RITHESH" clearly, keeps the same electronics+and protection, and uses JLCPCB basic parts where that does not reduce reliability.++## What stays the same as Version 1++- Same 5 × 7 letter patterns and **107 LEDs** (see `requirements.md` in the project root for the patterns).+- ESP32-C3-MINI-1-N4 running standard **WLED**: LED data on GPIO10, ON/OFF button on GPIO5.+- USB-C 5 V input with the same protection chain:+ - SMAJ5.0A surge diode+ - USBLC6-2SC6 USB ESD chip+ - TPS259531 eFuse (soft start, current limit, 5.7 V clamp)+- TLV76733 3.3 V regulator. **Kept** (approved 2026-09-25).+- SN74AHCT1G125 5 V level shifter with the 33 Ω series resistor and 10 kΩ pull-down. **Kept** (approved 2026-09-25).+- RESET / ON/OFF / BOOT buttons, 10 test pads, 4 × M3 mounting holes with 7 mm keep-outs.+- 2 copper layers, 1.6 mm FR-4, 1 oz copper. All parts on the front. JLCPCB assembly.+- A 100 nF capacitor next to every LED.++## Decisions (approved 2026-09-25)++| Decision | Choice |+|----------|--------|+| LED | **XL-2020RGBC-2812B** (XINGLIGHT, JLCPCB/LCSC **C5349955**), 2.0 × 2.0 mm. **PROVISIONAL** until the timing test passes |+| LED spacing | **5 mm** centre to centre (V1: 7 mm) |+| Layout | Electronics at the **right end** of the word (V1: in a strip under the word) |+| Board size | **250 × 40 mm** target (V1: 300 × 70 mm). Placement may trim a few mm; the 4 mm option is not used |+| 750 Ω eFuse current-limit resistor | **Two 1.5 kΩ 1 % resistors in parallel** (basic part C25867) |+| 499 Ω serial TX resistor | **470 Ω** (basic part C25117) |+| Buttons | **3.9 × 3 mm** basic buttons TS-1088-AR02016 (C720477) instead of 5.1 × 5.1 mm |+| Other substitutions | **None** that reduce reliability just to avoid an extended-part fee |+| Storage | Everything for Version 2 lives in `v2/` |+| LED chain | **Starts at the electronics side**: V2 LED1 is where V1 LED107 was (approved 2026-09-25) |+| 5.7 V clamp vs 5.5 V LED | **Accepted only as a documented prototype risk**, not as a verified production condition (approved 2026-09-25 with conditions; see below) |+| Clearances (corrected 2026-09-25) | LED courtyard **≥ 1.0 mm** outside each 7 mm hole keep-out (required ≥ 0.5 mm); antenna clearance **15.5 mm** (required ≥ 15 mm) |++## LED check (datasheet XL-2020RGBC-WS2812B, rechecked 2026-09-25)++| Item | Datasheet | WLED / board | Result |+|------|-----------|--------------|--------|+| "0" bit high time | typ 0.25 µs, **max 0.47 µs** | about 400 ns | ✅ 70 ns margin (V1 LED: 10 ns) |+| "1" bit high time | 0.58–1.0 µs (typ 0.85) | about 800 ns | ✅ |+| "1" bit low time | typ 0.4 µs (no limits given) | about 450 ns | ✅ near typical |+| "0" bit low time | typ 1.0 µs (no limits given) | about 850 ns | ✅ near typical |+| Bit period | min 1.2 µs | 1.25 µs | ✅ |+| Reset (latch) | more than 80 µs | 300 µs | ✅ |+| Data input "high" level | at least 0.5 × VDD (2.5 V at 5 V; 2.75 V at 5.5 V) | 5 V from the level shifter | ✅ |+| Supply voltage | **3.5–5.5 V**, listed in the *absolute maximum* table; full function 4.5–5.5 V | 5 V rail | ✅ normal use; see the 5.7 V risk below |+| LED current | constant current **5 mA per colour** (typ; ±3 % between chips) | | 15 mA per LED at full white |+| Chip standby current | 0.5 mA (typ) | | 54 mA for 107 LEDs |+| Moisture sensitivity | MSL 5 | | JLCPCB handles baking |++WLED's timing figures are the NeoPixelBus WS2812x values used for Version 1's check (400 / 850 ns for a "0",+800 / 450 ns for a "1"). This is a **datasheet check only**. The bench test below must still pass.++**Rejected candidates:**++| LED | Reason |+|-----|--------|+| WS2812C-2020 | "0" bit max 380 ns (WLED sends 400); supply max 5.3 V |+| WS2812B-Mini 3535 | "0" bit max 380 ns; supply max 5.3 V |+| SK6805-EC15 | "0" bit max 400 ns, so no margin at all |+| XL-1615RGBC-2812B | "1" bit min 900 ns; WLED sends 800 |++### Timing test (required before any Version 2 order)++Same method as Version 1 (`requirements.md`, "Prototype plan", Step 1), but with 5–10 **XL-2020RGBC-2812B** LEDs:++- **Setup:** ESP32-C3 running standard WLED, 74AHCT1G125 level shifter powered from 5 V, 33 Ω series resistor,+ 100 nF per LED.+- **Pass:**+ - 30 minutes of colours, effects and brightness changes with no wrong colours or random flashes+ - correct after a power cycle and after a reset+ - oscilloscope reading of the "0" high time is about 400 ns+- **Pass:** the LED stops being provisional. **Fail:** do not order. Decide between a different LED and (last resort)+ a custom WLED timing build.++## LED footprint (verified against the datasheet, page 11)++File: `v2/hardware/rithesh_v2_fp.pretty/LED_XL-2020RGBC-2812B.kicad_mod` (generated by `v2/hardware/tools/make_v2_footprint.py`).++| Item | Value |+|------|-------|+| Body | 2.0 × 2.0 × 0.65 mm |+| Pads | 4 × **0.85 × 0.75 mm** (across × down), gaps **0.5 mm** both ways, centres ±0.675 × ±0.625 mm (the datasheet's recommended soldering pattern) |+| Pins, seen from the top | **DI** top-left (pin 3), **GND** top-right (pin 2), **VDD** bottom-left (pin 4), **DO** bottom-right (pin 1) |+| Orientation mark | Round mark on the top at the **DO / pin 1** corner. The bottom-view triangle points to the same side. Footprint has a silkscreen dot outside that corner and a chamfered fab outline |+| Courtyard | 2.70 × 2.50 mm |++⚠️ **The pin order differs from the Version 1 LED** (XL-5050: 1 VDD, 2 DO, 3 GND, 4 DI). The V2 schematic+symbol must use the new numbering. JLCPCB's placement preview must be checked for this LED's rotation.++## LED pattern and dimensions++Drawings (generated by `v2/hardware/tools/make_v2_plan.py`):+- `v2/hardware/layout-plan/led-pattern-v2.svg` is 1:1 when printed at 100 %; the `.png` is a preview.+- `board-plan-v2.png`+- `electronics-area-v2.png`++| Item | Value |+|------|-------|+| Grid | 41 columns × 7 rows at 5 mm (7 letters × 5 columns + 6 empty columns between letters) |+| One letter | **22 mm wide × 32 mm tall** (V1: 33 × 47 mm) |+| Space between letters | 10 mm centre to centre, **8 mm clear** between LED bodies |+| Whole word | **202 × 32 mm** (V1: 285 × 47 mm) |+| LED positions | column 0 at x = **9.1 mm**, row 0 at y = 5.0 mm (from the board's top-left corner). Moved 0.6 mm right on 2026-09-25 for hole clearance; pattern and 5 mm spacing unchanged |+| **Board** | **250.0 × 40.0 mm**, 2 mm corner radius |++### LED chain order (changed from Version 1)++The data line starts next to the electronics, so the chain now **starts at the right end**:+- **V2 LED1** is at the bottom-right of the last "H" (where V1's LED107 is).+- **V2 LED107** is at the top-left of the "R".+- **V2 LED n sits where V1 LED (108 − n) sits.**+- **LEDs per letter:** H 1–17, S 18–32, E 33–50, H 51–67, T 68–78, I 79–89, R 90–107.+- **The visible letters are identical; only the physical numbering (data order) changed.**++This keeps the 5 V data line from the level shifter to LED1 only a few millimetres long, instead of a 21 cm track.++The separate Version 2 files are in `v2/firmware/`, generated by `v2/hardware/tools/make_v2_ledmaps.py`, which+checks each map two independent ways:+- `ledmap.json` ("RITHESH V2 41x7")+- `ledmap1.json` ("RITHESH V2 left-to-right")+- `led-order.csv`++Version 1's `firmware/` files are unchanged.++## Board outline and electronics area (preliminary)++| Item | Position (mm, from the top-left corner) | Notes |+|------|------------------------------------------|-------|+| Mounting holes (M3, 3.2 mm, non-plated) | (3.2, 3.2), (3.2, 36.8), **(215.0, 3.2), (215.0, 36.8)** | 7 mm keep-outs. Right-hand holes sit between the word and the electronics. Clearances in the table below |+| ESP32-C3-MINI-1 | body x 233.4–250.0, y 2.0–15.2; antenna end flush with the right edge | Top-right corner. The board corner gives free air above the antenna |+| Antenna keep-out | x 244.6–250, y 2.0–15.2 | No copper on either layer (module rule, as V1) |+| **Antenna clearance zone** | x **244.0–250** (6.0 mm deep), y 15.2–**30.7** (**15.5 mm**) | Espressif Hardware Design Guidelines Fig. 21: at least 15 mm beside the antenna along the board edge, as deep as the antenna area (about 6 mm). **No parts, pads, tracks, vias or copper fill on either layer.** Every part stays at least 0.5 mm outside it |+| Strip above the antenna | x 244.0–250, y 0–2.0 | No copper. The board ends here, so this side has free air (more than 15 mm) |+| USB-C | centred at x = 238.0 on the bottom edge | Plug from below. 8 mm away from the antenna clearance |+| Buttons | RESET / ON/OFF / BOOT stacked at x 211.4–217.4, y 8–25 | Labels printed to the right |+| Test pads | TP6 LED_DIN beside the level shifter; 8 in two rows along the bottom (5V_IN, 5V_OUT, FLT, ILM / 3V3, GND, RX, TX); TP5 GND right of the USB-C | ≥ 3.6 mm apart |+| Level shifter | beside LED1 (x ≈ 213.6, y ≈ 29) | Short data line |+| eFuse, surge diode, USB ESD | just above the USB-C | |+| C301 220 µF (fitted) | at the eFuse output (x ≈ 227, y ≈ 22) | |+| C302 220 µF (fitted) | between "R" and "I" (x 36, y 20) | Far end of the 5 V rail |+| C303 220 µF (DNP spare) | between "T" and "H" (x 93, y 22) | Middle of the rail |++### Mounting-hole clearances (corrected 2026-09-25)++The closest LED is measured for both 0° and 90° LED rotations; the worst case is shown.++| From the closest LED's … | to the 7 mm keep-out | to a 6 mm washer | to an M3 screw head (5.7 mm, largest common: button head) | to the 3.2 mm hole |+|---|---|---|---|---|+| courtyard (2.70 × 2.50 mm) | **1.08 mm** | 1.58 mm | 1.73 mm | 2.98 mm |+| body (2.0 × 2.0 mm) | 1.46 mm | 1.96 mm | 2.11 mm | 3.36 mm |+| solder pads (2.2 × 2.0 mm outer edges) | 1.37 mm | 1.87 mm | 2.02 mm | 3.27 mm |++- **Requirement:** at least 0.5 mm, target about 1 mm. Met, with 1.08 mm at the courtyard (was 0.39 mm).+- **Screw heads and washers:** a 5.7 mm screw head stays 0.35 mm inside the board edge and a 6 mm washer 0.20 mm.+- **Larger washers:** the 7 mm keep-out also fits a DIN 125 washer (7.0 mm), but that would reach 0.3 mm past the+ board edge (same as V1).++### Antenna clearance (corrected 2026-09-25)++- **Clearance zone below the antenna:** **15.5 mm** long and 6.0 mm deep (requirement at least 15 mm; the extra+ 0.5 mm is a safety margin, not a rounding).+- **Nearest part in the edge strip below the antenna:** **19.05 mm** from the antenna keep-out (TP5).+- **Nearest part to any antenna zone boundary:** 0.90 mm outside it (U1, moved 0.6 mm left).+- **Above the antenna:** the board corner, then free air.+- **At placement:** the zone becomes a KiCad rule area on **both copper layers** forbidding tracks, vias, pads and+ copper fill. The placement checker then verifies bodies, pads, tracks, vias and fills against it (as for V1).++**Check results** (`make_v2_plan.py`, 2026-09-25):+- No overlaps between parts, LEDs, hole keep-outs, the antenna zones (0.5 mm margin) or the board edge.+- The electronics area (x 211.0–250) is **60 % used** by parts, keep-outs and passive-part zones; **631 mm²** is free for routing.+- The plan therefore fits without crowding. Final positions come at placement.++## Power++| Item | Value |+|------|-------|+| One LED at full white | 3 × 5 mA + 0.5 mA = **15.5 mA** |+| All 107 LEDs at full white | **1.66 A** (up to about 1.71 A with the ±3 % chip spread) |+| All LEDs off (standby) | about 54 mA |+| ESP32-C3 | about 100–130 mA on average; Wi-Fi transmit peaks up to **335 mA** |+| **Board worst case** | 1.71 + 0.335 + about 0.01 = **about 2.06 A** |+| Typical (colourful effects) | about 0.6–0.9 A |+| eFuse current limit | **2.49–2.90 A** (see below): at least **0.43 A (17 %)** above the worst case |+| Charger | USB-C **5 V ⎓ 3 A** (15 W) recommended: 0.94 A above the worst case. It also works on a 1.5 A USB-C source if WLED's limit is lowered to 1,300 mA |+| USB-C connector | same part as V1. V1's accepted risk 7 (one VBUS pad pair reaches its 2.5 A rating only during the eFuse's brief 2.9 A limit) still applies; the normal maximum is now 2.06 A instead of 2.2 A |++### WLED settings and what happens at full white++Checked in the WLED source code, release v16.0.1:+- `settings_leds.htm`: the per-LED current menu has 55 / 35 / 30 / 15 mA presets plus **Custom (1–255 mA)**.+- `bus_manager.cpp`: WLED estimates each LED as (colour sum ÷ 765) × mA-per-LED **+ 1 mA standby**, and reserves+ `MA_FOR_ESP` = **120 mA** for the ESP32.++| Setting | Value |+|---------|-------|+| LED current | **Custom: 16 mA** (15.5 mA rounded up) |+| Maximum PSU current | **2,000 mA** (unchanged from V1), brightness limiter **on** |++At full white, WLED estimates 107 × 16 + 107 × 1 = **1,819 mA** for the LEDs. Its LED budget is 2,000 − 120 =+**1,880 mA**. Because 1,819 is less than 1,880, **WLED does not dim**: full white runs at full brightness. The real+LED current is about 1.66 A.++The 2,000 mA limit becomes a safety net. It only acts if a setting is wrong, for example too many LEDs entered.+If the default 55 mA is left in place, WLED dims full white to about 31 % for no reason, so **set 16 mA**.++### eFuse current limit with two 1.5 kΩ resistors (TI TPS2595 datasheet)++- **Formula:** I = 2000 / R + 0.04 A. It matches the datasheet table (487 Ω gives 4.17 A; 1780 Ω gives 1.17 A).+- **Resistance:** 1.5 kΩ ∥ 1.5 kΩ = **750 Ω**. The worst case, with both resistors 1 % high or both 1 % low, is+ 742.5–757.5 Ω. That is the **same ±1 %** as a single 1 % resistor; with random tolerances it is usually tighter.+- **Limit:** about 2.71 A typical. With the chip's own spread (about −7 / +6 %) the range is **2.49–2.90 A**,+ identical to Version 1. Resistor drift with temperature (±100 ppm/°C) adds less than ±0.5 %.+- **Current monitor:** unchanged, about 0.21 V per amp (276 µA/A × 750 Ω).++**Failure modes (datasheet fault table):**++| Fault | Result | Safe? |+|-------|--------|-------|+| One resistor missing or open | R = 1.5 kΩ, so the limit falls to about **1.37 A** (roughly 1.27–1.47 A). The board current-limits at high brightness, and the ILM test pad reads about 0.41 V/A instead of 0.21 V/A | ✅ fails safe (less current). The prototype full-white test and the ILM check find it |+| Both open (ILM pin open) | limit 0 A, so the eFuse shuts down | ✅ safe |+| Solder bridge across a resistor (ILM shorted to GND) | circuit breaker trips at 3 A | ✅ same as with a single resistor |+| Bridge between the two resistors | none: both sit between the same two nets | ✅ harmless |++Place the two resistors side by side with a direct ground connection. Add to the prototype test: **ILM pad about+0.21 V per amp** (0.41 V/A means one resistor is missing).++### Voltage drop and copper++- **Supply path:** power enters at the right end, and the farthest LED is about 200 mm away.+- **Estimate:** 5 V fill about 30 mm wide (about 6.7 squares, 3.3 mΩ) plus a ground plane about 40 mm wide+ (about 5 squares, 2.5 mΩ). With the load spread along the word, the drop is about 1.66 A × 5.8 mΩ ÷ 2 ≈+ **5 mV**, plus vias and narrow sections: **estimated under 30 mV** (V1: 9 mV).+- **To do:** calculate it with `hardware/tools/power_analysis.py` after routing.+- **Copper:** 1 oz is enough. IPC-2221 needs **≥ 0.8 mm** for 2 A at a 10 °C rise on an outer layer; plan+ **1.2 mm** power tracks as in V1.+- **5 V feed:** runs from C301 to the LED area between the ON/OFF/BOOT buttons and the level shifter.++### Capacitors++| Where | Value | Change from V1 |+|-------|-------|----------------|+| Every LED | 100 nF 0402 X7R (basic C1525), within about 2 mm of its LED | same (107 + level shifter) |+| Bulk on the LED rail | **3 footprints** for 220 µF 16 V (C72496): **2 fitted** (C301 at the feed, C302 at the far end) and **1 spare DNP** (C303, middle) | V1 had 4 footprints (2 fitted). Worst-case synchronised LED current is now 1.6 A instead of about 4.3 A, so the V1 method gives about 0.4 V of dip instead of 1.1 V |+| USB input | 4.7 µF (basic) | same; the charger still sees about 5 µF at plug-in (USB-C limit 10 µF) |+| eFuse soft start | 100 nF | same; covers all 3 bulk capacitors |+| Regulator | 10 µF in + 10 µF out (basic) | same |++## Remaining risk: LED 5.5 V rating vs eFuse 5.7 V clamp++- **Normal use:** USB-C chargers deliver 4.75–5.5 V. The eFuse drops about 0.07 V at 2 A, so the LEDs see at most+ about 5.5 V, at their limit but within it.+- **Fault:** if a charger fails and goes above about 5.5–5.9 V, the TPS259531 clamps its output at **5.2–5.7 V**+ (5.45 V typical). The LEDs could then see **up to 0.2 V above their 5.5 V absolute maximum** for as long as the+ fault lasts.+- **Consequence:** possible LED damage in that fault case. How much margin the LED really has is unknown.+- **Same as V1:** this is Version 1's accepted **risk 2** unchanged. The XL-2020 datasheet also lists 5.5 V as an+ absolute maximum.+- **Options if it is not acceptable:** an eFuse or clamp with a lower output limit, or a series diode (costs about+ 0.3 V of LED supply). Neither is proposed now.+- **Status (approved 2026-09-25):** accepted **only as a documented prototype risk**, **not** as a verified+ production condition.++**Conditions attached to this risk:**+1. **Measure** the actual LED supply (5 V rail at the first and last LED) during the prototype test.+2. **Test with at least two** normal 5 V USB-C chargers.+3. **Confirm** the LED rail **never exceeds 5.5 V** in normal operation (idle, full white, effects, plug-in), with the+ oscilloscope and a meter.+4. **Do not** intentionally test a destructive overvoltage fault on the assembled board.+5. **Do not approve a larger batch** until the prototype passes.+6. **Before completing the Version 2 schematic**, briefly check whether a simple, reliable alternative can keep the LED+ rail at or below 5.5 V without adding significant cost, heat or extended parts. **Present it for comparison only;+ do not change the approved design automatically.**++## Parts changes (JLCPCB catalogue, checked 2026-09-25)++| Current (V1) | Version 2 | JLCPCB # | Type | Stock | Price (100 pcs) | Size | Compatible? | Why |+|--------------|-----------|----------|------|-------|-----------------|------|-------------|-----|+| XL-5050RGBC-2812B (C2843785) | **XL-2020RGBC-2812B** | C5349955 | extended | 127,770 | $0.081 | 2.0 × 2.0 mm | Yes (timing and supply above). **New footprint, new pin order** | smaller, better timing margin, lower current |+| 750 Ω 1 % (C25132, extended) | **2 × 1.5 kΩ 1 % in parallel** | C25867 | **basic** | 2,187,279 | $0.003 each | 0402 | Yes: same 750 Ω and same tolerance; fails safe | removes one extended type |+| 499 Ω (C4125, extended) | **470 Ω 1 %** | C25117 | **basic** | 1,047,546 | $0.003 | 0402 | Yes: series protection on TX; value not critical | removes one extended type |+| TS-1187A 5.1 × 5.1 mm button (C318884, basic) | **TS-1088-AR02016** 3.9 × 3 mm, 160 gf, no locating post | C720477 | **basic** | 942,241 | $0.054 | 3.9 × 3.0 × 2.0 mm, pads 5.5 × 2.0 mm | Yes: same 2-pin switch | saves space |++**Unchanged extended parts:**+- ESP32-C3-MINI-1-N4, TPS259531, TLV76733, TYPE-C-31-M-12, USBLC6-2SC6, SN74AHCT1G125, 220 µF RVT (C72496)+- plus the LED++**"Preferred" parts (no fee):** SMAJ5.0A and the 39 kΩ resistor.++| Extended types with a setup fee | V1: **10** (about $30 per order) | V2: **8** (about $24 per order) |+|---|---|---|++**Before any future quote (required):**+1. Recheck every part's basic/extended status and stock on JLCPCB **on the day of the quote**. The status+ above comes from JLCPCB's public catalogue; the Adom tool's basic/extended flag was found to be unreliable.+2. The eFuse has the lowest stock (3,299 today). Check it again.+3. **All prices here are preliminary estimates.** Only a JLCPCB quote is real.++## Version 1 vs Version 2++| | Version 1 (approved prototype) | Version 2 (proposal) |+|---|---|---|+| Board | 300 × 70 mm (210 cm²) | **250 × 40 mm (100 cm², −52 %)** |+| LEDs | 107 × XL-5050 (5 mm) at 7 mm pitch | 107 × XL-2020 (2 mm) at 5 mm pitch |+| Letter size | 33 × 47 mm | 22 × 32 mm |+| Readability | readable from about 5 m; large bright dots | readable from about 3.5 m; smaller dots, more "dotted"; about ¼ of the brightness (good indoors, weak in daylight) |+| Full-white current | 3.85 A (WLED dims to about 30 %) | 1.66 A (no dimming needed) |+| Board maximum | 2.2 A (with WLED limiting) | about 2.06 A (without limiting) |+| Timing margin ("0" bit) | 10 ns | 70 ns |+| Supply rating vs 5.7 V clamp | 5.5 V (accepted risk) | 5.5 V (same risk) |+| Parts per board (estimate) | about $9.70 | about $13.60 (small LEDs cost more) |+| Extended-part fees | about $30 (10 types) | about $24 (8 types) |+| 5 assembled boards, excluding shipping and tax (preliminary) | about $120–160 | about $120–150 |+| Repair by hand | 5050 LED: possible with care | 2020 LED: needs hot air |+| New risks | – | new footprint and pin order, new timing test, tiny parts, changed LED numbering (V2 maps required) |++## Next steps (each needs approval)++1. Approve this proposal (dimensions, pattern, electronics plan, footprint), including the corrected clearances.+2. Version 2 schematic (copy of V1 with the approved changes), ERC, pin audit.+3. Placement, then routing, DRC and checks. Then images, a power recalculation and review.+4. Version 2 firmware: maps and LED order done (`v2/firmware/`). A full V2 setup guide comes with the schematic.+ Before the schematic is completed: the 5.5 V alternative comparison (5.7 V risk, condition 6).+5. Timing test with real XL-2020 LEDs **before** any Version 2 order.