GKD.RoboCtrl
RoboMaster Linux 电控:异步 IO、设备驱动与机器人控制
载入中...
搜索中...
未找到
motion_control.cpp
1#include "ctrl/motion_control.h"
2
3#include "core/multiton.hpp"
4#include "ctrl/robot.h"
5#include "ctrl/shoot.h"
6#include "ctrl/power_manager.h"
7#include "device/aim_link.hpp"
8#include "device/referee/referee.h"
9#include "device/super_cap.h"
10#include "device/remote_logger.hpp"
11
12namespace roboctrl::ctrl {
13
14bool motion_control::init(const info_type& info) {
15 info_ = info;
16 auto& robot = roboctrl::get<ctrl::robot>();
17 chassis_ = robot.chassis();
18 gimbal_ = robot.gimbal();
19 secondary_gimbal_ = robot.secondary_gimbal();
20 large_yaw_ = robot.large_yaw();
21 if (!info.primary_aim_key.empty()) primary_aim_ = &roboctrl::get<device::aim_link>(info.primary_aim_key);
22 if (!info.secondary_aim_key.empty()) secondary_aim_ = &roboctrl::get<device::aim_link>(info.secondary_aim_key);
23 if (!info.navigation_key.empty()) navigation_ = &roboctrl::get<device::navigation_link>(info.navigation_key);
24 if (!info.remote_logger_key.empty()) remote_logger_ = &roboctrl::get<device::remote_logger>(info.remote_logger_key);
25 follow_.configure(info.follow_pid, info.follow_direction, info.spin_recenter_speed, info.follow_tolerance);
26 auto& pad = roboctrl::get<device::control_pad>(info.control_pad_key);
27 pad.on_update([this](const device::control_pad_state& input) {
28 input_ = input;
29 input_pending_ = true;
30 });
31 roboctrl::spawn(task());
32 return true;
33}
34
35void motion_control::stop_outputs() {
36 if (chassis_) {
37 chassis_->set_planar_velocity({0.0f, 0.0f});
38 chassis_->set_rotate_speed(0.0f);
39 }
40 auto& robot = roboctrl::get<ctrl::robot>();
41 for (auto* gimbal : {gimbal_, secondary_gimbal_, large_yaw_})
42 if (gimbal) gimbal->hold();
43 if (info_.enable_shoot) {
44 auto& shoot = roboctrl::get<ctrl::shoot>();
45 shoot.set_firing(false);
46 shoot.set_fire_permitted(false);
47 shoot.set_friction_enabled(false);
48 }
49 if (auto* shoot = robot.secondary_shoot()) {
50 shoot->set_firing(false);
51 shoot->set_fire_permitted(false);
52 shoot->set_friction_enabled(false);
53 }
54 follow_.reset();
55 search_elapsed_ = 0;
56 auto_aim_last_ = false;
57 if (robot.state() == robot_state::NoForce) mapper_.reset();
58 if (roboctrl::get<device::referee>().configured())
59 roboctrl::get<device::referee>().set_ui_status({});
60}
61
62void motion_control::dispatch(const control_command& command, fp32 dt) {
63 auto& robot = roboctrl::get<ctrl::robot>();
64 if (auto_aim_last_ != command.auto_aim) {
65 for (auto* gimbal : {gimbal_, secondary_gimbal_, large_yaw_})
66 if (gimbal) gimbal->hold();
67 auto_aim_last_ = command.auto_aim;
68 }
69 const auto primary_target = primary_aim_ ? primary_aim_->target() : std::nullopt;
70 const auto secondary_target = secondary_aim_ ? secondary_aim_->target() : std::nullopt;
71 const bool searching = command.auto_aim && info_.search_when_vision_stale &&
72 !primary_target && (!secondary_gimbal_ || !secondary_target);
73 if (searching) {
74 if (robot.state() != robot_state::Search) {
75 search_elapsed_ = 0;
76 robot.set_state(robot_state::Search);
77 }
78 search_elapsed_ += dt;
79 } else if (robot.state() == robot_state::Search) robot.set_state(robot_state::FollowGimbal);
80
81 const auto aim = [&](device::gimbal_base* gimbal, const auto& target) {
82 if (!gimbal) return false;
83 if (command.auto_aim) {
84 if (target) {
85 gimbal->set_target_yaw(target->yaw);
86 gimbal->set_target_pitch(target->pitch);
87 return target->fire && gimbal->initialized() && gimbal->online();
88 }
89 if (searching) {
90 gimbal->add_yaw(info_.search_yaw_speed * dt);
91 gimbal->set_target_pitch(info_.search_pitch_center + info_.search_pitch_amplitude *
92 std::sin(search_elapsed_ * info_.search_pitch_speed));
93 } else gimbal->hold();
94 return false;
95 }
96 if (command.use_pitch_target) gimbal->set_target_pitch(command.pitch_target);
97 else gimbal->add_pitch(command.pitch_delta);
98 gimbal->add_yaw(command.yaw_delta);
99 return gimbal->initialized() && gimbal->online();
100 };
101 const bool primary_permission = gimbal_ ? aim(gimbal_, primary_target) : !command.auto_aim;
102 const bool secondary_permission = aim(secondary_gimbal_, secondary_target);
103
104 // Keep the small head near its mechanical forward position without assuming
105 // that independently booted IMUs share the same absolute yaw origin.
106 if (large_yaw_ && gimbal_ && gimbal_->relative_yaw_valid())
107 large_yaw_->set_target_yaw(large_yaw_->yaw() +
108 info_.large_yaw_follow_direction * gimbal_->relative_yaw());
109
110 vectorf velocity = command.velocity;
111 if (info_.enable_navigation && command.auto_aim && navigation_) {
112 const auto navigation = navigation_->command();
113 velocity = navigation ? vectorf{.x = navigation->vx, .y = navigation->vy} : vectorf{};
114 }
115 if (chassis_) {
116 auto* heading = large_yaw_ ? large_yaw_ : gimbal_;
117 if (heading && !heading->relative_yaw_valid()) {
118 // World-yaw hold may be enabled without a mechanical zero; a chassis
119 // frame transform is still invalid and therefore cannot drive wheels.
120 chassis_->set_planar_velocity({});
121 chassis_->set_rotate_speed(0);
122 follow_.reset();
123 } else {
124 const auto relative_yaw = heading ? heading->relative_yaw() : 0.0f;
125 chassis_->set_planar_velocity(gimbal_to_chassis(velocity, relative_yaw));
126 const bool follow = heading && info_.enable_follow && robot.state() != robot_state::NotFollow;
127 chassis_->set_rotate_speed(follow ? follow_.update(command.rotate_speed, relative_yaw, dt)
128 : command.rotate_speed);
129 }
130 }
131 const bool permitted = primary_permission;
132 if (info_.enable_shoot) {
133 auto& shoot = roboctrl::get<ctrl::shoot>();
134 shoot.set_friction_enabled(command.friction_enabled);
135 shoot.set_fire_permitted(permitted);
136 shoot.set_firing(command.auto_aim ? permitted : command.firing);
137 }
138 if (auto* shoot = robot.secondary_shoot()) {
139 shoot->set_friction_enabled(command.friction_enabled);
140 shoot->set_fire_permitted(secondary_permission);
141 shoot->set_firing(command.auto_aim ? secondary_permission : command.firing);
142 }
143 auto& referee = roboctrl::get<device::referee>();
144 auto& capacitor = roboctrl::get<device::super_cap>();
145 if (referee.configured()) referee.set_ui_status({
146 .friction_ready = info_.enable_shoot && roboctrl::get<ctrl::shoot>().friction_ready(),
147 .auto_aim = command.auto_aim, .spinning = command.rotate_speed != 0.0f,
148 .fire_permitted = (info_.enable_shoot && roboctrl::get<ctrl::shoot>().fire_allowed()) ||
149 (robot.secondary_shoot() && robot.secondary_shoot()->fire_allowed()),
150 .capacitor_percent = capacitor.configured() && !capacitor.offline() ?
151 float(capacitor.energy()) / 255.0f * 100.0f : 0.0f});
152}
153
154awaitable<void> motion_control::send_telemetry() {
155 const auto now = std::chrono::steady_clock::now();
156 auto& referee = roboctrl::get<device::referee>();
157 const auto& data = referee.data();
158 const bool identity_fresh = referee.configured() && data.robot.fresh(now, referee.timeout());
159 const bool red = identity_fresh ? data.robot.value.robot_id < 100 : info_.red_team;
160 if (primary_aim_ && gimbal_ && gimbal_->online())
161 co_await primary_aim_->send_posture(gimbal_->yaw(), gimbal_->pitch(), red);
162 if (secondary_aim_ && secondary_gimbal_ && secondary_gimbal_->online())
163 co_await secondary_aim_->send_posture(secondary_gimbal_->yaw(), secondary_gimbal_->pitch(), red);
164 if (navigation_) {
165 const auto* heading = large_yaw_ ? large_yaw_ : gimbal_;
166 const float hp = identity_fresh && data.robot.value.max_hp > 0 ?
167 std::clamp(float(data.robot.value.hp) / data.robot.value.max_hp, 0.0f, 1.0f) : 0.0f;
168 const bool started = referee.configured() && data.game.fresh(now, referee.timeout()) &&
169 data.game.value.progress == 4;
170 if (heading && heading->online()) co_await navigation_->send_status(heading->yaw(), hp, started);
171 }
172}
173
174awaitable<void> motion_control::send_debug_telemetry() {
175 if (!remote_logger_) co_return;
176 if (gimbal_) {
177 co_await remote_logger_->push_value("gimbal.yaw.target", gimbal_->target_yaw());
178 co_await remote_logger_->push_value("gimbal.yaw.measured", gimbal_->yaw());
179 co_await remote_logger_->push_value("gimbal.yaw.rate", gimbal_->yaw_rate());
180 co_await remote_logger_->push_value("gimbal.pitch.target", gimbal_->target_pitch());
181 co_await remote_logger_->push_value("gimbal.pitch.measured", gimbal_->pitch());
182 }
183 const auto& power = roboctrl::get<power_manager>().status();
184 co_await remote_logger_->push_value("chassis.power.requested", power.requested_power);
185 co_await remote_logger_->push_value("chassis.power.allocated", power.allocated_power);
186 co_await remote_logger_->push_value("robot.state", static_cast<int>(roboctrl::get<robot>().state()));
187 if (info_.enable_shoot) {
188 const auto& shoot = roboctrl::get<ctrl::shoot>();
189 co_await remote_logger_->push_value("shoot.firing", shoot.firing());
190 co_await remote_logger_->push_value("shoot.friction_ready", shoot.friction_ready());
191 co_await remote_logger_->push_value("shoot.fire_allowed", shoot.fire_allowed());
192 }
193}
194
195awaitable<void> motion_control::task() {
196 auto previous = std::chrono::steady_clock::now();
197 auto telemetry_at = previous;
198 auto debug_at = previous;
199 while (true) {
200 const auto now = std::chrono::steady_clock::now();
201 const auto elapsed = now - previous;
202 const fp32 dt = elapsed > std::chrono::steady_clock::duration::zero() && elapsed <= info_.control_time * 5
203 ? std::chrono::duration<fp32>(elapsed).count() : 0.f;
204 previous = now;
205 auto& robot = roboctrl::get<ctrl::robot>();
206 const bool remote_online = !roboctrl::get<device::control_pad>(info_.control_pad_key).offline();
207 bool fresh_input = false;
208 if (!remote_online) {
209 robot.set_state(robot_state::NoForce);
210 mapper_.reset();
211 command_ = {};
212 input_pending_ = false;
213 } else if (input_pending_) {
214 input_pending_ = false;
215 fresh_input = true;
216 command_ = mapper_.update(input_);
217 if (command_.arm_requested && robot.state() == robot_state::NoForce)
218 robot.set_state(gimbal_ ? robot_state::FinishInit : robot_state::FollowGimbal);
219 }
220 auto command = command_;
221 // Mouse deltas are per received packet, unlike the held RC stick.
222 if (mapper_.keyboard_mode() && !fresh_input) command.yaw_delta = command.pitch_delta = 0.f;
223 if (robot.state() == robot_state::FinishInit && robot.gimbals_initialized())
224 robot.set_state(robot_state::FollowGimbal);
225 else if (robot.state() != robot_state::NoForce && robot.state() != robot_state::FinishInit &&
226 !robot.gimbals_online()) robot.set_state(robot_state::NoForce);
227 const bool output_allowed = robot.state() != robot_state::NoForce &&
228 robot.state() != robot_state::FinishInit && robot.state() != robot_state::Idle;
229 if (output_allowed) dispatch(command, dt);
230 else stop_outputs();
231 if (chassis_) co_await roboctrl::get<power_manager>().update(*chassis_, output_allowed, dt);
232 if (now >= telemetry_at) {
233 co_await send_telemetry();
234 telemetry_at = now + 20ms;
235 }
236 if (now >= debug_at) {
237 co_await send_debug_telemetry();
238 debug_at = now + info_.telemetry_period;
239 }
240 co_await roboctrl::wait_for(info_.control_time);
241 }
242}
243} // namespace roboctrl::ctrl
void reset() noexcept
清除解锁、按键边沿和摩擦轮状态。
control_command update(const device::control_pad_state &input)
将一个遥控器输入快照转换为控制命令。
virtual void set_rotate_speed(fp32)=0
设置绕 z 轴的旋转速度。
virtual void set_planar_velocity(vectorf)=0
设置平面速度 (x, y),单位由具体底盘约定为 m/s。
virtual fp32 yaw() const =0
获取当前 yaw 姿态,单位 rad。
virtual void set_target_yaw(fp32)=0
设置 yaw 绝对目标,单位 rad。
virtual fp32 pitch() const =0
获取当前 pitch 姿态,单位 rad。
virtual fp32 relative_yaw() const =0
用于实现多例模式的通用组件。
awaitable< void > wait_for(const duration &duration)
协程任务等待。
Definition async.hpp:262
auto spawn(task_context::task_type &&task)
添加一个协程任务到全局任务上下文中执行。
Definition async.hpp:191
auto now()
记录程序启动后的纳秒级时间戳。
Definition utils.hpp:158
机器人级状态与子系统生命周期编排。
robot_state
机器人控制状态。
Definition robot.h:26
发射器和拨弹机构控制。
float fp32
单精度浮点别名。
Definition utils.hpp:22