Skip to content

Controlling Motions from Python

To build walking into your own script, use the Palmimo class in robot.py. Every method it exposes is listed in Core Methods; this page shows the shapes those calls take.

Step Reference
Confirm all motors respond diagnose_servos.py scan
Reset to neutral stance Palmimo.stop() eases to neutral
Torque off (move by hand) Exit the app / cut power (see Safety)

Lift the unit so the feet are in the air the first time you run a new motion.

from palmimo_sdk import Palmimo
robot = Palmimo(gait_speed=0.012, step_length=30.0, step_height=30.0)
robot.forward()
for _ in range(120): # ~2 seconds @ 60 Hz
positions = robot.step() # dict[str, int]: motor name -> tick (0-4095)
print(positions)
robot.stop()
for _ in range(60): # return to neutral
robot.step()

⚠️ Ticks run 0-4095, but keep motion inside the safe range 200-3900 — the raw ends are mechanical limits. See Safety Design.

The return value of step() (a dict of target ticks for the 18 leg motors plus neck_yaw, neck_pitch1, and neck_pitch2, 21 motors total — see robot.look() in Core Methods for how the two neck-pitch joints share a look target) is what DynamixelDriver.write_positions() takes, so passing it there drives real hardware. For the concrete bus-initialization sequence, see DynamixelDriver and the Peripherals & Connection section of the API reference.

Palmimo.play() in robot.py plays back a sequence of (motion name, seconds) steps. The special motion look_around sweeps the neck in a sine wave. Note that with the (name, seconds) tuple form, motion stays at the default Motion.IDLE, so the legs stay still during that segment. To sweep the neck while walking, pass RoutineStep(motion=..., neck_sweep=True) instead of a tuple.

routine = [
("forward", 2.0),
("rotate_left", 1.0),
("dance", 3.0),
("look_around", 2.0),
("idle", 0.5),
]
robot = Palmimo()
for positions in robot.play(routine, fps=60):
# send positions to real hardware / a simulator
...

For real-time playback (paced internally with time.sleep), use play_realtime.

To make a gesture of your own without writing an engine method, list the poses the robot should pass through, each with the time to reach it, and let the SDK ease between them. Angles are degrees from each servo’s neutral; a joint a keyframe leaves out holds where it was.

  1. Write the track and check it in a dry run. With no driver attached nothing moves, and an invalid track (a joint out of range, a move too fast) raises ValueError on the call:

    from palmimo_sdk import Keyframe, Palmimo
    raise_front_right = [
    Keyframe(1.0, {"leg_6_pitch1": -30.0, "neck_yaw": 10.0}), # lift the leg, glance aside
    Keyframe(1.5, {"leg_6_yaw": 15.0}), # swing it sideways; the rest hold
    Keyframe(2.5, {"leg_6_pitch1": 0.0, "leg_6_yaw": 0.0}), # put it back
    ]
    robot = Palmimo()
    frames = list(robot.iter_keyframes(raise_front_right, arm_legs=[6]))
    print(len(frames), frames[60]["leg_6_pitch1"]) # 2.5 s + the 1 s glide home, at 60 fps
  2. Lift the robot so every foot is in the air, then play it on the hardware:

    from palmimo_sdk import DynamixelDriver, Palmimo
    with Palmimo(driver=DynamixelDriver()) as robot:
    robot.play_keyframes(raise_front_right, arm_legs=[6])

    Pass the legs the track lifts as arms in arm_legs, so the overload guard watches them with the arm threshold.

  3. Set it down and play it on the floor only after it looks right in the air. The range checks stop servo-limit and speed mistakes, not a leg swinging into the body or another leg.

The rules and the arguments are in Keyframe Playback in the API reference.

Instead of typing angles, move the robot into each pose and capture it.

  1. Lift the robot so every foot is in the air. The legs you record go limp and carry no weight.

  2. Start the recorder with the legs to move (here the front-right leg, 6):

    Terminal window
    uv run python scripts/record_keyframes.py --legs 6 --step 1.5 --out wave.json
  3. Pose the leg by hand and press Enter for each pose (u undoes, q finishes).

  4. Answer y to play it back while still lifted; if it is refused, edit the file (a longer --step gives every move more time).

  5. Play the file from your own code. Playback starts from wherever the robot is when you call it — neutral right after connecting — whereas the recorder’s preview starts from the last pose you captured, so the first move can differ:

    from palmimo_sdk import DynamixelDriver, Palmimo, load_keyframes
    with Palmimo(driver=DynamixelDriver()) as robot:
    robot.play_keyframes(load_keyframes("wave.json"), arm_legs=[6])

The recorder’s options are in scripts/README.md.

  • Serial port not found: Check the actual device name with ls /dev/tty.usbmodem* (macOS) or ls /dev/ttyACM* (Linux), then specify the port explicitly when constructing DynamixelDriver
  • Motors don’t move / rattle: Check power supply capacity and cable connections, and confirm individual IDs respond with diagnose_servos.py scan
  • Motion is erratic: Always lift the unit so the feet are in the air, run against real hardware to check behavior, then set it down