base_agent/uas/uas_control/base_adapter.py

188 lines
7.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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