Skill: ivangdavila/robot (version 1.0.0)

--- name: Robot slug: robot version: 1.0.0 description: Build robots from hobby to industrial with hardware wiring, ROS2, motion planning, and safety constraints. metadata: {"clawdbot":{"emoji":"🤖","requires":{"bins":[]},"os":["linux","darwin","win32"]}} --- ## When to Use User needs robotics help — Arduino/ESP32 wiring, ROS2 configuration, motor control, sensor integration, or industrial robot programming. Agent handles hardware selection, code generation, and debugging across hobby to professional contexts. ## Architecture Memory lives in `~/robot/` with tiered structure. See `memory-template.md` for initial setup. ``` ~/robot/ ├── memory.md # HOT: inventory + active project ├── inventory.md # Hardware owned (boards, sensors, motors) ├── projects/ # Per-project configs and learnings │ └── {name}.md # Project-specific notes ├── corrections.md # What failed + fixes found └── archive/ # Completed project summaries ``` ## Quick Reference | Topic | File | |-------|------| | Memory setup | `memory-template.md` | | Arduino, ESP32, RPi wiring | `hardware.md` | | Sensors: wiring + code | `sensors.md` | | Motors: types + drivers | `motors.md` | | ROS1/ROS2, Gazebo, MoveIt | `ros.md` | | Industrial arms (ABB, KUKA, UR) | `industrial.md` | | Systematic troubleshooting | `debugging.md` | | Common project templates | `projects.md` | ## Core Rules ### 1. Check Memory First Before ANY recommendation: 1. Read ~/robot/memory.md — what hardware does user have? 2. Check ~/robot/projects/ — is there an active project? 3. Check ~/robot/corrections.md — past failures to avoid? ### 2. ASK Exact Hardware Before ANY code: exact board model, exact sensor/motor models, voltage rails. "Arduino" is ambiguous (Uno? Nano? ESP32-based?). Add to inventory once confirmed. ### 3. Update Memory Proactively | Event | Action | |-------|--------| | User mentions hardware they own | Add to inventory.md | | User starts new project | Create projects/{name}.md | | Something fails → fix found | Log in corrections.md | | Project completed | Archive to archive/ | ### 4. Version Everything Always ask and specify: - Arduino core version, library versions - ROS distro (Humble, Iron, Foxy, Noetic) - Firmware versions for industrial controllers ### 5. Simulation First for Industrial For ABB/KUKA/Fanuc/UR code: - Always clarify: simulation or real hardware? - Never generate motion code without safety discussion - Include speed limits and safety checks in ALL code ## Hardware Traps ### Board Selection - `Servo.h` crashes on ESP32 — use `ESP32Servo.h` (different API) - `analogWrite()` missing on ESP32 — use `ledcWrite()` + channel setup - ESP32 GPIO 6-11 are flash pins — touching them = crash - ESP32 GPIO 34-39 are input-only — output silently fails - Arduino pins 0,1 are Serial — using them breaks upload ### Voltage and Current - 5V sensor → 3.3V board without divider — burns pin permanently - GPIO sourcing >40mA (Uno) or >12mA (ESP32) — pin damage over time - Motor on same rail as logic — brownouts cause random resets - No common ground between boards — erratic sensor readings ### Sensors - HC-SR04 Echo pin 5V → 3.3V board — needs divider or level shifter - DHT22 read interval <2s — returns stale/error values - I2C bus >30cm without pullups — intermittent failures - MPU6050 FIFO overflow if not read fast — readings corrupt ## ROS Traps - Mixing `rospy` (ROS1) and `rclpy` (ROS2) — import errors - Forgot `source install/setup.bash` — "package not found" - QoS mismatch publisher/subscriber — messages silently dropped - `static_transform_publisher` syntax varies by ROS2 version - Gazebo Classic plugins ≠ Ignition/Fortress plugins ## Industrial Traps - MoveL through singularity — joint whip, dangerous - Wrong coordinate frame (base vs world vs tool) — unexpected position - Omitting MoveJ before MoveL — path through obstacles - Speed too high in shared human space — safety violation - Bypassing SafeMove/SafetyIO signals — defeats physical safeties

path: debugging.md
content: # Debugging — Systematic Troubleshooting

## The Debug Order

Always follow this sequence. Most problems are in steps 1-3.

```
1. POWER
   ↓
2. WIRING  
   ↓
3. LIBRARY/VERSION
   ↓
4. CODE LOGIC
```

**Do NOT skip to code debugging until hardware is verified.**

## Step 1: Power Verification

### Checklist
```
□ Power LED on board is lit
□ Voltage at board matches spec (3.3V or 5V rail)
□ Voltage under load (motor running) doesn't drop >10%
□ Motors/servos on SEPARATE power from logic
□ All grounds connected together
```

### Measuring Power

```
Multimeter settings:
- DC Voltage: Check rails
- DC Current: Check total draw (in series with power)

Expected voltages:
- USB: 4.75V - 5.25V
- 3.3V rail: 3.0V - 3.6V
- LiPo 1S: 3.0V (empty) - 4.2V (full)
- LiPo 2S: 6.0V - 8.4V
```

### Common Power Problems

| Symptom | Likely Cause | Fix |
|---------|--------------|-----|
| Board resets randomly | Motor brownout | Separate power rails |
| Board won't boot | Voltage too low | Check USB cable, power supply |
| Works on USB, not battery | Battery voltage wrong | Check battery voltage, regulator |
| Components hot | Overcurrent | Check for shorts, reduce load |

## Step 2: Wiring Verification

### Visual Check
```
□ No loose connections
□ No bare wires touching
□ Correct pins (double-check datasheet)
□ Pull-up/pull-down resistors where needed
□ Voltage dividers for 5V→3.3V
```

### Continuity Test (Multimeter)
```
1. Power OFF
2. Set multimeter to continuity (beep mode)
3. Touch probes to both ends of each wire
4. Should beep immediately
5. Check there's NO continuity between power and ground (short check)
```

### I2C Debug

```cpp
// Run this FIRST if I2C device not responding
#include <Wire.h>

void setup() {
  Wire.begin();
  Serial.begin(115200);
  delay(1000);
  
  Serial.println("I2C Scanner");
  for (byte addr = 1; addr < 127; addr++) {
    Wire.beginTransmission(addr);
    byte error = Wire.endTransmission();
    if (error == 0) {
      Serial.printf("Device at 0x%02X\n", addr);
    }
  }
  Serial.println("Scan complete");
}

void loop() {}
```

**If scan finds nothing:**
1. SDA/SCL swapped? (most common)
2. Pull-up resistors present? (4.7kΩ for long wires)
3. Correct voltage level? (5V device on 3.3V bus)
4. Device address correct? (check if configurable)

### SPI Debug

```
□ MOSI → MOSI (or SDI)
□ MISO → MISO (or SDO)  
□ SCK → SCK (or CLK)
□ CS → Correct chip select pin
□ Clock speed not too high (try 1MHz first)
```

## Step 3: Library & Version Check

### Arduino IDE
```cpp
// Print library version at startup
void setup() {
  Serial.begin(115200);
  
  // Most libraries have version macros
  #ifdef SERVO_VERSION
    Serial.printf("Servo: %s\n", SERVO_VERSION);
  #endif
}
```

### Common Version Conflicts

| Symptom | Cause | Fix |
|---------|-------|-----|
| Compile error on ESP32 | Arduino library on ESP32 | Use ESP32-specific library |
| "Multiple definitions" | Library installed twice | Remove duplicate from libraries folder |
| Works on Uno, fails on Mega | Pin mapping different | Check board-specific pins |
| ROS2 "module not found" | Didn't source workspace | `source install/setup.bash` |

### Library Compatibility Matrix

| Library | Arduino AVR | ESP32 | RP2040 |
|---------|-------------|-------|--------|
| Servo.h | ✅ | ❌ | ✅ |
| ESP32Servo | ❌ | ✅ | ❌ |
| Wire.h | ✅ | ✅ | ✅ |
| SPI.h | ✅ | ✅ | ✅ |
| analogWrite() | ✅ | ❌ | ✅ |

## Step 4: Code Logic Debug

### Serial Debug Pattern

```cpp
#define DEBUG 1

#if DEBUG
  #define DBG(x) Serial.println(x)
  #define DBGF(x, ...) Serial.printf(x, __VA_ARGS__)
#else
  #define DBG(x)
  #define DBGF(x, ...)
#endif

void setup() {
  Serial.begin(115200);
  DBG("Setup started");
}

void loop() {
  int sensorVal = analogRead(A0);
  DBGF("Sensor: %d\n", sensorVal);
  
  if (sensorVal > 500) {
    DBG("Threshold exceeded");
    // ... action ...
  }
}
```

### Timing Debug

```cpp
unsigned long start = micros();
// ... code to measure ...
unsigned long elapsed = micros() - start;
Serial.printf("Took %lu μs\n", elapsed);
```

### State Machine Debug

```cpp
enum State { IDLE, MOVING, STOPPED, ERROR };
State currentState = IDLE;

const char* stateNames[] = {"IDLE", "MOVING", "STOPPED", "ERROR"};

void changeState(State newState) {
  Serial.printf("State: %s -> %s\n", 
    stateNames[currentState], 
    stateNames[newState]);
  currentState = newState;
}
```

## Quick Diagnosis Table

| Symptom | First Check | Second Check | Third Check |
|---------|-------------|--------------|-------------|
| Nothing works | Power LED on? | Correct board selected? | USB cable data-capable? |
| Upload fails | Correct port? | Board in boot mode? | Another serial monitor open? |
| Sensor returns 0 | Wiring correct? | I2C address right? | Library initialized? |
| Motor doesn't move | Enable pin set? | H-bridge powered? | PWM value > 0? |
| Servo jitters | Power supply? | Signal wire loose? | PWM frequency correct? |
| Random resets | Power rail stable? | Watchdog timeout? | Stack overflow? |
| I2C hangs | Pull-ups present? | Clock stretching? | Bus locked? |

## ESP32-Specific Issues

| Issue | Cause | Fix |
|-------|-------|-----|
| Boot loop | GPIO 12 high at boot | Don't use GPIO 12, or pull low |
| WiFi crashes system | GPIO 6-11 used | These are flash pins, never use |
| ADC2 not working | WiFi is on | Use ADC1 pins when WiFi active |
| Serial garbage | Wrong baud rate | Match Serial.begin() with monitor |
| OTA fails | Partition too small | Check partition scheme in IDE |

## When to Ask for Help

After verifying:
1. ✅ Power is correct and stable
2. ✅ Wiring matches schematic
3. ✅ Correct library for board
4. ✅ Serial debug shows expected flow

Provide:
- Exact board model and core version
- Library names and versions
- Schematic or clear wiring description
- Serial output showing the problem
- What you already tried


path: hardware.md
content: # Hardware — Arduino, ESP32, Raspberry Pi

## Pin Restrictions by Board

### ESP32
| Pins | Restriction | Consequence |
|------|-------------|-------------|
| GPIO 6-11 | Flash memory | Touch = crash, boot loop |
| GPIO 34-39 | Input only | Output code compiles, does nothing |
| GPIO 0 | Boot mode | Pulled low = won't boot |
| GPIO 2 | Boot mode | Must be low or floating at boot |
| GPIO 12 | Boot mode | Must be low at boot (3.3V flash) |

### Arduino Uno/Nano
| Pins | Restriction | Consequence |
|------|-------------|-------------|
| 0, 1 | Serial TX/RX | Upload fails if connected |
| 13 | Built-in LED | External LED may conflict |
| A6, A7 (Nano) | Analog input only | digitalRead returns garbage |

### Raspberry Pi Pico
| Pins | Restriction | Consequence |
|------|-------------|-------------|
| GP23 | SMPS mode | Affects power regulation |
| GP24 | VBUS sense | Reserved for USB detection |
| GP25 | Built-in LED | Conflicts if used externally |

## Common Wiring Mistakes

```
WRONG: Motor shares 5V with Arduino
       ┌───────────┐
  USB──┤ Arduino   ├──5V──Motor
       └───────────┘
Result: Motor draws current → voltage drops → Arduino resets

RIGHT: Separate power for motor
       ┌───────────┐
  USB──┤ Arduino   │
       └─────┬─────┘
             │ GND (shared)
       ┌─────┴─────┐
  12V──┤ L298N     ├──Motor
       └───────────┘
```

## I2C Troubleshooting

```cpp
// Scan for devices — first debug step
#include <Wire.h>
void setup() {
  Wire.begin();
  Serial.begin(115200);
  for (byte addr = 1; addr < 127; addr++) {
    Wire.beginTransmission(addr);
    if (Wire.endTransmission() == 0) {
      Serial.printf("Found: 0x%02X\n", addr);
    }
  }
}
```

Common addresses:
- MPU6050: 0x68 (or 0x69 if AD0 high)
- OLED SSD1306: 0x3C (or 0x3D)
- BMP280: 0x76 (or 0x77)

If scan finds nothing:
1. Check SDA/SCL not swapped
2. Add 4.7kΩ pullups if wire length >20cm
3. Reduce I2C speed: `Wire.setClock(100000);`

## ESP32 PWM (NOT analogWrite)

```cpp
// ESP32 — ledcWrite replaces analogWrite
const int ledChannel = 0;
const int freq = 5000;
const int resolution = 8;  // 0-255

void setup() {
  ledcSetup(ledChannel, freq, resolution);
  ledcAttachPin(LED_PIN, ledChannel);
}

void loop() {
  ledcWrite(ledChannel, 127);  // 50% duty
}
```

## Servo on ESP32

```cpp
// WRONG: #include <Servo.h> — crashes or doesn't compile

// RIGHT: ESP32Servo library
#include <ESP32Servo.h>

Servo myServo;

void setup() {
  myServo.attach(13);  // Any PWM pin
}

void loop() {
  myServo.write(90);   // Center
}
```

## Power Budget Calculator

```
Component               Typical mA    Peak mA
─────────────────────────────────────────────
Arduino Uno board           50          50
ESP32 (WiFi active)        100         240
ESP32 (Bluetooth)           80         130
Servo SG90 (moving)        100         500
DC motor (small)           200         800
WS2812B LED (white)         60/LED     60/LED
HC-SR04 ultrasonic          15          15
DHT22                        2           2
OLED SSD1306                 20          20
─────────────────────────────────────────────

USB max: 500mA
If total > 400mA: use external power supply
```

## MicroPython (Pico/ESP32)

```python
from machine import Pin, PWM, I2C

# PWM for servo (50Hz)
servo = PWM(Pin(15))
servo.freq(50)
servo.duty_u16(4915)  # 1.5ms = center (duty = pulse_ms/20ms * 65535)

# I2C
i2c = I2C(0, sda=Pin(0), scl=Pin(1), freq=400000)
print(i2c.scan())  # List of addresses found

# Digital input with pull-up
button = Pin(14, Pin.IN, Pin.PULL_UP)
print(button.value())  # 0 = pressed, 1 = released
```


path: industrial.md
content: # Industrial Robotics — Safety Critical

## ⚠️ SIMULATION vs REAL HARDWARE

Before generating ANY motion code, clarify:
1. **Target:** Simulation (RobotStudio, KUKA.Sim) or real robot?
2. **Safety:** If real, what safety systems are in place?
3. **Speed:** Start at ≤10% speed for testing

Code that works in simulation can cause injury on real hardware.

## Motion Types

| Command | Path | Use Case | Danger |
|---------|------|----------|--------|
| MoveJ / PTP | Joint space (unpredictable) | Reorientation, clear space | Path goes through obstacles |
| MoveL / LIN | Straight line | Welding, dispensing | Singularities cause joint whip |
| MoveC / CIRC | Arc | Curved paths | Requires 3 points minimum |

**Rule:** Always MoveJ to approach → MoveL for precision work.

## Singularities — Where Robots Lose Control

| Type | Position | Symptom |
|------|----------|---------|
| Wrist | J4 and J6 axes aligned | J4/J6 spin rapidly |
| Shoulder | Wrist center on J1 axis | J1 spins uncontrolled |
| Elbow | Arm fully extended/folded | Infinite solutions |

**Solution:** Plan paths that avoid these zones. Use MoveJ to pass through.

## Coordinate Frames

| Frame | Description | When to Use |
|-------|-------------|-------------|
| World | Fixed reference (floor) | Multi-robot cells |
| Base | Robot mounting point | Single robot, default |
| Tool (TCP) | End effector tip | Welding torch, gripper |
| Workobject | Workpiece reference | Part on table |

**Common error:** Wrong frame = wrong position. Always specify explicitly.

## Language Quick Reference

### ABB RAPID (IRC5)
```
MODULE MainModule
    PROC main()
        MoveJ home, v500, fine, tool0;        ! Joint move to home
        MoveL pick, v100, z10, gripper;       ! Linear to pick position
        Set DO_Gripper;                        ! Digital output ON
        WaitTime 0.5;
        MoveL place, v100, fine, gripper;
        Reset DO_Gripper;
    ENDPROC
ENDMODULE
```

### KUKA KRL (KRC4/5)
```
DEF main()
    $VEL.CP = 0.5                              ; Cartesian velocity m/s
    $ACC.CP = 1.0                              ; Acceleration
    
    PTP HOME                                   ; Joint move
    LIN pick_pos C_DIS                         ; Linear with blending
    $OUT[1] = TRUE                             ; Digital output
    WAIT SEC 0.5
    LIN place_pos                              ; Linear exact
    $OUT[1] = FALSE
END
```

### Universal Robots (URScript)
```python
def main():
    # Velocity: joint rad/s, linear m/s
    movej(home, a=1.4, v=1.05)                # Joint move
    movel(pick_pos, a=1.2, v=0.25)            # Linear move
    set_digital_out(0, True)                   # Gripper close
    sleep(0.5)
    movel(place_pos, a=1.2, v=0.25)
    set_digital_out(0, False)
```

## Safety Interlocks — NEVER Bypass

```
// BAD — ignores safety, robot continues
WHILE NOT SafetyOK DO
    WaitTime 0.1;
ENDWHILE

// GOOD — stops and requires manual intervention
IF NOT SafetyOK THEN
    Stop;
    TPWrite "Safety system triggered - check area";
    ! Robot halts, operator must acknowledge
ENDIF
```

## Speed Limits by Zone

| Zone | Max Speed | Typical Use |
|------|-----------|-------------|
| Collaborative (human present) | 250 mm/s | UR cobots, shared workspace |
| Restricted (fenced, light curtain) | 1000 mm/s | Standard industrial |
| Emergency (safety triggered) | 0 | Immediate stop |

**Code must check zone before setting speed.** Hardcoded high speeds = safety violation.

## TCP Calibration — Cannot Generate

Tool Center Point requires physical measurement:
1. Touch fixed point from 4+ orientations
2. Controller calculates offset
3. Store named tool

**Never assume TCP values.** Always ask: "Is the tool calibrated? What's the tool name?"


path: memory-template.md
content: # Memory Setup — Robot Skill

## Initial Setup

Create the directory structure on first robotics interaction:

```bash
mkdir -p ~/robot/projects ~/robot/archive
touch ~/robot/memory.md ~/robot/inventory.md ~/robot/corrections.md
```

## File Templates

### memory.md (HOT — always read)

```markdown
# Robot Memory

## Active Project
<!-- Current project name and status -->
<!-- Format: "project: status (board)" -->

## Quick Inventory
<!-- Most-used hardware, 1 line each -->
<!-- Format: "type: model (notes)" -->

## Recent Corrections
<!-- Last 5 fixes, newest first -->
<!-- Format: "symptom → fix" -->

## Index
- Full inventory: `inventory.md`
- Project details: `projects/{name}.md`
- All corrections: `corrections.md`

---
*Last: YYYY-MM-DD*
```

### inventory.md (WARM — load when selecting hardware)

```markdown
# Hardware Inventory

## Microcontrollers
<!-- Format: model | voltage | pins | notes -->
| Model | Logic | USB | Notes |
|-------|-------|-----|-------|
| | | | |

## Sensors
<!-- Format: model | type | interface | address/pins -->
| Model | Type | I/F | Addr/Pins | Notes |
|-------|------|-----|-----------|-------|
| | | | | |

## Actuators
<!-- Format: model | type | voltage | current -->
| Model | Type | Voltage | Current | Notes |
|-------|------|---------|---------|-------|
| | | | | |

## Drivers & Modules
<!-- Motor drivers, power modules, communication -->
| Model | Purpose | Notes |
|-------|---------|-------|
| | | |

## Power Supplies
| Source | Voltage | Current | Notes |
|--------|---------|---------|-------|
| | | | |

---
*Last updated: YYYY-MM-DD*
```

### corrections.md (WARM — check before debugging)

```markdown
# Corrections Log

## Format
Each entry: date | symptom | root cause | fix

## Log

<!-- Newest first, keep last 30 -->

---
*Entries older than 90 days archived to archive/corrections-YYYY.md*
```

### projects/{name}.md (COLD — load for specific project)

```markdown
# Project: {Name}

## Status
<!-- active | paused | complete -->

## Hardware
- Board: 
- Sensors: 
- Actuators: 
- Power: 

## Wiring
<!-- Pin assignments -->
| Component | Pin | Notes |
|-----------|-----|-------|
| | | |

## Code
- Repo/location: 
- Libraries: 

## Issues Found
<!-- Project-specific gotchas -->

## Notes

---
*Started: YYYY-MM-DD*
*Last: YYYY-MM-DD*
```

## Memory Rules

### Budget Limits
| File | Max Lines | Action When Exceeded |
|------|-----------|---------------------|
| memory.md | 50 | Move details to inventory/projects |
| inventory.md | 150 | Archive unused hardware |
| corrections.md | 100 | Archive old entries |
| project/*.md | 100 | Summarize, archive details |

### When to Update

| Event | Update |
|-------|--------|
| User mentions hardware they own | Add to inventory.md, update memory.md quick list |
| User starts new project | Create projects/{name}.md, set as active in memory.md |
| Code fails → fix found | Add to corrections.md, summarize in memory.md |
| User asks about past project | Load from projects/ or archive/ |
| Project completed | Move to archive/, clear from memory.md active |

### Tiered Loading

1. **Always load:** `memory.md` (50 lines max)
2. **Load on hardware selection:** `inventory.md`
3. **Load on debugging:** `corrections.md`
4. **Load on project context:** `projects/{name}.md`
5. **Load on explicit request:** `archive/*`

### Archive Rules

- Projects inactive >30 days → prompt to archive
- Corrections >90 days old → move to `archive/corrections-YYYY.md`
- Completed projects → `archive/{name}-summary.md` (condensed)


path: motors.md
content: # Motors — Types, Drivers & Control

## Motor Types Comparison

| Type | Precision | Torque | Speed | Use Case |
|------|-----------|--------|-------|----------|
| DC Brushed | Low | Medium | High | Wheels, fans |
| DC Brushless | Medium | High | Very High | Drones, spindles |
| Servo (hobby) | High | Low | Medium | Arms, pan/tilt |
| Servo (industrial) | Very High | High | Medium | CNC, robots |
| Stepper | Very High | Medium | Low | 3D printers, CNC |

## DC Motors

### L298N Driver (2A per channel)

```
Power: 5V-35V motor supply
Logic: 5V (from board or onboard regulator)
Enable: PWM for speed control
IN1/IN2: Direction
```

```cpp
const int ENA = 9;   // PWM
const int IN1 = 8;
const int IN2 = 7;

void setup() {
  pinMode(ENA, OUTPUT);
  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT);
}

void setMotor(int speed) {  // -255 to 255
  if (speed > 0) {
    digitalWrite(IN1, HIGH);
    digitalWrite(IN2, LOW);
    analogWrite(ENA, speed);
  } else if (speed < 0) {
    digitalWrite(IN1, LOW);
    digitalWrite(IN2, HIGH);
    analogWrite(ENA, -speed);
  } else {
    digitalWrite(IN1, LOW);
    digitalWrite(IN2, LOW);
    analogWrite(ENA, 0);
  }
}
```

### TB6612FNG (1.2A per channel, more efficient)

```cpp
// Same logic as L298N but:
// - STBY pin must be HIGH to enable
// - Lower voltage drop (MOSFET vs BJT)
// - Better for battery-powered robots

const int STBY = 10;

void setup() {
  // ... pin setup ...
  pinMode(STBY, OUTPUT);
  digitalWrite(STBY, HIGH);  // Enable driver
}
```

### BTS7960 (43A, high power)

```cpp
// For motors >2A (power wheels, e-bikes)
const int RPWM = 5;
const int LPWM = 6;
const int R_EN = 7;
const int L_EN = 8;

void setup() {
  pinMode(RPWM, OUTPUT);
  pinMode(LPWM, OUTPUT);
  pinMode(R_EN, OUTPUT);
  pinMode(L_EN, OUTPUT);
  digitalWrite(R_EN, HIGH);
  digitalWrite(L_EN, HIGH);
}

void setMotor(int speed) {
  if (speed > 0) {
    analogWrite(RPWM, speed);
    analogWrite(LPWM, 0);
  } else {
    analogWrite(RPWM, 0);
    analogWrite(LPWM, -speed);
  }
}
```

## Servo Motors

### Hobby Servo (SG90, MG996R)

```
Signal: 50Hz PWM
Pulse width: 500μs (0°) to 2500μs (180°)
Power: 4.8V-6V (SEPARATE from logic!)
```

```cpp
// Arduino
#include <Servo.h>
Servo myServo;

void setup() {
  myServo.attach(9);
}

void loop() {
  myServo.write(90);  // Center
}
```

```cpp
// ESP32 — different library!
#include <ESP32Servo.h>
Servo myServo;

void setup() {
  myServo.attach(13, 500, 2500);  // pin, min_us, max_us
}
```

**Trap:** Servos draw 500mA+ when moving. Jitter = power issue.

### PCA9685 (16-channel PWM driver)

```cpp
#include <Adafruit_PWMServoDriver.h>

Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();

void setup() {
  pwm.begin();
  pwm.setPWMFreq(50);  // 50Hz for servos
}

void setServo(uint8_t channel, int angle) {
  // Map 0-180 to pulse length
  int pulse = map(angle, 0, 180, 102, 512);  // ~500-2500μs at 50Hz
  pwm.setPWM(channel, 0, pulse);
}
```

## Stepper Motors

### A4988 / DRV8825 Driver

```
STEP: Pulse = one step
DIR: HIGH/LOW = direction
MS1/MS2/MS3: Microstepping selection
ENABLE: LOW = enabled (active low!)
```

```cpp
const int STEP_PIN = 3;
const int DIR_PIN = 4;
const int ENABLE_PIN = 5;

void setup() {
  pinMode(STEP_PIN, OUTPUT);
  pinMode(DIR_PIN, OUTPUT);
  pinMode(ENABLE_PIN, OUTPUT);
  digitalWrite(ENABLE_PIN, LOW);  // Enable
}

void step(int steps, int delayUs) {
  for (int i = 0; i < abs(steps); i++) {
    digitalWrite(STEP_PIN, HIGH);
    delayMicroseconds(delayUs);
    digitalWrite(STEP_PIN, LOW);
    delayMicroseconds(delayUs);
  }
}

void setDirection(bool clockwise) {
  digitalWrite(DIR_PIN, clockwise ? HIGH : LOW);
}
```

### Microstepping Table

| MS1 | MS2 | MS3 | Resolution |
|-----|-----|-----|------------|
| LOW | LOW | LOW | Full step |
| HIGH | LOW | LOW | 1/2 step |
| LOW | HIGH | LOW | 1/4 step |
| HIGH | HIGH | LOW | 1/8 step |
| HIGH | HIGH | HIGH | 1/16 step |

**Note:** Higher microstepping = smoother but less torque.

### AccelStepper Library (recommended)

```cpp
#include <AccelStepper.h>

AccelStepper stepper(AccelStepper::DRIVER, STEP_PIN, DIR_PIN);

void setup() {
  stepper.setMaxSpeed(1000);     // steps/sec
  stepper.setAcceleration(500);  // steps/sec²
}

void loop() {
  stepper.moveTo(2048);  // Target position
  stepper.run();         // Call frequently!
}
```

## Brushless DC (BLDC)

### ESC Control (drones, etc.)

```cpp
// ESCs use same signal as servos
#include <Servo.h>  // or ESP32Servo

Servo esc;

void setup() {
  esc.attach(9);
  esc.writeMicroseconds(1000);  // Arm: min throttle
  delay(2000);                   // Wait for ESC to arm
}

void setThrottle(int percent) {
  // 1000μs = 0%, 2000μs = 100%
  int us = map(percent, 0, 100, 1000, 2000);
  esc.writeMicroseconds(us);
}
```

**Warning:** ESC arming sequence varies by manufacturer. Check docs.

## Power Calculations

```
Motor current × Voltage = Power (Watts)
Battery capacity (mAh) / Average current (mA) = Runtime (hours)

Example:
- 2× motors @ 500mA each = 1A
- ESP32 @ 100mA
- Sensors @ 50mA
- Total: 1.15A
- 2200mAh battery → ~1.9 hours runtime
```

## Common Problems

| Symptom | Cause | Fix |
|---------|-------|-----|
| Motor doesn't spin | Enable pin not set | Check STBY/EN pins |
| Motor only spins one direction | Broken H-bridge channel | Test with different IN pins |
| Motor stutters | Insufficient current | Use appropriate driver, check wiring |
| Servo jitters | Power supply noise | Separate power, add capacitor |
| Stepper skips steps | Speed too high | Reduce speed, check current limit |
| Stepper gets hot | Current too high | Adjust driver potentiometer |


path: projects.md
content: # Project Templates — Common Robot Builds

## Line Follower

### Hardware
- Board: Arduino Nano or ESP32
- Sensors: 3-5× TCRT5000 or QTR-8A array
- Motors: 2× N20 or TT motors with encoder
- Driver: TB6612FNG or L298N
- Power: 2S LiPo (7.4V) or 4×AA

### Wiring Diagram
```
                    TCRT5000 Array
                    [1][2][3][4][5]
                         │
                    Analog pins
                         │
┌─────────────────────────────────────┐
│           Arduino/ESP32             │
│  PWM1 PWM2  IN1 IN2 IN3 IN4        │
└──┬────┬─────┬───┬───┬───┬──────────┘
   │    │     │   │   │   │
┌──┴────┴─────┴───┴───┴───┴──┐
│        TB6612FNG           │
│   PWMA PWMB AIN1 AIN2 BIN1 BIN2    │
│         MOTORA    MOTORB   │
└─────────┬───────────┬──────┘
          M1          M2
```

### Core Algorithm
```cpp
const int sensors[] = {A0, A1, A2, A3, A4};
const int NUM_SENSORS = 5;
int sensorValues[NUM_SENSORS];
int weights[] = {-2, -1, 0, 1, 2};  // Position weights

int getLinePosition() {
  long weightedSum = 0;
  long sum = 0;
  
  for (int i = 0; i < NUM_SENSORS; i++) {
    sensorValues[i] = analogRead(sensors[i]);
    weightedSum += (long)sensorValues[i] * weights[i] * 1000;
    sum += sensorValues[i];
  }
  
  if (sum == 0) return 0;  // No line detected
  return weightedSum / sum;
}

void loop() {
  int position = getLinePosition();
  
  // PID control
  int error = position - 0;  // Target = center
  int motorSpeed = Kp * error + Kd * (error - lastError);
  lastError = error;
  
  setMotors(baseSpeed + motorSpeed, baseSpeed - motorSpeed);
}
```

---

## Self-Balancing Robot

### Hardware
- Board: ESP32 (faster PWM, more memory)
- IMU: MPU6050 or BNO055 (better)
- Motors: 2× JGA25-370 with encoder (high torque)
- Driver: TB6612FNG or L298N
- Power: 3S LiPo (11.1V)

### Key Code
```cpp
#include <MPU6050_light.h>

MPU6050 mpu(Wire);
float Kp = 60, Ki = 0.5, Kd = 20;
float integral = 0, lastError = 0;
float targetAngle = -2.5;  // Tune this! Robot leans slightly

void setup() {
  Wire.begin();
  mpu.begin();
  mpu.calcOffsets();  // KEEP STILL!
}

void loop() {
  mpu.update();
  float angle = mpu.getAngleX();
  
  float error = angle - targetAngle;
  integral += error;
  integral = constrain(integral, -100, 100);  // Anti-windup
  float derivative = error - lastError;
  
  float output = Kp*error + Ki*integral + Kd*derivative;
  output = constrain(output, -255, 255);
  
  setMotors(output, output);  // Both same direction
  lastError = error;
  
  delay(5);  // 200Hz control loop
}
```

**Tuning order:** Kp first (until oscillates), then Kd (dampen), finally Ki (steady-state).

---

## Robot Arm (3-6 DOF)

### Hardware
- Board: Arduino Mega (more PWM) or ESP32 + PCA9685
- Servos: MG996R (base, shoulder) + SG90 (wrist, gripper)
- Power: 5V 3A+ supply (SEPARATE from logic)
- Frame: Acrylic or 3D printed

### Inverse Kinematics (2-link planar)
```cpp
// For shoulder + elbow arm in XY plane
float L1 = 100;  // Upper arm length (mm)
float L2 = 100;  // Forearm length (mm)

bool inverseKinematics(float x, float y, float &theta1, float &theta2) {
  float d = sqrt(x*x + y*y);
  if (d > L1 + L2) return false;  // Unreachable
  
  float cos_theta2 = (x*x + y*y - L1*L1 - L2*L2) / (2*L1*L2);
  if (abs(cos_theta2) > 1) return false;
  
  theta2 = acos(cos_theta2);
  theta1 = atan2(y, x) - atan2(L2*sin(theta2), L1 + L2*cos(theta2));
  
  // Convert to degrees
  theta1 = theta1 * 180 / PI;
  theta2 = theta2 * 180 / PI;
  return true;
}

void moveTo(float x, float y) {
  float t1, t2;
  if (inverseKinematics(x, y, t1, t2)) {
    servo1.write(90 + t1);  // Offset to center
    servo2.write(90 + t2);
  }
}
```

---

## Differential Drive Robot (ROS2)

### Hardware
- Board: Raspberry Pi 4 + Arduino (motor control)
- Motors: JGB37-520 with encoder
- Driver: L298N or BTS7960
- Lidar: RPLidar A1 or YDLIDAR X4
- IMU: BNO055

### ROS2 Package Structure
```
my_robot/
├── my_robot/
│   ├── __init__.py
│   ├── motor_driver.py
│   └── odometry.py
├── launch/
│   └── robot.launch.py
├── config/
│   └── params.yaml
├── urdf/
│   └── robot.urdf.xacro
├── package.xml
└── setup.py
```

### Basic Motor Driver Node
```python
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist
import serial

class MotorDriver(Node):
    def __init__(self):
        super().__init__('motor_driver')
        self.sub = self.create_subscription(
            Twist, '/cmd_vel', self.cmd_callback, 10)
        self.serial = serial.Serial('/dev/ttyACM0', 115200)
        
    def cmd_callback(self, msg):
        linear = msg.linear.x
        angular = msg.angular.z
        
        # Differential drive kinematics
        wheel_base = 0.2  # meters
        left = linear - angular * wheel_base / 2
        right = linear + angular * wheel_base / 2
        
        # Send to Arduino
        cmd = f"{left:.2f},{right:.2f}\n"
        self.serial.write(cmd.encode())

def main():
    rclpy.init()
    node = MotorDriver()
    rclpy.spin(node)
```

### Arduino Firmware
```cpp
void loop() {
  if (Serial.available()) {
    String cmd = Serial.readStringUntil('\n');
    int comma = cmd.indexOf(',');
    float left = cmd.substring(0, comma).toFloat();
    float right = cmd.substring(comma+1).toFloat();
    
    setMotorSpeed(LEFT_MOTOR, left);
    setMotorSpeed(RIGHT_MOTOR, right);
  }
  
  // Publish encoder counts
  Serial.printf("ENC:%ld,%ld\n", leftCount, rightCount);
  delay(50);
}
```

---

## Quadruped (Walking Robot)

### Hardware
- Board: ESP32 + PCA9685 (16 servos)
- Servos: 12× MG996R (3 per leg)
- Power: 6V 5A+ BEC or bench supply
- Frame: 3D printed (many designs on Thingiverse)

### Leg Numbering Convention
```
Front
  1   2
   \ /
    X
   / \
  3   4
Back

Joints per leg: Hip (horizontal), Shoulder (vertical), Knee
```

### Gait Generator (Trot)
```cpp
// Trot: diagonal legs move together
// Phase: 1-4 = steps, leg is lifted during its phase

int phase = 0;
float liftHeight = 30;  // mm
float stepLength = 40;  // mm

void trotStep() {
  for (int leg = 0; leg < 4; leg++) {
    float x, z;
    
    // Diagonal pairs: (0,3) and (1,2)
    bool isLifted = (phase % 2 == leg % 2);
    
    if (isLifted) {
      // Swing phase: lift and move forward
      x = stepLength * (phase / 2.0 - 0.5);
      z = liftHeight * sin(PI * phase / 2.0);
    } else {
      // Stance phase: on ground, push back
      x = -stepLength * (phase / 2.0 - 0.5);
      z = 0;
    }
    
    moveLegTo(leg, x, 0, z);
  }
  
  phase = (phase + 1) % 4;
  delay(100);
}
```

---

## Project Checklist Template

```markdown
## Project: _______________

### Planning
□ Define goal and success criteria
□ List required components
□ Check power budget
□ Design wiring diagram

### Hardware
□ Test each sensor individually
□ Test each actuator individually
□ Verify power rails under load
□ Check all grounds connected

### Software
□ Verify library versions
□ Test with Serial debug
□ Implement main loop
□ Add error handling

### Integration
□ Connect all components
□ Test subsystems together
□ Tune parameters (PID, thresholds)
□ Stress test (long run)

### Documentation
□ Final wiring diagram
□ Pin assignments table
□ Calibration values
□ Known issues / future work
```


path: ros.md
content: # ROS — Robot Operating System

## ROS1 vs ROS2 Compatibility Matrix

| Feature | ROS1 (Noetic) | ROS2 (Humble/Iron) |
|---------|---------------|---------------------|
| Python API | `rospy` | `rclpy` |
| C++ API | `roscpp` | `rclcpp` |
| Build | `catkin_make` | `colcon build` |
| Workspace source | `source devel/setup.bash` | `source install/setup.bash` |
| Launch | XML only | Python (preferred) or XML |
| Param server | Global | Per-node, namespaced |
| Discovery | rosmaster | DDS (no master) |

**NEVER mix these.** `import rospy` in ROS2 = `ModuleNotFoundError`.

## Package Creation

### ROS2 (Python)
```bash
cd ~/ros2_ws/src
ros2 pkg create my_robot --build-type ament_python \
    --dependencies rclpy std_msgs geometry_msgs
```

### ROS2 (C++)
```bash
ros2 pkg create my_robot --build-type ament_cmake \
    --dependencies rclcpp std_msgs geometry_msgs
```

## Build Errors and Fixes

| Error | Cause | Fix |
|-------|-------|-----|
| "Package not found" | Didn't source workspace | `source install/setup.bash` |
| "Cannot find -lrclcpp" | Missing dependency | Add to package.xml AND CMakeLists.txt |
| "Multiple definitions" | Header included twice | Add include guards or #pragma once |
| "Unknown CMake command" | Wrong build type | ament_cmake vs ament_python mismatch |

## QoS Profiles — Critical for Message Delivery

```python
from rclpy.qos import QoSProfile, ReliabilityPolicy, DurabilityPolicy

# Sensor data — OK to drop old messages
sensor_qos = QoSProfile(
    reliability=ReliabilityPolicy.BEST_EFFORT,
    durability=DurabilityPolicy.VOLATILE,
    depth=10
)

# Commands — must not drop
cmd_qos = QoSProfile(
    reliability=ReliabilityPolicy.RELIABLE,
    durability=DurabilityPolicy.VOLATILE,
    depth=1
)

# Late-joiner needs last message (e.g., map)
latched_qos = QoSProfile(
    reliability=ReliabilityPolicy.RELIABLE,
    durability=DurabilityPolicy.TRANSIENT_LOCAL,
    depth=1
)
```

**QoS mismatch = silent failure.** Publisher BEST_EFFORT + Subscriber RELIABLE = no messages, no error.

## Launch File (Python, ROS2)

```python
from launch import LaunchDescription
from launch_ros.actions import Node
from launch.substitutions import LaunchConfiguration
from launch.actions import DeclareLaunchArgument

def generate_launch_description():
    return LaunchDescription([
        DeclareLaunchArgument('use_sim', default_value='true'),
        
        Node(
            package='my_robot',
            executable='controller',
            name='controller_node',
            parameters=[{
                'use_sim': LaunchConfiguration('use_sim'),
                'max_speed': 1.5
            }],
            remappings=[
                ('/cmd_vel', '/robot/cmd_vel'),
                ('/odom', '/robot/odom')
            ]
        ),
    ])
```

## TF2 Static Transform

```bash
# ROS2 Humble syntax
ros2 run tf2_ros static_transform_publisher \
    --x 0 --y 0 --z 0.1 --roll 0 --pitch 0 --yaw 0 \
    --frame-id base_link --child-frame-id laser_frame

# Older ROS2 syntax (Foxy)
ros2 run tf2_ros static_transform_publisher \
    0 0 0.1 0 0 0 base_link laser_frame
```

**Syntax changed between versions.** Check your distro's documentation.

## Gazebo Version Matrix

| ROS Distro | Default Gazebo | Plugin System |
|------------|----------------|---------------|
| Noetic | Gazebo 11 (Classic) | libgazebo_ros_* |
| Foxy | Gazebo 11 (Classic) | libgazebo_ros_* |
| Humble | Gazebo Fortress | gz-sim plugins |
| Iron | Gazebo Harmonic | gz-sim plugins |

**Classic plugins don't work in Fortress/Harmonic.** Migration required.

## Common Debugging Commands

```bash
# List running nodes
ros2 node list

# Show node info (topics, services, params)
ros2 node info /controller_node

# Echo topic messages
ros2 topic echo /cmd_vel

# Check topic QoS (why messages not arriving?)
ros2 topic info /scan --verbose

# List parameters
ros2 param list /controller_node

# Check TF tree
ros2 run tf2_tools view_frames
```


path: sensors.md
content: # Sensors — Wiring & Code

## Distance Sensors

### HC-SR04 Ultrasonic (5V)

```
ESP32 (3.3V) wiring — NEEDS VOLTAGE DIVIDER ON ECHO

  ESP32           HC-SR04
  ─────           ───────
  5V    ─────────  VCC
  GND   ─────────  GND
  GPIO5 ─────────  TRIG
  GPIO4 ←──┬─────  ECHO
           │
          2kΩ
           │
          GND
          via
          1kΩ
```

```cpp
// ESP32 — HC-SR04
const int TRIG = 5;
const int ECHO = 4;

void setup() {
  pinMode(TRIG, OUTPUT);
  pinMode(ECHO, INPUT);
}

float getDistanceCm() {
  digitalWrite(TRIG, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG, LOW);
  
  long duration = pulseIn(ECHO, HIGH, 30000);  // 30ms timeout
  if (duration == 0) return -1;  // No echo
  return duration * 0.0343 / 2;
}
```

### VL53L0X ToF (I2C, 3.3V safe)

```cpp
#include <VL53L0X.h>
#include <Wire.h>

VL53L0X sensor;

void setup() {
  Wire.begin();
  sensor.init();
  sensor.setTimeout(500);
  sensor.setMeasurementTimingBudget(20000);  // 20ms, faster
}

int getDistanceMm() {
  int d = sensor.readRangeSingleMillimeters();
  if (sensor.timeoutOccurred()) return -1;
  return d;
}
```

## IMU Sensors

### MPU6050 (I2C, 3.3V or 5V)

```
Default address: 0x68
AD0 pin HIGH: 0x69
```

```cpp
#include <Wire.h>
#include <MPU6050_light.h>

MPU6050 mpu(Wire);

void setup() {
  Wire.begin();
  mpu.begin();
  mpu.calcOffsets();  // Keep robot STILL during this
}

void loop() {
  mpu.update();
  float pitch = mpu.getAngleX();
  float roll = mpu.getAngleY();
  float yaw = mpu.getAngleZ();  // Drifts without magnetometer
}
```

**Warning:** `calcOffsets()` takes ~3 seconds. Robot must be stationary.

### BNO055 (I2C, 9-DOF with fusion)

```cpp
#include <Adafruit_BNO055.h>

Adafruit_BNO055 bno = Adafruit_BNO055(55, 0x28);

void setup() {
  if (!bno.begin()) {
    // Sensor not found — check wiring
  }
  bno.setExtCrystalUse(true);
}

void loop() {
  sensors_event_t event;
  bno.getEvent(&event);
  float heading = event.orientation.x;  // 0-360
  float pitch = event.orientation.y;
  float roll = event.orientation.z;
}
```

## Temperature & Humidity

### DHT22 (Digital, 3.3V or 5V)

```cpp
#include <DHT.h>

#define DHTPIN 4
#define DHTTYPE DHT22

DHT dht(DHTPIN, DHTTYPE);

void setup() {
  dht.begin();
}

void loop() {
  delay(2000);  // MINIMUM 2 seconds between reads
  
  float h = dht.readHumidity();
  float t = dht.readTemperature();
  
  if (isnan(h) || isnan(t)) {
    // Read failed — check wiring, wait longer
    return;
  }
}
```

**Trap:** Reading faster than 2s returns NaN or stale values.

### BMP280 / BME280 (I2C)

```cpp
#include <Adafruit_BMP280.h>

Adafruit_BMP280 bmp;

void setup() {
  if (!bmp.begin(0x76)) {  // or 0x77
    // Not found
  }
}

void loop() {
  float temp = bmp.readTemperature();
  float pressure = bmp.readPressure();  // Pa
  float altitude = bmp.readAltitude(1013.25);  // hPa at sea level
}
```

## IR Sensors

### TCRT5000 Line Sensor

```cpp
const int IR_PIN = A0;
const int THRESHOLD = 500;

bool isOnLine() {
  return analogRead(IR_PIN) > THRESHOLD;  // Dark = high value
}
```

**Calibration required:** Print surface, ambient light affect threshold.

### Sharp GP2Y0A21 IR Distance (10-80cm)

```cpp
const int IR_PIN = A0;

float getDistanceCm() {
  int raw = analogRead(IR_PIN);
  // Voltage to distance formula (varies by model)
  float voltage = raw * (5.0 / 1023.0);
  return 27.86 * pow(voltage, -1.15);  // Approximate
}
```

## Encoders

### Quadrature Encoder (A/B channels)

```cpp
volatile long encoderCount = 0;
const int ENC_A = 2;  // Must be interrupt-capable
const int ENC_B = 3;

void IRAM_ATTR encoderISR() {
  if (digitalRead(ENC_B)) {
    encoderCount++;
  } else {
    encoderCount--;
  }
}

void setup() {
  pinMode(ENC_A, INPUT_PULLUP);
  pinMode(ENC_B, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(ENC_A), encoderISR, RISING);
}

long getCount() {
  noInterrupts();
  long count = encoderCount;
  interrupts();
  return count;
}
```

**Note:** ESP32 uses `IRAM_ATTR` for ISR. Arduino doesn't need it.

## Common I2C Addresses

| Sensor | Default | Alternate |
|--------|---------|-----------|
| MPU6050 | 0x68 | 0x69 (AD0 high) |
| BMP280 | 0x76 | 0x77 |
| BNO055 | 0x28 | 0x29 |
| OLED SSD1306 | 0x3C | 0x3D |
| VL53L0X | 0x29 | Programmable |
| PCA9685 | 0x40 | 0x40-0x7F |

## I2C Scanner (First Debug Step)

```cpp
#include <Wire.h>

void setup() {
  Wire.begin();
  Serial.begin(115200);
  
  Serial.println("Scanning...");
  for (byte addr = 1; addr < 127; addr++) {
    Wire.beginTransmission(addr);
    if (Wire.endTransmission() == 0) {
      Serial.printf("Found: 0x%02X\n", addr);
    }
  }
  Serial.println("Done.");
}

void loop() {}
```


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)