281 lines
13 KiB
Python
281 lines
13 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
无人系统控制指令模型
|
||
─────────────────────────────────────────────────────────────────
|
||
Command 指令基类(dataclass)
|
||
CommandResult 指令执行结果
|
||
CommandType 指令类型枚举(覆盖无人机/机器狗/通用)
|
||
CommandStatus 指令状态枚举
|
||
─────────────────────────────────────────────────────────────────
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import dataclasses
|
||
import time
|
||
import uuid
|
||
from dataclasses import dataclass, field
|
||
from enum import Enum
|
||
from typing import Any, Dict
|
||
|
||
|
||
# ─────────────────────────────────────────────────────────────────
|
||
# 指令类型枚举
|
||
# ─────────────────────────────────────────────────────────────────
|
||
|
||
class CommandType(Enum):
|
||
# ── 通用指令 ──────────────────────────────────────────────────
|
||
ARM = "arm" # 解锁/上电
|
||
DISARM = "disarm" # 加锁/断电
|
||
START = "start" # 启动
|
||
STOP = "stop" # 停止
|
||
PAUSE = "pause" # 暂停
|
||
RESUME = "resume" # 恢复
|
||
RESET = "reset" # 重置
|
||
EMERGENCY_STOP = "emergency_stop" # 紧急停止
|
||
SET_MODE = "set_mode" # 设置飞行/运动模式
|
||
HEARTBEAT = "heartbeat" # 心跳保活
|
||
|
||
# ── 运动指令 ──────────────────────────────────────────────────
|
||
TAKEOFF = "takeoff" # 起飞(无人机)
|
||
LAND = "land" # 降落(无人机)
|
||
RTL = "rtl" # 返回起飞点
|
||
GOTO = "goto" # 飞往/走往目标点
|
||
SET_VELOCITY = "set_velocity" # 设置速度向量
|
||
SET_HEADING = "set_heading" # 设置航向
|
||
SET_ALTITUDE = "set_altitude" # 设置目标高度
|
||
FOLLOW_ROUTE = "follow_route" # 执行预设航线
|
||
LOITER = "loiter" # 盘旋/原地待命
|
||
|
||
# ── 载荷指令 ──────────────────────────────────────────────────
|
||
PAYLOAD_ON = "payload_on" # 载荷上电
|
||
PAYLOAD_OFF = "payload_off" # 载荷断电
|
||
CAMERA_SHOOT = "camera_shoot" # 拍照
|
||
CAMERA_RECORD = "camera_record" # 录像开关
|
||
GIMBAL_CONTROL = "gimbal_control" # 云台控制
|
||
|
||
# ── 机器狗专有 ────────────────────────────────────────────────
|
||
SIT = "sit" # 坐下
|
||
STAND = "stand" # 站立
|
||
WALK = "walk" # 行走
|
||
TROT = "trot" # 小跑
|
||
CLIMB_STAIRS = "climb_stairs" # 爬楼梯
|
||
SET_GAIT = "set_gait" # 设置步态
|
||
|
||
# ── 自定义扩展 ────────────────────────────────────────────────
|
||
CUSTOM = "custom" # 自定义指令
|
||
|
||
|
||
# ─────────────────────────────────────────────────────────────────
|
||
# 指令状态枚举
|
||
# ─────────────────────────────────────────────────────────────────
|
||
|
||
class CommandStatus(Enum):
|
||
PENDING = "pending" # 待发送
|
||
SENT = "sent" # 已发送,等待确认
|
||
ACCEPTED = "accepted" # 设备已接受
|
||
EXECUTING = "executing" # 执行中
|
||
COMPLETED = "completed" # 执行完成
|
||
FAILED = "failed" # 执行失败
|
||
TIMEOUT = "timeout" # 超时未响应
|
||
REJECTED = "rejected" # 设备拒绝
|
||
|
||
|
||
# ─────────────────────────────────────────────────────────────────
|
||
# 指令基类
|
||
# ─────────────────────────────────────────────────────────────────
|
||
|
||
@dataclass
|
||
class Command:
|
||
"""
|
||
无人系统控制指令
|
||
|
||
所有指令均继承此类,通过 params 字典传递指令参数。
|
||
子类可新增强类型字段以提升可读性。
|
||
"""
|
||
|
||
# ── 标识 ──────────────────────────────────────────────────────
|
||
command_id: str = field(
|
||
default_factory=lambda: str(uuid.uuid4())[:8]
|
||
)
|
||
command_type: CommandType = CommandType.CUSTOM
|
||
target_id: str = "" # 目标设备 ID
|
||
|
||
# ── 参数 ──────────────────────────────────────────────────────
|
||
params: Dict[str, Any] = field(default_factory=dict)
|
||
|
||
# ── 时序 ──────────────────────────────────────────────────────
|
||
created_at: float = field(default_factory=time.time)
|
||
timeout_s: float = 10.0 # 超时时间(秒)
|
||
priority: int = 0 # 优先级(越大越高)
|
||
|
||
# ── 状态 ──────────────────────────────────────────────────────
|
||
status: CommandStatus = CommandStatus.PENDING
|
||
|
||
# ── 序列化 ────────────────────────────────────────────────────
|
||
|
||
def to_dict(self) -> Dict[str, Any]:
|
||
d = dataclasses.asdict(self)
|
||
d["command_type"] = self.command_type.value
|
||
d["status"] = self.status.value
|
||
return d
|
||
|
||
def __repr__(self) -> str:
|
||
return (f"Command({self.command_type.value}"
|
||
f"[{self.command_id}]"
|
||
f"→{self.target_id}"
|
||
f" {self.status.value})")
|
||
|
||
|
||
# ─────────────────────────────────────────────────────────────────
|
||
# 常用指令工厂方法
|
||
# ─────────────────────────────────────────────────────────────────
|
||
|
||
class Commands:
|
||
"""常用指令快速构造工厂"""
|
||
|
||
@staticmethod
|
||
def guide(target_id: str):
|
||
return Command(command_type=CommandType.CUSTOM,
|
||
target_id=target_id)
|
||
|
||
@staticmethod
|
||
def arm(target_id: str, **kw) -> Command:
|
||
return Command(command_type=CommandType.ARM,
|
||
target_id=target_id, **kw)
|
||
|
||
@staticmethod
|
||
def disarm(target_id: str, **kw) -> Command:
|
||
return Command(command_type=CommandType.DISARM,
|
||
target_id=target_id, **kw)
|
||
|
||
@staticmethod
|
||
def takeoff(target_id: str, altitude: float,
|
||
speed: float = 3.0, **kw) -> Command:
|
||
return Command(command_type=CommandType.TAKEOFF,
|
||
target_id=target_id,
|
||
params={"altitude": altitude, "speed": speed},
|
||
**kw)
|
||
|
||
@staticmethod
|
||
def land(target_id: str, **kw) -> Command:
|
||
return Command(command_type=CommandType.LAND,
|
||
target_id=target_id, **kw)
|
||
|
||
@staticmethod
|
||
def rtl(target_id: str, **kw) -> Command:
|
||
return Command(command_type=CommandType.RTL,
|
||
target_id=target_id, **kw)
|
||
|
||
@staticmethod
|
||
def goto(target_id: str, lat: float, lon: float,
|
||
alt: float, speed: float = 5.0, **kw) -> Command:
|
||
return Command(command_type=CommandType.GOTO,
|
||
target_id=target_id,
|
||
params={"lat": lat, "lon": lon,
|
||
"alt": alt, "speed": speed},
|
||
**kw)
|
||
|
||
@staticmethod
|
||
def set_velocity(target_id: str,
|
||
vx: float, vy: float, vz: float = 0.0,
|
||
**kw) -> Command:
|
||
return Command(command_type=CommandType.SET_VELOCITY,
|
||
target_id=target_id,
|
||
params={"vx": vx, "vy": vy, "vz": vz},
|
||
**kw)
|
||
|
||
@staticmethod
|
||
def emergency_stop(target_id: str, **kw) -> Command:
|
||
return Command(command_type=CommandType.EMERGENCY_STOP,
|
||
target_id=target_id,
|
||
priority=999, **kw)
|
||
|
||
@staticmethod
|
||
def set_mode(target_id: str, mode: str, **kw) -> Command:
|
||
return Command(command_type=CommandType.SET_MODE,
|
||
target_id=target_id,
|
||
params={"mode": mode}, **kw)
|
||
|
||
@staticmethod
|
||
def sit(target_id: str, **kw) -> Command:
|
||
return Command(command_type=CommandType.SIT,
|
||
target_id=target_id, **kw)
|
||
|
||
@staticmethod
|
||
def stand(target_id: str, **kw) -> Command:
|
||
return Command(command_type=CommandType.STAND,
|
||
target_id=target_id, **kw)
|
||
|
||
@staticmethod
|
||
def walk(target_id: str, speed: float = 1.0,
|
||
heading: float = 0.0, **kw) -> Command:
|
||
return Command(command_type=CommandType.WALK,
|
||
target_id=target_id,
|
||
params={"speed": speed, "heading": heading},
|
||
**kw)
|
||
|
||
@staticmethod
|
||
def custom(target_id: str, model: int,
|
||
**params) -> Command:
|
||
return Command(command_type=CommandType.CUSTOM,
|
||
target_id=target_id,
|
||
params={"model": model, **params})
|
||
|
||
|
||
# ─────────────────────────────────────────────────────────────────
|
||
# 指令执行结果
|
||
# ─────────────────────────────────────────────────────────────────
|
||
|
||
@dataclass
|
||
class CommandResult:
|
||
"""指令执行结果"""
|
||
command_id: str = ""
|
||
command_type: str = ""
|
||
status: CommandStatus = CommandStatus.PENDING
|
||
success: bool = False
|
||
message: str = ""
|
||
data: Dict[str, Any] = field(default_factory=dict)
|
||
elapsed_ms: float = 0.0
|
||
timestamp: float = field(default_factory=time.time)
|
||
|
||
@classmethod
|
||
def ok(cls, cmd: Command, message: str = "成功",
|
||
data: dict = None, elapsed_ms: float = 0.0) -> "CommandResult":
|
||
return cls(
|
||
command_id = cmd.command_id,
|
||
command_type = cmd.command_type.value,
|
||
status = CommandStatus.COMPLETED,
|
||
success = True,
|
||
message = message,
|
||
data = data or {},
|
||
elapsed_ms = elapsed_ms,
|
||
)
|
||
|
||
@classmethod
|
||
def fail(cls, cmd: Command, message: str = "失败",
|
||
elapsed_ms: float = 0.0) -> "CommandResult":
|
||
return cls(
|
||
command_id = cmd.command_id,
|
||
command_type = cmd.command_type.value,
|
||
status = CommandStatus.FAILED,
|
||
success = False,
|
||
message = message,
|
||
elapsed_ms = elapsed_ms,
|
||
)
|
||
|
||
@classmethod
|
||
def timeout(cls, cmd: Command) -> "CommandResult":
|
||
return cls(
|
||
command_id = cmd.command_id,
|
||
command_type = cmd.command_type.value,
|
||
status = CommandStatus.TIMEOUT,
|
||
success = False,
|
||
message = f"指令超时(>{cmd.timeout_s}s)",
|
||
)
|
||
|
||
def __repr__(self) -> str:
|
||
mark = "✅" if self.success else "❌"
|
||
return (f"{mark} CommandResult("
|
||
f"{self.command_type}[{self.command_id}] "
|
||
f"{self.status.value}: {self.message})") |