base_agent/uas/task/task_action_plan.py

294 lines
13 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 -*-
"""
Task Action 配置计划
─────────────────────────────────────────────────────────────────
描述 Task 中 Action 的挂载方式与执行策略:
挂载层级:
RouteActionPlan —— 整条航线级别的 Action 配置
SegmentActionPlan —— 单个航段RouteSegment级别的 Action 配置
执行策略:
PARALLEL —— 同组 Action 并行执行(无顺序约束)
SEQUENTIAL—— 同组 Action 按 order 字段串行执行
触发时机:
ON_ENTER —— 进入航线/航段时触发
ON_EXIT —— 离开航线/航段时触发
ON_WAYPOINT—— 到达指定航路点时触发
CONTINUOUS —— 在航线/航段内持续触发(按 interval_s 间隔)
SCHEDULED —— 在指定偏移时刻触发
─────────────────────────────────────────────────────────────────
"""
from __future__ import annotations
import uuid
from dataclasses import dataclass, field
from enum import Enum
from typing import Dict, List, Optional
# ─────────────────────────────────────────────────────────────────
# 执行策略
# ─────────────────────────────────────────────────────────────────
class ExecutionPolicy(Enum):
PARALLEL = "parallel" # 并行执行(无顺序约束的 Action 组)
SEQUENTIAL = "sequential" # 串行执行(按 order 字段顺序)
# ─────────────────────────────────────────────────────────────────
# 触发时机
# ─────────────────────────────────────────────────────────────────
class TriggerMoment(Enum):
ON_ENTER = "on_enter" # 进入航线/航段时触发(一次)
ON_EXIT = "on_exit" # 离开航线/航段时触发(一次)
ON_WAYPOINT = "on_waypoint" # 到达指定航路点时触发
CONTINUOUS = "continuous" # 持续触发(按 interval_s
SCHEDULED = "scheduled" # 在指定时间偏移处触发
# ─────────────────────────────────────────────────────────────────
# Action 挂载项
# ─────────────────────────────────────────────────────────────────
@dataclass
class ActionEntry:
"""
单个 Action 挂载项
封装 Action 对象 + 执行顺序 + 触发时机 + 调度参数
"""
action: object # Action 实例(来自 action_module
order: Optional[int] = None # 串行顺序编号None=不参与串行,归入并行组)
trigger: TriggerMoment = TriggerMoment.ON_ENTER
interval_s: float = 10.0 # CONTINUOUS 模式触发间隔(秒)
time_offset_s: float = 0.0 # SCHEDULED 模式时间偏移(秒,相对于段起始)
waypoint_id: Optional[str] = None # ON_WAYPOINT 模式目标航路点标识符
enabled: bool = True # 是否启用
entry_id: str = field(default_factory=lambda: str(uuid.uuid4())[:8])
description: str = ""
@property
def is_sequential(self) -> bool:
"""是否参与串行执行"""
return self.order is not None
@property
def is_parallel(self) -> bool:
"""是否归入并行执行组"""
return self.order is None
def __repr__(self):
policy = f"seq[{self.order}]" if self.is_sequential else "parallel"
return (f"ActionEntry(id={self.entry_id}, "
f"action={self.action.type_key}, "
f"policy={policy}, trigger={self.trigger.value})")
# ─────────────────────────────────────────────────────────────────
# 航段 Action 配置计划
# ─────────────────────────────────────────────────────────────────
@dataclass
class SegmentActionPlan:
"""
单个航段RouteSegment的 Action 配置计划
segment_index: 对应 FlightRoute.segments 的下标
entries: 该航段挂载的所有 ActionEntry
执行规则:
- entries 中 order=None 的 ActionEntry → 并行执行组
- entries 中 order!=None 的 ActionEntry → 按 order 升序串行执行
- 串行组与并行组的相对执行顺序由 serial_first 控制
"""
segment_index: int
entries: List[ActionEntry] = field(default_factory=list)
serial_first: bool = True # True=先串行后并行False=先并行后串行
description: str = ""
# ── 添加 Action ───────────────────────────────────────────────
def add(
self,
action,
order: Optional[int] = None,
trigger: TriggerMoment = TriggerMoment.ON_ENTER,
interval_s: float = 10.0,
time_offset_s: float = 0.0,
waypoint_id: Optional[str] = None,
description: str = "",
) -> "SegmentActionPlan":
"""
添加 Action 到本航段计划
:param order: None=并行,整数=串行顺序从0或1开始均可
:return: self支持链式调用
"""
entry = ActionEntry(
action = action,
order = order,
trigger = trigger,
interval_s = interval_s,
time_offset_s = time_offset_s,
waypoint_id = waypoint_id,
description = description,
)
self.entries.append(entry)
return self
# ── 分组查询 ──────────────────────────────────────────────────
@property
def parallel_entries(self) -> List[ActionEntry]:
"""获取并行执行组order=None"""
return [e for e in self.entries if e.is_parallel and e.enabled]
@property
def sequential_entries(self) -> List[ActionEntry]:
"""获取串行执行组order!=None按 order 升序排列"""
return sorted(
[e for e in self.entries if e.is_sequential and e.enabled],
key=lambda e: e.order
)
def entries_by_trigger(self, trigger: TriggerMoment) -> List[ActionEntry]:
"""按触发时机过滤"""
return [e for e in self.entries if e.trigger == trigger and e.enabled]
def __repr__(self):
return (f"SegmentActionPlan(seg={self.segment_index}, "
f"entries={len(self.entries)}, "
f"parallel={len(self.parallel_entries)}, "
f"sequential={len(self.sequential_entries)})")
# ─────────────────────────────────────────────────────────────────
# 航线 Action 配置计划
# ─────────────────────────────────────────────────────────────────
@dataclass
class RouteActionPlan:
"""
整条航线的 Action 配置计划
包含:
route_entries —— 航线级别的 ActionEntry整条航线触发
segment_plans —— 各航段的 SegmentActionPlan按段索引索引
执行规则与 SegmentActionPlan 相同:
- order=None → 并行组
- order!=None → 串行组(按 order 升序)
"""
route_entries: List[ActionEntry] = field(default_factory=list)
segment_plans: Dict[int, SegmentActionPlan] = field(default_factory=dict)
serial_first: bool = True
description: str = ""
# ── 航线级 Action ─────────────────────────────────────────────
def add_route_action(
self,
action,
order: Optional[int] = None,
trigger: TriggerMoment = TriggerMoment.ON_ENTER,
interval_s: float = 10.0,
time_offset_s: float = 0.0,
waypoint_id: Optional[str] = None,
description: str = "",
) -> "RouteActionPlan":
"""
添加航线级 Action整条航线范围内触发
:param order: None=并行,整数=串行顺序
:return: self链式调用
"""
entry = ActionEntry(
action = action,
order = order,
trigger = trigger,
interval_s = interval_s,
time_offset_s = time_offset_s,
waypoint_id = waypoint_id,
description = description,
)
self.route_entries.append(entry)
return self
# ── 航段级 Action ─────────────────────────────────────────────
def get_segment_plan(self, segment_index: int) -> SegmentActionPlan:
"""获取指定航段的计划(不存在则自动创建)"""
if segment_index not in self.segment_plans:
self.segment_plans[segment_index] = SegmentActionPlan(
segment_index=segment_index
)
return self.segment_plans[segment_index]
def add_segment_action(
self,
segment_index: int,
action,
order: Optional[int] = None,
trigger: TriggerMoment = TriggerMoment.ON_ENTER,
interval_s: float = 10.0,
time_offset_s: float = 0.0,
waypoint_id: Optional[str] = None,
description: str = "",
) -> "RouteActionPlan":
"""
为指定航段添加 Action
:param segment_index: FlightRoute.segments 的下标
:param order: None=并行,整数=串行顺序
:return: self链式调用
"""
plan = self.get_segment_plan(segment_index)
plan.add(
action = action,
order = order,
trigger = trigger,
interval_s = interval_s,
time_offset_s = time_offset_s,
waypoint_id = waypoint_id,
description = description,
)
return self
# ── 分组查询(航线级)────────────────────────────────────────
@property
def parallel_route_entries(self) -> List[ActionEntry]:
return [e for e in self.route_entries if e.is_parallel and e.enabled]
@property
def sequential_route_entries(self) -> List[ActionEntry]:
return sorted(
[e for e in self.route_entries if e.is_sequential and e.enabled],
key=lambda e: e.order
)
def route_entries_by_trigger(self, trigger: TriggerMoment) -> List[ActionEntry]:
return [e for e in self.route_entries
if e.trigger == trigger and e.enabled]
# ── 汇总信息 ──────────────────────────────────────────────────
def summary(self) -> str:
lines = [
f"RouteActionPlan:",
f" 航线级 Actions : {len(self.route_entries)} "
f"(并行={len(self.parallel_route_entries)}, "
f"串行={len(self.sequential_route_entries)})",
f" 已配置航段数 : {len(self.segment_plans)}",
]
for idx, plan in sorted(self.segment_plans.items()):
lines.append(
f" Seg[{idx}]: {len(plan.entries)} actions "
f"(并行={len(plan.parallel_entries)}, "
f"串行={len(plan.sequential_entries)})"
)
return "\n".join(lines)
def __repr__(self):
return (f"RouteActionPlan(route_actions={len(self.route_entries)}, "
f"segment_plans={len(self.segment_plans)})")