1#include "ctrl/motion_control.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"
12namespace roboctrl::ctrl {
14bool motion_control::init(
const info_type& 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) {
29 input_pending_ =
true;
35void motion_control::stop_outputs() {
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);
49 if (
auto* shoot = robot.secondary_shoot()) {
50 shoot->set_firing(
false);
51 shoot->set_fire_permitted(
false);
52 shoot->set_friction_enabled(
false);
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({});
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;
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);
74 if (robot.state() != robot_state::Search) {
76 robot.set_state(robot_state::Search);
78 search_elapsed_ += dt;
79 }
else if (robot.state() == robot_state::Search) robot.set_state(robot_state::FollowGimbal);
81 const auto aim = [&](device::gimbal_base* gimbal,
const auto& target) {
82 if (!gimbal)
return false;
83 if (command.auto_aim) {
85 gimbal->set_target_yaw(target->yaw);
86 gimbal->set_target_pitch(target->pitch);
87 return target->fire && gimbal->initialized() && gimbal->online();
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();
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();
101 const bool primary_permission = gimbal_ ? aim(gimbal_, primary_target) : !command.auto_aim;
102 const bool secondary_permission = aim(secondary_gimbal_, secondary_target);
106 if (large_yaw_ && gimbal_ && gimbal_->relative_yaw_valid())
108 info_.large_yaw_follow_direction * gimbal_->
relative_yaw());
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{};
116 auto* heading = large_yaw_ ? large_yaw_ : gimbal_;
117 if (heading && !heading->relative_yaw_valid()) {
124 const auto relative_yaw = heading ? heading->relative_yaw() : 0.0f;
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);
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);
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);
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});
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);
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);
174awaitable<void> motion_control::send_debug_telemetry() {
175 if (!remote_logger_)
co_return;
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());
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());
195awaitable<void> motion_control::task() {
196 auto previous = std::chrono::steady_clock::now();
197 auto telemetry_at = previous;
198 auto debug_at = previous;
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;
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);
212 input_pending_ =
false;
213 }
else if (input_pending_) {
214 input_pending_ =
false;
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);
220 auto command = command_;
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);
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;
236 if (now >= debug_at) {
237 co_await send_debug_telemetry();
238 debug_at =
now + info_.telemetry_period;
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)
协程任务等待。
auto spawn(task_context::task_type &&task)
添加一个协程任务到全局任务上下文中执行。
auto now()
记录程序启动后的纳秒级时间戳。