node重啟功能 與 定義檔優化

---
(Adding)
1. mavlinkROS2Nodes.py 各別 node 重啟的功能
2.  mainRochestrator.py 開發對應介面
(modify)
 1. 定義檔優化 移除 ackEnum.py 新增 ServiceAckResult.msg
wenchun
Chiyu Chen 3 weeks ago
parent 4e08b18a03
commit 61e283cd25

@ -2,9 +2,15 @@
=== ===
## 功能簡介 ## 功能簡介
1. mavlink 多對多支援平台 1. mavlink 多對多串流與交換
2. 不允許進到 ros 系統有相同 sysid 2. 支援 xbee 與 telemetry
3. 假設一台載具上所有 component 共用同一 socket 3. 支援 udp 管理
4. ROS2 介面監控載具
===
使用限制
1. 不允許進到 ros 系統有相同 sysid
2. 假設一台載具上所有 component 共用同一 socket
=== ===
## 運行環境 ## 運行環境

@ -14,6 +14,7 @@ find_package(builtin_interfaces REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME} rosidl_generate_interfaces(${PROJECT_NAME}
"msg/AttitudeRaw.msg" "msg/AttitudeRaw.msg"
"msg/GnssRaw.msg" "msg/GnssRaw.msg"
"msg/ServiceAckResult.msg"
"srv/MavPing.srv" "srv/MavPing.srv"
"srv/MavCommandLong.srv" "srv/MavCommandLong.srv"
"srv/MavPositionTargetGlobalInt.srv" "srv/MavPositionTargetGlobalInt.srv"

@ -0,0 +1,43 @@
# ServiceAckResult.msg
# ACK code definition for command-like service responses.
# 這邊只是定義常數的檔案
#
# Design:
# - 0~10 : MAVLink MAV_RESULT compatible range (autopilot/firmware ACK) 大多不會用到 直接吃飛控回傳值
# - 50~89 : System-level ACK codes (local bridge/orchestrator status)
# - 90~99 : Unknown/reserved fallback
#
# Rule:
# - If FC ACK is received, return MAV_RESULT_* code.
# - If FC ACK is not reachable / local pipeline failed, return SYS_* code.
# ---- MAVLink MAV_RESULT (standard) ---- 詳細請自行去網路找 mavlink MAV_RESULT
uint8 MAV_RESULT_ACCEPTED=0 # 封包正常接受並執行
uint8 MAV_RESULT_TEMPORARILY_REJECTED=1 # 封包正常接受 但是飛控很忙 不執行 需要等待後重試
uint8 MAV_RESULT_DENIED=2 # 封包不正常 參數可能給錯了
uint8 MAV_RESULT_UNSUPPORTED=3 # 封包不正常 根本認不出是啥
uint8 MAV_RESULT_FAILED=4 # 封包正常接受 但是載具的狀態異常 不執行
uint8 MAV_RESULT_IN_PROGRESS=5
uint8 MAV_RESULT_CANCELLED=6
uint8 MAV_RESULT_COMMAND_LONG_ONLY=7
uint8 MAV_RESULT_COMMAND_INT_ONLY=8
uint8 MAV_RESULT_COMMAND_UNSUPPORTED_MAV_FRAME=9
uint8 MAV_RESULT_NOT_IN_CONTROL=10
# ---- System-level result codes (custom) ----
# 50 : 有接到預期的封包 但是訊息建議再確認
# 51 : some messages will not have obviously ack, so monitering feature message. however, we do NOT get the feature message. ex. 85/86 pair
#
# 52 : mavlink communication timeout 可能是通訊丟包了
# 53 : there already has a command being execution for this sysid, try later
# 54 : this sysid is not found
uint8 FCSYS_GET_EXPECTED_FEEDBACK=50
uint8 FCSYS_NO_EXPECTED_FEEDBACK=51
uint8 FCSYS_MAVLINK_TIMEOUT=52
uint8 FCSYS_BUSY=53
uint8 FCSYS_DEVICE_NOT_FOUND=54
# ---- Fallback ----
uint8 FCSYS_UNKNOWN=90

@ -1,31 +0,0 @@
'''
單獨出來定義回傳碼的意義
若跟飛控韌體有正常的通訊 (無論是否被拒絕) 則以 mavlink 定義的結果回傳
若跟飛控韌體無法通訊 才使用這邊的錯誤代碼
所以要避開重疊的號碼
這邊備註 Mavlink MAV_RESULT 參數意義
0 : 封包正常接受並執行
1 : 封包正常接受 但是正在忙不執行 需要等待後重試
4 : 封包正常接受 但是載具的狀態異常 不執行
2 : 封包不正常 參數可能給錯了
3 : 封包不正常 根本認不出是啥
'''
from enum import IntEnum
class serviceAckResult(IntEnum):
MAVLINK_TIMEOUT = 51 # mavlink communication timeout
MAVLINK_SEND_BUT_NO_EXP_STEAM = 30 # some messages will not have obviously ack, so monitering feature message. however, we do NOT get the feature message. ex. 85/86 pair
MAVLINK_BUSY = 53 # there already has a command being execution for this sysid, try later
MAVLINK_DEV_NOTFOUND = 54 # this sysid not fount
UNKNOWN = 90 # i did not capture this error, try read the logs

@ -37,6 +37,7 @@
- *self.bridge* 存放 mavlink_bridge 實例 - *self.bridge* 存放 mavlink_bridge 實例
- *self.plumber* 存放 serial_manager 實例 - *self.plumber* 存放 serial_manager 實例
- *self.vehicle_registry* 存放 vehicle_registry 實例 - *self.vehicle_registry* 存放 vehicle_registry 實例
- *self.fc_ros_manager* 存放 fc_ros_manager 實例
- *self.panel_thread* 面板的執行緒 - *self.panel_thread* 面板的執行緒
- *self.panelState* 暫存面板與調配者互動的資料流動區 - *self.panelState* 暫存面板與調配者互動的資料流動區
@ -240,8 +241,14 @@
8. 基礎載具流量觀測 8. 基礎載具流量觀測
9. 載具狀態收集與彙整 9. 載具狀態收集與彙整
10. a. ros2 topic 應用開發介面 (基礎框架) 10. a. ros2 topic 應用開發介面 (基礎框架)
- GNSS (alt lan fix_type epv eph)
- attitude position_ned (pitch yaw row local_position)
- battery
b. ros2 service 應用開發介面 (基礎框架) b. ros2 service 應用開發介面 (基礎框架)
11. RTCM 轉發 - 訂閱 RTCM topic 轉為 MAVLink GPS_RTCM_DATA 廣播給所有載具 c. RTCM 轉發 - 訂閱 RTCM topic 轉為 MAVLink GPS_RTCM_DATA 廣播給所有載具
11. ros2 topic service rtcm Node 重啟功能
12. 可以定義 ROS_DOMAIN_ID
### 待開發功能 ### 待開發功能
5-1. 建立 serial 連線 並可以對接收器下達AT指令 5-1. 建立 serial 連線 並可以對接收器下達AT指令

@ -29,7 +29,7 @@ from .utils import acquireSerial, acquirePort
from .utils.acquirePort import find_available_port from .utils.acquirePort import find_available_port
logger = setup_logger(os.path.basename(__file__)) logger = setup_logger(os.path.basename(__file__))
PROJECT_VER = "v0.70" PROJECT_VER = "v1.00"
class PanelState: class PanelState:
def __init__(self): def __init__(self):
@ -177,11 +177,18 @@ class ControlPanel:
MenuNode("New+", "新增 Serial 連接埠", action = "LIST_SERIAL_RES"), MenuNode("New+", "新增 Serial 連接埠", action = "LIST_SERIAL_RES"),
MenuNode("ListAll", "顯示並管理已連線的 Serial", action = "LIST_SERIAL_LINKS"), MenuNode("ListAll", "顯示並管理已連線的 Serial", action = "LIST_SERIAL_LINKS"),
]), ]),
MenuNode("Vehicles Insp.", "檢視已連線的遠端載具", action = "INSPECT_VEHICLES"), MenuNode("Vehicles Insp.", "檢視已連線的遠端載具", action = "SHOW_VEHICLES"),
MenuNode("Engineer Mode", "工程模式", children=[ MenuNode("Engineer Mode", "工程模式", children=[
MenuNode("Stop Manager", "停止 Mavlink 物件管理", "STOP_MANAGER"), MenuNode("Stop Manager", "停止 Mavlink_Object 管理", "STOP_MANAGER"),
MenuNode("Stop Bridge", "停止 Mavlink-ROS 橋接", "STOP_BRIDGE"), MenuNode("Stop Bridge", "停止 Mavlink-ROS 橋接", "STOP_BRIDGE"),
MenuNode("Stop Serial M.", "停止 Serial 端口轉接", "STOP_SERIAL_MANAGER"), MenuNode("Stop Serial M.", "停止 Serial 端口", "STOP_SERIAL_MANAGER"),
MenuNode("Edit ROS Nodes", "管理 ROS NODE", children=[
MenuNode("Reboot Topics", "重建 ROS Topics", "REBOOT_ROS_TOPICS"),
MenuNode("Reboot Services", "重建 ROS Services", "REBOOT_ROS_SERVICES"),
MenuNode("Reboot RTCM", "重建 RTK RTCM", "REBOOT_ROS_RTCM"),
MenuNode("Stop ROS Manager", "停止 ROS 功能", "STOP_ROS_MANAGER"),
MenuNode("Start ROS Manager", "啟動 ROS 功能", "START_ROS_MANAGER"),
]),
]), ]),
MenuNode("Shutdown", "關閉整個系統", children=[ MenuNode("Shutdown", "關閉整個系統", children=[
MenuNode("Return", "繼續運行", "BACK"), MenuNode("Return", "繼續運行", "BACK"),
@ -384,6 +391,7 @@ class ControlPanel:
state.intoTERMINATION() state.intoTERMINATION()
pre_panel_shutdown() pre_panel_shutdown()
# related with -> UDP mavlink Object
elif selected.action == "TEXT_UDP_IP": elif selected.action == "TEXT_UDP_IP":
result = ControlPanel.input_dialog(stdscr, "請輸入監聽的 IP 位址: ") result = ControlPanel.input_dialog(stdscr, "請輸入監聽的 IP 位址: ")
if result is not None: if result is not None:
@ -412,6 +420,8 @@ class ControlPanel:
# menu_stack.pop() # menu_stack.pop()
# idx_stack.pop() # idx_stack.pop()
# related with -> Serial Connection
elif selected.action == "TEXT_BAUD_SERIAL": elif selected.action == "TEXT_BAUD_SERIAL":
result = ControlPanel.input_dialog(stdscr, "請輸入 Baud Rate (e.g., 9600, 115200): ") result = ControlPanel.input_dialog(stdscr, "請輸入 Baud Rate (e.g., 9600, 115200): ")
if result is not None: if result is not None:
@ -477,12 +487,7 @@ class ControlPanel:
menu_stack.pop() menu_stack.pop()
idx_stack.pop() idx_stack.pop()
elif selected.action == "LIST_MAV_OBJECT": # related with -> 列表選單
# 動態生成 mavlink_object 列表選單
created_list_menu = self.create_object_list_menu(state, page=0)
menu_stack.append(created_list_menu)
idx_stack.append(0)
elif selected.action in ("PREV_PAGE", "NEXT_PAGE"): elif selected.action in ("PREV_PAGE", "NEXT_PAGE"):
if hasattr(selected, 'page'): if hasattr(selected, 'page'):
current_list_menu = menu_stack[-1] current_list_menu = menu_stack[-1]
@ -507,6 +512,13 @@ class ControlPanel:
menu_stack.append(created_list_menu) menu_stack.append(created_list_menu)
idx_stack.append(0) idx_stack.append(0)
# related with -> mavlink object list
elif selected.action == "LIST_MAV_OBJECT":
# 動態生成 mavlink_object 列表選單
created_list_menu = self.create_object_list_menu(state, page=0)
menu_stack.append(created_list_menu)
idx_stack.append(0)
elif selected.action == "INSPECT_MAV_OBJECT": elif selected.action == "INSPECT_MAV_OBJECT":
# 顯示物件詳細資訊 # 顯示物件詳細資訊
if hasattr(selected, 'socket_id'): if hasattr(selected, 'socket_id'):
@ -551,6 +563,7 @@ class ControlPanel:
if target_id is not None: if target_id is not None:
cmd_q.put(("MAVOBJ_ADD_TARGET", selected.socket_id, target_id)) cmd_q.put(("MAVOBJ_ADD_TARGET", selected.socket_id, target_id))
# related with -> Engineer Mode
elif selected.action == "STOP_MANAGER": elif selected.action == "STOP_MANAGER":
if state.panel_status != "Engineer": if state.panel_status != "Engineer":
state.panel_info_msg_list.append(("Not in Engineer Mode.", time.time())) state.panel_info_msg_list.append(("Not in Engineer Mode.", time.time()))
@ -569,7 +582,39 @@ class ControlPanel:
continue # 只有在工程模式下才能操作 continue # 只有在工程模式下才能操作
cmd_q.put("SHUTDOWN_SERIAL_MANAGER") cmd_q.put("SHUTDOWN_SERIAL_MANAGER")
elif selected.action == "INSPECT_VEHICLES": elif selected.action == "STOP_ROS_MANAGER":
if state.panel_status != "Engineer":
state.panel_info_msg_list.append(("Not in Engineer Mode.", time.time()))
continue # 只有在工程模式下才能操作
cmd_q.put("STOP_ROS_MANAGER")
elif selected.action == "START_ROS_MANAGER":
if state.panel_status != "Engineer":
state.panel_info_msg_list.append(("Not in Engineer Mode.", time.time()))
continue # 只有在工程模式下才能操作
cmd_q.put("START_ROS_MANAGER")
elif selected.action == "REBOOT_ROS_TOPICS":
if state.panel_status != "Engineer":
state.panel_info_msg_list.append(("Not in Engineer Mode.", time.time()))
continue # 只有在工程模式下才能操作
cmd_q.put(("RESTART_ROS_NODE", "status_publisher")) # 參照一下 mavlinkROS2Nodes.NODE_KEYS
elif selected.action == "REBOOT_ROS_SERVICES":
if state.panel_status != "Engineer":
state.panel_info_msg_list.append(("Not in Engineer Mode.", time.time()))
continue # 只有在工程模式下才能操作
cmd_q.put(("RESTART_ROS_NODE", "command_service")) # 參照一下 mavlinkROS2Nodes.NODE_KEYS
elif selected.action == "REBOOT_ROS_RTCM":
if state.panel_status != "Engineer":
state.panel_info_msg_list.append(("Not in Engineer Mode.", time.time()))
continue # 只有在工程模式下才能操作
cmd_q.put(("RESTART_ROS_NODE", "rtcm_relay")) # 參照一下 mavlinkROS2Nodes.NODE_KEYS
# related with -> 進 bridge 的設備 列表
elif selected.action == "SHOW_VEHICLES":
# 進入載具檢視選單 # 進入載具檢視選單
cmd_q.put("UPDATE_VEHICLES_LIST") cmd_q.put("UPDATE_VEHICLES_LIST")
created_list_menu = self.create_vehicles_list_menu(state, page=0) created_list_menu = self.create_vehicles_list_menu(state, page=0)
@ -1268,6 +1313,12 @@ class Orchestrator:
elif action == "UNREGISTER_VEHICLE": elif action == "UNREGISTER_VEHICLE":
sysid = cmd[1] sysid = cmd[1]
self.vehicle_registry.unregister(sysid) self.vehicle_registry.unregister(sysid)
elif action == "RESTART_ROS_NODE":
node_key = cmd[1]
if not self.fc_ros_manager.schedule_restart_node(node_key):
logger.warning(
f"RESTART_ROS_NODE failed: {node_key!r}"
)
elif cmd == "CREATE_UDP_INBOUND": elif cmd == "CREATE_UDP_INBOUND":
self.panelState.udp_info_temp["direction"] = "inbound" self.panelState.udp_info_temp["direction"] = "inbound"
@ -1277,12 +1328,17 @@ class Orchestrator:
self.create_udp_object() self.create_udp_object()
elif cmd == "CREATE_SERIAL_PORT": elif cmd == "CREATE_SERIAL_PORT":
self.create_serial_port_object() self.create_serial_port_object()
elif cmd == "SHUTDOWN_BRIDGE": elif cmd == "SHUTDOWN_BRIDGE":
self.bridge.stop() self.bridge.stop()
elif cmd == "SHUTDOWN_MANAGER": elif cmd == "SHUTDOWN_MANAGER":
self.manager.shutdown() self.manager.shutdown()
elif cmd == "SHUTDOWN_SERIAL_MANAGER": elif cmd == "SHUTDOWN_SERIAL_MANAGER":
self.plumber.shutdown() self.plumber.shutdown()
elif cmd == "STOP_ROS_MANAGER":
self.fc_ros_manager.stop()
elif cmd == "START_ROS_MANAGER":
self.fc_ros_manager.start()
except queue.Empty: except queue.Empty:
pass pass
@ -1571,7 +1627,7 @@ def main():
if mo.MODULE_VER != "1.50": if mo.MODULE_VER != "1.50":
print("Module Version Error! : mavlinkObkect") print("Module Version Error! : mavlinkObkect")
version_check = False version_check = False
if mros.MODULE_VER != "1.50": if mros.MODULE_VER != "2.10":
print("Module Version Error! : mavlinkROS2Nodes") print("Module Version Error! : mavlinkROS2Nodes")
version_check = False version_check = False
if mvv.MODULE_VER != "1.00": if mvv.MODULE_VER != "1.00":
@ -1622,4 +1678,7 @@ if __name__ == "__main__":
2025.04.13 2025.04.13
1. 加入各項模組的版本先驗 檢驗失敗就直接離開 1. 加入各項模組的版本先驗 檢驗失敗就直接離開
2025.05.28
1. 工程模式 新增 Node 重啟的功能
''' '''

@ -37,7 +37,7 @@ import mavros_msgs.msg
# ROS2 Service imports # ROS2 Service imports
import fc_interfaces.srv as fcsrv import fc_interfaces.srv as fcsrv
import fc_interfaces.msg as fcmsg import fc_interfaces.msg as fcmsg
from .ackEnum import serviceAckResult from fc_interfaces.msg import ServiceAckResult
# 自定義 imports # 自定義 imports
from . import mavlinkVehicleView as mvv from . import mavlinkVehicleView as mvv
@ -45,7 +45,10 @@ from . import mavlinkObject as mo
from .utils import setup_logger from .utils import setup_logger
logger = setup_logger(os.path.basename(__file__)) logger = setup_logger(os.path.basename(__file__))
MODULE_VER = "1.50" MODULE_VER = "2.10"
FC_ROS_DOMAIN_ID = "0"
NODE_KEYS = ("status_publisher", "command_service", "rtcm_relay")
# ============================================================================ # ============================================================================
# VehicleStatusPublisher Node # VehicleStatusPublisher Node
@ -581,7 +584,7 @@ class MavlinkCommandService(Node):
self.handle_set_position_target_global_int self.handle_set_position_target_global_int
) )
logger.info("MavlinkCommandService initialized") # logger.info("MavlinkCommandService initialized")
def _index(self, target_sysid, target_compid): def _index(self, target_sysid, target_compid):
@ -608,20 +611,19 @@ class MavlinkCommandService(Node):
藉由 event.set() 解開 service 中的 block 藉由 event.set() 解開 service 中的 block
''' '''
return_tuple = mo.return_packet_ring.get() return_tuple = mo.return_packet_ring.get()
# 確認 return_packet_ring 有資料
if return_tuple == None: return if return_tuple == None: return
socketid, timestamp, msg = return_tuple socketid, timestamp, msg = return_tuple
sysid = msg.get_srcSystem() sysid = msg.get_srcSystem()
_pending = self._pending_by_sysid.get(sysid) _pending = self._pending_by_sysid.get(sysid)
# 確認是否有 service 在等待回應 若無直接 return 此封包也會被忽略 if _pending is None:
if _pending == None:
return return
if _pending.match_fn(msg): if _pending.match_fn(msg):
_pending.result_mav_msg = msg _pending.result_mav_msg = msg
_pending.event.set() _pending.event.set()
return
# ═══════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════
# Service Handler: 發送 TIMESYNC 可以作為模板 # Service Handler: 發送 TIMESYNC 可以作為模板
@ -706,20 +708,20 @@ class MavlinkCommandService(Node):
# 設定失效回應 # 設定失效回應
response.success = False response.success = False
response.message = "Unknown error" response.message = "Unknown error"
response.ack_result = serviceAckResult.UNKNOWN response.ack_result = ServiceAckResult.FCSYS_UNKNOWN
timeout_sec = request.timeout_sec timeout_sec = request.timeout_sec
# 1) 確認是否已經有相同 sysid 的其他需求正在 pending # 1) 確認是否已經有相同 sysid 的其他需求正在 pending
if request.target_sysid in self._pending_by_sysid: if request.target_sysid in self._pending_by_sysid:
response.message = f"sysid {request.target_sysid} already has pending request" response.message = f"sysid {request.target_sysid} already has pending request"
response.ack_result = serviceAckResult.MAVLINK_BUSY response.ack_result = ServiceAckResult.FCSYS_BUSY
return response return response
# 2) 找到 socket 標地 # 2) 找到 socket 標地
socketObject = self._index(request.target_sysid, request.target_compid) socketObject = self._index(request.target_sysid, request.target_compid)
if socketObject is None: if socketObject is None:
response.message = "This system id not found." response.message = "This system id not found."
response.ack_result = serviceAckResult.MAVLINK_DEV_NOTFOUND response.ack_result = ServiceAckResult.FCSYS_DEVICE_NOT_FOUND
return response return response
# 3) 接收封包系統 的設定 # 3) 接收封包系統 的設定
@ -750,12 +752,13 @@ class MavlinkCommandService(Node):
# 5) 等待回應封包 # 5) 等待回應封包
if not evt.wait(timeout_sec): if not evt.wait(timeout_sec):
response.message = "mavlink timeout - CommLONG" response.message = "mavlink timeout - CommLONG"
response.ack_result = serviceAckResult.MAVLINK_TIMEOUT response.ack_result = ServiceAckResult.FCSYS_MAVLINK_TIMEOUT
fail_skip = True fail_skip = True
# 6) 處理回應封包 # 6) 處理回應封包
if not fail_skip: if not fail_skip:
ack_msg = _pending.result_mav_msg ack_msg = _pending.result_mav_msg
response.success = ServiceAckResult.FCSYS_MAVLINK_BREAK # dev 故意觸發錯誤用的
response.success = (ack_msg.result == 0) # mavutil.mavlink.MAV_RESULT_ACCEPTED response.success = (ack_msg.result == 0) # mavutil.mavlink.MAV_RESULT_ACCEPTED
response.message = "" # 沒有消息就是好消息 response.message = "" # 沒有消息就是好消息
response.ack_result = ack_msg.result response.ack_result = ack_msg.result
@ -779,20 +782,20 @@ class MavlinkCommandService(Node):
''' '''
# 設定失效回應 # 設定失效回應
response.message = "Unknown error" response.message = "Unknown error"
response.ack_result = serviceAckResult.UNKNOWN response.ack_result = ServiceAckResult.FCSYS_UNKNOWN
timeout_sec = request.timeout_sec timeout_sec = request.timeout_sec
# 1) 確認是否已經有相同 sysid 的其他需求正在 pending # 1) 確認是否已經有相同 sysid 的其他需求正在 pending
if request.target_sysid in self._pending_by_sysid: if request.target_sysid in self._pending_by_sysid:
response.message = f"sysid {request.target_sysid} already has pending request" response.message = f"sysid {request.target_sysid} already has pending request"
response.ack_result = serviceAckResult.MAVLINK_BUSY response.ack_result = ServiceAckResult.FCSYS_BUSY
return response return response
# 2) 找到 socket 標地 # 2) 找到 socket 標地
socketObject = self._index(request.target_sysid, request.target_compid) socketObject = self._index(request.target_sysid, request.target_compid)
if socketObject is None: if socketObject is None:
response.message = "This system id not found." response.message = "This system id not found."
response.ack_result = serviceAckResult.MAVLINK_DEV_NOTFOUND response.ack_result = ServiceAckResult.FCSYS_DEVICE_NOT_FOUND
return response return response
# 3) 接收封包系統 的設定 # 3) 接收封包系統 的設定
@ -827,7 +830,7 @@ class MavlinkCommandService(Node):
# 5) 等待回應封包 # 5) 等待回應封包
if not evt.wait(timeout_sec): if not evt.wait(timeout_sec):
response.message = "mavlink timeout - SET POSITION 86" response.message = "mavlink timeout - SET POSITION 86"
response.ack_result = MAVLINK_SEND_BUT_NO_EXP_STEAM response.ack_result = ServiceAckResult.FCSYS_NO_EXPECTED_FEEDBACK
fail_skip = True fail_skip = True
# 6) 處理回應封包 # 6) 處理回應封包
@ -835,7 +838,7 @@ class MavlinkCommandService(Node):
ack_msg = _pending.result_mav_msg ack_msg = _pending.result_mav_msg
response.message = "" response.message = ""
response.ack_result = 0 response.ack_result = ServiceAckResult.FCSYS_GET_EXPECTED_FEEDBACK
response.r_time_boot_ms = ack_msg.time_boot_ms response.r_time_boot_ms = ack_msg.time_boot_ms
response.r_type_mask = ack_msg.type_mask response.r_type_mask = ack_msg.type_mask
response.r_lat_int = ack_msg.lat_int response.r_lat_int = ack_msg.lat_int
@ -952,11 +955,10 @@ class MavlinkCommandService(Node):
return response return response
def stop(self): def stop(self):
"""停止服務""" """停止服務"""
self.running = False self.running = False
logger.info("MavlinkCommandService stopped") # logger.info("MavlinkCommandService stopped")
# ============================================================================ # ============================================================================
@ -1025,7 +1027,7 @@ class RtcmRelay(Node):
rtcm_qos, rtcm_qos,
) )
logger.info("RtcmRelay initialized") # logger.info("RtcmRelay initialized")
# ── callback ────────────────────────────────────────────────────── # ── callback ──────────────────────────────────────────────────────
@ -1129,7 +1131,7 @@ class RtcmRelay(Node):
def stop(self): def stop(self):
"""停止轉發""" """停止轉發"""
self.running = False self.running = False
logger.info("RtcmRelay stopped") # logger.info("RtcmRelay stopped")
# ============================================================================ # ============================================================================
@ -1145,7 +1147,7 @@ class fc_ros_manager:
stop 是停下止 ROS2 nodes 的運行但不銷毀節點實例允許後續再次 start stop 是停下止 ROS2 nodes 的運行但不銷毀節點實例允許後續再次 start
shutdown 是完全關閉 ROS2 並銷毀節點實例 shutdown 是完全關閉 ROS2 並銷毀節點實例
管理三個獨立的 ROS2 Node 管理三個獨立的 ROS2 Node :
- VehicleStatusPublisher - VehicleStatusPublisher
- MavlinkCommandService - MavlinkCommandService
- RtcmRelay - RtcmRelay
@ -1156,6 +1158,9 @@ class fc_ros_manager:
def __init__(self): def __init__(self):
self.initialized = False self.initialized = False
self.running = False self.running = False
self._restarting = False
self._pending_restart: Optional[str] = None
self._restart_lock = threading.Lock()
# node 實例 # node 實例
self.status_publisher: Optional[VehicleStatusPublisher] = None self.status_publisher: Optional[VehicleStatusPublisher] = None
@ -1173,7 +1178,7 @@ class fc_ros_manager:
return False return False
try: try:
# 初始化 ROS2 os.environ.setdefault("ROS_DOMAIN_ID", FC_ROS_DOMAIN_ID)
if not rclpy.ok(): if not rclpy.ok():
rclpy.init(args=None) rclpy.init(args=None)
@ -1209,6 +1214,10 @@ class fc_ros_manager:
try: try:
self.running = True self.running = True
self.status_publisher.running = True
self.command_service.running = True
self.rtcm_relay.running = True
self.spin_thread = threading.Thread( self.spin_thread = threading.Thread(
target=self._spin_executor, target=self._spin_executor,
daemon=True, daemon=True,
@ -1224,21 +1233,102 @@ class fc_ros_manager:
self.running = False self.running = False
return False return False
# 給 orchestrator 呼叫的函數
def schedule_restart_node(self, node_key: str) -> bool:
"""由 orchestrator 排程單一 node 重啟(於 spin thread 下一輪執行)"""
if node_key not in NODE_KEYS:
logger.warning(f"schedule_restart_node: unknown key {node_key!r}")
return False
if not self.running:
logger.warning("schedule_restart_node: fc_ros_manager not running")
return False
if not self.spin_thread or not self.spin_thread.is_alive():
logger.warning("schedule_restart_node: spin thread not alive")
return False
with self._restart_lock:
self._pending_restart = node_key
logger.info(f"schedule_restart_node: queued {node_key}")
return True
# 循環執行的地方 # 循環執行的地方
def _spin_executor(self): def _spin_executor(self):
"""在 thread 中運行的 executor""" """在 thread 中運行的 executor"""
try: try:
# logger.info("ROS2 executor spinning...")
while self.running: while self.running:
self.executor.spin_once(timeout_sec=0.1) self.executor.spin_once(timeout_sec=0.1)
pending = None
with self._restart_lock:
if self._pending_restart:
pending = self._pending_restart
self._pending_restart = None
if pending:
self._restart_node(pending)
elif self.command_service and not self._restarting:
self.command_service.return_router() self.command_service.return_router()
except Exception as e: except Exception as e:
logger.error(f"fc_ros_manager error in spinning executor: {e}") logger.error(f"fc_ros_manager error in spinning executor: {e}")
finally:
self.running = False
def _restart_node(self, node_key: str) -> bool:
"""僅由 spin thread 呼叫 : destroy 並重建單一 node"""
old_node = getattr(self, node_key, None)
if old_node is None:
logger.error(f"_restart_node: {node_key} is None")
return False
# 重置前保留舊有或處理設定
saved_topic_intervals = None
if node_key == "status_publisher":
saved_topic_intervals = dict(old_node.rate_controller.topic_intervals)
# 如果是 command_service 清除所有等待的 pending 並且清除 return message
if node_key == "command_service":
old_node._pending_by_sysid.clear()
for sockObj in mo.mavlink_object.mavlinkObjects.values():
sockObj.set_return_message_types([])
try:
self._restarting = True
# 停止運作 移出執行迴圈 銷毀資源
old_node.stop()
self.executor.remove_node(old_node)
old_node.destroy_node()
# 建立新的 node
if node_key == "status_publisher":
new_node = VehicleStatusPublisher()
if saved_topic_intervals is not None:
new_node.rate_controller.topic_intervals = saved_topic_intervals
elif node_key == "command_service":
new_node = MavlinkCommandService()
elif node_key == "rtcm_relay":
new_node = RtcmRelay()
else:
return False
# 加回去管理系統中
setattr(self, node_key, new_node)
self.executor.add_node(new_node)
logger.info(f"_restart_node: {node_key} restarted")
return True
except Exception as e:
logger.error(f"_restart_node failed for {node_key}: {e}")
return False
finally:
self._restarting = False
def stop(self): def stop(self):
"""停止 ROS2 nodes""" """停止 ROS2 nodes"""
if not self.running: # if not self.running:
logger.warning("fc_ros_manager not running") # logger.warning("fc_ros_manager not running")
# return False
if not self.spin_thread:
logger.warning("fc_ros_manager without thread to stop")
return False return False
try: try:
@ -1289,7 +1379,7 @@ class fc_ros_manager:
except Exception as e: except Exception as e:
logger.error(f"Error during shutdown: {e}") logger.error(f"Error during shutdown: {e}")
def get_status(self) -> dict: def get_status(self):
return { return {
'initialized': self.initialized, 'initialized': self.initialized,
'running': self.running, 'running': self.running,
@ -1299,6 +1389,12 @@ class fc_ros_manager:
} }
# ============================================================================ # ============================================================================
# 全域實例 # 全域實例
# ============================================================================ # ============================================================================
@ -1331,8 +1427,13 @@ ros2_manager = fc_ros_manager()
2. 實作過期丟棄(1s)blake2s hash 去重最短轉發間隔節流分片(180 bytes/) 2. 實作過期丟棄(1s)blake2s hash 去重最短轉發間隔節流分片(180 bytes/)
3. 接入 fc_ros_manager initialize/start/stop/shutdown 生命週期 3. 接入 fc_ros_manager initialize/start/stop/shutdown 生命週期
2026.05.22
1. initialize() 設定 ROS_DOMAIN_ID ( FC_ROS_DOMAIN_ID )
2. schedule_restart_node / _restart_node : 手動重啟單一 node (spin thread 內執行
3. orchestrator cmd: ("RESTART_ROS_NODE", node_key), node_key NODE_KEYS
TODO TODO
1. service 部分會需要跟 mavlinkobject 大量互動 也許需要考慮對方的生命週期 1. service 部分會需要跟 mavlinkobject 大量互動 也許需要考慮對方的生命週期
2. 還沒測試 如果有失效狀態讓 fc_ros_manager 中斷了 如何恢復的問題
''' '''

@ -1,32 +1,25 @@
備選需要的功能 備選需要的功能
- serail 對於 telemetry 的支援
- serial_manager.serial_object.transport 這些變數可能不需要 - serial_manager.serial_object.transport 這些變數可能不需要
不用動 不用動
- mavlink_object 的 send_message 確認一下 mavlink_bridge 的 _send_to_socket 是不是應該做成 async - mavlink_object 的 send_message 確認一下 mavlink_bridge 的 _send_to_socket 是不是應該做成 async
- 不同 socket 上面有重複的 sysid 分開儲存 (不做 不允許sysid重複)
這一步 這一步
希望 ackEnum 可以擺到正確的位置
node 增加 GNSS 訊息 (2d fix 3d fix rtk float...)
RTK RTK
跟 RTK2go 抓取列表 跟 RTK2go 抓取列表 Done
從特定 mount point 得到數據 從特定 mount point 得到數據 Done
做一個 ros2 service(action?) 接到數據並包裝 做一個 ros2 service 接到數據並包裝
下一步 下一步
NODE 重啟 重啟做好了 但是還沒自動化
可以 refresh node topic
下下一步 下下一步
整合 xbee api-api 功能
後面 後面
rssi 資訊提取s
mavlink timeout - CommLONG 這個是 mavlink bus 沒有回應 可能是丟包了
自己的常用指令 自己的常用指令
@ -39,8 +32,18 @@ python -m someotherpkg.src.example_change_mode
python -m fc_network_module.fc_network_module.ntrip_client --ros-args -p mountpoint:=No1bio_02 -p username:=chiyu1468@hotmail.com python -m fc_network_module.fc_network_module.ntrip_client --ros-args -p mountpoint:=No1bio_02 -p username:=chiyu1468@hotmail.com
python -m fc_network_module.fc_network_module.ntrip_client --ros-args -p host:=210.241.63.193 -p port:=81 -p mountpoint:=2020_GNSS -p username:=uavlab6061 -p password:=iamsupersmart
export ROS_DOMAIN_ID=13
ros2 daemon stop
ros2 daemon start
ros2 topic list ros2 topic list
ros2 topic echo /fc_network/vehicle/sys3/battery ros2 topic echo /fc_network/vehicle/sys3/battery
ros2 topic echo hz /fc_network/vehicle/sys3/battery
ros2 topic bw /fc_network/vehicle/sys3/attitude
ros2 topic hz /fc_network/vehicle/sys3/attitude
/home/picars/ardupilot/build/sitl/bin/arducopter -S --model + --speedup 1 --slave 0 --defaults /home/picars/ardupilot/Tools/autotest/default_params/copter.parm --sim-address=127.0.0.1 -I3 --sysid 7 /home/picars/ardupilot/build/sitl/bin/arducopter -S --model + --speedup 1 --slave 0 --defaults /home/picars/ardupilot/Tools/autotest/default_params/copter.parm --sim-address=127.0.0.1 -I3 --sysid 7
@ -51,7 +54,7 @@ ros2 service call /mavlink/add_two_ints example_interfaces/srv/AddTwoInts "{a: 5
ros2 service call /fc_network/vehicle/mav_ping fc_interfaces/srv/MavPing "{target_sysid: 3, target_compid: 0, ping_seq: 1}" ros2 service call /fc_network/vehicle/mav_ping fc_interfaces/srv/MavPing "{target_sysid: 3, target_compid: 0, ping_seq: 1}"
ros2 service call /fc_network/vehicle/send_command_long fc_interfaces/srv/MavCommandLong "{target_sysid: 3, target_compid: 0, command: 176, confirmation: 0, param1: 1, param2: 4, param3: 0,param4: 0,param5: 0,param6: 0,param7: 0, timeout_sec: 2}" ros2 service call /fc_network/vehicle/send_command_long fc_interfaces/srv/MavCommandLong "{target_sysid: 3, target_compid: 0, command: 176, confirmation: 0, param1: 1, param2: 4, param3: 0,param4: 0,param5: 0,param6: 0,param7: 0, timeout_sec: 2}"
ros2 service call /fc_network/vehicle/send_command_long fc_interfaces/srv/MavCommandLong "{target_sysid: 3, target_compid: 0, command: 176, confirmation: 0, param1: 1, param2: 4, timeout_sec: 2}" ros2 service call /fc_network/vehicle/send_command_long fc_interfaces/srv/MavCommandLong "{target_sysid: 10, target_compid: 0, command: 176, confirmation: 0, param1: 1, param2: 4, timeout_sec: 2}"
ros2 service call /fc_network/vehicle/pos_global_int fc_interfaces/srv/MavPositionTargetGlobalInt "{target_sysid: 3, target_compid: 1, coordinate_frame: 6, type_mask: 3576, lat_int: -35376655, lon_int: 149157011, alt: 20.0, timeout_sec: 5.0}" ros2 service call /fc_network/vehicle/pos_global_int fc_interfaces/srv/MavPositionTargetGlobalInt "{target_sysid: 3, target_compid: 1, coordinate_frame: 6, type_mask: 3576, lat_int: -35376655, lon_int: 149157011, alt: 20.0, timeout_sec: 5.0}"
@ -60,10 +63,4 @@ sudo tcpdump -i lo 'udp dst port 14561' -X
sudo tcpdump -i lo 'udp dst port 14550' -X -vv sudo tcpdump -i lo 'udp dst port 14550' -X -vv
sudo tcpdump -i lo -X udp port 14550 sudo tcpdump -i lo -X udp port 14550
幾個要討論的
1. 專案文件中 .mat 檔案是幹嘛的?
2. GUI 的 print [Panel/Map Update] 11 Hz 希望關閉
3. readme 那串文字來源? 用途?
4. GUI介面 加一下海拔高 跟 相對高 速度跟位置 變更為 XY速度 XY位置
5. pitch yaw roll 出來了 弄一下
6.

Loading…
Cancel
Save