roboportal

Script a real robot.

skidbot is a real robot in a real 1.5 × 1.0 m room, with noisy lidar and drifting odometry and no simulator underneath. Take a turn from your browser, then drive it by hand or run your own ROS 2 code on it.

skidbot, a small grey 3D-printed robot with a lidar on top and a camera on the front
  1. 1

    Join the queue

    One robot, shared fairly. Everyone waits in the same line, people and agents alike.

  2. 2

    Get your turn

    Ten minutes on the robot, renewable up to an hour while nobody else is waiting.

  3. 3

    Drive it or code it

    Steer by hand with live video and lidar, or run rclpy scripts from the browser.

Everything your code has to live with

A 3D-printed body on a 16 × 12 cm base. No arm, no gripper, no absolute coordinates. The sensors lie a little, so your code has to cope, same as on every real robot.

  1. Slip ring

    Lets the robot spin all day without winding up its cable.

  2. LD19 lidar

    A full 360° 2D scan on /scan. Noisy, like every real one.

  3. Raspberry Pi 5

    Runs ROS 2 and the safety supervisor. Your code never runs here.

  4. IMX219 camera

    Fixed, forward facing, streamed to your browser over WebRTC.

  5. Skid-steer drive

    Two motors on an ESP32-C3. Wheel odometry that drifts as it turns.

Real rclpy, on the live ROS graph

Write Python in the browser. It runs in an isolated cloud sandbox for up to 60 seconds, reads the robot's real topics and streams its output back to you. Nine starter challenges get you going:

  • Read the lidar
  • Which way is it?
  • Where am I?
  • Your first move
  • Stop before the wall
  • Measure the room
  • Don't touch a wall
  • Find the corner
  • Square dance
read_lidar.py
class Listener(Node):
    def __init__(self):
        super().__init__("challenge_listener")
        self.create_subscription(
            LaserScan, "/scan", self.on_scan,
            qos_profile_sensor_data)

    def on_scan(self, msg: LaserScan):
        valid = [r for r in msg.ranges
                 if math.isfinite(r) and r > MIN_VALID]
        if not valid:
            return
        self.get_logger().info(
            f"closest obstacle: {min(valid):.2f} m",
            throttle_duration_sec=1.0)

Let your agent take a turn

Add the MCP server and your agent signs in as you with OAuth. It waits in the same queue and answers to the same safety supervisor as anyone in a browser.

Connect an agent
Claude Code
claude mcp add --transport http roboportal \
  https://mcp.roboportal.io/mcp
  • join_queue
  • wait_for_turn
  • drive
  • stop
  • read_lidar
  • read_odometry
  • read_camera
  • run_code

Your code can't crash it

Every command passes through a supervisor on the robot before it reaches the motors. The keep-out fence and its fail-closed behavior were tested on the real robot, not only in simulation.

Keep-out fence
The supervisor reads the lidar and refuses to move toward anything inside the path the body would sweep.
Fails closed
If the scan goes stale or the lidar goes quiet, driving stops. Rotating in place still works, so it can be turned away.
Speed clamp and heartbeat
Every command is limited on the robot itself, and it stops when the session's heartbeat does.
Code runs in a sandbox
Scripts run in the cloud. The router's ACL won't let them publish to the motor topic, whatever the code says.
Nothing connects in
The robot has no public address. It dials out over mutual TLS and everything rides on that link.