base_agent/uas/agent.py

435 lines
17 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 -*-
from __future__ import annotations
import logging
import queue
import threading
import traceback
from enum import Enum
from time import sleep
from typing import Optional, Tuple
from PyQt5.QtCore import QObject, pyqtSlot, pyqtSignal, QThread
from uas.action.action_executor import action_executor
from uas.event.event_bus import event_bus
from uas.event.handlers.device_handlers import DeviceErrorHandler, DeviceTimeoutHandler, EquipmentFaultHandler
from uas.task.task_executor import task_executor
from uas.task.task_model import Task, TaskState
from uas.uas_control.base_controller import BaseUASController
logger = logging.getLogger(__name__)
class AgentState(Enum):
IDLE = "IDLE"
READY = "READY"
RUNNING = "RUNNING"
PAUSED = "PAUSED"
STOPPED = "STOPPED"
SWITCHING = "SWITCHING"
# ─────────────────────────────────────────────────────────────────
# 装备模型抽象基类
# ─────────────────────────────────────────────────────────────────
class AgentTaskWorker(QObject):
# 信号:用于向主线程发送结果
finished = pyqtSignal()
result = pyqtSignal(object) # 可传数据
def __init__(self, agent:"Agent", dt:int):
super().__init__()
self.agent = agent
self.dt = dt
@pyqtSlot()
def do_work(self):
"""真正的耗时任务"""
try:
# 执行你的任务
print("线程开始执行")
res = self._start_task()
# 发送结果
self.result.emit(res)
finally:
self.finished.emit()
def _start_task(self):
self.agent._model.start()
self.agent.state = AgentState.RUNNING
while self.agent.state != AgentState.STOPPED and not self.agent._task_queue.empty():
self.agent.current_task = self.agent._task_queue.get()
priority, task = self.agent.current_task
assert isinstance(task, Task)
self.agent._planning_task(priority, task)
task_executor.submit(task)
while task.state in(TaskState.RUNNING, TaskState.PLANNED, TaskState.READY):
# 如果需要切换任务
if self.agent.from_to(AgentState.RUNNING, AgentState.SWITCHING):
self.agent._save_task(priority, task)
task_executor.cancel(task.task_id)
break
# 如果需要停止任务
elif self.agent.from_to(AgentState.RUNNING, AgentState.STOPPED):
task_executor.cancel(task.task_id)
break
elif self.agent.from_to(AgentState.RUNNING, AgentState.PAUSED):
task_executor.pause(task.task_id)
elif self.agent.from_to(AgentState.PAUSED, AgentState.RUNNING):
task_executor.resume(task.task_id)
try:
if task.state == TaskState.RUNNING and self.agent.state == AgentState.RUNNING:
result = self.agent._model.step(self.dt)
# 控制仿真速率实时sleep = dt加速sleep < dt
sleep(max(.050, (self.dt/1000) * 0.200))
except Exception as e:
traceback.print_exc()
print(f"[SimThread] 仿真异常: {e}")
break
self.agent.state = AgentState.IDLE
self.agent.current_task = None
self.agent._model.stop()
def _start_task2(self):
self.agent.state = AgentState.RUNNING
while self.agent.state != AgentState.STOPPED and not self.agent._task_queue.empty():
self.agent.current_task = self.agent._task_queue.get()
priority, task = self.agent.current_task
assert isinstance(task, Task)
self.agent._planning_task(priority, task)
task_executor.submit(task)
route = task.route
for seg_index, segment in enumerate(task.route):
pass
while task.state in(TaskState.RUNNING, TaskState.PLANNED, TaskState.READY):
# 如果需要切换任务
if self.agent.from_to(AgentState.RUNNING, AgentState.SWITCHING):
self.agent._save_task(priority, task)
task_executor.cancel(task.task_id)
break
# 如果需要停止任务
elif self.agent.from_to(AgentState.RUNNING, AgentState.STOPPED):
task_executor.cancel(task.task_id)
break
elif self.agent.from_to(AgentState.RUNNING, AgentState.PAUSED):
task_executor.pause(task.task_id)
elif self.agent.from_to(AgentState.PAUSED, AgentState.RUNNING):
task_executor.resume(task.task_id)
try:
if task.state == TaskState.RUNNING and self.agent.state == AgentState.RUNNING:
result = self.agent._model.step(self.dt)
# 控制仿真速率实时sleep = dt加速sleep < dt
sleep(max(.050, (self.dt/1000) * 0.200))
except Exception as e:
traceback.print_exc()
print(f"[SimThread] 仿真异常: {e}")
break
self.agent.state = AgentState.IDLE
self.agent.current_task = None
self.agent._model.stop()
class Agent(QObject):
state_updated = pyqtSignal(object)
def __init__(
self,
controller: BaseUASController,
parent: QObject = None,
):
super().__init__(parent)
self._controller = controller
# self._model = model
# 日志
self.logger = logging.getLogger(
f"{self.__class__.__name__}[{self._model.equipment_id}]"
)
self._prev_state: object = AgentState.IDLE
self._state: object = AgentState.IDLE
self._current_task: Optional[Tuple[int, Task]] = None
self._task_lock = threading.Lock()
self._task_queue = queue.PriorityQueue()
self._task_thread = None
self._setup()
def _setup(self):
# 注册事件类处理器
event_bus.register_handler(DeviceErrorHandler())
event_bus.register_handler(DeviceTimeoutHandler())
event_bus.register_handler(EquipmentFaultHandler())
# 扫描 @on_event 装饰器处理器
count = event_bus.scan_decorators()
event_bus.event_submitted.connect(self._on_event_submitted)
event_bus.event_handled.connect(self._on_event_handled)
event_bus.event_failed.connect(self._on_event_failed)
event_bus.event_ignored.connect(self._on_event_ignored)
event_bus.stats_updated.connect(self._on_stats_updated)
# ── 连接 ActionExecutor 信号 ──────────────────────────────
action_executor.action_completed.connect(self._on_action_completed)
action_executor.action_progress.connect(self._on_action_progress)
action_executor.events_produced.connect(self._on_events_produced)
# ── 连接 TaskExecutor 信号 ────────────────────────────────
task_executor.task_submitted.connect(self._on_task_submitted)
task_executor.task_started.connect(self._on_task_started)
task_executor.task_completed.connect(self._on_task_completed)
task_executor.task_failed.connect(self._on_task_failed)
task_executor.task_cancelled.connect(self._on_task_cancelled)
task_executor.task_paused.connect(self._on_task_paused)
task_executor.task_resumed.connect(self._on_task_resumed)
task_executor.segment_entered.connect(self._on_segment_entered)
task_executor.segment_exited.connect(self._on_segment_exited)
action_executor.set_event_bus(event_bus)
task_executor.set_action_executor(action_executor)
def _state_trans_handler(self):
pass
def put_task(self, priority: int, task: Task):
with self._task_lock:
self._task_queue.put((priority, task))
if self._state == AgentState.IDLE:
self._state = AgentState.READY
self.state_updated.emit(self._state)
def remove_task(self, task: Optional[Task|str]):
with self._task_lock:
temp_queue = queue.PriorityQueue()
while not self._task_queue.empty():
priorty, _task = self._task_queue.get()
assert isinstance(_task, Task)
if isinstance(task, Task):
if _task != task:
temp_queue.put((priorty, task))
elif isinstance(task, str):
if _task.task_id != task:
temp_queue.put((priorty, _task))
self._task_queue = temp_queue
if self._task_queue.empty() and self._state == AgentState.READY:
self._state = AgentState.IDLE
@property
def state(self):
with self._task_lock:
return self._state
@state.setter
def state(self, state: AgentState):
with self._task_lock:
self._prev_state = self._state
self._state = state
self.state_updated.emit(state)
@property
def current_task(self):
with self._task_lock:
return self._current_task
@current_task.setter
def current_task(self, task):
with self._task_lock:
self._current_task = task
def schedule_task(self):
self.state = AgentState.SWITCHING
def stop(self):
self.state = AgentState.STOPPED
def pause(self):
self.state = AgentState.PAUSED
def resume(self):
self.state = AgentState.RUNNING
def from_to(self, from_state, to_state):
with self._task_lock:
if self._prev_state == from_state and self._state == to_state:
return True
else:
return False
def _process_task_queue(self, dt: float):
if self.state == AgentState.RUNNING:
return
self._model.start()
self.state = AgentState.RUNNING
while self.state != AgentState.STOPPED and not self._task_queue.empty():
self.current_task = self._task_queue.get()
priority, task = self.current_task
assert isinstance(task, Task)
self._planning_task(priority, task)
task_executor.submit(task)
while task.state in(TaskState.RUNNING, TaskState.PLANNED, TaskState.READY):
# 如果需要切换任务
if self.from_to(AgentState.RUNNING, AgentState.SWITCHING):
self._save_task(priority, task)
task_executor.cancel(task.task_id)
break
# 如果需要停止任务
elif self.from_to(AgentState.RUNNING, AgentState.STOPPED):
task_executor.cancel(task.task_id)
break
elif self.from_to(AgentState.RUNNING, AgentState.PAUSED):
task_executor.pause(task.task_id)
elif self.from_to(AgentState.PAUSED, AgentState.RUNNING):
task_executor.resume(task.task_id)
try:
if task.state == TaskState.RUNNING and self.state == AgentState.RUNNING:
result = self._model.step(dt)
# 控制仿真速率实时sleep = dt加速sleep < dt
sleep(max(.050, (dt/1000) * 0.200))
except Exception as e:
traceback.print_exc()
print(f"[SimThread] 仿真异常: {e}")
break
self.state = AgentState.IDLE
self.current_task = None
self._model.stop()
def start(self, dt):
if self.state == AgentState.RUNNING:
return
self._task_thread = QThread()
self._worker = AgentTaskWorker(self, dt)
self._worker.moveToThread(self._task_thread)
self._task_thread.started.connect(self._worker.do_work)
self._worker.finished.connect(self._task_thread.quit)
self._worker.finished.connect(self._task_thread.quit) # 任务结束 → 退出线程
self._worker.finished.connect(self._worker.deleteLater) # 自动释放
self._task_thread.finished.connect(self._task_thread.deleteLater) # 自动释放
self._task_thread.finished.connect(self._on_worker_finished) # 任务结束后恢复按钮
self._task_thread.start()
@pyqtSlot()
def _on_worker_finished(self):
self._task_thread = None
# ── 属性(只读)──────────────────────────────────────────────
def _planning_task(self, priority, task):
pass
def _log(self, message, style):
print(message)
def _save_task(self, priority, task):
pass
@pyqtSlot(object)
def _on_event_submitted(self, event):
self._log(f"📥 提交 | {event.type_key} | src={event.source}", "#569cd6")
@pyqtSlot(object)
def _on_event_handled(self, event):
results_str = " | ".join(str(r) for r in event.results)
self._log(
f"✅ 完成 | {event.type_key} | {event.elapsed_ms:.1f}ms | {results_str}",
"#4ec9b0"
)
@pyqtSlot(object, str)
def _on_event_failed(self, event, error):
self._log(f"❌ 失败 | {event.type_key} | {error}", "#f44747")
@pyqtSlot(object)
def _on_event_ignored(self, event):
self._log(f"⚪ 忽略 | {event.type_key} | 无处理器", "#808080")
@pyqtSlot(int, int, int)
def _on_stats_updated(self, total: int, handled: int, failed: int):
pass
# ── TaskExecutor 信号槽 ───────────────────────────────────────
@pyqtSlot(object)
def _on_task_submitted(self, task):
self._log(
f"📥 任务提交: {task.name} [{task.task_id[:8]}]", "#569cd6"
)
@pyqtSlot(object)
def _on_task_started(self, task):
self._log(f"▶ 任务开始执行: {task.name}", "#4ec9b0")
@pyqtSlot(object)
def _on_task_completed(self, task):
self._log(
f"✅ 任务完成: {task.name} 耗时={task.elapsed_s:.1f}s",
"#4CAF50"
)
@pyqtSlot(object, str)
def _on_task_failed(self, task, error):
self._log(f"❌ 任务失败: {task.name} {error}", "#f44747")
@pyqtSlot(object)
def _on_task_cancelled(self, task):
self._log(f"🚫 任务取消: {task.name}", "#FF9800")
@pyqtSlot(object)
def _on_task_paused(self, task):
self._log(f"⏸ 任务暂停: {task.name}", "#9E9E9E")
# self._add_rec_row("state_change", "任务已暂停", "")
@pyqtSlot(object)
def _on_task_resumed(self, task):
self._log(f"▶ 任务恢复: {task.name}", "#4ec9b0")
# self._add_rec_row("state_change", "任务已恢复", "")
@pyqtSlot(object, int)
def _on_segment_entered(self, task, seg_index):
if task.has_route and seg_index < len(task.route.segments):
seg = task.route.segments[seg_index]
msg = (f"进入航段[{seg_index}] "
f"{seg.wp_from.identifier}{seg.wp_to.identifier} "
f"({seg.distance / 1000:.1f}km)")
else:
msg = f"进入航段[{seg_index}]"
self._log(f"✈️ {msg}", "#dcdcaa")
# self._add_rec_row("segment", msg, f"seg_index={seg_index}")
@pyqtSlot(object, int)
def _on_segment_exited(self, task, seg_index):
self._log(f"🏁 离开航段[{seg_index}]", "#808080")
# ── ActionExecutor 信号槽 ─────────────────────────────────────
@pyqtSlot(object)
def _on_action_completed(self, action):
self._log(
f" ✅ Action完成: {action.type_key} "
f"src={action.source} {action.elapsed_ms:.0f}ms",
"#6a9955",
)
@pyqtSlot(str, int, str)
def _on_action_progress(self, action_id: str, percent: int, message: str):
pass
@pyqtSlot(object, list)
def _on_events_produced(self, action, events):
# self._event_count += len(events)
for ev in events:
self._log(
f" ⚡ Event产生: {ev.type_key} src={ev.source}",
"#c586c0",
)
def __repr__(self) -> str:
return (f"{self.__class__.__name__}("
f"id={self.equipment_id}, "
f"name={self.name}, "
f"lc={self.lifecycle.value})")