486 lines
21 KiB
Markdown
486 lines
21 KiB
Markdown
# 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):
|
||
|
||
The original C++ firmware has a variable swap bug where `angle2` is passed to `stepper1` and `angle1` to `stepper2`. Thus, the physical mapping is:
|
||
- **Motor 1** is the **LEFT** motor, pivoting at **(-d2, 0) = (-12.9, 0)**. It uses `xpd = x + d2`.
|
||
- **Motor 2** is the **RIGHT** motor, pivoting at **(+d2, 0) = (+12.9, 0)**. It uses `xmd = x - d2`.
|
||
|
||
**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 and physically collide.**
|
||
|
||
### 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+<n>` | Move motor 1 CW by n steps |
|
||
| `S1-<n>` | Move motor 1 CCW by n steps |
|
||
| `S2+<n>` | Move motor 2 CW by n steps |
|
||
| `S2-<n>` | Move motor 2 CCW by n steps |
|
||
| `SPD:<n>` | Set max speed (steps/sec) for both motors |
|
||
| `ACC:<n>` | Set acceleration (steps/sec²) for both motors |
|
||
| `HOMEALL` | Home both motors simultaneously |
|
||
| `HOME1` | Home motor 1 |
|
||
| `HOME2` | Home motor 2 |
|
||
| `DISABLE` | Disable steppers (un-hold) and require rehoming. Auto-triggers after 30 mins (`30 * 60 * 1000` ms) of inactivity |
|
||
| `POS` | Request current positions (triggers NOTIFY) |
|
||
|
||
### Status notifications (firmware → browser)
|
||
|
||
`P:<s1>,<s2>,[<held>]` — current step positions for motor 1 and 2, sent every 200 ms while moving, or on request. 3rd parameter is 1 if holding, 0 if disabled.
|
||
`SYS:IDLE_TIMEOUT` — Sent when motors are automatically disabled due to 30 mins of inactivity.
|
||
|
||
---
|
||
|
||
## 4. Project Structure
|
||
|
||
```
|
||
esp32c3-wiji/
|
||
├── index.html # SPA shell — ES module bootstrap, sidebar, routing
|
||
├── platformio.ini # PlatformIO config (env: esp32-c3-devkitm-1)
|
||
├── reference/
|
||
│ └── fivebarIKGame.js # REFERENCE ONLY — original site's kinematics (do not import)
|
||
├── scripts/ # Helper scripts for testing, plotting, and generation
|
||
├── src/
|
||
│ ├── Config.h # Pins, Constants, UUIDs
|
||
│ ├── BleManager.cpp # BLE setup, callbacks, notifications
|
||
│ ├── CommandParser.cpp # Parses BLE string commands
|
||
│ ├── MotorController.cpp # AccelStepper wrap, homing state, idle timeout
|
||
│ ├── StatusLed.cpp # Onboard LED feedback logic
|
||
│ └── main.cpp # Main app glue, command router
|
||
└── 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 `<button data-route="...">` nav items that call
|
||
`Router.navigate(route)`. The active item gets `class="active"` via the router.
|
||
|
||
### Kinematic Motion Controller (`web/js/motion.js`)
|
||
|
||
Centralised trajectory generation and streaming module.
|
||
- **Why**: Moving side-to-side linearly in joint space causes the arms to arc dangerously out-of-bounds at the top.
|
||
- **How it works**: Intercepts `Motion.goto(x, y)`, slices the Cartesian straight line into small segments (max `25 mm` by default, configurable in `Motion.CONFIG.SEGMENT_SIZE_MM`), and streams them to the ESP32.
|
||
- **Dynamic Blending**: It monitors `P:` positional updates from the ESP32 and fires the next waypoint *before* `AccelStepper` decelerates (when within `BLEND_THRESHOLD_STEPS`). This produces a seamless Cartesian trajectory with no MCU code changes and allows for future "visual flavor" movement effects (e.g. snaky, erratic).
|
||
- **Simulation Sync**: In simulation mode, `Motion.goto()` instantly updates its internal steps and manually dispatches a fake `ble:status` event containing `P:s1,s2`. This allows all UI components to effortlessly sync their visual state using their existing BLE listeners.
|
||
|
||
---
|
||
|
||
## 6. Kinematics (`web/js/kinematics.js`)
|
||
|
||
### IK (`solve(x, y)`)
|
||
|
||
Exact port of `calculateInverseKinematics()` from `PositionControl.cpp`, with motor swap applied:
|
||
|
||
```javascript
|
||
// Motor 1 (Left, pivot at -d2 = -12.9 mm)
|
||
const xpd = x + d2; // Δx from M1 pivot
|
||
const t = hypot(xpd, y);
|
||
const cosW2 = (l2² - t² - l1²) / (-2·l1·t);
|
||
const theta1 = atan2(y, xpd) + acos(cosW2);
|
||
|
||
// Motor 2 (Right, pivot at +d2 = +12.9 mm)
|
||
const xmd = x - d2; // Δx from M2 pivot
|
||
const s = hypot(xmd, y);
|
||
const cosW1 = (l2² - s² - l1²) / (-2·l1·s);
|
||
const theta2 = atan2(y, xmd) - acos(cosW1);
|
||
```
|
||
|
||
Returns `{ theta1, theta2, reachable }` — angles in **radians**.
|
||
|
||
### FK (`forward(theta1, theta2)`)
|
||
|
||
Computes elbow and EE positions for visualisation:
|
||
|
||
```javascript
|
||
// M1 at -d2, M2 at +d2 ← critical sign convention
|
||
elbow1 = { x: -d2 + l1·cos(theta1), y: l1·sin(theta1) }
|
||
elbow2 = { x: +d2 + l1·cos(theta2), y: l1·sin(theta2) }
|
||
// EE = upward circle-circle intersection of circles (elbow1, l2) and (elbow2, l2)
|
||
```
|
||
|
||
Returns `{ elbow1, elbow2, endX, endY, valid }`.
|
||
|
||
### Arm crossing check (`armsCrossed(theta1, theta2)`)
|
||
|
||
Prevents moves that would push an elbow into the mechanism housing from the wrong side.
|
||
Based on elbow position relative to tunable thresholds (see LIMITS below).
|
||
|
||
### Workspace check (`checkWorkspace(x, y)`)
|
||
|
||
Four ordered checks:
|
||
1. Board outer boundary (rectangular)
|
||
2. Mechanism housing exclusion box (rectangular, centred on origin)
|
||
3. IK geometric reachability (cosW1/cosW2 range)
|
||
4. Elbow crossing guard (`armsCrossed`)
|
||
|
||
### LIMITS object (all values in mm — **tune to hardware**)
|
||
|
||
```javascript
|
||
const LIMITS = {
|
||
// Outer board boundary
|
||
X_MIN: -150, // ← TUNE
|
||
X_MAX: 150, // ← TUNE
|
||
Y_MIN: -30, // ← TUNE (numbers start at ~44 mm, signs below 0)
|
||
Y_MAX: 145, // ← TUNE (highest letter ~128 mm)
|
||
|
||
// Centre mechanism housing exclusion box
|
||
BOX_HALF_W: 30, // ← TUNE: half-width (motors at ±12.9, housing wider)
|
||
BOX_Y_MIN: -10, // ← TUNE: box bottom
|
||
BOX_Y_MAX: 40, // ← TUNE: box top
|
||
|
||
// Elbow crossing thresholds
|
||
ELBOW_BOX_X_INNER: 5, // ← TUNE: inner X margin each elbow must stay outside
|
||
ELBOW_BOX_Y_MAX: 50, // ← TUNE: Y below which elbow crossing is forbidden
|
||
};
|
||
```
|
||
|
||
### Unit converters
|
||
|
||
```javascript
|
||
IK.stepsToRad(steps) // steps → radians (for FK from jog state)
|
||
IK.radToSteps(rad) // radians → steps (for IK result → BLE command)
|
||
IK.stepsToDeg(steps) // steps → degrees (for readout display)
|
||
IK.radToDeg(rad) // radians → degrees
|
||
```
|
||
|
||
### Lookup table
|
||
|
||
`IK.LOOKUP_TABLE` — 36 character positions (A–Z, 0–9, +, -, *, ,) in mm.
|
||
`IK.lookup('T')` → `{ x: 1.3, y: 97.8 }`.
|
||
|
||
---
|
||
|
||
## 7. Stepper Test Section (`web/js/sections/stepper-test.js`)
|
||
|
||
### State
|
||
|
||
```javascript
|
||
let steps1 = 0; // accumulated step count from home for motor 1
|
||
let steps2 = 0; // accumulated step count from home for motor 2
|
||
```
|
||
|
||
Step counts are the authoritative position. All FK calls convert steps → radians first.
|
||
|
||
### SVG viewport
|
||
|
||
```javascript
|
||
const SV = {
|
||
W: 480, H: 380,
|
||
SCALE: 1.35, // px/mm
|
||
OX: 240, // SVG x of world origin
|
||
OY: 295, // SVG y of world origin (base line, motors here)
|
||
wx(worldX) { return this.OX + worldX * this.SCALE; },
|
||
wy(worldY) { return this.OY - worldY * this.SCALE; }, // Y inverted
|
||
svgToWorldX(sx) { return (sx - this.OX) / this.SCALE; },
|
||
svgToWorldY(sy) { return -(sy - this.OY) / this.SCALE; },
|
||
};
|
||
```
|
||
|
||
### Motor marker positions in SVG (match FK convention)
|
||
|
||
```javascript
|
||
// M1 at -d2 (left side), M2 at +d2 (right side)
|
||
motor1sx = SV.wx(-IK.ARM.d2);
|
||
motor2sx = SV.wx(+IK.ARM.d2);
|
||
```
|
||
|
||
### IK click-to-move flow
|
||
|
||
1. SVG click → `SV.svgToWorldX/Y(event coords)` → world `(x, y)`
|
||
2. `IK.checkWorkspace(x, y)` — reject with log message if invalid
|
||
3. `IK.solve(x, y)` → `{ theta1, theta2 }`
|
||
4. `IK.radToSteps(theta1/2)` → new step counts
|
||
5. Compute deltas: `delta1 = newSteps1 - steps1`
|
||
6. Send BLE: `S1+<delta1>`, `S2+<delta2>`
|
||
7. Update `steps1/2`, re-render arm
|
||
|
||
### Ghost arm (hover preview)
|
||
|
||
On `mousemove` over SVG: calls `checkWorkspace` → if valid, calls `showGhost(theta1, theta2)`
|
||
which renders a translucent preview arm. If invalid, hides ghost.
|
||
|
||
### Jog controls
|
||
|
||
Manual per-motor step controls. Delta applied directly to `steps1/2`, then
|
||
`renderArmFromSteps()` redraws. BLE command sent as `S<motor><±delta>`.
|
||
|
||
---
|
||
|
||
## 8. Development Workflow
|
||
|
||
### Running locally
|
||
|
||
```bash
|
||
# From project root:
|
||
python -m http.server 8080
|
||
# Then open http://localhost:8080 in Chrome/Edge
|
||
```
|
||
|
||
> Web Bluetooth requires Chrome or Edge. It does NOT work in Firefox.
|
||
> It works over `localhost` without HTTPS.
|
||
|
||
### PlatformIO build
|
||
|
||
```bash
|
||
pio run # compile
|
||
pio run --target upload # flash
|
||
pio device monitor # serial monitor (115200 baud)
|
||
```
|
||
|
||
### Cache-buster reminder
|
||
|
||
If you make JS changes and the browser still shows old behaviour, hard-refresh
|
||
(`Ctrl+Shift+R`) or open DevTools → Application → Clear Storage.
|
||
The `?v=Date.now()` on imports handles this automatically on page load.
|
||
|
||
---
|
||
|
||
## 9. What Is Done
|
||
|
||
- [x] SPA shell (`index.html`) with collapsible sidebar, hamburger button, CSS dark theme
|
||
- [x] Hash-based router (`router.js`) with mount/unmount lifecycle
|
||
- [x] BLE singleton (`ble.js`) — connect, write-without-response, NOTIFY subscription
|
||
- [x] Kinematics module (`kinematics.js`) — IK, FK, workspace check, lookup table
|
||
- [x] Stepper Test section (`stepper-test.js`):
|
||
- SVG SCARA arm visualiser with correct motor convention
|
||
- Click-to-move via IK with workspace validation
|
||
- Ghost arm hover preview
|
||
- Per-motor jog controls (±10, ±50, ±100, ±500 steps)
|
||
- Motor zero (HOME) buttons
|
||
- Current position readout panel (X/Y, θ1/θ2, steps)
|
||
- Move-to-XY numeric input with Go button
|
||
- Board boundary + mechanism exclusion zone visualisation
|
||
- Simulation badge (yellow) when BLE not connected
|
||
- [x] Board Control section (`board-control.js`):
|
||
- Interactive map with clickable spots
|
||
- Speed and Acceleration controls
|
||
- Background image selector with config memory
|
||
- [x] Input Text section (`input-text.js`):
|
||
- Text input to spell words automatically
|
||
- Delay, Speed, and Acceleration sliders
|
||
- Position overlay marker on background
|
||
- [x] Sequence Editor section (`sequence-editor.js`) & Runner:
|
||
- Vertical list builder for custom sequences (Spots or X/Y).
|
||
- JSON export to persist sequences to `sequences.json`.
|
||
- Quick Sequences UI in `stepper-test`, `board-control`, and `input-text`.
|
||
- Shared `sequence-runner.js` to handle asynchronous execution and delays.
|
||
- [x] Firmware (Modularized) — `BleManager`, `MotorController`, `StatusLed`, and `main.cpp` for routing.
|
||
|
||
---
|
||
|
||
## 10. What Remains (Planned Sections)
|
||
|
||
The router currently only has three routes. These sections need to be created/expanded:
|
||
|
||
### `board-control` section (implemented)
|
||
- Uses predefined background maps (SVG/PNG) with corresponding JSON config files specifying clickable coordinates in physical mm.
|
||
- Click a spot on the map or select from a compact list → IK move to coordinates.
|
||
- Supports movement modes (e.g., Direct, Erratic, Random, Snaky).
|
||
- Requires arm to be homed; homing state is persisted across sessions via `localStorage`.
|
||
|
||
### `input-text` section (implemented)
|
||
- Keyboard input that sequentially spells out words on the board.
|
||
- Requires background `spots.json` to have `"hasAlphabet": true`.
|
||
- Persisted Speed, Acceleration, and Delay settings.
|
||
|
||
### `sequence-editor` section (implemented)
|
||
- Record and replay a sequence of moves.
|
||
- Draft sequences auto-save to `localStorage`.
|
||
- Simple list UI: add Spot step, add XY step, delete step, reorder, play sequence.
|
||
- Export as JSON to paste into `web/assets/backgrounds/<bg>/sequences.json`.
|
||
|
||
To add a new section:
|
||
1. Create `web/js/sections/<name>.js` exporting `{ mount(el), unmount() }`
|
||
2. In `index.html` add the import: `const { default: XSection } = await import('./web/js/sections/<name>.js?v=${V}');`
|
||
3. Register: `Router.register('<name>', XSection);`
|
||
4. Add a nav button in the sidebar HTML inside `index.html`
|
||
|
||
---
|
||
|
||
## 11. Key Design Decisions (Why Things Are The Way They Are)
|
||
|
||
| Decision | Reason |
|
||
|---|---|
|
||
| **No WiFi / no server** | User requirement — BLE only, no hosting needed |
|
||
| **`?v=Date.now()` cache-buster** | ES module imports are cached by the browser; without this, stale code runs silently after edits |
|
||
| **Steps as primary state** | The firmware tracks absolute step counts. Angles are derived by `stepsToRad()`. This keeps web ↔ firmware in sync. |
|
||
| **M1 at -d2, M2 at +d2** | Corrects a hardware variable swap in C++ firmware where angle2 goes to stepper1. Verified by tracing `xmd = x - d2` to the right motor and `xpd = x + d2` to the left motor. |
|
||
| **`forward()` picks upward EE** | Two circle-circle intersections exist; the mechanism always operates in the "above" configuration. The lower solution is physically blocked by the board. |
|
||
| **`armsCrossed()` guard** | Prevents IK moves that would drive an elbow into the mechanism housing from the wrong side. Tunable via `LIMITS.ELBOW_BOX_X_INNER` and `ELBOW_BOX_Y_MAX`. |
|
||
| **Linux/BlueZ stability** | `pAdv->setMaxPreferred(0x0C)` is mandatory in firmware. Without it, Linux/ChromeOS will drop the connection immediately after the handshake. |
|
||
| **No dynamic BLE imports**| `index.html` must import `ble.js` via static `import`. Dynamic cache-busters create multiple instances of the BLE singleton, isolating UI event listeners! |
|
||
| **`fivebarIKGame.js` is reference only** | The original site's game uses a different angle convention (absolute degrees, different home, with `ikElbowSigns` tracking). We use the C++ IK formula instead because it directly produces step deltas from home. `fivebarIKGame.js` is kept in the repo for reference and understanding, not imported. It also shouldn't be IK main reference. Original github project `nerd-sniped/WijiBoard` should be. |
|
||
| **Hash-based routing** | Keeps the SPA working from `file://` and simple static servers without needing a history API setup |
|
||
| **Persistent Homing State** | The UI uses `localStorage.getItem('wiji_homed')` to track if the arm has been homed during the user's ongoing interaction. This avoids forcing the user to re-home every time they switch tabs or pages. Sending `HOMEALL` sets this to `true`. |
|
||
| **Stateless Sequence Storage** | To remain serverless, sequences are stored per-background in `web/assets/backgrounds/<bg>/sequences.json`. The Sequence Editor generates the JSON payload, which the user manually copy-pastes into the file to persist. Sequences marked `"favorite": true` appear automatically in Quick Sequences cards across the app. |
|
||
| **No Emojis in Logging** | Emojis in `UI.log` and console log statements are strictly forbidden. Always use text prefixes like `[ERROR]`, `[WARNING]`, `[INFO]` instead. |
|
||
|
||
---
|
||
|
||
## 12. Known Issues / Things to Verify on Hardware
|
||
|
||
- **Motor direction / sign**: The signs of the IK formulas (`theta1 = q - w1`, `theta2 = r + w2`)
|
||
assume a specific physical motor orientation. If the real arm moves in the wrong direction
|
||
when clicking a target, negate the step delta for that motor in `moveToXY()`.
|
||
|
||
- **Home position**: Stall homing is fully implemented matching the original C++ routine but adjusted to align with physical motor mappings. Homing pushes the motors against their physical limits and sets `steps1 = -1024` (-180°, points left) and `steps2 = 0` (0°, points right). This corresponds to the arms folded OUTWARDS.
|
||
- The initial web simulation starts with these coordinates, meaning the UI assumes the arm has already been homed before the browser connects.
|
||
- Homing can be triggered individually per-motor (`HOME1`, `HOME2`) or combined (`HOMEALL`) from the web UI.
|
||
|
||
- **LIMITS tuning**: All values in `LIMITS` in `kinematics.js` are estimates based on the
|
||
original project's data. They need physical verification:
|
||
- `BOX_HALF_W`, `BOX_Y_MAX`: size of the actual motor housing
|
||
- `ELBOW_BOX_X_INNER`, `ELBOW_BOX_Y_MAX`: elbow clearance above the housing
|
||
|
||
- **Step speed / acceleration**: `DEFAULT_SPEED = 600`, `DEFAULT_ACCEL = 100` in firmware.
|
||
28BYJ-48 motors are slow; these may need adjustment. Adjustable at runtime via `SPD:<n>`
|
||
and `ACC:<n>` BLE commands.
|
||
|
||
---
|
||
|
||
## 13. Reference: `fivebarIKGame.js`
|
||
|
||
This file (in the project root) is the original website's game kinematics. **Do not import it.**
|
||
Use it as a reference for understanding the mechanism, especially:
|
||
|
||
- `S.solveFK(a1_deg, a2_deg)` — forward kinematics in absolute degrees, with stateful branch
|
||
tracking (`fkBranchIndex`, `eeHistory`) and `isValidElbow` cross-product check.
|
||
- `S.solveIK(target, currentAngles)` — IK with `ikElbowSigns` continuity tracking and motor
|
||
angle limits: M1 = [-330°, -90°], M2 = [-90°, +150°] (in absolute angle convention).
|
||
- `intersectCircles(p1, p2, r)` — correct circle-circle intersection.
|
||
- `noGoRadius: 50` — the game's centre exclusion radius in mm.
|
||
- Home state in game: `a1 = -180°`, `a2 = 0°` → EE at approximately (0, 50.3 mm).
|
||
|
||
The motor angle limits and no-go radius from the game are good starting points for tuning
|
||
the LIMITS object in `kinematics.js`.
|