# WijiBoard – Project Context for AI Assistants > **Purpose**: This file is the authoritative project memory. Read it in full before making > any changes. It captures hardware, architecture, current state, open work, and every > important design decision made so far. --- ## 1. Project Overview WijiBoard is a **5-bar parallel linkage SCARA robot** (spirit-board / planchette mover) controlled wirelessly from a browser via **Web Bluetooth**. There is **no mobile app, no WiFi, no server** — only BLE between the ESP32 firmware and a local web SPA. - **Inspiration / upstream**: `nerd-sniped/WijiBoard` on GitHub. We took *only* the kinematics and lookup tables from that project. Everything else (frontend, BLE protocol, SPA shell, section architecture) was written from scratch. --- ## 2. Hardware | Component | Detail | |---|---| | **MCU** | Tenstar Robot ESP32-C3 Super Mini | | **Framework** | PlatformIO + Arduino core | | **Motors** | 2× 28BYJ-48 (unipolar stepper via ULN2003 driver) | | **Motor mode** | `AccelStepper::FULL4WIRE` — 2048 steps/rev | | **Motor 1 pins** | IN1=0, IN2=1, IN3=3, IN4=4 | | **Motor 2 pins** | IN1=5, IN2=6, IN3=7, IN4=10 | | **USB Serial** | Native USB CDC (`-D ARDUINO_USB_MODE=1 -D ARDUINO_USB_CDC_ON_BOOT=1`) | | **Default speed** | 600 steps/s, accel 100 steps/s² | ### Mechanism geometry (critical — do NOT change without hardware verification) ``` END EFFECTOR / \ l2 (110 mm) l2 (110 mm) / \ ELBOW1 ELBOW2 \ / l1 (85 mm) l1 (85 mm) \ / MOTOR1 (+12.9,0) MOTOR2 (-12.9,0) | | [===BASE (25.8 mm)===] ``` **⚠️ CRITICAL MOTOR CONVENTION** (source of many past bugs): `PositionControl.cpp` from the original project uses: - `xmd = x - d2` → Motor 1 pivot is at **(+d2, 0) = (+12.9, 0)** — the RIGHT side - `xpd = x + d2` → Motor 2 pivot is at **(-d2, 0) = (-12.9, 0)** — the LEFT side This is counter-intuitive. Motor 1 is physically on the right. **Every piece of code that computes elbow or motor-base positions must use M1 at +12.9 and M2 at -12.9 or the arms will appear visually crossed even for valid target positions.** ### Arm constants (from `PositionControl.cpp`) ``` d = 25.8 mm (full motor separation) d2 = 12.9 mm (half separation) l1 = 85.0 mm (proximal link) l2 = 110.0 mm (distal link) STEPS_PER_REV = 2048 STEP_ANGLE = 360/2048 ≈ 0.17578125 °/step ``` --- ## 3. BLE Protocol ### UUIDs (must be identical in firmware AND `web/js/ble.js`) ``` Service: 18f3b235-9831-4c75-8ec0-210469b820a0 Command: cd083b06-4447-4cf3-a7c3-322ecf802ce4 (WRITE) Status: 82e38c5b-d3ab-41d1-861c-b84dc6bb1e03 (NOTIFY) Name: WijiBoard ``` ### Command format (ASCII strings, write-without-response) | Command | Meaning | |---|---| | `S1+` | Move motor 1 CW by n steps | | `S1-` | Move motor 1 CCW by n steps | | `S2+` | Move motor 2 CW by n steps | | `S2-` | Move motor 2 CCW by n steps | | `SPD:` | Set max speed (steps/sec) for both motors | | `ACC:` | Set acceleration (steps/sec²) for both motors | | `HOMEALL` | Home both motors simultaneously | | `HOME1` | Home motor 1 | | `HOME2` | Home motor 2 | | `POS` | Request current positions (triggers NOTIFY) | ### Status notifications (firmware → browser) `P:,` — current step positions for motor 1 and 2, sent every 200 ms while moving. --- ## 4. Project Structure ``` esp32c3-wiji/ ├── index.html # SPA shell — ES module bootstrap, sidebar, routing ├── platformio.ini # PlatformIO config (env: esp32-c3-devkitm-1) ├── fivebarIKGame.js # REFERENCE ONLY — original site's kinematics (do not import) ├── src/ │ └── main.cpp # ESP32 firmware (BLE + AccelStepper) └── web/ ├── styles/ │ ├── base.css # CSS variables, reset, typography, layout primitives │ └── components.css # Cards, buttons, inputs, sidebar, nav items └── js/ ├── ble.js # Web Bluetooth singleton ├── kinematics.js # 5-bar IK/FK (PositionControl.cpp port) ├── router.js # Hash-based SPA router ├── ui.js # UI helpers (log panel, toast, BLE status badge) └── sections/ ├── home.js # Home/landing section (placeholder) └── stepper-test.js # Stepper test section — SVG arm visualiser + jog + IK ``` ### Module loading (index.html) All ES modules are loaded with static imports in `index.html`. **CRITICAL**: Do NOT use dynamic cache-busting imports (e.g. `import('./ble.js?v=123')`) for the BLE module. Doing so causes the browser to instantiate multiple copies of the `BLE` singleton, breaking the event emitter (UI will not update when BLE connects). --- ## 5. SPA Architecture ### Router (`web/js/router.js`) Hash-based SPA router. Sections are mounted/unmounted as the user navigates. ```javascript Router.register('home', HomeSection); Router.register('stepper-test', StepperSection); Router.init('#view', '#home'); ``` Each section module must export `{ mount(containerEl), unmount() }`. ### Section lifecycle - `mount(el)` — called when navigating TO this section; injects HTML, sets up event listeners - `unmount()` — called when navigating AWAY; removes event listeners from `eventCleanup[]` Pattern used in every section: ```javascript let eventCleanup = []; export default { async mount(container) { container.innerHTML = buildHTML(); // grab refs... const handler = (e) => { ... }; el.addEventListener('click', handler); eventCleanup.push(() => el.removeEventListener('click', handler)); renderArmFromSteps(); }, unmount() { eventCleanup.forEach(fn => fn()); eventCleanup = []; }, }; ``` ### Sidebar The sidebar is in `index.html` (not in any section). It is a collapsible panel opened by a hamburger (`☰`) button. It contains `