# -*- 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})")