|
|
|
|
@ -1,8 +1,5 @@
|
|
|
|
|
import sys
|
|
|
|
|
import time
|
|
|
|
|
import math
|
|
|
|
|
import rclpy
|
|
|
|
|
from collections import deque
|
|
|
|
|
from PyQt5 import QtWidgets
|
|
|
|
|
from PyQt5.QtCore import QThread, pyqtSignal
|
|
|
|
|
from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QCheckBox, QWidget, QMainWindow, QComboBox
|
|
|
|
|
@ -18,48 +15,15 @@ class MAVLinkWorker(QThread):
|
|
|
|
|
hdg_signal = pyqtSignal(str, float)
|
|
|
|
|
groundspeed_signal = pyqtSignal(str, float)
|
|
|
|
|
|
|
|
|
|
def __init__(self, connection_string="udp:127.0.0.1:14550", usb='/dev/ttyUSB0'):
|
|
|
|
|
def __init__(self, connection_string="udp:127.0.0.1:14550"):
|
|
|
|
|
super().__init__()
|
|
|
|
|
self.connection = mavutil.mavlink_connection(connection_string, baud=57600)
|
|
|
|
|
self.connection.wait_heartbeat()
|
|
|
|
|
self.connection = mavutil.mavlink_connection(connection_string)
|
|
|
|
|
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"]
|
|
|
|
|
# 記錄訊息的時間戳和丟包數
|
|
|
|
|
self.last_received_time = time.time()
|
|
|
|
|
self.message_count = 0
|
|
|
|
|
self.missed_messages = 0
|
|
|
|
|
# 用於計算頻率的時間窗口(每隔 1 秒計算一次頻率)
|
|
|
|
|
self.start_time = time.time()
|
|
|
|
|
# 記錄處理過的消息類型及其對應時間
|
|
|
|
|
self.message_times = {}
|
|
|
|
|
self.delay = deque(maxlen=20)
|
|
|
|
|
|
|
|
|
|
self.seq_numbers = {}
|
|
|
|
|
self.packet_loss_count = {}
|
|
|
|
|
self.total_packet_count = {}
|
|
|
|
|
self.loss_percentage = {}
|
|
|
|
|
|
|
|
|
|
self.seq = 0
|
|
|
|
|
self.ping_time = 0
|
|
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
|
while self.running:
|
|
|
|
|
try:
|
|
|
|
|
current_time = time.time()
|
|
|
|
|
self.connection.mav.timesync_send(int(current_time*1e6), 0)
|
|
|
|
|
'''
|
|
|
|
|
if self.ping_time - current_time > 1e3:
|
|
|
|
|
self.connection.mav.ping_send(
|
|
|
|
|
time_usec = current_time,
|
|
|
|
|
seq = self.seq,
|
|
|
|
|
target_system = 0,
|
|
|
|
|
target_component = 0
|
|
|
|
|
)
|
|
|
|
|
print(f"Ping {self.seq} sent at {current_time} ms")
|
|
|
|
|
'''
|
|
|
|
|
msg = self.connection.recv_match(blocking=True)
|
|
|
|
|
if not msg:
|
|
|
|
|
continue
|
|
|
|
|
@ -71,30 +35,6 @@ class MAVLinkWorker(QThread):
|
|
|
|
|
if sysid not in self.uav_data:
|
|
|
|
|
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.seq_numbers[sysid] = current_seq
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
if self.total_packet_count[sysid] > 0:
|
|
|
|
|
self.loss_percentage[sysid] = (self.packet_loss_count[sysid] / self.total_packet_count[sysid]) * 100
|
|
|
|
|
print(f"UAV {sysid} Packet Loss: {self.packet_loss_count[sysid]} / {self.total_packet_count[sysid]} "
|
|
|
|
|
f"({self.loss_percentage[sysid]:.2f}%)")
|
|
|
|
|
'''
|
|
|
|
|
|
|
|
|
|
if msg_type == "HEARTBEAT":
|
|
|
|
|
mode = mavutil.mode_string_v10(msg)
|
|
|
|
|
self.state_signal.emit(namespace, mode)
|
|
|
|
|
@ -120,7 +60,7 @@ class MAVLinkWorker(QThread):
|
|
|
|
|
self.local_position_signal.emit(namespace, x, y, z)
|
|
|
|
|
|
|
|
|
|
elif msg_type == "ATTITUDE":
|
|
|
|
|
pitch = math.degrees(msg.pitch)
|
|
|
|
|
pitch = msg.pitch
|
|
|
|
|
self.imu_signal.emit(namespace, pitch)
|
|
|
|
|
|
|
|
|
|
elif msg_type == "VFR_HUD":
|
|
|
|
|
@ -129,45 +69,6 @@ class MAVLinkWorker(QThread):
|
|
|
|
|
self.hdg_signal.emit(namespace, heading)
|
|
|
|
|
self.groundspeed_signal.emit(namespace, groundspeed)
|
|
|
|
|
|
|
|
|
|
elif msg_type == "TIMESYNC":
|
|
|
|
|
# Calculate the round-trip time in milliseconds
|
|
|
|
|
round_trip_time = (int(time.time() * 1e6) - msg.ts1) / 1000.0
|
|
|
|
|
print(f"Round-trip time (ping): {round_trip_time:.3f} ms")
|
|
|
|
|
|
|
|
|
|
self.seq += 1
|
|
|
|
|
|
|
|
|
|
if msg_type in self.messages_to_monitor:
|
|
|
|
|
# 更新消息接收時間
|
|
|
|
|
if msg_type not in self.message_times:
|
|
|
|
|
self.message_times[msg_type] = current_time
|
|
|
|
|
else:
|
|
|
|
|
# 計算每條消息的頻率
|
|
|
|
|
self.time_diff = current_time - self.message_times[msg_type]
|
|
|
|
|
self.message_times[msg_type] = current_time
|
|
|
|
|
frequency = 1 / self.time_diff if self.time_diff > 0 else 0
|
|
|
|
|
|
|
|
|
|
timestamp = msg._timestamp
|
|
|
|
|
round_trip_delay = (timestamp - current_time)*1000
|
|
|
|
|
self.delay.append(round_trip_delay)
|
|
|
|
|
|
|
|
|
|
# 計算總的頻率
|
|
|
|
|
self.message_count += 1
|
|
|
|
|
|
|
|
|
|
self.last_received_time = current_time
|
|
|
|
|
|
|
|
|
|
# 每隔一段時間更新總頻率
|
|
|
|
|
if current_time - self.start_time >= 1:
|
|
|
|
|
# 更新每秒頻率
|
|
|
|
|
frequency = self.message_count / (current_time - self.start_time)
|
|
|
|
|
print(f"Overall frequency: {frequency:.2f} Hz")
|
|
|
|
|
if self.delay:
|
|
|
|
|
average = sum(self.delay) / len(self.delay)
|
|
|
|
|
print(f"The average delay is: {average:.4f} ms")
|
|
|
|
|
self.start_time = current_time
|
|
|
|
|
self.message_count = 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Error reading message: {e}")
|
|
|
|
|
|
|
|
|
|
@ -185,7 +86,7 @@ class MAVLinkWorker(QThread):
|
|
|
|
|
mode = 4
|
|
|
|
|
elif mode == 'LOITER':
|
|
|
|
|
mode = 5
|
|
|
|
|
self.connection.mav.set_mode_send( #SET_MODE (11) — [DEP]
|
|
|
|
|
self.connection.mav.set_mode_send(
|
|
|
|
|
sysid,
|
|
|
|
|
mavutil.mavlink.MAV_MODE_FLAG_CUSTOM_MODE_ENABLED,
|
|
|
|
|
mode
|
|
|
|
|
@ -196,18 +97,27 @@ class MAVLinkWorker(QThread):
|
|
|
|
|
self.connection.mav.command_long_send(
|
|
|
|
|
sysid,
|
|
|
|
|
1, # Component ID
|
|
|
|
|
mavutil.mavlink.MAV_CMD_COMPONENT_ARM_DISARM, #MAV_CMD_COMPONENT_ARM_DISARM (400)
|
|
|
|
|
mavutil.mavlink.MAV_CMD_COMPONENT_ARM_DISARM,
|
|
|
|
|
0, # Confirmation
|
|
|
|
|
1 if arm else 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):
|
|
|
|
|
sysid = int(namespace[-1])
|
|
|
|
|
self.connection.mav.command_long_send(
|
|
|
|
|
sysid,
|
|
|
|
|
1, # Component ID
|
|
|
|
|
mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, #MAV_CMD_NAV_TAKEOFF (22)
|
|
|
|
|
mavutil.mavlink.MAV_CMD_NAV_TAKEOFF, # Command to execute
|
|
|
|
|
0, # Confirmation (0 = first transmission)
|
|
|
|
|
0, 0, 0, 0, # Parameters 1-4 (Unused in takeoff command)
|
|
|
|
|
0, 0, altitude # Latitude, Longitude, Altitude (target_altitude in meters)
|
|
|
|
|
@ -218,7 +128,7 @@ class MAVLinkWorker(QThread):
|
|
|
|
|
self.connection.mav.command_long_send(
|
|
|
|
|
sysid,
|
|
|
|
|
1, # Component ID
|
|
|
|
|
mavutil.mavlink.MAV_CMD_NAV_LAND, #MAV_CMD_NAV_LAND (21)
|
|
|
|
|
mavutil.mavlink.MAV_CMD_NAV_LAND,
|
|
|
|
|
0,
|
|
|
|
|
0, 0, 0, 0,
|
|
|
|
|
0, 0, 0
|
|
|
|
|
@ -236,24 +146,11 @@ class MAVLinkWorker(QThread):
|
|
|
|
|
0, 0 # Yaw, yaw_rate
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def set_parameter(self, namespace, param_name, value):
|
|
|
|
|
sysid = int(namespace[-1])
|
|
|
|
|
try:
|
|
|
|
|
self.connection.mav.param_set_send(
|
|
|
|
|
sysid,
|
|
|
|
|
1,
|
|
|
|
|
param_name.encode('utf-8'),
|
|
|
|
|
float(value),
|
|
|
|
|
mavutil.mavlink.MAV_PARAM_TYPE_REAL32
|
|
|
|
|
)
|
|
|
|
|
print(f"Parameter {param_name} set to {value}")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Failed to set parameter {param_name}: {e}")
|
|
|
|
|
|
|
|
|
|
class DroneControlApp(QMainWindow):
|
|
|
|
|
def __init__(self):
|
|
|
|
|
super().__init__()
|
|
|
|
|
self.namespaces = ['uav1', 'uav4', 'uav100']
|
|
|
|
|
self.namespaces = ['uav1', 'uav2', 'uav3']
|
|
|
|
|
self.workers = MAVLinkWorker()
|
|
|
|
|
self.initUI()
|
|
|
|
|
|
|
|
|
|
@ -266,6 +163,7 @@ class DroneControlApp(QMainWindow):
|
|
|
|
|
self.workers.imu_signal.connect(self.update_imu)
|
|
|
|
|
self.workers.hdg_signal.connect(self.update_hdg)
|
|
|
|
|
self.workers.groundspeed_signal.connect(self.update_groundspeed)
|
|
|
|
|
|
|
|
|
|
self.workers.start()
|
|
|
|
|
|
|
|
|
|
def initUI(self):
|
|
|
|
|
@ -315,17 +213,6 @@ class DroneControlApp(QMainWindow):
|
|
|
|
|
|
|
|
|
|
main_layout.addLayout(uav_layout)
|
|
|
|
|
|
|
|
|
|
# EK3_SRC2_POSXY參數設置
|
|
|
|
|
param_layout = QHBoxLayout()
|
|
|
|
|
self.paramInput = QLineEdit()
|
|
|
|
|
self.paramInput.setPlaceholderText("輸入EK3_SRC2_POSXY的新值")
|
|
|
|
|
self.setParamButton = QPushButton("設置EK3_SRC2_POSXY")
|
|
|
|
|
self.setParamButton.clicked.connect(self.set_ek3_src2_posxy)
|
|
|
|
|
param_layout.addWidget(QLabel("EK3_SRC2_POSXY:"))
|
|
|
|
|
param_layout.addWidget(self.paramInput)
|
|
|
|
|
param_layout.addWidget(self.setParamButton)
|
|
|
|
|
main_layout.addLayout(param_layout)
|
|
|
|
|
|
|
|
|
|
# 模式切換區域
|
|
|
|
|
mode_layout = QHBoxLayout()
|
|
|
|
|
self.modeComboBox = QComboBox()
|
|
|
|
|
@ -459,15 +346,6 @@ class DroneControlApp(QMainWindow):
|
|
|
|
|
except ValueError:
|
|
|
|
|
QtWidgets.QMessageBox.warning(self, "錯誤", "請輸入有效的數值!")
|
|
|
|
|
|
|
|
|
|
def set_ek3_src2_posxy(self):
|
|
|
|
|
param_value = self.paramInput.text().strip()
|
|
|
|
|
try:
|
|
|
|
|
for namespace in self.namespaces:
|
|
|
|
|
if self.uav_checkboxes[namespace].isChecked():
|
|
|
|
|
self.workers.set_parameter(namespace, "EK3_SRC2_POSXY", param_value)
|
|
|
|
|
except ValueError:
|
|
|
|
|
QtWidgets.QMessageBox.warning(self, "錯誤", "請輸入有效的數值!")
|
|
|
|
|
|
|
|
|
|
def main(args=None):
|
|
|
|
|
rclpy.init(args=args)
|
|
|
|
|
app = QtWidgets.QApplication(sys.argv)
|