diff --git a/CMakeLists.txt b/CMakeLists.txt
index a499c9ad..e6891245 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,7 @@
cmake_minimum_required(VERSION 3.22)
project(rmcs_auto_aim_v2)
+
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 23)
@@ -134,4 +135,8 @@ pluginlib_export_plugin_description_file(
)
find_package(ament_cmake REQUIRED)
+if(BUILD_TESTING)
+ add_subdirectory(test)
+endif()
+
ament_package()
diff --git a/config/config.yaml b/config/config.yaml
index 9145e575..c3b67eeb 100644
--- a/config/config.yaml
+++ b/config/config.yaml
@@ -14,17 +14,17 @@ capturer:
# float
exposure_us: 2000.0
# float
- framerate: 60
+ framerate: 120
# float
gain: 16.9807
invert_image: false
software_sync: false
trigger_mode: false
- fixed_framerate: true
+ fixed_framerate: false
local_video:
# 替换为你具体的路径
- location: "/workspaces/alliance/test_videos/translation.mp4"
+ location: "/workspaces/alliance/test_videos/outpost.mp4"
# double 帧率
frame_rate: 60
# bool 是否循环播放
@@ -40,20 +40,23 @@ identifier:
- "tongji-yolov5.xml"
- "shenzhen-0526.onnx"
- "shenzhen-0708.onnx"
- model_location: "shenzhen-0526.onnx"
- infer_device: "AUTO"
+ model_location: "tongji-yolov5.xml"
+ infer_device: "GPU"
use_roi_segment: false
roi_rows: 640
roi_cols: 640
input_rows: 640
input_cols: 640
min_confidence: 0.5
- score_threshold: 0.8
- nms_threshold: 0.45
+ score_threshold: 0.7
+ nms_threshold: 0.3
tracker:
# blue or red
- enemy_color: blue
+ enemy_color: red
+ max_temporary_loss_frames: 4
+ max_unconfirmed_loss_frames: 2
+ tracking_confirm_frames: 2
pose_estimator:
camera_matrix: [1.722231837421459e+03, 0, 7.013056440882832e+02, 0, 1.724876404292754e+03,5.645821718351237e+02 , 0, 0, 1]
@@ -74,21 +77,22 @@ pose_estimator:
q: [1., 0., 0., 0.]
fire_control:
- initial_bullet_speed: 20 # m/s
+ initial_bullet_speed: 26.6 # m/s
shoot_delay: 0.1 # s
- shoot_offset_x: 0.0 # m
- shoot_offset_y: 0.0 # m
- shoot_offset_z: 0.0 # m
+ yaw_offset: 0.0 # degree
+ pitch_offset: 0.0 # degree
- k: 0.019
- bias_scale: 1.0
-
- coming_angle: 60.0 # degree
+ coming_angle: 70.0 # degree
leaving_angle: 20.0 # degree
outpost_coming_angle: 70.0 # degree
outpost_leaving_angle: 30.0 # degree
angular_velocity_threshold: 120 # degree/s
+ first_tolerance: 3 # 近距离射击容差,degree
+ second_tolerance: 2 # 远距离射击容差,degree
+ judge_distance: 2 #距离判断阈值
+ auto_fire: true # 是否由自瞄控制射击
+
visualization:
framerate: 60
monitor_host: "127.0.0.1"
diff --git a/package.xml b/package.xml
index 4412f511..5534fd19 100644
--- a/package.xml
+++ b/package.xml
@@ -9,6 +9,7 @@
ament_cmake
+ ament_cmake_gtest
ament_lint_auto
ament_lint_common
diff --git a/src/component.cpp b/src/component.cpp
index f6228a59..3850cb5e 100644
--- a/src/component.cpp
+++ b/src/component.cpp
@@ -5,7 +5,9 @@
#include "utility/rclcpp/visual/transform.hpp"
#include "utility/shared/context.hpp"
+#include
#include
+#include
#include
#include
@@ -20,12 +22,11 @@ class AutoAimComponent final : public rmcs_executor::Component {
: rclcpp { get_component_name() } {
register_input("/tf", rmcs_tf);
- register_input("/referee/shooter/initial_speed", bullet_speed);
- register_output("/gimbal/auto_aim/controllable", gimbal_takeover, false);
+ register_output("/gimbal/auto_aim/auto_aim_enabled", gimbal_takeover, false);
register_output(
"/gimbal/auto_aim/control_direction", target_direction, Eigen::Vector3d::Zero());
- register_output("/gimbal/auto_aim/shoot_permit", shoot_permitted, false);
+ register_output("/gimbal/auto_aim/shoot_enable", shoot_permitted, false);
using namespace std::chrono_literals;
framerate.set_interval(2s);
@@ -39,56 +40,26 @@ class AutoAimComponent final : public rmcs_executor::Component {
visual_odom_to_camera = std::make_unique(config);
action_throttler.register_action("tf_not_ready");
- action_throttler.register_action("bullet_speed_not_ready");
action_throttler.register_action("commit_control_state_failed");
}
auto update() -> void override {
- using namespace rmcs_description;
-
if (!rmcs_tf.ready()) [[unlikely]] {
- action_throttler.dispatch("tf_not_ready", [&] { rclcpp.warn("rmcs_tf is not ready"); });
- control_state.set_identity();
- reset_control_commands();
- return;
- }
- if (!bullet_speed.ready()) [[unlikely]] {
- action_throttler.dispatch(
- "bullet_speed_not_ready", [&] { rclcpp.warn("bullet_speed is not ready"); });
- control_state.set_identity();
- reset_control_commands();
+ handle_tf_not_ready();
return;
}
- // TODO:适时交出云台和发射机构控制权
- {
- update_gimbal_direction();
- update_control_state();
-
- auto success = feishu.commit(control_state);
- if (!success) {
- action_throttler.dispatch("commit_control_state_failed",
- [&] { rclcpp.info("commit control state failed!"); });
- } else {
- action_throttler.reset("commit_control_state_failed");
- }
- }
- {
- if (feishu.updated()) {
- auto_aim_state = feishu.fetch();
- }
-
- *gimbal_takeover = auto_aim_state.gimbal_takeover;
- *shoot_permitted = auto_aim_state.shoot_permitted;
- update_target_direction();
- }
+
+ publish_control_state();
+ forward_auto_aim_outputs();
}
private:
+ static constexpr auto auto_aim_state_timeout { std::chrono::milliseconds { 100 } };
+
InputInterface rmcs_tf;
- double current_gimbal_yaw { 0. };
- double current_gimbal_pitch { 0. };
- InputInterface bullet_speed;
+ double current_gimbal_yaw { std::numeric_limits::quiet_NaN() };
+ double current_gimbal_pitch { std::numeric_limits::quiet_NaN() };
RclcppNode rclcpp;
std::unique_ptr visual_odom_to_camera;
@@ -96,6 +67,7 @@ class AutoAimComponent final : public rmcs_executor::Component {
Feishu feishu;
ControlState control_state;
AutoAimState auto_aim_state;
+ bool auto_aim_state_received_ { false };
OutputInterface gimbal_takeover;
OutputInterface shoot_permitted;
@@ -104,7 +76,73 @@ class AutoAimComponent final : public rmcs_executor::Component {
FramerateCounter framerate;
ActionThrottler action_throttler { std::chrono::seconds(1), 233 };
-private:
+ auto has_fresh_auto_aim_state() const -> bool {
+ return auto_aim_state_received_
+ && Clock::now() - auto_aim_state.timestamp <= auto_aim_state_timeout;
+ }
+
+ static auto make_invalid_auto_aim_state() -> AutoAimState {
+ auto state = AutoAimState {};
+ state.reset();
+ return state;
+ }
+
+ auto resolve_auto_aim_state() -> AutoAimState {
+ if (feishu.updated()) {
+ auto_aim_state = feishu.fetch();
+ auto_aim_state_received_ = true;
+ }
+
+ if (has_fresh_auto_aim_state()) {
+ return auto_aim_state;
+ }
+
+ return make_invalid_auto_aim_state();
+ }
+
+ auto publish_auto_aim_outputs(const AutoAimState& state) -> void {
+ *gimbal_takeover = state.gimbal_takeover;
+ *shoot_permitted = state.shoot_permitted;
+ *target_direction = compute_target_direction(state);
+ }
+
+ static auto compute_target_direction(const AutoAimState& state) -> Eigen::Vector3d {
+ if (!state.has_control_direction()) {
+ return Eigen::Vector3d::Zero();
+ }
+
+ const auto& [yaw, pitch] = std::tie(state.yaw, state.pitch);
+
+ // clang-format off
+ return Eigen::Vector3d {
+ std::cos(pitch) * std::cos(yaw),
+ std::cos(pitch) * std::sin(yaw),
+ std::sin(pitch)
+ };
+ // clang-format on
+ }
+
+ auto forward_auto_aim_outputs() -> void { publish_auto_aim_outputs(resolve_auto_aim_state()); }
+
+ auto handle_tf_not_ready() -> void {
+ action_throttler.dispatch("tf_not_ready", [&] { rclcpp.warn("rmcs_tf is not ready"); });
+ control_state.reset();
+ publish_auto_aim_outputs(make_invalid_auto_aim_state());
+ }
+
+ auto publish_control_state() -> void {
+ update_gimbal_direction();
+ update_control_state();
+
+ auto success = feishu.commit(control_state);
+ if (!success) {
+ action_throttler.dispatch("commit_control_state_failed",
+ [&] { rclcpp.info("commit control state failed!"); });
+ } else {
+ action_throttler.reset("commit_control_state_failed");
+ }
+ }
+
auto update_control_state() -> void {
control_state.timestamp = Clock::now();
@@ -123,41 +161,24 @@ class AutoAimComponent final : public rmcs_executor::Component {
// TODO:无敌状态下的装甲板需要从裁判系统获取并在此更新
control_state.invincible_devices = DeviceIds::None();
- control_state.bullet_speed = *bullet_speed;
- control_state.yaw = current_gimbal_yaw;
- control_state.pitch = current_gimbal_pitch;
- }
-
- auto update_target_direction() -> void {
- const auto& [yaw, pitch] = std::tie(auto_aim_state.yaw, auto_aim_state.pitch);
-
- // clang-format off
- *target_direction = Eigen::Vector3d {
- std::cos(pitch) * std::cos(yaw),
- std::cos(pitch) * std::sin(yaw),
- std::sin(pitch)
- };
- // clang-format on
- }
-
- auto reset_control_commands() -> void {
- *gimbal_takeover = false;
- *shoot_permitted = false;
- *target_direction = Eigen::Vector3d::Zero();
+ control_state.yaw = current_gimbal_yaw;
+ control_state.pitch = current_gimbal_pitch;
}
auto update_gimbal_direction() -> void {
- auto odom_to_muzzle_transform =
- fast_tf::lookup_transform(
+ using namespace rmcs_description;
+
+ auto odom_to_pitch_transform =
+ fast_tf::lookup_transform(
*rmcs_tf);
- auto quat = Eigen::Quaterniond { odom_to_muzzle_transform.rotation() };
+ auto quat = Eigen::Quaterniond { odom_to_pitch_transform.toRotationMatrix() };
- auto current_muzzle_direction = quat * Eigen::Vector3d::UnitX();
+ auto current_pitch_direction = quat * Eigen::Vector3d::UnitX();
- current_gimbal_yaw = std::atan2(current_muzzle_direction.y(), current_muzzle_direction.x());
- current_gimbal_pitch = std::atan2(current_muzzle_direction.z(),
- std::hypot(current_muzzle_direction.x(), current_muzzle_direction.y()));
+ current_gimbal_yaw = std::atan2(current_pitch_direction.y(), current_pitch_direction.x());
+ current_gimbal_pitch = std::atan2(current_pitch_direction.z(),
+ std::hypot(current_pitch_direction.x(), current_pitch_direction.y()));
}
};
diff --git a/src/kernel/fire_control.cpp b/src/kernel/fire_control.cpp
index 54123532..0a0ef471 100644
--- a/src/kernel/fire_control.cpp
+++ b/src/kernel/fire_control.cpp
@@ -1,9 +1,14 @@
#include "fire_control.hpp"
+#include
+#include
+#include
+#include
+
#include "module/fire_control/aim_point_chooser.hpp"
+#include "module/fire_control/shoot_evaluator.hpp"
#include "module/fire_control/trajectory_solution.hpp"
#include "module/predictor/snapshot.hpp"
-#include "utility/logging/printer.hpp"
#include "utility/math/angle.hpp"
#include "utility/serializable.hpp"
@@ -14,12 +19,8 @@ struct FireControl::Impl {
struct Config : util::Serializable {
double initial_bullet_speed; // m/s
double shoot_delay; // s
- double shoot_offset_x; // m
- double shoot_offset_y; // m
- double shoot_offset_z; // m
-
- double k; // 基础阻力系数 (小弹丸~0.019, 大弹丸~0.005)
- double bias_scale; // 动态补偿系数:修正额外阻力,阻力越大,该系数越大,default=1
+ double yaw_offset; // rad (config in degree)
+ double pitch_offset; // rad (config in degree)
double coming_angle; // rad
double leaving_angle; // rad
@@ -31,12 +32,8 @@ struct FireControl::Impl {
constexpr static std::tuple metas {
&Config::initial_bullet_speed, "initial_bullet_speed",
&Config::shoot_delay,"shoot_delay",
- &Config::shoot_offset_x,"shoot_offset_x",
- &Config::shoot_offset_y,"shoot_offset_y",
- &Config::shoot_offset_z,"shoot_offset_z",
-
- &Config::k,"k",
- &Config::bias_scale,"bias_scale",
+ &Config::yaw_offset,"yaw_offset",
+ &Config::pitch_offset,"pitch_offset",
&Config::coming_angle,"coming_angle",
&Config::leaving_angle,"leaving_angle",
@@ -49,21 +46,23 @@ struct FireControl::Impl {
Config config;
- double bullet_speed_buffer { 0. };
- double bullet_speed { 0. };
-
AimPointChooser aim_point_chooser;
+ ShootEvaluator shoot_evaluator;
- rmcs::Printer log { "FireControl" };
+ const double kMinValidBulletSpeed { 10. };
auto initialize(const YAML::Node& yaml) noexcept -> std::expected {
auto result = config.serialize(yaml);
if (!result.has_value()) {
return std::unexpected { result.error() };
}
+ if (!(config.initial_bullet_speed > kMinValidBulletSpeed)) {
+ return std::unexpected { std::format(
+ "Invalid initial_bullet_speed: {}", config.initial_bullet_speed) };
+ }
- bullet_speed = config.initial_bullet_speed;
-
+ config.yaw_offset = util::deg2rad(config.yaw_offset);
+ config.pitch_offset = util::deg2rad(config.pitch_offset);
config.coming_angle = util::deg2rad(config.coming_angle);
config.leaving_angle = util::deg2rad(config.leaving_angle);
config.outpost_coming_angle = util::deg2rad(config.outpost_coming_angle);
@@ -79,34 +78,71 @@ struct FireControl::Impl {
};
aim_point_chooser.initialize(chooser_config);
+ auto evaluate_result = shoot_evaluator.initialize(yaml);
+ if (!evaluate_result.has_value()) {
+ return std::unexpected { std::format(
+ "shoot_evaluator init failed: {}", evaluate_result.error()) };
+ }
return {};
}
const int kMaxIterateCount { 5 };
const double kMaxFlyTimeThreshold { 0.001 };
- auto set_bullet_speed(double speed) -> void { bullet_speed_buffer = speed; }
-
- auto solve(const predictor::Snapshot& snapshot, Translation const& odom_to_muzzle_translation)
+ auto make_result(const Armor3D& armor, bool control, double current_yaw)
-> std::optional {
- auto state = snapshot.ekf_x();
- auto target_position_in_world = Eigen::Vector3d { state[0], state[2], state[4] };
+ auto armor_position_in_world = Eigen::Vector3d {};
+ armor.translation.copy_to(armor_position_in_world);
+
+ auto target_d = std::sqrt(armor_position_in_world.x() * armor_position_in_world.x()
+ + armor_position_in_world.y() * armor_position_in_world.y());
+ auto target_h = armor_position_in_world.z();
+ if (!(target_d > 0.0)) {
+ return std::nullopt;
+ }
+
+ auto solution = TrajectorySolution {};
+ solution.input.v0 = config.initial_bullet_speed;
+ solution.input.target_d = target_d;
+ solution.input.target_h = target_h;
- if (bullet_speed_buffer > 10.) {
- bullet_speed = bullet_speed_buffer;
- } else {
- bullet_speed = config.initial_bullet_speed;
+ auto trajectory_result = solution.solve();
+ if (!trajectory_result) {
+ return std::nullopt;
}
- auto current_fly_time = target_position_in_world.norm() / bullet_speed;
+ auto final_yaw = std::atan2(armor_position_in_world.y(), armor_position_in_world.x());
+ final_yaw += config.yaw_offset;
+
+ auto command = ShootEvaluator::Command {
+ .control = control,
+ .auto_aim_enabled = control,
+ .aim_point_valid = true,
+ .yaw = final_yaw,
+ .distance = target_d,
+ };
+ auto shoot_permitted = shoot_evaluator.evaluate(command, current_yaw);
+ const auto final_pitch = trajectory_result->pitch + config.pitch_offset;
+
+ return Result {
+ .pitch = final_pitch,
+ .yaw = final_yaw,
+ .horizon_distance = target_d,
+ .shoot_permitted = shoot_permitted,
+ };
+ }
+
+ auto solve(const predictor::Snapshot& snapshot, bool control, double current_yaw)
+ -> std::optional {
+ auto target_kinematics = snapshot.kinematics();
+
+ // 以整车位置来初步迭代飞行时间
+ auto target_position_in_world = target_kinematics.center_position;
- auto best_armor_opt = std::optional {};
- auto trajectory_result = TrajectorySolution::Output {};
- auto horizon_distance = 0.0;
+ const double bullet_speed = config.initial_bullet_speed;
+ auto current_fly_time = target_position_in_world.norm() / bullet_speed;
- auto solution_params = fire_control::TrajectorySolution::TrajectoryParams {};
- solution_params.k = config.k;
- solution_params.bias_scale = config.bias_scale;
+ auto best_armor_opt = std::optional {};
for (int i = 0; i < kMaxIterateCount; ++i) {
// 计算预测的时间点 = 子弹飞行时间 + 系统响应延迟
@@ -115,53 +151,43 @@ struct FireControl::Impl {
+ std::chrono::duration_cast(
std::chrono::duration(total_predict_time));
- auto predicted_armors = snapshot.predicted_armors(t_target);
- auto predicted_ekf_x = snapshot.predict_at(t_target);
-
- best_armor_opt = aim_point_chooser.choose_armor(predicted_armors, predicted_ekf_x);
- if (!best_armor_opt) return std::nullopt;
+ auto predicted_armors = snapshot.predicted_armors(t_target);
+ auto predicted_kinematics = snapshot.kinematics_at(t_target);
- auto const& armor_translation = best_armor_opt->translation;
+ auto chosen_armor_opt = aim_point_chooser.choose_armor(predicted_armors,
+ predicted_kinematics.center_position, predicted_kinematics.angular_velocity);
+ if (!chosen_armor_opt) {
+ continue;
+ }
+ best_armor_opt = chosen_armor_opt;
auto armor_position_in_world = Eigen::Vector3d {};
- armor_translation.copy_to(armor_position_in_world);
-
- auto _odom_to_muzzle_translation = Eigen::Vector3d {};
- odom_to_muzzle_translation.copy_to(_odom_to_muzzle_translation);
+ best_armor_opt->translation.copy_to(armor_position_in_world);
- auto bullet_in_muzzle = armor_position_in_world - _odom_to_muzzle_translation;
-
- auto target_d = std::sqrt(bullet_in_muzzle.x() * bullet_in_muzzle.x()
- + bullet_in_muzzle.y() * bullet_in_muzzle.y());
- auto target_h = bullet_in_muzzle.z();
+ auto target_d = std::sqrt(armor_position_in_world.x() * armor_position_in_world.x()
+ + armor_position_in_world.y() * armor_position_in_world.y());
+ if (!(target_d > 0.0)) {
+ continue;
+ }
auto solution = TrajectorySolution {};
solution.input.v0 = bullet_speed;
solution.input.target_d = target_d;
- solution.input.target_h = target_h;
- solution.input.params = solution_params;
+ solution.input.target_h = armor_position_in_world.z();
auto result = solution.solve();
-
if (!result) {
- return std::nullopt;
+ continue;
}
- auto time_error = std::abs(result->fly_time - current_fly_time);
- current_fly_time = result->fly_time;
- trajectory_result = *result;
- horizon_distance = target_d;
-
+ auto time_error = std::abs(result->fly_time - current_fly_time);
+ current_fly_time = result->fly_time;
if (time_error < kMaxFlyTimeThreshold) break;
}
- auto final_yaw = std::atan2(best_armor_opt->translation.y, best_armor_opt->translation.x);
+ if (!best_armor_opt) return std::nullopt;
- return Result {
- .pitch = trajectory_result.pitch,
- .yaw = final_yaw,
- .horizon_distance = horizon_distance,
- };
+ return make_result(*best_armor_opt, control, current_yaw);
}
};
@@ -173,9 +199,7 @@ auto FireControl::initialize(const YAML::Node& yaml) noexcept -> std::expectedinitialize(yaml);
}
-auto FireControl::set_bullet_speed(double speed) -> void { return pimpl->set_bullet_speed(speed); }
-
-auto FireControl::solve(const predictor::Snapshot& snapshot,
- Translation const& odom_to_muzzle_translation) -> std::optional {
- return pimpl->solve(snapshot, odom_to_muzzle_translation);
+auto FireControl::solve(const predictor::Snapshot& snapshot, bool control, double current_yaw)
+ -> std::optional {
+ return pimpl->solve(snapshot, control, current_yaw);
}
diff --git a/src/kernel/fire_control.hpp b/src/kernel/fire_control.hpp
index 8ac03c89..4284bcc3 100644
--- a/src/kernel/fire_control.hpp
+++ b/src/kernel/fire_control.hpp
@@ -5,7 +5,6 @@
#include "module/predictor/snapshot.hpp"
#include "utility/clock.hpp"
-#include "utility/math/linear.hpp"
#include "utility/pimpl.hpp"
namespace rmcs::kernel {
@@ -20,13 +19,12 @@ class FireControl {
double pitch;
double yaw;
double horizon_distance;
+ bool shoot_permitted;
};
auto initialize(const YAML::Node&) noexcept -> std::expected;
- auto set_bullet_speed(double speed) -> void;
-
- auto solve(const predictor::Snapshot& snapshot, Translation const& odom_to_muzzle_translation)
+ auto solve(const predictor::Snapshot& snapshot, bool control, double current_yaw)
-> std::optional;
};
}
diff --git a/src/kernel/tracker.cpp b/src/kernel/tracker.cpp
index 5eea75a0..35914b15 100644
--- a/src/kernel/tracker.cpp
+++ b/src/kernel/tracker.cpp
@@ -32,6 +32,11 @@ struct Tracker::Impl {
return std::unexpected { "enemy_color 应该是 [blue] or [red]." };
}
+ result = decider.initialize(yaml);
+ if (!result.has_value()) {
+ return std::unexpected { result.error() };
+ }
+
return {};
}
diff --git a/src/kernel/tracker.hpp b/src/kernel/tracker.hpp
index 654e4eae..22afea0f 100644
--- a/src/kernel/tracker.hpp
+++ b/src/kernel/tracker.hpp
@@ -1,6 +1,7 @@
#pragma once
#include
+#include
#include
#include "module/tracker/decider.hpp"
diff --git a/src/module/debug/action_throttler.hpp b/src/module/debug/action_throttler.hpp
index c4f2bf3f..dea49e59 100644
--- a/src/module/debug/action_throttler.hpp
+++ b/src/module/debug/action_throttler.hpp
@@ -18,35 +18,32 @@ class ActionThrottler {
using duration = std::chrono::milliseconds;
ActionThrottler(duration interval, std::size_t default_quota) noexcept
- : default_quota_ { default_quota } {
- metronome_.set_interval(interval);
- }
+ : interval_ { interval }
+ , default_quota_ { default_quota } { }
auto register_action(std::string_view tag, std::optional quota = std::nullopt)
-> void {
- auto [it, inserted] =
- actions_.try_emplace(std::string { tag }, quota.value_or(default_quota_));
+ auto [it, inserted] = actions_.try_emplace(
+ std::string { tag }, Action { interval_, quota.value_or(default_quota_) });
if (!inserted) {
- it->second.limit = quota.value_or(default_quota_);
- it->second.reset();
- it->second.enable();
+ it->second = Action { interval_, quota.value_or(default_quota_) };
}
}
template
auto dispatch(std::string_view tag, Fn&& action) -> bool {
- if (!metronome_.tick()) return false;
-
auto it = actions_.find(tag);
if (it == actions_.end()) return false;
- auto& limit = it->second;
- if (limit.tick()) {
+ auto& action_state = it->second;
+ if (!action_state.metronome.tick()) return false;
+
+ if (action_state.limit.tick()) {
std::forward(action)();
return true;
}
- limit.disable();
+ action_state.limit.disable();
return false;
}
@@ -58,6 +55,23 @@ class ActionThrottler {
}
private:
+ struct Action {
+ explicit Action(duration interval, std::size_t quota) noexcept
+ : limit { quota } {
+ metronome.set_interval(interval);
+ }
+
+ TimesLimit limit;
+ FramerateCounter metronome;
+
+ auto reset() noexcept -> void {
+ limit.reset();
+ metronome.last_reach_interval_timestamp = {};
+ }
+
+ auto enable() noexcept -> void { limit.enable(); }
+ };
+
struct string_hash {
using is_transparent = void;
auto operator()(std::string_view sv) const -> std::size_t {
@@ -65,9 +79,9 @@ class ActionThrottler {
}
};
- FramerateCounter metronome_;
+ duration interval_;
std::size_t default_quota_;
- std::unordered_map> actions_;
+ std::unordered_map> actions_;
};
} // namespace rmcs::util
diff --git a/src/module/debug/visualization/armor_visualizer.cpp b/src/module/debug/visualization/armor_visualizer.cpp
index 634ad5ed..af3044f8 100644
--- a/src/module/debug/visualization/armor_visualizer.cpp
+++ b/src/module/debug/visualization/armor_visualizer.cpp
@@ -1,79 +1,165 @@
#include "armor_visualizer.hpp"
-#include "utility/rclcpp/visual/armor.hpp"
+#include "utility/panic.hpp"
+#include "utility/rclcpp/node.details.hpp"
#include "utility/robot/armor.hpp"
+#include
+#include
+#include
+
using namespace rmcs::debug;
-using VisualArmor = rmcs::util::visual::Armor;
+using Marker = visualization_msgs::msg::Marker;
+using MarkerArray = visualization_msgs::msg::MarkerArray;
-struct ArmorShadow {
- decltype(rmcs::Armor3D::genre) genre;
- decltype(rmcs::Armor3D::color) color;
- decltype(rmcs::Armor3D::id) id;
- std::string ns;
+namespace {
- bool operator==(ArmorShadow const& other) const = default;
- bool operator!=(ArmorShadow const& other) const { return !(*this == other); }
-};
+auto make_unique_marker_id(rmcs::DeviceId device, int armor_index) -> int {
+ constexpr auto kArmorIndexBitWidth = 16;
+ constexpr auto kArmorIndexLimit = 1 << kArmorIndexBitWidth;
+
+ if (armor_index < 0 || armor_index >= kArmorIndexLimit) {
+ rmcs::util::panic(std::format("Armor marker index out of range: {}", armor_index));
+ }
+
+ auto const device_index = static_cast(rmcs::to_index(device));
+ return (device_index << kArmorIndexBitWidth) | armor_index;
+}
+
+auto set_marker_scale(Marker& marker, rmcs::DeviceId device, bool is_arrow) -> void {
+ if (is_arrow) {
+ marker.scale.x = 0.2;
+ marker.scale.y = 0.01;
+ marker.scale.z = 0.01;
+ return;
+ }
+
+ if (rmcs::DeviceIds::kSmallArmor().contains(device)) {
+ marker.scale.x = 0.003;
+ marker.scale.y = 0.140;
+ marker.scale.z = 0.125;
+ } else if (rmcs::DeviceIds::kLargeArmor().contains(device)) {
+ marker.scale.x = 0.003;
+ marker.scale.y = 0.235;
+ marker.scale.z = 0.127;
+ }
+}
+
+auto set_marker_color(Marker& marker, rmcs::CampColor camp) -> void {
+ if (camp == rmcs::CampColor::RED) {
+ marker.color.r = 1.;
+ marker.color.g = 0.;
+ marker.color.b = 0.;
+ marker.color.a = 1.;
+ } else if (camp == rmcs::CampColor::BLUE) {
+ marker.color.r = 0.;
+ marker.color.g = 0.;
+ marker.color.b = 1.;
+ marker.color.a = 1.;
+ } else {
+ marker.color.r = 1.;
+ marker.color.g = 0.;
+ marker.color.b = 1.;
+ marker.color.a = 1.;
+ }
+}
+
+auto make_marker(std::string_view frame_id, std::string_view ns, int id, int type, int action,
+ rmcs::DeviceId device, rmcs::CampColor camp, const rmcs::Armor3D* armor, rclcpp::Time stamp)
+ -> Marker {
+ auto marker = Marker {};
+ marker.header.frame_id = frame_id;
+ marker.header.stamp = stamp;
+ marker.ns = std::string { ns };
+ marker.id = id;
+ marker.type = type;
+ marker.action = action;
+ marker.lifetime = rclcpp::Duration::from_seconds(0.1);
+
+ if (type == Marker::ARROW) {
+ set_marker_scale(marker, device, true);
+ } else {
+ set_marker_scale(marker, device, false);
+ }
+
+ set_marker_color(marker, camp);
+
+ if (armor) {
+ armor->translation.copy_to(marker.pose.position);
+ armor->orientation.copy_to(marker.pose.orientation);
+ }
+
+ return marker;
+}
+
+} // namespace
struct ArmorVisualizer::Impl final {
auto initialize(util::RclcppNode& visual_node) noexcept -> void {
node = std::ref(visual_node);
}
- auto visualize(std::span _armors, std::string const& name,
+ auto visualize(std::span armors, std::string const& name,
std::string const& link_name) -> bool {
if (!node.has_value()) {
return false;
}
- auto new_size = _armors.size();
- visual_armors.reserve(new_size);
- current_armors.reserve(new_size);
- visual_armors.resize(new_size);
- current_armors.resize(new_size);
-
- for (size_t i = 0; i < new_size; i++) {
- auto const& input = _armors[i];
- auto& armor_ptr = visual_armors[i];
- auto& shadow = current_armors[i];
-
- bool changed = !armor_ptr || needs_rebuild(shadow, input, name);
-
- if (changed) {
- auto const config = VisualArmor::Config {
- .rclcpp = node.value().get(),
- .device = input.genre,
- .camp = armor_color2camp_color(input.color),
- .id = input.id,
- .name = name,
- .tf = link_name,
- };
-
- armor_ptr = std::make_unique(config);
-
- shadow.genre = input.genre;
- shadow.color = input.color;
- shadow.id = input.id;
- shadow.ns = name;
+ if (!rmcs::util::prefix::check_naming(name)
+ || !rmcs::util::prefix::check_naming(link_name)) {
+ util::panic(std::format(
+ "Not a valid naming for armor name or tf: {}",
+ rmcs::util::prefix::naming_standard));
+ }
+
+ auto const topic_name = node.value().get().get_pub_topic_prefix() + name;
+ if (!rclcpp_pub || published_topic != topic_name) {
+ rclcpp_pub = node.value().get().details->make_pub(
+ topic_name, rmcs::util::qos::debug);
+ published_topic = topic_name;
+ previous_ids.clear();
+ }
+
+ auto visual_marker = MarkerArray {};
+ const auto current_time = rclcpp_clock.now();
+ auto current_ids = std::unordered_set {};
+ auto const arrow_name = std::format("{}_arrow", name);
+ current_ids.reserve(armors.size());
+
+ for (auto const& armor : armors) {
+ auto const camp = armor_color2camp_color(armor.color);
+ auto const marker_id = make_unique_marker_id(armor.genre, armor.id);
+ current_ids.emplace(marker_id);
+
+ visual_marker.markers.emplace_back(make_marker(link_name, name, marker_id, Marker::CUBE,
+ Marker::ADD, armor.genre, camp, &armor, current_time));
+ visual_marker.markers.emplace_back(make_marker(link_name, arrow_name, marker_id,
+ Marker::ARROW, Marker::ADD, armor.genre, camp, &armor, current_time));
+ }
+
+ for (auto const id : previous_ids) {
+ if (current_ids.contains(id)) {
+ continue;
}
- armor_ptr->move(input.translation, input.orientation);
- armor_ptr->update();
+ visual_marker.markers.emplace_back(make_marker(link_name, name, id, Marker::CUBE,
+ Marker::DELETE, rmcs::DeviceId {}, rmcs::CampColor {}, nullptr, current_time));
+ visual_marker.markers.emplace_back(make_marker(link_name, arrow_name, id,
+ Marker::ARROW, Marker::DELETE, rmcs::DeviceId {}, rmcs::CampColor {}, nullptr,
+ current_time));
}
+ previous_ids = std::move(current_ids);
+ rclcpp_pub->publish(visual_marker);
return true;
}
- static auto needs_rebuild(
- ArmorShadow const& shadow, Armor3D const& input, std::string_view name) -> bool {
- return shadow.genre != input.genre || shadow.color != input.color || shadow.id != input.id
- || shadow.ns != name;
- }
+ static inline rclcpp::Clock rclcpp_clock { RCL_STEADY_TIME };
std::optional> node;
- std::vector current_armors;
- std::vector> visual_armors;
+ std::shared_ptr> rclcpp_pub;
+ std::string published_topic;
+ std::unordered_set previous_ids;
};
auto ArmorVisualizer::initialize(util::RclcppNode& visual_node) noexcept -> void {
diff --git a/src/module/fire_control/aim_point_chooser.cpp b/src/module/fire_control/aim_point_chooser.cpp
index 5daf46d3..62b13c8f 100644
--- a/src/module/fire_control/aim_point_chooser.cpp
+++ b/src/module/fire_control/aim_point_chooser.cpp
@@ -1,5 +1,7 @@
#include "aim_point_chooser.hpp"
+#include
+
#include "utility/math/conversion.hpp"
using namespace rmcs::fire_control;
@@ -26,16 +28,14 @@ struct AimPointChooser::Impl {
angular_velocity_threshold = config.angular_velocity_threshold;
}
- auto choose_armor(std::span armors, Eigen::Vector const& ekf_x)
- -> std::optional {
+ auto choose_armor(std::span armors, Eigen::Vector3d const& center_position,
+ double angular_velocity) -> std::optional {
if (armors.empty()) {
last_chosen_id = -1;
return std::nullopt;
}
- const auto car_y = ekf_x[2], car_x = ekf_x[0];
- const auto center_yaw = std::atan2(car_y, car_x);
- const auto angular_velocity = ekf_x[7];
+ const auto center_yaw = std::atan2(center_position.y(), center_position.x());
struct ArmorCandidate {
int index;
@@ -129,6 +129,6 @@ auto AimPointChooser::initialize(Config const& config) noexcept -> void {
}
auto AimPointChooser::choose_armor(std::span armors,
- Eigen::Vector const& ekf_x) -> std::optional {
- return pimpl->choose_armor(armors, ekf_x);
+ Eigen::Vector3d const& center_position, double angular_velocity) -> std::optional {
+ return pimpl->choose_armor(armors, center_position, angular_velocity);
}
diff --git a/src/module/fire_control/aim_point_chooser.hpp b/src/module/fire_control/aim_point_chooser.hpp
index 9d968c5c..930d6f07 100644
--- a/src/module/fire_control/aim_point_chooser.hpp
+++ b/src/module/fire_control/aim_point_chooser.hpp
@@ -5,15 +5,11 @@
#include
#include
-#include "utility/math/kalman_filter/ekf.hpp"
#include "utility/pimpl.hpp"
#include "utility/robot/armor.hpp"
namespace rmcs::fire_control {
class AimPointChooser {
-private:
- using EKF = util::EKF<11, 4>;
-
public:
struct Config {
double coming_angle; // rad
@@ -24,8 +20,8 @@ class AimPointChooser {
};
auto initialize(Config const& config) noexcept -> void;
- auto choose_armor(std::span armors, EKF::XVec const& ekf_x)
- -> std::optional;
+ auto choose_armor(std::span armors, Eigen::Vector3d const& center_position,
+ double angular_velocity) -> std::optional;
RMCS_PIMPL_DEFINITION(AimPointChooser)
};
diff --git a/src/module/fire_control/shoot_evaluator.cpp b/src/module/fire_control/shoot_evaluator.cpp
new file mode 100644
index 00000000..ee72b785
--- /dev/null
+++ b/src/module/fire_control/shoot_evaluator.cpp
@@ -0,0 +1,98 @@
+#include "shoot_evaluator.hpp"
+
+#include
+#include
+
+#include "utility/math/angle.hpp"
+#include "utility/serializable.hpp"
+
+using namespace rmcs::fire_control;
+
+struct ShootEvaluator::Impl {
+ struct Config : util::Serializable {
+ double first_tolerance { 4.0 }; // degree
+ double second_tolerance { 2.0 }; // degree
+ double judge_distance { 3.0 }; // m
+ bool auto_fire { true };
+
+ constexpr static std::tuple metas {
+ &Config::first_tolerance,
+ "first_tolerance",
+ &Config::second_tolerance,
+ "second_tolerance",
+ &Config::judge_distance,
+ "judge_distance",
+ &Config::auto_fire,
+ "auto_fire",
+ };
+ };
+
+ Config config {};
+
+ double first_tolerance_ { 4.0 / 57.3 };
+ double second_tolerance_ { 2.0 / 57.3 };
+ double judge_distance_ { 3.0 };
+ bool auto_fire_ { true };
+
+ std::optional last_command_ {};
+
+ auto initialize(const YAML::Node& yaml) noexcept -> std::expected {
+ auto result = config.serialize(yaml);
+ if (!result.has_value()) {
+ return std::unexpected { result.error() };
+ }
+
+ first_tolerance_ = config.first_tolerance / 57.3;
+ second_tolerance_ = config.second_tolerance / 57.3;
+ judge_distance_ = config.judge_distance;
+ auto_fire_ = config.auto_fire;
+ last_command_.reset();
+
+ if (!(first_tolerance_ > 0.0) || !(second_tolerance_ > 0.0)) {
+ return std::unexpected { "first_tolerance and second_tolerance must be > 0" };
+ }
+ if (judge_distance_ < 0.0) {
+ return std::unexpected { "judge_distance must be >= 0" };
+ }
+
+ return {};
+ }
+
+ auto evaluate(Command const& command, double current_yaw) noexcept -> bool {
+ auto should_fire = false;
+
+ if (!command.control || !auto_fire_) {
+ last_command_ = command;
+ return false;
+ }
+
+ const auto tolerance =
+ (command.distance > judge_distance_) ? second_tolerance_ : first_tolerance_;
+
+ if (last_command_.has_value() && command.auto_aim_enabled && command.aim_point_valid) {
+ const auto yaw_delta =
+ std::abs(util::normalize_angle(last_command_->yaw - command.yaw));
+ const auto track_delta =
+ std::abs(util::normalize_angle(current_yaw - last_command_->yaw));
+
+ should_fire = (yaw_delta < tolerance * 2.0) && (track_delta < tolerance);
+ }
+
+ last_command_ = command;
+ return should_fire;
+ }
+};
+
+ShootEvaluator::ShootEvaluator() noexcept
+ : pimpl { std::make_unique() } { }
+
+ShootEvaluator::~ShootEvaluator() noexcept = default;
+
+auto ShootEvaluator::initialize(const YAML::Node& yaml) noexcept
+ -> std::expected {
+ return pimpl->initialize(yaml);
+}
+
+auto ShootEvaluator::evaluate(Command const& command, double current_yaw) noexcept -> bool {
+ return pimpl->evaluate(command, current_yaw);
+}
diff --git a/src/module/fire_control/shoot_evaluator.hpp b/src/module/fire_control/shoot_evaluator.hpp
new file mode 100644
index 00000000..f0795eca
--- /dev/null
+++ b/src/module/fire_control/shoot_evaluator.hpp
@@ -0,0 +1,29 @@
+#pragma once
+
+#include
+#include
+
+#include
+
+#include "utility/pimpl.hpp"
+
+namespace rmcs::fire_control {
+
+class ShootEvaluator {
+ RMCS_PIMPL_DEFINITION(ShootEvaluator)
+
+public:
+ struct Command {
+ bool control { false };
+ bool auto_aim_enabled { false };
+ bool aim_point_valid { false };
+ double yaw { 0. };
+ double distance { 0. };
+ };
+
+ auto initialize(const YAML::Node& yaml) noexcept -> std::expected;
+
+ auto evaluate(Command const& command, double current_yaw) noexcept -> bool;
+};
+
+} // namespace rmcs::fire_control
diff --git a/src/module/fire_control/trajectory_solution.cpp b/src/module/fire_control/trajectory_solution.cpp
index 599460a3..6518ca4c 100644
--- a/src/module/fire_control/trajectory_solution.cpp
+++ b/src/module/fire_control/trajectory_solution.cpp
@@ -15,13 +15,10 @@ using namespace rmcs::fire_control;
auto TrajectorySolution::solve() const -> std::optional