|
|
|
|
@ -20,7 +20,11 @@ from pymavlink import mavutil
|
|
|
|
|
|
|
|
|
|
# 自定義的 import
|
|
|
|
|
from . import mavlinkObject as mo
|
|
|
|
|
from . import serialManager as sm
|
|
|
|
|
|
|
|
|
|
from .utils import RingBuffer, setup_logger
|
|
|
|
|
from .utils import acquireSerial, acquirePort
|
|
|
|
|
from .utils.acquirePort import find_available_port
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -32,11 +36,13 @@ class PanelState:
|
|
|
|
|
termination_start_time = None
|
|
|
|
|
self.mavlink_bridge_state = "Stopped"
|
|
|
|
|
self.object_manager_state = "Stopped"
|
|
|
|
|
self.serial_manager_state = "Stopped"
|
|
|
|
|
self.socket_object_list = []
|
|
|
|
|
self.panel_info_msg_list = [] # 顯示在面板上的資訊訊息
|
|
|
|
|
|
|
|
|
|
# 這邊是儲存關於 socket object 的資料
|
|
|
|
|
self.udp_info_temp = {"IP": "127.0.0.1", "Port": "", "Direction": ""} # 暫存 UDP 設定資訊
|
|
|
|
|
self.serial_info_temp = {"Port": "", "Baud": 115200, "CommunicationType": "", "Go2Middleware": True} # 暫存 Serial 設定資訊
|
|
|
|
|
self.socket_info_single = {"socket_type": "", "socket_state": "", "bridge_msg_types": "", "return_msg_types": "",
|
|
|
|
|
"target_sockets": "", "primary_socket_id": "", "socket_connection_string": "",
|
|
|
|
|
"InfoReady": False} # 暫存單一 socket 的資訊
|
|
|
|
|
@ -122,7 +128,7 @@ class ControlPanel:
|
|
|
|
|
|
|
|
|
|
return user_input
|
|
|
|
|
|
|
|
|
|
def create_object_list_menu(self, state: PanelState, page=0, items_per_page=5):
|
|
|
|
|
def create_object_list_menu(self, state: PanelState, page=0, items_per_page=8):
|
|
|
|
|
"""動態創建 mavlink_object 列表選單(支持分頁)"""
|
|
|
|
|
children = []
|
|
|
|
|
|
|
|
|
|
@ -167,6 +173,55 @@ class ControlPanel:
|
|
|
|
|
menu.current_page = page
|
|
|
|
|
return menu
|
|
|
|
|
|
|
|
|
|
def create_serial_port_menu(self, state: PanelState, page=0, items_per_page=8):
|
|
|
|
|
"""動態創建 serial port 列表選單(支持分頁)"""
|
|
|
|
|
children = []
|
|
|
|
|
|
|
|
|
|
# 獲取可用的 Serial 連接埠列表
|
|
|
|
|
# serial_ports = acquireSerial.get_serial_ports() # debug 全部抓一抓
|
|
|
|
|
serial_ports = acquireSerial.get_serial_ports_with_filter('/dev/ttyUSB*')
|
|
|
|
|
|
|
|
|
|
if not serial_ports:
|
|
|
|
|
children.append(MenuNode("(空)", "目前沒有串口設備", None))
|
|
|
|
|
else:
|
|
|
|
|
total_items = len(serial_ports)
|
|
|
|
|
total_pages = (total_items + items_per_page - 1) // items_per_page
|
|
|
|
|
start_idx = page * items_per_page
|
|
|
|
|
end_idx = min(start_idx + items_per_page, total_items)
|
|
|
|
|
|
|
|
|
|
# 顯示當前頁的串口
|
|
|
|
|
for port in serial_ports[start_idx:end_idx]:
|
|
|
|
|
port_menu = MenuNode(f"{port}", children=[
|
|
|
|
|
MenuNode("Set Comm Type", "設定通訊形態", "SET_SERIAL_COMM", children=[
|
|
|
|
|
MenuNode("XBee(API-AT)", "XBee 模式", "SET_SERIAL_COMM_XBEE"),
|
|
|
|
|
MenuNode("Xbee(AT-AT)", "數傳模式", "SET_SERIAL_COMM_TELEMETRY"),
|
|
|
|
|
]),
|
|
|
|
|
MenuNode("Set Baud", "設定 Baud", "TEXT_BAUD_SERIAL"),
|
|
|
|
|
MenuNode("Create", "建立此串口", "CREATE_SERIAL_PORT"),
|
|
|
|
|
MenuNode("返回", "回到列表", "BACK"),
|
|
|
|
|
])
|
|
|
|
|
# 將 port 附加到每個子選單項目上
|
|
|
|
|
for child in port_menu.children:
|
|
|
|
|
child.port = port
|
|
|
|
|
children.append(port_menu)
|
|
|
|
|
|
|
|
|
|
# 添加分頁控制
|
|
|
|
|
if total_pages > 1:
|
|
|
|
|
children.append(MenuNode("---", f"第 {page+1}/{total_pages} 頁", None))
|
|
|
|
|
if page > 0:
|
|
|
|
|
prev_node = MenuNode("◀ Prev", "上頁", "PREV_PAGE")
|
|
|
|
|
prev_node.page = page - 1
|
|
|
|
|
children.append(prev_node)
|
|
|
|
|
if page < total_pages - 1:
|
|
|
|
|
next_node = MenuNode("Next ▶", "下頁", "NEXT_PAGE")
|
|
|
|
|
next_node.page = page + 1
|
|
|
|
|
children.append(next_node)
|
|
|
|
|
|
|
|
|
|
children.append(MenuNode("返回", "回到上層選單", "BACK"))
|
|
|
|
|
menu = MenuNode("Serial Port List", f"串口列表 (第 {page + 1} 頁)", children=children)
|
|
|
|
|
menu.current_page = page
|
|
|
|
|
return menu
|
|
|
|
|
|
|
|
|
|
def show_object_info(self, stdscr, socket_id, state: PanelState):
|
|
|
|
|
"""顯示物件詳細資訊的對話框"""
|
|
|
|
|
height, width = stdscr.getmaxyx()
|
|
|
|
|
@ -280,12 +335,14 @@ class ControlPanel:
|
|
|
|
|
MenuNode("Port(Target)", "設定目標的 Port", "TEXT_UDP_PORT"),
|
|
|
|
|
MenuNode("Create", "建立 UDP OutBound 連結口", "CREATE_UDP_OUTBOUND"),
|
|
|
|
|
]),
|
|
|
|
|
MenuNode("Serial InBound", action = "LIST_SERIAL_RES"),
|
|
|
|
|
]),
|
|
|
|
|
MenuNode("ListAll", "顯示並管理所有連結口", "LIST_MAV_OBJECT"),
|
|
|
|
|
]),
|
|
|
|
|
MenuNode("Engineer Mode", "工程模式", children=[
|
|
|
|
|
MenuNode("Stop Manager", "停止 Mavlink 物件管理", "STOP_MANAGER"), #TODO: 尚未實作
|
|
|
|
|
MenuNode("Stop Bridge", "停止 Mavlink-ROS 橋接", "STOP_BRIDGE"), #TODO: 尚未實作
|
|
|
|
|
MenuNode("Stop Manager", "停止 Mavlink 物件管理", "STOP_MANAGER"),
|
|
|
|
|
MenuNode("Stop Bridge", "停止 Mavlink-ROS 橋接", "STOP_BRIDGE"),
|
|
|
|
|
MenuNode("Stop Serial M.", "停止 Serial 端口轉接", "STOP_SERIAL_MANAGER"),
|
|
|
|
|
]),
|
|
|
|
|
MenuNode("Shutdown", "關閉整個系統", children=[
|
|
|
|
|
MenuNode("Return", "繼續運行", "BACK"),
|
|
|
|
|
@ -304,11 +361,11 @@ class ControlPanel:
|
|
|
|
|
curses.echo()
|
|
|
|
|
curses.endwin()
|
|
|
|
|
|
|
|
|
|
def panel_shutdown():
|
|
|
|
|
def pre_panel_shutdown():
|
|
|
|
|
# 先關閉所有模組 再關閉面板
|
|
|
|
|
cmd_q.put("SHUTDOWN_BRIDGE")
|
|
|
|
|
cmd_q.put("SHUTDOWN_MANAGER")
|
|
|
|
|
|
|
|
|
|
cmd_q.put("SHUTDOWN_SERIAL_MANAGER")
|
|
|
|
|
|
|
|
|
|
def draw_menu(screen):
|
|
|
|
|
nonlocal stdscr
|
|
|
|
|
@ -325,9 +382,6 @@ class ControlPanel:
|
|
|
|
|
state.intoSTART() # 設定狀態為運行中
|
|
|
|
|
|
|
|
|
|
while not stop_evt.is_set():
|
|
|
|
|
# 檢查是否需要退出
|
|
|
|
|
if stop_evt.is_set():
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
current_menu = menu_stack[-1]
|
|
|
|
|
current_idx = idx_stack[-1]
|
|
|
|
|
@ -335,20 +389,27 @@ class ControlPanel:
|
|
|
|
|
# 獲取終端機尺寸
|
|
|
|
|
height, width = stdscr.getmaxyx()
|
|
|
|
|
# 簡單暴力的限制視窗的大小
|
|
|
|
|
if height < 20 or width < 60:
|
|
|
|
|
MIN_HEIGHT = (
|
|
|
|
|
2 + # 邊界
|
|
|
|
|
6 + # 狀態列 操作說明列 一個空白
|
|
|
|
|
11+ # 最大選單 與 空白區
|
|
|
|
|
5 # 訊息區域
|
|
|
|
|
)
|
|
|
|
|
if height < MIN_HEIGHT or width < 60:
|
|
|
|
|
logger.error("Terminal size too small for Control Panel.")
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
stdscr.clear()
|
|
|
|
|
|
|
|
|
|
stdscr.border()
|
|
|
|
|
|
|
|
|
|
# 更新模組狀態顯示
|
|
|
|
|
stdscr.addstr(0, 10, " MavLink MiddleWare ", curses.A_BOLD)
|
|
|
|
|
stdscr.addstr(1, 2, f" Panel Status : {state.panel_status}")
|
|
|
|
|
stdscr.addstr(2, 2, f"mavlink Bridge State : {state.mavlink_bridge_state}")
|
|
|
|
|
stdscr.addstr(3, 2, f"Object Manager State : {state.object_manager_state}")
|
|
|
|
|
stdscr.addstr(2, 2, f"Object Manager State : {state.object_manager_state}")
|
|
|
|
|
stdscr.addstr(3, 2, f"Mavlink Bridge State : {state.mavlink_bridge_state}")
|
|
|
|
|
stdscr.addstr(4, 2, f"Socket Object number : {len(state.socket_object_list)}")
|
|
|
|
|
stdscr.addstr(2, 36, f"Serial Manager State : {state.serial_manager_state}")
|
|
|
|
|
|
|
|
|
|
# # 更新模組狀態顯示
|
|
|
|
|
# stdscr.addstr(2, 25, f"{state.mavlink_bridge_state}")
|
|
|
|
|
# stdscr.addstr(3, 25, f"{state.object_manager_state}")
|
|
|
|
|
# stdscr.addstr(4, 25, f"{len(state.socket_object_list)} ")
|
|
|
|
|
@ -363,6 +424,10 @@ class ControlPanel:
|
|
|
|
|
desc = f"{child.desc} [{state.udp_info_temp['IP']}]"
|
|
|
|
|
elif child.action == "TEXT_UDP_PORT" and state.udp_info_temp["Port"]:
|
|
|
|
|
desc = f"{child.desc} [{state.udp_info_temp['Port']}]"
|
|
|
|
|
elif child.action == "SET_SERIAL_COMM" and state.serial_info_temp["CommunicationType"]:
|
|
|
|
|
desc = f"{child.desc} [{state.serial_info_temp['CommunicationType']}]"
|
|
|
|
|
elif child.action == "TEXT_BAUD_SERIAL" and state.serial_info_temp["Baud"]:
|
|
|
|
|
desc = f"{child.desc} [{state.serial_info_temp['Baud']}]"
|
|
|
|
|
|
|
|
|
|
line = f"{marker}{child.name:15s} – {desc}"
|
|
|
|
|
attr = curses.A_REVERSE if i == current_idx else curses.A_NORMAL
|
|
|
|
|
@ -404,25 +469,29 @@ class ControlPanel:
|
|
|
|
|
stdscr.refresh()
|
|
|
|
|
|
|
|
|
|
# 若進入 TERMINATION 狀態,畫面可以刷新 但是不能操作
|
|
|
|
|
# 驗證 bridge 跟 manager 狀態 兩者都停止後 就進入 STOPPED 狀態並跳出迴圈
|
|
|
|
|
# 驗證 其他附屬模組的狀態都停止後 就進入 STOPPED 狀態並跳出迴圈
|
|
|
|
|
# 超過幾秒沒有反應就強制關閉
|
|
|
|
|
if state.panel_status == "Terminating":
|
|
|
|
|
if time.time() - state.termination_start_time > 3:
|
|
|
|
|
if time.time() - state.termination_start_time > 7: # 其他組件設定5秒 這邊給多一點
|
|
|
|
|
logger.warning("Control Panel forced shutdown after timeout.")
|
|
|
|
|
state.intoSTOPPED()
|
|
|
|
|
stop_evt.set()
|
|
|
|
|
continue
|
|
|
|
|
# stop_evt.set()
|
|
|
|
|
# continue
|
|
|
|
|
break
|
|
|
|
|
time.sleep(0.1)
|
|
|
|
|
if state.mavlink_bridge_state == "Stopped" and state.object_manager_state == "Stopped":
|
|
|
|
|
if (state.mavlink_bridge_state == "Stopped" and
|
|
|
|
|
state.object_manager_state == "Stopped" and
|
|
|
|
|
state.serial_manager_state == "Stopped"):
|
|
|
|
|
state.intoSTOPPED()
|
|
|
|
|
stop_evt.set()
|
|
|
|
|
# stop_evt.set()
|
|
|
|
|
break
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# 設定短暫的 timeout,讓執行緒能夠響應 stop_evt
|
|
|
|
|
stdscr.timeout(100) # 100ms timeout
|
|
|
|
|
stdscr.timeout(100)
|
|
|
|
|
ch = stdscr.getch()
|
|
|
|
|
|
|
|
|
|
if ch == -1: # timeout,繼續檢查 stop_evt
|
|
|
|
|
if ch == -1: # 沒有操作
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# 處理按鍵
|
|
|
|
|
@ -456,7 +525,7 @@ class ControlPanel:
|
|
|
|
|
elif ch in (ord('q'), 27):
|
|
|
|
|
if state.panel_status == "Engineer":
|
|
|
|
|
state.intoTERMINATION()
|
|
|
|
|
panel_shutdown()
|
|
|
|
|
pre_panel_shutdown()
|
|
|
|
|
|
|
|
|
|
elif ch in (curses.KEY_ENTER, 10, 13):
|
|
|
|
|
selected = current_menu.children[current_idx]
|
|
|
|
|
@ -473,7 +542,7 @@ class ControlPanel:
|
|
|
|
|
|
|
|
|
|
elif selected.action == "QUIT":
|
|
|
|
|
state.intoTERMINATION()
|
|
|
|
|
panel_shutdown()
|
|
|
|
|
pre_panel_shutdown()
|
|
|
|
|
|
|
|
|
|
elif selected.action == "TEXT_UDP_IP":
|
|
|
|
|
result = ControlPanel.input_dialog(stdscr, "請輸入監聽的 IP 位址: ")
|
|
|
|
|
@ -491,40 +560,98 @@ class ControlPanel:
|
|
|
|
|
if len(menu_stack) > 1:
|
|
|
|
|
menu_stack.pop()
|
|
|
|
|
idx_stack.pop()
|
|
|
|
|
menu_stack.pop()
|
|
|
|
|
idx_stack.pop()
|
|
|
|
|
# menu_stack.pop()
|
|
|
|
|
# idx_stack.pop()
|
|
|
|
|
|
|
|
|
|
elif selected.action == "CREATE_UDP_OUTBOUND":
|
|
|
|
|
cmd_q.put("CREATE_UDP_OUTBOUND")
|
|
|
|
|
# 確認後回到上兩層
|
|
|
|
|
if len(menu_stack) > 1:
|
|
|
|
|
menu_stack.pop()
|
|
|
|
|
idx_stack.pop()
|
|
|
|
|
# menu_stack.pop()
|
|
|
|
|
# idx_stack.pop()
|
|
|
|
|
|
|
|
|
|
elif selected.action == "TEXT_BAUD_SERIAL":
|
|
|
|
|
result = ControlPanel.input_dialog(stdscr, "請輸入 Baud Rate (e.g., 9600, 115200): ")
|
|
|
|
|
if result is not None:
|
|
|
|
|
try:
|
|
|
|
|
baud_rate = int(result)
|
|
|
|
|
except ValueError:
|
|
|
|
|
state.panel_info_msg_list.append(("Invalid Baud Rate input.", time.time()))
|
|
|
|
|
state.serial_info_temp["Baud"] = baud_rate
|
|
|
|
|
|
|
|
|
|
elif selected.action == "SET_SERIAL_COMM_XBEE":
|
|
|
|
|
state.serial_info_temp["CommunicationType"] = "XBee(API-AT)"
|
|
|
|
|
menu_stack.pop()
|
|
|
|
|
idx_stack.pop()
|
|
|
|
|
elif selected.action == "SET_SERIAL_COMM_TELEMETRY":
|
|
|
|
|
state.serial_info_temp["CommunicationType"] = "XBee(AT-AT)"
|
|
|
|
|
menu_stack.pop()
|
|
|
|
|
idx_stack.pop()
|
|
|
|
|
|
|
|
|
|
elif selected.action == "CREATE_SERIAL_PORT":
|
|
|
|
|
state.serial_info_temp["Port"] = menu_stack[-1].name # 從選單取得 Port 名稱
|
|
|
|
|
cmd_q.put("CREATE_SERIAL_PORT")
|
|
|
|
|
# 確認後回到上兩層
|
|
|
|
|
if len(menu_stack) > 1:
|
|
|
|
|
menu_stack.pop()
|
|
|
|
|
idx_stack.pop()
|
|
|
|
|
menu_stack.pop()
|
|
|
|
|
idx_stack.pop()
|
|
|
|
|
|
|
|
|
|
elif selected.action == "LIST_SERIAL_RES":
|
|
|
|
|
created_list_menu = self.create_serial_port_menu(state, page=0)
|
|
|
|
|
menu_stack.append(created_list_menu)
|
|
|
|
|
idx_stack.append(0)
|
|
|
|
|
|
|
|
|
|
elif selected.action == "LIST_MAV_OBJECT":
|
|
|
|
|
# 動態生成 mavlink_object 列表選單
|
|
|
|
|
object_list_menu = self.create_object_list_menu(state, page=0)
|
|
|
|
|
menu_stack.append(object_list_menu)
|
|
|
|
|
created_list_menu = self.create_object_list_menu(state, page=0)
|
|
|
|
|
menu_stack.append(created_list_menu)
|
|
|
|
|
idx_stack.append(0)
|
|
|
|
|
|
|
|
|
|
elif selected.action == "PREV_PAGE":
|
|
|
|
|
# 上一頁
|
|
|
|
|
# elif selected.action == "PREV_PAGE":
|
|
|
|
|
# # 上一頁
|
|
|
|
|
# if hasattr(selected, 'page'):
|
|
|
|
|
# menu_stack.pop()
|
|
|
|
|
# idx_stack.pop()
|
|
|
|
|
# if menu_stack[-1].name == "Serial Port List":
|
|
|
|
|
# created_list_menu = self.create_serial_port_menu(state, page=selected.page)
|
|
|
|
|
# elif menu_stack[-1].name == "Object List":
|
|
|
|
|
# created_list_menu = self.create_object_list_menu(state, page=selected.page)
|
|
|
|
|
# menu_stack.append(created_list_menu)
|
|
|
|
|
# idx_stack.append(0)
|
|
|
|
|
|
|
|
|
|
# elif selected.action == "NEXT_PAGE":
|
|
|
|
|
# # 下一頁
|
|
|
|
|
# if hasattr(selected, 'page'):
|
|
|
|
|
# menu_stack.pop()
|
|
|
|
|
# idx_stack.pop()
|
|
|
|
|
# if menu_stack[-1].name == "Serial Port List":
|
|
|
|
|
# created_list_menu = self.create_serial_port_menu(state, page=selected.page)
|
|
|
|
|
# elif menu_stack[-1].name == "Object List":
|
|
|
|
|
# created_list_menu = self.create_object_list_menu(state, page=selected.page)
|
|
|
|
|
# menu_stack.append(created_list_menu)
|
|
|
|
|
# idx_stack.append(0)
|
|
|
|
|
elif selected.action in ("PREV_PAGE", "NEXT_PAGE"):
|
|
|
|
|
if hasattr(selected, 'page'):
|
|
|
|
|
current_list_menu = menu_stack[-1]
|
|
|
|
|
menu_stack.pop()
|
|
|
|
|
idx_stack.pop()
|
|
|
|
|
object_list_menu = self.create_object_list_menu(state, page=selected.page)
|
|
|
|
|
menu_stack.append(object_list_menu)
|
|
|
|
|
idx_stack.append(0)
|
|
|
|
|
|
|
|
|
|
elif selected.action == "NEXT_PAGE":
|
|
|
|
|
# 下一頁
|
|
|
|
|
if hasattr(selected, 'page'):
|
|
|
|
|
menu_stack.pop()
|
|
|
|
|
idx_stack.pop()
|
|
|
|
|
object_list_menu = self.create_object_list_menu(state, page=selected.page)
|
|
|
|
|
menu_stack.append(object_list_menu)
|
|
|
|
|
# 依據選單種類 重新建立分頁
|
|
|
|
|
if current_list_menu.name == "Serial Port List":
|
|
|
|
|
created_list_menu = self.create_serial_port_menu(state, page=selected.page)
|
|
|
|
|
elif current_list_menu.name == "Object List":
|
|
|
|
|
created_list_menu = self.create_object_list_menu(state, page=selected.page)
|
|
|
|
|
else:
|
|
|
|
|
# 不支援的選單類型,回到原本的選單
|
|
|
|
|
menu_stack.append(current_list_menu)
|
|
|
|
|
idx_stack.append(0)
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
menu_stack.append(created_list_menu)
|
|
|
|
|
idx_stack.append(0)
|
|
|
|
|
|
|
|
|
|
elif selected.action == "INSPECT_MAV_OBJECT":
|
|
|
|
|
@ -583,6 +710,13 @@ class ControlPanel:
|
|
|
|
|
state.panel_info_msg_list.append(("Not in Engineer Mode.", time.time()))
|
|
|
|
|
continue # 只有在工程模式下才能操作
|
|
|
|
|
cmd_q.put("SHUTDOWN_BRIDGE")
|
|
|
|
|
|
|
|
|
|
elif selected.action == "STOP_SERIAL_MANAGER":
|
|
|
|
|
if state.panel_status != "Engineer":
|
|
|
|
|
state.panel_info_msg_list.append(("Not in Engineer Mode.", time.time()))
|
|
|
|
|
continue # 只有在工程模式下才能操作
|
|
|
|
|
cmd_q.put("SHUTDOWN_SERIAL_MANAGER")
|
|
|
|
|
|
|
|
|
|
elif callable(selected.action):
|
|
|
|
|
# 執行函式
|
|
|
|
|
cmd_q.put(selected.action)
|
|
|
|
|
@ -592,14 +726,13 @@ class ControlPanel:
|
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
|
pass
|
|
|
|
|
finally:
|
|
|
|
|
stop_evt.set()
|
|
|
|
|
cleanup()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Orchestrator:
|
|
|
|
|
def __init__(self, stop_sig):
|
|
|
|
|
self.stop_evt = stop_sig
|
|
|
|
|
self.stop_evt = stop_sig # 外部操作去中斷 "面板" 執行緒的訊號 (內部自己停止的話不需要用這個)
|
|
|
|
|
|
|
|
|
|
# === 1) 面板部分的準備 ===
|
|
|
|
|
self.cmd_q = queue.Queue()
|
|
|
|
|
@ -613,6 +746,9 @@ class Orchestrator:
|
|
|
|
|
self.manager = mo.async_io_manager()
|
|
|
|
|
self.bridge = mo.mavlink_bridge()
|
|
|
|
|
|
|
|
|
|
# === 3) serial_manager 部分的準備 ===
|
|
|
|
|
self.plumber = sm.serial_manager()
|
|
|
|
|
|
|
|
|
|
def engageWholeSystem(self):
|
|
|
|
|
"""啟動整個系統"""
|
|
|
|
|
# === 1) 面板部分的啟動 ===
|
|
|
|
|
@ -623,10 +759,14 @@ class Orchestrator:
|
|
|
|
|
self.manager.start()
|
|
|
|
|
self.bridge.start()
|
|
|
|
|
|
|
|
|
|
# === 3) serial_manager 部分的啟動 ===
|
|
|
|
|
self.plumber.start()
|
|
|
|
|
|
|
|
|
|
def mainLoop(self):
|
|
|
|
|
logger.info("Main orchestrator started <-")
|
|
|
|
|
try:
|
|
|
|
|
while not self.stop_evt.is_set():
|
|
|
|
|
# while not self.stop_evt.is_set():
|
|
|
|
|
while self.panel_thread.is_alive():
|
|
|
|
|
|
|
|
|
|
# A. 更新模組狀態
|
|
|
|
|
if self.manager.running:
|
|
|
|
|
@ -642,6 +782,11 @@ class Orchestrator:
|
|
|
|
|
else:
|
|
|
|
|
self.panelState.mavlink_bridge_state = 'Stopped'
|
|
|
|
|
|
|
|
|
|
if self.plumber.running:
|
|
|
|
|
self.panelState.serial_manager_state = 'Running'
|
|
|
|
|
else:
|
|
|
|
|
self.panelState.serial_manager_state = 'Stopped'
|
|
|
|
|
|
|
|
|
|
# 取出面板丟過來的「動作」
|
|
|
|
|
try:
|
|
|
|
|
cmd = self.cmd_q.get_nowait()
|
|
|
|
|
@ -686,10 +831,14 @@ class Orchestrator:
|
|
|
|
|
elif cmd == "CREATE_UDP_OUTBOUND":
|
|
|
|
|
self.panelState.udp_info_temp["direction"] = "outbound"
|
|
|
|
|
self.create_udp_object()
|
|
|
|
|
elif cmd == "CREATE_SERIAL_PORT":
|
|
|
|
|
self.create_serial_port_object()
|
|
|
|
|
elif cmd == "SHUTDOWN_BRIDGE":
|
|
|
|
|
self.bridge.stop()
|
|
|
|
|
elif cmd == "SHUTDOWN_MANAGER":
|
|
|
|
|
self.manager.shutdown()
|
|
|
|
|
elif cmd == "SHUTDOWN_SERIAL_MANAGER":
|
|
|
|
|
self.plumber.shutdown()
|
|
|
|
|
except queue.Empty:
|
|
|
|
|
pass
|
|
|
|
|
except Exception as e:
|
|
|
|
|
@ -703,18 +852,32 @@ class Orchestrator:
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Unexpected error in main loop: {e}")
|
|
|
|
|
finally:
|
|
|
|
|
logger.info("Main orchestrator END!")
|
|
|
|
|
|
|
|
|
|
# 關閉 mavlink_bridge (裡面有一個執行緒)
|
|
|
|
|
self.bridge.stop()
|
|
|
|
|
# 驗證並確保所有模組都被下達關閉訊號
|
|
|
|
|
# 若是由面板操作結束系統 這些關閉行為將於 ControlPanel.pre_panel_shutdown() 觸發
|
|
|
|
|
if self.bridge.thread.is_alive():
|
|
|
|
|
if self.bridge.running:
|
|
|
|
|
self.bridge.stop()
|
|
|
|
|
self.bridge.thread.join(timeout=2)
|
|
|
|
|
|
|
|
|
|
if self.manager.thread.is_alive():
|
|
|
|
|
if self.manager.running:
|
|
|
|
|
self.manager.shutdown()
|
|
|
|
|
self.manager.thread.join(timeout=2)
|
|
|
|
|
|
|
|
|
|
# 關閉 async_io_manager (裡面有一個執行緒)
|
|
|
|
|
self.manager.shutdown()
|
|
|
|
|
if self.plumber.thread.is_alive():
|
|
|
|
|
if self.plumber.running:
|
|
|
|
|
self.plumber.shutdown()
|
|
|
|
|
self.plumber.thread.join(timeout=2)
|
|
|
|
|
|
|
|
|
|
# 關閉面板執行緒
|
|
|
|
|
if self.panel_thread.is_alive():
|
|
|
|
|
self.panel_thread.join(timeout=2)
|
|
|
|
|
|
|
|
|
|
logger.info("Main orchestrator END!")
|
|
|
|
|
|
|
|
|
|
# =============== 面板動作 - Mavlink Object ===============
|
|
|
|
|
|
|
|
|
|
def create_udp_object(self):
|
|
|
|
|
if self.panelState.udp_info_temp["direction"] == "inbound":
|
|
|
|
|
connection_string = f"udp:{self.panelState.udp_info_temp['IP']}:{self.panelState.udp_info_temp['Port']}"
|
|
|
|
|
@ -761,6 +924,46 @@ class Orchestrator:
|
|
|
|
|
else:
|
|
|
|
|
self.panelState.panel_info_msg_list.append((f"Fail Removing target {target_id} from socket {source_id}", time.time()))
|
|
|
|
|
|
|
|
|
|
# =============== 面板動作 - Serial Manager ===============
|
|
|
|
|
|
|
|
|
|
def create_serial_port_object(self):
|
|
|
|
|
# 獲取可用的 udp port
|
|
|
|
|
udp_port_tmp = find_available_port(19000, 20000)
|
|
|
|
|
|
|
|
|
|
# 定義通訊類型映射表
|
|
|
|
|
COMM_TYPE_MAP = {
|
|
|
|
|
"XBee(API-AT)": sm.CommunicationType.XBee_API_AT,
|
|
|
|
|
# "XBee(AT-AT)": sm.CommunicationType.XBee_AT_AT, # TODO: 之後再弄
|
|
|
|
|
# 新增區
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 驗證輸入
|
|
|
|
|
comm_type = self.panelState.serial_info_temp['CommunicationType']
|
|
|
|
|
if not comm_type:
|
|
|
|
|
self.panelState.panel_info_msg_list.append(
|
|
|
|
|
("Please select Communication Type first.", time.time())
|
|
|
|
|
)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 查找對應的通訊類型
|
|
|
|
|
comm_type_tmp = COMM_TYPE_MAP.get(comm_type)
|
|
|
|
|
if comm_type_tmp is None:
|
|
|
|
|
self.panelState.panel_info_msg_list.append(
|
|
|
|
|
(f"Communication type '{comm_type}' not supported yet.", time.time())
|
|
|
|
|
)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
ret = self.plumber.create_serial_port(
|
|
|
|
|
port=self.panelState.serial_info_temp['Port'],
|
|
|
|
|
baudrate=self.panelState.serial_info_temp['Baud'],
|
|
|
|
|
target_port=udp_port_tmp,
|
|
|
|
|
communication_type=comm_type_tmp,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if not ret:
|
|
|
|
|
self.panelState.panel_info_msg_list.append((f"Failed to create Serial Port object at {self.panelState.serial_info_temp['Port']}.", time.time()))
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
|
|
|
|
|
stop_evt = threading.Event()
|
|
|
|
|
|