13 Commits
Author SHA1 Message Date
chiyu1468 d21bc6b069 Merge branch 'wenchun'
march 11 alignment
2025-03-11 13:40:18 +08:00
chiyu1468 c9802e3eb1 Merge branch 'lunu'
march 11 alignment
2025-03-11 13:39:45 +08:00
chiyu1468 4d8ffc4a95 Merge branch 'ken'
march 11 alignment
2025-03-11 13:30:39 +08:00
chiyu1468 ac272a6c3d Enhance mavlinkObject with multiplexing capabilities and add error handling; update README with team member assignments 2025-03-10 17:25:27 +08:00
lenting89 ff5c7718be 轉二進位封包 2025-03-10 16:13:28 +08:00
chiyu1468 cd9d055409 temp commit for share code 2025-03-07 16:41:05 +08:00
wenchun 4f9e6fa51e Gazebo物件生成 2025-02-26 11:13:15 +08:00
ken910606 ee4cf1d07c Update 'src/unitdev01/unitdev01/mavlink.py' 2025-02-22 00:03:11 +08:00
ken910606 b8965c0bd6 Update 'src/unitdev01/unitdev01/interface.py' 2025-02-22 00:02:50 +08:00
ken910606 a8b36c391f Update 'src/unitdev01/unitdev01/interface.py' 2025-02-22 00:01:35 +08:00
wenchun a8968e25b3 mavlink單機位置控制 2025-02-18 18:08:40 +08:00
chiyu1468 5c5cadc9da mavlink to gazebo devtest 02 2025-02-18 17:42:01 +08:00
wenchun c501189840 新增pymavlink_01.py 2025-02-18 17:38:54 +08:00
16 changed files with 1136 additions and 74 deletions
+10
View File
@@ -16,5 +16,15 @@
=== ===
Package 簡述 Package 簡述
1. unitdev 為各自協作者做開發時的測試區
01 -> 晉凱(ken)
02 -> 其宇(chiyu)
03 -> 文鈞
04 -> 倫渝
2. fc_network_adapter
建立、維護與飛控韌體的連接
構築 mavlink 封包
同時處理與 Gazebo 的 ardupilot_plugin 溝通的 FDM/JSON 訊息
處理無線模組的通訊格式 (XBee)
@@ -0,0 +1,197 @@
import threading
from pymavlink import mavutil
import queue
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
import time
'''
# 不同的匯流排只有單一種通訊協定
# 匯流排接到訊息後透過 queue stack 傳送到分析器
# 會有三種 Queue 分別作為 1. 固定串流分析器 2. 回傳封包處理器 3. 轉格式(例如 mavlink to FDM)的轉換器
# 第三種比較麻煩 會需要用 list 管理多個轉換器的 queue
# 分析器會將解析得到的結論 再透過 ros2 的 publisher 發送出去
# 關於 mavlink 採用 pymavlink 這個套件 製作匯流排
# pymavlink 的 socket 會由其他地方製作(例如 main) 再放到 mavlinkObject 裡面
# 這邊的 main 會是用來初始化 mavlinkObject 並啟動他的 run function
'''
fixed_stream_analyzer_queue = queue.Queue()
return_packet_processor_queue = queue.Queue()
converte_format_queues = []
class MavlinkObject():
def __init__(self, mavlink_socket, socket_id = None):
self.socket_id = socket_id
self.mavlink_socket = mavlink_socket
self.msg_count = {}
self.multiplexingList = []
self.multiplexingToAnalysis = [
# 0, # HEARTBEAT
30, # ATTITUDE
33, # GLOBAL_POSITION_INT
# 147, # BATTERY_STATUS
]
self.multiplexingToReturn = [
0, # HEARTBEAT
# 30, # ATTITUDE
]
self.multiplexingToConvert = [
[74, ] # VFR_HUD
]
def __del__(self):
self.mavlink_socket.close()
self.stop()
def run(self):
if not self.socket_id:
print('[mavlinkObject.py] Please set socket id before running')
return
self.thread = threading.Thread(target=self._run_thread)
self.running = self.updateMultiplexingList()
self.thread.start()
def stop(self):
self.running = False
def _run_thread(self):
start_time = time.time()
while self.running:
try:
mavlinkMsg = self.mavlink_socket.recv_msg()
except Exception as e:
print(f"[mavlinkObject.py] Error receiving mavlink data: {e}")
print(mavlinkMsg)
self.running = False
break
if mavlinkMsg:
self.count_mavlink_type(mavlinkMsg) # 統計收到的訊息
# print(mavlinkMsg) # data type 'pymavlink.dialects.v20.ardupilotmega.MAVLink_attitude_message' # debug
# break # Debug
# 將訊息依照 multiplexing list 分發到不同的 queue
for i in range(len(self.multiplexingList)):
if mavlinkMsg.get_msgId() in self.multiplexingList[i]:
if i == 0:
fixed_stream_analyzer_queue.put((self.socket_id,mavlinkMsg))
elif i == 1:
return_packet_processor_queue.put((self.socket_id,mavlinkMsg))
else:
convert_queue = converte_format_queues[i-2]
convert_queue.put((self.socket_id,mavlinkMsg))
# 每秒中 統計一次 收到的訊息總量
elapsed_time = time.time() - start_time
if elapsed_time > 1:
print('[mavlinkObject.py] Messages Type received (in about 1 sec) :')
print(self.msg_count)
start_time = time.time()
self.msg_count = {}
# thread 結束
print('[mavlinkObject.py] End of _run_thread')
def count_mavlink_type(self, mavlinkMsg):
msg_type = mavlinkMsg.get_type()
if msg_type in self.msg_count:
self.msg_count[msg_type] += 1
else:
self.msg_count[msg_type] = 1
def publish_mavlink_data(self, mavlinkMsg):
msg = String()
msg.data = str(mavlinkMsg)
self.publisher_.publish(msg)
def updateMultiplexingList(self):
# 更新 multiplexing list
self.multiplexingList = [self.multiplexingToAnalysis, self.multiplexingToReturn] + self.multiplexingToConvert
check = all(isinstance(i, list) and all(isinstance(j, int) for j in i) for i in self.multiplexingList)
# 若有錯誤則回傳 False
if not check:
print('[mavlinkObject.py] Multiplexing List Error, Please check the list')
return False
return True
class mavlink_analyzer(Node):
'''
這個 class 是用來接收 mavlink 訊息 並進行分析
記錄有 mavlink bus 上有那些 system id 和 component id
為了每個 system id 都有一個對應的 device object
並且看是否有重複 system id
也許可以用 node 去創建 這樣就可以直接進到 ros2 執行緒?
'''
def __init__(self):
super().__init__('mavlink_analyzer')
# self.publisher_ = self.create_publisher(String, 'mavlink_data', 10)
# self.mavlink_object = None
# self.create_mavlink_object()
def create_mavlink_object(self):
# connection_string="udp:
pass
class mavlink_device():
def __init__(self):
self.socket_id = None # 記錄來自於哪個 socket
self.sysid = None
self.component_count = 1
self.compid_list = []
def main(args=None):
connection_string="udp:127.0.0.1:14550"
mavlink_socket = mavutil.mavlink_connection(connection_string)
# 這邊是測試代碼 運行10秒 過程中把三個 queue 的資料印出來
mavlink_object = MavlinkObject(mavlink_socket, 1)
mavlink_object.multiplexingToAnalysis = [30] # only ATTITUDE
mavlink_object.multiplexingToReturn = [0] # only HEARTBEAT
mavlink_object.multiplexingToConvert = [[74,]] # only VFR_HUD
converte_format_queues.append(queue.Queue())
mavlink_object.run()
start_time = time.time()
while time.time() - start_time < 2:
while not fixed_stream_analyzer_queue.empty():
print('fixed_stream_analyzer_queue:')
t = fixed_stream_analyzer_queue.get()
print('from {} : {}'.format(t[0],t[1]))
while not return_packet_processor_queue.empty():
print('return_packet_processor_queue:')
t = return_packet_processor_queue.get()
print('from {} : {}'.format(t[0],t[1]))
for q in converte_format_queues:
while not q.empty():
print('converte_format_queues:')
t = q.get()
print('from {} : {}'.format(t[0],t[1]))
mavlink_object.running = False
mavlink_object.thread.join()
print('End of Program')
if __name__ == '__main__':
main()
@@ -0,0 +1,14 @@
'''
透過某個地方 得到 udp 或 uart 接口
對於每個接口 視為一個獨立的物件
物件對於不同的接口 是為不同類型的物件
每個類型的物件 創建一個獨立的執行緒 來處理資料
關於執行緒的實作 是寫在另一個模組
物件之間 也可以做資料轉換/轉拋
'''
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>fc_network_adapter</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="chiyu1468@hotmail.com">picars</maintainer>
<license>TODO: License declaration</license>
<test_depend>ament_copyright</test_depend>
<test_depend>ament_flake8</test_depend>
<test_depend>ament_pep257</test_depend>
<test_depend>python3-pytest</test_depend>
<export>
<build_type>ament_python</build_type>
</export>
</package>
+4
View File
@@ -0,0 +1,4 @@
[develop]
script_dir=$base/lib/fc_network_adapter
[install]
install_scripts=$base/lib/fc_network_adapter
+25
View File
@@ -0,0 +1,25 @@
from setuptools import find_packages, setup
package_name = 'fc_network_adapter'
setup(
name=package_name,
version='0.0.0',
packages=find_packages(exclude=['test']),
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='picars',
maintainer_email='chiyu1468@hotmail.com',
description='TODO: Package description',
license='TODO: License declaration',
tests_require=['pytest'],
entry_points={
'console_scripts': [
],
},
)
+234 -51
View File
@@ -1,4 +1,6 @@
import sys import sys
import time
import math
import rclpy import rclpy
from PyQt5 import QtWidgets from PyQt5 import QtWidgets
from PyQt5.QtCore import QThread, pyqtSignal from PyQt5.QtCore import QThread, pyqtSignal
@@ -14,26 +16,94 @@ class MAVLinkWorker(QThread):
imu_signal = pyqtSignal(str, float) imu_signal = pyqtSignal(str, float)
hdg_signal = pyqtSignal(str, float) hdg_signal = pyqtSignal(str, float)
groundspeed_signal = pyqtSignal(str, float) groundspeed_signal = pyqtSignal(str, float)
ping_signal = pyqtSignal(str, float)
loss_signal = pyqtSignal(str, float)
frequency_signal = pyqtSignal(str, float)
param_signal = pyqtSignal(str, int)
def __init__(self, connection_string="udp:127.0.0.1:14550"): def __init__(self, connection_string="udp:127.0.0.1:14550", usb='/dev/ttyUSB0'):
super().__init__() super().__init__()
self.connection = mavutil.mavlink_connection(connection_string) self.namespaces = ['UAV1', 'UAV4', 'UAV5']
self.connection = mavutil.mavlink_connection(usb, baud=57600)
self.connection.wait_heartbeat()
for sysid in self.namespaces:
sysid = int(sysid.replace('UAV', ''))
self.set_sr_params(sysid)
self.running = True self.running = True
self.uav_data = {}
# 設置需要監控的訊息類型
self.messages_to_monitor = ["HEARTBEAT", "BATTERY_STATUS", "GLOBAL_POSITION_INT", "GPS_RAW_INT", "LOCAL_POSITION_NED", "ATTITUDE", "VFR_HUD", "TIMESYNC"]
self.message_count = {}
self.frequency = {}
self.start_time = {}
# 用於計算丟包
self.seq_numbers = {}
self.packet_loss_count = {}
self.total_packet_count = {}
self.loss_percentage = {}
for namespace in self.namespaces:
self.request_param(namespace, "SR1_EXTRA1")
def run(self): def run(self):
while self.running: while self.running:
try: try:
msg = self.connection.recv_match(blocking=True) current_time = time.time()
msg = self.connection.recv_msg()
if not msg: if not msg:
continue continue
msg_type = msg.get_type()
sysid = msg.get_srcSystem() sysid = msg.get_srcSystem()
namespace = f"uav{sysid}" if sysid == 0:
continue
namespace = f"UAV{sysid}"
print(sysid)
msg_type = msg.get_type()
if msg_type =="BAD_DATA":
continue
if sysid not in self.uav_data: print(msg_type)
self.uav_data[sysid] = {"sysid": sysid}
# Packet loss calculation
if sysid not in self.seq_numbers:
self.seq_numbers[sysid] = msg.get_seq() # Initialize sequence number tracking
self.packet_loss_count[sysid] = 0
self.total_packet_count[sysid] = 1
else:
current_seq = msg.get_seq()
expected_seq = (self.seq_numbers[sysid] + 1) % 256 # MAVLink sequence numbers are modulo 256
self.total_packet_count[sysid] += 1
if current_seq != expected_seq: # Packet(s) lost
lost_packets = (current_seq - expected_seq) % 256
self.packet_loss_count[sysid] += lost_packets
self.total_packet_count[sysid] += lost_packets
self.seq_numbers[sysid] = current_seq
self.loss_percentage[sysid] = (self.packet_loss_count[sysid] / self.total_packet_count[sysid]) * 100
self.loss_signal.emit(namespace, self.loss_percentage[sysid])
# Frequency calculation
if sysid not in self.message_count:
self.message_count[sysid] = 0
self.start_time[sysid] = current_time
self.message_count[sysid] += 1
# 每隔一段時間更新
if current_time - self.start_time[sysid] >= 1:
# 每秒頻率
self.frequency[sysid] = self.message_count[sysid] / (current_time - self.start_time[sysid])
self.start_time[sysid] = current_time
self.message_count[sysid] = 0
self.frequency_signal.emit(namespace, self.frequency[sysid])
# 發送 timesync request
self.connection.mav.timesync_send(
0, #tc1
int(time.time() * 1e9) #ts1
)
#self.send_heartbeat
if msg_type == "HEARTBEAT": if msg_type == "HEARTBEAT":
mode = mavutil.mode_string_v10(msg) mode = mavutil.mode_string_v10(msg)
@@ -60,7 +130,7 @@ class MAVLinkWorker(QThread):
self.local_position_signal.emit(namespace, x, y, z) self.local_position_signal.emit(namespace, x, y, z)
elif msg_type == "ATTITUDE": elif msg_type == "ATTITUDE":
pitch = msg.pitch pitch = math.degrees(msg.pitch)
self.imu_signal.emit(namespace, pitch) self.imu_signal.emit(namespace, pitch)
elif msg_type == "VFR_HUD": elif msg_type == "VFR_HUD":
@@ -69,6 +139,16 @@ class MAVLinkWorker(QThread):
self.hdg_signal.emit(namespace, heading) self.hdg_signal.emit(namespace, heading)
self.groundspeed_signal.emit(namespace, groundspeed) self.groundspeed_signal.emit(namespace, groundspeed)
elif msg_type == "TIMESYNC":
round_trip_time = (int(time.time() * 1e9) - msg.ts1) / 1e6
if(round_trip_time<1e6):
self.ping_signal.emit(namespace, round_trip_time)
elif msg.get_type() == 'PARAM_VALUE':
param_name = "SR1_EXTRA1"
if msg.param_id == param_name:
self.param_signal.emit(namespace, msg.param_value)
except Exception as e: except Exception as e:
print(f"Error reading message: {e}") print(f"Error reading message: {e}")
@@ -77,7 +157,7 @@ class MAVLinkWorker(QThread):
self.connection.close() self.connection.close()
def set_mode(self, namespace, mode): def set_mode(self, namespace, mode):
sysid = int(namespace[-1]) sysid = int(namespace.replace('UAV', ''))
if mode == 'STABILIZE': if mode == 'STABILIZE':
mode = 0 mode = 0
elif mode == 'AUTO': elif mode == 'AUTO':
@@ -86,56 +166,47 @@ class MAVLinkWorker(QThread):
mode = 4 mode = 4
elif mode == 'LOITER': elif mode == 'LOITER':
mode = 5 mode = 5
self.connection.mav.set_mode_send( self.connection.mav.set_mode_send( #SET_MODE (11) — [DEP]
sysid, sysid,
mavutil.mavlink.MAV_MODE_FLAG_CUSTOM_MODE_ENABLED, mavutil.mavlink.MAV_MODE_FLAG_CUSTOM_MODE_ENABLED,
mode mode
) )
def arm(self, namespace, arm): def arm(self, namespace, arm):
sysid = int(namespace[-1]) sysid = int(namespace.replace('UAV', ''))
self.connection.mav.command_long_send( self.connection.mav.command_long_send(
sysid, sysid,
1, # Component ID 1, # Component ID
mavutil.mavlink.MAV_CMD_COMPONENT_ARM_DISARM, mavutil.mavlink.MAV_CMD_COMPONENT_ARM_DISARM, #MAV_CMD_COMPONENT_ARM_DISARM (400)
0, # Confirmation 0, # Confirmation
1 if arm else 0, 1 if arm else 0,
0, 0, 0, 0, 0, 0 0, 0, 0, 0, 0, 0
) )
# self.connection.mav.command_long_send(
# sysid,
# 1, # Component ID
# mavutil.mavlink.MAV_CMD_SET_MESSAGE_INTERVAL,
# 0,
# 33,
# -1,
# 0, 0, 0, 0, 0
# )
def takeoff(self, namespace, altitude): def takeoff(self, namespace, altitude):
sysid = int(namespace[-1]) sysid = int(namespace.replace('UAV', ''))
self.connection.mav.command_long_send( self.connection.mav.command_long_send(
sysid, sysid,
1, # Component ID 1, # Component ID
mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, # Command to execute mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, #MAV_CMD_NAV_TAKEOFF (22)
0, # Confirmation (0 = first transmission) 0, # Confirmation (0 = first transmission)
0, 0, 0, 0, # Parameters 1-4 (Unused in takeoff command) 0, 0, 0, 0, # Parameters 1-4 (Unused in takeoff command)
0, 0, altitude # Latitude, Longitude, Altitude (target_altitude in meters) 0, 0, altitude # Latitude, Longitude, Altitude (target_altitude in meters)
) )
def land(self, namespace): def land(self, namespace):
sysid = int(namespace[-1]) sysid = int(namespace.replace('UAV', ''))
self.connection.mav.command_long_send( self.connection.mav.command_long_send(
sysid, sysid,
1, # Component ID 1, # Component ID
mavutil.mavlink.MAV_CMD_NAV_LAND, mavutil.mavlink.MAV_CMD_NAV_LAND, #MAV_CMD_NAV_LAND (21)
0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0 0, 0, 0
) )
def set_local_position(self, namespace, x, y, z): def set_local_position(self, namespace, x, y, z):
sysid = int(namespace[-1]) sysid = int(namespace.replace('UAV', ''))
self.connection.mav.set_position_target_local_ned_send( self.connection.mav.set_position_target_local_ned_send(
0, sysid, 1, # time_boot_ms, sysid, compid 0, sysid, 1, # time_boot_ms, sysid, compid
mavutil.mavlink.MAV_FRAME_LOCAL_NED, # Frame mavutil.mavlink.MAV_FRAME_LOCAL_NED, # Frame
@@ -146,12 +217,70 @@ class MAVLinkWorker(QThread):
0, 0 # Yaw, yaw_rate 0, 0 # Yaw, yaw_rate
) )
def reboot_drone(self, namespace):
sysid = int(namespace.replace('UAV', ''))
self.connection.mav.command_long_send(
sysid,
1, # target_component (一般為1)
mavutil.mavlink.MAV_CMD_PREFLIGHT_REBOOT_SHUTDOWN, # Reboot 命令
0, # confirmation
1, # 第一個參數 (1 = reboot,0 = 無操作,2 = 關機)
0, 0, 0, 0, 0, 0 # 其餘參數保留
)
def set_param(self, namespace, param_name, value):
sysid = int(namespace.replace('UAV', ''))
try:
self.connection.mav.param_set_send(
sysid,
1,
param_name.encode('utf-8'),
float(value),
mavutil.mavlink.MAV_PARAM_TYPE_REAL32
)
except Exception as e:
print(f"Failed to set parameter {param_name}: {e}")
def request_param(self, namespace, param_name):
sysid = int(namespace.replace('UAV', ''))
try:
self.connection.mav.param_request_read_send(
sysid, # 系統 ID
1, # 組件 ID
param_name.encode('utf-8'), # 參數名稱
-1 # 參數索引,-1 表示根據名稱請求
)
except Exception as e:
print(f"Failed to set parameter {param_name}: {e}")
def send_heartbeat(self):
self.connection.mav.heartbeat_send(
mavutil.mavlink.MAV_TYPE_GCS,
mavutil.mavlink.MAV_AUTOPILOT_INVALID,
0, 0, 0
)
def set_sr_params(self, sysid):
""" 直接設置 MAVLink 訊息頻率 """
freqs = [0, 2, 4]
params = {
"SR1_ADSB": freqs[0],
"SR1_EXT_STAT": freqs[1],
"SR1_EXTRA1": freqs[2],
"SR1_EXTRA2": freqs[2],
"SR1_EXTRA3": freqs[1],
"SR1_POSITION": freqs[1],
"SR1_RAW_SENS": freqs[1],
"SR1_RC_CHAN": freqs[1]
}
for param, value in params.items():
self.set_param(f"UAV{sysid}", param, value)
class DroneControlApp(QMainWindow): class DroneControlApp(QMainWindow):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.namespaces = ['uav1', 'uav2', 'uav3']
self.workers = MAVLinkWorker() self.workers = MAVLinkWorker()
self.namespaces = self.workers.namespaces
self.initUI() self.initUI()
# Connect signals to update the UI # Connect signals to update the UI
@@ -163,7 +292,10 @@ class DroneControlApp(QMainWindow):
self.workers.imu_signal.connect(self.update_imu) self.workers.imu_signal.connect(self.update_imu)
self.workers.hdg_signal.connect(self.update_hdg) self.workers.hdg_signal.connect(self.update_hdg)
self.workers.groundspeed_signal.connect(self.update_groundspeed) self.workers.groundspeed_signal.connect(self.update_groundspeed)
self.workers.ping_signal.connect(self.update_ping)
self.workers.loss_signal.connect(self.update_loss)
self.workers.frequency_signal.connect(self.update_frequency)
self.workers.param_signal.connect(self.update_param)
self.workers.start() self.workers.start()
def initUI(self): def initUI(self):
@@ -193,15 +325,19 @@ class DroneControlApp(QMainWindow):
# 狀態顯示 # 狀態顯示
self.uav_labels[namespace] = { self.uav_labels[namespace] = {
"status": QLabel(f"{namespace} 狀態:等待數據..."), "status": QLabel("狀態:等待數據..."),
"battery": QLabel(f"{namespace} 電量:等待數據..."), "battery": QLabel("電量:等待數據..."),
"gps": QLabel(f"{namespace} GPS位置:等待數據...\n\n"), "gps": QLabel("GPS位置:等待數據...\n\n"),
"fix": QLabel(f"{namespace} Fix Type:等待數據..."), "fix": QLabel("Fix Type:等待數據..."),
"satellites": QLabel(f"{namespace} 衛星數量:等待數據..."), "satellites": QLabel("衛星數量:等待數據..."),
"local_position": QLabel(f"{namespace} Local Position:等待數據..."), "local_position": QLabel("Local Position:等待數據..."),
"groundspeed": QLabel(f"{namespace} 地面速度:等待數據..."), "groundspeed": QLabel("地面速度:等待數據..."),
"pitch": QLabel(f"{namespace} Pitch:等待數據..."), "pitch": QLabel("Pitch:等待數據..."),
"heading": QLabel(f"{namespace} Heading:等待數據..."), "heading": QLabel("Heading:等待數據..."),
"ping": QLabel("Ping:等待數據..."),
"loss": QLabel("丟包:等待數據..."),
"frequency": QLabel("頻率:等待數據..."),
"param": QLabel("SR1_EXTRA1:等待數據...")
} }
# 把每個資訊添加到該 UAV 的垂直佈局中 # 把每個資訊添加到該 UAV 的垂直佈局中
@@ -213,6 +349,16 @@ class DroneControlApp(QMainWindow):
main_layout.addLayout(uav_layout) main_layout.addLayout(uav_layout)
# SR1_EXTRA1參數設置
param_layout = QHBoxLayout()
self.paramInput = QLineEdit()
self.paramInput.setPlaceholderText("輸入SR1_EXTRA1的新值")
self.setParamButton = QPushButton("設置SR1_EXTRA1")
self.setParamButton.clicked.connect(self.set_SR1_EXTRA1)
param_layout.addWidget(self.paramInput)
param_layout.addWidget(self.setParamButton)
main_layout.addLayout(param_layout)
# 模式切換區域 # 模式切換區域
mode_layout = QHBoxLayout() mode_layout = QHBoxLayout()
self.modeComboBox = QComboBox() self.modeComboBox = QComboBox()
@@ -244,19 +390,26 @@ class DroneControlApp(QMainWindow):
main_layout.addWidget(self.setPositionButton) main_layout.addWidget(self.setPositionButton)
# 解鎖按鈕 # 解鎖按鈕
fly_layout = QHBoxLayout()
self.armButton = QPushButton("解鎖") self.armButton = QPushButton("解鎖")
self.armButton.clicked.connect(self.arm_drone) self.armButton.clicked.connect(self.arm_drone)
main_layout.addWidget(self.armButton)
# 起飛按鈕 # 起飛按鈕
self.takeoffButton = QPushButton("起飛") self.takeoffButton = QPushButton("起飛")
self.takeoffButton.clicked.connect(self.takeoff_drone) self.takeoffButton.clicked.connect(self.takeoff_drone)
main_layout.addWidget(self.takeoffButton)
# 降落按鈕 # 降落按鈕
self.landButton = QPushButton("降落") self.landButton = QPushButton("降落")
self.landButton.clicked.connect(self.land_drone) self.landButton.clicked.connect(self.land_drone)
main_layout.addWidget(self.landButton) fly_layout.addWidget(self.armButton)
fly_layout.addWidget(self.takeoffButton)
fly_layout.addWidget(self.landButton)
main_layout.addLayout(fly_layout)
#設置重啟按鈕
self.rebootButton = QPushButton("重啟")
self.rebootButton.clicked.connect(self.reboot_drone)
main_layout.addWidget(self.rebootButton)
# 設置主佈局 # 設置主佈局
central_widget = QWidget(self) central_widget = QWidget(self)
@@ -271,13 +424,13 @@ class DroneControlApp(QMainWindow):
event.accept() event.accept()
def update_state(self, namespace, mode): def update_state(self, namespace, mode):
self.uav_labels[namespace]["status"].setText(f"{namespace} 狀態:{mode}") self.uav_labels[namespace]["status"].setText(f"狀態:{mode}")
def update_battery(self, namespace, percentage): def update_battery(self, namespace, percentage):
self.uav_labels[namespace]["battery"].setText(f"{namespace} 電量:{percentage * 100:.2f}%") self.uav_labels[namespace]["battery"].setText(f"電量:{percentage * 100:.2f}%")
def update_gps(self, namespace, latitude, longitude): def update_gps(self, namespace, latitude, longitude):
self.uav_labels[namespace]["gps"].setText(f"{namespace} GPS位置: \nLat:{latitude:.6f}° \nLon:{longitude:.6f}°") self.uav_labels[namespace]["gps"].setText(f"GPS位置: \n Lat:{latitude:.6f}° \n Lon:{longitude:.6f}°")
def update_gps_status(self, namespace, fix_type, satellites_visible): def update_gps_status(self, namespace, fix_type, satellites_visible):
fix_type_str = { fix_type_str = {
@@ -288,20 +441,32 @@ class DroneControlApp(QMainWindow):
4: "RTK Float", 4: "RTK Float",
5: "RTK Fix" 5: "RTK Fix"
}.get(fix_type, "Unknown") }.get(fix_type, "Unknown")
self.uav_labels[namespace]["fix"].setText(f"{namespace} Fix Type:{fix_type_str}") self.uav_labels[namespace]["fix"].setText(f"Fix Type:{fix_type_str}")
self.uav_labels[namespace]["satellites"].setText(f"{namespace} 衛星數量:{satellites_visible}") self.uav_labels[namespace]["satellites"].setText(f"衛星數量:{satellites_visible}")
def update_local_position(self, namespace, x, y, z): def update_local_position(self, namespace, x, y, z):
self.uav_labels[namespace]["local_position"].setText(f"{namespace} Local:({x:.2f}, {y:.2f}, {z:.2f})") self.uav_labels[namespace]["local_position"].setText(f"Local:({x:.2f}, {y:.2f}, {z:.2f})")
def update_imu(self, namespace, pitch): def update_imu(self, namespace, pitch):
self.uav_labels[namespace]["pitch"].setText(f"{namespace} Pitch:{pitch:.2f}°") self.uav_labels[namespace]["pitch"].setText(f"Pitch:{pitch:.2f}°")
def update_hdg(self, namespace, heading): def update_hdg(self, namespace, heading):
self.uav_labels[namespace]["heading"].setText(f"{namespace} Heading:{heading:.2f}°") self.uav_labels[namespace]["heading"].setText(f"Heading:{heading:.2f}°")
def update_groundspeed(self, namespace, groundspeed): def update_groundspeed(self, namespace, groundspeed):
self.uav_labels[namespace]["groundspeed"].setText(f"{namespace} 地面速度:{groundspeed:.2f} m/s") self.uav_labels[namespace]["groundspeed"].setText(f"地面速度:{groundspeed:.2f} m/s")
def update_ping(self, namespace, ping):
self.uav_labels[namespace]["ping"].setText(f"Ping:{ping:.2f} ms")
def update_loss(self, namespace, loss):
self.uav_labels[namespace]["loss"].setText(f"丟包:{loss:.2f}%")
def update_frequency(self, namespace, frequency):
self.uav_labels[namespace]["frequency"].setText(f"頻率:{frequency:.2f} Hz")
def update_param(self, namespace, value):
self.uav_labels[namespace]["param"].setText(f"SR1_EXTRA1:{value}")
def change_mode(self): def change_mode(self):
try: try:
@@ -346,6 +511,24 @@ class DroneControlApp(QMainWindow):
except ValueError: except ValueError:
QtWidgets.QMessageBox.warning(self, "錯誤", "請輸入有效的數值!") QtWidgets.QMessageBox.warning(self, "錯誤", "請輸入有效的數值!")
def reboot_drone(self):
try:
for namespace in self.namespaces:
if self.uav_checkboxes[namespace].isChecked():
self.workers.reboot_drone(namespace)
except Exception as e:
QtWidgets.QMessageBox.warning(self, "錯誤", f"重啟失敗:{e}")
def set_SR1_EXTRA1(self):
param_value = self.paramInput.text().strip()
try:
for namespace in self.namespaces:
if self.uav_checkboxes[namespace].isChecked():
self.workers.set_param(namespace, "SR1_EXTRA1", param_value)
self.workers.request_param(namespace, "SR1_EXTRA1")
except ValueError:
QtWidgets.QMessageBox.warning(self, "錯誤", "請輸入有效的數值!")
def main(args=None): def main(args=None):
rclpy.init(args=args) rclpy.init(args=args)
app = QtWidgets.QApplication(sys.argv) app = QtWidgets.QApplication(sys.argv)
+2 -1
View File
@@ -21,7 +21,8 @@ setup(
entry_points={ entry_points={
'console_scripts': [ 'console_scripts': [
'showmavsimple = unitdev02.test01:mavlink_analyzer_simple', 'showmavsimple = unitdev02.test01:mavlink_analyzer_simple',
'fdm_switch_one = unitdev02.test01:fdm_swicth_example_one', 'fdm_switch_one = unitdev02.test01:fdm_switch_example_one',
'fdm_switch_two = unitdev02.test01:fdm_switch_example_two',
], ],
}, },
) )
File diff suppressed because one or more lines are too long
+105 -21
View File
@@ -6,6 +6,10 @@ import time
import socket import socket
import struct import struct
# used at fdm_switch_example_two
import threading
import queue
def mavlink_analyzer_simple(count = 500): def mavlink_analyzer_simple(count = 500):
# rclpy.init() # rclpy.init()
# node = rclpy.create_node('mavlink_analyzer_simple') # node = rclpy.create_node('mavlink_analyzer_simple')
@@ -32,11 +36,17 @@ def mavlink_analyzer_simple(count = 500):
if msg.get_type() == 'SERVO_OUTPUT_RAW': if msg.get_type() == 'SERVO_OUTPUT_RAW':
print(msg) print(msg)
pass
else: else:
print('No message yet, 1 second for data to fill') print('No message yet, 1 second for data to fill')
sleep(1) sleep(1)
print("markA ------")
print(msg.get_type())
print(msg.ordered_fieldnames)
print("markA ------ END")
print('Messages Type received:') print('Messages Type received:')
print(msg_count) print(msg_count)
@@ -52,7 +62,6 @@ def fdm_parser_example(data=None):
data = bytes.fromhex('1a48e903b1340500e803e803e803e803000000000000000000000000000000000000000000000000') data = bytes.fromhex('1a48e903b1340500e803e803e803e803000000000000000000000000000000000000000000000000')
# 1a48e903b1340500e803e803e803e803000000000000000000000000000000000000000000000000 # 1a48e903b1340500e803e803e803e803000000000000000000000000000000000000000000000000
parse_format = 'HHI16H' parse_format = 'HHI16H'
if len(data) != struct.calcsize(parse_format): if len(data) != struct.calcsize(parse_format):
@@ -103,7 +112,8 @@ def fdm_switch_example_one():
elapsed_time = current_time - start_time elapsed_time = current_time - start_time
if elapsed_time >= 1.0: if elapsed_time >= 1.0:
print(f"Packets received in the last second: {packet_count}") # print(f"Packets received in the last second: {packet_count}")
print(f'JSON Pack : {data_g2s}')
packet_count = 0 packet_count = 0
start_time = current_time start_time = current_time
fdm_parser_example(data_s2g) fdm_parser_example(data_s2g)
@@ -113,34 +123,108 @@ def fdm_switch_example_two():
''' '''
這個範例在 SITL 與 Gazebo 之間 做一個簡單的UDP轉發器 這個範例在 SITL 與 Gazebo 之間 做一個簡單的UDP轉發器
有轉換數據格式 有轉換數據格式
time_usec
servo1_raw
servo16_raw
''' '''
# read info from SITL via MAVLink # read info from SITL via MAVLink
connection_string="udp:127.0.0.1:14550" connection_string="udp:127.0.0.1:14550" # uart mavlink # MATLAB
connection = mavutil.mavlink_connection(connection_string) connection = mavutil.mavlink_connection(connection_string)
# set a target link pass JSON to Gazebo, as a client # set a target link pass JSON to Gazebo, as a client
sock_g2s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock_g2s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_address = ('127.0.0.1', 19002) server_address = ('127.0.0.1', 19002)
data_queue = queue.Queue()
servo_out = [0] * 16 # [0 0 0 0 0 0 0 0 0.....]
data_queue.put(servo_out)
def send_data_from_queue(sock, server_address, q, frame_rate_fdm=1, frame_count_fdm=1):
interval = 1.0 / frame_rate_fdm
while True:
start_time = time.time()
try:
data = q.get(timeout=0.1) # [0 0 0 0 0 0 0 0 0.....]
if data is None:
break
last_data = data
except queue.Empty:
data = last_data
data_fdm = struct.pack('HHI16H', 0x481a, int(frame_rate_fdm * 0.1), frame_count_fdm, *servo_out) # [ FMD ]
sock.sendto(data_fdm, server_address)
frame_count_fdm += 3
# fdm_parser_example(data_fdm) # debug
elapsed_time = time.time() - start_time
sleep_time = interval - elapsed_time
if sleep_time > 0:
sleep(sleep_time)
Running = True
thread = threading.Thread(target=send_data_from_queue, args=(sock_g2s, server_address, data_queue))
thread.start()
print('Start sending data to Gazebo')
while Running:
msgb1 = None
msg = connection.recv_msg()
while msg :
if msg.get_type() == 'SERVO_OUTPUT_RAW':
msgb1 = msg
# break # 這裡不需要break,因為我要最後一個 servo_output_raw 的值
msg = connection.recv_msg()
if msgb1:
for i in range(16):
servo_out[i] = getattr(msgb1, f'servo{i+1}_raw')
data_queue.put(servo_out)
msgb1 = None
# Running = False # debug
else:
# print('No message yet, 10 ms for data to fill')
sleep(0.01)
# Example of putting data into the queue
# data_queue.put(data)
# To stop the thread, you can put None into the queue
# data_queue.put(None)
from pymavlink.dialects.v20 import common # 使用 MAVLink 2.0
def mavlink_btye_generator_test():
# 創建一個 MAVLink 對象
mav = common.MAVLink(None) # 不透過 connection,直接使用 None
# 創建一個 HEARTBEAT 訊息
msg = mav.heartbeat_encode(
type=mavutil.mavlink.MAV_TYPE_QUADROTOR,
autopilot=mavutil.mavlink.MAV_AUTOPILOT_GENERIC,
base_mode=0,
custom_mode=0,
system_status=mavutil.mavlink.MAV_STATE_ACTIVE
)
# 取得 MAVLink 訊息的 bytes
mavlink_bytes = msg.pack(mav)
# 回傳 bytes
print(mavlink_bytes)
print(type(mavlink_bytes))
mav2 = common.MAVLink(None)
# my_msg = mav2.decode(mavlink_bytes)
print("========")
print(dir(mav2))
# print(my_msg)
print('Start') print('Start')
mavlink_analyzer_simple() # fdm_switch_example_two()
# fdm_parser_example()
# mavlink_analyzer_simple(8)
# mavlink_btye_generator_test()
# connection_string="udp:127.0.0.1:14550"
# connection = mavutil.mavlink_connection(connection_string)
# connection.mav.command_long_send(
# 1,
# 1,
# mavutil.mavlink.MAV_CMD_SET_MESSAGE_INTERVAL,
# 0, 33, 1000000, 0, 0, 0, 0, 0
# )
# connection.mav.command_long_send(
# 1,
# 1, # Component ID
# mavutil.mavlink.MAV_CMD_COMPONENT_ARM_DISARM,
# 0, # Confirmation
# 1, # 0: disarm, 1: arm
# 0, 0, 0, 0, 0, 0
# )
+174
View File
@@ -0,0 +1,174 @@
#!/usr/bin/env python3
import subprocess
import argparse
import time
import psutil
import h5py
import numpy as np
class GazeboModelManager:
def __init__(self):
self.base_cmd = "gz service -s /world/map"
self.timeout = 5000
def add_iris(self, number, x_pos, y_pos, yaw_angle):
cmd = f'''gz service -s /world/map/create --reqtype gz.msgs.EntityFactory --reptype gz.msgs.Boolean --timeout 5000 --req 'sdf: "<?xml version=\\"1.0\\" ?><sdf version=\\"1.9\\"><model name=\\"iris{number}\\"><include><uri>model://iris{number}</uri><pose degrees=\\"true\\">{x_pos} {y_pos} 0.195 0 0 {yaw_angle}</pose></include></model></sdf>"' '''
try:
print(f"正在新增 iris{number}...")
print(f"位置: x={x_pos}, y={y_pos}, yaw={yaw_angle}度")
subprocess.run(cmd, shell=True, check=True)
print(f"成功新增 iris{number}")
sitl_instance = number - 1
port = 14550 + sitl_instance
sitl_cmd = f"sim_vehicle.py -v ArduCopter -f gazebo-iris{sitl_instance} -L NCHU --model JSON --map --console -I{sitl_instance} --out=127.0.0.1:{port}"
terminal_cmd = f'gnome-terminal -- bash -c "{sitl_cmd}; exec bash"'
print(f"正在啟動 SITL...")
subprocess.Popen(terminal_cmd, shell=True)
return True
except subprocess.CalledProcessError as e:
print(f"新增失敗: {e}")
return False
def cleanup_sitl(self, number):
instance_number = number - 1
print(f"正在清理 SITL 進程 {number}...")
cleanup_commands = [
f"pkill -f 'sim_vehicle.py.*-I{instance_number}'",
f"pkill -f 'arducopter.*-I{instance_number}'",
"pkill -f 'mavproxy.py.*map'",
"pkill -f 'mavproxy.py.*console'",
f"pkill -f 'mavproxy.*{instance_number}'",
"pkill -f 'python.*map.py'",
"pkill -f 'MAVProxy.py.*map'"
]
for cmd in cleanup_commands:
try:
subprocess.run(cmd, shell=True)
except subprocess.CalledProcessError:
pass
time.sleep(2)
print(f"SITL 進程清理完成")
def remove_iris(self, number):
cmd = f'gz service -s /world/map/remove --reqtype gz.msgs.Entity --reptype gz.msgs.Boolean --timeout 1000 --req \'name: "iris{number}"\''
try:
print(f"正在移除 iris{number}...")
subprocess.run(cmd, shell=True, check=True)
print(f"成功移除 iris{number}")
return True
except subprocess.CalledProcessError as e:
print(f"移除失敗: {e}")
return False
def add_obstacle(self, number, x_pos, y_pos, radius=0.5, height=2.0):
cmd = f'''gz service -s /world/map/create --reqtype gz.msgs.EntityFactory --reptype gz.msgs.Boolean --timeout 5000 --req 'sdf: "<?xml version=\\"1.0\\" ?><sdf version=\\"1.9\\"><model name=\\"obstacle_{number}\\"><static>true</static><pose>{x_pos} {y_pos} {height/2} 0 0 0</pose><link name=\\"link\\"><collision name=\\"collision\\"><geometry><cylinder><radius>{radius}</radius><length>{height}</length></cylinder></geometry></collision><visual name=\\"visual\\"><geometry><cylinder><radius>{radius}</radius><length>{height}</length></cylinder></geometry><material><ambient>0.3 0.3 0.3 1</ambient><diffuse>0.7 0.7 0.7 1</diffuse></material></visual></link></model></sdf>"' '''
try:
print(f"正在新增障礙物 {number}...")
print(f"位置: x={x_pos}, y={y_pos}, radius={radius}, height={height}")
subprocess.run(cmd, shell=True, check=True)
return True
except subprocess.CalledProcessError as e:
print(f"新增失敗: {e}")
return False
def remove_obstacle(self, number):
cmd = f'gz service -s /world/map/remove --reqtype gz.msgs.Entity --reptype gz.msgs.Boolean --timeout 1000 --req \'name: "obstacle_{number}"\''
try:
print(f"正在移除障礙物 {number}...")
subprocess.run(cmd, shell=True, check=True)
print(f"成功移除障礙物 {number}")
return True
except subprocess.CalledProcessError as e:
print(f"移除失敗: {e}")
return False
def add_obstacles_from_mat(self, mat_file):
"""從 .mat 檔案逐一生成障礙物"""
try:
with h5py.File(mat_file, 'r') as f:
x_data = f['#refs#/d'][()]
y_data = f['#refs#/e'][()]
z_data = f['#refs#/f'][()]
radius_data = f['#refs#/g'][()]
height_data = f['#refs#/h'][()]
data_matrix = np.vstack((x_data, y_data, z_data, radius_data, height_data)).T
for i, obstacle in enumerate(data_matrix):
x, y, _, radius, height = obstacle
success = self.add_obstacle(i+1, float(x), float(y), float(radius), float(height))
if not success:
print(f"警告:障礙物 {i+1} 新增失敗")
time.sleep(0.1)
print("所有障礙物新增完成")
return True
except Exception as e:
print(f"錯誤:讀取或處理 .mat 檔案時發生問題: {e}")
return False
def main():
parser = argparse.ArgumentParser(description='Gazebo Iris and Obstacle Manager')
parser.add_argument('action', choices=['add', 'remove', 'cleanup', 'add_from_mat'],
help='Action to perform (add, remove, cleanup, or add_from_mat)')
parser.add_argument('type', choices=['iris', 'obstacle'],
help='Type of object to manage (iris or obstacle)')
parser.add_argument('number', type=int, nargs='?',
help='Object number (e.g., 4 for iris4 or obstacle_4)')
parser.add_argument('--x-pos', type=float, default=15.0,
help='X position for new object')
parser.add_argument('--y-pos', type=float, default=0.0,
help='Y position for new object')
parser.add_argument('--yaw', type=float, default=90.0,
help='Yaw angle in degrees for iris')
parser.add_argument('--radius', type=float, default=0.5,
help='Radius for obstacle cylinder')
parser.add_argument('--height', type=float, default=2.0,
help='Height for obstacle cylinder')
parser.add_argument('--mat-file', type=str,
help='Path to .mat file containing obstacles data')
args = parser.parse_args()
manager = GazeboModelManager()
if args.action == 'add_from_mat':
if args.type == 'obstacle' and args.mat_file:
manager.add_obstacles_from_mat(args.mat_file)
else:
print("錯誤:使用 add_from_mat 時需指定 --mat-file 參數")
elif args.action == 'add':
if args.number is None:
print("錯誤:使用 add 時需指定 number 參數")
return
if args.type == 'iris':
manager.add_iris(args.number, args.x_pos, args.y_pos, args.yaw)
else: # obstacle
manager.add_obstacle(args.number, args.x_pos, args.y_pos, args.radius, args.height)
elif args.action == 'cleanup':
if args.number is None:
print("錯誤:使用 cleanup 時需指定 number 參數")
return
if args.type == 'iris':
manager.cleanup_sitl(args.number)
else: # remove
if args.number is None:
print("錯誤:使用 remove 時需指定 number 參數")
return
if args.type == 'iris':
manager.remove_iris(args.number)
else:
manager.remove_obstacle(args.number)
if __name__ == "__main__":
main()
+232
View File
@@ -0,0 +1,232 @@
#!/usr/bin/env python3
from pymavlink import mavutil
import time
import math
import threading
class DroneController:
def __init__(self, connection_string="udp:127.0.0.1:14550"):
# 建立 MAVLink 連接
self.master = mavutil.mavlink_connection(connection_string)
# 等待心跳包
self.master.wait_heartbeat()
print("已建立連接!")
def set_mode(self, mode):
"""設置飛行模式"""
mode_id = self.master.mode_mapping()[mode]
self.master.mav.set_mode_send(
self.master.target_system,
mavutil.mavlink.MAV_MODE_FLAG_CUSTOM_MODE_ENABLED,
mode_id
)
for _ in range(5):
msg = self.master.recv_match(type='HEARTBEAT', blocking=True, timeout=1)
if msg and msg.custom_mode == mode_id:
return True
return False
def arm(self, arm_state=True):
"""解鎖/上鎖無人機"""
self.master.mav.command_long_send(
self.master.target_system,
self.master.target_component,
mavutil.mavlink.MAV_CMD_COMPONENT_ARM_DISARM,
0,
1 if arm_state else 0, 0, 0, 0, 0, 0, 0
)
for _ in range(5):
msg = self.master.recv_match(type='HEARTBEAT', blocking=True, timeout=1)
if msg:
armed = msg.base_mode & mavutil.mavlink.MAV_MODE_FLAG_SAFETY_ARMED
if armed == arm_state:
return True
return False
def takeoff(self, altitude):
"""起飛到指定高度"""
self.master.mav.command_long_send(
self.master.target_system,
self.master.target_component,
mavutil.mavlink.MAV_CMD_NAV_TAKEOFF,
0,
0, 0, 0, 0, 0, 0,
altitude
)
while True:
msg = self.master.recv_match(type='GLOBAL_POSITION_INT', blocking=True)
if msg:
current_alt = msg.relative_alt / 1000.0
if abs(current_alt - altitude) < 0.5:
return True
time.sleep(0.1)
def goto_position_local(self, x, y, z, vx=2, vy=2, vz=0, yaw=0, yaw_rate=0):
"""
飛到指定的本地座標位置
x, y, z: 目標位置(公尺)
"""
# NED系統中,向上飛需要給負值
z = -z # 將輸入的z值轉換為NED系統
# 重要:type_mask 設為 0 表示使用該維度
# 我們只想使用位置控制 (x,y,z),所以其他都設為 ignore
type_mask = (
# mavutil.mavlink.POSITION_TARGET_TYPEMASK_VX_IGNORE | # 8
# mavutil.mavlink.POSITION_TARGET_TYPEMASK_VY_IGNORE | # 16
# mavutil.mavlink.POSITION_TARGET_TYPEMASK_VZ_IGNORE | # 32
mavutil.mavlink.POSITION_TARGET_TYPEMASK_AX_IGNORE | # 64
mavutil.mavlink.POSITION_TARGET_TYPEMASK_AY_IGNORE | # 128
mavutil.mavlink.POSITION_TARGET_TYPEMASK_AZ_IGNORE | # 256
mavutil.mavlink.POSITION_TARGET_TYPEMASK_YAW_IGNORE | # 1024
mavutil.mavlink.POSITION_TARGET_TYPEMASK_YAW_RATE_IGNORE # 2048
)
# 或直接使用二進制: type_mask = 0b0000111111111000
print(f"發送位置指令: x={x}, y={y}, z={-z}")
self.master.mav.set_position_target_local_ned_send(
0, # timestamp
self.master.target_system,
self.master.target_component,
mavutil.mavlink.MAV_FRAME_LOCAL_NED,
type_mask,
x, y, z, # 位置
0, 0, 0, # 速度 (設為 0)
5, 5, 0, # 加速度 (設為 0)
0, 0 # yaw, yaw_rate (設為 0)
)
# 監控位置變化
print("開始監控位置變化...")
start_time = time.time()
while time.time() - start_time < 20: # 20秒超時
msg = self.master.recv_match(type='LOCAL_POSITION_NED', blocking=True, timeout=1)
if msg:
dist = math.sqrt((msg.x - x)**2 + (msg.y - y)**2 + (msg.z - z)**2)
print(f"當前位置: x={msg.x:.2f}, y={msg.y:.2f}, z={-msg.z:.2f}, 距離目標: {dist:.2f}m")
if dist < 0.5:
print("到達目標位置")
return True
time.sleep(0.1)
print("移動超時")
return False
def get_current_state(self):
"""獲取當前狀態"""
msg = self.master.recv_match(type='HEARTBEAT', blocking=True)
if msg:
mode = list(self.master.mode_mapping().keys())[list(self.master.mode_mapping().values()).index(msg.custom_mode)]
armed = bool(msg.base_mode & mavutil.mavlink.MAV_MODE_FLAG_SAFETY_ARMED)
return {
'mode': mode,
'armed': armed
}
return None
def print_menu():
"""列印功能表"""
menu = [
"\n=== 無人機控制界面 ===",
"1. 切換至 GUIDED 模式",
"2. 切換至 LAND 模式",
"3. 解鎖",
"4. 上鎖",
"5. 起飛",
"6. 飛到指定位置",
"7. 切換至 RTL 模式(返航)",
"8. 查看當前狀態",
"9. 退出",
"0. 顯示此清單",
"===================="
]
for item in menu:
print(item)
def main():
drone = DroneController()
print_menu()
while True:
try:
choice = input("\n請輸入指令編號: ")
if choice == '0':
print_menu()
elif choice == '1':
if drone.set_mode("GUIDED"):
print("已切換至 GUIDED 模式")
else:
print("切換模式失敗")
elif choice == '2':
if drone.set_mode("LAND"):
print("已切換至 LAND 模式")
else:
print("切換模式失敗")
elif choice == '3':
if drone.arm(True):
print("無人機已解鎖")
else:
print("解鎖失敗")
elif choice == '4':
if drone.arm(False):
print("無人機已上鎖")
else:
print("上鎖失敗")
elif choice == '5':
altitude = float(input("請輸入起飛高度(公尺): "))
if drone.takeoff(altitude):
print(f"已到達目標高度: {altitude}公尺")
else:
print("起飛失敗")
elif choice == '6':
x = float(input("請輸入X座標(公尺): "))
y = float(input("請輸入Y座標(公尺): "))
z = float(input("請輸入Z座標(公尺,負值代表向下): "))
if drone.goto_position_local(x, y, z):
print("已到達目標位置")
else:
print("移動失敗")
elif choice == '7':
if drone.set_mode("RTL"):
print("已切換至 RTL 模式,無人機正在返航")
else:
print("切換至 RTL 模式失敗")
elif choice == '8':
state = drone.get_current_state()
if state:
print("\n=== 無人機狀態 ===")
print(f"模式: {state['mode']}")
print(f"解鎖狀態: {'已解鎖' if state['armed'] else '已上鎖'}")
print("==================")
else:
print("無法獲取狀態")
elif choice == '9':
print("程式結束")
break
else:
print("無效的指令,請重新輸入")
except ValueError as e:
print(f"輸入錯誤: {e}")
except Exception as e:
print(f"發生錯誤: {e}")
if __name__ == "__main__":
main()
+47
View File
@@ -0,0 +1,47 @@
import threading
import time
from digi.xbee.devices import XBeeDevice
# Configure XBee connection
PORT = "/dev/ttyUSB1"
BAUD_RATE = 57600
# Initialize XBee device
device = XBeeDevice(PORT, BAUD_RATE)
device.open()
# Callback function to handle incoming messages
def data_received_callback(xbee_message):
sender = xbee_message.remote_device.get_64bit_addr()
try:
data = xbee_message.data.decode("utf-8") # Attempt UTF-8 decoding
except UnicodeDecodeError:
data = xbee_message.data.hex() # Fallback to HEX if decoding fails
print(f"\nReceived from {sender}: {data}\nEnter message: ", end="")
# Register callback for incoming data
device.add_data_received_callback(data_received_callback)
# Function to send broadcast messages (runs in a separate thread)
def send_messages():
while True:
user_input = input("\nEnter message: ") # User input
status = device.send_data_broadcast(user_input) # Broadcast message
if status:
print("Broadcast message sent successfully!")
else:
print("Failed to send broadcast message.")
# Start the message sending thread
send_thread = threading.Thread(target=send_messages, daemon=True)
send_thread.start()
print("Broadcast chat mode activated. Type a message and press Enter to send.\n")
# Keep the program running
try:
while True:
time.sleep(0.1) # Reduce CPU usage and ensure stability
except KeyboardInterrupt:
print("\nProgram terminated.")
device.close()
+41
View File
@@ -0,0 +1,41 @@
import serial
import threading
PORT = "COM15" # AT Mode XBee COM Port
BAUD_RATE = 57600
# Initialize serial connection
ser = serial.Serial(PORT, BAUD_RATE, timeout=1)
# Function to receive messages from the Coordinator
def receive_data():
while True:
data = ser.readline().decode("utf-8", errors="ignore").strip() # Read incoming data
if data:
print(f"\nReceived from Coordinator: {data}")
print("Enter message: ", end="", flush=True) # Keep input prompt intact
# Function to send messages to the Coordinator
def send_data():
while True:
user_input = input("\nEnter message: ") # User input
ser.write(user_input.encode() + b'\r') # Send message in AT Mode
print("Message sent.")
# Start the receiving thread
receive_thread = threading.Thread(target=receive_data, daemon=True)
receive_thread.start()
# Start the sending thread
send_thread = threading.Thread(target=send_data, daemon=True)
send_thread.start()
print("AT Mode XBee communication started. Type a message and press Enter to send.\n")
# Keep the program running
try:
while True:
pass
except KeyboardInterrupt:
print("\nProgram terminated.")
ser.close()