One file per machine. Extend Follower on the machine that moves and Leader on the one that drives, and everything else, the session, safety, recording and video sync, comes with them.

Follower

Three methods are required: what the joints are called, how to read them, and how to write them. The rest are there when your hardware needs them.
follower.py
  • super().__init__() goes last. It runs your descriptor() and limits(), so anything they read has to exist first.
  • descriptor() declares your units. Everything else, slew, limits() and every recorded row, is read in them. See Descriptor.
  • limits() is what keeps the arm inside its travel. Leave it out and there are no position limits at all.
  • connect() is where you open your bus.
If a read or a write fails, raise. The follower stops the arm, which is safe. Returning the last value you saw instead leaves it moving on numbers that are no longer true.

Leader

Two methods, and nothing to write. Whatever read_joints() returns is where the operator is: a second arm, a VR controller, a simulator, or a policy.
leader.py
Keep JOINTS and the units in one module both machines import. The two descriptors are compared when the leader connects, and a follower and leader that disagree cannot run together.

Run it

Before hardware

Run your subclass against a simulated bus first, then real hardware with the network removed, then hardware over the network. Each step removes one source of failure, so a break is never ambiguous.
Writing a subclass is not the route to a ROS 2 or LeRobot arm. ROS 2 and LeRobot already are these subclasses.

Writing your own

The same thing as reference, with every method and rule.

Descriptor

Declaring joints, units and space, including machines that mix them.

Follower

Every constructor argument, method and event.

Safety

The checks a command passes before it reaches the motors.