ROS/myrobot_pkg/scripts/controller.py
2025-12-10 23:40:01 +09:00

167 lines
6.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import rospy
import actionlib
import threading
from move_base_msgs.msg import MoveBaseAction, MoveBaseGoal
from geometry_msgs.msg import PoseStamped, PointStamped, Twist
from visualization_msgs.msg import Marker, MarkerArray
class MissionController:
def __init__(self):
rospy.init_node('nav_controller')
# --- 設定 ---
self.waypoint_queue = []
self.current_goal_idx = 0
self.mission_complete = False
# --- ROS Publisher ---
# 1. 速度控制 (強制停車用) - 提高 queue_size 確保指令送達
self.cmd_vel_pub = rospy.Publisher('/cmd_vel', Twist, queue_size=10)
# 2. 視覺化標記
self.marker_pub = rospy.Publisher('/waypoint_markers', MarkerArray, queue_size=10)
# --- ROS Subscriber ---
rospy.Subscriber('/clicked_point', PointStamped, self.click_callback)
# --- 導航客戶端 ---
self.client = actionlib.SimpleActionClient('move_base', MoveBaseAction)
rospy.loginfo("正在連接 Move Base Server...")
self.client.wait_for_server()
rospy.loginfo("導航系統已連線!")
# --- 讀取參數 ---
self.load_waypoints_from_param()
# 啟動監控執行緒
self.monitor_thread = threading.Thread(target=self.mission_loop)
self.monitor_thread.start()
def load_waypoints_from_param(self):
point_list = rospy.get_param('~waypoints', [])
if point_list:
rospy.loginfo(f"讀取到 {len(point_list)} 個路徑點。")
for p in point_list:
self.add_waypoint(p[0], p[1])
else:
rospy.logwarn("無預設路徑點。")
def add_waypoint(self, x, y):
target_pose = PoseStamped()
target_pose.header.frame_id = "map"
target_pose.header.stamp = rospy.Time.now()
target_pose.pose.position.x = x
target_pose.pose.position.y = y
target_pose.pose.orientation.w = 1.0
self.waypoint_queue.append(target_pose)
rospy.loginfo(f"加入點: ({x}, {y})")
self.publish_markers()
def click_callback(self, msg):
# 如果任務已結束,允許手動點擊來重置並開始新任務
if self.mission_complete:
rospy.loginfo("檢測到新輸入,重置任務狀態...")
self.mission_complete = False
self.current_goal_idx = 0
self.waypoint_queue = [] # 清空舊的
self.add_waypoint(msg.point.x, msg.point.y)
def publish_markers(self):
marker_array = MarkerArray()
if self.current_goal_idx >= len(self.waypoint_queue):
delete_all = Marker()
delete_all.action = Marker.DELETEALL
marker_array.markers.append(delete_all)
self.marker_pub.publish(marker_array)
return
line_marker = Marker()
line_marker.header.frame_id = "map"
line_marker.type = Marker.LINE_STRIP
line_marker.action = Marker.ADD
line_marker.id = 999
line_marker.scale.x = 0.05
line_marker.color.r, line_marker.color.g, line_marker.color.b, line_marker.color.a = 0.0, 0.0, 1.0, 0.8
for i in range(self.current_goal_idx, len(self.waypoint_queue)):
p = self.waypoint_queue[i].pose.position
line_marker.points.append(p)
text_marker = Marker()
text_marker.header.frame_id = "map"
text_marker.type = Marker.TEXT_VIEW_FACING
text_marker.action = Marker.ADD
text_marker.id = i
text_marker.pose.position.x, text_marker.pose.position.y, text_marker.pose.position.z = p.x, p.y, 0.5
text_marker.text = f"P{i+1}"
text_marker.scale.z = 0.3
text_marker.color.r, text_marker.color.g, text_marker.color.b, text_marker.color.a = 1.0, 0.0, 0.0, 1.0
marker_array.markers.append(text_marker)
marker_array.markers.append(line_marker)
self.marker_pub.publish(marker_array)
def stop_robot(self):
"""持續發送速度 0 的指令"""
stop_msg = Twist()
stop_msg.linear.x = 0.0
stop_msg.linear.y = 0.0
stop_msg.angular.z = 0.0
self.cmd_vel_pub.publish(stop_msg)
def execute_navigation(self, target_pose):
goal = MoveBaseGoal()
goal.target_pose = target_pose
self.client.send_goal(goal)
self.client.wait_for_result()
return self.client.get_state() == actionlib.GoalStatus.SUCCEEDED
def mission_loop(self):
# 提高頻率到 10Hz讓煞車指令更密集
rate = rospy.Rate(10)
rospy.sleep(1.0)
while not rospy.is_shutdown():
if self.current_goal_idx < len(self.waypoint_queue):
# --- 導航中 ---
self.publish_markers()
target = self.waypoint_queue[self.current_goal_idx]
rospy.loginfo(f"--- 前往 P{self.current_goal_idx + 1} ---")
success = self.execute_navigation(target)
if success:
rospy.loginfo(f"P{self.current_goal_idx + 1} 到達!")
else:
rospy.logwarn("導航受阻,跳過此點。")
self.current_goal_idx += 1
else:
# --- 任務結束 ---
if len(self.waypoint_queue) > 0:
if not self.mission_complete:
rospy.loginfo(">>> 任務全部完成,執行強制停車。")
self.publish_markers() # 清除標記
# 1. 告訴 Move Base 取消所有目標 (不要再嘗試對齊方向)
self.client.cancel_all_goals()
self.mission_complete = True
# 2. 【關鍵修改】在迴圈內持續呼叫 stop_robot
# 只要任務結束每一輪迴圈都會發送速度0
# 這會強制覆蓋掉任何滑行或旋轉的殘留指令
self.stop_robot()
rate.sleep()
if __name__ == '__main__':
try:
MissionController()
rospy.spin()
except rospy.ROSInterruptException:
pass