Actuator Control is a communication library to interface with eRob, Robstride, and Sito actuators over SocketCAN. The implementation is in Rust, with Python bindings.
The main control model exposed by the library is MIT control. At the API level, an MIT command is one control frame with five fields:
position: target output position in radiansvelocity: target output velocity in radians per secondkp: proportional gain, or stiffnesskd: derivative gain, or dampingtorque: feedforward output torque in newton-meters
Those values are sent with write_mit_control(). The library keeps that control
surface consistent across backends even when the underlying device protocol does
not use a literal MIT packet on the wire.
The package uses maturin to provide Python-Rust binding under the hood, but uv remains the user-facing
workflow for syncing, building, and running the project.
uv syncFor the plotting utilities in examples/actuator_characterization:
uv sync --extra exampleswrite_mit_control(actuator, position, velocity, kp, kd, torque) is the main
command interface for closed-loop actuator control.
Conceptually, the commanded output torque is:
tau = kp * (position_target - position) +
kd * (velocity_target - velocity) +
torque_feedforward
All three backends now use the same receive architecture:
- Python API calls send command frames on the calling thread
- a dedicated receive thread continuously reads the bus
- received frames are dispatched by protocol/message type
- the receiver updates the local actuator state buffer
get_state()returns the latest cached state reported by the backend
For eRob, write_mit_control() refreshes the cached position and velocity
before returning.
from actuator_control import Actuator, ERobBus
actuators = {
"joint": Actuator(id=15, model="eRob70"),
}
bus = ERobBus(channel="can0", actuators=actuators, bitrate=1_000_000)
bus.connect()
try:
bus.enable("joint")
bus.write_mit_control("joint", position=0.25, velocity=0.0, kp=10.0, kd=1.0, torque=0.0)
state = bus.get_state("joint")
if state is not None:
print(state.position, state.velocity)
finally:
bus.disconnect()More examples can be found in ./examples/ folder.
Optional calibration is passed as a Python dictionary:
calibration = {
"joint": {
"direction": -1,
"homing_offset": 0.15,
},
}- MIT control is still mapped onto the position-mode protocol.
write_mit_control()performs the explicit register reads needed to refresh the local state cache.write_mit_control()caches the last appliedkpandkdper actuator and only rewrites the loop-gain registers when they change.
- MIT gains are packed directly into each operation-control frame.
- Operation-control writes are followed by a device status frame that updates the local state cache.
- The actuator streams feedback continuously after
enable(). write_mit_control()caches the last appliedkpandkdper actuator and only sendsSET_MIT_KP_KDwhen they change.with_control_frequency()sets the requested feedback intervals for the feedback-1 and feedback-2 streams.