188 lines
7.6 KiB
Python
188 lines
7.6 KiB
Python
|
|
# -*- coding: utf-8 -*-
|
|||
|
|
"""
|
|||
|
|
协议适配器抽象基类
|
|||
|
|
─────────────────────────────────────────────────────────────────
|
|||
|
|
BaseProtocolAdapter 所有协议适配器的统一抽象基类
|
|||
|
|
|
|||
|
|
职责:
|
|||
|
|
- 屏蔽底层协议差异(MAVLink / ROS / 自定义 SDK)
|
|||
|
|
- 将上层 Command 翻译为协议原生消息并发送
|
|||
|
|
- 将协议原生遥测数据解析为统一 BaseTelemetry
|
|||
|
|
- 管理连接生命周期(connect / disconnect / reconnect)
|
|||
|
|
|
|||
|
|
新增协议只需继承 BaseProtocolAdapter,实现抽象方法即可。
|
|||
|
|
─────────────────────────────────────────────────────────────────
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import logging
|
|||
|
|
import time
|
|||
|
|
from abc import abstractmethod
|
|||
|
|
from enum import Enum
|
|||
|
|
from typing import Any, Dict, List, Optional
|
|||
|
|
|
|||
|
|
from PyQt5.QtCore import QObject, pyqtSignal
|
|||
|
|
|
|||
|
|
from uas.uas_control.command import Command, CommandResult, CommandType
|
|||
|
|
from uas.uas_control.telemetry import BaseTelemetry
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ─────────────────────────────────────────────────────────────────
|
|||
|
|
# 协议类型枚举
|
|||
|
|
# ─────────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
class ProtocolType(Enum):
|
|||
|
|
MAVLINK = "mavlink" # MAVLink v1/v2(ArduPilot / PX4)
|
|||
|
|
ROS = "ros" # ROS1 / ROS2
|
|||
|
|
CUSTOM = "custom" # 厂商私有 SDK
|
|||
|
|
SIMULATION= "simulation" # 仿真协议(内部测试)
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ─────────────────────────────────────────────────────────────────
|
|||
|
|
# 协议适配器抽象基类
|
|||
|
|
# ─────────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
class BaseProtocolAdapter(QObject):
|
|||
|
|
"""
|
|||
|
|
协议适配器抽象基类
|
|||
|
|
|
|||
|
|
── 子类必须实现 ──────────────────────────────────────────────
|
|||
|
|
connect() 建立协议连接
|
|||
|
|
disconnect() 断开连接
|
|||
|
|
send_command(cmd) 发送指令(翻译 + 下发)
|
|||
|
|
get_telemetry() 拉取最新遥测数据
|
|||
|
|
supported_commands() 返回本协议支持的指令类型列表
|
|||
|
|
|
|||
|
|
── 子类可选覆盖 ──────────────────────────────────────────────
|
|||
|
|
reconnect() 重连策略
|
|||
|
|
on_raw_message(msg) 处理底层原始消息(用于调试)
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
# ── PyQt5 信号 ────────────────────────────────────────────────
|
|||
|
|
connected = pyqtSignal(str) # device_id
|
|||
|
|
disconnected = pyqtSignal(str) # device_id
|
|||
|
|
telemetry_ready = pyqtSignal(object) # BaseTelemetry
|
|||
|
|
command_ack = pyqtSignal(object) # CommandResult
|
|||
|
|
error_occurred = pyqtSignal(str) # 错误消息
|
|||
|
|
raw_message = pyqtSignal(object) # 原始协议消息(调试)
|
|||
|
|
|
|||
|
|
def __init__(
|
|||
|
|
self,
|
|||
|
|
device_id: str,
|
|||
|
|
protocol: ProtocolType = ProtocolType.SIMULATION,
|
|||
|
|
config: Dict[str, Any] = None,
|
|||
|
|
parent: QObject = None,
|
|||
|
|
):
|
|||
|
|
super().__init__(parent)
|
|||
|
|
self.device_id = device_id
|
|||
|
|
self.protocol = protocol
|
|||
|
|
self.config: Dict[str, Any] = config or {}
|
|||
|
|
self._connected: bool = False
|
|||
|
|
self._telemetry: Optional[BaseTelemetry] = None
|
|||
|
|
self._connect_time: float = 0.0
|
|||
|
|
self._msg_count: int = 0
|
|||
|
|
self.logger = logging.getLogger(
|
|||
|
|
f"{self.__class__.__name__}[{device_id}]"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# ── 连接状态属性 ──────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def is_connected(self) -> bool:
|
|||
|
|
return self._connected
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def uptime_s(self) -> float:
|
|||
|
|
return time.time() - self._connect_time if self._connected else 0.0
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def message_count(self) -> int:
|
|||
|
|
return self._msg_count
|
|||
|
|
|
|||
|
|
# ── 抽象接口(子类必须实现)──────────────────────────────────
|
|||
|
|
|
|||
|
|
@abstractmethod
|
|||
|
|
def connect(self) -> bool:
|
|||
|
|
"""
|
|||
|
|
建立协议连接
|
|||
|
|
:return: True=连接成功
|
|||
|
|
"""
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
@abstractmethod
|
|||
|
|
def disconnect(self) -> bool:
|
|||
|
|
"""
|
|||
|
|
断开协议连接
|
|||
|
|
:return: True=断开成功
|
|||
|
|
"""
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
@abstractmethod
|
|||
|
|
def send_command(self, cmd: Command) -> CommandResult:
|
|||
|
|
"""
|
|||
|
|
发送控制指令
|
|||
|
|
将上层 Command 翻译为协议原生格式并发送,
|
|||
|
|
返回执行结果。
|
|||
|
|
"""
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
@abstractmethod
|
|||
|
|
def get_telemetry(self) -> Optional[BaseTelemetry]:
|
|||
|
|
"""
|
|||
|
|
拉取最新遥测数据
|
|||
|
|
将协议原生遥测解析为统一 BaseTelemetry 子类并返回。
|
|||
|
|
"""
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
@abstractmethod
|
|||
|
|
def supported_commands(self) -> List[CommandType]:
|
|||
|
|
"""返回本协议适配器支持的指令类型列表"""
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
@abstractmethod
|
|||
|
|
def get_connection(self) -> object:
|
|||
|
|
...
|
|||
|
|
|
|||
|
|
# ── 可选覆盖 ──────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
def reconnect(self) -> bool:
|
|||
|
|
"""重连(默认:先断开再连接)"""
|
|||
|
|
self.logger.info(f"尝试重连: {self.device_id}")
|
|||
|
|
self.disconnect()
|
|||
|
|
time.sleep(0.5)
|
|||
|
|
return self.connect()
|
|||
|
|
|
|||
|
|
def on_raw_message(self, msg: Any) -> None:
|
|||
|
|
"""处理底层原始消息(调试用,子类可覆盖)"""
|
|||
|
|
self._msg_count += 1
|
|||
|
|
self.raw_message.emit(msg)
|
|||
|
|
|
|||
|
|
# ── 内部工具 ──────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
def _set_connected(self, state: bool) -> None:
|
|||
|
|
self._connected = state
|
|||
|
|
if state:
|
|||
|
|
self._connect_time = time.time()
|
|||
|
|
self.connected.emit(self.device_id)
|
|||
|
|
self.logger.info(f"已连接: {self.device_id} [{self.protocol.value}]")
|
|||
|
|
else:
|
|||
|
|
self.disconnected.emit(self.device_id)
|
|||
|
|
self.logger.info(f"已断开: {self.device_id}")
|
|||
|
|
|
|||
|
|
def _check_command_supported(self, cmd: Command) -> Optional[CommandResult]:
|
|||
|
|
"""检查指令是否被支持,不支持则返回失败结果"""
|
|||
|
|
if cmd.command_type not in self.supported_commands():
|
|||
|
|
return CommandResult.fail(
|
|||
|
|
cmd,
|
|||
|
|
f"协议 {self.protocol.value} 不支持指令: "
|
|||
|
|
f"{cmd.command_type.value}"
|
|||
|
|
)
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
def __repr__(self) -> str:
|
|||
|
|
state = "已连接" if self._connected else "未连接"
|
|||
|
|
return (f"{self.__class__.__name__}("
|
|||
|
|
f"id={self.device_id}, "
|
|||
|
|
f"protocol={self.protocol.value}, "
|
|||
|
|
f"{state})")
|