diff --git a/simulator/simulator/models/ai_car_caster_trail/model.sdf b/simulator/simulator/models/ai_car_caster_trail/model.sdf index a6da54b..abb7d59 100644 --- a/simulator/simulator/models/ai_car_caster_trail/model.sdf +++ b/simulator/simulator/models/ai_car_caster_trail/model.sdf @@ -463,6 +463,7 @@ 30 wheel_left_joint wheel_right_joint + wheel_caster_steering_joint diff --git a/simulator/simulator/simulator/line_follower_caster.py b/simulator/simulator/simulator/line_follower_caster.py new file mode 100644 index 0000000..ec57e67 --- /dev/null +++ b/simulator/simulator/simulator/line_follower_caster.py @@ -0,0 +1,149 @@ +import rclpy +from rclpy.node import Node +from geometry_msgs.msg import Twist +from nav_msgs.msg import Odometry +from math import sqrt, atan2, sin, cos, asin +from sensor_msgs.msg import JointState + +class PurePursuitNode(Node): + def __init__(self): + super().__init__('pure_pursuit_node') + + # オドメトリのサブスクライバー + self.odom_subscriber = self.create_subscription(Odometry, '/p3d/odom', self.odom_callback, 1) + + # joint_stateのサブスクライバー + self.joint_subscriber = self.create_subscription(JointState, '/joint_states', self.joint_callback, 1) + + # cmd_velのパブリッシャー + self.cmd_vel_publisher = self.create_publisher(Twist, '/cmd_vel', 1) + + # Pure Pursuitアルゴリズムのパラメータ + self.lookahead_distance = 1.0 # Lookahead distance (meters) + self.linear_velocity = 0.5 # 前進速度 (m/s) + self.goal_threshold = 0.1 # ゴール位置の閾値 (meters) + + # 直線の始点と終点を設定 (例: x=0, y=0 から x=10, y=0) + self.target_line_start = (0, 1) + self.target_line_end = (100, 1) + + self.current_position = None + self.current_orientation = None + self.caster_steering_angle = None + self.caster_steering_angular_velocity = None + + # 制御ループ + self.timer = self.create_timer(0.1, self.control_loop) + + def odom_callback(self, msg): + """オドメトリのコールバック""" + self.current_position = msg.pose.pose.position + orientation = msg.pose.pose.orientation + + # 四元数からヨー角に変換(ここでは2Dなのでyaw角のみ使う) + siny_cosp = 2 * (orientation.w * orientation.z + orientation.x * orientation.y) + cosy_cosp = 1 - 2 * (orientation.y * orientation.y + orientation.z * orientation.z) + self.current_orientation = atan2(siny_cosp, cosy_cosp) + + def joint_callback(self, msg): + """joint_stateのコールバック""" + if 'wheel_caster_steering_joint' in msg.name: + index = msg.name.index('wheel_caster_steering_joint') + self.caster_steering_angle = msg.position[index] + self.caster_steering_angular_velocity = msg.velocity[index] + + def control_loop(self): + """Pure Pursuitアルゴリズムの実行ループ""" + if self.current_position is None or self.current_orientation is None: + return + + # 現在位置 + robot_x = self.current_position.x + robot_y = self.current_position.y + + # Lookahead点を計算 + goal_x, goal_y = self.calculate_lookahead_point(robot_x, robot_y) + + # ゴールまでの距離 + distance_to_goal = sqrt((goal_x - robot_x) ** 2 + (goal_y - robot_y) ** 2) + + # ゴールに近づいた場合は停止 + if distance_to_goal < self.goal_threshold: + self.publish_cmd_vel(0.0, 0.0) + self.get_logger().info('Goal reached') + return + + # ロボットのヨー角と目標点までの方位角を計算 + heading_to_goal = atan2(goal_y - robot_y, goal_x - robot_x) + + # 進行方向との差分 + angle_difference = heading_to_goal - self.current_orientation + + # cmd_velの生成 + angular_velocity = 2.0 * angle_difference # 回転速度制御 + + # caster steering の角度による補正 + wheelbase = 0.65 + target_caster_steering_angle = -asin(max(min(wheelbase * angular_velocity / self.linear_velocity, 1.0), -1.0)) + diff_steering_angle = target_caster_steering_angle - self.caster_steering_angle + print("target: "+str(target_caster_steering_angle)+", current: "+str(self.caster_steering_angle)+", diff_steering_angle: "+str(diff_steering_angle)) + if abs(diff_steering_angle) > 0.2: + self.linear_velocity = 0.1 + else: + self.linear_velocity = 0.5 + self.publish_cmd_vel(self.linear_velocity, angular_velocity) + + def calculate_lookahead_point(self, robot_x, robot_y): + """直線上のLookaheadポイントを計算""" + x1, y1 = self.target_line_start + x2, y2 = self.target_line_end + + # 直線のパラメータ (Ax + By + C = 0の形式) + A = y2 - y1 + B = x1 - x2 + C = x2 * y1 - x1 * y2 + + # ロボットから直線への垂線の距離 + distance_to_line = (A * robot_x + B * robot_y + C) / sqrt(A**2 + B**2) + + # 直線に沿ったLookaheadポイントの位置を計算 + # ロボットから直線上の最も近い点を見つけ、その点からlookahead距離を加えた点 + dx = x2 - x1 + dy = y2 - y1 + line_length = sqrt(dx**2 + dy**2) + + if line_length == 0: + return x1, y1 + + # 直線上の進行方向のベクトルを正規化 + dx /= line_length + dy /= line_length + + # ロボットの現在位置とLookahead距離を使って目標点を計算 + closest_point_x = robot_x - A * distance_to_line / sqrt(A**2 + B**2) + closest_point_y = robot_y - B * distance_to_line / sqrt(A**2 + B**2) + + # Lookahead距離分進んだ点を目標点とする + goal_x = closest_point_x + dx * self.lookahead_distance + goal_y = closest_point_y + dy * self.lookahead_distance + + return goal_x, goal_y + + def publish_cmd_vel(self, linear_velocity, angular_velocity): + """cmd_velトピックにTwistメッセージをパブリッシュ""" + cmd_vel_msg = Twist() + cmd_vel_msg.linear.x = linear_velocity + cmd_vel_msg.angular.z = angular_velocity + self.cmd_vel_publisher.publish(cmd_vel_msg) + + +def main(args=None): + rclpy.init(args=args) + node = PurePursuitNode() + rclpy.spin(node) + node.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/simulator/vehicle/launch/extrinsic_tfstatic_broadcaster_no_namespace.launch.py b/simulator/vehicle/launch/extrinsic_tfstatic_broadcaster_no_namespace.launch.py index f0f2934..c6b69ee 100644 --- a/simulator/vehicle/launch/extrinsic_tfstatic_broadcaster_no_namespace.launch.py +++ b/simulator/vehicle/launch/extrinsic_tfstatic_broadcaster_no_namespace.launch.py @@ -104,5 +104,5 @@ def generate_launch_description(): return LaunchDescription([ *launch_args, robot_state_publisher, - joint_state_publisher, + #joint_state_publisher, ])