Local locomotion

Move to a measured planar pose using local odometry feedback. Finite locomotion does not plan a path or avoid obstacles, so use it only where the swept path is known to be clear.

move accepts X and Y displacement in metres and signed yaw in radians, all relative to the base frame captured when the command starts. Yaw is unwrapped: pass the total rotation you want, not an angle limited to one turn. For example, 3π radians rotates the base about 1.5 full turns before the command succeeds. Commands stop when odometry reaches the target and report progress through on_feedback / onFeedback.

Note

  • The endpoint is controlled without guaranteeing a particular path or that translation and rotation finish simultaneously.

  • Every requested axis is commanded regardless of motion model; unsupported axes stall when odometry stops making progress on them.

For direct teleoperation, or applying your own control logic rather than measured motion based on odometry feedback, see Velocity streaming.

import math
import time

from olo import Client
from olo.locomotion import LocomotionFeedback, PlanarMotion

last_feedback_time = 0.0


def on_feedback(feedback: LocomotionFeedback, rate_limit: float = 3) -> None:
    global last_feedback_time
    now = time.monotonic()
    if now - last_feedback_time < 1 / rate_limit:
        return
    last_feedback_time = now
    print(f"Remaining: {feedback.remaining}")


with Client() as client:
    # Drive forward
    client.robot().locomotion.move(
        PlanarMotion(x=0.5),
        on_feedback=on_feedback,
    )
    time.sleep(1)

    # Turn_left
    client.robot().locomotion.move(
        PlanarMotion(yaw=math.pi / 2),
        on_feedback=on_feedback,
    )
    time.sleep(1)

    # Combined motion at a slower speed. Note that combined motions are much less
    # accurate, and separate motions are recommended.
    client.robot().locomotion.move(
        PlanarMotion(x=-1.0, yaw=-math.pi / 2),
        max_linear_speed=0.2,
        max_angular_speed=0.3,
        on_feedback=on_feedback,
    )
    time.sleep(1)
import { connect } from "olo/web";
import type { LocomotionFeedback } from "olo/locomotion";

const delay = (milliseconds: number) =>
  new Promise<void>((resolve) => setTimeout(resolve, milliseconds));

let lastFeedbackTime = 0;

function onFeedback(feedback: LocomotionFeedback, rateLimit = 3): void {
  const now = performance.now() / 1000;
  if (now - lastFeedbackTime < 1 / rateLimit) {
    return;
  }
  lastFeedbackTime = now;
  const remaining = feedback.remaining;
  console.log(
    `Remaining: PlanarMotion(x=${remaining.x.toFixed(3)}, y=${remaining.y.toFixed(3)}, yaw=${remaining.yaw.toFixed(3)})`,
  );
}

const robot = await connect().robot();

// Drive forward
await robot.locomotion.move({ x: 0.5, y: 0, yaw: 0 }, { onFeedback });
await delay(1_000);

// Turn_left
await robot.locomotion.move({ x: 0, y: 0, yaw: Math.PI / 2 }, { onFeedback });
await delay(1_000);

// Combined motion at a slower speed. Note that combined motions are much less
// accurate, and separate motions are recommended.
await robot.locomotion.move(
  { x: -1.0, y: 0, yaw: -Math.PI / 2 },
  {
    maxLinearSpeed: 0.2,
    maxAngularSpeed: 0.3,
    onFeedback,
  },
);
await delay(1_000);

The conventional topics are /{namespace}/odom and /{namespace}/cmd_vel. Pass odom_topic / odomTopic or cmd_vel_topic / cmdVelTopic for a nonstandard robot. Cancellation and all terminal outcomes publish zero velocity.