Learning path from raw serial communication to ROS2, Computer Vision and autonomous manipulation.
| Component | Details |
|---|---|
| Robotic Arm | Waveshare RoArm-M2-Pro |
| Controller | ESP32 (onboard) |
| Communication | USB/UART Serial @ 115200 baud |
| Protocol | JSON commands |
| Camera | IMX335 5MP 2K (eye-in-hand, USB) |
| Host | ThinkPad T14 Gen5 — Arch Linux |
| Joint | Servo | DOF | Note |
|---|---|---|---|
| Base | ST3235 | 1 | 360° rotation |
| Shoulder | ST3235 × 2 | 1 | Dual-drive, doubled torque |
| Elbow | ST3235 | 1 | Carbon fiber segment |
| EoAT / Gripper | ST3215-HS | 1 | Precise grip force control |
roarm-m2-pro/
├── pyproject.toml # pytest configuration
├── requirements.txt # Runtime dependency (pyserial)
├── requirements-dev.txt # Dev dependencies (pytest)
└── scripts/
├── roarm/ # Control library (two layers)
│ ├── __init__.py # Exports: RoArm, SerialTransport, Command, JOINT_KEYS
│ ├── transport.py # SerialTransport — serial I/O only
│ └── controller.py # RoArm — commands & feedback-driven motion
├── logging_config.py # Centralized logging configuration
├── serial_console.py # Interactive JSON command console (thin app)
├── demo_sequence.py # Position-based movement demo (thin app)
├── tests/ # Unit tests (pytest, no hardware needed)
│ ├── conftest.py # FakeTransport + fixtures
│ ├── test_controller.py # RoArm logic
│ └── test_transport.py # SerialTransport line encoding/decoding
└── logs/ # Runtime logs (auto-generated, not in git)
├── roarm_debug.log # Logs from serial_console.py
└── roarm_sequence.log # Logs from demo_sequence.py
Architecture — the arm-control logic lives in the roarm package, split into
two layers so it can be unit-tested without hardware:
SerialTransportknows the wire (open/close/read/write over serial).RoArmknows the arm's language (move_joints,home,set_torque,get_feedback, feedback-drivenwait_until_reached) and talks to the arm through an injected transport.
The two scripts are thin apps that configure logging and drive a RoArm.
pip install -r requirements.txt- Connect 12V 5A power supply
- Connect USB cable to computer
- Power on the arm — joints move to home position
- Verify connection:
ls /dev/ttyUSB0python scripts/serial_console.pyType JSON commands directly in the terminal:
{"T":105}
{"T":100}
{"T":210,"cmd":0}python scripts/demo_sequence.pyThe arm executes 6 steps with position-based control — each step waits until the target position is confirmed before proceeding.
The RoArm logic is unit-tested against a fake transport, so the tests run
without the physical arm. On Arch (PEP 668) use a virtual environment:
python -m venv --system-site-packages .venv # keeps system pyserial available
.venv/bin/pip install -r requirements-dev.txt
.venv/bin/python -m pytestpytest reads its configuration from pyproject.toml (test path and import path).
Both scripts automatically log all operations to the logs/ directory:
roarm_debug.log— Serial console session: connection and manual command executionroarm_sequence.log— Movement demo execution and timing
Log files are automatically rotated when they exceed 5MB. The last 5 backups are preserved.
Log levels:
DEBUG— Detailed command/response info (file only)INFO— Important events (console + file)WARNING— Warnings like user cancellations (console + file)ERROR— Critical failures (console + file)
Example log entry:
2026-04-20 14:32:15 - __main__ - INFO - Serial port opened: /dev/ttyUSB0
2026-04-20 14:32:16 - __main__ - INFO - Step 1/6: Home position
2026-04-20 14:32:20 - __main__ - INFO - Target position reached (stable)
The logs/ directory is excluded from git via .gitignore to keep the repository clean.
The control scripts have been optimized for reliability and performance:
Serial Communication
- Immediate buffer flushing after commands for faster responsiveness
- Reduced communication delays (0.5s → 0.2s for commands)
- Optimized polling rate for position feedback
Position Control
- Stability verification (2 consecutive stable readings before confirming target)
- Smart output filtering (reduces console spam during movement)
- Individual joint tolerance checking
Code Quality
- Centralized logging configuration (
logging_config.py) — DRY principle - Comprehensive error handling with detailed logging
- Guard against duplicate logging handlers
| Command | Type | Description |
|---|---|---|
{"T":100} |
CMD_MOVE_INIT | All joints to home position |
{"T":102,"base":0,"shoulder":0,"elbow":1.6,"hand":3.14,"spd":500,"acc":10} |
CMD_SERVO_RAD_CTRL | Control all joints simultaneously |
{"T":105} |
CMD_SERVO_RAD_FEEDBACK | Get current position of all joints |
{"T":210,"cmd":0} |
CMD_TORQUE_LOCK | Torque off (manual movement) |
{"T":210,"cmd":1} |
CMD_TORQUE_LOCK | Torque on |
| Joint | Positive (+) | Negative (−) | Home |
|---|---|---|---|
| Base | Left | Right | 0 rad |
| Shoulder | Forward / down | Backward / up | ~0 rad |
| Elbow | Down | Up | ~1.6 rad |
| EoAT / Gripper | Closed | Open | 3.14 rad |
Angle conversion: degrees × (π / 180) = radians
Common values:
- 45° = 0.7854 rad
- 90° = 1.5708 rad
- 180° = 3.1416 rad
{
"T": 1051,
"x": 307.47, "y": -16.52, "z": 231.49,
"b": -0.053, "s": -0.009, "e": 1.599, "t": 3.147,
"torB": 149, "torS": -25, "torE": 85, "torH": 16
}| Field | Meaning | Unit |
|---|---|---|
| x, y, z | EoAT position in space | mm |
| b | Base angle | rad |
| s | Shoulder angle | rad |
| e | Elbow angle | rad |
| t | EoAT / Gripper angle | rad |
| torB/S/E/H | Current load per joint | raw |
- Serial connection established
- JSON command communication
- Position-based movement control
- Multi-step movement sequence
- Camera integration (IMX335)
- Object detection (YOLOv8)
- Hand/face tracking
- ROS2 integration
- Autonomous pick & place