diff --git a/crane_bringup/launch/crane.launch.xml b/crane_bringup/launch/crane.launch.xml index 58461919d..3f848c809 100644 --- a/crane_bringup/launch/crane.launch.xml +++ b/crane_bringup/launch/crane.launch.xml @@ -55,7 +55,7 @@ limitations under the License. - + @@ -69,15 +69,15 @@ limitations under the License. - + - - + + - + @@ -140,6 +140,10 @@ limitations under the License. + + + + diff --git a/crane_local_planner/package.xml b/crane_local_planner/package.xml index 36ebcf6cc..c6059a66f 100755 --- a/crane_local_planner/package.xml +++ b/crane_local_planner/package.xml @@ -11,6 +11,7 @@ closest_point_vendor crane_comm + crane_physics crane_msg_wrappers crane_msgs crane_utils diff --git a/crane_local_planner/src/rvo2_planner.cpp b/crane_local_planner/src/rvo2_planner.cpp index d6ac1cc2e..11f56b84d 100644 --- a/crane_local_planner/src/rvo2_planner.cpp +++ b/crane_local_planner/src/rvo2_planner.cpp @@ -180,36 +180,12 @@ auto RVO2Planner::reflectWorldToRVOSim(crane_msgs::msg::RobotCommands & msg) -> position_diff << pos_mode.target_x - current_position.x(), pos_mode.target_y - current_position.y(); - double pre_vel = [&]() { - if ( - auto it = ranges::find_if( - pre_commands.robot_commands, - [&](const auto & c) { return c.robot_id == command.robot_id; }); - it != ranges::end(pre_commands.robot_commands)) { - if (it->position_target_mode.empty()) { - return 0.0; - } - return std::hypot( - it->position_target_mode.front().target_x - current_position.x(), - it->position_target_mode.front().target_y - current_position.y()) > 0.01 - ? vel - : 0.0; - } else { - return 0.0; - } - }(); + // 現在速度ベースで減速度を選択 + double current_speed = std::hypot(command.current_velocity.x, command.current_velocity.y); + double max_brk = (current_speed >= planning_deceleration_velocity_threshold) + ? planning_deceleration_high_speed + : planning_deceleration_low_speed; - // 減速計算用の減速度を選択(現在速度に応じて高速域・低速域を選択) - double deceleration_for_planning; - if (pre_vel >= planning_deceleration_velocity_threshold) { - deceleration_for_planning = planning_deceleration_high_speed; - } else { - deceleration_for_planning = planning_deceleration_low_speed; - } - - // max_brk: 停止のための減速度(planning_decelerationの値を使用) - double max_brk = deceleration_for_planning; - // max_acc: 加速フェーズの加速度(planning_accelerationの値を使用、スキルによる制限も適用) command.local_planner_config.max_acceleration_factors.emplace_back( crane_msgs::msg::NamedFloat() .set__name("RVO2Planner::max_acc from parameter") @@ -228,48 +204,43 @@ auto RVO2Planner::reflectWorldToRVOSim(crane_msgs::msg::RobotCommands & msg) -> } double max_vel = resolveMaxVelocityFactors(command, MAX_VEL); + max_vel = std::max(0.4, max_vel); + // 1D BangBangTrajectoryで速度プロファイルを計算し、方向は目標への単位ベクトルを使用 + // 横方向の速度成分をBangBangに渡さないことで旧実装の周回問題を解消 Velocity target_vel; - target_vel << (pos_mode.target_x - current_position.x()), - pos_mode.target_y - current_position.y(); + double distance_to_target = position_diff.norm(); - // 速度超過クランプ: Vision観測誤差でmax_velをわずかに超えた速度を正規化(Sumatra adaptVel相当) - constexpr double MAX_VEL_TOLERANCE = 0.2; - Eigen::Vector2d v0(command.current_velocity.x, command.current_velocity.y); - if (vel > max_vel && vel < max_vel + MAX_VEL_TOLERANCE) { - v0 = v0 * (max_vel / vel); - } + if (distance_to_target > 0.01) { + Eigen::Vector2d dir = position_diff.normalized(); - BangBangTrajectory2D trajectory; - trajectory.generate( - Eigen::Vector2d(current_position.x(), current_position.y()), - Eigen::Vector2d(pos_mode.target_x, pos_mode.target_y), v0, max_vel, max_acc, max_brk); - - // BangBangTrajectoryから速度を取得 - // Vision遅延(~100ms)を考慮してlookahead時間を設定 - const double lookahead_time = 0.1; - Eigen::Vector2d next_vel = trajectory.getVelocity(lookahead_time); - - // 目標との距離を計算 - double distance_to_target = std::hypot( - pos_mode.target_x - current_position.x(), pos_mode.target_y - current_position.y()); - - // terminal_velocity: スキルが明示的に設定した場合のみ疑似I項として適用する - // 0(デフォルト・未指定)のときはBangBang軌道の出力に従い、目標で停止する - const double terminal_vel = command.local_planner_config.terminal_velocity; - const double min_distance = - std::max(static_cast(pos_mode.position_tolerance) * 2, 0.03); // 最低3cm - - if (terminal_vel > 0 && next_vel.norm() < terminal_vel && distance_to_target > min_distance) { - // terminal_velocityが指定されており、低速かつ目標から離れている場合に補正 - Eigen::Vector2d direction = - (Eigen::Vector2d(pos_mode.target_x, pos_mode.target_y) - current_position).normalized(); - target_vel = direction * terminal_vel; - } else if (next_vel.norm() < 1e-6) { - // 目標到達済み - target_vel.setZero(); + // 現在速度を目標方向に射影してスカラー初速度を算出 + Eigen::Vector2d v0(command.current_velocity.x, command.current_velocity.y); + double v0_along_target = std::clamp(v0.dot(dir), 0.0, max_vel); + + // 1D BangBangで目標方向の速度プロファイルを計算 + BangBangTrajectory1D bb_trajectory; + bb_trajectory.generate(0.0, distance_to_target, v0_along_target, max_vel, max_acc, max_brk); + + const double lookahead_time = 0.1; + double speed = bb_trajectory.getVelocity(lookahead_time); + + addOrUpdatePlanningFactor(command, "BBAccel", formatPlanningDouble(max_acc, 1)); + addOrUpdatePlanningFactor(command, "BBBrk", formatPlanningDouble(max_brk, 1)); + addOrUpdatePlanningFactor(command, "BBMaxVel", formatPlanningDouble(max_vel, 1)); + addOrUpdatePlanningFactor(command, "BBSpeed", formatPlanningDouble(speed, 2)); + + target_vel = dir * speed; + + // terminal_velocity: パス等で目標通過速度が指定されている場合 + const double terminal_vel = command.local_planner_config.terminal_velocity; + const double min_distance = + std::max(static_cast(pos_mode.position_tolerance) * 2, 0.03); + if (terminal_vel > 0 && speed < terminal_vel && distance_to_target > min_distance) { + target_vel = dir * terminal_vel; + } } else { - target_vel << next_vel.x(), next_vel.y(); + target_vel.setZero(); } // 衝突ファール (crashing) 回避: // SSLルールでは衝突時の速度ベクトル差をロボット間直線に射影した値が @@ -367,23 +338,27 @@ auto RVO2Planner::reflectWorldToRVOSim(crane_msgs::msg::RobotCommands & msg) -> ? 0.0f : PENALTY_AREA_TIME_HORIZON_OBST); + // 診断用: RVO2入力速度(crash/penalty修正後) + addOrUpdatePlanningFactor( + command, "PrefVel", + formatPlanningDouble(target_vel.x(), 2) + "," + formatPlanningDouble(target_vel.y(), 2)); + addOrUpdatePlanningFactor( + command, "PrefVelAngle", + formatPlanningDouble(std::atan2(target_vel.y(), target_vel.x()) * 180.0 / M_PI, 1)); + rvo_sim->setAgentPrefVelocity(command.robot_id, toRVO(target_vel)); rvo_sim->setAgentMaxSpeed(command.robot_id, max_vel); - // 速度計画トレースに計画点を追加 - if (enable_velocity_plan_trace && !command.velocity_plan_trace.empty()) { - // 現在時刻から100ms後の予測位置・速度を記録 - const int32_t target_time_us = - static_cast(lookahead_time * 1e6); // 100ms = 100000us - Eigen::Vector2d predicted_pos = trajectory.getPosition(lookahead_time); - Eigen::Vector2d predicted_vel = next_vel; - - VelocityPlanTracker::addPlanPoint( - command.velocity_plan_trace[0], "local_planner", predicted_pos, predicted_vel, - target_time_us, - 0 // estimated_arrival_time_us(後で実装可能) - ); - } + // [BangBang無効化] 速度計画トレース(trajectory/next_velに依存するためコメントアウト) + // if (enable_velocity_plan_trace && !command.velocity_plan_trace.empty()) { + // const int32_t target_time_us = + // static_cast(lookahead_time * 1e6); + // Eigen::Vector2d predicted_pos = trajectory.getPosition(lookahead_time); + // Eigen::Vector2d predicted_vel = next_vel; + // VelocityPlanTracker::addPlanPoint( + // command.velocity_plan_trace[0], "local_planner", predicted_pos, predicted_vel, + // target_time_us, 0); + // } } for (const auto & enemy_robot : world_model->theirs().robots) { @@ -420,6 +395,13 @@ auto RVO2Planner::extractVelocityCommandsFromRVOSim( auto pref_vel = toPoint(rvo_sim->getAgentPrefVelocity(original_command.robot_id)); auto vel = toPoint(rvo_sim->getAgentVelocity(original_command.robot_id)); addOrUpdatePlanningFactor(command, "RVO2PrefSpeed", formatPlanningDouble(pref_vel.norm())); + // 診断用: RVO2 prefVelocityと出力速度の角度差 + addOrUpdatePlanningFactor( + command, "RVO2PrefVelAngle", + formatPlanningDouble(std::atan2(pref_vel.y(), pref_vel.x()) * 180.0 / M_PI, 1)); + addOrUpdatePlanningFactor( + command, "RVO2OutVelAngle", + formatPlanningDouble(std::atan2(vel.y(), vel.x()) * 180.0 / M_PI, 1)); // 速度修正をトレースに記録(RVO2による修正) if (enable_velocity_plan_trace && !command.velocity_plan_trace.empty()) { @@ -475,8 +457,9 @@ auto RVO2Planner::extractVelocityCommandsFromRVOSim( command.polar_velocity_target_mode.front().target_velocity_theta = std::atan2(vel.y(), vel.x()) + theta_offset; - // 効率的な加速のための回転制御 - if (command.local_planner_config.enable_rotation_stop_on_accel) { +// 効率的な加速のための回転制御 + // if (command.local_planner_config.enable_rotation_stop_on_accel) { + if(true){ double move_angle = std::atan2(vel.y(), vel.x()); double angle_diff = getAngleDiff(robot->pose.theta, move_angle); @@ -497,7 +480,6 @@ auto RVO2Planner::extractVelocityCommandsFromRVOSim( command.omega_limit = 0.0; } } - commands.robot_commands.emplace_back(command); } @@ -718,15 +700,12 @@ auto RVO2Planner::adjustForPenaltyAreaAvoidance( } else if (isInBox(penalty_area, closest_point_2, PENALTY_AREA_OFFSET)) { target_pos = around_corner_2; } else { - static rclcpp::Clock steady_clock(RCL_STEADY_TIME); - RCLCPP_WARN_THROTTLE( - rclcpp::get_logger("rvo2_local_planner"), steady_clock, 1000, - "Failed to find a target position outside the penalty area. Fallback to current " - "position. current_pos:(%.3f, %.3f) target_pos:(%.3f, %.3f) " - "closest_point_1:(%.3f, %.3f) closest_point_2:(%.3f, %.3f)", - current_pos.x(), current_pos.y(), target_pos.x(), target_pos.y(), closest_point_1.x(), - closest_point_1.y(), closest_point_2.x(), closest_point_2.y()); - target_pos = current_pos; + // フォールバック: 各角を経由した総距離が短い方を選択 + const double dist_via_1 = + (around_corner_1 - current_pos).norm() + (target_pos - around_corner_1).norm(); + const double dist_via_2 = + (around_corner_2 - current_pos).norm() + (target_pos - around_corner_2).norm(); + target_pos = (dist_via_1 <= dist_via_2) ? around_corner_1 : around_corner_2; } } }; diff --git a/crane_msgs/msg/LocalPlannerConfig.msg b/crane_msgs/msg/LocalPlannerConfig.msg index 267ec818a..bea4d41c1 100644 --- a/crane_msgs/msg/LocalPlannerConfig.msg +++ b/crane_msgs/msg/LocalPlannerConfig.msg @@ -4,7 +4,6 @@ bool disable_placement_avoidance false bool disable_ball_avoidance false bool disable_field_boundary false bool disable_crash_avoidance false -bool enable_rotation_stop_on_accel true NamedFloat[] max_velocity_factors NamedFloat final_planned_max_velocity diff --git a/crane_msgs/msg/RobotFeedback.msg b/crane_msgs/msg/RobotFeedback.msg index a55e768b9..e3aa04bab 100755 --- a/crane_msgs/msg/RobotFeedback.msg +++ b/crane_msgs/msg/RobotFeedback.msg @@ -1,6 +1,10 @@ builtin_interfaces/Time received_stamp uint8 robot_id float32 packet_frequency_hz +float32 receive_interval_mean_ms +float32 receive_interval_min_ms +float32 receive_interval_max_ms +float32 receive_interval_stddev_ms uint8 counter diff --git a/crane_robot_receiver/src/robot_receiver_node.cpp b/crane_robot_receiver/src/robot_receiver_node.cpp index 69b9e8628..30751cfe4 100644 --- a/crane_robot_receiver/src/robot_receiver_node.cpp +++ b/crane_robot_receiver/src/robot_receiver_node.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -13,144 +14,194 @@ #include #include #include +#include +#include #include +#include #include -using boost::asio::ip::udp; +namespace asio = boost::asio; using crane::robot_receiver::RobotFeedback; namespace protocol = crane::robot_receiver::protocol; class RobotFeedbackReceiver { public: - RobotFeedbackReceiver(const std::string & host, const int port) + RobotFeedbackReceiver(asio::io_context & io_ctx, const std::string & host, const int port) : robot_id(port - 50100), - receiver_(std::make_unique(host, port)), - buffer_(protocol::BUFFER_SIZE), + async_receiver_( + std::make_unique(io_ctx, host, port, protocol::BUFFER_SIZE)), clock(RCL_ROS_TIME) { - // 初回比較時のエラー回避 - robot_feedback.received_stamp = rclcpp::Time(0, 0, RCL_ROS_TIME); + async_receiver_->startReceive( + [this](const std::vector & buf, size_t size) { onReceive(buf, size); }); } - auto receive() -> bool + // asioスレッドから呼ばれる + void onReceive(const std::vector & buf, size_t /*size*/) { - if (receiver_->available()) { - receiver_->receive(buffer_); - // 受信タイムスタンプを記録 - receive_timestamps_.push_back(clock.now()); - return true; + auto stamp = clock.now(); + RobotFeedback feedback = parseBuffer(buf, stamp); + std::lock_guard lock(mutex_); + latest_feedback_ = feedback; + receive_timestamps_.push_back(stamp); + } + + // ROS2タイマーから呼ばれる + auto getLatestFeedback() -> std::optional + { + std::lock_guard lock(mutex_); + return latest_feedback_; + } + + struct IntervalStats + { + float mean_ms = 0.f; + float min_ms = 0.f; + float max_ms = 0.f; + float stddev_ms = 0.f; + }; + + auto getPacketFrequency() -> float + { + std::lock_guard lock(mutex_); + pruneTimestampsLocked(); + return static_cast(receive_timestamps_.size()); + } + + auto getReceiveIntervalStats() -> IntervalStats + { + std::lock_guard lock(mutex_); + pruneTimestampsLocked(); + if (receive_timestamps_.size() < 2) { + return {}; + } + + std::vector intervals; + intervals.reserve(receive_timestamps_.size() - 1); + for (size_t i = 1; i < receive_timestamps_.size(); ++i) { + float dt = static_cast( + (receive_timestamps_[i] - receive_timestamps_[i - 1]).nanoseconds() * 1e-6); + intervals.push_back(dt); + } + + float sum = 0.f; + float min_val = intervals[0]; + float max_val = intervals[0]; + for (float v : intervals) { + sum += v; + if (v < min_val) min_val = v; + if (v > max_val) max_val = v; + } + float mean = sum / static_cast(intervals.size()); + + float var = 0.f; + for (float v : intervals) { + float d = v - mean; + var += d * d; } - return false; + float stddev = std::sqrt(var / static_cast(intervals.size())); + + return {mean, min_val, max_val, stddev}; } - auto updateFeedback() -> void + const int robot_id; + +private: + auto parseBuffer(const std::vector & buf, rclcpp::Time stamp) -> RobotFeedback { RobotFeedback feedback; - // 受信タイムスタンプを更新 - feedback.received_stamp = clock.now(); + feedback.received_stamp = stamp; // 基本情報 - feedback.counter = protocol::readByte(buffer_, protocol::offset::COUNTER); - feedback.yaw_angle = protocol::readFloat(buffer_, protocol::offset::YAW_ANGLE); - feedback.voltage[0] = protocol::readFloat(buffer_, protocol::offset::VOLTAGE_0); + feedback.counter = protocol::readByte(buf, protocol::offset::COUNTER); + feedback.yaw_angle = protocol::readFloat(buf, protocol::offset::YAW_ANGLE); + feedback.voltage[0] = protocol::readFloat(buf, protocol::offset::VOLTAGE_0); // ボール検出 - feedback.ball_detection[0] = protocol::readByte(buffer_, protocol::offset::BALL_DETECTION_0); - feedback.ball_detection[1] = protocol::readByte(buffer_, protocol::offset::BALL_DETECTION_1); - feedback.ball_detection[2] = protocol::readByte(buffer_, protocol::offset::BALL_DETECTION_2); - feedback.ball_detection[3] = protocol::readByte(buffer_, protocol::offset::BALL_DETECTION_3); + feedback.ball_detection[0] = protocol::readByte(buf, protocol::offset::BALL_DETECTION_0); + feedback.ball_detection[1] = protocol::readByte(buf, protocol::offset::BALL_DETECTION_1); + feedback.ball_detection[2] = protocol::readByte(buf, protocol::offset::BALL_DETECTION_2); + feedback.ball_detection[3] = protocol::readByte(buf, protocol::offset::BALL_DETECTION_3); feedback.kick_state = - protocol::readByte(buffer_, protocol::offset::KICK_STATE) * protocol::KICK_STATE_SCALE; + protocol::readByte(buf, protocol::offset::KICK_STATE) * protocol::KICK_STATE_SCALE; // エラー情報 - feedback.error_id = protocol::readUint16(buffer_, protocol::offset::ERROR_ID); - feedback.error_info = protocol::readUint16(buffer_, protocol::offset::ERROR_INFO); - feedback.error_value = protocol::readFloat(buffer_, protocol::offset::ERROR_VALUE); + feedback.error_id = protocol::readUint16(buf, protocol::offset::ERROR_ID); + feedback.error_info = protocol::readUint16(buf, protocol::offset::ERROR_INFO); + feedback.error_value = protocol::readFloat(buf, protocol::offset::ERROR_VALUE); // モーター電流 - feedback.motor_current[0] = protocol::readByte(buffer_, protocol::offset::MOTOR_CURRENT_0) / - protocol::MOTOR_CURRENT_SCALE; - feedback.motor_current[1] = protocol::readByte(buffer_, protocol::offset::MOTOR_CURRENT_1) / - protocol::MOTOR_CURRENT_SCALE; - feedback.motor_current[2] = protocol::readByte(buffer_, protocol::offset::MOTOR_CURRENT_2) / - protocol::MOTOR_CURRENT_SCALE; - feedback.motor_current[3] = protocol::readByte(buffer_, protocol::offset::MOTOR_CURRENT_3) / - protocol::MOTOR_CURRENT_SCALE; + feedback.motor_current[0] = + protocol::readByte(buf, protocol::offset::MOTOR_CURRENT_0) / protocol::MOTOR_CURRENT_SCALE; + feedback.motor_current[1] = + protocol::readByte(buf, protocol::offset::MOTOR_CURRENT_1) / protocol::MOTOR_CURRENT_SCALE; + feedback.motor_current[2] = + protocol::readByte(buf, protocol::offset::MOTOR_CURRENT_2) / protocol::MOTOR_CURRENT_SCALE; + feedback.motor_current[3] = + protocol::readByte(buf, protocol::offset::MOTOR_CURRENT_3) / protocol::MOTOR_CURRENT_SCALE; // 温度 - feedback.temperature[0] = protocol::readByte(buffer_, protocol::offset::TEMPERATURE_0); - feedback.temperature[1] = protocol::readByte(buffer_, protocol::offset::TEMPERATURE_1); - feedback.temperature[2] = protocol::readByte(buffer_, protocol::offset::TEMPERATURE_2); - feedback.temperature[3] = protocol::readByte(buffer_, protocol::offset::TEMPERATURE_3); - feedback.temperature[4] = protocol::readByte(buffer_, protocol::offset::TEMPERATURE_4); - feedback.temperature[5] = protocol::readByte(buffer_, protocol::offset::TEMPERATURE_5); - feedback.temperature[6] = protocol::readByte(buffer_, protocol::offset::TEMPERATURE_6); + feedback.temperature[0] = protocol::readByte(buf, protocol::offset::TEMPERATURE_0); + feedback.temperature[1] = protocol::readByte(buf, protocol::offset::TEMPERATURE_1); + feedback.temperature[2] = protocol::readByte(buf, protocol::offset::TEMPERATURE_2); + feedback.temperature[3] = protocol::readByte(buf, protocol::offset::TEMPERATURE_3); + feedback.temperature[4] = protocol::readByte(buf, protocol::offset::TEMPERATURE_4); + feedback.temperature[5] = protocol::readByte(buf, protocol::offset::TEMPERATURE_5); + feedback.temperature[6] = protocol::readByte(buf, protocol::offset::TEMPERATURE_6); // 角度と電圧 - feedback.diff_angle = protocol::readFloat(buffer_, protocol::offset::DIFF_ANGLE); - feedback.voltage[1] = protocol::readFloat(buffer_, protocol::offset::VOLTAGE_1); + feedback.diff_angle = protocol::readFloat(buf, protocol::offset::DIFF_ANGLE); + feedback.voltage[1] = protocol::readFloat(buf, protocol::offset::VOLTAGE_1); // オドメトリ - feedback.odom[0] = protocol::readFloat(buffer_, protocol::offset::ODOM_X); - feedback.odom[1] = protocol::readFloat(buffer_, protocol::offset::ODOM_Y); - feedback.odom_speed[0] = protocol::readFloat(buffer_, protocol::offset::ODOM_SPEED_X); - feedback.odom_speed[1] = protocol::readFloat(buffer_, protocol::offset::ODOM_SPEED_Y); + feedback.odom[0] = protocol::readFloat(buf, protocol::offset::ODOM_X); + feedback.odom[1] = protocol::readFloat(buf, protocol::offset::ODOM_Y); + feedback.odom_speed[0] = protocol::readFloat(buf, protocol::offset::ODOM_SPEED_X); + feedback.odom_speed[1] = protocol::readFloat(buf, protocol::offset::ODOM_SPEED_Y); - feedback.check_ver = protocol::readByte(buffer_, protocol::offset::CHECK_VER); + feedback.check_ver = protocol::readByte(buf, protocol::offset::CHECK_VER); // マウスセンサー - feedback.mouse_odom[0] = protocol::readFloat(buffer_, protocol::offset::MOUSE_ODOM_X); - feedback.mouse_odom[1] = protocol::readFloat(buffer_, protocol::offset::MOUSE_ODOM_Y); - feedback.mouse_vel[0] = protocol::readFloat(buffer_, protocol::offset::MOUSE_VEL_X); - feedback.mouse_vel[1] = protocol::readFloat(buffer_, protocol::offset::MOUSE_VEL_Y); + feedback.mouse_odom[0] = protocol::readFloat(buf, protocol::offset::MOUSE_ODOM_X); + feedback.mouse_odom[1] = protocol::readFloat(buf, protocol::offset::MOUSE_ODOM_Y); + feedback.mouse_vel[0] = protocol::readFloat(buf, protocol::offset::MOUSE_VEL_X); + feedback.mouse_vel[1] = protocol::readFloat(buf, protocol::offset::MOUSE_VEL_Y); // デバッグ値 feedback.values.clear(); for (size_t i = protocol::offset::DEBUG_VALUES_START; i < protocol::DEBUG_VALUES_END - protocol::FLOAT_SIZE; i += protocol::FLOAT_SIZE) { - feedback.values.push_back(protocol::readFloat(buffer_, static_cast(i))); + feedback.values.push_back(protocol::readFloat(buf, static_cast(i))); } - robot_feedback = feedback; + return feedback; } - auto getFeedback() const -> RobotFeedback { return robot_feedback; } - - auto getPacketFrequency() -> float + // mutex_ を保持した状態で呼ぶこと + void pruneTimestampsLocked() { using std::chrono::operator""ms; auto now = clock.now(); - - // 1秒より古いタイムスタンプを削除 while (!receive_timestamps_.empty() && (now - receive_timestamps_.front()) > 1000ms) { receive_timestamps_.pop_front(); } - - // 最近1秒間の受信回数を返す - return static_cast(receive_timestamps_.size()); } - const int robot_id; - -private: - std::unique_ptr receiver_; - - std::vector buffer_; - - RobotFeedback robot_feedback; - - rclcpp::Clock clock; - - // パケット受信頻度計算用のタイムスタンプキュー + std::unique_ptr async_receiver_; + std::mutex mutex_; + std::optional latest_feedback_; std::deque receive_timestamps_; + rclcpp::Clock clock; }; class RobotReceiverNode : public rclcpp::Node { public: explicit RobotReceiverNode(const rclcpp::NodeOptions & options = rclcpp::NodeOptions()) - : rclcpp::Node("robot_receiver_node", options), clock(RCL_ROS_TIME) + : rclcpp::Node("robot_receiver_node", options), + work_guard_(asio::make_work_guard(io_context_)), + clock(RCL_ROS_TIME) { crane::CraneVisualizerBuffer::activate(*this); publisher = create_publisher("/robot_feedback", 10); @@ -169,63 +220,82 @@ class RobotReceiverNode : public rclcpp::Node for (int i = 0; i <= max_robot_id; i++) { std::string ip; if (sim_mode) { - // シミュレータモード: 127.0.0.1 (固定) ip = "127.0.0.1"; } else { - // 実機モード: 224.5.20.{100+robot_id} ip = std::format("{}.{}", ip_base, i + ip_offset); } int port = port_base + i; - receivers.push_back(std::make_shared(ip, port)); + receivers.push_back(std::make_shared(io_context_, ip, port)); } + // asioイベントループを専用スレッドで開始 + io_thread_ = std::thread([this]() { io_context_.run(); }); + using std::chrono::operator""ms; + using std::chrono::operator""s; + log_timer = rclcpp::create_timer(this, get_clock(), 5s, [&]() { + for (auto & receiver : receivers) { + float freq = receiver->getPacketFrequency(); + if (freq <= 0.f) continue; + auto stats = receiver->getReceiveIntervalStats(); + RCLCPP_INFO( + get_logger(), + "[Robot %d] interval: mean=%.1fms, min=%.1fms, max=%.1fms, stddev=%.1fms, freq=%.1fHz", + receiver->robot_id, stats.mean_ms, stats.min_ms, stats.max_ms, stats.stddev_ms, freq); + } + }); + timer = rclcpp::create_timer(this, get_clock(), 10ms, [&]() { crane_msgs::msg::RobotFeedbackArray msg; auto now = clock.now(); for (auto & receiver : receivers) { - while (receiver->receive()) { - receiver->updateFeedback(); - } + auto robot_feedback = receiver->getLatestFeedback(); + if (!robot_feedback) continue; // 古いデータは入れない(100msより古いデータはVisionより価値が薄い可能性が高い) - if ( - auto robot_feedback = receiver->getFeedback(); - (now - robot_feedback.received_stamp) < 100ms) { - crane_msgs::msg::RobotFeedback robot_feedback_msg; - robot_feedback_msg.received_stamp = robot_feedback.received_stamp; - robot_feedback_msg.robot_id = receiver->robot_id; - robot_feedback_msg.packet_frequency_hz = receiver->getPacketFrequency(); - robot_feedback_msg.counter = robot_feedback.counter; - robot_feedback_msg.kick_state = robot_feedback.kick_state; - robot_feedback_msg.temperatures.assign( - std::begin(robot_feedback.temperature), std::end(robot_feedback.temperature)); - - robot_feedback_msg.error_id = robot_feedback.error_id; - robot_feedback_msg.error_info = robot_feedback.error_info; - robot_feedback_msg.error_value = robot_feedback.error_value; - - robot_feedback_msg.motor_current.assign( - std::begin(robot_feedback.motor_current), std::end(robot_feedback.motor_current)); - robot_feedback_msg.ball_detection.assign( - std::begin(robot_feedback.ball_detection), std::end(robot_feedback.ball_detection)); - robot_feedback_msg.ball_sensor = (robot_feedback_msg.ball_detection[0] == 1); - robot_feedback_msg.yaw_angle = robot_feedback.yaw_angle; - robot_feedback_msg.diff_angle = robot_feedback.diff_angle; - robot_feedback_msg.odom.assign(robot_feedback.odom.begin(), robot_feedback.odom.end()); - robot_feedback_msg.odom_speed.assign( - robot_feedback.odom_speed.begin(), robot_feedback.odom_speed.end()); - robot_feedback_msg.mouse_odom.assign( - robot_feedback.mouse_odom.begin(), robot_feedback.mouse_odom.end()); - robot_feedback_msg.mouse_vel.assign( - robot_feedback.mouse_vel.begin(), robot_feedback.mouse_vel.end()); - robot_feedback_msg.voltage.assign( - robot_feedback.voltage.begin(), robot_feedback.voltage.end()); - robot_feedback_msg.check_ver = robot_feedback.check_ver; - robot_feedback_msg.values = robot_feedback.values; - msg.feedback.push_back(robot_feedback_msg); + using std::chrono::operator""ms; + if ((now - robot_feedback->received_stamp) >= 100ms) continue; + + crane_msgs::msg::RobotFeedback robot_feedback_msg; + robot_feedback_msg.received_stamp = robot_feedback->received_stamp; + robot_feedback_msg.robot_id = receiver->robot_id; + robot_feedback_msg.packet_frequency_hz = receiver->getPacketFrequency(); + { + auto stats = receiver->getReceiveIntervalStats(); + robot_feedback_msg.receive_interval_mean_ms = stats.mean_ms; + robot_feedback_msg.receive_interval_min_ms = stats.min_ms; + robot_feedback_msg.receive_interval_max_ms = stats.max_ms; + robot_feedback_msg.receive_interval_stddev_ms = stats.stddev_ms; } + robot_feedback_msg.counter = robot_feedback->counter; + robot_feedback_msg.kick_state = robot_feedback->kick_state; + robot_feedback_msg.temperatures.assign( + std::begin(robot_feedback->temperature), std::end(robot_feedback->temperature)); + + robot_feedback_msg.error_id = robot_feedback->error_id; + robot_feedback_msg.error_info = robot_feedback->error_info; + robot_feedback_msg.error_value = robot_feedback->error_value; + + robot_feedback_msg.motor_current.assign( + std::begin(robot_feedback->motor_current), std::end(robot_feedback->motor_current)); + robot_feedback_msg.ball_detection.assign( + std::begin(robot_feedback->ball_detection), std::end(robot_feedback->ball_detection)); + robot_feedback_msg.ball_sensor = (robot_feedback_msg.ball_detection[0] == 1); + robot_feedback_msg.yaw_angle = robot_feedback->yaw_angle; + robot_feedback_msg.diff_angle = robot_feedback->diff_angle; + robot_feedback_msg.odom.assign(robot_feedback->odom.begin(), robot_feedback->odom.end()); + robot_feedback_msg.odom_speed.assign( + robot_feedback->odom_speed.begin(), robot_feedback->odom_speed.end()); + robot_feedback_msg.mouse_odom.assign( + robot_feedback->mouse_odom.begin(), robot_feedback->mouse_odom.end()); + robot_feedback_msg.mouse_vel.assign( + robot_feedback->mouse_vel.begin(), robot_feedback->mouse_vel.end()); + robot_feedback_msg.voltage.assign( + robot_feedback->voltage.begin(), robot_feedback->voltage.end()); + robot_feedback_msg.check_ver = robot_feedback->check_ver; + robot_feedback_msg.values = robot_feedback->values; + msg.feedback.push_back(robot_feedback_msg); } publisher->publish(msg); visualizer->flush(); @@ -233,7 +303,19 @@ class RobotReceiverNode : public rclcpp::Node }); } + ~RobotReceiverNode() + { + work_guard_.reset(); + io_context_.stop(); + if (io_thread_.joinable()) io_thread_.join(); + } + + asio::io_context io_context_; + asio::executor_work_guard work_guard_; + std::thread io_thread_; + rclcpp::TimerBase::SharedPtr timer; + rclcpp::TimerBase::SharedPtr log_timer; std::vector> receivers; diff --git a/crane_robot_skills/src/goalie.cpp b/crane_robot_skills/src/goalie.cpp index 2620928ec..b8c955cd7 100644 --- a/crane_robot_skills/src/goalie.cpp +++ b/crane_robot_skills/src/goalie.cpp @@ -21,7 +21,7 @@ void Goalie::initialize() { setParameter("run_inplay", true); setParameter("block_distance", 0.5); - setParameter("emit_force_timeout", 3.0); + setParameter("emit_force_timeout", 1.5); setParameter("emit_enemy_distance_threshold", 0.5); setParameter("emit_ball_speed_threshold", 0.5); } diff --git a/crane_robot_skills/src/kick.cpp b/crane_robot_skills/src/kick.cpp index 1ed3396d0..5090a4fdf 100644 --- a/crane_robot_skills/src/kick.cpp +++ b/crane_robot_skills/src/kick.cpp @@ -76,6 +76,7 @@ void Kick::initialize() return computeAroundBallApproachTargetDynamic( ball_pos, safe_target, robot()->pose.pos, interval, max_interval); }(); + approach += (approach - robot()->pose.pos) * 1.0; command->disableBallAvoidance(); using boost::math::constants::degree; diff --git a/crane_robot_skills/src/single_ball_placement.cpp b/crane_robot_skills/src/single_ball_placement.cpp index 76cf7279f..f5ed5a675 100644 --- a/crane_robot_skills/src/single_ball_placement.cpp +++ b/crane_robot_skills/src/single_ball_placement.cpp @@ -291,7 +291,7 @@ void SingleBallPlacement::initialize() command->dribble(0.0); command->setOmegaLimit(10.0); - if (command->getTargetDistance() < 0.02) { + if (command->getTargetDistance() < 0.05) { skill_status = Status::SUCCESS; } else { skill_status = Status::RUNNING; diff --git a/crane_sender/include/crane_sender/sim_sender.hpp b/crane_sender/include/crane_sender/sim_sender.hpp index 2676dec45..b31754051 100644 --- a/crane_sender/include/crane_sender/sim_sender.hpp +++ b/crane_sender/include/crane_sender/sim_sender.hpp @@ -67,7 +67,7 @@ class SimSenderComponent : public SenderBase p_gain.getValue(), i_gain.getValue(), d_gain.getValue(), i_saturation_); state.theta_controller.setGain( theta_p_gain.getValue(), theta_i_gain.getValue(), theta_d_gain.getValue()); - state.previous_velocity = Velocity::Zero(); + state.previous_velocity_global = Velocity::Zero(); } // the parameters of the PID controller @@ -109,7 +109,10 @@ class SimSenderComponent : public SenderBase PIDController vx_controller; PIDController vy_controller; PIDController theta_controller; - Velocity previous_velocity; + // 前回出力した速度をグローバルフレームで保持する。 + // ローカルフレームで保持するとロボット回転時にフレーム不一致が生じ、 + // Δv の計算が誤るため、グローバルフレームで統一する。 + Velocity previous_velocity_global; }; // 全ロボットの状態 diff --git a/crane_sender/src/sender_base.cpp b/crane_sender/src/sender_base.cpp index d27f0b31a..f66de8a77 100644 --- a/crane_sender/src/sender_base.cpp +++ b/crane_sender/src/sender_base.cpp @@ -50,6 +50,12 @@ SenderBase::SenderBase(const std::string & name, const rclcpp::NodeOptions & opt get_parameter("robot_acceleration.velocity_threshold", robot_acceleration_velocity_threshold_); world_model = std::make_shared(*this); + + RCLCPP_INFO( + get_logger(), + "[DIAG] robot_acceleration: acc=%.2f decel_high=%.2f decel_low=%.2f thresh=%.2f", + robot_acceleration_acceleration_, robot_acceleration_deceleration_high_, + robot_acceleration_deceleration_low_, robot_acceleration_velocity_threshold_); } void SenderBase::callback(const VelocityCommandsMsg & msg) diff --git a/crane_sender/src/sim_sender.cpp b/crane_sender/src/sim_sender.cpp index e70bd3edf..7a2dde554 100644 --- a/crane_sender/src/sim_sender.cpp +++ b/crane_sender/src/sim_sender.cpp @@ -58,8 +58,14 @@ void SimSenderComponent::sendCommands(const crane_msgs::msg::RobotCommands & msg Eigen::Vector2d target_vel_local( target_velocity_r * std::cos(velocity_theta), target_velocity_r * std::sin(velocity_theta)); - // Step 2: ローカル座標系での現在速度(前回の出力速度を使用) - Eigen::Vector2d current_vel_local = robot_states_[command.robot_id].previous_velocity; + // Step 2: 前回の出力速度(グローバルフレーム)を現在のローカルフレームへ変換 + // グローバル→ローカル変換: R(-θ) = [[cos θ, sin θ], [-sin θ, cos θ]] + const Eigen::Vector2d & prev_vel_global = + robot_states_[command.robot_id].previous_velocity_global; + Eigen::Vector2d current_vel_local( + prev_vel_global.x() * std::cos(current_theta) + prev_vel_global.y() * std::sin(current_theta), + -prev_vel_global.x() * std::sin(current_theta) + + prev_vel_global.y() * std::cos(current_theta)); // Step 3: 加速度制限を選択(速度の大きさで加速/減速を判定) double current_speed = current_vel_local.norm(); @@ -82,8 +88,11 @@ void SimSenderComponent::sendCommands(const crane_msgs::msg::RobotCommands & msg move_local_velocity->set_forward(vx); move_local_velocity->set_left(vy); - // 次回の計算用に保存 - robot_states_[command.robot_id].previous_velocity = Velocity(vx, vy); + // 次回の計算用に保存(ローカル→グローバルへ変換) + // グローバル→ローカル変換の逆: R(θ) = [[cos θ, -sin θ], [sin θ, cos θ]] + robot_states_[command.robot_id].previous_velocity_global = Velocity( + vx * std::cos(current_theta) - vy * std::sin(current_theta), + vx * std::sin(current_theta) + vy * std::cos(current_theta)); // ストップ if (command.stop_flag) { diff --git a/crane_session_coordinator/config/unified_session_config.yaml b/crane_session_coordinator/config/unified_session_config.yaml index 7f297111f..7c0134480 100644 --- a/crane_session_coordinator/config/unified_session_config.yaml +++ b/crane_session_coordinator/config/unified_session_config.yaml @@ -16,15 +16,15 @@ situations: - name: defender max_robots: 3 params: - reduced_robots: 2 + reduced_robots: 1 - name: pass_receive max_robots: 1 - - name: second_threat_defender - max_robots: 1 - name: sub_attacker_skill max_robots: 1 + - name: second_threat_defender + max_robots: 1 - name: marker - max_robots: 3 + max_robots: 2 - name: forward max_robots: 20 diff --git a/crane_sessions/config/test_planner.yaml b/crane_sessions/config/test_planner.yaml index 94e62f898..fb8a2611d 100644 --- a/crane_sessions/config/test_planner.yaml +++ b/crane_sessions/config/test_planner.yaml @@ -16,7 +16,7 @@ waypoints: # - position: [-3.5, -2.0, -90] # - position: [-3.5, 2.0, 0] # - position: [0.0, 0.0] # 角度なし(位置のみ) - - position: [4.5, 2.5] - - position: [-4.5, 2.5] + - position: [2.0, 2.5] + - position: [2.0, -2.5] # このプランナで動かす自チームロボットID -robot_id: 0 +robot_id: 10 diff --git a/crane_sessions/include/crane_sessions/session_base.hpp b/crane_sessions/include/crane_sessions/session_base.hpp index c9d882dd5..901c5bfc9 100644 --- a/crane_sessions/include/crane_sessions/session_base.hpp +++ b/crane_sessions/include/crane_sessions/session_base.hpp @@ -117,9 +117,12 @@ class SessionBase msg.is_yellow = world_model->isYellow(); msg.on_positive_half = world_model->onPositiveHalf(); for (auto command : position_commands) { - // WorldModelから現在の速度情報を取得して設定 + // WorldModelから現在の位置・速度情報を取得して設定 auto robot = world_model->getOurRobot(command.robot_id); if (robot) { + command.current_pose.x = robot->pose.pos.x(); + command.current_pose.y = robot->pose.pos.y(); + command.current_pose.theta = robot->pose.theta; command.current_velocity.x = robot->vel.linear.x(); command.current_velocity.y = robot->vel.linear.y(); command.current_velocity.theta = robot->vel.omega; diff --git a/docker/dev/docker-compose.yaml b/docker/dev/docker-compose.yaml index 7b00d8ab5..c7077f5d5 100644 --- a/docker/dev/docker-compose.yaml +++ b/docker/dev/docker-compose.yaml @@ -1,6 +1,6 @@ services: grsim: - image: ghcr.io/ibis-ssl/grsim:latest + image: grsim-local:dev container_name: grsim network_mode: host command: | @@ -70,8 +70,3 @@ services: # image: voicevox/voicevox_engine:nvidia-latest ports: - 127.0.0.1:50021:50021 - - aivis-speech: - image: ghcr.io/aivis-project/aivisspeech-engine:cpu-latest - ports: - - 127.0.0.1:10101:10101 diff --git a/utility/crane_comm/include/crane_comm/unicast.hpp b/utility/crane_comm/include/crane_comm/unicast.hpp index 2dd0c3571..fe158aacf 100644 --- a/utility/crane_comm/include/crane_comm/unicast.hpp +++ b/utility/crane_comm/include/crane_comm/unicast.hpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -148,6 +149,108 @@ class UdpReceiver asio::ip::udp::socket socket; }; +class AsyncUdpReceiver +{ +public: + AsyncUdpReceiver( + asio::io_context & io_ctx, const std::string & host, const int port, size_t buffer_size = 2048) + : socket_(io_ctx, asio::ip::udp::v4()), recv_buffer_(buffer_size) + { + asio::ip::address addr = asio::ip::address::from_string(host); + boost::system::error_code ec; + + socket_.set_option(asio::socket_base::reuse_address(true), ec); + socket_.set_option(reuse_port(true), ec); + + if (addr.is_multicast()) { + socket_.bind(asio::ip::udp::endpoint(asio::ip::udp::v4(), port), ec); + if (ec) { + RCLCPP_ERROR( + rclcpp::get_logger("crane_comm"), "[ERROR] bind失敗: %s", ec.message().c_str()); + throw std::runtime_error("bind failed: " + ec.message()); + } + + try { + struct ifaddrs * interfaces = nullptr; + if (getifaddrs(&interfaces) == -1) { + throw std::runtime_error("Error: getifaddrs failed."); + } + + std::unordered_set joined_interfaces; + for (struct ifaddrs * ifa = interfaces; ifa != nullptr; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == nullptr || ifa->ifa_addr->sa_family != AF_INET) continue; + + char ip[INET_ADDRSTRLEN]; + auto * addr_in = reinterpret_cast(ifa->ifa_addr); + inet_ntop(AF_INET, &(addr_in->sin_addr), ip, INET_ADDRSTRLEN); + + std::string if_name(ifa->ifa_name); + if (joined_interfaces.count(if_name) > 0) continue; + + boost::asio::ip::detail::socket_option::multicast_request< + IPPROTO_IP, IP_ADD_MEMBERSHIP, IPPROTO_IPV6, IPV6_JOIN_GROUP> + join_device(addr.to_v4(), asio::ip::address::from_string(ip).to_v4()); + + boost::system::error_code join_ec; + socket_.set_option(join_device, join_ec); + if (!join_ec) { + joined_interfaces.insert(if_name); + } + } + freeifaddrs(interfaces); + } catch (std::exception & e) { + RCLCPP_ERROR(rclcpp::get_logger("crane_comm"), "%s", e.what()); + } + } else { + socket_.bind(asio::ip::udp::endpoint(addr, port), ec); + if (ec) { + RCLCPP_ERROR( + rclcpp::get_logger("crane_comm"), "[ERROR] bind失敗: %s", ec.message().c_str()); + throw std::runtime_error("bind failed: " + ec.message()); + } + } + } + + void startReceive(std::function &, size_t)> callback) + { + callback_ = std::move(callback); + running_ = true; + doReceive(); + } + + void stop() + { + running_ = false; + boost::system::error_code ec; + socket_.cancel(ec); + } + +private: + void doReceive() + { + socket_.async_receive_from( + asio::buffer(recv_buffer_), sender_endpoint_, + [this](const boost::system::error_code & ec, size_t bytes_transferred) { + if (!ec) { + try { + callback_(recv_buffer_, bytes_transferred); + } catch (const std::exception & e) { + RCLCPP_ERROR(rclcpp::get_logger("crane_comm"), "受信コールバックエラー: %s", e.what()); + } + } else if (ec != asio::error::operation_aborted) { + RCLCPP_ERROR(rclcpp::get_logger("crane_comm"), "UDP受信エラー: %s", ec.message().c_str()); + } + if (running_) doReceive(); + }); + } + + asio::ip::udp::socket socket_; + asio::ip::udp::endpoint sender_endpoint_; + std::vector recv_buffer_; + std::function &, size_t)> callback_; + bool running_ = false; +}; + } // namespace crane #endif // CRANE_COMM__UNICAST_HPP_ diff --git a/utility/crane_msg_wrappers/include/crane_msg_wrappers/robot_command_wrapper.hpp b/utility/crane_msg_wrappers/include/crane_msg_wrappers/robot_command_wrapper.hpp index d41dc28dc..790e9ed6d 100644 --- a/utility/crane_msg_wrappers/include/crane_msg_wrappers/robot_command_wrapper.hpp +++ b/utility/crane_msg_wrappers/include/crane_msg_wrappers/robot_command_wrapper.hpp @@ -291,18 +291,6 @@ class RobotCommandWrapper : public CommandWrapperBase, return *this; } - auto enableRotationStopOnAccel() -> RobotCommandWrapper & - { - latest_msg.local_planner_config.enable_rotation_stop_on_accel = true; - return *this; - } - - auto disableRotationStopOnAccel() -> RobotCommandWrapper & - { - latest_msg.local_planner_config.enable_rotation_stop_on_accel = false; - return *this; - } - auto disableAnyAreaAvoidance() -> RobotCommandWrapper & { return disableGoalAreaAvoidance()