7#include <initializer_list>
13#include <unordered_map>
14#include <unordered_set>
18#include "device/imu/serial_imu.hpp"
20#include "device/motor/j6006.h"
21#include "device/motor/m9025.h"
25namespace roboctrl::config {
27template<
typename Info>
28void validate_unique_keys(std::string_view kind, std::span<const Info> infos) {
29 std::unordered_set<typename Info::key_type> keys;
30 for (
const auto& info : infos) {
31 const auto key = info.key();
32 if constexpr (std::convertible_to<
decltype(key), std::string_view>) {
33 if (std::string_view{key}.empty())
34 throw std::invalid_argument(std::format(
"{} key must not be empty", kind));
36 if (!keys.emplace(key).second)
37 throw std::invalid_argument(std::format(
"duplicate {} key {}", kind, key));
41template<
typename Info>
42void validate_unique_keys(std::string_view kind, std::initializer_list<Info> infos) {
43 validate_unique_keys(kind, std::span{infos.begin(), infos.size()});
46inline bool valid_pid(
const auto& pid,
float max_command = std::numeric_limits<float>::max()) {
47 return std::isfinite(pid.kp) && std::isfinite(pid.ki) && std::isfinite(pid.kd) &&
48 std::isfinite(pid.max_out) && std::isfinite(pid.max_iout) &&
49 pid.max_out >= 0 && pid.max_iout >= 0 &&
50 pid.max_out <= max_command && pid.max_iout <= max_command;
54inline void validate_configuration(
55 std::span<const io::can::info_type> cans,
56 std::span<const io::serial::info_type> serials,
57 std::span<const device::dji_motor::info_type> motors,
58 const device::control_pad::info_type& control_pad,
59 const device::serial_imu::info_type& imu,
60 const ctrl::robot::info_type& robot,
61 std::span<const device::serial_imu::info_type> additional_imus = {},
62 std::span<const device::j6006::info_type> j6006_motors = {},
63 std::span<const device::m9025::info_type> m9025_motors = {})
65 const auto require = [](
bool condition,
const std::string& message) {
66 if (!condition)
throw std::invalid_argument(message);
68 validate_unique_keys(
"CAN", cans);
69 validate_unique_keys(
"serial", serials);
70 validate_unique_keys(
"DJI motor", motors);
71 validate_unique_keys(
"J6006 motor", j6006_motors);
72 validate_unique_keys(
"M9025 motor", m9025_motors);
73 std::unordered_set<std::string> can_names, can_interfaces;
74 std::unordered_map<std::string, bool> serial_names;
75 std::unordered_set<std::string> serial_devices;
76 for (
const auto& can : cans) {
77 require(!can.interface_name.empty(),
"CAN interface_name must not be empty");
78 require(can_interfaces.insert(can.interface_name).second,
"multiple CAN keys reference the same physical interface");
79 can_names.insert(can.name);
81 for (
const auto& serial : serials) {
82 require(!serial.device.empty() && serial.baud_rate > 0,
"serial has invalid device or baud rate");
83 require(serial_devices.insert(serial.device).second,
"multiple serial instances reference the same physical device");
84 serial_names.emplace(serial.name, serial.raw);
86 require(!control_pad.name.empty() && serial_names.contains(control_pad.serial_name),
87 "control pad name or serial reference is invalid");
88 require(!serial_names.at(control_pad.serial_name),
"control pad requires a keyed serial");
89 require(robot.control_pad_key == control_pad.key(),
"robot references missing control pad");
90 std::unordered_set<std::string> imu_names, imu_channels;
91 const auto check_imu = [&](
const auto& item) {
92 require(!item.name.empty() && imu_names.insert(item.name).second,
"duplicate or empty IMU key");
93 require(serial_names.contains(item.serial_name) && !serial_names.at(item.serial_name),
94 "IMU requires an existing keyed serial");
95 require(imu_channels.insert(item.serial_name).second,
"two IMUs occupy serial key 1 on same serial");
96 const auto sign = [](
float value) {
return value == 1.f || value == -1.f; };
97 require(sign(item.roll_sign) && sign(item.pitch_sign) && sign(item.yaw_sign) &&
98 sign(item.roll_rate_sign) && sign(item.pitch_rate_sign) && sign(item.yaw_rate_sign) &&
99 std::isfinite(item.gyro_scale) && item.gyro_scale > 0,
"invalid IMU signs or gyro scale");
102 for (
const auto& item : additional_imus) check_imu(item);
104 std::unordered_map<std::string, std::string> motor_types;
105 std::unordered_set<std::string> receive_slots, command_slots, command_ids;
106 const auto add_motor = [&](
const auto& motor, std::string driver) {
107 require(!motor.name.empty() && motor_types.emplace(motor.name, driver).second,
108 "motor names must be unique across driver types");
109 require(can_names.contains(motor.can_name),
"motor references missing CAN: " + motor.name);
110 require(std::isfinite(motor.radius) && motor.radius > 0 &&
111 motor.control_time > std::chrono::steady_clock::duration::zero(),
112 "motor radius or period invalid: " + motor.name);
114 const auto rx = [&](
const std::string& bus,
unsigned id) {
115 require(receive_slots.insert(std::format(
"{}:{}", bus,
id)).second,
116 "conflicting motor feedback CAN ID");
118 for (
const auto& motor : motors) {
119 add_motor(motor,
"dji");
121 (motor.type_ == device::dji_motor::M2006 || motor.type_ == device::dji_motor::M3508 ||
122 motor.type_ == device::dji_motor::M6020),
"invalid DJI model/id");
124 const bool gimbal = motor.type_ == device::dji_motor::M6020;
125 rx(motor.can_name, (gimbal ? 0x204 : 0x200) + motor.id);
126 const unsigned command = gimbal ? (motor.id <= 4 ? 0x1ff : 0x2ff) :
127 (motor.id <= 4 ? 0x200 : 0x1ff);
128 const auto slot = std::format(
"{}:{}:{}", motor.can_name, command, (motor.id - 1) % 4);
129 require(command_slots.insert(slot).second,
"conflicting DJI command slot");
130 command_ids.insert(std::format(
"{}:{}", motor.can_name, command));
132 std::unordered_set<std::string> j6006_receive_slots, j6006_controller_slots;
133 for (
const auto& motor : j6006_motors) {
134 add_motor(motor,
"j6006");
135 require(device::j6006::valid_configuration(motor),
"invalid J6006 configuration");
136 const auto slot = std::format(
"{}:{}", motor.can_name, motor.master_id);
138 require(!receive_slots.contains(slot) || j6006_receive_slots.contains(slot),
139 "J6006 feedback overlaps another motor protocol");
140 receive_slots.insert(slot);
141 j6006_receive_slots.insert(slot);
142 require(j6006_controller_slots.insert(std::format(
"{}:{}", motor.can_name, motor.id)).second,
143 "duplicate J6006 controller ID on bus");
144 require(command_ids.insert(std::format(
"{}:{}", motor.can_name, 0x200 + motor.id)).second,
145 "J6006 command conflicts with another motor protocol");
147 for (
const auto& motor : m9025_motors) {
148 add_motor(motor,
"m9025");
149 require(device::m9025::valid_configuration(motor),
"invalid M9025 configuration");
150 rx(motor.can_name, 0x140 + motor.id);
151 require(command_ids.insert(std::format(
"{}:{}", motor.can_name, 0x140 + motor.id)).second,
152 "M9025 command conflicts with another motor protocol");
155 for (
const auto& motor : j6006_motors) {
156 require(!command_ids.contains(std::format(
"{}:{}", motor.can_name, motor.master_id)),
157 "J6006 feedback overlaps a motor command ID");
158 require(!receive_slots.contains(std::format(
"{}:{}", motor.can_name, 0x200 + motor.id)),
159 "J6006 command overlaps a motor feedback ID");
162 std::unordered_set<std::string> controlled_motors;
163 const auto bind_motor = [&](
const std::string& name,
const std::string& type) {
164 require(motor_types.contains(name) && motor_types.at(name) == type,
165 "missing motor or driver mismatch: " + name);
166 require(controlled_motors.insert(name).second,
"actuator has multiple controllers: " + name);
168 if (robot.enable_chassis) {
169 for (
int direction : robot.chassis_info.wheel_directions)
170 require(direction == 1 || direction == -1,
"wheel directions must be +1 or -1");
171 for (
const auto* name : {&robot.chassis_info.left_front_motor, &robot.chassis_info.right_front_motor,
172 &robot.chassis_info.left_rear_motor, &robot.chassis_info.right_rear_motor})
173 bind_motor(*name,
"dji");
174 require(robot.chassis_info.control_time > std::chrono::steady_clock::duration::zero() &&
175 std::isfinite(robot.chassis_info.max_rotate_speed) && robot.chassis_info.max_rotate_speed > 0,
176 "chassis has invalid control parameters");
178 const auto check_gimbal = [&](
const auto& gimbal) {
179 bind_motor(gimbal.yaw_motor_key, gimbal.yaw_motor_type);
180 if (!gimbal.yaw_only) bind_motor(gimbal.pitch_motor_key, gimbal.pitch_motor_type);
181 require(imu_names.contains(gimbal.imu_key),
"gimbal references missing IMU");
182 require(!(gimbal.yaw_current_control && gimbal.yaw_motor_type ==
"j6006") &&
183 !(!gimbal.yaw_only && gimbal.pitch_current_control && gimbal.pitch_motor_type ==
"j6006"),
184 "J6006 firmware speed mode cannot accept a direct current target");
185 require(gimbal.control_time > std::chrono::steady_clock::duration::zero() &&
186 gimbal.init_settle_time >= std::chrono::steady_clock::duration::zero() &&
187 std::isfinite(gimbal.yaw_zero) && std::isfinite(gimbal.init_tolerance) && gimbal.init_tolerance > 0 &&
188 std::isfinite(gimbal.init_pitch) && std::isfinite(gimbal.pitch_min) && std::isfinite(gimbal.pitch_max) &&
189 gimbal.pitch_min <= gimbal.pitch_max && gimbal.init_pitch >= gimbal.pitch_min && gimbal.init_pitch <= gimbal.pitch_max &&
190 (gimbal.yaw_direction == 1 || gimbal.yaw_direction == -1) &&
191 (gimbal.yaw_angle_direction == 1 || gimbal.yaw_angle_direction == -1) &&
192 (gimbal.yaw_recenter_direction == 1 || gimbal.yaw_recenter_direction == -1) &&
193 (gimbal.pitch_direction == 1 || gimbal.pitch_direction == -1) &&
194 valid_pid(gimbal.yaw_angle_pid) && valid_pid(gimbal.pitch_angle_pid) && valid_pid(gimbal.yaw_relative_pid) &&
195 valid_pid(gimbal.yaw_rate_pid, 32767) && valid_pid(gimbal.pitch_rate_pid, 32767),
196 "gimbal has invalid control parameters");
198 require(robot.enable_gimbal || (!robot.secondary_gimbal_info && !robot.large_yaw_info),
199 "additional gimbals require enable_gimbal");
200 if (robot.enable_gimbal) {
201 check_gimbal(robot.gimbal_info);
202 if (robot.secondary_gimbal_info) check_gimbal(*robot.secondary_gimbal_info);
203 if (robot.large_yaw_info) check_gimbal(*robot.large_yaw_info);
205 const auto check_shoot = [&](
const auto& shoot) {
206 bind_motor(shoot.left_friction_motor,
"dji");
207 bind_motor(shoot.right_friction_motor,
"dji");
208 bind_motor(shoot.trigger_motor,
"dji");
209 require(shoot.control_time > std::chrono::steady_clock::duration::zero() &&
210 shoot.jam_release_time >= std::chrono::steady_clock::duration::zero() &&
211 std::isfinite(shoot.friction_params.acc) && shoot.friction_params.acc >= 0 &&
212 std::isfinite(shoot.friction_max_speed) && shoot.friction_max_speed >= 0 &&
213 std::isfinite(shoot.trigger_speed) && std::isfinite(shoot.friction_ready_speed) && shoot.friction_ready_speed > 0 && shoot.friction_ready_speed <= shoot.friction_max_speed &&
214 std::isfinite(shoot.jam_current) && shoot.jam_current >= 0 &&
215 std::isfinite(shoot.jam_speed) && shoot.jam_speed >= 0 &&
216 (shoot.bullet_caliber == 17 || shoot.bullet_caliber == 42),
"shoot has invalid control parameters");
218 require(!robot.secondary_shoot_info || (robot.enable_shoot && robot.secondary_gimbal_info),
219 "secondary shoot requires enabled shoot and secondary gimbal");
220 if (robot.enable_shoot) {
221 check_shoot(robot.shoot_info);
222 if (robot.secondary_shoot_info) check_shoot(*robot.secondary_shoot_info);
224 const auto& motion = robot.motion_info;
225 require(motion.control_time > std::chrono::steady_clock::duration::zero() && valid_pid(motion.follow_pid) &&
226 (motion.follow_direction == 1 || motion.follow_direction == -1) &&
227 std::isfinite(motion.spin_recenter_speed) && motion.spin_recenter_speed >= 0 &&
228 std::isfinite(motion.follow_tolerance) && motion.follow_tolerance > 0 &&
229 std::isfinite(motion.search_yaw_speed) && std::isfinite(motion.search_pitch_speed) &&
230 std::isfinite(motion.search_pitch_amplitude) && motion.search_pitch_amplitude >= 0 &&
231 std::isfinite(motion.search_pitch_center),
"invalid motion control parameters");
234inline void validate_configuration(
235 std::initializer_list<io::can::info_type> cans,
236 std::initializer_list<io::serial::info_type> serials,
237 std::initializer_list<device::dji_motor::info_type> motors,
238 const device::control_pad::info_type& control_pad,
239 const device::serial_imu::info_type& imu,
240 const ctrl::robot::info_type& robot)
242 validate_configuration(std::span{cans.begin(), cans.size()}, std::span{serials.begin(), serials.size()},
243 std::span{motors.begin(), motors.size()}, control_pad, imu, robot);
基于 Linux SocketCAN 的 CAN 总线封装。
static constexpr int max_device_id(type motor_type) noexcept
协议允许的最大设备 ID。未知型号返回 0。
static constexpr fp32 command_current_limit(type motor_type) noexcept
DJI 电调命令字段的型号级绝对值上限。未知型号返回 0。