1#include "ctrl/power_manager.h"
10namespace roboctrl::ctrl {
13bool nonnegative(
float value) {
return std::isfinite(value) && value >= 0.0f; }
14bool positive(
float value) {
return std::isfinite(value) && value > 0.0f; }
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);
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;
42bool power_manager::init(
const info_type& info) {
43 if (!valid_configuration(info))
return false;
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}}};
50 last_measurement_sequence_ = 0;
57void power_manager::reset_energy() {
59 previous_base_error_ = previous_full_error_ = 0.0f;
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) {
71 return info_.offline_power_limit;
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;
81 return std::min(ceiling, source_limit);
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);
100 return std::clamp(ceiling, std::min(base_limit, full_limit), std::max(base_limit, full_limit));
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;
112 last_measurement_sequence_ = feedback.measurement_sequence;
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;
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;
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;
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;
147 speed_loss_ =
static_cast<float>(new_speed_loss);
148 torque_loss_ =
static_cast<float>(new_torque_loss);
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) {
163 status_.motor_fault = !std::all_of(wheels.begin(), wheels.end(), valid_wheel);
164 if (status_.motor_fault) {
169 for (std::size_t i = 0; i < wheels.size(); ++i) status_.current_limits[i] = wheels[i].max_current;
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;
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];
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;
200 if (baseline > status_.power_limit) {
202 status_.budget_unachievable =
true;
203 status_.limited =
true;
204 status_.allocated_power =
static_cast<float>(baseline);
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);
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;
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);
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;
249 status_.allocated_power =
static_cast<float>(total);
250 status_.limited = status_.requested_power > status_.power_limit;
255 bool output_allowed,
float dt_seconds) {
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()};
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]);
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)
virtual std::array< motor_base *, 4 > wheel_motors() const