RevPi compatibility: architecture refactor, actuator interface, robot model #25
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Arm Controller Architecture
This document describes the abstraction architecture for multi-robot and multi-servo compatibility in GradientOS.
Overview
The arm controller is designed to support:
gradient0,gradient0_5, future designs)This is achieved through two parallel abstraction layers:
Initialization Flow
File Structure
ActuatorBackend Interface
The complete interface that all servo backends must implement:
RobotConfig Interface
The complete interface that all robot configurations must implement:
Migration Path
Current State
servo_protocol.pycontains all Feetech-specific code (1264 lines)servo_protocol__getattr__for constantsTarget State
servo_protocol.pycontent tobackends/feetech/protocol.pyFeetechBackend(ActuatorBackend)inbackends/feetech/backend.pyservo_protocol.pybecomes thin dispatcher:Backward Compatibility
servo_protocolcontinue to workrobot_configcontinue to workAdding a New Robot
Create
robots/my_robot/config.py:Register in
robots/__init__.py:Use:
python -m gradient_os.run_controller --robot my_robotAdding a New Servo Backend
Create
backends/dynamixel/:Implement
DynamixelBackend(ActuatorBackend)with all required methodsRegister in
backends/__init__.py:Use:
python -m gradient_os.run_controller --robot gradient0 --servo-backend dynamixelDesign Principles
run_controller.pyDetailed Migration TODO
Current State Assessment
servo_protocol.pyservo_driver.pyservo_protocoldirectly, mixes concernstrajectory_execution.pyservo_protocoldirectlycommand_api.pyservo_protocoldirectlyrun_controller.pybackends/feetech/protocol.pybackends/feetech/driver.pybackends/registry.pyactuator_interface.pyrobot_config.pyutils.py_populate_servo_constants()and_populate_robot_constants()robots/base.pyrobots/gradient0/config.pyPhase 1: Backend Instance Management ✅ COMPLETE
Goal: Registry manages backend INSTANCES (not just configs)
1.1 Update
backends/registry.py:_active_backend_instance: Optional[ActuatorBackend] = Nonecreate_backend(name, robot_config) -> ActuatorBackendget_active_backend() -> ActuatorBackendset_active_backend_instance(backend: ActuatorBackend)1.2 Update
backends/__init__.py:BACKEND_CLASSES = {"feetech": FeetechBackend, "simulation": SimulationBackend}1.3 Update
run_controller.pyinitialization:Phase 2: High-Level Module Migration
Goal: Modules use backend instance instead of
servo_protocoldirectly2.1 servo_driver.py (Priority: HIGH) ✅ COMPLETE
2.1.1 Add backend accessor at top:
2.1.2 Migrate
initialize_servos():_get_backend().initialize()2.1.3 Migrate
set_servo_positions():servo_protocol.sync_write_goal_pos_speed_accel()_get_backend().set_joint_positions()orsync_write()2.1.4 Migrate
get_current_arm_state_rad():servo_protocol.sync_read_positions()_get_backend().get_joint_positions()2.1.5 Migrate calibration functions:
set_current_position_as_hardware_zero()→backend.set_current_position_as_zero()set_servo_pid_gains()→backend.set_pid_gains()set_servo_angle_limits_from_urdf()→backend.apply_joint_limits()2.1.6 Keep angle conversion helpers (robot-config dependent):
angle_to_raw(),raw_to_angle_rad()- these use robot config mapping_build_logical_to_servo_id_map()helper2.2 trajectory_execution.py (Priority: HIGH) ✅ COMPLETE
2.2.1 Add backend accessor functions:
_get_backend(),_use_backend()_build_primary_feedback_ids()- dynamically builds primary IDs from robot config_build_logical_to_physical_index_map()- replaces hardcoded joint-to-servo mapping_get_twin_motor_pairs()- gets twin motor pairs from robot config2.2.2 Migrate open-loop executor:
sync_write_goal_pos_speed_accel()now uses backend if available2.2.3 Migrate closed-loop execution:
sync_read_positions()now uses backend if availablesync_write_goal_pos_speed_accel()now uses backend if available2.2.4 Remove hardcoded twin motor logic:
20, 21, 30, 31with dynamic_get_twin_motor_pairs()2.2.5 Remove hardcoded primary ID mapping:
{0: 10, 1: 20, 2: 30, ...}with_build_primary_feedback_ids()2.2.6 Update sync_profiles for diagnostics:
backend.get_sync_profiles()if available2.3 command_api.py (Priority: MEDIUM) ✅ COMPLETE
servo_protocolandservo_drivercallsservo_protocolimport - all calls now go throughservo_driverread_single_servo_position()helper inservo_driver.pyset_single_servo_position_rads()to use backendSimulationBackendto properly handle servo ID to joint index mapping_populate_backend_constants()→_populate_servo_constants()_reinitialize_state()→_populate_robot_constants()2.4 run_controller.py (Priority: MEDIUM) ✅ COMPLETE
servo_driver.read_single_servo_position()backend.present_servo_idsfor servo listbackend.sync_read_block()for telemetry dataservo_protocolif backend doesn't have methodservo_driver.read_single_servo_position()backend.factory_reset_actuator()andbackend.restart_actuator()backend.sync_read_positions()servo_protocolcalls are fallbacks when backend lacks methodPhase 3: Deprecate Old Modules ✅ COMPLETE
Goal:
servo_protocol.pybecomes thin wrapper, then removed3.1 Made
servo_protocol.pya dispatcher:_get_backend(),_use_backend(),_warn_deprecated()helpersping()→backend.ping_actuator()read_servo_position()→backend.read_single_actuator_position()sync_read_positions()→backend.sync_read_positions()sync_write_goal_pos_speed_accel()→backend.sync_write()factory_reset_servo()→backend.factory_reset_actuator()restart_servo()→backend.restart_actuator()sync_read_block()→backend.sync_read_block()3.2 Added deprecation notices to all key functions via docstrings
3.3 All imports still work - backward compatible
3.4 (Future) Remove
servo_protocol.pywhen all usages are migrated to backendPhase 4: Clean Up ✅ COMPLETE
4.1 Created
backends/simulation/with proper structure:backends/simulation/__init__.py- exports SimulationBackendbackends/simulation/backend.py- full SimulationBackend implementation4.2 Cleaned up
actuator_interface.py:SimulationBackendtobackends/simulation/backend.pyactuator_interface.pynow only containsActuatorBackendABCSimulationBackendfor backward compatibility4.3 Updated
backends/__init__.py:SimulationBackendfrom new location4.4 Added deprecation notice to
sim_backend.py:Backward compatibility maintained:
from actuator_interface import SimulationBackendstill worksfrom backends import SimulationBackendstill worksfrom backends.simulation import SimulationBackendPhase 5: Testing & Validation
5.1 Test with real hardware:
gradient0robot works with new backend system5.2 Test simulation mode:
--simflag works5.3 Test error cases:
Function Migration Reference
servo_protocol.py → FeetechBackend
ping(servo_id)backend.ping_actuator(servo_id)send_servo_command()backend.set_single_actuator_position()sync_write_goal_pos_speed_accel()backend.sync_write()prepare_sync_write_commands()sync_read_positions()backend.sync_read_positions()read_servo_position()backend.read_single_actuator_position()read_servo_register_word()write_servo_register_word()calibrate_servo_middle_position()backend.set_current_position_as_zero()factory_reset_servo()backend.factory_reset_actuator()restart_servo()backend.restart_actuator()write_servo_angle_limits()backend.apply_joint_limits()set_servo_acceleration()get_present_servo_ids()backend.get_present_actuator_ids()servo_driver.py → FeetechBackend
initialize_servos()backend.initialize()set_servo_positions()backend.set_joint_positions()get_current_arm_state_rad()backend.get_joint_positions()set_single_servo_position_rads()backend.set_single_actuator_position()set_servo_pid_gains()backend.set_pid_gains()set_servo_angle_limits_from_urdf()backend.apply_joint_limits()set_current_position_as_hardware_zero()backend.set_current_position_as_zero()reinitialize_servo()logical_q_to_syncwrite_tuple()backend.prepare_sync_write_commands()angle_to_raw()raw_to_angle_rad()raw_to_joint_positions())Files to Create
Files to Modify
backends/registry.py- Add instance managementbackends/__init__.py- Register backend classesservo_driver.py- Use backend instancetrajectory_execution.py- Use backend instancecommand_api.py- Use backend instancerun_controller.py- Create backend instance at startupFiles to Deprecate/Remove
servo_protocol.py- Convert to dispatcher, then removesim_backend.py- Move tobackends/simulation/