diff --git a/auth.json b/auth.json
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/zh-cn/steering_infantry_can_id_audit.md b/docs/zh-cn/steering_infantry_can_id_audit.md
new file mode 100644
index 00000000..2bed724f
--- /dev/null
+++ b/docs/zh-cn/steering_infantry_can_id_audit.md
@@ -0,0 +1,81 @@
+# steering-infantry 电机 CAN ID 对照审计
+
+## 范围
+- 文件: `rmcs_ws/src/rmcs_core/src/hardware/steering-infantry.cpp`
+- 目标: 收集所有电机的发送报文 ID 与回调报文 ID,反推出电机 ID,并检查是否有偏差。
+
+## 反推规则
+- `DjiMotor` 采用 4 路合帧发送(`device::CanPacket8`)。
+- 对于 `can_id = 0x200` 的发送帧:
+ - `q0 -> 0x201`
+ - `q1 -> 0x202`
+ - `q2 -> 0x203`
+ - `q3 -> 0x204`
+- 对于 `can_id = 0x1FF / 0x1FE` 的发送帧(本项目代码中的使用方式):
+ - `q0 -> 0x205`
+ - `q1 -> 0x206`
+ - `q2 -> 0x207`
+ - `q3 -> 0x208`
+- `LkMotor` 直接使用单独 `can_id` 发送与接收(非 4 路合帧电机映射)。
+
+## TopBoard 电机映射
+代码位置:
+- 构造/电机定义: `121-137`
+- 发送: `212-253`
+- 回调: `256-279`
+
+| 电机 | 发送总线/ID/槽位 | 回调 ID | 反推电机 ID | 结果 |
+|---|---|---|---|---|
+| `/gimbal/left_friction` | CAN1, `0x200`, `q2` | `0x203` | `3` | 一致 |
+| `/gimbal/right_friction` | CAN1, `0x200`, `q3` | `0x204` | `4` | 一致 |
+| `/gimbal/bullet_feeder` | CAN1, `0x1FF`, `q0` | `0x205` | `5` | 一致 |
+| `/gimbal/yaw` (LK) | CAN2, `0x141` | `0x141` | `0x141` | 一致 |
+| `/gimbal/pitch` (LK) | CAN2, `0x142` | `0x142` | `0x142` | 一致 |
+
+## BottomBoard_one 电机映射
+代码位置:
+- 构造/电机定义: `308-328`
+- 发送: `396-434`
+- 回调: `437-466`
+
+| 电机 | 发送总线/ID/槽位 | 回调 ID | 反推电机 ID | 结果 |
+|---|---|---|---|---|
+| `/chassis/right_front_wheel` (`chassis_wheel_motors_[1]`) | CAN1, `0x200`, `q0` | `0x201` | `1` | 一致 |
+| `/chassis/left_front_wheel` (`chassis_wheel_motors_[0]`) | CAN1, `0x200`, `q1` | `0x202` | `2` | 一致 |
+| `/chassis/left_front_steering` (`chassis_steering_motors_[0]`) | CAN1, `0x1FE`, `q1` | `0x206` | `6` | 一致 |
+| `/chassis/right_front_steering` (`chassis_steering_motors_[1]`) | CAN1, `0x1FE`, `q3` | `0x208` | `8` | 一致 |
+| `/chassis/climber/left_back_motor` (`chassis_back_climber_motor_[0]`) | CAN2, `0x200`, `q0` | `0x201` | `1` | 一致 |
+| `/chassis/climber/right_back_motor` (`chassis_back_climber_motor_[1]`) | CAN2, `0x200`, `q1` | `0x202` | `2` | 一致 |
+| `/chassis/climber/left_front_motor` (`chassis_front_climber_motor_[0]`) | CAN2, `0x200`, `q2` | `0x203` | `3` | 一致 |
+| `/chassis/climber/right_front_motor` (`chassis_front_climber_motor_[1]`) | CAN2, `0x200`, `q3` | `0x204` | `4` | 一致 |
+
+## BottomBoard_two 电机映射
+代码位置:
+- 构造/电机定义: `491-507`
+- 发送: `563-591`
+- 回调: `594-610`
+
+| 电机 | 发送总线/ID/槽位 | 回调 ID | 反推电机 ID | 结果 |
+|---|---|---|---|---|
+| `/chassis/right_back_wheel` (`chassis_wheel_motors2_[1]`) | CAN1, `0x200`, `q2` | `0x203` | `3` | 一致 |
+| `/chassis/left_back_wheel` (`chassis_wheel_motors2_[0]`) | CAN1, `0x200`, `q3` | `0x204` | `4` | 一致 |
+| `/chassis/left_back_steering` (`chassis_steering_motors2_[0]`) | CAN1, `0x1FE`, `q0` | `0x205` | `5` | 一致 |
+| `/chassis/right_back_steering` (`chassis_steering_motors2_[1]`) | CAN1, `0x1FE`, `q2` | `0x207` | `7` | 一致 |
+
+## 总结
+- 结论 1: 所有电机都能在代码中找到“发送 -> 回调”的闭环映射,未发现直接冲突或漏接收。
+- 结论 2: 从代码反推,当前电机 ID 规划为:
+ - TopBoard: 摩擦轮 `3/4`,拨弹 `5`,LK 云台 `0x141/0x142`
+ - BottomBoard_one: 轮 `1/2`,前转向 `6/8`,攀爬 `1/2/3/4`(CAN2)
+ - BottomBoard_two: 后轮 `3/4`,后转向 `5/7`
+
+## 偏差/问题反馈
+- 问题 1(注释已提示): 文件头标注“电机ID没更改”,说明该映射可能为占位或沿用旧车配置,不能等同于当前实车真值。
+- 问题 2(可维护性): 多处左右轮顺序与 ID 顺序是“交叉映射”(例如右前轮是 ID1、左前轮是 ID2),逻辑上可行,但后续维护容易误改。
+- 问题 3(非 ID 但会影响状态): `TopBoard::update()` 中调用了 `gimbal_pitch_motor_.update_status()`,但未调用 `gimbal_yaw_motor_.update_status()`,可能导致 yaw 角度状态不更新。
+
+## 建议
+- 用实车实测确认最终 ID:
+ 1. 在各 `can*_receive_callback` 临时打印所有 `can_id`。
+ 2. 单电机动作,建立“物理电机 <-> can_id”表。
+ 3. 以实测表覆盖本文件映射,再同步更新发送槽位与回调分发。
diff --git a/librmcs-firmware-c_board-3.0.1-0.dev.4.gbaf538b.dfu b/librmcs-firmware-c_board-3.0.1-0.dev.4.gbaf538b.dfu
new file mode 100644
index 00000000..c3e952e1
Binary files /dev/null and b/librmcs-firmware-c_board-3.0.1-0.dev.4.gbaf538b.dfu differ
diff --git a/rmcs_ws/src/rmcs_bringup/config/climbable-infantry.yaml b/rmcs_ws/src/rmcs_bringup/config/climbable-infantry.yaml
new file mode 100644
index 00000000..8f464574
--- /dev/null
+++ b/rmcs_ws/src/rmcs_bringup/config/climbable-infantry.yaml
@@ -0,0 +1,266 @@
+rmcs_executor:
+ ros__parameters:
+ update_rate: 1000.0
+ components:
+ - rmcs_core::hardware::ClimbableInfantry -> Infantry_hardware
+
+ - rmcs_core::referee::Status -> referee_status
+ - rmcs_core::referee::command::Interaction -> referee_interaction
+ - rmcs_core::referee::command::interaction::Ui -> referee_ui
+ - rmcs_core::referee::app::ui::UIClimbableInfantry -> referee_ui_infantry
+ - rmcs_core::referee::Command -> referee_command
+
+ - rmcs_core::controller::gimbal::SimpleGimbalController -> gimbal_controller
+ - rmcs_core::controller::pid::ErrorPidController -> pitch_angle_pid_controller
+ - rmcs_core::controller::pid::PidController -> pitch_velocity_pid_controller
+ - rmcs_core::controller::pid::ErrorPidController -> yaw_angle_pid_controller
+ - rmcs_core::controller::pid::PidController -> yaw_velocity_pid_controller
+
+ # - rmcs_core::controller::gimbal::PlayerViewer -> gimbal_player_viewer_controller
+ # - rmcs_core::controller::pid::ErrorPidController -> viewer_angle_pid_controller
+
+ - rmcs_core::controller::shooting::FrictionWheelController -> friction_wheel_controller
+ - rmcs_core::controller::shooting::HeatController -> heat_controller
+ - rmcs_core::controller::shooting::BulletFeederController17mm -> bullet_feeder_controller
+ # - rmcs_core::controller::shooting::ShootingRecorder -> shooting_recorder
+ - rmcs_core::controller::pid::PidController -> left_friction_velocity_pid_controller
+ - rmcs_core::controller::pid::PidController -> right_friction_velocity_pid_controller
+ - rmcs_core::controller::pid::PidController -> bullet_feeder_velocity_pid_controller
+
+ - rmcs_core::controller::chassis::SteeringWheelStatus -> steering_wheel_status
+ - rmcs_core::controller::chassis::ClimbableInfantryChassisController -> chassis_controller
+ - rmcs_core::controller::chassis::ChassisPowerController -> chassis_power_controller
+ - rmcs_core::controller::chassis::SteeringWheelController -> steering_wheel_controller
+ - rmcs_core::controller::chassis::ClimbableInfantryChassisClimberController -> climber_controller
+
+ # - rmcs_auto_aim::AutoAimInitializer -> auto_aim_initializer
+ # - rmcs_auto_aim::AutoAimController -> auto_aim_controller
+
+ # - rmcs_core::broadcaster::ValueBroadcaster -> value_broadcaster
+
+Infantry_hardware:
+ ros__parameters:
+ board_serial_top_board: "D4-632C-37BC-6A17-ACA2-E3FE-91E9"
+ board_serial_bottom_board: "AF-EEF5-24BA-6675-F1B1-C797-50AF-869C-870E"
+ yaw_motor_zero_point: 7570
+ pitch_motor_zero_point: 24243
+ # top_yaw_motor_zero_point: 58744
+ # viewer_motor_zero_point: 3030
+ # external_imu_port: /dev/ttyUSB0
+ # bullet_feeder_motor_zero_point: 15111
+ left_front_zero_point: 5806
+ right_front_zero_point: 5092
+ left_back_zero_point: 3759
+ right_back_zero_point: 3103
+
+
+value_broadcaster:
+ ros__parameters:
+ forward_list:
+ - /gimbal/yaw/velocity
+ - /gimbal/yaw/control_velocity
+ # - /gimbal/yaw/control_torque
+ # - /gimbal/left_friction/velocity
+ # - /gimbal/right_friction/velocity
+ # - /chassis/pitch_imu
+ # - /gimbal/pitch/velocity_imu
+ # - /gimbal/pitch/angle
+ # - /gimbal/yaw/angle
+ # - /chassis/climber/right_back_motor/control_torque
+ # - /chassis/climber/left_back_motor/control_torque
+ # - /gimbal/putter/velocity
+ # - /gimbal/first_left_friction/velocity
+ # - /gimbal/first_second_friction/velocity
+ # - /gimbal/second_left_friction/velocity
+ # - /gimbal/second_right_friction/velocity
+ # - /gimbal/first_left_friction/control_torque
+ # - /gimbal/first_second_friction/control_torque
+ # - /gimbal/second_left_friction/control_torque
+ # - /gimbal/second_right_friction/control_torque
+ # - /chassis/left_front_steering/angle
+ # - /chassis/left_back_steering/angle
+ # - /chassis/right_back_steering/angle
+ # - /chassis/right_front_steering/angle
+ # - /chassis/left_front_steering/control_torque
+ # - /chassis/left_back_steering/control_torque
+ # - /chassis/right_back_steering/control_torque
+ # - /chassis/right_front_steering/control_torque
+ # - /gimbal/pitch/control_torque
+ # - /gimbal/pitch/velocity
+ # - /gimbal/pitch/control_velocity
+ # - /gimbal/pitch/control_angle_error
+ # - /gimbal/bullet_feeder/phase_zero
+ # - /gimbal/bullet_feeder/alignment_error
+ # - /chassis/climber/left_back_motor/torque
+ # - /chassis/climber/right_back_motor/torque
+ # - /chassis/climber/left_back_motor/velocity
+ # - /chassis/climber/right_back_motor/velocity
+ # - /chassis/climber/right_back_motor/control_torque
+ # - /chassis/climber/left_back_motor/control_torque
+
+climber_controller:
+ ros__parameters:
+ front_climber_velocity: 60.0
+ back_climber_velocity: 20.0 #13.0
+ auto_climb_dash_velocity: 4.0 #3.0
+ auto_climb_support_retract_velocity: 40.0 #47.0
+ sync_coefficient: 0.8
+ first_stair_approach_pitch: 0.590
+ second_stair_approach_pitch: 0.320
+ front_kp: 1.0
+ front_ki: 0.0
+ front_kd: 0.5
+ back_kp: 1.0
+ back_ki: 0.0
+ back_kd: 0.0
+
+gimbal_controller:
+ ros__parameters:
+ upper_limit: -0.54
+ lower_limit: 0.274
+
+pitch_angle_pid_controller:
+ ros__parameters:
+ measurement: /gimbal/pitch/control_angle_error
+ control: /gimbal/pitch/control_velocity
+ kp: 15.0 #5.0
+ ki: 0.0
+ kd: 0.0 #1.5
+
+pitch_velocity_pid_controller:
+ ros__parameters:
+ measurement: /gimbal/pitch/velocity_imu
+ setpoint: /gimbal/pitch/control_velocity
+ control: /gimbal/pitch/control_torque
+ kp: 40.0 #
+ ki: 0.00
+ kd: 0.8 #
+
+yaw_angle_pid_controller:
+ ros__parameters:
+ measurement: /gimbal/yaw/control_angle_error
+ control: /gimbal/yaw/control_velocity
+ kp: 20.0 #20.00
+ ki: 0.0
+ kd: 0.9 #0.9
+
+yaw_velocity_pid_controller:
+ ros__parameters:
+ measurement: /gimbal/yaw/velocity_imu
+ setpoint: /gimbal/yaw/control_velocity
+ control: /gimbal/yaw/control_torque
+ kp: 2.5 #2.5
+ ki: 0.00
+ kd: 0.6 #0.6
+
+friction_wheel_controller:
+ ros__parameters:
+ friction_wheels:
+ - /gimbal/left_friction
+ - /gimbal/right_friction
+ friction_velocities:
+ - 590.0
+ - 590.0
+ friction_soft_start_stop_time: 0.3
+
+heat_controller:
+ ros__parameters:
+ heat_per_shot: 10000
+ reserved_heat: 5000
+
+bullet_feeder_controller:
+ ros__parameters:
+ bullets_per_feeder_turn: 9.0
+ shot_frequency: 60.0
+ safe_shot_frequency: 10.0
+ eject_frequency: 10.0
+ eject_time: 0.05
+ deep_eject_frequency: 5.0
+ deep_eject_time: 0.2
+ single_shot_max_stop_delay: 2.0
+
+shooting_recorder:
+ ros__parameters:
+ friction_wheel_count: 2
+ log_mode: 1 # 1: trigger, 2: timing
+
+left_friction_velocity_pid_controller:
+ ros__parameters:
+ measurement: /gimbal/left_friction/velocity
+ setpoint: /gimbal/left_friction/control_velocity
+ control: /gimbal/left_friction/control_torque
+ kp: 0.003436926 #0.003436926 #0.0016
+ ki: 0.00
+ kd: 0.009373434 #0.009373434 #0.001
+
+right_friction_velocity_pid_controller:
+ ros__parameters:
+ measurement: /gimbal/right_friction/velocity
+ setpoint: /gimbal/right_friction/control_velocity
+ control: /gimbal/right_friction/control_torque
+ kp: 0.003436926 #0.0016
+ ki: 0.00
+ kd: 0.009373434 #0.009373434 #0.001
+
+bullet_feeder_velocity_pid_controller:
+ ros__parameters:
+ measurement: /gimbal/bullet_feeder/velocity
+ setpoint: /gimbal/bullet_feeder/control_velocity
+ control: /gimbal/bullet_feeder/control_torque
+ kp: 0.7
+ ki: 0.0
+ kd: 0.0
+
+steering_wheel_status:
+ ros__parameters:
+ vehicle_radius: 0.28284271247462
+ wheel_radius: 0.055
+
+steering_wheel_controller:
+ ros__parameters:
+ mess: 22.0
+ moment_of_inertia: 1.08
+ vehicle_radius: 0.28284271247462
+ wheel_radius: 0.055
+ friction_coefficient: 0.6
+ k1: 2.958580e+00
+ k2: 3.082190e-03
+ no_load_power: 11.37
+
+auto_aim_controller:
+ ros__parameters:
+ # capture
+ use_video: false # If true, use video stream instead of camera.
+ video_path: "/workspaces/RMCS/rmcs_ws/resources/1.avi"
+ exposure_time: 1
+ invert_image: false
+ # identifier
+ armor_model_path: "/models/mlp.onnx"
+ # pnp
+ fx: 1.722231837421459e+03
+ fy: 1.724876404292754e+03
+ cx: 7.013056440882832e+02
+ cy: 5.645821718351237e+02
+ k1: -0.064232403853946
+ k2: -0.087667493884102
+ k3: 0.792381808294582
+ # tracker
+ armor_predict_duration: 500
+ # controller
+ gimbal_predict_duration: 100
+ yaw_error: 0.
+ pitch_error: 0.
+ shoot_velocity: 28.0
+ predict_sec: 0.095
+ # etc
+ buff_predict_duration: 200
+ buff_model_path: "/models/buff_nocolor_v6.onnx"
+ omni_exposure: 1000.0
+ record_fps: 120
+ debug: false # Setup in actual using.Debug mode is used when referee is not ready
+ debug_color: 0 # 0 For blue while 1 for red. mine
+ debug_robot_id: 4
+ debug_buff_mode: false
+ record: false
+ raw_img_pub: false # Set false in actual use
+ image_viewer_type: 2
diff --git a/rmcs_ws/src/rmcs_bringup/config/steering-hero.yaml b/rmcs_ws/src/rmcs_bringup/config/steering-hero.yaml
new file mode 100644
index 00000000..d58c5745
--- /dev/null
+++ b/rmcs_ws/src/rmcs_bringup/config/steering-hero.yaml
@@ -0,0 +1,285 @@
+rmcs_executor:
+ ros__parameters:
+ update_rate: 1000.0
+ components:
+ - rmcs_core::hardware::SteeringHero -> hero_hardware
+
+ - rmcs_core::referee::Status -> referee_status
+ - rmcs_core::referee::command::Interaction -> referee_interaction
+ # - rmcs_core::referee::command::interaction::Ui -> referee_ui
+ # - rmcs_core::referee::app::ui::Hero -> referee_ui_hero
+ - rmcs_core::referee::Command -> referee_command
+
+ - rmcs_core::controller::gimbal::HeroGimbalController -> gimbal_controller
+ - rmcs_core::controller::gimbal::DualYawController -> dual_yaw_controller
+ - rmcs_core::controller::pid::ErrorPidController -> pitch_angle_pid_controller
+ - rmcs_core::controller::pid::PidController -> pitch_velocity_pid_controller
+
+ # - rmcs_core::controller::gimbal::PlayerViewer -> gimbal_player_viewer_controller
+ # - rmcs_core::controller::pid::ErrorPidController -> viewer_angle_pid_controller
+
+ - rmcs_core::controller::shooting::FrictionWheelController -> friction_wheel_controller
+ - rmcs_core::controller::shooting::HeatController -> heat_controller
+ - rmcs_core::controller::shooting::PutterController -> bullet_feeder_controller
+ - rmcs_core::controller::pid::PidController -> first_left_friction_velocity_pid_controller
+ - rmcs_core::controller::pid::PidController -> first_right_friction_velocity_pid_controller
+ - rmcs_core::controller::pid::PidController -> second_left_friction_velocity_pid_controller
+ - rmcs_core::controller::pid::PidController -> second_right_friction_velocity_pid_controller
+ - rmcs_core::controller::shooting::ShootingRecorder -> shooting_recorder
+
+ - rmcs_core::controller::chassis::ChassisController -> chassis_controller
+ - rmcs_core::controller::chassis::ChassisPowerController -> chassis_power_controller
+ - rmcs_core::controller::chassis::SteeringWheelController -> steering_wheel_controller
+ - rmcs_core::controller::chassis::ChassisClimberController -> climber_controller
+
+ # - rmcs_auto_aim::AutoAimInitializer -> auto_aim_initializer
+ # - rmcs_auto_aim::AutoAimController -> auto_aim_controller
+
+ - rmcs_core::broadcaster::ValueBroadcaster -> value_broadcaster
+
+hero_hardware:
+ ros__parameters:
+ board_serial_top_board: "D4-2BCA-2E47-76CD-23BC-0B78-684B"
+ board_serial_bottom_board_one: "D4-7973-19A9-EA40-4A3E-306F-10F9"
+ board_serial_bottom_board_two: "D4-3674-7174-8768-879E-E44A-3931"
+ bottom_yaw_motor_zero_point: 52508
+ pitch_motor_zero_point: 40651 #35701
+ top_yaw_motor_zero_point: 58744
+ viewer_motor_zero_point: 3030
+ external_imu_port: /dev/ttyUSB0
+ bullet_feeder_motor_zero_point: 15111
+ left_front_zero_point: 4516
+ right_front_zero_point: 7190
+ left_back_zero_point: 3841
+ right_back_zero_point: 5159
+ # left_front_zero_point: 3473
+ # left_back_zero_point: 6124
+ # right_back_zero_point: 6157
+ # right_front_zero_point: 2723
+
+value_broadcaster:
+ ros__parameters:
+ forward_list:
+ - /gimbal/putter/velocity
+ - /gimbal/first_left_friction/velocity
+ - /gimbal/first_second_friction/velocity
+ - /gimbal/second_left_friction/velocity
+ - /gimbal/second_right_friction/velocity
+ - /gimbal/first_left_friction/control_torque
+ - /gimbal/first_second_friction/control_torque
+ - /gimbal/second_left_friction/control_torque
+ - /gimbal/second_right_friction/control_torque
+ # - /chassis/left_front_steering/angle
+ # - /chassis/left_back_steering/angle
+ # - /chassis/right_back_steering/angle
+ # - /chassis/right_front_steering/angle
+ # - /chassis/left_front_steering/control_torque
+ # - /chassis/left_back_steering/control_torque
+ # - /chassis/right_back_steering/control_torque
+ # - /chassis/right_front_steering/control_torque
+ # - /gimbal/pitch/control_torque
+ # - /gimbal/pitch/velocity
+ # - /gimbal/pitch/control_velocity
+ # - /gimbal/pitch/control_angle_error
+ # - /gimbal/bullet_feeder/phase_zero
+ # - /gimbal/bullet_feeder/alignment_error
+ # - /chassis/climber/left_back_motor/torque
+ # - /chassis/climber/right_back_motor/torque
+ # - /chassis/climber/left_back_motor/velocity
+ # - /chassis/climber/right_back_motor/velocity
+ # - /chassis/climber/right_back_motor/control_torque
+ # - /chassis/climber/left_back_motor/control_torque
+
+climber_controller:
+ ros__parameters:
+ front_climber_velocity: 60.0
+ back_climber_velocity: 20.0
+ auto_climb_dash_velocity: 3.0
+ auto_climb_support_retract_velocity: 30.0
+ sync_coefficient: 0.3
+ climb_timeout_limit: 1000
+ first_stair_approach_pitch: 0.485
+ second_stair_approach_pitch: 0.35
+ back_climber_burst_velocity: 15.0
+ back_climber_burst_duration: 300
+ front_kp: 1.0
+ front_ki: 0.0
+ front_kd: 0.5
+ back_kp: 1.0
+ back_ki: 0.0
+ back_kd: 0.0
+
+gimbal_controller:
+ ros__parameters:
+ upper_limit: -0.6266
+ lower_limit: 0.4629
+
+dual_yaw_controller:
+ ros__parameters:
+ top_yaw_angle_kp: 10.0
+ top_yaw_angle_ki: 0.0
+ top_yaw_angle_kd: 0.0
+ top_yaw_velocity_kp: 50.0
+ top_yaw_velocity_ki: 0.004
+ top_yaw_velocity_kd: 0.0
+ bottom_yaw_angle_kp: 5.0
+ bottom_yaw_angle_ki: 0.0
+ bottom_yaw_angle_kd: 0.0
+ bottom_yaw_velocity_kp: 10.0
+ bottom_yaw_velocity_ki: 0.0
+ bottom_yaw_velocity_kd: 50.0
+
+# dual_yaw_controller:
+# ros__parameters:
+# top_yaw_angle_kp: 24.5
+# top_yaw_angle_ki: 0.0
+# top_yaw_angle_kd: 0.0
+# top_yaw_velocity_kp: 77.4
+# top_yaw_velocity_ki: 0.004
+# top_yaw_velocity_kd: 1.0
+# bottom_yaw_angle_kp: 8.6
+# bottom_yaw_angle_ki: 0.0
+# bottom_yaw_angle_kd: 0.0
+# bottom_yaw_velocity_kp: 25.85
+# bottom_yaw_velocity_ki: 0.0
+# bottom_yaw_velocity_kd: 50.0
+
+pitch_angle_pid_controller:
+ ros__parameters:
+ measurement: /gimbal/pitch/control_angle_error
+ control: /gimbal/pitch/control_velocity
+ kp: 30.0 #28.00
+ ki: 0.0
+ kd: 0.6 #0.6
+
+pitch_velocity_pid_controller:
+ ros__parameters:
+ measurement: /gimbal/pitch/velocity_imu
+ setpoint: /gimbal/pitch/control_velocity
+ control: /gimbal/pitch/control_torque
+ kp: 15.0 #45.00
+ ki: 0.00
+ kd: 2.0 #1.00
+
+gimbal_player_viewer_controller:
+ ros__parameters:
+ upper_limit: 0.0101
+ lower_limit: 0.6191
+
+viewer_angle_pid_controller:
+ ros__parameters:
+ measurement: /gimbal/player_viewer/control_angle_error
+ control: /gimbal/player_viewer/control_velocity
+ kp: 17.00
+ ki: 0.00
+ kd: 2.00
+
+friction_wheel_controller:
+ ros__parameters:
+ friction_wheels:
+ - /gimbal/first_left_friction
+ - /gimbal/first_right_friction
+ - /gimbal/second_left_friction
+ - /gimbal/second_right_friction
+ friction_velocities:
+ - 375.00
+ - 375.00
+ - 520.00
+ - 520.00
+ friction_soft_start_stop_time: 1.0
+
+heat_controller:
+ ros__parameters:
+ heat_per_shot: 100000
+ reserved_heat: 0
+
+shooting_recorder:
+ ros__parameters:
+ friction_wheel_count: 4
+ aim_velocity: 11.8
+ log_mode: 1 # 1: trigger, 2: timing
+
+first_left_friction_velocity_pid_controller:
+ ros__parameters:
+ measurement: /gimbal/first_left_friction/velocity
+ setpoint: /gimbal/first_left_friction/control_velocity
+ control: /gimbal/first_left_friction/control_torque
+ kp: 0.0016
+ ki: 0.00
+ kd: 0.001
+
+first_right_friction_velocity_pid_controller:
+ ros__parameters:
+ measurement: /gimbal/first_right_friction/velocity
+ setpoint: /gimbal/first_right_friction/control_velocity
+ control: /gimbal/first_right_friction/control_torque
+ kp: 0.0016
+ ki: 0.00
+ kd: 0.001
+
+second_left_friction_velocity_pid_controller:
+ ros__parameters:
+ measurement: /gimbal/second_left_friction/velocity
+ setpoint: /gimbal/second_left_friction/control_velocity
+ control: /gimbal/second_left_friction/control_torque
+ kp: 0.001
+ ki: 0.00
+ kd: 0.001
+
+second_right_friction_velocity_pid_controller:
+ ros__parameters:
+ measurement: /gimbal/second_right_friction/velocity
+ setpoint: /gimbal/second_right_friction/control_velocity
+ control: /gimbal/second_right_friction/control_torque
+ kp: 0.001
+ ki: 0.00
+ kd: 0.00
+
+steering_wheel_controller:
+ ros__parameters:
+ mess: 22.0
+ moment_of_inertia: 1.08
+ vehicle_radius: 0.28284271247462
+ wheel_radius: 0.055
+ friction_coefficient: 0.6
+ k1: 2.958580e+00
+ k2: 3.082190e-03
+ no_load_power: 11.37
+
+auto_aim_controller:
+ ros__parameters:
+ # capture
+ use_video: false # If true, use video stream instead of camera.
+ video_path: "/workspaces/RMCS/rmcs_ws/resources/1.avi"
+ exposure_time: 1
+ invert_image: false
+ # identifier
+ armor_model_path: "/models/mlp.onnx"
+ # pnp
+ fx: 1.722231837421459e+03
+ fy: 1.724876404292754e+03
+ cx: 7.013056440882832e+02
+ cy: 5.645821718351237e+02
+ k1: -0.064232403853946
+ k2: -0.087667493884102
+ k3: 0.792381808294582
+ # tracker
+ armor_predict_duration: 500
+ # controller
+ gimbal_predict_duration: 100
+ yaw_error: 0.
+ pitch_error: 0.
+ shoot_velocity: 28.0
+ predict_sec: 0.095
+ # etc
+ buff_predict_duration: 200
+ buff_model_path: "/models/buff_nocolor_v6.onnx"
+ omni_exposure: 1000.0
+ record_fps: 120
+ debug: false # Setup in actual using.Debug mode is used when referee is not ready
+ debug_color: 0 # 0 For blue while 1 for red. mine
+ debug_robot_id: 4
+ debug_buff_mode: false
+ record: false
+ raw_img_pub: false # Set false in actual use
+ image_viewer_type: 2
diff --git a/rmcs_ws/src/rmcs_core/CMakeLists.txt b/rmcs_ws/src/rmcs_core/CMakeLists.txt
index dd3c8579..36e7e570 100644
--- a/rmcs_ws/src/rmcs_core/CMakeLists.txt
+++ b/rmcs_ws/src/rmcs_core/CMakeLists.txt
@@ -17,8 +17,8 @@ include(FetchContent)
set(BUILD_STATIC_LIBRMCS ON CACHE BOOL "Build static librmcs SDK" FORCE)
FetchContent_Declare(
librmcs
- URL https://github.com/Alliance-Algorithm/librmcs/releases/download/v3.0.0/librmcs-sdk-src-3.0.0.zip
- URL_HASH SHA256=b39f51c21baacdcbf3f0176119b8850137a108b88a67e12395d37d89e5ef53e8
+ URL https://github.com/Alliance-Algorithm/librmcs/releases/download/v3.1.0/librmcs-sdk-src-3.1.0.zip
+ URL_HASH SHA256=07107e251745ddb23f7b3e39edec5d6910be1a197025d167ec9849c5c80dd954
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
)
FetchContent_MakeAvailable(librmcs)
diff --git a/rmcs_ws/src/rmcs_core/librmcs b/rmcs_ws/src/rmcs_core/librmcs
new file mode 160000
index 00000000..eff14b71
--- /dev/null
+++ b/rmcs_ws/src/rmcs_core/librmcs
@@ -0,0 +1 @@
+Subproject commit eff14b71e334baaaa462a83c7e6d834886b10168
diff --git a/rmcs_ws/src/rmcs_core/plugins.xml b/rmcs_ws/src/rmcs_core/plugins.xml
index f7847151..0bd9315c 100644
--- a/rmcs_ws/src/rmcs_core/plugins.xml
+++ b/rmcs_ws/src/rmcs_core/plugins.xml
@@ -1,29 +1,146 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Steering wheel controller.
+
+
+ Steering wheel controller.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Gimbal player viewer
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ the recorder of Hero
+
+
+ Test plugin.
+
+
+ Recorder for friction wheel PID tuning.
+
+
+ Test plugin.
+
+
+ Feedforward pid controller.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
+
+ Test plugin.
+
diff --git a/rmcs_ws/src/rmcs_core/src/controller/chassis/chassis_controller.cpp b/rmcs_ws/src/rmcs_core/src/controller/chassis/chassis_controller.cpp
index da70a96f..f183e349 100644
--- a/rmcs_ws/src/rmcs_core/src/controller/chassis/chassis_controller.cpp
+++ b/rmcs_ws/src/rmcs_core/src/controller/chassis/chassis_controller.cpp
@@ -19,7 +19,7 @@ class ChassisController
: Node(
get_component_name(),
rclcpp::NodeOptions{}.automatically_declare_parameters_from_overrides(true))
- , following_velocity_controller_(7.0, 0.0, 0.0) {
+ , following_velocity_controller_(9.0 , 0.0, 1000.0) {
following_velocity_controller_.output_max = angular_velocity_max;
following_velocity_controller_.output_min = -angular_velocity_max;
@@ -34,6 +34,7 @@ class ChassisController
register_input("/gimbal/yaw/angle", gimbal_yaw_angle_, false);
register_input("/gimbal/yaw/control_angle_error", gimbal_yaw_angle_error_, false);
+ register_input("/chassis/climbing_forward_velocity", climbing_forward_velocity_, nan);
register_output("/chassis/angle", chassis_angle_, nan);
register_output("/chassis/control_angle", chassis_control_angle_, nan);
@@ -58,8 +59,8 @@ class ChassisController
using namespace rmcs_msgs;
auto switch_right = *switch_right_;
- auto switch_left = *switch_left_;
- auto keyboard = *keyboard_;
+ auto switch_left = *switch_left_;
+ auto keyboard = *keyboard_;
do {
if ((switch_left == Switch::UNKNOWN || switch_right == Switch::UNKNOWN)
@@ -74,14 +75,14 @@ class ChassisController
if (mode == rmcs_msgs::ChassisMode::SPIN) {
mode = rmcs_msgs::ChassisMode::STEP_DOWN;
} else {
- mode = rmcs_msgs::ChassisMode::SPIN;
+ mode = rmcs_msgs::ChassisMode::SPIN;
spinning_forward_ = !spinning_forward_;
}
} else if (!last_keyboard_.c && keyboard.c) {
if (mode == rmcs_msgs::ChassisMode::SPIN) {
mode = rmcs_msgs::ChassisMode::AUTO;
} else {
- mode = rmcs_msgs::ChassisMode::SPIN;
+ mode = rmcs_msgs::ChassisMode::SPIN;
spinning_forward_ = !spinning_forward_;
}
} else if (!last_keyboard_.x && keyboard.x) {
@@ -100,8 +101,8 @@ class ChassisController
} while (false);
last_switch_right_ = switch_right;
- last_switch_left_ = switch_left;
- last_keyboard_ = keyboard;
+ last_switch_left_ = switch_left;
+ last_keyboard_ = keyboard;
}
void reset_all_controls() {
@@ -112,12 +113,16 @@ class ChassisController
void update_velocity_control() {
auto translational_velocity = update_translational_velocity_control();
- auto angular_velocity = update_angular_velocity_control();
+ auto angular_velocity = update_angular_velocity_control();
chassis_control_velocity_->vector << translational_velocity, angular_velocity;
}
Eigen::Vector2d update_translational_velocity_control() {
+ if (!std::isnan(*climbing_forward_velocity_)) {
+ return Eigen::Vector2d{*climbing_forward_velocity_, 0.0};
+ }
+
auto keyboard = *keyboard_;
Eigen::Vector2d keyboard_move{keyboard.w - keyboard.s, keyboard.a - keyboard.d};
@@ -133,7 +138,13 @@ class ChassisController
}
double update_angular_velocity_control() {
- double angular_velocity = 0.0;
+ if (!std::isnan(*climbing_forward_velocity_)) {
+ *chassis_angle_ = 2 * std::numbers::pi - *gimbal_yaw_angle_;
+ *chassis_control_angle_ = nan;
+ return 0.0;
+ }
+
+ double angular_velocity = 0.0;
double chassis_control_angle = nan;
switch (*mode_) {
@@ -171,7 +182,7 @@ class ChassisController
angular_velocity = following_velocity_controller_.update(err);
} break;
}
- *chassis_angle_ = 2 * std::numbers::pi - *gimbal_yaw_angle_;
+ *chassis_angle_ = 2 * std::numbers::pi - *gimbal_yaw_angle_;
*chassis_control_angle_ = chassis_control_angle;
return angular_velocity;
@@ -202,7 +213,7 @@ class ChassisController
// Maximum control velocities
static constexpr double translational_velocity_max = 10.0;
- static constexpr double angular_velocity_max = 16.0;
+ static constexpr double angular_velocity_max = 16.0;
InputInterface joystick_right_;
InputInterface joystick_left_;
@@ -214,10 +225,11 @@ class ChassisController
InputInterface rotary_knob_;
rmcs_msgs::Switch last_switch_right_ = rmcs_msgs::Switch::UNKNOWN;
- rmcs_msgs::Switch last_switch_left_ = rmcs_msgs::Switch::UNKNOWN;
- rmcs_msgs::Keyboard last_keyboard_ = rmcs_msgs::Keyboard::zero();
+ rmcs_msgs::Switch last_switch_left_ = rmcs_msgs::Switch::UNKNOWN;
+ rmcs_msgs::Keyboard last_keyboard_ = rmcs_msgs::Keyboard::zero();
InputInterface gimbal_yaw_angle_, gimbal_yaw_angle_error_;
+ InputInterface climbing_forward_velocity_;
OutputInterface chassis_angle_, chassis_control_angle_;
OutputInterface mode_;
diff --git a/rmcs_ws/src/rmcs_core/src/controller/chassis/chassis_power_controller.cpp b/rmcs_ws/src/rmcs_core/src/controller/chassis/chassis_power_controller.cpp
index 2d084f8b..cf4cb1a8 100644
--- a/rmcs_ws/src/rmcs_core/src/controller/chassis/chassis_power_controller.cpp
+++ b/rmcs_ws/src/rmcs_core/src/controller/chassis/chassis_power_controller.cpp
@@ -33,6 +33,9 @@ class ChassisPowerController
register_input("/referee/chassis/power_limit", chassis_power_limit_referee_);
register_input("/referee/chassis/buffer_energy", chassis_buffer_energy_referee_);
+ register_input(
+ "/chassis/climber/left_front_motor/velocity",
+ chassis_climber_left_front_motor_velocity_);
register_output("/chassis/supercap/charge_power_limit", supercap_charge_power_limit_, 0.0);
register_output("/chassis/control_power_limit", chassis_control_power_limit_, 0.0);
@@ -51,9 +54,9 @@ class ChassisPowerController
using namespace rmcs_msgs;
auto switch_right = *switch_right_;
- auto switch_left = *switch_left_;
- auto keyboard = *keyboard_;
- auto rotary_knob = *rotary_knob_;
+ auto switch_left = *switch_left_;
+ auto keyboard = *keyboard_;
+ auto rotary_knob = *rotary_knob_;
if ((switch_left == Switch::UNKNOWN || switch_right == Switch::UNKNOWN)
|| (switch_left == Switch::DOWN && switch_right == Switch::DOWN)) {
@@ -63,7 +66,7 @@ class ChassisPowerController
update_virtual_buffer_energy();
- boost_mode_ = keyboard.shift || rotary_knob < -0.9;
+ boost_mode_ = keyboard.shift || rotary_knob < -0.9 || rotary_knob > 0.9 || keyboard.g;
update_control_power_limit();
}
@@ -74,8 +77,8 @@ class ChassisPowerController
// charging_power_limit =
constexpr double buffer_energy_control_line = 120; // = referee + excess
- constexpr double buffer_energy_base_line = 30; // = referee
- constexpr double buffer_energy_dead_line = 0; // = 0
+ constexpr double buffer_energy_base_line = 30; // = referee
+ constexpr double buffer_energy_dead_line = 0; // = 0
*supercap_charge_power_limit_ =
*chassis_power_limit_referee_
@@ -91,8 +94,8 @@ class ChassisPowerController
}
void reset_power_control() {
- virtual_buffer_energy_ = virtual_buffer_energy_limit_;
- boost_mode_ = false;
+ virtual_buffer_energy_ = virtual_buffer_energy_limit_;
+ boost_mode_ = false;
*chassis_control_power_limit_ = 0.0;
}
@@ -106,9 +109,10 @@ class ChassisPowerController
void update_control_power_limit() {
double power_limit;
-
+ double chassis_climber_left_front_motor = *chassis_climber_left_front_motor_velocity_;
if (boost_mode_ && *supercap_enabled_)
- power_limit = *mode_ == rmcs_msgs::ChassisMode::LAUNCH_RAMP
+ power_limit = (*mode_ == rmcs_msgs::ChassisMode::LAUNCH_RAMP)
+ || (chassis_climber_left_front_motor > 0.5)
? inf_
: *chassis_power_limit_referee_ + 80.0;
else
@@ -117,8 +121,8 @@ class ChassisPowerController
// chassis_control_power_limit =
constexpr double supercap_voltage_control_line = 12.5; // = supercap
- constexpr double supercap_voltage_base_line = 12.0; // = referee
- power_limit = *chassis_power_limit_referee_
+ constexpr double supercap_voltage_base_line = 12.0; // = referee
+ power_limit = *chassis_power_limit_referee_
+ (power_limit - *chassis_power_limit_referee_)
* std::clamp(
(*supercap_voltage_ - supercap_voltage_base_line)
@@ -126,7 +130,7 @@ class ChassisPowerController
0.0, 1.0);
// Maximum excess power when virtual buffer energy is full.
- constexpr double excess_power_limit = 15;
+ constexpr double excess_power_limit = 0; // 15
power_limit += excess_power_limit;
power_limit *= virtual_buffer_energy_ / virtual_buffer_energy_limit_;
@@ -160,6 +164,8 @@ class ChassisPowerController
InputInterface chassis_power_limit_referee_;
InputInterface chassis_buffer_energy_referee_;
+ InputInterface chassis_climber_left_front_motor_velocity_;
+
bool boost_mode_ = false;
OutputInterface supercap_charge_power_limit_;
double chassis_power_limit_expected_;
diff --git a/rmcs_ws/src/rmcs_core/src/controller/chassis/climbable_infantry_chassis_climber_controller.cpp b/rmcs_ws/src/rmcs_core/src/controller/chassis/climbable_infantry_chassis_climber_controller.cpp
new file mode 100644
index 00000000..652ac130
--- /dev/null
+++ b/rmcs_ws/src/rmcs_core/src/controller/chassis/climbable_infantry_chassis_climber_controller.cpp
@@ -0,0 +1,571 @@
+#include "controller/pid/matrix_pid_calculator.hpp"
+#include "rmcs_msgs/keyboard.hpp"
+#include "rmcs_msgs/switch.hpp"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace rmcs_core::controller::chassis {
+
+enum class AutoClimbState {
+ IDLE,
+ ALIGN,
+ APPROACH,
+ SUPPORT_DEPLOY,
+ DASH,
+ SUPPORT_RETRACT,
+ FORERAKED
+};
+
+class ClimbableInfantryChassisClimberController
+ : public rmcs_executor::Component
+ , public rclcpp::Node {
+public:
+ ClimbableInfantryChassisClimberController()
+ : Node(
+ get_component_name(),
+ rclcpp::NodeOptions{}.automatically_declare_parameters_from_overrides(true))
+ , logger_(get_logger())
+ , front_velocity_pid_calculator_(
+ get_parameter("front_kp").as_double(), get_parameter("front_ki").as_double(),
+ get_parameter("front_kd").as_double())
+ , back_velocity_pid_calculator_(
+ get_parameter("back_kp").as_double(), get_parameter("back_ki").as_double(),
+ get_parameter("back_kd").as_double()) {
+
+ track_velocity_max_ = get_parameter("front_climber_velocity").as_double();
+ climber_back_control_velocity_abs_ = get_parameter("back_climber_velocity").as_double();
+ auto_climb_dash_velocity_ = get_parameter("auto_climb_dash_velocity").as_double();
+ auto_climb_support_retract_velocity_abs_ =
+ get_parameter("auto_climb_support_retract_velocity").as_double();
+ sync_coefficient_ = get_parameter("sync_coefficient").as_double();
+ first_stair_approach_pitch_ = get_parameter("first_stair_approach_pitch").as_double();
+ second_stair_approach_pitch_ = get_parameter("second_stair_approach_pitch").as_double();
+
+ register_output(
+ "/chassis/climber/left_front_motor/control_torque", climber_front_left_control_torque_,
+ nan_);
+ register_output(
+ "/chassis/climber/right_front_motor/control_torque",
+ climber_front_right_control_torque_, nan_);
+ register_output(
+ "/chassis/climber/left_back_motor/control_torque", climber_back_left_control_torque_,
+ nan_);
+ register_output(
+ "/chassis/climber/right_back_motor/control_torque", climber_back_right_control_torque_,
+ nan_);
+ register_output("/chassis/climbing_forward_velocity", climbing_forward_velocity_, nan_);
+
+ register_input("/chassis/climber/left_front_motor/velocity", climber_front_left_velocity_);
+ register_input(
+ "/chassis/climber/right_front_motor/velocity", climber_front_right_velocity_);
+ register_input("/chassis/climber/left_back_motor/velocity", climber_back_left_velocity_);
+ register_input("/chassis/climber/right_back_motor/velocity", climber_back_right_velocity_);
+
+ register_input("/chassis/climber/left_back_motor/torque", climber_back_left_torque_);
+ register_input("/chassis/climber/right_back_motor/torque", climber_back_right_torque_);
+
+ register_input("/remote/switch/right", switch_right_);
+ register_input("/remote/switch/left", switch_left_);
+ register_input("/remote/keyboard", keyboard_);
+ register_input("/remote/rotary_knob_switch", rotary_knob_switch_);
+ register_input("/chassis/pitch_imu", chassis_pitch_imu_);
+
+ register_input("/gimbal/yaw/angle", gimbal_yaw_angle_);
+ register_input("/gimbal/yaw/control_angle_error", gimbal_yaw_angle_error_);
+ register_input("/gimbal/yaw/velocity_imu", gimbal_yaw_velocity_imu_);
+ }
+
+ void update() override {
+ using namespace rmcs_msgs;
+
+ auto switch_right = *switch_right_;
+ auto switch_left = *switch_left_;
+ auto keyboard = *keyboard_;
+ auto rotary_knob_switch = *rotary_knob_switch_;
+
+ // RCLCPP_INFO(logger_, "%lf", *chassis_pitch_imu_);
+
+ bool rotary_knob_to_down =
+ (last_rotary_knob_switch_ != Switch::DOWN && rotary_knob_switch == Switch::DOWN);
+ bool rotary_knob_from_down =
+ (last_rotary_knob_switch_ == Switch::DOWN && rotary_knob_switch != Switch::DOWN);
+
+ if ((switch_left == Switch::UNKNOWN || switch_right == Switch::UNKNOWN)
+ || (switch_left == Switch::DOWN && switch_right == Switch::DOWN)) {
+ reset_all_controls();
+ } else {
+ handle_auto_climb_requests(
+ (!last_keyboard_.g && keyboard.g) || rotary_knob_to_down, rotary_knob_from_down,
+ rotary_knob_switch);
+
+ if (auto_climb_state_ != AutoClimbState::IDLE) {
+ stop_manual_support();
+ apply_auto_climb_control(update_auto_climb_control());
+ } else {
+ apply_auto_climb_control(update_manual_support_control(keyboard));
+ apply_manual_support_zero_output();
+ }
+ }
+
+ last_switch_left_ = switch_left;
+ last_switch_right_ = switch_right;
+ last_keyboard_ = keyboard;
+ last_rotary_knob_switch_ = rotary_knob_switch;
+ }
+
+private:
+ struct AutoClimbControl {
+ double front_track_velocity = nan_;
+ double back_climber_velocity = nan_;
+ double override_chassis_vx = nan_;
+ };
+
+ void handle_auto_climb_requests(
+ bool start_requested, bool abort_by_rotary, rmcs_msgs::Switch rotary_knob_switch) {
+
+ if (start_requested) {
+ if (auto_climb_state_ == AutoClimbState::IDLE) {
+ start_auto_climb(
+ rotary_knob_switch == rmcs_msgs::Switch::UP ? "Rotary Knob" : "Keyboard G");
+ } else {
+ abort_auto_climb("toggled again");
+ }
+ } else if (abort_by_rotary && auto_climb_state_ != AutoClimbState::IDLE) {
+ abort_auto_climb("rotary knob left UP");
+ }
+ }
+
+ void start_auto_climb(const char* source) {
+ stop_manual_support();
+ auto_climb_continue_ = true;
+ auto_climb_stair_index_ = 0;
+ auto_climb_align_stable_count_ = 0;
+ auto_climb_support_block_count_ = 0;
+ enter_auto_climb_state(AutoClimbState::ALIGN);
+
+ RCLCPP_INFO(logger_, "Auto climb started by %s. Entering ALIGN.", source);
+ }
+
+ void abort_auto_climb(const char* reason) {
+ reset_all_controls();
+ RCLCPP_INFO(logger_, "Auto climb aborted (%s).", reason);
+ }
+
+ AutoClimbControl update_auto_climb_control() {
+
+ if (auto_climb_state_ == AutoClimbState::IDLE) {
+ detect_is_foreraked();
+ if (auto_climb_state_ == AutoClimbState::IDLE) {
+ return {};
+ }
+ }
+
+ auto_climb_timer_++;
+
+ switch (auto_climb_state_) {
+ case AutoClimbState::IDLE: return {};
+ case AutoClimbState::ALIGN: return update_auto_climb_align();
+ case AutoClimbState::APPROACH: return update_auto_climb_approach();
+ case AutoClimbState::SUPPORT_DEPLOY: return update_auto_climb_support_deploy();
+ case AutoClimbState::DASH: return update_auto_climb_dash();
+ case AutoClimbState::SUPPORT_RETRACT: return update_auto_climb_support_retract();
+ case AutoClimbState::FORERAKED: return update_forerake_defend();
+ }
+
+ return {};
+ }
+
+ AutoClimbControl update_manual_support_control(const rmcs_msgs::Keyboard& keyboard) {
+ AutoClimbControl control;
+
+ if (keyboard.b) {
+ stop_manual_support();
+ control.back_climber_velocity = climber_back_control_velocity_abs_;
+ return control;
+ }
+
+ if (last_keyboard_.b) {
+ manual_support_retracting_ = true;
+ manual_support_retract_block_count_ = 0;
+ manual_support_zero_output_ = false;
+ RCLCPP_INFO(logger_, "Manual support retract started.");
+ }
+
+ if (!manual_support_retracting_) {
+ return control;
+ }
+
+ control.back_climber_velocity = -auto_climb_support_retract_velocity_abs_;
+
+ if (is_back_climber_blocked()) {
+ manual_support_retract_block_count_++;
+ } else {
+ manual_support_retract_block_count_ = 0;
+ }
+
+ RCLCPP_INFO_THROTTLE(
+ logger_, *get_clock(), 500, "MANUAL_SUPPORT_RETRACT: blocked_ticks=%d",
+ manual_support_retract_block_count_);
+
+ if (manual_support_retract_block_count_ >= kManualSupportRetractConfirmTicks) {
+ stop_manual_support();
+ manual_support_zero_output_ = true;
+ RCLCPP_INFO(logger_, "Manual support retract completed.");
+ return {};
+ }
+
+ return control;
+ }
+
+ AutoClimbControl update_auto_climb_align() {
+ AutoClimbControl control{
+ .front_track_velocity = 0.0,
+ .back_climber_velocity = 0.0,
+ .override_chassis_vx = 0.0,
+ };
+
+ double gimbal_yaw_angle_error = *gimbal_yaw_angle_error_;
+ if (gimbal_yaw_angle_error < 0)
+ gimbal_yaw_angle_error += 2 * std::numbers::pi;
+
+ double err = gimbal_yaw_angle_error + *gimbal_yaw_angle_;
+ while (err >= std::numbers::pi)
+ err -= 2 * std::numbers::pi;
+ while (err < -std::numbers::pi)
+ err += 2 * std::numbers::pi;
+
+ double yaw_velocity = *gimbal_yaw_velocity_imu_;
+ bool is_aligned = std::abs(err) < kAutoClimbAlignThreshold;
+ bool is_stable = std::abs(yaw_velocity) < kAutoClimbAlignVelocityThreshold;
+
+ if (is_aligned && is_stable) {
+ auto_climb_align_stable_count_++;
+ } else {
+ auto_climb_align_stable_count_ = 0;
+ }
+
+ RCLCPP_INFO_THROTTLE(
+ logger_, *get_clock(), 500, "ALIGN: err=%.3f, yaw_velocity=%.3f, stable_ticks=%d", err,
+ yaw_velocity, auto_climb_align_stable_count_);
+
+ if (auto_climb_align_stable_count_ >= kAutoClimbAlignConfirmTicks) {
+ enter_auto_climb_state(AutoClimbState::APPROACH);
+ RCLCPP_INFO(logger_, "Chassis aligned. Entering APPROACH.");
+ }
+
+ return control;
+ }
+
+ AutoClimbControl update_auto_climb_approach() {
+ AutoClimbControl control{
+ .front_track_velocity = track_velocity_max_,
+ .back_climber_velocity = 0.0,
+ .override_chassis_vx = kAutoClimbApproachVelocity,
+ };
+
+ double pitch = *chassis_pitch_imu_;
+ double target_pitch = auto_climb_stair_index_ == 0 ? first_stair_approach_pitch_
+ : second_stair_approach_pitch_;
+
+ RCLCPP_INFO_THROTTLE(
+ logger_, *get_clock(), 500, "APPROACH (step %d): pitch=%.3f, target>%.3f",
+ auto_climb_stair_index_ + 1, pitch, target_pitch);
+
+ if (pitch > target_pitch) {
+ enter_auto_climb_state(AutoClimbState::SUPPORT_DEPLOY);
+ RCLCPP_INFO(
+ logger_, "Auto climb entering SUPPORT_DEPLOY (step %d).",
+ auto_climb_stair_index_ + 1);
+ }
+
+ return control;
+ }
+
+ AutoClimbControl update_auto_climb_support_deploy() {
+ AutoClimbControl control{
+ .front_track_velocity = 0.0,
+ .back_climber_velocity = climber_back_control_velocity_abs_,
+ .override_chassis_vx = 0.5,
+ };
+
+ if (is_back_climber_blocked()) {
+ auto_climb_support_block_count_++;
+ } else {
+ auto_climb_support_block_count_ = 0;
+ }
+
+ RCLCPP_INFO_THROTTLE(
+ logger_, *get_clock(), 500, "SUPPORT_DEPLOY (step %d): blocked_ticks=%d",
+ auto_climb_stair_index_ + 1, auto_climb_support_block_count_);
+
+ if (auto_climb_support_block_count_ >= kAutoClimbSupportConfirmTicks) {
+ enter_auto_climb_state(AutoClimbState::DASH);
+ RCLCPP_INFO(
+ logger_, "Auto climb entering DASH (step %d).", auto_climb_stair_index_ + 1);
+ }
+
+ return control;
+ }
+
+ AutoClimbControl update_auto_climb_dash() {
+ AutoClimbControl control{
+ .front_track_velocity = 0.0,
+ .back_climber_velocity = climber_back_control_velocity_abs_,
+ .override_chassis_vx = auto_climb_dash_velocity_,
+ };
+
+ double pitch = *chassis_pitch_imu_;
+ bool is_leveled = std::abs(pitch) < kAutoClimbLeveledPitchThreshold
+ && auto_climb_timer_ > kAutoClimbDashMinTicks;
+ bool timeout = auto_climb_timer_ > kAutoClimbDashTimeoutTicks;
+
+ RCLCPP_INFO_THROTTLE(
+ logger_, *get_clock(), 500, "DASH (step %d): pitch=%.3f, timer=%d",
+ auto_climb_stair_index_ + 1, pitch, auto_climb_timer_);
+
+ if (is_leveled || timeout) {
+ enter_auto_climb_state(AutoClimbState::SUPPORT_RETRACT);
+
+ if (timeout) {
+ RCLCPP_WARN(
+ logger_, "Auto climb DASH timeout on step %d. Entering SUPPORT_RETRACT.",
+ auto_climb_stair_index_ + 1);
+ } else {
+ RCLCPP_INFO(
+ logger_, "Auto climb reached platform on step %d.",
+ auto_climb_stair_index_ + 1);
+ }
+ }
+
+ return control;
+ }
+
+ AutoClimbControl update_auto_climb_support_retract() {
+ AutoClimbControl control{
+ .front_track_velocity = 0.0,
+ .back_climber_velocity = -auto_climb_support_retract_velocity_abs_,
+ .override_chassis_vx = auto_climb_dash_velocity_,
+ };
+
+ RCLCPP_INFO_THROTTLE(
+ logger_, *get_clock(), 500, "SUPPORT_RETRACT (step %d): timer=%d",
+ auto_climb_stair_index_ + 1, auto_climb_timer_);
+
+ if (auto_climb_timer_ > kAutoClimbSupportRetractTicks) {
+ bool has_next_stair =
+ auto_climb_continue_ && (auto_climb_stair_index_ + 1 < kAutoClimbMaxStairs);
+
+ if (has_next_stair) {
+ auto_climb_stair_index_++;
+ enter_auto_climb_state(AutoClimbState::APPROACH);
+ RCLCPP_INFO(
+ logger_, "Auto climb continuing to step %d.", auto_climb_stair_index_ + 1);
+ } else {
+ int finished_steps = auto_climb_stair_index_ + 1;
+ stop_auto_climb();
+ RCLCPP_INFO(logger_, "Auto climb completed (finished %d steps).", finished_steps);
+ }
+ }
+
+ return control;
+ }
+
+ AutoClimbControl update_forerake_defend() {
+ AutoClimbControl control{
+ .front_track_velocity = track_velocity_max_,
+ .back_climber_velocity = 0.0,
+ .override_chassis_vx = nan_,
+ };
+
+ double pitch = *chassis_pitch_imu_;
+ bool is_leveled = std::abs(pitch) < kAutoClimbLeveledPitchThreshold
+ && auto_climb_timer_ > kAutoClimbDashMinTicks;
+ bool timeout = auto_climb_timer_ > kAutoClimbForerakedTimeoutTicks;
+
+ if (is_leveled || timeout) {
+ enter_auto_climb_state(AutoClimbState::IDLE);
+ }
+
+ return control;
+ }
+
+ void apply_auto_climb_control(const AutoClimbControl& control) {
+ *climbing_forward_velocity_ = control.override_chassis_vx;
+
+ dual_motor_sync_control(
+ control.front_track_velocity, *climber_front_left_velocity_,
+ *climber_front_right_velocity_, front_velocity_pid_calculator_,
+ *climber_front_left_control_torque_, *climber_front_right_control_torque_);
+
+ dual_motor_sync_control(
+ control.back_climber_velocity, *climber_back_left_velocity_,
+ *climber_back_right_velocity_, back_velocity_pid_calculator_,
+ *climber_back_left_control_torque_, *climber_back_right_control_torque_);
+ }
+
+ void reset_all_controls() {
+ *climber_front_left_control_torque_ = nan_;
+ *climber_front_right_control_torque_ = nan_;
+ *climber_back_left_control_torque_ = nan_;
+ *climber_back_right_control_torque_ = nan_;
+ *climbing_forward_velocity_ = nan_;
+ stop_manual_support();
+ stop_auto_climb();
+ }
+
+ void apply_manual_support_zero_output() {
+ if (!manual_support_zero_output_) {
+ return;
+ }
+
+ *climber_back_left_control_torque_ = 0.0;
+ *climber_back_right_control_torque_ = 0.0;
+ }
+
+ void stop_manual_support() {
+ manual_support_retracting_ = false;
+ manual_support_retract_block_count_ = 0;
+ manual_support_zero_output_ = false;
+ }
+
+ void stop_auto_climb() {
+ auto_climb_state_ = AutoClimbState::IDLE;
+ auto_climb_timer_ = 0;
+ auto_climb_stair_index_ = 0;
+ auto_climb_continue_ = false;
+ auto_climb_align_stable_count_ = 0;
+ auto_climb_support_block_count_ = 0;
+ }
+
+ void enter_auto_climb_state(AutoClimbState state) {
+ if (state == auto_climb_state_) {
+ return;
+ }
+ auto_climb_state_ = state;
+ auto_climb_timer_ = 0;
+ auto_climb_align_stable_count_ = 0;
+ auto_climb_support_block_count_ = 0;
+ }
+
+ bool is_back_climber_blocked() const {
+ return (std::abs(*climber_back_left_torque_) > kBackClimberBlockedTorqueThreshold
+ && std::abs(*climber_back_left_velocity_) < kBackClimberBlockedVelocityThreshold)
+ || (std::abs(*climber_back_right_torque_) > kBackClimberBlockedTorqueThreshold
+ && std::abs(*climber_back_right_velocity_) < kBackClimberBlockedVelocityThreshold);
+ }
+
+ void dual_motor_sync_control(
+ double setpoint, double left_velocity, double right_velocity,
+ pid::MatrixPidCalculator<2>& pid_calculator, double& left_torque_out,
+ double& right_torque_out) {
+
+ if (std::isnan(setpoint)) {
+ left_torque_out = nan_;
+ right_torque_out = nan_;
+ return;
+ }
+
+ Eigen::Vector2d setpoint_error{setpoint - left_velocity, setpoint - right_velocity};
+ Eigen::Vector2d relative_velocity{
+ left_velocity - right_velocity, right_velocity - left_velocity};
+
+ Eigen::Vector2d control_error = setpoint_error - sync_coefficient_ * relative_velocity;
+ auto control_torques = pid_calculator.update(control_error);
+
+ left_torque_out = control_torques[0];
+ right_torque_out = control_torques[1];
+ }
+
+ void detect_is_foreraked() {
+ if (auto_climb_state_ != AutoClimbState::IDLE) {
+ return;
+ }
+
+ double pitch = *chassis_pitch_imu_;
+ bool is_foreraked = pitch < kForerakedetectPitchThreshold;
+ if (is_foreraked) {
+ RCLCPP_INFO(get_logger(), "Enter foreraked");
+ enter_auto_climb_state(AutoClimbState::FORERAKED);
+ }
+ }
+
+ rclcpp::Logger logger_;
+ static constexpr double nan_ = std::numeric_limits::quiet_NaN();
+ static constexpr double kAutoClimbApproachVelocity = 1.5;
+ static constexpr double kAutoClimbAlignThreshold = 0.10;
+ static constexpr double kAutoClimbAlignVelocityThreshold = 0.2;
+ static constexpr double kAutoClimbLeveledPitchThreshold = 0.1;
+ static constexpr double kForerakedetectPitchThreshold = -0.05;
+ static constexpr double kBackClimberBlockedTorqueThreshold = 0.1;
+ static constexpr double kBackClimberBlockedVelocityThreshold = 0.1;
+ static constexpr int kAutoClimbAlignConfirmTicks = 50;
+ static constexpr int kAutoClimbSupportConfirmTicks = 100;
+ static constexpr int kAutoClimbDashMinTicks = 500;
+ static constexpr int kAutoClimbDashTimeoutTicks = 3000;
+ static constexpr int kAutoClimbForerakedTimeoutTicks = 2000;
+ static constexpr int kAutoClimbSupportRetractTicks = 1500;
+ static constexpr int kAutoClimbMaxStairs = 2;
+ static constexpr int kManualSupportRetractConfirmTicks = 50;
+
+ double sync_coefficient_;
+ double first_stair_approach_pitch_;
+ double second_stair_approach_pitch_;
+
+ double track_velocity_max_;
+ double climber_back_control_velocity_abs_;
+ double auto_climb_dash_velocity_;
+ double auto_climb_support_retract_velocity_abs_;
+
+ AutoClimbState auto_climb_state_ = AutoClimbState::IDLE;
+ int auto_climb_timer_ = 0;
+ int auto_climb_stair_index_ = 0;
+ int auto_climb_align_stable_count_ = 0;
+ int auto_climb_support_block_count_ = 0;
+ bool auto_climb_continue_ = false;
+ bool manual_support_retracting_ = false;
+ int manual_support_retract_block_count_ = 0;
+ bool manual_support_zero_output_ = false;
+
+ OutputInterface climber_front_left_control_torque_;
+ OutputInterface climber_front_right_control_torque_;
+ OutputInterface climber_back_left_control_torque_;
+ OutputInterface climber_back_right_control_torque_;
+ OutputInterface climbing_forward_velocity_;
+
+ InputInterface climber_front_left_velocity_;
+ InputInterface climber_front_right_velocity_;
+ InputInterface climber_back_left_velocity_;
+ InputInterface climber_back_right_velocity_;
+
+ InputInterface climber_back_left_torque_;
+ InputInterface climber_back_right_torque_;
+
+ InputInterface switch_right_;
+ InputInterface switch_left_;
+ InputInterface keyboard_;
+ InputInterface rotary_knob_switch_;
+
+ InputInterface chassis_pitch_imu_;
+ InputInterface gimbal_yaw_angle_, gimbal_yaw_angle_error_, gimbal_yaw_velocity_imu_;
+
+ rmcs_msgs::Switch last_switch_right_ = rmcs_msgs::Switch::UNKNOWN;
+ rmcs_msgs::Switch last_switch_left_ = rmcs_msgs::Switch::UNKNOWN;
+ rmcs_msgs::Switch last_rotary_knob_switch_ = rmcs_msgs::Switch::UNKNOWN;
+ rmcs_msgs::Keyboard last_keyboard_ = rmcs_msgs::Keyboard::zero();
+
+ pid::MatrixPidCalculator<2> front_velocity_pid_calculator_, back_velocity_pid_calculator_;
+};
+} // namespace rmcs_core::controller::chassis
+
+#include
+
+PLUGINLIB_EXPORT_CLASS(
+ rmcs_core::controller::chassis::ClimbableInfantryChassisClimberController,
+ rmcs_executor::Component)
\ No newline at end of file
diff --git a/rmcs_ws/src/rmcs_core/src/controller/chassis/climbable_infantry_chassis_controller.cpp b/rmcs_ws/src/rmcs_core/src/controller/chassis/climbable_infantry_chassis_controller.cpp
new file mode 100644
index 00000000..13d101ef
--- /dev/null
+++ b/rmcs_ws/src/rmcs_core/src/controller/chassis/climbable_infantry_chassis_controller.cpp
@@ -0,0 +1,287 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "controller/pid/pid_calculator.hpp"
+
+namespace rmcs_core::controller::chassis {
+
+class ClimbableInfantryChassisController
+ : public rmcs_executor::Component
+ , public rclcpp::Node {
+public:
+ ClimbableInfantryChassisController()
+ : Node(
+ get_component_name(),
+ rclcpp::NodeOptions{}.automatically_declare_parameters_from_overrides(true))
+ , following_velocity_controller_(10.0, 0.0, 1.2) {
+ following_velocity_controller_.output_max = angular_velocity_max;
+ following_velocity_controller_.output_min = -angular_velocity_max;
+
+ register_input("/remote/joystick/right", joystick_right_);
+ register_input("/remote/joystick/left", joystick_left_);
+ register_input("/remote/switch/right", switch_right_);
+ register_input("/remote/switch/left", switch_left_);
+ register_input("/remote/mouse/velocity", mouse_velocity_);
+ register_input("/remote/mouse", mouse_);
+ register_input("/remote/keyboard", keyboard_);
+ register_input("/remote/rotary_knob", rotary_knob_);
+
+ register_input("/gimbal/yaw/angle", gimbal_yaw_angle_, false);
+ register_input("/gimbal/yaw/control_angle_error", gimbal_yaw_angle_error_, false);
+ register_input("/chassis/velocity", chassis_velocity_, false);
+ register_input("/chassis/climbing_forward_velocity", climbing_forward_velocity_, false);
+
+ register_output("/chassis/angle", chassis_angle_, nan);
+ register_output("/chassis/control_angle", chassis_control_angle_, nan);
+
+ register_output("/chassis/control_mode", mode_);
+ register_output("/chassis/control_velocity", chassis_control_velocity_);
+ }
+
+ void before_updating() override {
+ if (!gimbal_yaw_angle_.ready()) {
+ gimbal_yaw_angle_.make_and_bind_directly(0.0);
+ RCLCPP_WARN(get_logger(), "Failed to fetch \"/gimbal/yaw/angle\". Set to 0.0.");
+ }
+ if (!gimbal_yaw_angle_error_.ready()) {
+ gimbal_yaw_angle_error_.make_and_bind_directly(0.0);
+ RCLCPP_WARN(
+ get_logger(), "Failed to fetch \"/gimbal/yaw/control_angle_error\". Set to 0.0.");
+ }
+ chassis_velocity_feedback_ready_ = chassis_velocity_.ready();
+ if (!chassis_velocity_feedback_ready_) {
+ chassis_velocity_.make_and_bind_directly(0.0, 0.0, 0.0);
+ }
+ }
+
+ void update() override {
+ using namespace rmcs_msgs;
+
+ auto switch_right = *switch_right_;
+ auto switch_left = *switch_left_;
+ auto keyboard = *keyboard_;
+ auto mode = *mode_;
+
+ do {
+ if ((switch_left == Switch::UNKNOWN || switch_right == Switch::UNKNOWN)
+ || (switch_left == Switch::DOWN && switch_right == Switch::DOWN)) {
+ reset_all_controls();
+ break;
+ }
+ if (switch_left != Switch::DOWN) {
+ if (last_switch_right_ == Switch::MIDDLE && switch_right == Switch::DOWN) {
+ if (mode == rmcs_msgs::ChassisMode::SPIN) {
+ mode = rmcs_msgs::ChassisMode::STEP_DOWN;
+ } else {
+ mode = rmcs_msgs::ChassisMode::SPIN;
+ spinning_forward_ = !spinning_forward_;
+ }
+ } else if (!last_keyboard_.c && keyboard.c) {
+ if (mode == rmcs_msgs::ChassisMode::SPIN) {
+ mode = rmcs_msgs::ChassisMode::STEP_DOWN;
+ } else {
+ mode = rmcs_msgs::ChassisMode::SPIN;
+ spinning_forward_ = !spinning_forward_;
+ }
+ } else if (!last_keyboard_.x && keyboard.x) {
+ mode = rmcs_msgs::ChassisMode::STEP_DOWN;
+ step_down_facing_ = StepDownFacing::FRONT;
+
+ } else if (!last_keyboard_.z && keyboard.z) {
+ if (mode == rmcs_msgs::ChassisMode::STEP_DOWN) {
+ mode = rmcs_msgs::ChassisMode::AUTO;
+ } else {
+ mode = rmcs_msgs::ChassisMode::STEP_DOWN;
+ step_down_facing_ = StepDownFacing::BACK;
+ }
+ }
+ *mode_ = mode;
+ }
+
+ update_velocity_control();
+ } while (false);
+
+ last_switch_right_ = switch_right;
+ last_switch_left_ = switch_left;
+ last_keyboard_ = keyboard;
+ }
+
+ void reset_all_controls() {
+ *mode_ = rmcs_msgs::ChassisMode::AUTO;
+ step_down_facing_ = StepDownFacing::BACK;
+
+ *chassis_control_velocity_ = {nan, nan, nan};
+ }
+
+ void update_velocity_control() {
+ auto translational_velocity = update_translational_velocity_control();
+ auto angular_velocity = update_angular_velocity_control(translational_velocity);
+
+ chassis_control_velocity_->vector << translational_velocity, angular_velocity;
+ }
+
+ Eigen::Vector2d update_translational_velocity_control() {
+ if (!std::isnan(*climbing_forward_velocity_)) {
+ return Eigen::Vector2d{*climbing_forward_velocity_, 0.0};
+ }
+
+ auto keyboard = *keyboard_;
+ Eigen::Vector2d keyboard_move{keyboard.w - keyboard.s, keyboard.a - keyboard.d};
+
+ Eigen::Vector2d translational_velocity =
+ Eigen::Rotation2Dd{*gimbal_yaw_angle_} * (*joystick_right_ + keyboard_move);
+
+ if (translational_velocity.norm() > 1.0)
+ translational_velocity.normalize();
+
+ translational_velocity *= translational_velocity_max;
+
+ return translational_velocity;
+ }
+
+ double update_angular_velocity_control(const Eigen::Vector2d& translational_velocity) {
+ double angular_velocity = 0.0;
+ double chassis_control_angle = nan;
+
+ if (!std::isnan(*climbing_forward_velocity_)) {
+ double err = calculate_unsigned_chassis_angle_error(chassis_control_angle);
+
+ constexpr double alignment = 2 * std::numbers::pi;
+ if (err > alignment / 2)
+ err -= alignment;
+
+ angular_velocity = following_velocity_controller_.update(err);
+
+ *chassis_angle_ = 2 * std::numbers::pi - *gimbal_yaw_angle_;
+ *chassis_control_angle_ = chassis_control_angle;
+ return angular_velocity;
+ }
+
+ switch (*mode_) {
+ case rmcs_msgs::ChassisMode::AUTO: {
+ angular_velocity =
+ update_following_angular_velocity(StepDownFacing::BACK, chassis_control_angle);
+
+ // Keep AUTO rear-following gentle at low translation speed and fully enabled at max.
+ const double measured_translational_speed =
+ chassis_velocity_feedback_ready_ ? chassis_velocity_->vector.head<2>().norm()
+ : translational_velocity.norm();
+ angular_velocity *=
+ std::clamp(measured_translational_speed / translational_velocity_max, 0.0, 0.3);
+ } break;
+ case rmcs_msgs::ChassisMode::SPIN: {
+ angular_velocity =
+ 0.6 * (spinning_forward_ ? angular_velocity_max : -angular_velocity_max);
+ } break;
+ case rmcs_msgs::ChassisMode::STEP_DOWN: {
+ angular_velocity =
+ update_following_angular_velocity(step_down_facing_, chassis_control_angle);
+ } break;
+ case rmcs_msgs::ChassisMode::LAUNCH_RAMP: {
+ double err = calculate_unsigned_chassis_angle_error(chassis_control_angle);
+
+ // err: [0, 2pi) -> signed
+ // In launch ramp mode, only one direction can be used for alignment.
+ // TODO: Dynamically determine the split angle based on chassis velocity.
+ constexpr double alignment = 2 * std::numbers::pi;
+ if (err > alignment / 2)
+ err -= alignment;
+
+ angular_velocity = following_velocity_controller_.update(err);
+ } break;
+ }
+ *chassis_angle_ = 2 * std::numbers::pi - *gimbal_yaw_angle_;
+ *chassis_control_angle_ = chassis_control_angle;
+
+ return angular_velocity;
+ }
+
+ double calculate_unsigned_chassis_angle_error(double& chassis_control_angle) {
+ chassis_control_angle = normalize_positive_angle(*gimbal_yaw_angle_error_);
+
+ // err = setpoint - measurement
+ // ^ ^
+ // |gimbal_yaw_angle_error |chassis_angle
+ // ^
+ // |(2pi - gimbal_yaw_angle)
+ double err = normalize_positive_angle(chassis_control_angle + *gimbal_yaw_angle_);
+
+ return err;
+ }
+
+private:
+ enum class StepDownFacing { FRONT, BACK };
+
+ double update_following_angular_velocity(
+ StepDownFacing target_facing, double& chassis_control_angle) {
+ double err = calculate_unsigned_chassis_angle_error(chassis_control_angle);
+ if (target_facing == StepDownFacing::BACK) {
+ chassis_control_angle =
+ normalize_positive_angle(chassis_control_angle + std::numbers::pi);
+ err = normalize_positive_angle(err + std::numbers::pi);
+ }
+
+ err = normalize_signed_angle(err);
+ return following_velocity_controller_.update(err);
+ }
+
+ static double normalize_positive_angle(double angle) {
+ constexpr double full_turn = 2 * std::numbers::pi;
+ while (angle >= full_turn)
+ angle -= full_turn;
+ while (angle < 0.0)
+ angle += full_turn;
+ return angle;
+ }
+
+ static double normalize_signed_angle(double angle) {
+ angle = normalize_positive_angle(angle);
+ if (angle > std::numbers::pi)
+ angle -= 2 * std::numbers::pi;
+ return angle;
+ }
+
+ static constexpr double nan = std::numeric_limits::quiet_NaN();
+ // Maximum control velocities
+ static constexpr double translational_velocity_max = 10.0;
+ static constexpr double angular_velocity_max = 16.0;
+
+ InputInterface joystick_right_;
+ InputInterface joystick_left_;
+ InputInterface switch_right_;
+ InputInterface switch_left_;
+ InputInterface mouse_velocity_;
+ InputInterface mouse_;
+ InputInterface keyboard_;
+ InputInterface rotary_knob_;
+
+ rmcs_msgs::Switch last_switch_right_ = rmcs_msgs::Switch::UNKNOWN;
+ rmcs_msgs::Switch last_switch_left_ = rmcs_msgs::Switch::UNKNOWN;
+ rmcs_msgs::Keyboard last_keyboard_ = rmcs_msgs::Keyboard::zero();
+
+ InputInterface gimbal_yaw_angle_, gimbal_yaw_angle_error_;
+ InputInterface chassis_velocity_;
+ InputInterface climbing_forward_velocity_;
+ OutputInterface chassis_angle_, chassis_control_angle_;
+
+ OutputInterface mode_;
+ bool spinning_forward_ = true;
+ bool chassis_velocity_feedback_ready_ = false;
+ StepDownFacing step_down_facing_ = StepDownFacing::BACK;
+ pid::PidCalculator following_velocity_controller_;
+
+ OutputInterface chassis_control_velocity_;
+};
+
+} // namespace rmcs_core::controller::chassis
+
+#include
+
+PLUGINLIB_EXPORT_CLASS(
+ rmcs_core::controller::chassis::ClimbableInfantryChassisController, rmcs_executor::Component)
\ No newline at end of file
diff --git a/rmcs_ws/src/rmcs_core/src/controller/chassis/steering_wheel_status.cpp b/rmcs_ws/src/rmcs_core/src/controller/chassis/steering_wheel_status.cpp
new file mode 100644
index 00000000..8450b386
--- /dev/null
+++ b/rmcs_ws/src/rmcs_core/src/controller/chassis/steering_wheel_status.cpp
@@ -0,0 +1,104 @@
+#include
+
+#include
+#include
+#include
+#include
+
+namespace rmcs_core::controller::chassis {
+
+class SteeringWheelStatus
+ : public rmcs_executor::Component
+ , public rclcpp::Node {
+public:
+ explicit SteeringWheelStatus()
+ : Node(
+ get_component_name(),
+ rclcpp::NodeOptions{}.automatically_declare_parameters_from_overrides(true))
+ , vehicle_radius_(get_parameter("vehicle_radius").as_double())
+ , wheel_radius_(get_parameter("wheel_radius").as_double()) {
+ register_input("/chassis/left_front_steering/angle", left_front_steering_angle_);
+ register_input("/chassis/left_back_steering/angle", left_back_steering_angle_);
+ register_input("/chassis/right_back_steering/angle", right_back_steering_angle_);
+ register_input("/chassis/right_front_steering/angle", right_front_steering_angle_);
+
+ register_input("/chassis/left_front_wheel/velocity", left_front_wheel_velocity_);
+ register_input("/chassis/left_back_wheel/velocity", left_back_wheel_velocity_);
+ register_input("/chassis/right_back_wheel/velocity", right_back_wheel_velocity_);
+ register_input("/chassis/right_front_wheel/velocity", right_front_wheel_velocity_);
+
+ register_output("/chassis/velocity", chassis_velocity_, 0.0, 0.0, 0.0);
+ }
+
+ void update() override {
+ auto steering_status = calculate_steering_status();
+ auto wheel_velocities = calculate_wheel_velocities();
+ chassis_velocity_->vector = calculate_chassis_velocity(steering_status, wheel_velocities);
+ }
+
+private:
+ struct SteeringStatus {
+ Eigen::Vector4d angle, cos_angle, sin_angle;
+ };
+
+ SteeringStatus calculate_steering_status() const {
+ SteeringStatus steering_status;
+
+ steering_status.angle = {
+ *left_front_steering_angle_, //
+ *left_back_steering_angle_, //
+ *right_back_steering_angle_, //
+ *right_front_steering_angle_ //
+ };
+ steering_status.angle.array() -= std::numbers::pi / 4;
+ steering_status.cos_angle = steering_status.angle.array().cos();
+ steering_status.sin_angle = steering_status.angle.array().sin();
+
+ return steering_status;
+ }
+
+ Eigen::Vector4d calculate_wheel_velocities() const {
+ return {
+ *left_front_wheel_velocity_, //
+ *left_back_wheel_velocity_, //
+ *right_back_wheel_velocity_, //
+ *right_front_wheel_velocity_ //
+ };
+ }
+
+ Eigen::Vector3d calculate_chassis_velocity(
+ const SteeringStatus& steering_status, const Eigen::Vector4d& wheel_velocities) const {
+ Eigen::Vector3d velocity;
+ double one_quarter_r = wheel_radius_ / 4.0;
+ velocity.x() = one_quarter_r * wheel_velocities.dot(steering_status.cos_angle);
+ velocity.y() = one_quarter_r * wheel_velocities.dot(steering_status.sin_angle);
+ velocity.z() = -one_quarter_r / vehicle_radius_
+ * (-wheel_velocities[0] * steering_status.sin_angle[0]
+ + wheel_velocities[1] * steering_status.cos_angle[1]
+ + wheel_velocities[2] * steering_status.sin_angle[2]
+ - wheel_velocities[3] * steering_status.cos_angle[3]);
+ return velocity;
+ }
+
+ const double vehicle_radius_;
+ const double wheel_radius_;
+
+ InputInterface left_front_steering_angle_;
+ InputInterface left_back_steering_angle_;
+ InputInterface right_back_steering_angle_;
+ InputInterface right_front_steering_angle_;
+
+ InputInterface left_front_wheel_velocity_;
+ InputInterface left_back_wheel_velocity_;
+ InputInterface right_back_wheel_velocity_;
+ InputInterface right_front_wheel_velocity_;
+
+ OutputInterface chassis_velocity_;
+};
+
+} // namespace rmcs_core::controller::chassis
+
+#include
+
+PLUGINLIB_EXPORT_CLASS(
+ rmcs_core::controller::chassis::SteeringWheelStatus, rmcs_executor::Component)
diff --git a/rmcs_ws/src/rmcs_core/src/controller/gimbal/dual_yaw_controller.cpp b/rmcs_ws/src/rmcs_core/src/controller/gimbal/dual_yaw_controller.cpp
index 80d7a72e..90ae2576 100644
--- a/rmcs_ws/src/rmcs_core/src/controller/gimbal/dual_yaw_controller.cpp
+++ b/rmcs_ws/src/rmcs_core/src/controller/gimbal/dual_yaw_controller.cpp
@@ -44,9 +44,9 @@ class DualYawController
register_output("/gimbal/top_yaw/control_torque", top_yaw_control_torque_, 0.0);
register_output("/gimbal/bottom_yaw/control_torque", bottom_yaw_control_torque_, 0.0);
-
- register_output("/gimbal/top_yaw/control_angle", top_yaw_control_torque_, 0.0);
- register_output("/gimbal/bottom_yaw/control_angle_shift", bottom_yaw_control_torque_, 0.0);
+ register_output("/gimbal/top_yaw/control_angle", top_yaw_control_angle_, nan_);
+ register_output(
+ "/gimbal/bottom_yaw/control_angle_shift", bottom_yaw_control_angle_shift_, nan_);
status_component_ =
create_partner_component(get_component_name() + "_status");
diff --git a/rmcs_ws/src/rmcs_core/src/controller/gimbal/hero_gimbal_controller.cpp b/rmcs_ws/src/rmcs_core/src/controller/gimbal/hero_gimbal_controller.cpp
index f20bd98f..40aba208 100644
--- a/rmcs_ws/src/rmcs_core/src/controller/gimbal/hero_gimbal_controller.cpp
+++ b/rmcs_ws/src/rmcs_core/src/controller/gimbal/hero_gimbal_controller.cpp
@@ -67,6 +67,7 @@ class HeroGimbalController
}
*gimbal_mode_ =
*switch_right_ == Switch::UP ? GimbalMode::ENCODER : gimbal_mode_keyboard_;
+ // *gimbal_mode_ = gimbal_mode_keyboard_;
if (*gimbal_mode_ == GimbalMode::IMU) {
auto angle_error = update_imu_control();
@@ -121,7 +122,7 @@ class HeroGimbalController
double yaw_shift =
joystick_sensitivity * joystick_left_->y() + mouse_sensitivity * mouse_velocity_->y();
double pitch_shift =
- -joystick_sensitivity * joystick_left_->x() - mouse_sensitivity * mouse_velocity_->x();
+ -joystick_sensitivity * joystick_left_->x() + mouse_sensitivity * mouse_velocity_->x();
return imu_gimbal_solver.update(
TwoAxisGimbalSolver::SetControlShift{yaw_shift, pitch_shift});
@@ -138,7 +139,7 @@ class HeroGimbalController
double yaw_shift = joystick_sensitivity * joystick_left_->y()
+ mouse_yaw_sensitivity * mouse_velocity_->y();
double pitch_shift = -joystick_sensitivity * joystick_left_->x()
- - mouse_pitch_sensitivity * mouse_velocity_->x();
+ + mouse_pitch_sensitivity * mouse_velocity_->x();
return encoder_gimbal_solver.update(
PreciseTwoAxisGimbalSolver::SetControlShift{yaw_shift, pitch_shift});
diff --git a/rmcs_ws/src/rmcs_core/src/controller/gimbal/simple_gimbal_controller.cpp b/rmcs_ws/src/rmcs_core/src/controller/gimbal/simple_gimbal_controller.cpp
index d27704d5..dd37c17a 100644
--- a/rmcs_ws/src/rmcs_core/src/controller/gimbal/simple_gimbal_controller.cpp
+++ b/rmcs_ws/src/rmcs_core/src/controller/gimbal/simple_gimbal_controller.cpp
@@ -67,7 +67,7 @@ class SimpleGimbalController
double yaw_shift =
joystick_sensitivity * joystick_left_->y() + mouse_sensitivity * mouse_velocity_->y();
double pitch_shift =
- -joystick_sensitivity * joystick_left_->x() - mouse_sensitivity * mouse_velocity_->x();
+ -joystick_sensitivity * joystick_left_->x() + mouse_sensitivity * mouse_velocity_->x();
return two_axis_gimbal_solver.update(
TwoAxisGimbalSolver::SetControlShift(yaw_shift, pitch_shift));
diff --git a/rmcs_ws/src/rmcs_core/src/controller/pid/friction_wheel_pid_recorder.cpp b/rmcs_ws/src/rmcs_core/src/controller/pid/friction_wheel_pid_recorder.cpp
new file mode 100644
index 00000000..366f66b6
--- /dev/null
+++ b/rmcs_ws/src/rmcs_core/src/controller/pid/friction_wheel_pid_recorder.cpp
@@ -0,0 +1,166 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+namespace rmcs_core::controller::pid {
+
+class FrictionWheelPidRecorder
+ : public rmcs_executor::Component
+ , public rclcpp::Node {
+public:
+ FrictionWheelPidRecorder()
+ : Node(
+ get_component_name(),
+ rclcpp::NodeOptions{}.automatically_declare_parameters_from_overrides(true)) {
+ const auto wheels =
+ declare_parameter>("wheels", std::vector{});
+ if (wheels.empty())
+ throw std::runtime_error("Parameter \"wheels\" must not be empty");
+
+ min_setpoint_abs_ = declare_parameter("min_setpoint_abs", 10.0);
+
+ const auto flush_every_n_samples = declare_parameter("flush_every_n_samples", 100);
+ if (flush_every_n_samples <= 0)
+ throw std::runtime_error("Parameter \"flush_every_n_samples\" must be positive");
+ flush_every_n_samples_ = static_cast(flush_every_n_samples);
+
+ const auto output_dir = std::filesystem::path{
+ declare_parameter("output_dir", "/tmp/friction_pid_logs")};
+ const auto output_name = declare_parameter("output_name", "");
+
+ wheel_count_ = wheels.size();
+ wheel_ids_ = std::make_unique(wheel_count_);
+ setpoints_ = std::make_unique[]>(wheel_count_);
+ measurements_ = std::make_unique[]>(wheel_count_);
+ control_torques_ = std::make_unique[]>(wheel_count_);
+ run_ids_ = std::make_unique(wheel_count_);
+ sample_indices_ = std::make_unique(wheel_count_);
+ previously_enabled_ = std::make_unique(wheel_count_);
+
+ for (size_t i = 0; i < wheel_count_; ++i) {
+ wheel_ids_[i] = wheel_id_from_topic(wheels[i]);
+ register_input(wheels[i] + "/control_velocity", setpoints_[i]);
+ register_input(wheels[i] + "/velocity", measurements_[i]);
+ register_input(wheels[i] + "/control_torque", control_torques_[i]);
+
+ run_ids_[i] = 0;
+ sample_indices_[i] = 0;
+ previously_enabled_[i] = false;
+ }
+
+ std::filesystem::create_directories(output_dir);
+ log_file_path_ = output_dir / resolve_output_name(output_name);
+ log_stream_.open(log_file_path_);
+ if (!log_stream_.is_open())
+ throw std::runtime_error(
+ "Failed to open recorder output file: " + log_file_path_.string());
+
+ log_stream_.setf(std::ios::fixed);
+ log_stream_.precision(9);
+ log_stream_
+ << "timestamp_us,wheel_id,run_id,sample_idx,setpoint_velocity,measured_velocity,"
+ "control_torque,enabled\n";
+
+ RCLCPP_INFO(get_logger(), "FrictionWheelPidRecorder writing to %s", log_file_path_.c_str());
+ }
+
+ ~FrictionWheelPidRecorder() override {
+ if (log_stream_.is_open()) {
+ log_stream_.flush();
+ log_stream_.close();
+ }
+ }
+
+ void update() override {
+ const auto timestamp_us = now_timestamp_us();
+
+ for (size_t i = 0; i < wheel_count_; ++i) {
+ const bool enabled = should_record(i);
+ if (enabled && !previously_enabled_[i]) {
+ ++run_ids_[i];
+ sample_indices_[i] = 0;
+ }
+
+ if (enabled) {
+ log_stream_ << timestamp_us << ',' << wheel_ids_[i] << ',' << run_ids_[i] << ','
+ << sample_indices_[i]++ << ',' << *setpoints_[i] << ','
+ << *measurements_[i] << ',' << *control_torques_[i] << ",1\n";
+ if (++unflushed_samples_ >= flush_every_n_samples_) {
+ log_stream_.flush();
+ unflushed_samples_ = 0;
+ }
+ }
+
+ previously_enabled_[i] = enabled;
+ }
+ }
+
+private:
+ static std::string wheel_id_from_topic(std::string_view topic) {
+ auto pos = topic.find_last_of('/');
+ if (pos == std::string_view::npos || pos + 1 >= topic.size())
+ return std::string(topic);
+ return std::string(topic.substr(pos + 1));
+ }
+
+ static uint64_t now_timestamp_us() {
+ const auto now = std::chrono::steady_clock::now().time_since_epoch();
+ return static_cast(
+ std::chrono::duration_cast(now).count());
+ }
+
+ static std::string resolve_output_name(const std::string& configured_name) {
+ if (!configured_name.empty())
+ return configured_name;
+
+ const auto now = std::chrono::system_clock::now().time_since_epoch();
+ const auto ms = std::chrono::duration_cast(now).count();
+ return "friction_wheel_pid_" + std::to_string(ms) + ".csv";
+ }
+
+ bool should_record(size_t index) const {
+ if (!setpoints_[index].ready() || !measurements_[index].ready()
+ || !control_torques_[index].ready())
+ return false;
+
+ const auto setpoint = *setpoints_[index];
+ const auto measurement = *measurements_[index];
+ const auto control_torque = *control_torques_[index];
+
+ return std::isfinite(setpoint) && std::isfinite(measurement)
+ && std::isfinite(control_torque) && std::abs(setpoint) >= min_setpoint_abs_;
+ }
+
+ size_t wheel_count_ = 0;
+ double min_setpoint_abs_ = 10.0;
+ size_t flush_every_n_samples_ = 100;
+ size_t unflushed_samples_ = 0;
+
+ std::filesystem::path log_file_path_;
+ std::ofstream log_stream_;
+
+ std::unique_ptr wheel_ids_;
+ std::unique_ptr[]> setpoints_;
+ std::unique_ptr[]> measurements_;
+ std::unique_ptr[]> control_torques_;
+ std::unique_ptr run_ids_;
+ std::unique_ptr sample_indices_;
+ std::unique_ptr previously_enabled_;
+};
+
+} // namespace rmcs_core::controller::pid
+
+#include
+
+PLUGINLIB_EXPORT_CLASS(
+ rmcs_core::controller::pid::FrictionWheelPidRecorder, rmcs_executor::Component)
diff --git a/rmcs_ws/src/rmcs_core/src/controller/shooting/bullet_feeder_controller_42mm.cpp b/rmcs_ws/src/rmcs_core/src/controller/shooting/bullet_feeder_controller_42mm.cpp
index bf3076e3..95d75cb9 100644
--- a/rmcs_ws/src/rmcs_core/src/controller/shooting/bullet_feeder_controller_42mm.cpp
+++ b/rmcs_ws/src/rmcs_core/src/controller/shooting/bullet_feeder_controller_42mm.cpp
@@ -31,14 +31,15 @@ class BulletFeederController42mm
register_input(
"/gimbal/control_bullet_allowance/limited_by_heat",
control_bullet_allowance_limited_by_heat_);
+ register_input("/gimbal/bullet_fired", bullet_fired_);
- bullet_feeder_velocity_pid_.kp = 50.0;
- bullet_feeder_velocity_pid_.ki = 10.0;
- bullet_feeder_velocity_pid_.kd = 0.0;
+ bullet_feeder_velocity_pid_.kp = 50.0;
+ bullet_feeder_velocity_pid_.ki = 10.0;
+ bullet_feeder_velocity_pid_.kd = 0.0;
bullet_feeder_velocity_pid_.integral_max = 60.0;
bullet_feeder_velocity_pid_.integral_min = 0.0;
- bullet_feeder_angle_pid_.kp = 50.0;
+ bullet_feeder_angle_pid_.kp = 60.0;
bullet_feeder_angle_pid_.ki = 0.0;
bullet_feeder_angle_pid_.kd = 2.0;
@@ -51,9 +52,9 @@ class BulletFeederController42mm
void update() override {
const auto switch_right = *switch_right_;
- const auto switch_left = *switch_left_;
- const auto mouse = *mouse_;
- const auto keyboard = *keyboard_;
+ const auto switch_left = *switch_left_;
+ const auto mouse = *mouse_;
+ const auto keyboard = *keyboard_;
using namespace rmcs_msgs;
if ((switch_left == Switch::UNKNOWN || switch_right == Switch::UNKNOWN)
@@ -61,7 +62,6 @@ class BulletFeederController42mm
reset_all_controls();
return;
}
-
overdrive_mode_ = keyboard.f;
if (keyboard.ctrl && !last_keyboard_.r && keyboard.r)
low_latency_mode_ = !low_latency_mode_;
@@ -84,8 +84,8 @@ class BulletFeederController42mm
} else {
if (!*friction_ready_ || std::isnan(bullet_feeder_control_angle_)) {
bullet_feeder_control_angle_ = *bullet_feeder_angle_;
- shoot_stage_ = ShootStage::PRELOADED;
- bullet_fed_count_ = static_cast(
+ shoot_stage_ = ShootStage::PRELOADED;
+ bullet_fed_count_ = static_cast(
(*bullet_feeder_angle_ - bullet_feeder_compressed_zero_point_ - 0.1)
/ bullet_feeder_angle_per_bullet_);
}
@@ -135,24 +135,24 @@ class BulletFeederController42mm
}
last_switch_right_ = switch_right;
- last_switch_left_ = switch_left;
- last_mouse_ = mouse;
- last_keyboard_ = keyboard;
+ last_switch_left_ = switch_left;
+ last_mouse_ = mouse;
+ last_keyboard_ = keyboard;
}
private:
void reset_all_controls() {
last_switch_right_ = rmcs_msgs::Switch::UNKNOWN;
- last_switch_left_ = rmcs_msgs::Switch::UNKNOWN;
- last_mouse_ = rmcs_msgs::Mouse::zero();
- last_keyboard_ = rmcs_msgs::Keyboard::zero();
+ last_switch_left_ = rmcs_msgs::Switch::UNKNOWN;
+ last_mouse_ = rmcs_msgs::Mouse::zero();
+ last_keyboard_ = rmcs_msgs::Keyboard::zero();
overdrive_mode_ = low_latency_mode_ = false;
- shoot_stage_ = ShootStage::PRELOADED;
+ shoot_stage_ = ShootStage::PRELOADED;
bullet_fed_count_ = std::numeric_limits::min();
- bullet_feeder_control_angle_ = nan_;
+ bullet_feeder_control_angle_ = nan_;
bullet_feeder_angle_pid_.output_max = inf_;
bullet_feeder_velocity_pid_.reset();
@@ -160,13 +160,13 @@ class BulletFeederController42mm
*bullet_feeder_control_torque_ = nan_;
bullet_feeder_faulty_count_ = 0;
- bullet_feeder_cool_down_ = 0;
+ bullet_feeder_cool_down_ = 0;
}
void set_preloading() {
RCLCPP_INFO(get_logger(), "PRELOADING");
bullet_fed_count_++;
- shoot_stage_ = ShootStage::PRELOADING;
+ shoot_stage_ = ShootStage::PRELOADING;
bullet_feeder_control_angle_ = bullet_feeder_compressed_zero_point_
+ (bullet_fed_count_ + 0.5) * bullet_feeder_angle_per_bullet_;
bullet_feeder_angle_pid_.output_max = 1.0;
@@ -179,7 +179,7 @@ class BulletFeederController42mm
void set_compressing() {
RCLCPP_INFO(get_logger(), "COMPRESSING");
- shoot_stage_ = ShootStage::COMPRESSING;
+ shoot_stage_ = ShootStage::COMPRESSING;
bullet_feeder_control_angle_ = bullet_feeder_compressed_zero_point_
+ (bullet_fed_count_ + 1) * bullet_feeder_angle_per_bullet_;
bullet_feeder_angle_pid_.output_max = 0.8;
@@ -192,7 +192,7 @@ class BulletFeederController42mm
void set_shooting() {
RCLCPP_INFO(get_logger(), "SHOOTING");
- shoot_stage_ = ShootStage::SHOOTING;
+ shoot_stage_ = ShootStage::SHOOTING;
bullet_feeder_control_angle_ = bullet_feeder_compressed_zero_point_
+ (bullet_fed_count_ + 1.2) * bullet_feeder_angle_per_bullet_;
bullet_feeder_angle_pid_.output_max = 1.0;
@@ -215,7 +215,7 @@ class BulletFeederController42mm
void enter_jam_protection() {
bullet_feeder_control_angle_ = nan_;
- bullet_feeder_cool_down_ = 1000;
+ bullet_feeder_cool_down_ = 1000;
bullet_feeder_angle_pid_.reset();
bullet_feeder_velocity_pid_.reset();
RCLCPP_INFO(get_logger(), "Jammed!");
@@ -225,9 +225,10 @@ class BulletFeederController42mm
static constexpr double inf_ = std::numeric_limits::infinity();
static constexpr double bullet_feeder_compressed_zero_point_ = 0.58;
- static constexpr double bullet_feeder_angle_per_bullet_ = 2 * std::numbers::pi / 6;
+ static constexpr double bullet_feeder_angle_per_bullet_ = 2 * std::numbers::pi / 6;
InputInterface friction_ready_;
+ InputInterface bullet_fired_;
InputInterface switch_right_;
InputInterface switch_left_;
@@ -235,9 +236,9 @@ class BulletFeederController42mm
InputInterface keyboard_;
rmcs_msgs::Switch last_switch_right_ = rmcs_msgs::Switch::UNKNOWN;
- rmcs_msgs::Switch last_switch_left_ = rmcs_msgs::Switch::UNKNOWN;
- rmcs_msgs::Mouse last_mouse_ = rmcs_msgs::Mouse::zero();
- rmcs_msgs::Keyboard last_keyboard_ = rmcs_msgs::Keyboard::zero();
+ rmcs_msgs::Switch last_switch_left_ = rmcs_msgs::Switch::UNKNOWN;
+ rmcs_msgs::Mouse last_mouse_ = rmcs_msgs::Mouse::zero();
+ rmcs_msgs::Keyboard last_keyboard_ = rmcs_msgs::Keyboard::zero();
bool overdrive_mode_ = false, low_latency_mode_ = false;
@@ -247,8 +248,8 @@ class BulletFeederController42mm
InputInterface control_bullet_allowance_limited_by_heat_;
enum class ShootStage { PRELOADING, PRELOADED, COMPRESSING, COMPRESSED, SHOOTING };
- ShootStage shoot_stage_ = ShootStage::PRELOADED;
- int bullet_fed_count_ = std::numeric_limits::min();
+ ShootStage shoot_stage_ = ShootStage::PRELOADED;
+ int bullet_fed_count_ = std::numeric_limits::min();
double bullet_feeder_control_angle_ = nan_;
pid::PidCalculator bullet_feeder_velocity_pid_;
@@ -256,7 +257,7 @@ class BulletFeederController42mm
OutputInterface bullet_feeder_control_torque_;
int bullet_feeder_faulty_count_ = 0;
- int bullet_feeder_cool_down_ = 0;
+ int bullet_feeder_cool_down_ = 0;
OutputInterface shoot_mode_;
};
diff --git a/rmcs_ws/src/rmcs_core/src/controller/shooting/friction_wheel_controller.cpp b/rmcs_ws/src/rmcs_core/src/controller/shooting/friction_wheel_controller.cpp
index a83dc5f4..4e22c13e 100644
--- a/rmcs_ws/src/rmcs_core/src/controller/shooting/friction_wheel_controller.cpp
+++ b/rmcs_ws/src/rmcs_core/src/controller/shooting/friction_wheel_controller.cpp
@@ -89,6 +89,10 @@ class FrictionWheelController
last_switch_right_ = switch_right;
last_switch_left_ = switch_left;
last_keyboard_ = keyboard;
+ // RCLCPP_INFO(logger_, "Bullet Fired: {%d}", *bullet_fired_);
+ }
+ if (!friction_enabled_) {
+ reset_all_controls();
}
}
@@ -150,8 +154,8 @@ class FrictionWheelController
bool detect_friction_faulty() {
for (size_t i = 0; i < friction_count_; i++) {
- if (*friction_velocities_[i] < *friction_control_velocities_[i] * 0.5)
- return true;
+ if (abs(*friction_velocities_[i]) < abs(*friction_control_velocities_[i] * 0.5))
+ return false;
}
return false;
}
diff --git a/rmcs_ws/src/rmcs_core/src/controller/shooting/heat_controller.cpp b/rmcs_ws/src/rmcs_core/src/controller/shooting/heat_controller.cpp
index 3de0960a..6cbbda2b 100644
--- a/rmcs_ws/src/rmcs_core/src/controller/shooting/heat_controller.cpp
+++ b/rmcs_ws/src/rmcs_core/src/controller/shooting/heat_controller.cpp
@@ -1,5 +1,7 @@
#include
+#include
+#include
#include
#include
@@ -26,10 +28,12 @@ class HeatController
}
void update() override {
+
shooter_heat_ = std::max(0, shooter_heat_ - *shooter_cooling_);
- if (*bullet_fired_)
- shooter_heat_ += heat_per_shot + 10;
+ if (*bullet_fired_) {
+ shooter_heat_ += heat_per_shot;
+ }
*control_bullet_allowance_ = std::max(
0, (*shooter_heat_limit_ - shooter_heat_ - reserved_heat) / heat_per_shot);
diff --git a/rmcs_ws/src/rmcs_core/src/controller/shooting/shooting_recorder.cpp b/rmcs_ws/src/rmcs_core/src/controller/shooting/shooting_recorder.cpp
index 5500b15c..6d8fbb4d 100644
--- a/rmcs_ws/src/rmcs_core/src/controller/shooting/shooting_recorder.cpp
+++ b/rmcs_ws/src/rmcs_core/src/controller/shooting/shooting_recorder.cpp
@@ -20,7 +20,7 @@ class ShootingRecorder
register_input("/referee/shooter/initial_speed", initial_speed_);
register_input("/referee/shooter/shoot_timestamp", shoot_timestamp_);
- register_input("/friction_wheels/temperature", fractional_temperature_);
+ // register_input("/friction_wheels/temperature", fractional_temperature_);
if (friction_wheel_count_ == 2) {
const auto topic = std::array{
@@ -42,7 +42,7 @@ class ShootingRecorder
using namespace std::chrono;
auto now = high_resolution_clock::now();
- auto ms = duration_cast(now.time_since_epoch()).count();
+ auto ms = duration_cast(now.time_since_epoch()).count();
auto file = fmt::format("/robot_shoot/{}.log", ms);
log_stream_.open(file);
@@ -65,14 +65,14 @@ class ShootingRecorder
break;
}
- auto log_text = std::string{};
+ auto log_text = std::string{};
auto timestamp = timestamp_to_string(*shoot_timestamp_);
if (friction_wheel_count_ == 2) {
log_text = fmt::format(
- "{},{},{},{},{}", timestamp, *initial_speed_, //
- *friction_wheels_velocity_[0], *friction_wheels_velocity_[1],
- *fractional_temperature_);
+ "{},{},{},{}", timestamp, *initial_speed_, //
+ *friction_wheels_velocity_[0], *friction_wheels_velocity_[1]);
+ // *fractional_temperature_);
} else if (friction_wheel_count_ == 4) {
log_text = fmt::format(
"{},{},{},{},{},{},{}", timestamp, *initial_speed_, //
@@ -108,14 +108,14 @@ class ShootingRecorder
private:
static std::string timestamp_to_string(double timestamp) {
- auto time = static_cast(timestamp);
+ auto time = static_cast(timestamp);
auto local_time = std::localtime(&time);
char buffer[100];
std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", local_time);
double fractional_seconds = timestamp - std::floor(timestamp);
- int milliseconds = static_cast(fractional_seconds * 1000);
+ int milliseconds = static_cast(fractional_seconds * 1000);
char result[150];
std::snprintf(result, sizeof(result), "%s.%03d", buffer, milliseconds);
diff --git a/rmcs_ws/src/rmcs_core/src/hardware/climbable-infantry.cpp b/rmcs_ws/src/rmcs_core/src/hardware/climbable-infantry.cpp
new file mode 100644
index 00000000..35948421
--- /dev/null
+++ b/rmcs_ws/src/rmcs_core/src/hardware/climbable-infantry.cpp
@@ -0,0 +1,626 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "hardware/device/bmi088.hpp"
+#include "hardware/device/can_packet.hpp"
+#include "hardware/device/dji_motor.hpp"
+#include "hardware/device/dr16.hpp"
+#include "hardware/device/lk_motor.hpp"
+#include "hardware/device/supercap.hpp"
+
+namespace rmcs_core::hardware {
+
+class ClimbableInfantry
+ : public rmcs_executor::Component
+ , public rclcpp::Node {
+public:
+ ClimbableInfantry()
+ : Node(
+ get_component_name(),
+ rclcpp::NodeOptions().automatically_declare_parameters_from_overrides(true))
+ , command_component_(
+ create_partner_component(
+ get_component_name() + "_command", *this)) {
+
+ register_output("/tf", tf_);
+ gimbal_calibrate_subscription_ = create_subscription(
+ "/gimbal/calibrate", rclcpp::QoS{0}, [this](std_msgs::msg::Int32::UniquePtr&& msg) {
+ gimbal_calibrate_subscription_callback(std::move(msg));
+ });
+
+ top_board_ = std::make_unique(
+ *this, *command_component_, get_parameter("board_serial_top_board").as_string());
+ bottom_board_ = std::make_unique(
+ *this, *command_component_, get_parameter("board_serial_bottom_board").as_string());
+
+ tf_->set_transform(
+ Eigen::Translation3d{0.06603, 0.0, 0.082});
+ }
+
+ ClimbableInfantry(const ClimbableInfantry&) = delete;
+ ClimbableInfantry& operator=(const ClimbableInfantry&) = delete;
+ ClimbableInfantry(ClimbableInfantry&&) = delete;
+ ClimbableInfantry& operator=(ClimbableInfantry&&) = delete;
+
+ ~ClimbableInfantry() override = default;
+
+ void update() override {
+ top_board_->update();
+ bottom_board_->update();
+ }
+
+ void command_update() {
+ top_board_->command_update();
+ bottom_board_->command_update();
+
+ }
+
+private:
+ void gimbal_calibrate_subscription_callback(std_msgs::msg::Int32::UniquePtr) {
+ RCLCPP_INFO(
+ get_logger(), "[gimbal calibration] New yaw offset: %ld",
+ bottom_board_->gimbal_yaw_motor_.calibrate_zero_point());
+ RCLCPP_INFO(
+ get_logger(), "[gimbal calibration] New pitch offset: %ld",
+ top_board_->gimbal_pitch_motor_.calibrate_zero_point());
+ RCLCPP_INFO(
+ get_logger(), "[gimbal calibration] New bullet feeder offset: %ld",
+ static_cast(bottom_board_->gimbal_bullet_feeder_.calibrate_zero_point()));
+ RCLCPP_INFO(
+ get_logger(), "[chassis calibration] left front steering offset: %d",
+ bottom_board_->chassis_front_steering_motors_[0].calibrate_zero_point());
+ RCLCPP_INFO(
+ get_logger(), "[chassis calibration] right front steering offset: %d",
+ bottom_board_->chassis_front_steering_motors_[1].calibrate_zero_point());
+ RCLCPP_INFO(
+ get_logger(), "[chassis calibration] left back steering offset: %d",
+ bottom_board_->chassis_back_steering_motors_[0].calibrate_zero_point());
+ RCLCPP_INFO(
+ get_logger(), "[chassis calibration] right back steering offset: %d",
+ bottom_board_->chassis_back_steering_motors_[1].calibrate_zero_point());
+ }
+
+ class ClimbableInfantryCommand : public rmcs_executor::Component {
+ public:
+ explicit ClimbableInfantryCommand(ClimbableInfantry& infantry_)
+ : infantry_(infantry_) {}
+
+ void update() override { infantry_.command_update(); }
+
+ ClimbableInfantry& infantry_;
+ };
+ std::shared_ptr command_component_;
+
+ class TopBoard final : private librmcs::agent::CBoard {
+ public:
+ friend class ClimbableInfantry;
+ explicit TopBoard(
+ ClimbableInfantry& climbable_infantry,
+ ClimbableInfantryCommand& climbable_infantry_command,
+ std::string_view board_serial = {})
+ : librmcs::agent::CBoard(board_serial)
+ , logger_(climbable_infantry.get_logger())
+ , tf_(climbable_infantry.tf_)
+ , imu_(1000, 0.2, 0.0)
+ , gimbal_pitch_motor_(climbable_infantry, climbable_infantry_command, "/gimbal/pitch")
+ , gimbal_left_friction_(
+ climbable_infantry, climbable_infantry_command, "/gimbal/left_friction")
+ , gimbal_right_friction_(
+ climbable_infantry, climbable_infantry_command, "/gimbal/right_friction") {
+
+ gimbal_pitch_motor_.configure(
+ device::LkMotor::Config{device::LkMotor::Type::kMG4010Ei10}
+ .set_encoder_zero_point(
+ static_cast(
+ climbable_infantry.get_parameter("pitch_motor_zero_point").as_int()))
+ .enable_multi_turn_angle());
+ gimbal_left_friction_.configure(
+ device::DjiMotor::Config{device::DjiMotor::Type::kM3508}.set_reduction_ratio(1.));
+ gimbal_right_friction_.configure(
+ device::DjiMotor::Config{device::DjiMotor::Type::kM3508}
+ .set_reduction_ratio(1.)
+ .set_reversed());
+ climbable_infantry.register_output(
+ "/gimbal/yaw/velocity_imu", gimbal_yaw_velocity_imu_);
+ climbable_infantry.register_output(
+ "/gimbal/pitch/velocity_imu", gimbal_pitch_velocity_imu_);
+ imu_.set_coordinate_mapping(
+ [](double x, double y, double z) { return std::make_tuple(x, y, z); });
+ }
+
+ TopBoard(const TopBoard&) = delete;
+ TopBoard& operator=(const TopBoard&) = delete;
+ TopBoard(TopBoard&&) = delete;
+ TopBoard& operator=(TopBoard&&) = delete;
+
+ ~TopBoard() final = default;
+
+ double yaw_imu_velocity() const { return imu_.gz(); }
+
+ void update() {
+ imu_.update_status();
+ const Eigen::Quaterniond gimbal_imu_pose{imu_.q0(), imu_.q1(), imu_.q2(), imu_.q3()};
+
+ tf_->set_transform(
+ gimbal_imu_pose.conjugate());
+
+ *gimbal_yaw_velocity_imu_ = imu_.gz();
+ *gimbal_pitch_velocity_imu_ = imu_.gy();
+
+ gimbal_pitch_motor_.update_status();
+ gimbal_left_friction_.update_status();
+ gimbal_right_friction_.update_status();
+
+ tf_->set_state(
+ gimbal_pitch_motor_.angle());
+ }
+
+ void command_update() {
+ auto builder = start_transmit();
+
+ builder.can1_transmit({
+ .can_id = 0x200,
+ .can_data =
+ device::CanPacket8{
+ gimbal_right_friction_.generate_command(),
+ gimbal_left_friction_.generate_command(),
+ device::CanPacket8::PaddingQuarter{},
+ device::CanPacket8::PaddingQuarter{},
+ }
+ .as_bytes(),
+ });
+
+ builder.can2_transmit({
+ .can_id = 0x143,
+ .can_data = gimbal_pitch_motor_.generate_command().as_bytes(),
+ });
+ }
+
+ private:
+ void can1_receive_callback(const librmcs::data::CanDataView& data) override {
+ if (data.is_extended_can_id || data.is_remote_transmission) [[unlikely]]
+ return;
+ auto can_id = data.can_id;
+ if (can_id == 0x201) {
+ gimbal_right_friction_.store_status(data.can_data);
+ } else if (can_id == 0x202) {
+ gimbal_left_friction_.store_status(data.can_data);
+ }
+ }
+
+ void can2_receive_callback(const librmcs::data::CanDataView& data) override {
+ if (data.is_extended_can_id || data.is_remote_transmission) [[unlikely]]
+ return;
+ auto can_id = data.can_id;
+ if (can_id == 0x143) {
+ gimbal_pitch_motor_.store_status(data.can_data);
+ }
+ }
+
+ void accelerometer_receive_callback(
+ const librmcs::data::AccelerometerDataView& data) override {
+ imu_.store_accelerometer_status(data.x, data.y, data.z);
+ }
+
+ void gyroscope_receive_callback(const librmcs::data::GyroscopeDataView& data) override {
+ imu_.store_gyroscope_status(data.x, data.y, data.z);
+ }
+
+ rclcpp::Logger logger_;
+
+ OutputInterface& tf_;
+
+ device::Bmi088 imu_;
+ device::LkMotor gimbal_pitch_motor_;
+ device::DjiMotor gimbal_left_friction_;
+ device::DjiMotor gimbal_right_friction_;
+
+ OutputInterface gimbal_yaw_velocity_imu_;
+ OutputInterface gimbal_pitch_velocity_imu_;
+ };
+
+ class BottomBoard final : private librmcs::agent::RmcsBoardLite {
+ public:
+ friend class ClimbableInfantry;
+ explicit BottomBoard(
+ ClimbableInfantry& climbable_infantry,
+ ClimbableInfantryCommand& climbable_infantry_command,
+ std::string_view board_serial = {})
+ : librmcs::agent::RmcsBoardLite(board_serial)
+ , logger_(climbable_infantry.get_logger())
+ , tf_(climbable_infantry.tf_)
+ , imu_(1000, 0.2, 0.0)
+ , dr16_(climbable_infantry)
+ , gimbal_yaw_motor_(climbable_infantry, climbable_infantry_command, "/gimbal/yaw")
+ , supercap_(climbable_infantry, climbable_infantry_command)
+ , gimbal_bullet_feeder_(
+ climbable_infantry, climbable_infantry_command, "/gimbal/bullet_feeder")
+ , chassis_front_climber_motor_(
+ {climbable_infantry, climbable_infantry_command,
+ "/chassis/climber/left_front_motor"},
+ {climbable_infantry, climbable_infantry_command,
+ "/chassis/climber/right_front_motor"})
+ , chassis_back_climber_motor_(
+ {climbable_infantry, climbable_infantry_command,
+ "/chassis/climber/left_back_motor"},
+ {climbable_infantry, climbable_infantry_command,
+ "/chassis/climber/right_back_motor"})
+ , chassis_front_steering_motors_(
+ {climbable_infantry, climbable_infantry_command, "/chassis/left_front_steering"},
+ {climbable_infantry, climbable_infantry_command, "/chassis/right_front_steering"})
+ , chassis_front_wheel_motors_(
+ {climbable_infantry, climbable_infantry_command, "/chassis/left_front_wheel"},
+ {climbable_infantry, climbable_infantry_command, "/chassis/right_front_wheel"})
+ , chassis_back_steering_motors_(
+ {climbable_infantry, climbable_infantry_command, "/chassis/left_back_steering"},
+ {climbable_infantry, climbable_infantry_command, "/chassis/right_back_steering"})
+ , chassis_back_wheel_motors_(
+ {climbable_infantry, climbable_infantry_command, "/chassis/left_back_wheel"},
+ {climbable_infantry, climbable_infantry_command, "/chassis/right_back_wheel"}) {
+
+ gimbal_yaw_motor_.configure(
+ device::LkMotor::Config{device::LkMotor::Type::kMG4010Ei10}.set_encoder_zero_point(
+ static_cast(
+ climbable_infantry.get_parameter("yaw_motor_zero_point").as_int())));
+ gimbal_bullet_feeder_.configure(
+ device::DjiMotor::Config{device::DjiMotor::Type::kM3508}
+ .set_reversed()
+ .enable_multi_turn_angle());
+
+ chassis_front_steering_motors_[0].configure(
+ device::DjiMotor::Config{device::DjiMotor::Type::kGM6020}
+ .set_encoder_zero_point(
+ static_cast(
+ climbable_infantry.get_parameter("left_front_zero_point").as_int()))
+ .set_reversed());
+ chassis_front_steering_motors_[1].configure(
+ device::DjiMotor::Config{device::DjiMotor::Type::kGM6020}
+ .set_encoder_zero_point(
+ static_cast(
+ climbable_infantry.get_parameter("right_front_zero_point").as_int()))
+ .set_reversed());
+ chassis_back_steering_motors_[0].configure(
+ device::DjiMotor::Config{device::DjiMotor::Type::kGM6020}
+ .set_encoder_zero_point(
+ static_cast(
+ climbable_infantry.get_parameter("left_back_zero_point").as_int()))
+ .set_reversed());
+ chassis_back_steering_motors_[1].configure(
+ device::DjiMotor::Config{device::DjiMotor::Type::kGM6020}
+ .set_encoder_zero_point(
+ static_cast(
+ climbable_infantry.get_parameter("right_back_zero_point").as_int()))
+ .set_reversed());
+
+ chassis_front_wheel_motors_[0].configure(
+ device::DjiMotor::Config{device::DjiMotor::Type::kM3508}
+ .set_reversed()
+ .set_reduction_ratio(2232. / 169.));
+ chassis_front_wheel_motors_[1].configure(
+ device::DjiMotor::Config{device::DjiMotor::Type::kM3508}
+ .set_reversed()
+ .set_reduction_ratio(2232. / 169.));
+ chassis_back_wheel_motors_[0].configure(
+ device::DjiMotor::Config{device::DjiMotor::Type::kM3508}
+ .set_reversed()
+ .set_reduction_ratio(2232. / 169.));
+ chassis_back_wheel_motors_[1].configure(
+ device::DjiMotor::Config{device::DjiMotor::Type::kM3508}
+ .set_reversed()
+ .set_reduction_ratio(2232. / 169.));
+
+ chassis_front_climber_motor_[0].configure(
+ device::DjiMotor::Config{device::DjiMotor::Type::kM3508}
+ .set_reversed()
+ .set_reduction_ratio(19.));
+ chassis_front_climber_motor_[1].configure(
+ device::DjiMotor::Config{device::DjiMotor::Type::kM3508}.set_reduction_ratio(19.));
+ chassis_back_climber_motor_[0].configure(
+ device::DjiMotor::Config{device::DjiMotor::Type::kM3508}
+ .enable_multi_turn_angle()
+ .set_reduction_ratio(19.)
+ .set_reversed());
+ chassis_back_climber_motor_[1].configure(
+ device::DjiMotor::Config{device::DjiMotor::Type::kM3508}
+ .enable_multi_turn_angle()
+ .set_reduction_ratio(19.));
+
+ climbable_infantry.register_output("/referee/serial", referee_serial_);
+ referee_serial_->read = [this](std::byte* buffer, size_t size) {
+ return referee_ring_buffer_receive_.pop_front_n(
+ [&buffer](std::byte byte) noexcept { *buffer++ = byte; }, size);
+ };
+ referee_serial_->write = [this](const std::byte* buffer, size_t size) {
+ start_transmit().uart0_transmit({
+ .uart_data = std::span{buffer, size}
+ });
+ return size;
+ };
+
+ climbable_infantry.register_output(
+ "/chassis/yaw/velocity_imu", chassis_yaw_velocity_imu_, 0);
+ climbable_infantry.register_output("/chassis/pitch_imu", chassis_pitch_imu_, 0.0);
+ }
+
+ BottomBoard(const BottomBoard&) = delete;
+ BottomBoard& operator=(const BottomBoard&) = delete;
+ BottomBoard(BottomBoard&&) = delete;
+ BottomBoard& operator=(BottomBoard&&) = delete;
+
+ ~BottomBoard() final = default;
+
+ void update() {
+ imu_.update_status();
+ dr16_.update_status();
+
+ *chassis_yaw_velocity_imu_ = imu_.gz();
+ *chassis_pitch_imu_ = -std::asin(2.0 * (imu_.q0() * imu_.q2() - imu_.q3() * imu_.q1()));
+
+ chassis_front_climber_motor_[0].update_status();
+ chassis_front_climber_motor_[1].update_status();
+ chassis_back_climber_motor_[0].update_status();
+ chassis_back_climber_motor_[1].update_status();
+
+ gimbal_yaw_motor_.update_status();
+ supercap_.update_status();
+ gimbal_bullet_feeder_.update_status();
+
+ tf_->set_state(
+ gimbal_yaw_motor_.angle());
+
+ for (auto& motor : chassis_front_wheel_motors_)
+ motor.update_status();
+ for (auto& motor : chassis_front_steering_motors_)
+ motor.update_status();
+ for (auto& motor : chassis_back_wheel_motors_)
+ motor.update_status();
+ for (auto& motor : chassis_back_steering_motors_)
+ motor.update_status();
+ }
+
+ void command_update() {
+ auto builder = start_transmit();
+
+ builder.can0_transmit({
+ .can_id = 0x200,
+ .can_data =
+ device::CanPacket8{
+ chassis_back_wheel_motors_[0].generate_command(),
+ chassis_back_wheel_motors_[1].generate_command(),
+ device::CanPacket8::PaddingQuarter{},
+ device::CanPacket8::PaddingQuarter{},
+ }
+ .as_bytes(),
+ });
+
+ builder.can0_transmit({
+ .can_id = 0x1FE,
+ .can_data =
+ device::CanPacket8{
+ chassis_back_steering_motors_[0].generate_command(),
+ chassis_back_steering_motors_[1].generate_command(),
+ device::CanPacket8::PaddingQuarter{},
+ device::CanPacket8::PaddingQuarter{},
+ }
+ .as_bytes(),
+ });
+
+ builder.can1_transmit({
+ .can_id = 0x200,
+ .can_data =
+ device::CanPacket8{
+ chassis_front_wheel_motors_[0].generate_command(),
+ chassis_front_wheel_motors_[1].generate_command(),
+ device::CanPacket8::PaddingQuarter{},
+ device::CanPacket8::PaddingQuarter{},
+ }
+ .as_bytes(),
+ });
+
+ builder.can1_transmit({
+ .can_id = 0x1FE,
+ .can_data =
+ device::CanPacket8{
+ chassis_front_steering_motors_[0].generate_command(),
+ chassis_front_steering_motors_[1].generate_command(),
+ device::CanPacket8::PaddingQuarter{},
+ device::CanPacket8::PaddingQuarter{},
+ }
+ .as_bytes(),
+ });
+
+ builder.can2_transmit({
+ .can_id = 0x200,
+ .can_data =
+ device::CanPacket8{
+ gimbal_bullet_feeder_.generate_command(),
+ device::CanPacket8::PaddingQuarter{},
+ device::CanPacket8::PaddingQuarter{},
+ device::CanPacket8::PaddingQuarter{},
+ }
+ .as_bytes(),
+ });
+
+ builder.can2_transmit({
+ .can_id = 0x141,
+ .can_data = gimbal_yaw_motor_.generate_torque_command().as_bytes(),
+ });
+
+ builder.can3_transmit({
+ .can_id = 0x1FE,
+ .can_data =
+ device::CanPacket8{
+ device::CanPacket8::PaddingQuarter{},
+ device::CanPacket8::PaddingQuarter{},
+ device::CanPacket8::PaddingQuarter{},
+ supercap_.generate_command(),
+ }
+ .as_bytes(),
+ });
+
+ builder.can3_transmit({
+ .can_id = 0x200,
+ .can_data =
+ device::CanPacket8{
+ chassis_back_climber_motor_[0].generate_command(),
+ chassis_back_climber_motor_[1].generate_command(),
+ chassis_front_climber_motor_[0].generate_command(),
+ chassis_front_climber_motor_[1].generate_command(),
+ }
+ .as_bytes(),
+ });
+
+
+ }
+
+ private:
+ void can0_receive_callback(const librmcs::data::CanDataView& data) override {
+ if (data.is_extended_can_id || data.is_remote_transmission) [[unlikely]]
+ return;
+ auto can_id = data.can_id;
+
+ if (can_id == 0x201) {
+ chassis_back_wheel_motors_[0].store_status(data.can_data);
+ } else if (can_id == 0x202) {
+ chassis_back_wheel_motors_[1].store_status(data.can_data);
+ } else if (can_id == 0x205) {
+ chassis_back_steering_motors_[0].store_status(data.can_data);
+ } else if (can_id == 0x206) {
+ chassis_back_steering_motors_[1].store_status(data.can_data);
+ }
+ }
+
+ void can1_receive_callback(const librmcs::data::CanDataView& data) override {
+ if (data.is_extended_can_id || data.is_remote_transmission) [[unlikely]]
+ return;
+ auto can_id = data.can_id;
+
+ if (can_id == 0x201) {
+ chassis_front_wheel_motors_[0].store_status(data.can_data);
+ } else if (can_id == 0x202) {
+ chassis_front_wheel_motors_[1].store_status(data.can_data);
+ } else if (can_id == 0x205) {
+ chassis_front_steering_motors_[0].store_status(data.can_data);
+ } else if (can_id == 0x206) {
+ chassis_front_steering_motors_[1].store_status(data.can_data);
+ }
+ }
+
+ void can2_receive_callback(const librmcs::data::CanDataView& data) override {
+ if (data.is_extended_can_id || data.is_remote_transmission) [[unlikely]]
+ return;
+ auto can_id = data.can_id;
+
+ if (can_id == 0x201) {
+ gimbal_bullet_feeder_.store_status(data.can_data);
+ } else if (can_id == 0x141) {
+ gimbal_yaw_motor_.store_status(data.can_data);
+ }
+ }
+
+ void can3_receive_callback(const librmcs::data::CanDataView& data) override {
+ if (data.is_extended_can_id || data.is_remote_transmission) [[unlikely]]
+ return;
+ auto can_id = data.can_id;
+
+ if (can_id == 0x201) {
+ chassis_back_climber_motor_[0].store_status(data.can_data);
+ } else if (can_id == 0x202) {
+ chassis_back_climber_motor_[1].store_status(data.can_data);
+ } else if (can_id == 0x203) {
+ chassis_front_climber_motor_[0].store_status(data.can_data);
+ } else if (can_id == 0x204) {
+ chassis_front_climber_motor_[1].store_status(data.can_data);
+ } else if (can_id == 0x300) {
+ supercap_.store_status(data.can_data);
+ }
+ }
+
+ void uart0_receive_callback(const librmcs::data::UartDataView& data) override {
+ const auto* uart_data = data.uart_data.data();
+ referee_ring_buffer_receive_.emplace_back_n(
+ [&uart_data](std::byte* storage) noexcept { *storage = *uart_data++; },
+ data.uart_data.size());
+ // if (!data.uart_data.empty()) {
+ // RCLCPP_INFO(
+ // logger_, "ref uart0 recv: size=%zu first=0x%02X",
+ // data.uart_data.size(),
+ // std::to_integer(data.uart_data[0]));
+ // }
+
+ }
+
+ void dbus_receive_callback(const librmcs::data::UartDataView& data) override {
+ dr16_.store_status(data.uart_data.data(), data.uart_data.size());
+ }
+
+ void accelerometer_receive_callback(
+ const librmcs::data::AccelerometerDataView& data) override {
+ imu_.store_accelerometer_status(data.x, data.y, data.z);
+ }
+
+ void gyroscope_receive_callback(const librmcs::data::GyroscopeDataView& data) override {
+ imu_.store_gyroscope_status(data.x, data.y, data.z);
+ }
+
+ rclcpp::Logger logger_;
+
+ OutputInterface& tf_;
+
+ device::Bmi088 imu_;
+ device::Dr16 dr16_;
+
+ device::LkMotor gimbal_yaw_motor_;
+
+ device::Supercap supercap_;
+ device::DjiMotor gimbal_bullet_feeder_;
+
+ device::DjiMotor chassis_front_climber_motor_[2];
+ device::DjiMotor chassis_back_climber_motor_[2];
+ device::DjiMotor chassis_front_steering_motors_[2];
+ device::DjiMotor chassis_front_wheel_motors_[2];
+ device::DjiMotor chassis_back_steering_motors_[2];
+ device::DjiMotor chassis_back_wheel_motors_[2];
+
+ rmcs_utility::RingBuffer referee_ring_buffer_receive_{256};
+ OutputInterface referee_serial_;
+
+ OutputInterface chassis_yaw_velocity_imu_;
+ OutputInterface chassis_pitch_imu_;
+ };
+
+ OutputInterface tf_;
+ InputInterface robot_chassis_power_limit_;
+
+ rclcpp::Subscription::SharedPtr gimbal_calibrate_subscription_;
+
+ std::shared_ptr top_board_;
+ std::shared_ptr bottom_board_;
+};
+
+} // namespace rmcs_core::hardware
+
+#include
+
+PLUGINLIB_EXPORT_CLASS(rmcs_core::hardware::ClimbableInfantry, rmcs_executor::Component)
diff --git a/rmcs_ws/src/rmcs_core/src/hardware/steering-hero.cpp b/rmcs_ws/src/rmcs_core/src/hardware/steering-hero.cpp
index d30c1ce0..0f16692d 100644
--- a/rmcs_ws/src/rmcs_core/src/hardware/steering-hero.cpp
+++ b/rmcs_ws/src/rmcs_core/src/hardware/steering-hero.cpp
@@ -1,14 +1,15 @@
+#include
#include
#include
#include
#include
#include
#include
-#include
#include
#include
#include
+#include
#include
#include
#include
@@ -18,10 +19,8 @@
#include
#include
#include
-#include
#include
-#include "hardware/device/benewake.hpp"
#include "hardware/device/bmi088.hpp"
#include "hardware/device/can_packet.hpp"
#include "hardware/device/dji_motor.hpp"
@@ -36,16 +35,14 @@ class SteeringHero
, public rclcpp::Node {
public:
SteeringHero()
- : Node{
+ : Node(
get_component_name(),
- rclcpp::NodeOptions{}.automatically_declare_parameters_from_overrides(true)}
+ rclcpp::NodeOptions().automatically_declare_parameters_from_overrides(true))
, command_component_(
create_partner_component(
get_component_name() + "_command", *this)) {
register_output("/tf", tf_);
- tf_->set_transform(
- Eigen::Translation3d{0.16, 0.0, 0.15});
gimbal_calibrate_subscription_ = create_subscription(
"/gimbal/calibrate", rclcpp::QoS{0}, [this](std_msgs::msg::Int32::UniquePtr&& msg) {
@@ -54,10 +51,15 @@ class SteeringHero
top_board_ = std::make_unique(
*this, *command_component_, get_parameter("board_serial_top_board").as_string());
- bottom_board_ = std::make_unique(
- *this, *command_component_, get_parameter("board_serial_bottom_board").as_string());
- temperature_logging_timer_.reset(1000);
+ bottom_board_one_ = std::make_unique(
+ *this, *command_component_, get_parameter("board_serial_bottom_board_one").as_string());
+
+ bottom_board_two_ = std::make_unique(
+ *this, *command_component_, get_parameter("board_serial_bottom_board_two").as_string());
+
+ tf_->set_transform(
+ Eigen::Translation3d{0.06603, 0.0, 0.082});
}
SteeringHero(const SteeringHero&) = delete;
@@ -69,30 +71,21 @@ class SteeringHero
void update() override {
top_board_->update();
- bottom_board_->update();
-
- if (temperature_logging_timer_.tick()) {
- temperature_logging_timer_.reset(1000);
- RCLCPP_INFO(
- get_logger(),
- "Temperature: pitch: %.1f, top_yaw: %.1f, bottom_yaw: %.1f, feeder: %.1f",
- top_board_->gimbal_pitch_motor_.temperature(),
- top_board_->gimbal_top_yaw_motor_.temperature(),
- bottom_board_->gimbal_bottom_yaw_motor_.temperature(),
- top_board_->gimbal_bullet_feeder_.temperature());
- }
+ bottom_board_one_->update();
+ bottom_board_two_->update();
}
void command_update() {
top_board_->command_update();
- bottom_board_->command_update();
+ bottom_board_one_->command_update();
+ bottom_board_two_->command_update();
}
private:
void gimbal_calibrate_subscription_callback(std_msgs::msg::Int32::UniquePtr) {
RCLCPP_INFO(
get_logger(), "[gimbal calibration] New bottom yaw offset: %ld",
- bottom_board_->gimbal_bottom_yaw_motor_.calibrate_zero_point());
+ bottom_board_two_->gimbal_bottom_yaw_motor_.calibrate_zero_point());
RCLCPP_INFO(
get_logger(), "[gimbal calibration] New pitch offset: %ld",
top_board_->gimbal_pitch_motor_.calibrate_zero_point());
@@ -100,20 +93,20 @@ class SteeringHero
get_logger(), "[gimbal calibration] New top yaw offset: %ld",
top_board_->gimbal_top_yaw_motor_.calibrate_zero_point());
RCLCPP_INFO(
- get_logger(), "[gimbal calibration] New viewer offset: %ld",
- top_board_->gimbal_player_viewer_motor_.calibrate_zero_point());
+ get_logger(), "[gimbal calibration] New bullet feeder offset: %ld",
+ top_board_->gimbal_bullet_feeder_.calibrate_zero_point());
RCLCPP_INFO(
get_logger(), "[chassis calibration] left front steering offset: %d",
- bottom_board_->chassis_steering_motors_[0].calibrate_zero_point());
+ bottom_board_one_->chassis_steering_motors_[0].calibrate_zero_point());
+ RCLCPP_INFO(
+ get_logger(), "[chassis calibration] right front steering offset: %d",
+ bottom_board_one_->chassis_steering_motors_[1].calibrate_zero_point());
RCLCPP_INFO(
get_logger(), "[chassis calibration] left back steering offset: %d",
- bottom_board_->chassis_steering_motors_[1].calibrate_zero_point());
+ bottom_board_two_->chassis_steering_motors2_[0].calibrate_zero_point());
RCLCPP_INFO(
get_logger(), "[chassis calibration] right back steering offset: %d",
- bottom_board_->chassis_steering_motors_[2].calibrate_zero_point());
- RCLCPP_INFO(
- get_logger(), "[chassis calibration] right front steering offset: %d",
- bottom_board_->chassis_steering_motors_[3].calibrate_zero_point());
+ bottom_board_two_->chassis_steering_motors2_[1].calibrate_zero_point());
}
class SteeringHeroCommand : public rmcs_executor::Component {
@@ -123,7 +116,6 @@ class SteeringHero
void update() override { hero_.command_update(); }
- private:
SteeringHero& hero_;
};
std::shared_ptr command_component_;
@@ -132,68 +124,75 @@ class SteeringHero
public:
friend class SteeringHero;
explicit TopBoard(
- SteeringHero& hero, SteeringHeroCommand& hero_command,
+ SteeringHero& steering_hero, SteeringHeroCommand& steering_hero_command,
std::string_view board_serial = {})
: librmcs::agent::CBoard(board_serial)
- , tf_(hero.tf_)
+ , logger_(steering_hero.get_logger())
+ , tf_(steering_hero.tf_)
, imu_(1000, 0.2, 0.0)
- , benewake_(hero, "/gimbal/auto_aim/laser_distance")
- , gimbal_top_yaw_motor_(
- hero, hero_command, "/gimbal/top_yaw",
- device::LkMotor::Config{device::LkMotor::Type::kMG5010Ei10}
- .set_encoder_zero_point(
- static_cast(
- hero.get_parameter("top_yaw_motor_zero_point").as_int())))
- , gimbal_pitch_motor_(
- hero, hero_command, "/gimbal/pitch",
- device::LkMotor::Config{device::LkMotor::Type::kMG5010Ei10}
- .set_encoder_zero_point(
- static_cast(hero.get_parameter("pitch_motor_zero_point").as_int())))
+ , gimbal_top_yaw_motor_(steering_hero, steering_hero_command, "/gimbal/top_yaw")
+ , gimbal_pitch_motor_(steering_hero, steering_hero_command, "/gimbal/pitch")
, gimbal_friction_wheels_(
- {hero, hero_command, "/gimbal/second_left_friction",
- device::DjiMotor::Config{device::DjiMotor::Type::kM3508}.set_reduction_ratio(
- 1.)},
- {hero, hero_command, "/gimbal/first_left_friction",
- device::DjiMotor::Config{device::DjiMotor::Type::kM3508}.set_reduction_ratio(
- 1.)},
- {hero, hero_command, "/gimbal/first_right_friction",
- device::DjiMotor::Config{device::DjiMotor::Type::kM3508}
- .set_reduction_ratio(1.)
- .set_reversed()},
- {hero, hero_command, "/gimbal/second_right_friction",
- device::DjiMotor::Config{device::DjiMotor::Type::kM3508}
- .set_reduction_ratio(1.)
- .set_reversed()})
- , gimbal_bullet_feeder_(
- hero, hero_command, "/gimbal/bullet_feeder",
- device::LkMotor::Config{device::LkMotor::Type::kMG5010Ei10}
- .set_reversed()
- .enable_multi_turn_angle())
- , gimbal_scope_motor_(
- hero, hero_command, "/gimbal/scope",
- device::DjiMotor::Config{device::DjiMotor::Type::kM2006})
- , gimbal_player_viewer_motor_(
- hero, hero_command, "/gimbal/player_viewer",
- device::LkMotor::Config{device::LkMotor::Type::kMG4005Ei10}
- .set_encoder_zero_point(
- static_cast(hero.get_parameter("viewer_motor_zero_point").as_int()))
- .set_reversed()) {
+ {steering_hero, steering_hero_command, "/gimbal/first_left_friction"},
+ {steering_hero, steering_hero_command, "/gimbal/first_right_friction"},
+ {steering_hero, steering_hero_command, "/gimbal/second_left_friction"},
+ {steering_hero, steering_hero_command, "/gimbal/second_right_friction"})
+ , gimbal_bullet_feeder_(steering_hero, steering_hero_command, "/gimbal/bullet_feeder")
+ , putter_motor_(steering_hero, steering_hero_command, "/gimbal/putter") {
+
+ gimbal_top_yaw_motor_.configure(
+ device::LkMotor::Config{device::LkMotor::Type::kMG5010Ei10}.set_encoder_zero_point(
+ static_cast(
+ steering_hero.get_parameter("top_yaw_motor_zero_point").as_int())));
+ gimbal_pitch_motor_.configure(
+ device::LkMotor::Config{device::LkMotor::Type::kMG5010Ei10}.set_encoder_zero_point(
+ static_cast(
+ steering_hero.get_parameter("pitch_motor_zero_point").as_int())));
+ gimbal_friction_wheels_[0].configure(
+ device::DjiMotor::Config{device::DjiMotor::Type::kM3508}.set_reduction_ratio(1.));
+ gimbal_friction_wheels_[1].configure(
+ device::DjiMotor::Config{device::DjiMotor::Type::kM3508}
+ .set_reversed()
+ .set_reduction_ratio(1.));
+ gimbal_friction_wheels_[2].configure(
+ device::DjiMotor::Config{device::DjiMotor::Type::kM3508}.set_reduction_ratio(1.));
+ gimbal_friction_wheels_[3].configure(
+ device::DjiMotor::Config{device::DjiMotor::Type::kM3508}
+ .set_reversed()
+ .set_reduction_ratio(1.));
+ gimbal_bullet_feeder_.configure(
+ device::LkMotor::Config{device::LkMotor::Type::kMG5010Ei10}
+ .set_encoder_zero_point(
+ static_cast(
+ steering_hero.get_parameter("bullet_feeder_motor_zero_point").as_int()))
+ .set_reversed()
+ .enable_multi_turn_angle());
+ putter_motor_.configure(
+ device::DjiMotor::Config{device::DjiMotor::Type::kM3508}
+ .set_reduction_ratio(1.)
+ .enable_multi_turn_angle());
+
+ steering_hero.register_output("/gimbal/yaw/velocity_imu", gimbal_yaw_velocity_imu_);
+ steering_hero.register_output("/gimbal/pitch/velocity_imu", gimbal_pitch_velocity_imu_);
+
+ steering_hero.register_output(
+ "/gimbal/photoelectric_sensor", photoelectric_sensor_status_, false);
+ steering_hero.register_output(
+ "/auto_aim/image_capturer/timestamp", camera_capturer_trigger_timestamp_, 0);
+ steering_hero.register_output(
+ "/auto_aim/image_capturer/trigger", camera_capturer_trigger_, 0);
imu_.set_coordinate_mapping([](double x, double y, double z) {
// Get the mapping with the following code.
- // The rotation angle must be an exact multiple of 90 degrees, otherwise use a
- // matrix.
+ // The rotation angle must be an exact multiple of 90 degrees, otherwise
+ // use a matrix.
- // Eigen::AngleAxisd pitch_link_to_imu_link{
- // std::numbers::pi, Eigen::Vector3d::UnitZ()};
- // Eigen::Vector3d mapping = pitch_link_to_imu_link * Eigen::Vector3d{1, 2, 3};
+ // Eigen::AngleAxisd pitch_link_to_bmi088_link{
+ // std::numbers::pi / 2, Eigen::Vector3d::UnitZ()};
+ // Eigen::Vector3d mapping = pitch_link_to_bmi088_link * Eigen::Vector3d{1, 2, 3};
// std::cout << mapping << std::endl;
-
return std::make_tuple(-x, -y, z);
});
-
- hero.register_output("/gimbal/yaw/velocity_imu", gimbal_yaw_velocity_imu_);
- hero.register_output("/gimbal/pitch/velocity_imu", gimbal_pitch_velocity_imu_);
}
TopBoard(const TopBoard&) = delete;
@@ -202,16 +201,13 @@ class SteeringHero
TopBoard& operator=(TopBoard&&) = delete;
~TopBoard() final = default;
-
void update() {
imu_.update_status();
- const Eigen::Quaterniond gimbal_imu_pose{imu_.q0(), imu_.q1(), imu_.q2(), imu_.q3()};
+ Eigen::Quaterniond gimbal_imu_pose{imu_.q0(), imu_.q1(), imu_.q2(), imu_.q3()};
tf_->set_transform(
gimbal_imu_pose.conjugate());
- benewake_.update_status();
-
*gimbal_yaw_velocity_imu_ = imu_.gz();
*gimbal_pitch_velocity_imu_ = imu_.gy();
@@ -221,16 +217,15 @@ class SteeringHero
tf_->set_state(
gimbal_pitch_motor_.angle());
- gimbal_player_viewer_motor_.update_status();
- tf_->set_state(
- gimbal_player_viewer_motor_.angle());
-
- gimbal_scope_motor_.update_status();
-
for (auto& motor : gimbal_friction_wheels_)
motor.update_status();
gimbal_bullet_feeder_.update_status();
+ putter_motor_.update_status();
+
+ if (last_camera_capturer_trigger_timestamp_ != *camera_capturer_trigger_timestamp_)
+ *camera_capturer_trigger_ = true;
+ last_camera_capturer_trigger_timestamp_ = *camera_capturer_trigger_timestamp_;
}
void command_update() {
@@ -240,26 +235,19 @@ class SteeringHero
.can_id = 0x200,
.can_data =
device::CanPacket8{
- gimbal_friction_wheels_[0].generate_command(),
+ gimbal_friction_wheels_[3].generate_command(),
gimbal_friction_wheels_[1].generate_command(),
gimbal_friction_wheels_[2].generate_command(),
- gimbal_friction_wheels_[3].generate_command(),
+ gimbal_friction_wheels_[0].generate_command(),
}
.as_bytes(),
});
builder.can1_transmit({
- .can_id = 0x141,
- .can_data = gimbal_bullet_feeder_
- .generate_torque_command(gimbal_bullet_feeder_.control_torque())
- .as_bytes(),
- });
-
- builder.can2_transmit({
- .can_id = 0x200,
+ .can_id = 0x1FF,
.can_data =
device::CanPacket8{
- gimbal_scope_motor_.generate_command(),
+ putter_motor_.generate_command(),
device::CanPacket8::PaddingQuarter{},
device::CanPacket8::PaddingQuarter{},
device::CanPacket8::PaddingQuarter{},
@@ -267,12 +255,9 @@ class SteeringHero
.as_bytes(),
});
- builder.can2_transmit({
- .can_id = 0x143,
- .can_data =
- gimbal_player_viewer_motor_
- .generate_velocity_command(gimbal_player_viewer_motor_.control_velocity())
- .as_bytes(),
+ builder.can1_transmit({
+ .can_id = 0x141,
+ .can_data = gimbal_bullet_feeder_.generate_torque_command().as_bytes(),
});
builder.can2_transmit({
@@ -284,22 +269,28 @@ class SteeringHero
.can_id = 0x142,
.can_data = gimbal_pitch_motor_.generate_command().as_bytes(),
});
+
+ builder.gpio_digital_read({
+ .channel = 1,
+ .falling_edge = true,
+ });
}
private:
void can1_receive_callback(const librmcs::data::CanDataView& data) override {
if (data.is_extended_can_id || data.is_remote_transmission) [[unlikely]]
return;
-
auto can_id = data.can_id;
- if (can_id == 0x201) {
+ if (can_id == 0x204) {
gimbal_friction_wheels_[0].store_status(data.can_data);
} else if (can_id == 0x202) {
gimbal_friction_wheels_[1].store_status(data.can_data);
} else if (can_id == 0x203) {
gimbal_friction_wheels_[2].store_status(data.can_data);
- } else if (can_id == 0x204) {
+ } else if (can_id == 0x201) {
gimbal_friction_wheels_[3].store_status(data.can_data);
+ } else if (can_id == 0x205) {
+ putter_motor_.store_status(data.can_data);
} else if (can_id == 0x141) {
gimbal_bullet_feeder_.store_status(data.can_data);
}
@@ -308,21 +299,22 @@ class SteeringHero
void can2_receive_callback(const librmcs::data::CanDataView& data) override {
if (data.is_extended_can_id || data.is_remote_transmission) [[unlikely]]
return;
-
auto can_id = data.can_id;
+
if (can_id == 0x141) {
gimbal_top_yaw_motor_.store_status(data.can_data);
} else if (can_id == 0x142) {
gimbal_pitch_motor_.store_status(data.can_data);
- } else if (can_id == 0x143) {
- gimbal_player_viewer_motor_.store_status(data.can_data);
- } else if (can_id == 0x201) {
- gimbal_scope_motor_.store_status(data.can_data);
}
}
- void uart2_receive_callback(const librmcs::data::UartDataView& data) override {
- benewake_.store_status(data.uart_data.data(), data.uart_data.size());
+ void gpio_digital_read_result_callback(
+ const librmcs::data::GpioDigitalDataView& data) override {
+ *photoelectric_sensor_status_ = false;
+ if (data.channel == 1) {
+ *photoelectric_sensor_status_ = true;
+ RCLCPP_INFO(logger_, "trigger!");
+ }
}
void accelerometer_receive_callback(
@@ -334,82 +326,257 @@ class SteeringHero
imu_.store_gyroscope_status(data.x, data.y, data.z);
}
- OutputInterface& tf_;
+ void putter_receive_callback(bool status) { *photoelectric_sensor_status_ = status; }
- device::Bmi088 imu_;
- device::Benewake benewake_;
+ rclcpp::Logger logger_;
+ OutputInterface& tf_;
- OutputInterface gimbal_yaw_velocity_imu_;
- OutputInterface gimbal_pitch_velocity_imu_;
+ std::time_t last_camera_capturer_trigger_timestamp_{0};
+ device::Bmi088 imu_;
device::LkMotor gimbal_top_yaw_motor_;
device::LkMotor gimbal_pitch_motor_;
-
device::DjiMotor gimbal_friction_wheels_[4];
device::LkMotor gimbal_bullet_feeder_;
+ device::DjiMotor putter_motor_;
- device::DjiMotor gimbal_scope_motor_;
- device::LkMotor gimbal_player_viewer_motor_;
+ OutputInterface gimbal_yaw_velocity_imu_;
+ OutputInterface gimbal_pitch_velocity_imu_;
+ OutputInterface photoelectric_sensor_status_;
+ OutputInterface camera_capturer_trigger_;
+ OutputInterface camera_capturer_trigger_timestamp_;
+ std::atomic photoelectric_sensor_status_atomic{false};
};
- class BottomBoard final : private librmcs::agent::CBoard {
+ class BottomBoard_one final : private librmcs::agent::CBoard {
public:
friend class SteeringHero;
- explicit BottomBoard(
- SteeringHero& hero, SteeringHeroCommand& hero_command,
+ explicit BottomBoard_one(
+ SteeringHero& steering_hero, SteeringHeroCommand& steering_hero_command,
std::string_view board_serial = {})
: librmcs::agent::CBoard(board_serial)
+ , logger_(steering_hero.get_logger())
, imu_(1000, 0.2, 0.0)
- , tf_(hero.tf_)
- , dr16_(hero)
+ , chassis_front_climber_motor_(
+ {steering_hero, steering_hero_command, "/chassis/climber/left_front_motor"},
+ {steering_hero, steering_hero_command, "/chassis/climber/right_front_motor"})
+ , chassis_back_climber_motor_(
+ {steering_hero, steering_hero_command, "/chassis/climber/left_back_motor"},
+ {steering_hero, steering_hero_command, "/chassis/climber/right_back_motor"})
, chassis_steering_motors_(
- {hero, hero_command, "/chassis/left_front_steering",
- device::DjiMotor::Config{device::DjiMotor::Type::kGM6020}
- .set_encoder_zero_point(
- static_cast(hero.get_parameter("left_front_zero_point").as_int()))
- .set_reversed()},
- {hero, hero_command, "/chassis/left_back_steering",
- device::DjiMotor::Config{device::DjiMotor::Type::kGM6020}
- .set_encoder_zero_point(
- static_cast(hero.get_parameter("left_back_zero_point").as_int()))
- .set_reversed()},
- {hero, hero_command, "/chassis/right_back_steering",
- device::DjiMotor::Config{device::DjiMotor::Type::kGM6020}
- .set_encoder_zero_point(
- static_cast(hero.get_parameter("right_back_zero_point").as_int()))
- .set_reversed()},
- {hero, hero_command, "/chassis/right_front_steering",
- device::DjiMotor::Config{device::DjiMotor::Type::kGM6020}
- .set_encoder_zero_point(
- static_cast(hero.get_parameter("right_front_zero_point").as_int()))
- .set_reversed()})
+ {steering_hero, steering_hero_command, "/chassis/left_front_steering"},
+ {steering_hero, steering_hero_command, "/chassis/right_front_steering"})
, chassis_wheel_motors_(
- {hero, hero_command, "/chassis/left_front_wheel",
- device::DjiMotor::Config{device::DjiMotor::Type::kM3508}
- .set_reversed()
- .set_reduction_ratio(2232. / 169.)},
- {hero, hero_command, "/chassis/left_back_wheel",
- device::DjiMotor::Config{device::DjiMotor::Type::kM3508}
- .set_reversed()
- .set_reduction_ratio(2232. / 169.)},
- {hero, hero_command, "/chassis/right_back_wheel",
- device::DjiMotor::Config{device::DjiMotor::Type::kM3508}
- .set_reversed()
- .set_reduction_ratio(2232. / 169.)},
- {hero, hero_command, "/chassis/right_front_wheel",
- device::DjiMotor::Config{device::DjiMotor::Type::kM3508}
- .set_reversed()
- .set_reduction_ratio(2232. / 169.)})
- , supercap_(hero, hero_command)
- , gimbal_bottom_yaw_motor_(
- hero, hero_command, "/gimbal/bottom_yaw",
- device::LkMotor::Config{device::LkMotor::Type::kMG6012Ei8}
- .set_reversed()
- .set_encoder_zero_point(
- static_cast(
- hero.get_parameter("bottom_yaw_motor_zero_point").as_int()))) {
-
- hero.register_output("/referee/serial", referee_serial_);
+ {steering_hero, steering_hero_command, "/chassis/left_front_wheel"},
+ {steering_hero, steering_hero_command, "/chassis/right_front_wheel"}) {
+ //
+
+ chassis_steering_motors_[0].configure(
+ device::DjiMotor::Config{device::DjiMotor::Type::kGM6020}
+ .set_encoder_zero_point(
+ static_cast(
+ steering_hero.get_parameter("left_front_zero_point").as_int()))
+ .set_reversed());
+ chassis_steering_motors_[1].configure(
+ device::DjiMotor::Config{device::DjiMotor::Type::kGM6020}
+ .set_encoder_zero_point(
+ static_cast(
+ steering_hero.get_parameter("right_front_zero_point").as_int()))
+ .set_reversed());
+ chassis_wheel_motors_[0].configure(
+ device::DjiMotor::Config{device::DjiMotor::Type::kM3508}
+ .set_reversed()
+ .set_reduction_ratio(2232. / 169.));
+ chassis_wheel_motors_[1].configure(
+ device::DjiMotor::Config{device::DjiMotor::Type::kM3508}
+ .set_reversed()
+ .set_reduction_ratio(2232. / 169.));
+
+ chassis_front_climber_motor_[0].configure(
+ device::DjiMotor::Config{device::DjiMotor::Type::kM3508}
+ .set_reversed()
+ .set_reduction_ratio(19.));
+ chassis_front_climber_motor_[1].configure(
+ device::DjiMotor::Config{device::DjiMotor::Type::kM3508}.set_reduction_ratio(19.));
+ chassis_back_climber_motor_[0].configure(
+ device::DjiMotor::Config{device::DjiMotor::Type::kM3508}
+ .enable_multi_turn_angle()
+ .set_reduction_ratio(19.));
+ chassis_back_climber_motor_[1].configure(
+ device::DjiMotor::Config{device::DjiMotor::Type::kM3508}
+ .set_reversed()
+ .enable_multi_turn_angle()
+ .set_reduction_ratio(19.));
+
+ steering_hero.register_output(
+ "/chassis/yaw/velocity_imu", chassis_yaw_velocity_imu_, 0);
+ steering_hero.register_output("/chassis/pitch_imu", chassis_pitch_imu_, 0.0);
+ }
+ BottomBoard_one(const BottomBoard_one&) = delete;
+ BottomBoard_one& operator=(const BottomBoard_one&) = delete;
+ BottomBoard_one(BottomBoard_one&&) = delete;
+ BottomBoard_one& operator=(BottomBoard_one&&) = delete;
+
+ ~BottomBoard_one() final = default;
+
+ void update() {
+ imu_.update_status();
+
+ *chassis_yaw_velocity_imu_ = imu_.gz();
+ *chassis_pitch_imu_ = -std::asin(2.0 * (imu_.q0() * imu_.q2() - imu_.q3() * imu_.q1()));
+
+ chassis_front_climber_motor_[0].update_status();
+ chassis_front_climber_motor_[1].update_status();
+ chassis_back_climber_motor_[0].update_status();
+ chassis_back_climber_motor_[1].update_status();
+
+ for (auto& motor : chassis_wheel_motors_)
+ motor.update_status();
+ for (auto& motor : chassis_steering_motors_)
+ motor.update_status();
+ }
+
+ void command_update() {
+ auto builder = start_transmit();
+
+ builder.can1_transmit({
+ .can_id = 0x200,
+ .can_data =
+ device::CanPacket8{
+ chassis_wheel_motors_[1].generate_command(),
+ chassis_wheel_motors_[0].generate_command(),
+ device::CanPacket8::PaddingQuarter{},
+ device::CanPacket8::PaddingQuarter{},
+ }
+ .as_bytes(),
+ });
+
+ builder.can1_transmit({
+ .can_id = 0x1FE,
+ .can_data =
+ device::CanPacket8{
+ device::CanPacket8::PaddingQuarter{},
+ chassis_steering_motors_[0].generate_command(),
+ device::CanPacket8::PaddingQuarter{},
+ chassis_steering_motors_[1].generate_command(),
+ }
+ .as_bytes(),
+ });
+
+ builder.can2_transmit({
+ .can_id = 0x200,
+ .can_data =
+ device::CanPacket8{
+ chassis_back_climber_motor_[0].generate_command(),
+ chassis_back_climber_motor_[1].generate_command(),
+ chassis_front_climber_motor_[0].generate_command(),
+ chassis_front_climber_motor_[1].generate_command(),
+ }
+ .as_bytes(),
+ });
+ }
+
+ private:
+ void can1_receive_callback(const librmcs::data::CanDataView& data) override {
+ if (data.is_extended_can_id || data.is_remote_transmission) [[unlikely]]
+ return;
+ auto can_id = data.can_id;
+ if (can_id == 0x201) {
+ chassis_wheel_motors_[1].store_status(data.can_data);
+ } else if (can_id == 0x202) {
+ chassis_wheel_motors_[0].store_status(data.can_data);
+ } else if (can_id == 0x206) {
+ chassis_steering_motors_[0].store_status(data.can_data);
+ } else if (can_id == 0x208) {
+ chassis_steering_motors_[1].store_status(data.can_data);
+ }
+ }
+
+ void can2_receive_callback(const librmcs::data::CanDataView& data) override {
+ if (data.is_extended_can_id || data.is_remote_transmission) [[unlikely]]
+ return;
+ auto can_id = data.can_id;
+
+ if (can_id == 0x203) {
+ chassis_front_climber_motor_[0].store_status(data.can_data);
+ } else if (can_id == 0x204) {
+ chassis_front_climber_motor_[1].store_status(data.can_data);
+ } else if (can_id == 0x201) {
+ chassis_back_climber_motor_[0].store_status(data.can_data);
+ } else if (can_id == 0x202) {
+ chassis_back_climber_motor_[1].store_status(data.can_data);
+ }
+ }
+
+ void accelerometer_receive_callback(
+ const librmcs::data::AccelerometerDataView& data) override {
+ imu_.store_accelerometer_status(data.x, data.y, data.z);
+ }
+
+ void gyroscope_receive_callback(const librmcs::data::GyroscopeDataView& data) override {
+ imu_.store_gyroscope_status(data.x, data.y, data.z);
+ }
+
+ rclcpp::Logger logger_;
+
+ device::Bmi088 imu_;
+ device::DjiMotor chassis_front_climber_motor_[2];
+ device::DjiMotor chassis_back_climber_motor_[2];
+ device::DjiMotor chassis_steering_motors_[2];
+ device::DjiMotor chassis_wheel_motors_[2];
+
+ OutputInterface chassis_yaw_velocity_imu_;
+ OutputInterface chassis_pitch_imu_;
+ OutputInterface gimbal_yaw_velocity_imu_;
+ OutputInterface