base_agent/uas/task/task_action_plan.py

294 lines
13 KiB
Python
Raw Normal View History

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