Merge branch 'chiyu' into wenchun
commit
456d12ce56
@ -0,0 +1,531 @@
|
|||||||
|
# 基礎功能的 import
|
||||||
|
import queue
|
||||||
|
import time
|
||||||
|
|
||||||
|
# ROS2 的 import
|
||||||
|
import rclpy
|
||||||
|
|
||||||
|
# mavlink 的 import
|
||||||
|
from pymavlink import mavutil
|
||||||
|
|
||||||
|
# 自定義的 import
|
||||||
|
import mavlinkObject as mo
|
||||||
|
import mavlinkDevice as md
|
||||||
|
|
||||||
|
# ====================== 分割線 =====================
|
||||||
|
|
||||||
|
test_item = 51
|
||||||
|
running_time = 30
|
||||||
|
print('test_item : ', test_item)
|
||||||
|
|
||||||
|
if test_item == 1:
|
||||||
|
# 測試 mavlink_object 中 updateMultiplexingList 的輸出
|
||||||
|
print('===> Start of Program .Test ', test_item)
|
||||||
|
mavlink_object_none = mo.mavlink_object(None)
|
||||||
|
|
||||||
|
print('=====================')
|
||||||
|
print('正常範例')
|
||||||
|
mavlink_object_none.multiplexingToAnalysis = []
|
||||||
|
mavlink_object_none.multiplexingToReturn = []
|
||||||
|
# mavlink_object_none.multiplexingToSwap = [[]]
|
||||||
|
ret = mavlink_object_none.updateMultiplexingList()
|
||||||
|
print('execute result : ', ret)
|
||||||
|
print('multiplexingList : ', mavlink_object_none._multiplexingList)
|
||||||
|
|
||||||
|
print('=====================')
|
||||||
|
print('錯誤範例 長度不一致')
|
||||||
|
mavlink_object_none.multiplexingToSwap = [[-1], [11,12,13], [14,15,16],]
|
||||||
|
ret = mavlink_object_none.updateMultiplexingList()
|
||||||
|
print('execute result : ', ret)
|
||||||
|
|
||||||
|
print('=====================')
|
||||||
|
print('正常範例')
|
||||||
|
mo.swap_queues.append(queue.Queue())
|
||||||
|
mo.swap_queues.append(queue.Queue())
|
||||||
|
mavlink_object_none.multiplexingToAnalysis = [0, 1, 2, 3, 4, 5]
|
||||||
|
mavlink_object_none.multiplexingToReturn = [6, 7, 8, 9, 10]
|
||||||
|
mavlink_object_none.multiplexingToSwap = [[-1], [11,12,13], [14,15,16],]
|
||||||
|
ret = mavlink_object_none.updateMultiplexingList()
|
||||||
|
print('execute result : ', ret)
|
||||||
|
print('multiplexingList : ', mavlink_object_none._multiplexingList)
|
||||||
|
|
||||||
|
print('=====================')
|
||||||
|
print('錯誤範例 multiplexingToAnalysis 不可以有 -1')
|
||||||
|
mavlink_object_none.multiplexingToAnalysis = [-1, 1, 2, 3, 4, 5]
|
||||||
|
ret = mavlink_object_none.updateMultiplexingList()
|
||||||
|
print('execute result : ', ret)
|
||||||
|
|
||||||
|
print('<=== End of Program')
|
||||||
|
|
||||||
|
elif test_item == 2:
|
||||||
|
# 測試 mavlink_object 創建時 socket_id 是否正確
|
||||||
|
# 說實在 這個測試項 似乎因為 python 的 GC 機制 會導致難以測試
|
||||||
|
print('===> Start of Program .Test ', test_item)
|
||||||
|
mavlink_object_none1 = mo.mavlink_object(None)
|
||||||
|
mavlink_object_none2 = mo.mavlink_object(None)
|
||||||
|
mavlink_object_none3 = mo.mavlink_object(None)
|
||||||
|
del mavlink_object_none2
|
||||||
|
|
||||||
|
print(len(mo.swap_queues))
|
||||||
|
|
||||||
|
mavlink_object_none2 = mo.mavlink_object(None)
|
||||||
|
print(len(mo.swap_queues))
|
||||||
|
|
||||||
|
print(mavlink_object_none1._multiplexingList)
|
||||||
|
|
||||||
|
print('<=== End of Program')
|
||||||
|
|
||||||
|
elif test_item == 10:
|
||||||
|
# 需要開啟一個 ardupilot 的模擬器
|
||||||
|
# 這邊是測試代碼 運行10秒 過程中把三個 queue 的資料印出來
|
||||||
|
# 只啟用了 mavlink_object 的功能
|
||||||
|
print('===> Start of Program .Test ', test_item)
|
||||||
|
|
||||||
|
# 創建一個空的通道 這個通道的 socket_id 是 0
|
||||||
|
mavlink_object_none = mo.mavlink_object(None)
|
||||||
|
|
||||||
|
# 創建另一個通道
|
||||||
|
connection_string="udp:127.0.0.1:14550"
|
||||||
|
mavlink_socket = mavutil.mavlink_connection(connection_string)
|
||||||
|
mavlink_object1 = mo.mavlink_object(mavlink_socket)
|
||||||
|
# 設定通道轉發的參數
|
||||||
|
mavlink_object1.multiplexingToAnalysis = [30] # only ATTITUDE
|
||||||
|
mavlink_object1.multiplexingToReturn = [0] # only HEARTBEAT
|
||||||
|
mavlink_object1.multiplexingToSwap[0] = [74, ] # only VFR_HUD
|
||||||
|
|
||||||
|
# print(mavlink_object1.multiplexingToSwap)
|
||||||
|
# print(mo.swap_queues)
|
||||||
|
|
||||||
|
# 啟動通道
|
||||||
|
mavlink_object1.run()
|
||||||
|
|
||||||
|
# 確認轉拋的設定有沒有錯
|
||||||
|
print("_multiplexingList for mavlink object :", mavlink_object1._multiplexingList)
|
||||||
|
|
||||||
|
# 運行幾秒並印出 queue 的資料
|
||||||
|
start_time = time.time()
|
||||||
|
while time.time() - start_time < running_time:
|
||||||
|
while not mo.fixed_stream_bridge_queue.empty():
|
||||||
|
print('fixed_stream_bridge_queue:')
|
||||||
|
t = mo.fixed_stream_bridge_queue.get()
|
||||||
|
print('from {} : {}'.format(t[0],t[2]))
|
||||||
|
while not mo.return_packet_processor_queue.empty():
|
||||||
|
print('return_packet_processor_queue:')
|
||||||
|
t = mo.return_packet_processor_queue.get()
|
||||||
|
print('from {} : {}'.format(t[0],t[2]))
|
||||||
|
# print(t[2].get_msgbuf())
|
||||||
|
# print(t[2].type)
|
||||||
|
|
||||||
|
for n in range(len(mo.swap_queues)):
|
||||||
|
q = mo.swap_queues[n]
|
||||||
|
while not q.empty():
|
||||||
|
print('swap_queues:')
|
||||||
|
t = q.get()
|
||||||
|
print('{} gets : {}'.format(n,t))
|
||||||
|
|
||||||
|
|
||||||
|
# 結束程式 退出所有 thread
|
||||||
|
mavlink_object1.running = False
|
||||||
|
mavlink_object1.thread.join()
|
||||||
|
mavlink_socket.close()
|
||||||
|
print('<=== End of Program')
|
||||||
|
|
||||||
|
elif test_item == 11:
|
||||||
|
# 需要開啟一個 ardupilot 的模擬器
|
||||||
|
# 這邊是測試代碼 確認 analyzer 運行後對於 device object 的建立與封包統計狀況
|
||||||
|
# 啟用 mavlink_object 與 mavlink_bridge的thread區塊 的功能
|
||||||
|
print('===> Start of Program .Test ', test_item)
|
||||||
|
|
||||||
|
# 啟動 mavlink_bridge
|
||||||
|
analyzer = mo.mavlink_bridge()
|
||||||
|
|
||||||
|
# 創建通道
|
||||||
|
connection_string="udp:127.0.0.1:14550"
|
||||||
|
mavlink_socket = mavutil.mavlink_connection(connection_string)
|
||||||
|
mavlink_object2 = mo.mavlink_object(mavlink_socket)
|
||||||
|
# mavlink_object2.multiplexingToAnalysis = [0] # only HEARTBEAT
|
||||||
|
# mavlink_object2.multiplexingToReturn = []
|
||||||
|
|
||||||
|
# 啟動連線的模組
|
||||||
|
mavlink_object2.run()
|
||||||
|
|
||||||
|
# 做點延遲 讓 heartbeat 先吃進來
|
||||||
|
time.sleep(2)
|
||||||
|
print("=== connection established! ===")
|
||||||
|
|
||||||
|
# 印出目前所有 mavlink_systems 的內容
|
||||||
|
print('目前所有的系統 : ')
|
||||||
|
for sysid in analyzer.mavlink_systems:
|
||||||
|
print(analyzer.mavlink_systems[sysid])
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
show_time = time.time()
|
||||||
|
compid = 1
|
||||||
|
while time.time() - start_time < running_time:
|
||||||
|
if (time.time() - show_time) >= 1:
|
||||||
|
show_time = time.time()
|
||||||
|
for sysid in analyzer.mavlink_systems:
|
||||||
|
print("目前收到的訊息數量 : ",analyzer.mavlink_systems[sysid].components[compid].msg_count)
|
||||||
|
print("目前遺失的訊息數量 : ",analyzer.mavlink_systems[sysid].components[compid].lost_packet_count)
|
||||||
|
print("現在飛機的模式 : ",analyzer.mavlink_systems[sysid].components[compid].emitParams['flightMode_mode'])
|
||||||
|
analyzer.mavlink_systems[sysid].resetComponentPacketCount(compid)
|
||||||
|
print("===================")
|
||||||
|
|
||||||
|
# 結束程式 退出所有 thread
|
||||||
|
mavlink_object2.stop()
|
||||||
|
mavlink_object2.thread.join()
|
||||||
|
analyzer.stop()
|
||||||
|
|
||||||
|
analyzer = mo.mavlink_bridge() # 這邊是測試是否只有一個 instance
|
||||||
|
analyzer.thread.join()
|
||||||
|
mavlink_socket.close()
|
||||||
|
print('<=== End of Program')
|
||||||
|
|
||||||
|
elif test_item == 12:
|
||||||
|
# 需要開啟一個 ardupilot 的模擬器 與 GCS
|
||||||
|
# 這邊是測試 mavlink object 作為交換器功能的代碼
|
||||||
|
print('===> Start of Program .Test ', test_item)
|
||||||
|
|
||||||
|
# 啟動連線的模組
|
||||||
|
analyzer = mo.mavlink_bridge()
|
||||||
|
|
||||||
|
# 初始化輸入通道
|
||||||
|
connection_string_in="udp:127.0.0.1:15551"
|
||||||
|
mavlink_socket_in = mavutil.mavlink_connection(connection_string_in)
|
||||||
|
mavlink_object_in = mo.mavlink_object(mavlink_socket_in)
|
||||||
|
mavlink_object_in.multiplexingToAnalysis = [0] # only HEARTBEAT
|
||||||
|
|
||||||
|
# 初始化輸出通道
|
||||||
|
connection_string_out="udpout:127.0.0.1:14553"
|
||||||
|
mavlink_socket_out = mavutil.mavlink_connection(connection_string_out)
|
||||||
|
mavlink_object_out = mo.mavlink_object(mavlink_socket_out)
|
||||||
|
mavlink_object_out.multiplexingToAnalysis = [0]
|
||||||
|
|
||||||
|
# 做一個空的通道驗證 可以拿來 debug
|
||||||
|
mavlink_object_none = mo.mavlink_object(None)
|
||||||
|
|
||||||
|
# 讓兩個通道互相傳輸
|
||||||
|
mavlink_object_in.multiplexingToSwap[mavlink_object_out.socket_id] = [-1, ] # all
|
||||||
|
mavlink_object_out.multiplexingToSwap[mavlink_object_in.socket_id] = [-1, ] # all
|
||||||
|
# mavlink_object_out.multiplexingToSwap[mavlink_object_none.socket_id] = [-1, ] # all
|
||||||
|
|
||||||
|
# 啟動通道
|
||||||
|
mavlink_object_in.run()
|
||||||
|
mavlink_object_out.run()
|
||||||
|
|
||||||
|
# 做點延遲 讓 heartbeat 先吃進來
|
||||||
|
time.sleep(3)
|
||||||
|
print("=== connection established! ===")
|
||||||
|
|
||||||
|
print('目前所有的系統 : ')
|
||||||
|
for sysid in analyzer.mavlink_systems:
|
||||||
|
print(analyzer.mavlink_systems[sysid])
|
||||||
|
|
||||||
|
# print(type(mavlink_socket_out))
|
||||||
|
# print(type(mavlink_socket_out.mav))
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
show_time = time.time()
|
||||||
|
while time.time() - start_time < running_time:
|
||||||
|
try:
|
||||||
|
test = mo.swap_queues[mavlink_object_none.socket_id].get(block=False)
|
||||||
|
print('none object : ', test)
|
||||||
|
except queue.Empty:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if (time.time() - show_time) >= 2:
|
||||||
|
show_time = time.time()
|
||||||
|
for sysid in analyzer.mavlink_systems:
|
||||||
|
for compid in analyzer.mavlink_systems[sysid].components:
|
||||||
|
print("Sysid : {} ,目前收到的訊息數量 : {}".format(sysid, analyzer.mavlink_systems[sysid].components[compid].msg_count))
|
||||||
|
analyzer.mavlink_systems[sysid].resetComponentPacketCount(compid)
|
||||||
|
print("===================")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 結束程式 退出所有 thread
|
||||||
|
mavlink_object_in.stop()
|
||||||
|
mavlink_object_in.thread.join()
|
||||||
|
mavlink_object_out.stop()
|
||||||
|
mavlink_object_out.thread.join()
|
||||||
|
mavlink_socket_in.close()
|
||||||
|
mavlink_socket_out.close()
|
||||||
|
|
||||||
|
analyzer.stop()
|
||||||
|
print('<=== End of Program')
|
||||||
|
|
||||||
|
elif test_item == 20:
|
||||||
|
# 這邊測試 node 生成 topic 的功能
|
||||||
|
# 利用 空的通道 發出假的 heartbeat 封包
|
||||||
|
print('===> Start of Program .Test ', test_item)
|
||||||
|
rclpy.init() # 注意要初始化 rclpy 才能使用 node
|
||||||
|
|
||||||
|
from pymavlink.dialects.v20 import common as mavlink2
|
||||||
|
|
||||||
|
sysid = 1
|
||||||
|
compid = 1
|
||||||
|
|
||||||
|
# 啟動 mavlink_bridge
|
||||||
|
analyzer = mo.mavlink_bridge()
|
||||||
|
|
||||||
|
mav = mavlink2.MAVLink(None)
|
||||||
|
mav.srcSystem = sysid
|
||||||
|
mav.srcComponent = compid
|
||||||
|
|
||||||
|
# 這是一個 heartbeat 封包
|
||||||
|
fake_heartbeat = mavlink2.MAVLink_heartbeat_message(
|
||||||
|
type=mavlink2.MAV_TYPE_QUADROTOR,
|
||||||
|
autopilot=mavlink2.MAV_AUTOPILOT_ARDUPILOTMEGA,
|
||||||
|
base_mode=3,
|
||||||
|
custom_mode=0,
|
||||||
|
system_status=0,
|
||||||
|
mavlink_version=3
|
||||||
|
)
|
||||||
|
mavlink_bytes = fake_heartbeat.pack(mav)
|
||||||
|
fake_heartbeat_msg = mav.parse_char(mavlink_bytes)
|
||||||
|
|
||||||
|
print(analyzer.mavlink_systems)
|
||||||
|
|
||||||
|
mo.fixed_stream_bridge_queue.put((0, 0, fake_heartbeat_msg)) # socket id, timestamp, data
|
||||||
|
time.sleep(0.1)
|
||||||
|
print(analyzer.mavlink_systems)
|
||||||
|
|
||||||
|
# ROS2 初始化
|
||||||
|
analyzer._init_node()
|
||||||
|
|
||||||
|
# 創建 ROS2 topic
|
||||||
|
analyzer.create_flightMode(sysid, analyzer.mavlink_systems[sysid].components[compid]) # sysid, compid
|
||||||
|
print("topic created")
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
# 丟出 topic
|
||||||
|
analyzer.emit_info()
|
||||||
|
|
||||||
|
# 結束程式
|
||||||
|
analyzer.running = False
|
||||||
|
analyzer.destroy_node()
|
||||||
|
|
||||||
|
rclpy.shutdown()
|
||||||
|
analyzer.stop()
|
||||||
|
analyzer.thread.join()
|
||||||
|
print('<=== End of Program')
|
||||||
|
|
||||||
|
elif test_item == 21:
|
||||||
|
# 需要開啟一個 ardupilot 的模擬器
|
||||||
|
# 這邊是測試代碼 引入 rclpy 來測試 node 的運行
|
||||||
|
|
||||||
|
print('===> Start of Program .Test ', test_item)
|
||||||
|
rclpy.init() # 注意要初始化 rclpy 才能使用 node
|
||||||
|
|
||||||
|
# 啟動 mavlink_bridge
|
||||||
|
analyzer = mo.mavlink_bridge()
|
||||||
|
# 關於 Node 的初始化
|
||||||
|
show_time = time.time()
|
||||||
|
analyzer._init_node() # 初始化 node
|
||||||
|
print('初始化 node 完成 耗時 : ',time.time() - show_time)
|
||||||
|
|
||||||
|
# 創建通道
|
||||||
|
connection_string="udp:127.0.0.1:15551"
|
||||||
|
mavlink_socket = mavutil.mavlink_connection(connection_string)
|
||||||
|
mavlink_object3 = mo.mavlink_object(mavlink_socket)
|
||||||
|
# 啟動通道
|
||||||
|
mavlink_object3.run()
|
||||||
|
|
||||||
|
|
||||||
|
print('=== waiting for mavlink data ...')
|
||||||
|
time.sleep(2) # 等待 2 秒鐘 讓 device object 收到足夠的 mavlink 訊息
|
||||||
|
|
||||||
|
print('目前所有的系統 : ')
|
||||||
|
for sysid in analyzer.mavlink_systems:
|
||||||
|
print(analyzer.mavlink_systems[sysid])
|
||||||
|
|
||||||
|
compid = 1
|
||||||
|
sysid = 1
|
||||||
|
start_time = time.time()
|
||||||
|
analyzer.create_flightMode(sysid, analyzer.mavlink_systems[sysid].components[compid])
|
||||||
|
end_time = time.time()
|
||||||
|
print(f"Execution time for create_flightMode: {end_time - start_time} seconds")
|
||||||
|
|
||||||
|
print("start emit info")
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
show_time = time.time()
|
||||||
|
while time.time() - start_time < running_time:
|
||||||
|
try:
|
||||||
|
# rclpy.spin(analyzer)
|
||||||
|
analyzer.emit_info() # 這邊是測試 node 的運行
|
||||||
|
time.sleep(1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
# 程式結束
|
||||||
|
analyzer.destroy_node()
|
||||||
|
rclpy.shutdown()
|
||||||
|
|
||||||
|
# 結束程式 退出所有 thread
|
||||||
|
mavlink_object3.stop()
|
||||||
|
mavlink_object3.thread.join()
|
||||||
|
analyzer.stop()
|
||||||
|
analyzer.thread.join()
|
||||||
|
|
||||||
|
mavlink_socket.close()
|
||||||
|
print('<=== End of Program')
|
||||||
|
|
||||||
|
elif test_item == 22:
|
||||||
|
# 需要開啟一個 ardupilot 的模擬器 與 GCS
|
||||||
|
# 這邊是測試代碼 引入 rclpy 來測試 node 的運行
|
||||||
|
print('===> Start of Program .Test ', test_item)
|
||||||
|
rclpy.init() # 注意要初始化 rclpy 才能使用 node
|
||||||
|
|
||||||
|
# 啟動 mavlink_bridge
|
||||||
|
analyzer = mo.mavlink_bridge()
|
||||||
|
# 關於 Node 的初始化
|
||||||
|
show_time = time.time()
|
||||||
|
analyzer._init_node() # 初始化 node
|
||||||
|
print('初始化 node 完成 耗時 : ',time.time() - show_time)
|
||||||
|
|
||||||
|
# 初始化兩個通道
|
||||||
|
connection_string_in="udp:127.0.0.1:15551"
|
||||||
|
mavlink_socket_in = mavutil.mavlink_connection(connection_string_in)
|
||||||
|
mavlink_object_in = mo.mavlink_object(mavlink_socket_in)
|
||||||
|
mavlink_object_in.multiplexingToAnalysis = [0, 30, 32, 33, 74, 147]
|
||||||
|
|
||||||
|
|
||||||
|
connection_string_out="udpout:127.0.0.1:14553"
|
||||||
|
mavlink_socket_out = mavutil.mavlink_connection(connection_string_out)
|
||||||
|
mavlink_object_out = mo.mavlink_object(mavlink_socket_out)
|
||||||
|
mavlink_object_out.multiplexingToAnalysis = []
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 讓兩個通道互相傳輸
|
||||||
|
mavlink_object_in.multiplexingToSwap[mavlink_object_out.socket_id] = [-1, ] # all
|
||||||
|
mavlink_object_out.multiplexingToSwap[mavlink_object_in.socket_id] = [-1, ] # all
|
||||||
|
|
||||||
|
# 啟動通道
|
||||||
|
mavlink_object_in.run()
|
||||||
|
mavlink_object_out.run()
|
||||||
|
|
||||||
|
print('waiting for mavlink data ...')
|
||||||
|
time.sleep(2) # 等待 2 秒鐘 讓 device object 收到足夠的 mavlink 訊息
|
||||||
|
|
||||||
|
print('目前所有的系統 : ')
|
||||||
|
for sysid in analyzer.mavlink_systems:
|
||||||
|
print(analyzer.mavlink_systems[sysid])
|
||||||
|
|
||||||
|
compid = 1
|
||||||
|
sysid = 1
|
||||||
|
show_time = time.time()
|
||||||
|
analyzer.create_flightMode(sysid, analyzer.mavlink_systems[sysid].components[compid])
|
||||||
|
print(f"Execution time for create_flightMode: {time.time() - show_time} seconds")
|
||||||
|
|
||||||
|
print("start emit info")
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
show_time = time.time()
|
||||||
|
show_time2 = time.time()
|
||||||
|
|
||||||
|
while time.time() - start_time < running_time:
|
||||||
|
if (time.time() - show_time2) >= 1:
|
||||||
|
analyzer.emit_info() # 這邊是測試 node 的運行
|
||||||
|
# sss = analyzer.mavlink_systems[1].components[1].emitParams['flightMode_mode']
|
||||||
|
fmsg = analyzer.mavlink_systems[1].components[1].emitParams['flightMode']
|
||||||
|
sss = mavutil.mode_string_v10(fmsg)
|
||||||
|
# print("目前的飛行模式 : {}, Msg Seq : {}".format(sss, fmsg.get_seq()))
|
||||||
|
print("目前的飛行模式 : {}".format(sss))
|
||||||
|
show_time2 = time.time()
|
||||||
|
|
||||||
|
# if (time.time() - show_time) >= 2:
|
||||||
|
# show_time = time.time()
|
||||||
|
# for sysid in analyzer.mavlink_systems:
|
||||||
|
# for compid in analyzer.mavlink_systems[sysid].components:
|
||||||
|
# print("Sysid : {} ,目前收到的訊息數量 : {}".format(sysid, analyzer.mavlink_systems[sysid].components[compid].msg_count))
|
||||||
|
# analyzer.mavlink_systems[sysid].resetComponentPacketCount(compid)
|
||||||
|
# print("===================")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
analyzer.destroy_node()
|
||||||
|
rclpy.shutdown()
|
||||||
|
|
||||||
|
|
||||||
|
# 結束程式 退出所有 thread
|
||||||
|
mavlink_object_in.stop()
|
||||||
|
mavlink_object_in.thread.join()
|
||||||
|
mavlink_socket_in.close()
|
||||||
|
mavlink_object_out.stop()
|
||||||
|
mavlink_object_out.thread.join()
|
||||||
|
mavlink_socket_out.close()
|
||||||
|
analyzer.stop()
|
||||||
|
analyzer.thread.join()
|
||||||
|
|
||||||
|
|
||||||
|
print('<=== End of Program')
|
||||||
|
|
||||||
|
elif test_item == 51:
|
||||||
|
|
||||||
|
# 晉凱的測試項目
|
||||||
|
print('===> Start of Program .Test ', test_item)
|
||||||
|
rclpy.init() # 注意要初始化 rclpy 才能使用 node
|
||||||
|
|
||||||
|
# 啟動 mavlink_bridge
|
||||||
|
analyzer = mo.mavlink_bridge()
|
||||||
|
# 關於 Node 的初始化
|
||||||
|
show_time = time.time()
|
||||||
|
analyzer._init_node() # 初始化 node
|
||||||
|
print('初始化 node 完成 耗時 : ',time.time() - show_time)
|
||||||
|
|
||||||
|
# 創建通道
|
||||||
|
connection_string="udp:127.0.0.1:15551"
|
||||||
|
mavlink_socket3 = mavutil.mavlink_connection(connection_string)
|
||||||
|
mavlink_object3 = mo.mavlink_object(mavlink_socket3)
|
||||||
|
# 設定通道流動
|
||||||
|
mavlink_object3.multiplexingToAnalysis = [0, 30, 32, 33, 74, 147]
|
||||||
|
mavlink_object3.multiplexingToReturn = [] #
|
||||||
|
# mavlink_object3.multiplexingToSwap = [] #
|
||||||
|
# 啟動通道
|
||||||
|
mavlink_object3.run()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
print('waiting for mavlink data ...')
|
||||||
|
time.sleep(2) # 等待 2 秒鐘 讓 device object 收到足夠的 mavlink 訊息
|
||||||
|
|
||||||
|
compid = 1
|
||||||
|
sysid = 1
|
||||||
|
start_time = time.time()
|
||||||
|
analyzer.create_flightMode(sysid, analyzer.mavlink_systems[sysid].components[compid])
|
||||||
|
analyzer.create_attitude(sysid, analyzer.mavlink_systems[sysid].components[compid])
|
||||||
|
analyzer.create_local_position_pose(sysid, analyzer.mavlink_systems[sysid].components[compid])
|
||||||
|
analyzer.create_local_position_velocity(sysid, analyzer.mavlink_systems[sysid].components[compid])
|
||||||
|
analyzer.create_global_global(sysid, analyzer.mavlink_systems[sysid].components[compid])
|
||||||
|
analyzer.create_vfr_hud(sysid, analyzer.mavlink_systems[sysid].components[compid])
|
||||||
|
analyzer.create_battery(sysid, analyzer.mavlink_systems[sysid].components[compid])
|
||||||
|
analyzer.create_global_rel(sysid, analyzer.mavlink_systems[sysid].components[compid])
|
||||||
|
end_time = time.time()
|
||||||
|
print(f"Execution time for create all topic: {end_time - start_time} seconds")
|
||||||
|
|
||||||
|
print("start emit info")
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
show_time = time.time()
|
||||||
|
while time.time() - start_time < running_time:
|
||||||
|
try:
|
||||||
|
# rclpy.spin(analyzer)
|
||||||
|
analyzer.emit_info() # 這邊是測試 node 的運行
|
||||||
|
time.sleep(0.5)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
break
|
||||||
|
|
||||||
|
analyzer.destroy_node()
|
||||||
|
rclpy.shutdown()
|
||||||
|
|
||||||
|
# 結束程式 退出所有 thread
|
||||||
|
mavlink_object3.stop()
|
||||||
|
mavlink_object3.thread.join()
|
||||||
|
analyzer.stop()
|
||||||
|
analyzer.thread.join()
|
||||||
|
|
||||||
|
mavlink_socket3.close()
|
||||||
|
print('<=== End of Program')
|
||||||
@ -0,0 +1,91 @@
|
|||||||
|
|
||||||
|
# 自定義的 import
|
||||||
|
from theLogger import setup_logger
|
||||||
|
|
||||||
|
# ====================== 分割線 =====================
|
||||||
|
|
||||||
|
logger = setup_logger("mavlinkDevice.py")
|
||||||
|
|
||||||
|
# 用來記錄每個 system 的資訊
|
||||||
|
# 資料格式 { sysid : mavlink_device object }
|
||||||
|
mavlink_systems = {}
|
||||||
|
|
||||||
|
# ====================== 分割線 =====================
|
||||||
|
|
||||||
|
# 這個 class 是用來記錄每個 system 的資訊 每個 system 可能會有多個 component
|
||||||
|
class mavlink_device():
|
||||||
|
def __init__(self):
|
||||||
|
self.socket_id = None # 記錄來自於哪個 socket
|
||||||
|
self.sysid = None
|
||||||
|
self.components = {} # 用來記錄每個 component 的資訊 key 是 compid, value 是 component object
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
p_str = '=====================\n'
|
||||||
|
p_str += f'object id : {id(self)}\n' # debug
|
||||||
|
p_str += f'socket_id : {self.socket_id}\n'
|
||||||
|
p_str += f'sysid : {self.sysid}\n'
|
||||||
|
p_str += 'has components : \n'
|
||||||
|
for compid in self.components:
|
||||||
|
p_str += f'compid : {compid}\n'
|
||||||
|
p_str += f'mav_type : {self.components[compid].mav_type}\n'
|
||||||
|
# p_str += '=====================\n'
|
||||||
|
return p_str
|
||||||
|
|
||||||
|
def updateComponentPacketCount(self, compid, current_seq, current_type, current_time):
|
||||||
|
# 這段檢查遺失封包
|
||||||
|
try:
|
||||||
|
last_seq = self.components[compid].msg_seq
|
||||||
|
except KeyError:
|
||||||
|
# 這個 component id 還不存在
|
||||||
|
# logger.error('System ID : {} This Component ID : {} Did not init yet'.format(self.sysid, compid)) # 因為初始化的之前 會有大量非 heartbeat 的訊息進來 這是正常現象 TODO 之後要幫這個類別加上初始化狀態 再進行這個判斷
|
||||||
|
return
|
||||||
|
|
||||||
|
if last_seq != None:
|
||||||
|
expected_seq = (last_seq + 1) % 256
|
||||||
|
diff = current_seq - expected_seq
|
||||||
|
# print("current last exp diff : ",current_seq, last_seq, expected_seq, diff) # debug
|
||||||
|
if diff < 0:
|
||||||
|
diff += 256
|
||||||
|
self.components[compid].lost_packet_count += diff
|
||||||
|
|
||||||
|
# 這段更新封包的基本資訊
|
||||||
|
self.components[compid].msg_seq = current_seq
|
||||||
|
self.components[compid].last_msg_time = current_time
|
||||||
|
if current_type in self.components[compid].msg_count:
|
||||||
|
self.components[compid].msg_count[current_type] += 1
|
||||||
|
else:
|
||||||
|
self.components[compid].msg_count[current_type] = 1
|
||||||
|
|
||||||
|
def resetComponentPacketCount(self, compid):
|
||||||
|
self.components[compid].msg_count = {}
|
||||||
|
self.components[compid].msg_seq = None
|
||||||
|
self.components[compid].lost_packet_count = 0
|
||||||
|
|
||||||
|
class mavlink_component():
|
||||||
|
# 程式用不到的參數 但是做個記錄
|
||||||
|
# paraEmitList = ['base_mode', 'flightMode_mode',
|
||||||
|
# 'battery_voltage', # from BATTERY_STATUS (147)
|
||||||
|
# 'gps_fix_type', # from GPS_RAW_INT (24)
|
||||||
|
# 'roll', 'pitch', 'yaw', # from ATTITUDE (30)
|
||||||
|
# 'position_latitude', 'position_longitude', 'position_altitude', # from GLOBAL_POSITION_INT (33)
|
||||||
|
# 'heading', # for VFR_HUD (74)
|
||||||
|
# ]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.mav_type = None # 表示 Vehicle 或 component type (例如: 旋翼機是2, 雲台是26, GCS是6)
|
||||||
|
self.mav_autopilot = None # 表示 autopilot type (例如: ardupilot是3, px4是12)
|
||||||
|
self.mav_system_status = None # 表示 system status (例如: active是3, standby是4)
|
||||||
|
|
||||||
|
# 紀錄從這個 component 收到最後的訊息序號和時間
|
||||||
|
self.msg_count = {}
|
||||||
|
self.msg_seq = None
|
||||||
|
self.lost_packet_count = 0
|
||||||
|
self.last_msg_time = 0
|
||||||
|
|
||||||
|
# 存放該 emit component 參數的區域
|
||||||
|
# 內容格式為 {param_name(字串) : param_value}
|
||||||
|
# param_name 請見上面 paraEmitList
|
||||||
|
self.emitParams = {}
|
||||||
|
# 用來存放每個 topic 的 publisher
|
||||||
|
# 內容格式 為 {topic_name(字串) : [publisher(物件), method(函式)]} (?
|
||||||
|
self.publishers = {}
|
||||||
@ -0,0 +1,414 @@
|
|||||||
|
'''
|
||||||
|
# 不同的匯流排只有單一種通訊協定
|
||||||
|
# 匯流排接到訊息後透過 queue stack 傳送到橋接器
|
||||||
|
# 會有三種 Queue 分別作為 1. 固定串流橋接器 2. 回傳封包處理器 3. 轉拋串流
|
||||||
|
# 第三種比較麻煩 會需要用 list 管理多個轉換器的 queue
|
||||||
|
# 橋接器會將解析得到的結論 再透過 ros2 的 publisher 發送出去
|
||||||
|
|
||||||
|
# 關於 mavlink 採用 pymavlink 這個套件 製作匯流排
|
||||||
|
# pymavlink 的 socket 會由其他地方製作(例如 main) 再放到 mavlink_object 裡面
|
||||||
|
# 這邊的 main 會是用來初始化 mavlink_object 並啟動他的 run function
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
# 基礎功能的 import
|
||||||
|
import threading
|
||||||
|
import queue
|
||||||
|
import time
|
||||||
|
|
||||||
|
# mavlink 的 import
|
||||||
|
from pymavlink import mavutil
|
||||||
|
|
||||||
|
# ROS2 的 import
|
||||||
|
from rclpy.node import Node
|
||||||
|
|
||||||
|
# 自定義的 import
|
||||||
|
from mavlinkDevice import mavlink_device
|
||||||
|
from mavlinkPublish import mavlink_publisher
|
||||||
|
from theLogger import setup_logger
|
||||||
|
|
||||||
|
|
||||||
|
# ====================== 分割線 =====================
|
||||||
|
|
||||||
|
logger = setup_logger("mavlinkObject.py")
|
||||||
|
|
||||||
|
# 基礎功能的 import
|
||||||
|
import threading
|
||||||
|
import queue
|
||||||
|
import time
|
||||||
|
|
||||||
|
# mavlink 的 import
|
||||||
|
from pymavlink import mavutil
|
||||||
|
|
||||||
|
# ROS2 的 import
|
||||||
|
from rclpy.node import Node
|
||||||
|
|
||||||
|
# 自定義的 import
|
||||||
|
from mavlinkDevice import mavlink_device, mavlink_systems
|
||||||
|
from mavlinkPublish import mavlink_publisher
|
||||||
|
from theLogger import setup_logger
|
||||||
|
|
||||||
|
# ====================== 分割線 =====================
|
||||||
|
|
||||||
|
logger = setup_logger("mavlinkObject.py")
|
||||||
|
|
||||||
|
fixed_stream_bridge_queue = queue.Queue()
|
||||||
|
return_packet_processor_queue = queue.Queue()
|
||||||
|
swap_queues = [] # 這個 list 用來存放轉拋串流的 queue # 這些 queue 同時也是各自 mavlink_object 的 output buffer
|
||||||
|
|
||||||
|
# ====================== 分割線 =====================
|
||||||
|
|
||||||
|
class mavlink_bridge(Node, mavlink_publisher):
|
||||||
|
'''
|
||||||
|
這個 class 就是 固定串流橋接器
|
||||||
|
是用來接收 mavlink 訊息 並進行橋接
|
||||||
|
這個地方是針對 fixed_stream_bridge_queue 的資料做處理的
|
||||||
|
記錄有 mavlink bus 上有那些 system id 和 component id
|
||||||
|
為了每個 system id 都有一個對應的 device object
|
||||||
|
並且看是否有重複 system id
|
||||||
|
|
||||||
|
整段代碼包含兩大區塊 thread 和 node
|
||||||
|
|
||||||
|
thread 區塊內會對 fixed_stream_bridge_queue 進行監聽 並且將收到的訊息進行處理
|
||||||
|
其中 HEARTBEAT 是一個特殊類別 用來初始化整個 device object
|
||||||
|
|
||||||
|
node 區塊則是處理 ros2 的 publisher 和 subscriber 訂閱相關
|
||||||
|
藉由控制 ros2 的機制再把 device object 的資訊發送出去
|
||||||
|
|
||||||
|
ps. 我限制了這個 class 只能有一個 instance
|
||||||
|
'''
|
||||||
|
_instance = None
|
||||||
|
_lock = threading.Lock() # 確保多線程安全
|
||||||
|
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
with cls._lock: # 確保多線程環境下只有一個實例被創建
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = super(mavlink_bridge, cls).__new__(cls)
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
if not hasattr(self, "initialized"): # 防止重複初始化
|
||||||
|
self.initialized = True
|
||||||
|
|
||||||
|
# 關聯到全域變數
|
||||||
|
global mavlink_systems
|
||||||
|
self.mavlink_systems = mavlink_systems
|
||||||
|
|
||||||
|
# 當 object 建立時會直接運行 thread 直到消滅
|
||||||
|
self.running = True
|
||||||
|
self.thread = threading.Thread(target=self._run_thread)
|
||||||
|
self.thread.start()
|
||||||
|
else:
|
||||||
|
logger.error('mavlink_bridge instance already exists. Do not create another one.')
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
# === Thread 區塊 ===
|
||||||
|
def _run_thread(self):
|
||||||
|
# start_time = time.time() # debug
|
||||||
|
logger.info('Start of mavlink_bridge._run_thread')
|
||||||
|
# 從 Queue fixed_stream_bridge_queue 中取出 mavlink 資料 並呼叫相對應的 function 進行後處理
|
||||||
|
while self.running:
|
||||||
|
if fixed_stream_bridge_queue.empty():
|
||||||
|
continue
|
||||||
|
msg_pack = fixed_stream_bridge_queue.get()
|
||||||
|
|
||||||
|
msg = msg_pack[2]
|
||||||
|
sysid = msg.get_srcSystem()
|
||||||
|
compid = msg.get_srcComponent()
|
||||||
|
msg_id = msg.get_msgId()
|
||||||
|
|
||||||
|
# 若這個 system id 還不存在 則建立 device object
|
||||||
|
if not sysid in self.mavlink_systems:
|
||||||
|
this_device = mavlink_device() # 創建一個新的 device object
|
||||||
|
self.mavlink_systems[sysid] = this_device
|
||||||
|
this_device.socket_id = msg_pack[0]
|
||||||
|
this_device.sysid = sysid
|
||||||
|
else:
|
||||||
|
this_device = self.mavlink_systems[sysid]
|
||||||
|
|
||||||
|
# 若該 component id 存在 則直接使用該 component object
|
||||||
|
# 若該 component id 不存在 則利用 heartbeat 創建一個新的 component object
|
||||||
|
# 若該 component id 不存在 又不是 heartbeat 則不處理
|
||||||
|
if compid in self.mavlink_systems[sysid].components:
|
||||||
|
this_component = self.mavlink_systems[sysid].components[compid]
|
||||||
|
elif msg_id == 0:
|
||||||
|
# 只有透過 heartbeat 可以創建一個新的 component object
|
||||||
|
this_component = this_device.mavlink_component()
|
||||||
|
this_device.components[msg.get_srcComponent()] = this_component
|
||||||
|
this_component.mav_type = msg.type
|
||||||
|
this_component.mav_autopilot = msg.autopilot
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# ↓↓↓↓↓↓↓↓↓↓↓↓ 處理不同訊息類型的功能寫在這裡 請加在這個 elif 之內 ↓↓↓↓↓↓↓↓↓↓↓↓
|
||||||
|
|
||||||
|
if msg_id == 0: # HEARTBEAT 處理
|
||||||
|
this_component.emitParams['base_mode'] = msg.base_mode
|
||||||
|
this_component.emitParams['flightMode_mode'] = mavutil.mode_string_v10(msg)
|
||||||
|
this_component.emitParams['flightMode'] = msg # debug
|
||||||
|
|
||||||
|
# print("mav_type : ", msg.type) # debug
|
||||||
|
# print("get mode :", mavutil.mode_string_v10(msg)) # debug
|
||||||
|
# print("record mode :", this_component.emitParams['flightMode_mode']) # debug
|
||||||
|
|
||||||
|
elif msg_id == 30: # ATTITUDE 處理
|
||||||
|
this_component.emitParams['attitude'] = msg
|
||||||
|
|
||||||
|
elif msg_id == 32: # LOCAL_POSITION_NED 處理
|
||||||
|
this_component.emitParams['local_position'] = msg
|
||||||
|
|
||||||
|
elif msg_id == 33: # GLOBAL_POSITION_INT 處理
|
||||||
|
this_component.emitParams['global_position'] = msg
|
||||||
|
|
||||||
|
elif msg_id == 74: # VFR_HUD 處理
|
||||||
|
this_component.emitParams['vfr_hud'] = msg
|
||||||
|
|
||||||
|
elif msg_id == 147: # BATTERY_STATUS 處理
|
||||||
|
this_component.emitParams['battery'] = msg
|
||||||
|
|
||||||
|
# ↑↑↑↑↑↑↑↑↑↑↑↑ 處理不同訊息類型的功能寫在這裡 請加在這個 elif 之內 ↑↑↑↑↑↑↑↑↑↑↑↑
|
||||||
|
|
||||||
|
# 若未定義的訊息類型則不處理 並跳出訊息
|
||||||
|
else:
|
||||||
|
logger.warning('This Message Type Did not define process method : {} / {}'.format(msg.get_msgId(), msg.get_type()))
|
||||||
|
continue
|
||||||
|
|
||||||
|
logger.info('End of mavlink_bridge._run_thread')
|
||||||
|
|
||||||
|
# === Node 區塊 ===
|
||||||
|
def _init_node(self):
|
||||||
|
logger.info('Start of mavlink_bridge._init_node')
|
||||||
|
super().__init__('mavlink_bridge') # TODO 不知道為何 這句耗時超長 可以到 5~10 秒
|
||||||
|
|
||||||
|
def emit_info(self):
|
||||||
|
# 這邊將 mavlink_systems 內所有的 device object 內所有的 component 輪循過
|
||||||
|
# 把 emitParams 的參數發送出去
|
||||||
|
for sysid, device in self.mavlink_systems.items():
|
||||||
|
for compid, component in device.components.items():
|
||||||
|
for topic_name in component.publishers.keys():
|
||||||
|
publisher = component.publishers[topic_name][0]
|
||||||
|
packEmit_func = component.publishers[topic_name][1]
|
||||||
|
packEmit_func(component.emitParams, publisher)
|
||||||
|
|
||||||
|
def _del_node(self):
|
||||||
|
# TODO 這邊要刪除 node 的時候要做的事情
|
||||||
|
# 先註銷所有 mavlink_systems 中 component 的 publisher
|
||||||
|
# 再註銷所有 mavlink_systems 中的 device object
|
||||||
|
# 再註銷 node
|
||||||
|
pass
|
||||||
|
|
||||||
|
# ====================== 分割線 =====================
|
||||||
|
|
||||||
|
class mavlink_object():
|
||||||
|
'''
|
||||||
|
每個 mavlink bus 都會有一個 mavlink_object
|
||||||
|
其中主要是 thread 做統計封包與分流
|
||||||
|
分流表的控制在三個 list 分別為
|
||||||
|
multiplexingToAnalysis : 這個 list 是用來分流到固定串流橋接器的
|
||||||
|
multiplexingToReturn : 這個 list 是用來分流到回傳封包處理器的
|
||||||
|
multiplexingToSwap : 這個 list 是用來分流到轉拋串流的
|
||||||
|
透過先更新上述三個 list 後再執行 updateMultiplexingList 可以變更分流行為
|
||||||
|
'''
|
||||||
|
mavlinkObjects = {} # 用來記錄所有的 mavlink_object instance 資料格式 { socket_id(序號) : mavlink_object(物件實例) }
|
||||||
|
socket_num = 0 # 用來記錄目前的 socket 數量
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
# 每創建一個實例 就將其添加到 mavlinkObjects 列表中
|
||||||
|
# 創建時 會檢查 mavlinkObjects 列表中空缺的 socket_id 序號
|
||||||
|
# 若序號無中斷 則 socket_id 往後加一 若序號有中斷 則填補最小的序號
|
||||||
|
# socket_id 從 0 開始
|
||||||
|
|
||||||
|
instance = super().__new__(cls)
|
||||||
|
socket_id = 0
|
||||||
|
for k in cls.mavlinkObjects.keys():
|
||||||
|
if k == socket_id:
|
||||||
|
socket_id += 1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
instance.socket_id = socket_id
|
||||||
|
cls.socket_num += 1
|
||||||
|
cls.mavlinkObjects[socket_id] = instance
|
||||||
|
return instance
|
||||||
|
|
||||||
|
def __init__(self, socket):
|
||||||
|
self.mavlink_socket = socket
|
||||||
|
# 這邊變數是執行的時候被使用的 不要直接寫入它
|
||||||
|
self._multiplexingList = []
|
||||||
|
# 存放要發送的訊息的 queue 或稱 buffer
|
||||||
|
self.output_buffer = queue.Queue()
|
||||||
|
if self.socket_id >= len(swap_queues):
|
||||||
|
swap_queues.append(self.output_buffer)
|
||||||
|
else:
|
||||||
|
swap_queues[self.socket_id] = self.output_buffer
|
||||||
|
|
||||||
|
# 關聯到全域變數
|
||||||
|
global mavlink_systems
|
||||||
|
self.mavlink_systems = mavlink_systems
|
||||||
|
|
||||||
|
# 這三個 list 用來分配不同的訊息到不同的 queue
|
||||||
|
self.multiplexingToAnalysis = [
|
||||||
|
0, # HEARTBEAT # 挺必要的項目
|
||||||
|
# 24, # GPS_RAW_INT
|
||||||
|
# 30, # ATTITUDE
|
||||||
|
# 33, # GLOBAL_POSITION_INT
|
||||||
|
# 74, # VFR_HUD
|
||||||
|
# 147, # BATTERY_STATUS
|
||||||
|
]
|
||||||
|
self.multiplexingToReturn = []
|
||||||
|
self.multiplexingToSwap = [
|
||||||
|
[] for _ in range(len(swap_queues))
|
||||||
|
]
|
||||||
|
|
||||||
|
# 刷新其他 mavlink_object 的 multiplexingToSwap
|
||||||
|
for k, object in self.mavlinkObjects.items():
|
||||||
|
if (k != self.socket_id) and (len(object.multiplexingToSwap) <= self.socket_id):
|
||||||
|
object.multiplexingToSwap.append([])
|
||||||
|
object.updateMultiplexingList()
|
||||||
|
|
||||||
|
logger.info('mavlink_object instance {} created'.format(self.socket_id))
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
# 停下自己的 thread
|
||||||
|
if self.mavlink_socket != None:
|
||||||
|
self.mavlink_socket.close()
|
||||||
|
self.stop()
|
||||||
|
|
||||||
|
# 移除其他 mavlink_object 的 multiplexingToSwap
|
||||||
|
for k, object in self.mavlinkObjects.items():
|
||||||
|
if (k != self.socket_id) and (len(object.multiplexingToSwap) > self.socket_id):
|
||||||
|
object.multiplexingToSwap[self.socket_id] = []
|
||||||
|
object.updateMultiplexingList()
|
||||||
|
|
||||||
|
# 移除自己的 swap_queues
|
||||||
|
swap_queues[self.socket_id] = None
|
||||||
|
|
||||||
|
# 處理 class 的 instance 記錄
|
||||||
|
self.socket_num -= 1
|
||||||
|
self.mavlinkObjects.pop(self.socket_id) # 刪除這個 instance
|
||||||
|
|
||||||
|
# logger 模組在這邊會一直掛掉 找不到問題 先用 print 代替
|
||||||
|
# print('mavlink_object instance {} deleted'.format(self.socket_id)) # debug
|
||||||
|
# logger.info('mavlink_object instance {} deleted'.format(self.socket_id))
|
||||||
|
# try:
|
||||||
|
# logger.info('mavlink_object instance {} deleted'.format(self.socket_id))
|
||||||
|
# except Exception as e:
|
||||||
|
# print(f"Error logging in __del__: {e}")
|
||||||
|
# if 'logger' in globals() and logger:
|
||||||
|
# logger.info('mavlink_object instance {} deleted'.format(self.socket_id))
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
# TODO 檢查 socket 是否有連線
|
||||||
|
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):
|
||||||
|
logger.info('Start of mavlink_object._run_thread id : {}'.format(self.socket_id))
|
||||||
|
logger.info('Start of mavlink_object._run_thread id : {}'.format(self.socket_id))
|
||||||
|
start_time = time.time()
|
||||||
|
while self.running:
|
||||||
|
timestamp = time.time() # 記錄接受到訊息的時間 # 這邊若是在大量訊息造成壅塞時 可能會有些微偏差
|
||||||
|
|
||||||
|
# 處理接收到的封包
|
||||||
|
try:
|
||||||
|
mavlinkMsg = self.mavlink_socket.recv_msg()
|
||||||
|
except Exception as e:
|
||||||
|
logger.critical(f"Receiving data not mavlink format. Object Delete.")
|
||||||
|
logger.critical(f"Receiving data not mavlink format. Object Delete.")
|
||||||
|
print(f"[mavlinkObject.py] Error receiving mavlink data: {e}")
|
||||||
|
print(mavlinkMsg)
|
||||||
|
self.running = False
|
||||||
|
break
|
||||||
|
|
||||||
|
if mavlinkMsg: # data type should be 'pymavlink.dialects.v20.ardupilotmega. etc...'
|
||||||
|
# 統計收到的訊息 & 透過 mavlink 封包的序列號來檢查是否有遺失的封包 & 記錄最後收到的封包時間
|
||||||
|
sysid = mavlinkMsg.get_srcSystem()
|
||||||
|
compid = mavlinkMsg.get_srcComponent()
|
||||||
|
if sysid in self.mavlink_systems: # 只有當這個 system id 已經透過 HEARTBEAT 訊號被初始化過 才會記錄相關訊息
|
||||||
|
# mavlinkMsg.get_type() # mavlinkMsg.get_msgId() # 前者是字串 後者是 int 都是代表 mavlink 類型
|
||||||
|
mavlink_systems[sysid].updateComponentPacketCount(compid, mavlinkMsg.get_seq(), mavlinkMsg.get_msgId(), timestamp)
|
||||||
|
|
||||||
|
# break # Debug function can put here you can see the input data from mavlink
|
||||||
|
|
||||||
|
# 將訊息依照 multiplexing list 分發到不同的 queue
|
||||||
|
for i in range(len(self._multiplexingList)):
|
||||||
|
if (self._multiplexingList[i] == []):
|
||||||
|
continue
|
||||||
|
elif (mavlinkMsg.get_msgId() in self._multiplexingList[i]) or (self._multiplexingList[i][0] == -1):
|
||||||
|
if i == 0:
|
||||||
|
fixed_stream_bridge_queue.put((self.socket_id,timestamp,mavlinkMsg))
|
||||||
|
elif i == 1:
|
||||||
|
return_packet_processor_queue.put((self.socket_id,timestamp,mavlinkMsg))
|
||||||
|
else:
|
||||||
|
_queue = swap_queues[i-2]
|
||||||
|
# _queue.put((self.socket_id,timestamp,mavlinkMsg)) # 測試看看 也許不需要別的資訊 只需要封包
|
||||||
|
_queue.put(mavlinkMsg)
|
||||||
|
|
||||||
|
# 處理要送出的封包
|
||||||
|
# 如果 有資料在 output_buffer 中則將其取出並發送
|
||||||
|
# 沒有就略過發送
|
||||||
|
try:
|
||||||
|
mavlinkMsg_send = self.output_buffer.get(block=False)
|
||||||
|
except queue.Empty:
|
||||||
|
mavlinkMsg_send = None
|
||||||
|
# except Exception as e:
|
||||||
|
# logger.error(f"Error getting data from output_buffer: {e}")
|
||||||
|
# mavlinkMsg_send = None
|
||||||
|
|
||||||
|
if mavlinkMsg_send:
|
||||||
|
# self.mavlink_socket.mav.send(mavlinkMsg_send)
|
||||||
|
self.mavlink_socket.write(mavlinkMsg_send.get_msgbuf()) # 這邊會將封包寫入 socket 中
|
||||||
|
|
||||||
|
# thread 結束
|
||||||
|
logger.info('End of mavlink_object._run_thread id : {}'.format(self.socket_id))
|
||||||
|
|
||||||
|
def updateMultiplexingList(self):
|
||||||
|
'''
|
||||||
|
更新 multiplexing list 並做簡單的檢查
|
||||||
|
'''
|
||||||
|
# 檢查 multiplexingToAnalysis 與 multiplexingToReturn 是否有 -1 值
|
||||||
|
check = (-1 in self.multiplexingToAnalysis) or (-1 in self.multiplexingToReturn)
|
||||||
|
if check:
|
||||||
|
logger.error('MultiplexingToAnalysis or MultiplexingToReturn NOT all type spilt. socket id : {}'.format(self.socket_id))
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 檢查 multiplexingToSwap 與 swap_queues 的長度是否一致 而且 swap_queues 的長度不能為 0
|
||||||
|
check = len(self.multiplexingToSwap) != len(swap_queues) or len(swap_queues) == 0
|
||||||
|
if check:
|
||||||
|
logger.error('Multiplexing Queue not fit List , Please check the list. socket id : {}'.format(self.socket_id))
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 對應到自己的 multiplexingToSwap 必需為空 避免對自己迴圈轉拋
|
||||||
|
try:
|
||||||
|
self.multiplexingToSwap[self.socket_id] = []
|
||||||
|
except IndexError:
|
||||||
|
logger.error('Multiplexing List of self socket id should be void. socket id : {}'.format(self.socket_id))
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 組合 multiplexing list
|
||||||
|
multiL_tmp = [self.multiplexingToAnalysis, self.multiplexingToReturn] + self.multiplexingToSwap
|
||||||
|
|
||||||
|
# 檢查 multiplexing list 格式是否有錯誤 # 全部都要是 list 每個 list 裡面都要是 int
|
||||||
|
check = all(isinstance(i, list) and all(isinstance(j, int) for j in i) for i in multiL_tmp)
|
||||||
|
if not check:
|
||||||
|
logger.error('Multiplexing List Format Error, Please check the list. socket id : {}'.format(self.socket_id))
|
||||||
|
return False # 若有錯誤則回傳 False
|
||||||
|
|
||||||
|
# 更新 multiplexing list
|
||||||
|
self._multiplexingList = multiL_tmp
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
# ====================== 分割線 =====================
|
||||||
|
|
||||||
|
# 整合到 ros2 之後的程式進入點
|
||||||
|
def main_node():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
pass
|
||||||
@ -0,0 +1,208 @@
|
|||||||
|
|
||||||
|
'''
|
||||||
|
這個檔案只有一個 class
|
||||||
|
是作為 mavlinkObject.py 中 mavlink_analyzer 類別的功能衍生
|
||||||
|
|
||||||
|
主要概念是將 "離散的" mavlink 參數轉換成 ROS topic
|
||||||
|
包含了創建 publisher 和 以及包裝並丟到 ros2 topic 的 packEmit
|
||||||
|
|
||||||
|
publisher topic name 命名規則為 <前綴詞>/s<sysid>/<具體 topic>
|
||||||
|
'''
|
||||||
|
|
||||||
|
import std_msgs.msg
|
||||||
|
import sensor_msgs.msg
|
||||||
|
import geometry_msgs.msg
|
||||||
|
import mavros_msgs.msg
|
||||||
|
|
||||||
|
from theLogger import setup_logger
|
||||||
|
import math
|
||||||
|
|
||||||
|
|
||||||
|
logger = setup_logger("mavlinkPublish.py")
|
||||||
|
|
||||||
|
class mavlink_publisher():
|
||||||
|
|
||||||
|
prefix_path = 'MavLinkBus'
|
||||||
|
|
||||||
|
def create_flightMode(self, sysid, component_obj):
|
||||||
|
# target topic name # 請跟這個 method 的名稱保持一致
|
||||||
|
target_topic = 'flightMode'
|
||||||
|
|
||||||
|
# 這邊要檢查 flight_mode 是否存在
|
||||||
|
try:
|
||||||
|
_ = component_obj.emitParams['flightMode_mode']
|
||||||
|
except KeyError:
|
||||||
|
# 這個 component id 還不存在
|
||||||
|
logger.warning('System ID : {} This Component ID : {} Did not init yet'.format(component_obj['sysid'], component_obj['compid']))
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 若存在則 建立 publisher object 並回傳 true
|
||||||
|
topic_name = '{}/s{}/{}'.format(self.prefix_path, sysid, target_topic)
|
||||||
|
publisher_ = self.create_publisher(std_msgs.msg.String, topic_name, 1)
|
||||||
|
component_obj.publishers[target_topic] = [publisher_, self.packEmit_flightMode]
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def packEmit_flightMode(cls, emitParams, publisher):
|
||||||
|
msg_str = emitParams['flightMode_mode']
|
||||||
|
msg = std_msgs.msg.String()
|
||||||
|
msg.data = msg_str
|
||||||
|
publisher.publish(msg)
|
||||||
|
pass
|
||||||
|
|
||||||
|
# ↓↓↓↓↓↓↓↓↓↓↓↓ 處理不同 ros2 topic 訊息 請放在這裡 ↓↓↓↓↓↓↓↓↓↓↓↓
|
||||||
|
def euler_to_quaternion(cls,roll, pitch, yaw):
|
||||||
|
qx = math.sin(roll/2) * math.cos(pitch/2) * math.cos(yaw/2) - math.cos(roll/2) * math.sin(pitch/2) * math.sin(yaw/2)
|
||||||
|
qy = math.cos(roll/2) * math.sin(pitch/2) * math.cos(yaw/2) + math.sin(roll/2) * math.cos(pitch/2) * math.sin(yaw/2)
|
||||||
|
qz = math.cos(roll/2) * math.cos(pitch/2) * math.sin(yaw/2) - math.sin(roll/2) * math.sin(pitch/2) * math.cos(yaw/2)
|
||||||
|
qw = math.cos(roll/2) * math.cos(pitch/2) * math.cos(yaw/2) + math.sin(roll/2) * math.sin(pitch/2) * math.sin(yaw/2)
|
||||||
|
return [qx, qy, qz, qw]
|
||||||
|
|
||||||
|
def create_attitude(self, sysid, component_obj):
|
||||||
|
target_topic = 'attitude'
|
||||||
|
|
||||||
|
try:
|
||||||
|
_ = component_obj.emitParams['attitude']
|
||||||
|
except KeyError:
|
||||||
|
logger.warning('System ID : {} This Component ID : {} Did not init yet'.format(component_obj['sysid'], component_obj['compid']))
|
||||||
|
return False
|
||||||
|
|
||||||
|
topic_name = '{}/s{}/{}'.format(self.prefix_path, sysid, target_topic)
|
||||||
|
publisher_ = self.create_publisher(sensor_msgs.msg.Imu, topic_name, 1)
|
||||||
|
component_obj.publishers[target_topic] = [publisher_, self.packEmit_attitude]
|
||||||
|
|
||||||
|
def packEmit_attitude(self, emitParams, publisher):
|
||||||
|
mav_msg = emitParams['attitude']
|
||||||
|
msg = sensor_msgs.msg.Imu()
|
||||||
|
x, y, z, w = self.euler_to_quaternion(mav_msg.roll, mav_msg.pitch, mav_msg.yaw)
|
||||||
|
msg.orientation.x = x
|
||||||
|
msg.orientation.y = y
|
||||||
|
msg.orientation.z = z
|
||||||
|
msg.orientation.w = w
|
||||||
|
msg.angular_velocity.x = mav_msg.rollspeed
|
||||||
|
msg.angular_velocity.y = mav_msg.pitchspeed
|
||||||
|
msg.angular_velocity.z = mav_msg.yawspeed
|
||||||
|
publisher.publish(msg)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create_local_position_pose(self, sysid, component_obj):
|
||||||
|
target_topic = 'local_position/pose'
|
||||||
|
try:
|
||||||
|
_ = component_obj.emitParams['local_position']
|
||||||
|
except KeyError:
|
||||||
|
logger.warning('System ID : {} This Component ID : {} Did not init yet'.format(component_obj['sysid'], component_obj['compid']))
|
||||||
|
return False
|
||||||
|
topic_name = '{}/s{}/{}'.format(self.prefix_path, sysid, target_topic)
|
||||||
|
publisher_ = self.create_publisher(geometry_msgs.msg.Point, topic_name, 1)
|
||||||
|
component_obj.publishers[target_topic] = [publisher_, self.packEmit_local_pose]
|
||||||
|
|
||||||
|
def packEmit_local_pose(cls, emitParams, publisher):
|
||||||
|
mav_msg = emitParams['local_position']
|
||||||
|
msg = geometry_msgs.msg.Point()
|
||||||
|
msg.x = mav_msg.x
|
||||||
|
msg.y = mav_msg.y
|
||||||
|
msg.z = mav_msg.z
|
||||||
|
publisher.publish(msg)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create_local_position_velocity(self, sysid, component_obj):
|
||||||
|
target_topic = 'local_position/velocity'
|
||||||
|
try:
|
||||||
|
_ = component_obj.emitParams['local_position']
|
||||||
|
except KeyError:
|
||||||
|
logger.warning('System ID : {} This Component ID : {} Did not init yet'.format(component_obj['sysid'], component_obj['compid']))
|
||||||
|
return False
|
||||||
|
topic_name = '{}/s{}/{}'.format(self.prefix_path, sysid, target_topic)
|
||||||
|
publisher_ = self.create_publisher(geometry_msgs.msg.Vector3, topic_name, 1)
|
||||||
|
component_obj.publishers[target_topic] = [publisher_, self.packEmit_local_vel]
|
||||||
|
|
||||||
|
def packEmit_local_vel(cls, emitParams, publisher):
|
||||||
|
mav_msg = emitParams['local_position']
|
||||||
|
msg = geometry_msgs.msg.Vector3()
|
||||||
|
msg.x = mav_msg.vx
|
||||||
|
msg.y = mav_msg.vy
|
||||||
|
msg.z = mav_msg.vz
|
||||||
|
publisher.publish(msg)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create_global_global(self, sysid, component_obj):
|
||||||
|
target_topic = 'global_position/global'
|
||||||
|
try:
|
||||||
|
_ = component_obj.emitParams['global_position']
|
||||||
|
except KeyError:
|
||||||
|
logger.warning('System ID : {} This Component ID : {} Did not init yet'.format(component_obj['sysid'], component_obj['compid']))
|
||||||
|
return False
|
||||||
|
topic_name = '{}/s{}/{}'.format(self.prefix_path, sysid, target_topic)
|
||||||
|
publisher_ = self.create_publisher(sensor_msgs.msg.NavSatFix, topic_name, 1)
|
||||||
|
component_obj.publishers[target_topic] = [publisher_, self.packEmit_global_global]
|
||||||
|
|
||||||
|
def packEmit_global_global(cls, emitParams, publisher):
|
||||||
|
mav_msg = emitParams['global_position']
|
||||||
|
msg = sensor_msgs.msg.NavSatFix()
|
||||||
|
msg.latitude = mav_msg.lat/1e7
|
||||||
|
msg.longitude = mav_msg.lon/1e7
|
||||||
|
msg.altitude = mav_msg.alt/1e3
|
||||||
|
publisher.publish(msg)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create_global_rel(self, sysid, component_obj):
|
||||||
|
target_topic = 'global_position/rel_alt'
|
||||||
|
try:
|
||||||
|
_ = component_obj.emitParams['global_position']
|
||||||
|
except KeyError:
|
||||||
|
logger.warning('System ID : {} This Component ID : {} Did not init yet'.format(component_obj['sysid'], component_obj['compid']))
|
||||||
|
return False
|
||||||
|
topic_name = '{}/s{}/{}'.format(self.prefix_path, sysid, target_topic)
|
||||||
|
publisher_ = self.create_publisher(std_msgs.msg.Float64, topic_name, 1)
|
||||||
|
component_obj.publishers[target_topic] = [publisher_, self.packEmit_global_rel]
|
||||||
|
|
||||||
|
def packEmit_global_rel(cls, emitParams, publisher):
|
||||||
|
mav_msg = emitParams['global_position']
|
||||||
|
msg = std_msgs.msg.Float64()
|
||||||
|
msg.data = float(mav_msg.relative_alt/1e3)
|
||||||
|
publisher.publish(msg)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create_vfr_hud(self, sysid, component_obj):
|
||||||
|
target_topic = 'vfr_hud'
|
||||||
|
try:
|
||||||
|
_ = component_obj.emitParams['vfr_hud']
|
||||||
|
except KeyError:
|
||||||
|
logger.warning('System ID : {} This Component ID : {} Did not init yet'.format(component_obj['sysid'], component_obj['compid']))
|
||||||
|
return False
|
||||||
|
topic_name = '{}/s{}/{}'.format(self.prefix_path, sysid, target_topic)
|
||||||
|
publisher_ = self.create_publisher(mavros_msgs.msg.VfrHud, topic_name, 1)
|
||||||
|
component_obj.publishers[target_topic] = [publisher_, self.packEmit_vfr_hud]
|
||||||
|
|
||||||
|
def packEmit_vfr_hud(cls, emitParams, publisher):
|
||||||
|
mav_msg = emitParams['vfr_hud']
|
||||||
|
msg = mavros_msgs.msg.VfrHud()
|
||||||
|
msg.airspeed = mav_msg.airspeed
|
||||||
|
msg.groundspeed = mav_msg.groundspeed
|
||||||
|
msg.heading = mav_msg.heading
|
||||||
|
msg.throttle = float(mav_msg.throttle)
|
||||||
|
msg.altitude = mav_msg.alt
|
||||||
|
msg.climb = mav_msg.climb
|
||||||
|
publisher.publish(msg)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def create_battery(self, sysid, component_obj):
|
||||||
|
target_topic = 'battery'
|
||||||
|
try:
|
||||||
|
_ = component_obj.emitParams['battery']
|
||||||
|
except KeyError:
|
||||||
|
logger.warning('System ID : {} This Component ID : {} Did not init yet'.format(component_obj['sysid'], component_obj['compid']))
|
||||||
|
return False
|
||||||
|
topic_name = '{}/s{}/{}'.format(self.prefix_path, sysid, target_topic)
|
||||||
|
publisher_ = self.create_publisher(sensor_msgs.msg.BatteryState, topic_name, 1)
|
||||||
|
component_obj.publishers[target_topic] = [publisher_, self.packEmit_battery]
|
||||||
|
|
||||||
|
def packEmit_battery(cls, emitParams, publisher):
|
||||||
|
mav_msg = emitParams['battery']
|
||||||
|
msg = sensor_msgs.msg.BatteryState()
|
||||||
|
msg.voltage = mav_msg.voltages[0]/1e3
|
||||||
|
publisher.publish(msg)
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# ↑↑↑↑↑↑↑↑↑↑↑↑ 處理不同 ros2 topic 訊息 請放在這裡 ↑↑↑↑↑↑↑↑↑↑↑↑
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
透過某個地方 得到 udp 或 uart 接口
|
||||||
|
對於每個接口 視為一個獨立的物件
|
||||||
|
|
||||||
|
物件對於不同的接口 是為不同類型的物件
|
||||||
|
|
||||||
|
每個類型的物件 創建一個獨立的執行緒 來處理資料
|
||||||
|
關於執行緒的實作 是寫在另一個模組
|
||||||
|
|
||||||
|
物件之間 也可以做資料轉換/轉拋
|
||||||
|
|
||||||
|
'''
|
||||||
@ -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>
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
[develop]
|
||||||
|
script_dir=$base/lib/fc_network_adapter
|
||||||
|
[install]
|
||||||
|
install_scripts=$base/lib/fc_network_adapter
|
||||||
@ -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': [
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
@ -0,0 +1,444 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import rclpy
|
||||||
|
from rclpy.node import Node
|
||||||
|
from PyQt6.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QHBoxLayout,
|
||||||
|
QWidget, QLabel, QSplitter, QScrollArea,
|
||||||
|
QSizePolicy, QApplication)
|
||||||
|
from PyQt6.QtCore import Qt, QTimer, QObject, pyqtSignal, QUrl
|
||||||
|
from PyQt6.QtGui import QColor
|
||||||
|
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
||||||
|
import math
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from threading import Lock
|
||||||
|
from geometry_msgs.msg import Point, Vector3
|
||||||
|
from sensor_msgs.msg import BatteryState, NavSatFix, Imu
|
||||||
|
from std_msgs.msg import String, Float64
|
||||||
|
from mavros_msgs.msg import VfrHud
|
||||||
|
|
||||||
|
class DroneSignals(QObject):
|
||||||
|
update_signal = pyqtSignal(str, str, object) # (msg_type, drone_id, data)
|
||||||
|
|
||||||
|
class DroneMonitor(Node):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__('drone_monitor')
|
||||||
|
self.signals = DroneSignals()
|
||||||
|
self.drone_topics = {}
|
||||||
|
self.lock = Lock()
|
||||||
|
|
||||||
|
# 主题检测定时器
|
||||||
|
self.create_timer(1.0, self.scan_topics)
|
||||||
|
|
||||||
|
def scan_topics(self):
|
||||||
|
topics = self.get_topic_names_and_types()
|
||||||
|
drone_pattern = re.compile(r'/MavLinkBus/(s\d+)/(\w+)')
|
||||||
|
|
||||||
|
found_drones = set()
|
||||||
|
for topic_name, _ in topics:
|
||||||
|
if match := drone_pattern.match(topic_name):
|
||||||
|
drone_id, topic_type = match.groups()
|
||||||
|
found_drones.add(drone_id)
|
||||||
|
with self.lock:
|
||||||
|
self.drone_topics.setdefault(drone_id, set()).add(topic_type)
|
||||||
|
|
||||||
|
for drone_id in found_drones:
|
||||||
|
if not hasattr(self, f'drone_{drone_id}_subs'):
|
||||||
|
self.setup_drone_subs(drone_id)
|
||||||
|
|
||||||
|
def setup_drone_subs(self, drone_id):
|
||||||
|
base_topic = f'/MavLinkBus/{drone_id}'
|
||||||
|
|
||||||
|
subs = {
|
||||||
|
'attitude': self.create_subscription(
|
||||||
|
Imu,
|
||||||
|
f'{base_topic}/attitude',
|
||||||
|
lambda msg, did=drone_id: self.attitude_callback(did, msg),
|
||||||
|
10
|
||||||
|
),
|
||||||
|
'battery': self.create_subscription(
|
||||||
|
BatteryState,
|
||||||
|
f'{base_topic}/battery',
|
||||||
|
lambda msg, did=drone_id: self.battery_callback(did, msg),
|
||||||
|
10
|
||||||
|
),
|
||||||
|
'flightMode': self.create_subscription(
|
||||||
|
String,
|
||||||
|
f'{base_topic}/flightMode',
|
||||||
|
lambda msg, did=drone_id: self.mode_callback(did, msg),
|
||||||
|
10
|
||||||
|
),
|
||||||
|
'global': self.create_subscription(
|
||||||
|
NavSatFix,
|
||||||
|
f'{base_topic}/global_position/global',
|
||||||
|
lambda msg, did=drone_id: self.gps_callback(did, msg),
|
||||||
|
10
|
||||||
|
),
|
||||||
|
'rel_alt': self.create_subscription(
|
||||||
|
Float64,
|
||||||
|
f'{base_topic}/global_position/rel_alt',
|
||||||
|
lambda msg, did=drone_id: self.altitude_callback(did, msg),
|
||||||
|
10
|
||||||
|
),
|
||||||
|
'local_pose': self.create_subscription(
|
||||||
|
Point,
|
||||||
|
f'{base_topic}/local_position/pose',
|
||||||
|
lambda msg, did=drone_id: self.local_pose_callback(did, msg),
|
||||||
|
10
|
||||||
|
),
|
||||||
|
'local_vel': self.create_subscription(
|
||||||
|
Vector3,
|
||||||
|
f'{base_topic}/local_position/velocity',
|
||||||
|
lambda msg, did=drone_id: self.local_vel_callback(did, msg),
|
||||||
|
10
|
||||||
|
),
|
||||||
|
'vfr_hud': self.create_subscription(
|
||||||
|
VfrHud,
|
||||||
|
f'{base_topic}/vfr_hud',
|
||||||
|
lambda msg, did=drone_id: self.hud_callback(did, msg),
|
||||||
|
10
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setattr(self, f'drone_{drone_id}_subs', subs)
|
||||||
|
self.signals.update_signal.emit('new_drone', drone_id, None)
|
||||||
|
|
||||||
|
def quaternion_to_euler(self, q):
|
||||||
|
sinr_cosp = 2 * (q.w * q.x + q.y * q.z)
|
||||||
|
cosr_cosp = 1 - 2 * (q.x**2 + q.y**2)
|
||||||
|
roll = math.atan2(sinr_cosp, cosr_cosp)
|
||||||
|
|
||||||
|
sinp = 2 * (q.w * q.y - q.z * q.x)
|
||||||
|
pitch = math.asin(sinp) if abs(sinp) < 1 else math.copysign(math.pi/2, sinp)
|
||||||
|
|
||||||
|
siny_cosp = 2 * (q.w * q.z + q.x * q.y)
|
||||||
|
cosy_cosp = 1 - 2 * (q.y**2 + q.z**2)
|
||||||
|
yaw = math.atan2(siny_cosp, cosy_cosp)
|
||||||
|
|
||||||
|
return math.degrees(roll), math.degrees(pitch), math.degrees(yaw)
|
||||||
|
|
||||||
|
# 回调函数
|
||||||
|
def attitude_callback(self, drone_id, msg):
|
||||||
|
if hasattr(msg, 'orientation'):
|
||||||
|
roll, pitch, yaw = self.quaternion_to_euler(msg.orientation)
|
||||||
|
self.signals.update_signal.emit('attitude', drone_id, {
|
||||||
|
'roll': roll,
|
||||||
|
'pitch': pitch,
|
||||||
|
'yaw': yaw,
|
||||||
|
'rates': (msg.angular_velocity.x,
|
||||||
|
msg.angular_velocity.y,
|
||||||
|
msg.angular_velocity.z)
|
||||||
|
})
|
||||||
|
|
||||||
|
def battery_callback(self, drone_id, msg):
|
||||||
|
self.signals.update_signal.emit('battery', drone_id, {
|
||||||
|
'voltage': msg.voltage
|
||||||
|
})
|
||||||
|
|
||||||
|
def mode_callback(self, drone_id, msg):
|
||||||
|
self.signals.update_signal.emit('mode', drone_id, msg.data)
|
||||||
|
|
||||||
|
def gps_callback(self, drone_id, msg):
|
||||||
|
self.signals.update_signal.emit('gps', drone_id, {
|
||||||
|
'lat': msg.latitude,
|
||||||
|
'lon': msg.longitude
|
||||||
|
,'alt': msg.altitude
|
||||||
|
})
|
||||||
|
|
||||||
|
def local_vel_callback(self, drone_id, msg):
|
||||||
|
self.signals.update_signal.emit('velocity', drone_id, {
|
||||||
|
'vx': msg.x,
|
||||||
|
'vy': msg.y,
|
||||||
|
'vz': msg.z
|
||||||
|
})
|
||||||
|
|
||||||
|
def altitude_callback(self, drone_id, msg):
|
||||||
|
self.signals.update_signal.emit('altitude', drone_id, {
|
||||||
|
'altitude': msg.data
|
||||||
|
})
|
||||||
|
|
||||||
|
def local_pose_callback(self, drone_id, msg):
|
||||||
|
self.signals.update_signal.emit('local_pose', drone_id, {
|
||||||
|
'x': msg.x,
|
||||||
|
'y': msg.y,
|
||||||
|
'z': msg.z
|
||||||
|
})
|
||||||
|
|
||||||
|
def hud_callback(self, drone_id, msg):
|
||||||
|
self.signals.update_signal.emit('hud', drone_id, {
|
||||||
|
'airspeed': msg.airspeed,
|
||||||
|
'groundspeed': msg.groundspeed,
|
||||||
|
'heading': msg.heading,
|
||||||
|
'throttle': msg.throttle,
|
||||||
|
'alt': msg.altitude,
|
||||||
|
'climb': msg.climb
|
||||||
|
})
|
||||||
|
|
||||||
|
class ControlStationUI(QMainWindow):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.setWindowTitle('GCS')
|
||||||
|
self.resize(1400, 900)
|
||||||
|
self.drones = {}
|
||||||
|
|
||||||
|
# 初始化ROS2
|
||||||
|
rclpy.init()
|
||||||
|
self.monitor = DroneMonitor()
|
||||||
|
self.monitor.signals.update_signal.connect(self.update_ui)
|
||||||
|
|
||||||
|
# ROS执行器配置
|
||||||
|
self.executor = rclpy.executors.SingleThreadedExecutor()
|
||||||
|
self.executor.add_node(self.monitor)
|
||||||
|
|
||||||
|
# 初始化UI
|
||||||
|
self.drone_positions = {}
|
||||||
|
self.map_loaded = False
|
||||||
|
self.init_ui()
|
||||||
|
|
||||||
|
# 定时处理ROS事件
|
||||||
|
self.timer = QTimer()
|
||||||
|
self.timer.timeout.connect(self.spin_ros)
|
||||||
|
self.timer.start(50)
|
||||||
|
|
||||||
|
def init_ui(self):
|
||||||
|
main_splitter = QSplitter(Qt.Orientation.Horizontal)
|
||||||
|
|
||||||
|
# 左侧信息面板
|
||||||
|
left_panel = QWidget()
|
||||||
|
left_layout = QVBoxLayout(left_panel)
|
||||||
|
left_layout.setContentsMargins(5, 5, 5, 5)
|
||||||
|
|
||||||
|
# 信息滚动区域
|
||||||
|
scroll_area = QScrollArea()
|
||||||
|
scroll_area.setWidgetResizable(True)
|
||||||
|
self.info_container = QWidget()
|
||||||
|
self.info_layout = QVBoxLayout(self.info_container)
|
||||||
|
self.info_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||||
|
scroll_area.setWidget(self.info_container)
|
||||||
|
|
||||||
|
left_layout.addWidget(scroll_area)
|
||||||
|
|
||||||
|
# 右侧地图区域
|
||||||
|
self.map_view = QWebEngineView()
|
||||||
|
|
||||||
|
inline_html = '''
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
|
||||||
|
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
|
||||||
|
<script src="https://unpkg.com/leaflet-rotatedmarker/leaflet.rotatedMarker.js"></script>
|
||||||
|
<style>
|
||||||
|
html, body, #map { height: 100%; margin: 0; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="map"></div>
|
||||||
|
<script>
|
||||||
|
var map = L.map('map').setView([0, 0], 2);
|
||||||
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
|
maxZoom: 19
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
var arrowIcon = L.icon({
|
||||||
|
iconUrl: 'https://cdn-icons-png.flaticon.com/512/399/399308.png', // 朝上的箭頭
|
||||||
|
iconSize: [40, 40],
|
||||||
|
iconAnchor: [20, 20]
|
||||||
|
});
|
||||||
|
|
||||||
|
var markers = {};
|
||||||
|
|
||||||
|
function updateDrone(lat, lon, id, heading) {
|
||||||
|
if (markers[id]) {
|
||||||
|
markers[id].setLatLng([lat, lon]);
|
||||||
|
markers[id].setRotationAngle(heading);
|
||||||
|
} else {
|
||||||
|
markers[id] = L.marker([lat, lon], {
|
||||||
|
icon: arrowIcon,
|
||||||
|
rotationAngle: heading,
|
||||||
|
rotationOrigin: 'center'
|
||||||
|
}).addTo(map).bindPopup(id);
|
||||||
|
}
|
||||||
|
map.setView([lat, lon], 16);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
'''
|
||||||
|
self.map_view.setHtml(inline_html)
|
||||||
|
self.map_view.loadFinished.connect(self.on_map_loaded)
|
||||||
|
|
||||||
|
main_splitter.addWidget(left_panel)
|
||||||
|
main_splitter.addWidget(self.map_view)
|
||||||
|
main_splitter.setSizes([400, 1000])
|
||||||
|
|
||||||
|
self.setCentralWidget(main_splitter)
|
||||||
|
|
||||||
|
def on_map_loaded(self, ok: bool):
|
||||||
|
if ok:
|
||||||
|
self.map_loaded = True
|
||||||
|
else:
|
||||||
|
print("⚠️ 地图页加载失败")
|
||||||
|
|
||||||
|
def create_drone_panel(self, drone_id):
|
||||||
|
panel = QWidget()
|
||||||
|
panel.setObjectName(f"panel_{drone_id}")
|
||||||
|
panel.setStyleSheet("""
|
||||||
|
QWidget#panel_%s {
|
||||||
|
background-color: #2A2A2A;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 6px;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #444;
|
||||||
|
}
|
||||||
|
QLabel {
|
||||||
|
color: #DDD;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
""" % drone_id)
|
||||||
|
|
||||||
|
layout = QVBoxLayout(panel)
|
||||||
|
layout.setContentsMargins(8, 8, 8, 8)
|
||||||
|
layout.setSpacing(4)
|
||||||
|
|
||||||
|
# 顶部标题栏
|
||||||
|
header = QWidget()
|
||||||
|
header_layout = QHBoxLayout(header)
|
||||||
|
header_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
|
# ID显示
|
||||||
|
id_label = QLabel(drone_id)
|
||||||
|
id_label.setStyleSheet("""
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #7FFFD4;
|
||||||
|
min-width: 80px;
|
||||||
|
""")
|
||||||
|
|
||||||
|
# 状态指示灯
|
||||||
|
status_light = QLabel("●")
|
||||||
|
status_light.setStyleSheet("color: #00FF00; font-size: 16px;")
|
||||||
|
|
||||||
|
header_layout.addWidget(id_label)
|
||||||
|
header_layout.addWidget(status_light)
|
||||||
|
header_layout.addStretch()
|
||||||
|
|
||||||
|
layout.addWidget(header)
|
||||||
|
|
||||||
|
# 数据字段(带标签)
|
||||||
|
self.create_data_row(layout, "模式:", f"{drone_id}_mode", "--")
|
||||||
|
self.create_data_row(layout, "電壓:", f"{drone_id}_battery", "--")
|
||||||
|
self.create_data_row(layout, "GPS:", f"{drone_id}_gps", "--")
|
||||||
|
self.create_data_row(layout, "高度:", f"{drone_id}_altitude", "--")
|
||||||
|
self.create_data_row(layout, "Local:", f"{drone_id}_local", "--")
|
||||||
|
self.create_data_row(layout, "HUD:", f"{drone_id}_hud", "--")
|
||||||
|
|
||||||
|
return panel
|
||||||
|
|
||||||
|
def create_data_row(self, layout, title, object_name, default):
|
||||||
|
row = QWidget()
|
||||||
|
hbox = QHBoxLayout(row)
|
||||||
|
hbox.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
|
# 标题标签
|
||||||
|
title_label = QLabel(title)
|
||||||
|
title_label.setStyleSheet("color: #888; min-width: 80px;")
|
||||||
|
title_label.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred)
|
||||||
|
|
||||||
|
# 数据标签
|
||||||
|
value_label = QLabel(default)
|
||||||
|
value_label.setObjectName(object_name)
|
||||||
|
value_label.setWordWrap(True)
|
||||||
|
value_label.setStyleSheet("margin-left: 10px;")
|
||||||
|
|
||||||
|
hbox.addWidget(title_label)
|
||||||
|
hbox.addWidget(value_label)
|
||||||
|
layout.addWidget(row)
|
||||||
|
|
||||||
|
def update_ui(self, msg_type, drone_id, data):
|
||||||
|
if msg_type == 'new_drone':
|
||||||
|
self.add_drone(drone_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not (panel := self.drones.get(drone_id)):
|
||||||
|
return
|
||||||
|
|
||||||
|
if msg_type == 'mode':
|
||||||
|
self.update_field(panel, drone_id, 'mode', f"{data}",
|
||||||
|
'#FF5555' if '返航' in data else '#55FF55')
|
||||||
|
|
||||||
|
elif msg_type == 'battery':
|
||||||
|
voltage = data.get('voltage', 0)
|
||||||
|
self.update_field(panel, drone_id, 'battery',
|
||||||
|
f"{voltage:.1f} V",
|
||||||
|
'#FF6464' if voltage < 12 else '#FFFFFF')
|
||||||
|
|
||||||
|
elif msg_type == 'gps':
|
||||||
|
lat, lon = data['lat'], data['lon']
|
||||||
|
self.drone_positions[drone_id] = (lat, lon)
|
||||||
|
text = (f"緯度: {lat:.6f}°\n"
|
||||||
|
f"經度: {lon:.6f}°")
|
||||||
|
self.update_field(panel, drone_id, 'gps', text)
|
||||||
|
|
||||||
|
elif msg_type == 'altitude':
|
||||||
|
text = (f"{data['altitude']:.1f} m")
|
||||||
|
self.update_field(panel, drone_id, 'altitude', text)
|
||||||
|
|
||||||
|
elif msg_type == 'local_pose':
|
||||||
|
text = (f"({data['x']:.1f}, {data['y']:.1f}, {data['z']:.1f})")
|
||||||
|
self.update_field(panel, drone_id, 'local', text)
|
||||||
|
|
||||||
|
elif msg_type == 'hud':
|
||||||
|
heading = data['heading']
|
||||||
|
text = (f"地速: {data['groundspeed']:.1f} m/s\n"
|
||||||
|
f"航向: {heading:.1f}°")
|
||||||
|
self.update_field(panel, drone_id, 'hud', text)
|
||||||
|
|
||||||
|
if self.map_loaded and drone_id in self.drone_positions:
|
||||||
|
lat, lon = self.drone_positions[drone_id]
|
||||||
|
js = f"updateDrone({lat:.6f}, {lon:.6f}, '{drone_id}', {heading:.1f});"
|
||||||
|
self.map_view.page().runJavaScript(js)
|
||||||
|
'''
|
||||||
|
elif msg_type == 'velocity':
|
||||||
|
text = (f"VX: {data['vx']:.1f} m/s\n"
|
||||||
|
f"VY: {data['vy']:.1f} m/s\n"
|
||||||
|
f"VZ: {data['vz']:.1f} m/s")
|
||||||
|
self.update_field(panel, drone_id, 'velocity', text)
|
||||||
|
|
||||||
|
elif msg_type == 'attitude':
|
||||||
|
text = (f"Roll: {data['roll']:.1f}°\n"
|
||||||
|
f"Pitch: {data['pitch']:.1f}°\n"
|
||||||
|
f"Yaw: {data['yaw']:.1f}°")
|
||||||
|
self.update_field(panel, drone_id, 'attitude', text)
|
||||||
|
'''
|
||||||
|
def update_field(self, panel, drone_id, field, text, color=None):
|
||||||
|
if label := panel.findChild(QLabel, f"{drone_id}_{field}"):
|
||||||
|
label.setText(text)
|
||||||
|
if color:
|
||||||
|
label.setStyleSheet(f"color: {color};")
|
||||||
|
|
||||||
|
def add_drone(self, drone_id):
|
||||||
|
if drone_id not in self.drones:
|
||||||
|
panel = self.create_drone_panel(drone_id)
|
||||||
|
self.info_layout.addWidget(panel)
|
||||||
|
self.drones[drone_id] = panel
|
||||||
|
|
||||||
|
def spin_ros(self):
|
||||||
|
try:
|
||||||
|
self.executor.spin_once(timeout_sec=0)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ROS spin error: {e}")
|
||||||
|
|
||||||
|
def closeEvent(self, event):
|
||||||
|
self.monitor.destroy_node()
|
||||||
|
self.executor.shutdown()
|
||||||
|
rclpy.shutdown()
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
station = ControlStationUI()
|
||||||
|
station.show()
|
||||||
|
app.exec(app.exec())
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
import socket
|
||||||
|
|
||||||
|
SOCKET_PATH = "/tmp/unix_socket_example"
|
||||||
|
|
||||||
|
# 建立 socket
|
||||||
|
client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
client.connect(SOCKET_PATH)
|
||||||
|
|
||||||
|
# 傳送資料
|
||||||
|
client.sendall(b"Hello from client!")
|
||||||
|
data = client.recv(1024)
|
||||||
|
print("📤 Server replied:", data.decode())
|
||||||
|
|
||||||
|
client.close()
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import rclpy
|
||||||
|
from rclpy.node import Node
|
||||||
|
from std_msgs.msg import String
|
||||||
|
import time
|
||||||
|
|
||||||
|
# import mavros_msgs.srv
|
||||||
|
|
||||||
|
class TalkerNode(Node):
|
||||||
|
def __init__(self):
|
||||||
|
start_time = time.time()
|
||||||
|
super().__init__('talker_node')
|
||||||
|
end_time = time.time()
|
||||||
|
print(f"Node initialization took {end_time - start_time:.2f} seconds")
|
||||||
|
|
||||||
|
self.publisher_ = self.create_publisher(String, 'hahatest/_1', 10)
|
||||||
|
self.timer = self.create_timer(1.0, self.timer_callback) # 每秒執行一次
|
||||||
|
self.get_logger().info('TalkerNode has been started.')
|
||||||
|
|
||||||
|
def timer_callback(self):
|
||||||
|
msg = String()
|
||||||
|
msg.data = 'Hello, ROS 2!'
|
||||||
|
self.publisher_.publish(msg)
|
||||||
|
self.get_logger().info(f'Published: "{msg.data}"')
|
||||||
|
|
||||||
|
def main(args=None):
|
||||||
|
rclpy.init(args=args)
|
||||||
|
node = TalkerNode()
|
||||||
|
|
||||||
|
print("Before sleep")
|
||||||
|
time.sleep(5) # 等待 5 秒鐘
|
||||||
|
print("After sleep")
|
||||||
|
try:
|
||||||
|
start_time = time.time()
|
||||||
|
while time.time() - start_time < 10: # 持續 10 秒鐘
|
||||||
|
rclpy.spin_once(node)
|
||||||
|
time.sleep(1) # 每秒執行一次
|
||||||
|
finally:
|
||||||
|
node.destroy_node()
|
||||||
|
rclpy.shutdown()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
import socket
|
||||||
|
import os
|
||||||
|
|
||||||
|
# socket file path
|
||||||
|
SOCKET_PATH = "/tmp/unix_socket_example"
|
||||||
|
|
||||||
|
# 若檔案存在就先刪除
|
||||||
|
if os.path.exists(SOCKET_PATH):
|
||||||
|
os.remove(SOCKET_PATH)
|
||||||
|
|
||||||
|
# 建立 socket
|
||||||
|
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
|
||||||
|
# 綁定 socket 到檔案
|
||||||
|
server.bind(SOCKET_PATH)
|
||||||
|
server.listen(1)
|
||||||
|
|
||||||
|
print("🔌 Server waiting for connection...")
|
||||||
|
|
||||||
|
# 等待 client 連線
|
||||||
|
conn, _ = server.accept()
|
||||||
|
print("✅ Client connected.")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
data = conn.recv(1024)
|
||||||
|
if not data:
|
||||||
|
break
|
||||||
|
print("📥 Received:", data.decode())
|
||||||
|
conn.sendall(b"Echo: " + data)
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
server.close()
|
||||||
|
os.remove(SOCKET_PATH)
|
||||||
@ -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()
|
||||||
@ -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()
|
||||||
Loading…
Reference in New Issue