Hierarchical VLA architecture with multi-modal contact sensing and conditioned motion planning, deployed on an affordable 5-DOF serial-bus servo arm.
This project implements a Hierarchical Vision-Language-Action (VLA) system for robotic manipulation on a low-cost 5-DOF arm built around five STS3215 serial-bus servos. The system accepts natural language instructions (e.g. "place the red block in tray two") and executes them using a hierarchical pipeline that decomposes manipulation into interpretable skill tokens — REACH → GRASP → LIFT → PLACE — rather than flat end-to-end joint prediction.
The compute stack is split across two processors:
- ESP32-WROOM-32 — 50Hz deterministic servo control, sensor acquisition, hardware safety enforcement
- Raspberry Pi 5 (8GB) — 8Hz VLA inference, YOLOv8-nano perception, language encoding, IK planning
Training requires as few as 30 teleoperation demonstrations, making this viable for resource-constrained labs and educational settings.
| Component | Part | Role |
|---|---|---|
| Arm | 5× STS3215 serial-bus servos | 5-DOF manipulation |
| MCU | ESP32-WROOM-32 @ 240MHz | Real-time control, sensor fusion |
| SBC | Raspberry Pi 5 8GB | VLA inference, perception |
| Camera | Pi Camera Module 3 (fixed overhead) | Object detection, (X, Y) localization |
| ToF | VL53L5CX 8×8 array (wrist-mounted) | Grasp depth (Z) measurement |
| IMU | ISM330DHCX (end-effector) | Contact detection via vibration |
J0 — Base yaw (servo_pos[0])
J1a — Shoulder A (servo_pos[1])
J1b — Shoulder B (servo_pos[2])
J2 — Elbow/wrist pitch (servo_pos[3])
J3 — Gripper (servo_pos[4])
.
├── firmware/ # ESP32 embedded firmware (PlatformIO)
│ ├── src/ # Main firmware source
│ │ ├── main.cpp # FreeRTOS dual-core entry point
│ │ ├── comms.* # USB serial protocol (250-byte telemetry, 20-byte command)
│ │ ├── servo_bus.* # STS3215 serial bus driver
│ │ ├── tof_driver.* # VL53L5CX ToF driver
│ │ ├── ism330dhcx_driver.* # IMU driver
│ │ ├── contact_oracle.* # Gyro RMS contact detection
│ │ ├── safety_layer.* # Hardware joint limit enforcement
│ │ └── waypoint_interp.* # Smooth waypoint interpolation
│ ├── include/config.h # Pin assignments, tuning constants
│ ├── test_sketches/ # Standalone hardware verification sketches
│ │ ├── imu_whoami/ # IMU I2C connectivity test
│ │ ├── servo_ping/ # Servo bus ping test
│ │ └── tof_distance/ # ToF ranging test
│ ├── tools/ # Python diagnostic tools
│ │ ├── verify_telemetry.py # Validate live telemetry packets
│ │ ├── servo_monitor.py # Real-time servo state monitor
│ │ └── read_servos.py # One-shot servo state reader
│ └── platformio.ini
│
├── rpi5_inference/ # Raspberry Pi 5 Python inference stack
│ ├── main.py # Inference loop entry point (8Hz)
│ ├── comms/
│ │ ├── teensy_serial.py # USB serial reader/writer
│ │ └── servo_driver.py # High-level servo command interface
│ ├── perception/
│ │ ├── yolo_detector.py # YOLOv8-nano object detection
│ │ ├── pose_estimation.py # 3D pose from camera + ToF fusion
│ │ └── camera_manager.py # Pi Camera 3 capture manager
│ ├── language/
│ │ └── language_encoder.py # Text instruction encoder
│ ├── vla/
│ │ ├── vla_policy.py # SmolVLA-450M / Octo-small policy
│ │ ├── skill_predictor.py # Discrete skill token predictor
│ │ └── action_generator.py # Skill-conditioned joint delta generator
│ ├── planning/
│ │ ├── ik_solver.py # Analytical IK for 5-DOF arm
│ │ └── safety_filter.py # Joint limit + singularity enforcement
│ ├── calibration/
│ │ ├── camera_calibrate.py # Camera intrinsic calibration
│ │ ├── overhead_height_calib.py # Z_table measurement
│ │ ├── wrist_tof_calib.py # Wrist ToF offset calibration
│ │ ├── camera_intrinsics.yaml # K matrix + distortion coefficients
│ │ ├── camera_extrinsics.yaml # T_cam_base transform
│ │ └── homography_dots.yaml # Pixel-to-world homography
│ ├── config/
│ │ ├── arm_config.yaml # Joint limits, DH params, servo IDs
│ │ └── model_config.yaml # Model paths, inference settings
│ ├── dashboard/
│ │ └── gui.py # PyQt6 live monitoring dashboard
│ └── evaluation/
│ ├── run_eval.py # Full evaluation runner
│ ├── skill_f1.py # Skill segmentation F1 metric
│ ├── contact_latency.py # Contact detection latency benchmark
│ └── ablation.py # Ablation study runner
│
├── dataset/ # Dataset pipeline (runs on Colab / workstation)
│ ├── hdf5_reader.py # Reads raw teleoperation HDF5 recordings
│ ├── skill_segmenter.py # Automatic skill boundary detection
│ ├── augmentation.py # Data augmentation transforms
│ └── vla_dataset.py # PyTorch Dataset for VLA fine-tuning
│
├── checkpoints/ # Model weights
│ └── yolov8n_vla/weights/ # Fine-tuned YOLOv8-nano weights
│
├── demos/ # Raw teleoperation HDF5 recordings (gitkeep)
│
├── docs/superpowers/
│ ├── specs/ # Design specifications
│ └── plans/ # Implementation plans
│
├── AI_ML_WORKPLAN.md # AI/ML engineer workplan
├── HARDWARE_EMBEDDED_WORKPLAN.md # Hardware/firmware engineer workplan
├── VLA_Robotic_Arm_Project_Report_FINAL.md # Master project report
├── requirements.txt
└── README.md
The ESP32 and RPi 5 communicate over USB serial at 2 Mbps.
TELEMETRY_DTYPE = np.dtype([
('timestamp_us', np.uint32), # ESP32 microsecond counter
('servo_pos', np.float32, (5,)), # degrees, 0.088°/step
('servo_load', np.float32, (5,)), # normalized 0.0–1.0
('servo_speed', np.float32, (5,)), # degrees/second
('servo_temp', np.float32, (5,)), # Celsius
('tof_grid', np.uint16, (64,)), # 8×8 zone distances in mm
('tof_timestamp_us', np.uint32),
('tof_resolution', np.uint8), # 64 = 8×8 mode
('tof_valid', np.uint8), # 1 if frame passed validity
('imu_gyro', np.float32, (3,)), # deg/s (gx, gy, gz)
('imu_accel', np.float32, (3,)), # m/s² (ax, ay, az)
('contact_flag', np.uint8), # 1 if contact oracle triggered
('contact_rms', np.float32), # gyro RMS value
('safety_clamped', np.uint8), # 1 if hw safety clamped a cmd
('checksum', np.uint16),
]) # Total: 250 bytesCOMMAND_DTYPE = np.dtype([
('target_arm', np.float32, (3,)), # J0, J1, J2 target degrees
('skill_state', np.uint8), # 0=REACH 1=GRASP 2=LIFT 3=PLACE
('execute', np.uint8), # 1=execute, 0=hold
('gripper_command', np.float32), # 0.0=open, 1.0=closed
('emergency_stop', np.uint8), # 1=halt all servos immediately
('checksum', np.uint8),
]) # Total: 20 bytes- Python 3.10+
- PlatformIO (for firmware)
- Google Colab (for VLA training — A100/T4 GPU required)
pip install -r requirements.txtcd firmware
pio run --target upload# Camera intrinsics
python rpi5_inference/calibration/camera_calibrate.py
# Table height (Z_table)
python rpi5_inference/calibration/overhead_height_calib.py
# Wrist ToF offset
python rpi5_inference/calibration/wrist_tof_calib.pyOutputs are saved to rpi5_inference/calibration/*.yaml.
# Dry run — validates imports and serial connection without moving servos
python rpi5_inference/main.py --dry-run
# Live inference
python rpi5_inference/main.pypython firmware/tools/verify_telemetry.py- Collect 30 teleoperation demonstrations — saved as HDF5 files in
demos/ - Upload
demos/anddataset/to Google Drive - Open the training notebook, mount Drive, and run the pipeline:
dataset/hdf5_reader.py— parse raw recordingsdataset/skill_segmenter.py— auto-label skill boundariesdataset/augmentation.py— apply augmentationdataset/vla_dataset.py— build PyTorch dataset
- Fine-tune SmolVLA-450M with LoRA (Octo-small as fallback baseline)
- Export weights to
checkpoints/
demos/demo_001_pick_red_block.h5
/telemetry/
servo_pos (N, 5) float32 degrees
servo_load (N, 5) float32 normalized
servo_speed (N, 5) float32 deg/s
imu_gyro (N, 3) float32 deg/s
imu_accel (N, 3) float32 m/s²
tof_grid (N, 64) uint16 mm
contact_flag (N,) uint8
timestamps_us (N,) uint32
/video/
rgb_frames (N, H, W, 3) uint8
/metadata/
task_instruction str
demo_id str
# Full evaluation suite
python rpi5_inference/evaluation/run_eval.py
# Individual metrics
python rpi5_inference/evaluation/skill_f1.py
python rpi5_inference/evaluation/contact_latency.py
python rpi5_inference/evaluation/ablation.py| Metric | Target |
|---|---|
| Task success (pick-place, stack, sort) | ≥ 80% |
| Workspace-compliant trajectories | 100% |
| VLA inference latency (P95) | ≤ 110ms |
| Full pipeline latency (P95) | ≤ 125ms |
| Contact detection latency | ≤ 20ms |
python rpi5_inference/dashboard/gui.pyPyQt6 dashboard showing real-time servo states, ToF depth map, contact oracle status, skill state, and inference latency.
Natural Language Instruction
│
▼
Language Encoder (RPi 5)
│
▼
Skill Predictor ──── Visual Observation (YOLOv8-nano + ToF)
(SmolVLA-450M)
│
Skill Token
(REACH/GRASP/LIFT/PLACE)
│
▼
Action Generator
(Skill-conditioned joint deltas)
│
▼
Analytical IK + Safety Filter
│
▼
Command Packet (20 bytes @ 8Hz)
│
USB Serial
│
▼
ESP32-WROOM-32
(50Hz servo control + hardware safety)
│
▼
STS3215 Servo Bus (5 servos)
Why hierarchical (skill tokens) instead of flat VLA? A 5-DOF arm has no kinematic redundancy — small joint prediction errors cause large end-effector deviations. Decomposing into discrete skill phases stabilizes regression and makes failures interpretable.
Why fixed overhead camera + wrist ToF instead of eye-in-hand? Eye-in-hand routing requires a CSI ribbon cable through revolute joints, which fatigues within tens of cycles. Fixed overhead avoids occlusion during REACH; wrist ToF provides precise grasp depth (Z) without the cable routing problem.
Why STS3215 load feedback for contact sensing? The STS3215 servo bus provides normalized load current at 50Hz per servo — a natural force proxy. Combined with ISM330DHCX gyro RMS on the end-effector, this gives contact detection without a dedicated force-torque sensor.
Why SmolVLA-450M? Designed for affordable robotics and CPU-capable deployment. Octo-small + LoRA is retained as a lower-latency fallback if SmolVLA cannot meet the 125ms/step budget on RPi 5.
See requirements.txt. Key packages:
| Package | Purpose |
|---|---|
torch, torchvision |
Model training and inference |
ultralytics |
YOLOv8-nano object detection |
transformers, peft |
SmolVLA / Octo fine-tuning with LoRA |
h5py |
HDF5 dataset reading/writing |
pyserial |
USB serial communication with ESP32 |
opencv-python |
Camera capture and image processing |
PyQt6, pyqtgraph |
Live dashboard |
scipy, numpy |
IK solver, signal processing |
Full technical report including literature review, system architecture, kinematic model, training methodology, and evaluation plan: VLA_Robotic_Arm_Project_Report_FINAL.md