167 lines
6.3 KiB
Python
167 lines
6.3 KiB
Python
#!/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 |