Supported robots
OhhO OS is robot-agnostic by design. A capability model describes what a robot can do — not how — so the same code runs across very different hardware, and adapters translate each robot's native protocol underneath.
Robot categories
OhhO OS organizes hardware into categories, each with one or more models:
- Wheeled (differential, mecanum, ackermann, omnidirectional)
- Legged (quadrupeds, hexapods)
- Humanoid
- Industrial arms
- Mobile manipulators
- Drones
- Tracked, Marine, Underwater ROV
- Delivery, Inspection, Agricultural, Medical, and more
Built-in robots (v1.0.0)
Three robots ship in the box:
| Robot ID | Name | Category | Capabilities | Adapter |
|---|---|---|---|---|
omnibot |
OmniBot | mobile-manipulator | base.drive, base.holonomic_drive, manipulation, perception.rgb, perception.depth | yahboom-serial + feetech (composite) |
unitree-go2 |
Unitree Go2 | legged | base.drive, base.holonomic_drive, legged.pose, perception.rgb, perception.depth | unitree-dds |
sim |
Generic Sim Bot | wheeled | base.drive, base.holonomic_drive, perception.rgb | sim |
The capability model
Every robot ships a manifest declaring its capabilities and limits:
id: unitree-go2
category: legged
capabilities: [base.holonomic_drive, perception.depth, perception.rgb, legged.pose]
dof: { base: 3, arm: 0 }
adapter: unitree-dds
limits: { max_lin: 1.5, max_ang: 2.0 }
Your code asks what a robot can do, rather than assuming:
if bot.has("manipulation"):
bot.move_joints(...)
else:
bot.drive(...) # graceful degradation, never a crash
This is what makes a behavior — or an entire agent — portable across a wheeled base, a quadruped, a humanoid and an arm with no rewrite.
Capability vocabulary
| Capability | Description |
|---|---|
base.drive |
The robot can move linearly and turn |
base.holonomic_drive |
The robot can strafe (linear_y) — implies base.drive |
legged.pose |
The robot is legged and can pose |
manipulation |
The robot has an arm with joints |
perception.rgb |
The robot has an RGB camera |
perception.depth |
The robot has a depth camera |
Protocol adapters (v1.0.0)
Six adapters ship in v1.0.0:
| Adapter | Scheme | Brand / type | Transport | Extra |
|---|---|---|---|---|
sim |
sim:// |
Built-in simulator | In-process (holonomic physics) | none |
yahboom |
serial:// |
Yahboom Rosmaster X3 / OmniBot base | USB serial @ 115200 baud | [serial] |
feetech |
feetech:// |
SO-101 6-DOF arm (STS3215 servos) | USB serial @ 1 Mbps | [arm] |
composite |
serial://base,arm |
Merges a base + arm transport | Delegates to both | inherits |
unitree |
dds:// |
Unitree G1, Go2, H1, B2, A2 | Cyclone DDS | [unitree] |
ros2 |
ros2:// |
Any ROS 2 robot | ROS 2 topics (DDS) | [ros2] |
Composite transport
Mobile-manipulators like OmniBot have two independent physical links — a base
(Yahboom serial) and an arm (Feetech serial). The composite transport merges
them into one Transport:
send_velocity→ base (the arm has no wheels)send_joint_command→ arm (the base board doesn't drive the arm)read/ telemetry → merged (odom from base, joints from arm)emergency_stop/release_stop→ both
# Auto-composes a composite for a robot with manipulation capability:
bot = Robot.connect("omnibot", "serial:///dev/ttyUSB0,/dev/ttyACM0")
Add a robot
Adding a new robot is a small, contained change: one manifest + one adapter + one test. No platform fork, no core rewrite.
1. Write a manifest (JSON or YAML)
{
"id": "my-bot",
"name": "My Bot",
"category": "wheeled",
"capabilities": ["base.drive", "perception.rgb"],
"adapter": "my-adapter",
"dof_base": 3,
"max_lin": 0.5,
"max_ang": 1.0
}
2. Write an adapter (subclass BaseTransport)
from ohho.transport import BaseTransport
class MyAdapterTransport(BaseTransport):
protocol = "my-adapter"
# implement connect, send_velocity, read, send_joint_command, etc.
3. Register the scheme + write a test
See Contributing for the full guide.