Introduce ServoManager for centralised bus servo control#162
Conversation
|
@copilot We have a few areas now where we need to manage multiple servos, the current architecture of independent servo instances is a problem because there are requests to coordinate motion as well as port access. The best practice for managing multiple servos, especially when you need coordinated motion and shared hardware resources (like a serial port), is to introduce a centralised ServoManager class. Here’s how this architecture should look: ServoManager: Owns the shared controller instance (and thus the serial port). Provides high-level APIs for:
Handles all low-level communication with the hardware, ensuring only one access at a time. Servo objects:
Coordination:
Messaging/Events:
Migration path:
|
Agent-Logs-Url: https://github.com/makerforgetech/modular-biped/sessions/ac60dd13-c688-4151-b48b-7f769e416658 Co-authored-by: danic85 <6583012+danic85@users.noreply.github.com>
Implemented the New classes in
Shared port access: Config changes:
|
What type of change is this?
📝 What does this change do?
Replaces the previous architecture of independent
Servoinstances with a centralisedServoManager+ lightweightServoStatedesign:ServoManager(newBaseModule): owns all hardware backends, manages every servo on a bus, processes per-servo move queues each loop cycle, and exposes high-level coordination APIs:move_to_pose(pose_name)— queues all servos for a named pose in one callgroup_move(moves)— queues coordinated moves for multiple servos simultaneouslydetach_all()/exit()— cleanly releases all hardware on shutdownServoState: lightweight per-servo data holder (ID, range, speed, queue, poses). Owns no hardware; delegates all I/O to theServoManager.WaveshareBusServonow uses a class-level singleton per(port, baudrate, model_type)with reference counting — all ST servos on the same bus share one serial connection rather than re-opening the port per servo.ServoManagerexposes a dict-like interface (__getitem__,items(),values(),get()) so existing code such asself.servos['neck_tilt'].move_relative(pitch)works unchanged after updating the inject key from"Servo_*"toServoManager.❓ Why is this change needed?
The previous architecture created one independent
Servoinstance per physical servo, each owning its own serial port handle. This caused two key problems:WaveshareBusServoinstances attempted to open the same serial port, risking conflicts on a shared RS-485 bus.A centralised manager resolves both issues and provides a clear extension point for trajectory planning and collision avoidance in the future.
🛠️ How was this implemented?
bus_servo.pyrewritten withServoState(lightweight holder) andServoManager(newBaseModule).WaveshareBusServoupdated with a class-level_port_singletonsdict (reference-counted) so all servos on a port share onePortHandler/PacketHandler.config.ymlclass changed fromServo→ServoManager.cody.yml,laptop.yml) updated: servo instances moved frominstances:list toconfig.servos:list; personality inject changed from"Servo_*"toServoManager.servo:<name>:mvabs,servo:<name>:mv,servo/pose, etc.) are now registered by the singleServoManagerinstance rather than per-servo.🧪 How was this tested?
bus_servo_test.pyandtest_bus_servo.pyto exerciseServoManagerandServoStatedirectly.detach,attach,get_position,is_moving), shared port singleton, and reference counting.TestRustypotBusServofixed.💥 Breaking changes
Yes (please describe)
Environment YAML files must be updated: servo instances should move from the
instances:key toconfig.servos:. Thepersonality(or any module) that previously injectedservos: "Servo_*"must change toservos: ServoManager. TheServoManagerdict-like interface ensures Python code that accessesself.servos[name]requires no further changes.🗂 Related issues
✅ PR Checklist
🚀 Thank you for your contribution to the project!
📍 Connect Copilot coding agent with Jira, Azure Boards or Linear to delegate work to Copilot in one click without leaving your project management tool.