Compare commits

...

2 Commits

Author SHA1 Message Date
Chiyu Chen fa0a2d0831 (Tested)
新增刪除載具功能
代碼優化
詳見改版記錄
4 months ago
Chiyu Chen c12959d964 (Sort Out) 1. 新增 fc_network_adapter.md
整理並記錄專案結構
程式功能無改動 修正排版與命名
4 months ago

@ -0,0 +1,174 @@
這個檔案整理 此專案下 程式代碼的流程與思路
只會挑出重要的變數與方法描述
以利後續開發使用
# 檔案結構
特別注意:
1. 有標註 [async method] 都是不該被直接呼叫的內部方法
- *valuable* 這個是變數 **沒有括號**
- *method (parameters...)* 這個是方法 **有括號**
## mainOrchestrator.py : 程式進入點
### **[Class]** Orchestrator
最上層的發配資源與啟動終端機面板的調配者
- *self.manager* 存放 async_io_manager 實例
- *self.bridge* 存放 mavlink_bridge 實例
- *self.plumber* 存放 serial_manager 實例
- *self.vehicle_registry* 存放 vehicle_registry 實例
- *self.panel_thread* 面板的執行緒
- *self.panelState* 暫存面板與調配者互動的資料流動區
- 面板運行狀態
- 面板操作結果
- 其他模組的運行狀態
---
- *mainLoop()* 核心方法
- 更新個模組狀態到 *self.panelState*
- 對應面板來的操作指令
---
對於 async_io_manager 控制實現
- *create_udp_object()*
- *delete_udp_object()*
- *add_target_to_object()*
- *remove_target_from_object()*
---
關於載具管理與檢視
- *_update_vehicles_list()*
- *_prepare_vehicle_info*
---
關於 serial_manager 控制實現
- *create_serial_port_object()*
### **[Class]** ControlPanel
面板的核心運行物件
把自己的變數 獨立出來都放到 PanelState 去
- *panel_thread()* 核心方法
- 主選單的引入
- 主選單下所有的按鍵操作
- 定義所有人為操作後續面板執行緒行為
- *menu_tree()* 基礎選單的定義檔
---
關於 udp object 的操作
- *create_object_list_menu()* object 選單的定義檔
- *show_object_info()* 顯示 object 資訊
- *select_target_socket()* object 對於轉拋功能的操作
---
關於 serial 的操作
- *create_serial_port_menu()*
- *create_linked_serial_menu()*
- *show_linked_serial_info()*
---
關於載具檢視與操作
- *create_vehicles_list_menu()*
- *show_vehicle_info()*
### **[Class]** PanelState
作為面板執行緒(ControlPanel)與調配者(Orchestrator)溝通的管道
不包含具體實作方法 是 ControlPanel 的延伸
- *self.panel_info_msg_list* 顯示在面板上的資訊訊息
## mavlinkObject.py
### 全域變數
- *stream_bridge_ring*
- *return_packet_ring*
### **[Class]** mavlink_bridge
唯一實例
實際去解析 mavlink 封包的地方
接收 stream_bridge_ring 與 return_packet_ring 的資料
這邊是比較偏自動化 不會被操作的
- *self.thread* 自己的執行緒
---
- *_run_thread()* 核心方法
- *_handle_XXXXX()* 每一種單項 mavlink 封包的解析
- *send_message()* 是 _send_to_socket() 的高階包裝 跟 ros2 介面做互動的方法
- *_send_to_socket()* 把要傳送的封包 丟給 mavlink 去處理
### **[Class]** async_io_manager
唯一實例
異步 event loop
沒有核心方法
這邊主要是管理 mavlink_object 的地方 (但不會對於某個 mavlink_object 內部需求做操作)
- *self.thread* 自己的執行緒
- *self.managed_objects* 資料結構 socket_id: mavlink_object
---
- *add_mavlink_object(mavlink_object)* [call method] 把一個 mavlink_object 物件加入管理
- *_async_add_mavlink_object(mavlink_object)* [async method] 對應上面的內部方法 不該直接使用
- *remove_mavlink_object(socket_id)* [call method] 從管理區把指定 mavlink_object 移除
### **[Class]** mavlink_object
儲存 mavlink socket
處理 mavlink 封包分流的地方
- *cls.mavlinkObjects* 資料結構 { socket_id(序號) : mavlink_object(物件實例) }
- *self.mavlink_socket* 從 pymavlink 繼承的socket物件
- *self.state* 描述這個 socket 物件的狀態
---
- *process_data()* [async method] 核心方法
- *remove_target_socket()* *add_target_socket()*
- *message_put_queue()* 把要傳送的封包放到自己這個物件的暫存區 會由 process_data() 依照異步流程被實際丟出
## serialManager.py
看這個檔案的重點再於要搞清楚 端口物件 還是 傳輸物件
### **[Class]** serial_manager
異步 event loop
管理 mavlink_object 的地方
- *self.thread* 自己的執行緒
- *self.loop* 自己的事件迴圈
---
- *create_serial_link()* [call method] 把 serial 端口跟 UDP 端口打通
- *_async_create_serial_link()* [async method] 把兩種端口接起來的重點程序
- *remove_serial_link()* [call method] 關閉指定的 serial 端口
- *_async_remove_serial_link()* [async method]
### **[Class]** serial_object
被塞在 serial_manager 裡面
只是一個變數物件
用來被儲存 serial 的資訊
- *self.transport*
- *self.protocol*
- *self.udp_handler* UDP 端口物件
- *self.serial_handler* Serial 端口物件
### **[Class]** UDPHandler
處理 UDP 收發的端口 作為一個端口物件
作為 UDP OutBound 使用 所以不會佔用系統監聽資源
- *self.transport* 自己的傳輸物件
---
- *datagram_received()* 先加碼成 Xbee 再呼叫 Serial 端口物件送出
### **[Class]** SerialHandler
處理 Serial 收發的端口 作為一個端口物件
- *self.transport* 自己的傳輸物件
---
- *data_received()* 先組合 Serial 封包 再解碼 再呼叫 UDP 端口物件送出
## mavlinkVehicleView.py
這個檔案是作為載具的資訊暫存庫使用 會搭配 ROS2 的功能 再做利用
# 開發記錄
## 已實現功能
1. mavlink 分流解析
2. mavlink socket 建立
3. mavlink socket 轉拋 proxy
4. 建立 Serial 轉 UDP 連結 並管理
5. 建立 serial 連線
6. 各單元模組化
7. 終端機介面控制
8. 基礎載具流量觀測
9. 載具狀態收集與彙整
### 待開發功能
5-1. 建立 serial 連線 並可以對接收器下達AT指令
5-2. 模組化 serial 連線機制 以利後期擴容其他模組
10-1. ros2 應用開發介面

@ -323,7 +323,7 @@ class mavlink_bridge:
return False return False
mav_obj = mavlink_object.mavlinkObjects[socket_id] mav_obj = mavlink_object.mavlinkObjects[socket_id]
return mav_obj.send_message(message_bytes) return mav_obj.message_put_queue(message_bytes)
# 定義 mavlink_object 的狀態 # 定義 mavlink_object 的狀態
class MavlinkObjectState(Enum): class MavlinkObjectState(Enum):
@ -501,7 +501,7 @@ class mavlink_object:
logger.error(f"Invalid return message types: {msg_types}") logger.error(f"Invalid return message types: {msg_types}")
return False return False
def send_message(self, message_bytes): def message_put_queue(self, message_bytes):
""" """
從主線程向此 mavlink_object socket 發送數據 從主線程向此 mavlink_object socket 發送數據
將數據添加到簡單的列表中 asyncio 任務處理 將數據添加到簡單的列表中 asyncio 任務處理
@ -568,9 +568,7 @@ class async_io_manager:
self.loop = None self.loop = None
self.running = False self.running = False
# self.main_task = None # self.main_task = None
self.managed_objects = {} # socket_id: mavlink_object
self.thread = None self.thread = None
self._stop_event = threading.Event()
def __del__(self): def __del__(self):
self.loop = None self.loop = None
@ -586,7 +584,6 @@ class async_io_manager:
return return
self.running = True self.running = True
self._stop_event.clear()
# 啟動獨立線程 命名為 AsyncIOManager # 啟動獨立線程 命名為 AsyncIOManager
self.thread = threading.Thread( self.thread = threading.Thread(
@ -618,12 +615,11 @@ class async_io_manager:
return return
# 停止所有被管理的 mavlink_object 所屬的 task # 停止所有被管理的 mavlink_object 所屬的 task
for socket_id in list(self.managed_objects.keys()): for socket_id in list(mavlink_object.mavlinkObjects.keys()):
self.remove_mavlink_object(socket_id) self.remove_mavlink_object(socket_id)
# 停止自己的 task # 停止自己的 task
self.running = False self.running = False
self._stop_event.set()
# 解開事件循環的阻塞 # 解開事件循環的阻塞
self.loop.call_soon_threadsafe(self.loop.stop) self.loop.call_soon_threadsafe(self.loop.stop)
@ -662,17 +658,8 @@ class async_io_manager:
self.loop = None self.loop = None
self.running = False self.running = False
logger.info("async_io_manager event loop END!") logger.info("async_io_manager event loop END!")
async def _main_task(self): # 當初想說可能要一個額外的 task 來管理 但是目前好像用不掉 先放著不管
"""主任務協程 讓 async_io_manager 在執行緒中持續運作"""
logger.info("async_io_manager main task started")
while self.running and not self._stop_event.is_set():
await asyncio.sleep(0.1)
logger.info("async_io_manager main task ended")
def add_mavlink_object(self, mavlink_obj): def add_mavlink_object(self, mavlink_obj: mavlink_object):
"""添加 mavlink_object""" """添加 mavlink_object"""
# 一個防呆 確保有 event loop 與 _main_task 正在運作 # 一個防呆 確保有 event loop 與 _main_task 正在運作
if not self.running or not self.loop: if not self.running or not self.loop:
@ -681,9 +668,12 @@ class async_io_manager:
socket_id = mavlink_obj.socket_id socket_id = mavlink_obj.socket_id
if socket_id in self.managed_objects: # 檢查該對象是否已經在運行中
logger.warning(f"mavlink_object {socket_id} already managed") if socket_id in mavlink_object.mavlinkObjects:
return False existing_obj = mavlink_object.mavlinkObjects[socket_id]
if existing_obj.state == MavlinkObjectState.RUNNING:
logger.warning(f"mavlink_object {socket_id} already managed")
return False
# 使用 run_coroutine_threadsafe 執行協程並獲取結果 # 使用 run_coroutine_threadsafe 執行協程並獲取結果
future = asyncio.run_coroutine_threadsafe( future = asyncio.run_coroutine_threadsafe(
@ -708,7 +698,6 @@ class async_io_manager:
try: try:
task = asyncio.create_task(mavlink_obj.process_data()) task = asyncio.create_task(mavlink_obj.process_data())
self.managed_objects[socket_id] = mavlink_obj
mavlink_obj.task = task mavlink_obj.task = task
mavlink_obj.state = MavlinkObjectState.RUNNING mavlink_obj.state = MavlinkObjectState.RUNNING
mavlink_obj.outgoing_msgs.clear() mavlink_obj.outgoing_msgs.clear()
@ -718,7 +707,7 @@ class async_io_manager:
logger.error(f"Failed to create task for mavlink_object {socket_id}: {e}") logger.error(f"Failed to create task for mavlink_object {socket_id}: {e}")
return False return False
def remove_mavlink_object(self, socket_id): def remove_mavlink_object(self, socket_id: int):
"""移除 mavlink_object""" """移除 mavlink_object"""
# 一個防呆 確保有 event loop 正在運作 # 一個防呆 確保有 event loop 正在運作
@ -743,11 +732,11 @@ class async_io_manager:
async def _async_remove_mavlink_object(self, socket_id): async def _async_remove_mavlink_object(self, socket_id):
"""在事件循環線程中同步執行""" """在事件循環線程中同步執行"""
if socket_id not in self.managed_objects: if socket_id not in mavlink_object.mavlinkObjects:
logger.warning(f"mavlink_object {socket_id} not managed") logger.warning(f"mavlink_object {socket_id} not found")
return return False
mavlink_obj = self.managed_objects[socket_id] mavlink_obj = mavlink_object.mavlinkObjects[socket_id]
mavlink_obj.state = MavlinkObjectState.SHUTTINGDOWN mavlink_obj.state = MavlinkObjectState.SHUTTINGDOWN
if not mavlink_obj.task.done(): if not mavlink_obj.task.done():
@ -761,9 +750,8 @@ class async_io_manager:
break break
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
# 如果正常結束 則移除 # 如果正常結束 則設置為關閉狀態(物件的清理由 __del__ 處理)
if mavlink_obj.task.done(): if mavlink_obj.task.done():
del self.managed_objects[socket_id]
mavlink_obj.state = MavlinkObjectState.CLOSED mavlink_obj.state = MavlinkObjectState.CLOSED
logger.info(f"Removed mavlink_object {socket_id} from manager.") logger.info(f"Removed mavlink_object {socket_id} from manager.")
return True return True
@ -773,8 +761,9 @@ class async_io_manager:
return False return False
def get_managed_objects(self): def get_managed_objects(self):
"""獲取所有被管理的對象列表""" """獲取所有被管理的對象列表(狀態為 RUNNING 的對象)"""
return list(self.managed_objects.keys()) return [socket_id for socket_id, obj in mavlink_object.mavlinkObjects.items()
if obj.state == MavlinkObjectState.RUNNING]
# ====================== 分割線 ===================== # ====================== 分割線 =====================
@ -807,9 +796,13 @@ if __name__ == '__main__':
3. mavlink_bridge 的主要迴圈 增加 send_message 功能 可指定目標 sysid socket_id 發送 mavlink 封包 3. mavlink_bridge 的主要迴圈 增加 send_message 功能 可指定目標 sysid socket_id 發送 mavlink 封包
4. async_io_manager 循環邏輯大改動 優化 mavlink_object 加入與移除的邏輯 並使得 task evenlt loop 分層更清楚 4. async_io_manager 循環邏輯大改動 優化 mavlink_object 加入與移除的邏輯 並使得 task evenlt loop 分層更清楚
5. mavlink_object 移除不必要的 start stop 方法 async_io_manager 統一管理其生命週期 5. mavlink_object 移除不必要的 start stop 方法 async_io_manager 統一管理其生命週期
6. mavlink_object 優化 send_message 方法 避免無效判斷 增加一些防呆檢驗 並與 mavlink_bridge 連動工作 6. mavlink_object 優化 message_put_queue 方法 避免無效判斷 增加一些防呆檢驗 並與 mavlink_bridge 連動工作
7. 移除迴圈內的 try except 堆疊 增加效能 7. 移除迴圈內的 try except 堆疊 增加效能
8. 移除對於 mavlinkDevice 的依賴 改用 vehicle_registry 來管理所有的載具 8. 移除對於 mavlinkDevice 的依賴 改用 vehicle_registry 來管理所有的載具
2026 01 15
1. async_io_manager.managed_objects mavlink_object.mavlinkObjects 功能重複整合 保留 mavlink_object.mavlinkObjects
2. async_io_manager _stop_event 無效變數移除
''' '''

@ -222,15 +222,15 @@ class VehicleComponent:
def reset_packet_stats(self) -> None: def reset_packet_stats(self) -> None:
"""重置封包統計""" """重置封包統計"""
self.packet_stats = PacketStats() self.packet_stats = PacketStats()
def set_parameter(self, param_name: str, param_value: Any) -> None: def set_parameter(self, param_name: str, param_value: Any) -> None:
"""設定參數 (手動餵入)""" """設定參數 (手動餵入)"""
self.parameters[param_name] = param_value self.parameters[param_name] = param_value
def get_parameter(self, param_name: str, default: Any = None) -> Any: def get_parameter(self, param_name: str, default: Any = None) -> Any:
"""取得參數""" """取得參數"""
return self.parameters.get(param_name, default) return self.parameters.get(param_name, default)
def __str__(self) -> str: def __str__(self) -> str:
return (f"Component(id={self.component_id}, type={self.type.value}, " return (f"Component(id={self.component_id}, type={self.type.value}, "
f"mav_type={self.mav_type}, received={self.packet_stats.received_count}, " f"mav_type={self.mav_type}, received={self.packet_stats.received_count}, "
@ -323,7 +323,7 @@ class VehicleView:
""" """
if component_id not in self.components: if component_id not in self.components:
self.components[component_id] = VehicleComponent(component_id, comp_type) self.components[component_id] = VehicleComponent(component_id, comp_type)
logger.info(f"Added component {component_id} to system {self.sysid}") # logger.info(f"Added component {component_id} to system {self.sysid}")
return self.components[component_id] return self.components[component_id]
def get_component(self, component_id: int) -> Optional[VehicleComponent]: def get_component(self, component_id: int) -> Optional[VehicleComponent]:
@ -334,10 +334,17 @@ class VehicleView:
"""移除組件""" """移除組件"""
if component_id in self.components: if component_id in self.components:
del self.components[component_id] del self.components[component_id]
logger.info(f"Removed component {component_id} from system {self.sysid}") # logger.info(f"Removed component {component_id} from system {self.sysid}")
return True return True
return False return False
def reset_component_stats(self, component_id: int) -> None:
"""重置指定組件的封包統計"""
component = self.get_component(component_id)
if component:
component.reset_packet_stats()
# logger.info(f"Reset packet stats for component {component_id} in system {self.sysid}")
def set_rf_module(self, rf_type: RFModuleType) -> RFModule: def set_rf_module(self, rf_type: RFModuleType) -> RFModule:
"""設定RF模組""" """設定RF模組"""
self.rf_module = RFModule(rf_type) self.rf_module = RFModule(rf_type)
@ -435,3 +442,12 @@ class VehicleViewRegistry:
# 全域註冊表實例 # 全域註冊表實例
vehicle_registry = VehicleViewRegistry() vehicle_registry = VehicleViewRegistry()
'''
================= 改版記錄 ============================
2026.01.16
1. 新增 重置指定組件的封包統計 功能
'''

@ -187,11 +187,11 @@ class ATCommandHandler:
# print(f"[{self.serial_port}] Serial Low: 0x{serial_low:08X}") # print(f"[{self.serial_port}] Serial Low: 0x{serial_low:08X}")
pass pass
# ====================== 分割線 =====================
class SerialHandler(asyncio.Protocol): # asyncio.Protocol 用於處理 Serial 收發 class SerialHandler(asyncio.Protocol): # asyncio.Protocol 用於處理 Serial 收發
def __init__(self, udp_handler, serial_port_str): def __init__(self, udp_handler, serial_port_str):
self.udp_handler = udp_handler # UDP 的傳輸把手 self.udp_handler = udp_handler # UDP 的傳輸物件
self.serial_port_str = serial_port_str self.serial_port_str = serial_port_str
self.at_handler = ATCommandHandler(serial_port_str) self.at_handler = ATCommandHandler(serial_port_str)
@ -327,8 +327,8 @@ class serial_manager:
self.receiver_type = receiver_type self.receiver_type = receiver_type
self.target_port = target_port # 指向的 UPD 端口 self.target_port = target_port # 指向的 UPD 端口
self.transport = None self.transport = None # TODO 這個變數可能沒有作用
self.protocol = None self.protocol = None # TODO 這個變數可能沒有作用
self.udp_handler = None self.udp_handler = None
self.serial_handler = None self.serial_handler = None

Loading…
Cancel
Save