GKD.RoboCtrl
RoboMaster Linux 电控:异步 IO、设备驱动与机器人控制
载入中...
搜索中...
未找到
power_manager.cpp
1#include "ctrl/power_manager.h"
2
3#include <algorithm>
4#include <cmath>
5#include <limits>
6
9
10namespace roboctrl::ctrl {
11namespace {
12
13bool nonnegative(float value) { return std::isfinite(value) && value >= 0.0f; }
14bool positive(float value) { return std::isfinite(value) && value > 0.0f; }
15
16bool valid_wheel(const power_wheel& wheel) {
17 return wheel.online && std::isfinite(wheel.requested_current) &&
18 std::isfinite(wheel.measured_current) && std::isfinite(wheel.angular_speed) &&
19 std::isfinite(wheel.target_angular_speed) && positive(wheel.max_current);
20}
21
22} // namespace
23
24bool power_manager::valid_configuration(const info_type& info) {
25 return positive(info.configured_power_limit) && positive(info.offline_power_limit) &&
26 info.offline_power_limit <= info.configured_power_limit &&
27 nonnegative(info.max_cap_boost) && positive(info.torque_per_current) &&
28 nonnegative(info.speed_loss) && nonnegative(info.torque_loss) &&
29 nonnegative(info.constant_loss) && nonnegative(info.error_weight_low) &&
30 positive(info.error_weight_high) && info.error_weight_high > info.error_weight_low &&
31 nonnegative(info.cap_base_energy) && positive(info.cap_full_energy) &&
32 info.cap_base_energy <= info.cap_full_energy && info.cap_full_energy <= 255.0f &&
33 nonnegative(info.buffer_base_energy) && positive(info.buffer_full_energy) &&
34 info.buffer_base_energy <= info.buffer_full_energy &&
35 nonnegative(info.energy_kp) && nonnegative(info.energy_kd) &&
36 positive(info.rls_forgetting_factor) && info.rls_forgetting_factor <= 1.0f &&
37 positive(info.rls_initial_covariance) && positive(info.rls_period) &&
38 positive(info.max_speed_loss) && positive(info.max_torque_loss) &&
39 info.speed_loss <= info.max_speed_loss && info.torque_loss <= info.max_torque_loss;
40}
41
42bool power_manager::init(const info_type& info) {
43 if (!valid_configuration(info)) return false;
44 info_ = info;
45 configured_ = true;
46 speed_loss_ = info.speed_loss;
47 torque_loss_ = info.torque_loss;
48 covariance_ = {{{info.rls_initial_covariance, 0.0}, {0.0, info.rls_initial_covariance}}};
49 rls_elapsed_ = 0.0f;
50 last_measurement_sequence_ = 0;
51 rls_updates_ = 0;
52 status_ = {};
53 reset_energy();
54 return true;
55}
56
57void power_manager::reset_energy() {
58 energy_source_ = 0;
59 previous_base_error_ = previous_full_error_ = 0.0f;
60}
61
62float power_manager::energy_limit(const power_feedback& feedback, float dt_seconds) {
63 const bool referee_online = feedback.referee_online && nonnegative(feedback.referee_power_limit);
64 const bool cap_online = feedback.cap_online && nonnegative(feedback.cap_power_limit) &&
65 nonnegative(feedback.cap_energy) && feedback.cap_energy <= 255.0f;
66 const bool buffer_online = referee_online && feedback.buffer_online &&
67 nonnegative(feedback.buffer_energy);
68 status_.telemetry_offline = !referee_online && !cap_online;
69 if (status_.telemetry_offline) {
70 reset_energy();
71 return info_.offline_power_limit;
72 }
73
74 // A fresh referee limit always takes precedence over a capacitor echo.
75 const float source_limit = referee_online ? feedback.referee_power_limit : feedback.cap_power_limit;
76 const float ceiling = std::min(info_.configured_power_limit,
77 source_limit + (cap_online ? info_.max_cap_boost : 0.0f));
78 unsigned source = cap_online ? 1U : buffer_online ? 2U : 0U;
79 if (!source) {
80 reset_energy();
81 return std::min(ceiling, source_limit);
82 }
83 const float energy = cap_online ? feedback.cap_energy : feedback.buffer_energy;
84 const float base_target = cap_online ? info_.cap_base_energy : info_.buffer_base_energy;
85 const float full_target = cap_online ? info_.cap_full_energy : info_.buffer_full_energy;
86 const float base_error = std::sqrt(base_target) - std::sqrt(energy);
87 const float full_error = std::sqrt(full_target) - std::sqrt(energy);
88 const bool derivative_valid = energy_source_ == source;
89 const float base_pd = info_.energy_kp * base_error +
90 (derivative_valid ? info_.energy_kd * (base_error - previous_base_error_) / dt_seconds : 0.0f);
91 const float full_pd = info_.energy_kp * full_error +
92 (derivative_valid ? info_.energy_kd * (full_error - previous_full_error_) / dt_seconds : 0.0f);
93 previous_base_error_ = base_error;
94 previous_full_error_ = full_error;
95 energy_source_ = source;
96 const float base_limit = std::clamp(source_limit - base_pd, 0.0f, ceiling);
97 const float full_limit = std::clamp(source_limit - full_pd, 0.0f, ceiling);
98 // Preserve the two legacy energy thresholds without forcing a minimum draw
99 // when the buffer is empty. The configured limit competes with both bounds.
100 return std::clamp(ceiling, std::min(base_limit, full_limit), std::max(base_limit, full_limit));
101}
102
103void power_manager::update_model(const std::array<power_wheel, 4>& wheels,
104 const power_feedback& feedback, float dt_seconds) {
105 rls_elapsed_ = std::min(rls_elapsed_ + dt_seconds, info_.rls_period);
106 if (!info_.rls_enabled || !feedback.cap_online ||
107 !std::isfinite(feedback.measured_power) || feedback.measured_power <= 5.0f ||
108 feedback.measurement_sequence == 0 ||
109 feedback.measurement_sequence == last_measurement_sequence_ ||
110 rls_elapsed_ < info_.rls_period) return;
111
112 last_measurement_sequence_ = feedback.measurement_sequence;
113 rls_elapsed_ = 0.0f;
114 std::array<double, 2> samples {};
115 double mechanical_power = 0.0;
116 for (const auto& wheel : wheels) {
117 const double torque = wheel.measured_current * info_.torque_per_current;
118 mechanical_power += torque * wheel.angular_speed;
119 samples[0] += std::abs(wheel.angular_speed);
120 samples[1] += torque * torque;
121 }
122 const double loss = feedback.measured_power - mechanical_power - info_.constant_loss;
123 if (!std::isfinite(loss) || loss < 0.0 || samples[0] + samples[1] < 1e-9) return;
124 const std::array<double, 2> px {
125 covariance_[0][0] * samples[0] + covariance_[0][1] * samples[1],
126 covariance_[1][0] * samples[0] + covariance_[1][1] * samples[1]};
127 const double denominator = info_.rls_forgetting_factor + samples[0] * px[0] + samples[1] * px[1];
128 if (!std::isfinite(denominator) || denominator <= 1e-12) return;
129 const double residual = loss - samples[0] * speed_loss_ - samples[1] * torque_loss_;
130 const double new_speed_loss = speed_loss_ + px[0] / denominator * residual;
131 const double new_torque_loss = torque_loss_ + px[1] / denominator * residual;
132 if (!std::isfinite(new_speed_loss) || !std::isfinite(new_torque_loss) ||
133 new_speed_loss < 0.0 || new_speed_loss > info_.max_speed_loss ||
134 new_torque_loss < 0.0 || new_torque_loss > info_.max_torque_loss) return;
135
136 auto next = covariance_;
137 for (std::size_t row = 0; row < 2; ++row) {
138 for (std::size_t col = 0; col < 2; ++col) {
139 next[row][col] = (covariance_[row][col] - px[row] * px[col] / denominator) /
140 info_.rls_forgetting_factor;
141 if (!std::isfinite(next[row][col]) || std::abs(next[row][col]) > 1e12) return;
142 }
143 }
144 if (next[0][0] < 0.0 || next[1][1] < 0.0 ||
145 next[0][0] * next[1][1] < next[0][1] * next[1][0] - 1e-10) return;
146 covariance_ = next;
147 speed_loss_ = static_cast<float>(new_speed_loss);
148 torque_loss_ = static_cast<float>(new_torque_loss);
149 ++rls_updates_;
150}
151
152power_allocation power_manager::allocate(const std::array<power_wheel, 4>& wheels,
153 const power_feedback& feedback, bool output_allowed,
154 float dt_seconds) {
155 status_ = {};
156 status_.rls_updates = rls_updates_;
157 status_.speed_loss = speed_loss_;
158 status_.torque_loss = torque_loss_;
159 if (!output_allowed || !positive(dt_seconds) || dt_seconds > 1.0f) {
160 reset_energy();
161 return status_;
162 }
163 status_.motor_fault = !std::all_of(wheels.begin(), wheels.end(), valid_wheel);
164 if (status_.motor_fault) {
165 reset_energy();
166 return status_;
167 }
168 if (!enabled()) {
169 for (std::size_t i = 0; i < wheels.size(); ++i) status_.current_limits[i] = wheels[i].max_current;
170 return status_;
171 }
172
173 update_model(wheels, feedback, dt_seconds);
174 status_.speed_loss = speed_loss_;
175 status_.torque_loss = torque_loss_;
176 status_.rls_updates = rls_updates_;
177 status_.power_limit = energy_limit(feedback, dt_seconds);
178 status_.measured_power = feedback.cap_online && std::isfinite(feedback.measured_power)
179 ? feedback.measured_power : 0.0f;
180 std::array<double, 4> demand {}, error {}, allocated {};
181 double baseline = info_.constant_loss;
182 double sum_demand = 0.0, sum_error = 0.0;
183 for (std::size_t i = 0; i < wheels.size(); ++i) {
184 const auto& wheel = wheels[i];
185 const double current = std::min(std::abs(wheel.requested_current), wheel.max_current);
186 const double torque = current * info_.torque_per_current;
187 // No regenerative credit: this is an upper envelope valid for either
188 // current sign until the next tick, unlike the signed legacy estimate.
189 demand[i] = torque * std::abs(wheel.angular_speed) + torque_loss_ * torque * torque;
190 baseline += speed_loss_ * std::abs(wheel.angular_speed);
191 error[i] = std::abs(wheel.target_angular_speed - wheel.angular_speed);
192 sum_demand += demand[i];
193 if (demand[i] > 0.0) sum_error += error[i];
194 }
195 status_.requested_power = static_cast<float>(baseline + sum_demand);
196 if (!std::isfinite(status_.requested_power) || !nonnegative(status_.power_limit)) {
197 status_.motor_fault = true;
198 return status_;
199 }
200 if (baseline > status_.power_limit) {
201 // Zero current cannot remove existing coasting/friction losses.
202 status_.budget_unachievable = true;
203 status_.limited = true;
204 status_.allocated_power = static_cast<float>(baseline);
205 return status_;
206 }
207
208 double remaining = std::max(0.0, static_cast<double>(status_.power_limit) - baseline);
209 const double confidence = std::clamp((sum_error - info_.error_weight_low) /
210 (info_.error_weight_high - info_.error_weight_low), 0.0, 1.0);
211 std::array<double, 4> weights {};
212 for (std::size_t i = 0; i < wheels.size(); ++i) {
213 weights[i] = (sum_error > 0.0 ? confidence * error[i] / sum_error : 0.0) +
214 (sum_demand > 0.0 ? (1.0 - confidence) * demand[i] / sum_demand : 0.0);
215 }
216 // Water filling redistributes unused wheel budgets; at most four caps can
217 // saturate. A final proportional pass handles zero-error demand safely.
218 for (unsigned pass = 0; pass < 5 && remaining > 1e-9; ++pass) {
219 double weight_sum = 0.0;
220 for (std::size_t i = 0; i < wheels.size(); ++i)
221 if (demand[i] > allocated[i] + 1e-9) weight_sum += pass == 4 ? demand[i] : weights[i];
222 if (weight_sum <= 0.0) continue;
223 const double available = remaining;
224 for (std::size_t i = 0; i < wheels.size(); ++i) {
225 if (demand[i] <= allocated[i] + 1e-9) continue;
226 const double weight = pass == 4 ? demand[i] : weights[i];
227 const double grant = std::min(demand[i] - allocated[i], available * weight / weight_sum);
228 allocated[i] += grant;
229 remaining -= grant;
230 }
231 }
232
233 double total = baseline;
234 for (std::size_t i = 0; i < wheels.size(); ++i) {
235 const double speed = std::abs(wheels[i].angular_speed);
236 const double requested = std::min(std::abs(wheels[i].requested_current), wheels[i].max_current);
237 double current = requested;
238 if (allocated[i] < demand[i]) {
239 const double torque = torque_loss_ > 0.0f
240 ? 2.0 * allocated[i] / (speed + std::sqrt(speed * speed + 4.0 * torque_loss_ * allocated[i]) + 1e-30)
241 : speed > 0.0 ? allocated[i] / speed : 0.0;
242 current = std::min(requested, torque / info_.torque_per_current);
243 }
244 // Round toward zero so converting the cap to float cannot enlarge it.
245 status_.current_limits[i] = std::max(0.0f, std::nextafter(static_cast<float>(current), 0.0f));
246 const double torque = status_.current_limits[i] * info_.torque_per_current;
247 total += torque * speed + torque_loss_ * torque * torque;
248 }
249 status_.allocated_power = static_cast<float>(total);
250 status_.limited = status_.requested_power > status_.power_limit;
251 return status_;
252}
253
255 bool output_allowed, float dt_seconds) {
256 const auto motors = chassis.wheel_motors();
257 std::array<power_wheel, 4> wheels {};
258 for (std::size_t i = 0; i < motors.size(); ++i) {
259 const auto* motor = motors[i];
260 if (!motor) continue;
261 wheels[i] = {motor->requested_current(), motor->current_feedback_raw(), motor->angle_speed(),
262 motor->target_angle_speed(), motor->max_current(),
263 !motor->offline() && motor->supports_current_control()};
264 }
265 const auto allocation = allocate(wheels, feedback, output_allowed, dt_seconds);
266 for (std::size_t i = 0; i < motors.size(); ++i) {
267 if (!motors[i]) continue;
268 motors[i]->set_output_scale(1.0f);
269 motors[i]->set_current_limit(allocation.current_limits[i]);
270 }
271}
272
273} // namespace roboctrl::ctrl
void update(device::chassis_base &chassis, const power_feedback &feedback, bool output_allowed, float dt_seconds)
power_allocation allocate(const std::array< power_wheel, 4 > &wheels, const power_feedback &feedback, bool output_allowed, float dt_seconds)
底盘的统一控制接口。
Definition base.hpp:23
virtual std::array< motor_base *, 4 > wheel_motors() const
Definition base.hpp:61
底盘抽象接口与运行时注册表。
电机基础组件。