base_agent/uas/uas_control/base_adapter.py

188 lines
7.6 KiB
Python
Raw Normal View History

2026-06-18 03:28:14 +00:00
# -*- 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/v2ArduPilot / 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})")