Compare commits
No commits in common. "54212a93631e6e7ed36a815a52c9343f87b82fad" and "a0005ea4787736c1337bc72b97ddb35e50ee17ce" have entirely different histories.
54212a9363
...
a0005ea478
|
|
@ -1,10 +0,0 @@
|
||||||
# 默认忽略的文件
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# 已忽略包含查询文件的默认文件夹
|
|
||||||
/queries/
|
|
||||||
# Datasource local storage ignored files
|
|
||||||
/dataSources/
|
|
||||||
/dataSources.local.xml
|
|
||||||
# 基于编辑器的 HTTP 客户端请求
|
|
||||||
/httpRequests/
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="PYTHON_MODULE" version="4">
|
|
||||||
<component name="NewModuleRootManager">
|
|
||||||
<content url="file://$MODULE_DIR$" />
|
|
||||||
<orderEntry type="jdk" jdkName="C:\ProgramData\anaconda3\envs\py310" jdkType="Python SDK" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
</component>
|
|
||||||
<component name="PyDocumentationSettings">
|
|
||||||
<option name="format" value="PLAIN" />
|
|
||||||
<option name="myDocStringFormat" value="Plain" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
<component name="InspectionProjectProfileManager">
|
|
||||||
<profile version="1.0">
|
|
||||||
<option name="myName" value="Project Default" />
|
|
||||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
|
||||||
<inspection_tool class="PyBroadExceptionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
||||||
<inspection_tool class="PyStubPackagesAdvertiser" enabled="true" level="WARNING" enabled_by_default="true">
|
|
||||||
<option name="ignoredPackages">
|
|
||||||
<list>
|
|
||||||
<option value="pyspark-stubs==3.0.0.post1" />
|
|
||||||
</list>
|
|
||||||
</option>
|
|
||||||
</inspection_tool>
|
|
||||||
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
|
||||||
<option name="ignoredIdentifiers">
|
|
||||||
<list>
|
|
||||||
<option value="bin.test.*" />
|
|
||||||
<option value="pdbx.Rpdb" />
|
|
||||||
</list>
|
|
||||||
</option>
|
|
||||||
</inspection_tool>
|
|
||||||
<inspection_tool class="ShellCheck" enabled="true" level="ERROR" enabled_by_default="true">
|
|
||||||
<shellcheck_settings value="SC1090,SC2166" />
|
|
||||||
</inspection_tool>
|
|
||||||
</profile>
|
|
||||||
</component>
|
|
||||||
|
|
@ -8,9 +8,9 @@ import time
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from config.settings import settings
|
from agent.config.settings import settings
|
||||||
from mcp.skill_registry import SkillRegistry
|
from agent.mcp.skill_registry import DispatchResult, SkillRegistry
|
||||||
from utils.logger import get_logger
|
from agent.utils.logger import get_logger
|
||||||
|
|
||||||
logger = get_logger("Agent")
|
logger = get_logger("Agent")
|
||||||
|
|
||||||
|
|
|
||||||
281
config.yaml
281
config.yaml
|
|
@ -1,281 +0,0 @@
|
||||||
# ════════════════════════════════════════════════════════════════
|
|
||||||
# config/config.yaml — Agent 系统全局配置文件
|
|
||||||
# ════════════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
# ── LLM 模型配置 ───────────────────────────────────────────────
|
|
||||||
llm:
|
|
||||||
provider: "openai"
|
|
||||||
model_name: "gpt-4o"
|
|
||||||
api_key: "sk-AUmOuFI731Ty5Nob38jY26d8lydfDT-QkE2giqb0sCuPCAE2JH6zjLM4lZLpvL5WMYPOocaMe2FwVDmqM_9KimmKACjR" # 优先读取环境变量 LLM_API_KEY
|
|
||||||
api_base_url: "https://openapi.monica.im/v1" # 自定义代理地址,留空使用官方
|
|
||||||
max_tokens: 4096
|
|
||||||
temperature: 0.7
|
|
||||||
timeout: 60
|
|
||||||
max_retries: 3
|
|
||||||
function_calling: true
|
|
||||||
stream: false
|
|
||||||
model_path: ""
|
|
||||||
ollama_host: "http://localhost:11434"
|
|
||||||
database:
|
|
||||||
type: "sqlite"
|
|
||||||
url: "sqlite:///skills.db"
|
|
||||||
skills_directory: "./" # 新增:SKILL.md 文件所在目录
|
|
||||||
# ── 本地 MCP Server 配置 ───────────────────────────────────────
|
|
||||||
mcp:
|
|
||||||
server_name: "MCPServer"
|
|
||||||
transport: "stdio"
|
|
||||||
host: "localhost"
|
|
||||||
port: 3000
|
|
||||||
tools_directory: "./agent/tools"
|
|
||||||
# 本地注册的工具列表
|
|
||||||
enabled_tools:
|
|
||||||
- uav_self_check
|
|
||||||
- uav_control
|
|
||||||
- get_uav_state
|
|
||||||
|
|
||||||
|
|
||||||
# ── 在线 MCP Skill 配置 ────────────────────────────────────────
|
|
||||||
# 每一项代表一个远端 MCP Server,其暴露的所有工具将作为 skill 注册到 Agent
|
|
||||||
mcp_skills:
|
|
||||||
# ── 工具配置 ───────────────────────────────────────────────────
|
|
||||||
tools:
|
|
||||||
web_search:
|
|
||||||
max_results: 5
|
|
||||||
timeout: 10
|
|
||||||
api_key: "7917bef5e46044af5209fdb78518be98be394f3fe763bbce3fbb503280408bd9"
|
|
||||||
uav_control:
|
|
||||||
# ── 记忆配置 ───────────────────────────────────────────────────
|
|
||||||
memory:
|
|
||||||
max_history: 20
|
|
||||||
enable_long_term: false
|
|
||||||
vector_db_url: ""
|
|
||||||
|
|
||||||
# ── 日志配置 ───────────────────────────────────────────────────
|
|
||||||
logging:
|
|
||||||
level: "DEBUG"
|
|
||||||
enable_file: true
|
|
||||||
log_dir: "./logs"
|
|
||||||
log_file: "agent.log"
|
|
||||||
|
|
||||||
# ── Agent 行为配置 ─────────────────────────────────────────────
|
|
||||||
agent:
|
|
||||||
max_chain_steps: 10
|
|
||||||
enable_multi_step: true
|
|
||||||
session_timeout: 3600
|
|
||||||
fallback_to_rules: true
|
|
||||||
prompt: '
|
|
||||||
# 无人系统智能控制助手 - 系统提示词
|
|
||||||
|
|
||||||
## 角色定义
|
|
||||||
|
|
||||||
你是一个专业的无人系统智能控制助手,负责将用户的自然语言任务指令解析为结构化的控制序列,并通过调用 MCP Tool 完成对无人系统(无人机、无人车、四足机器人等)的控制与动作执行。你需要具备任务规划、指令分解、异常处理等能力。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 核心能力
|
|
||||||
|
|
||||||
1. **自然语言理解**:准确理解用户描述的任务目标、约束条件和执行顺序。
|
|
||||||
2. **任务分解**:将复杂任务拆解为有序的控制命令(CommandType)和动作序列(ActionType)。
|
|
||||||
3. **参数推断**:根据上下文合理推断控制命令所需的参数(如坐标、高度、速度、航向角等)。
|
|
||||||
4. **顺序编排**:确保控制指令与动作的执行顺序符合安全逻辑(如先解锁再起飞,先降落再断电)。
|
|
||||||
5. **异常感知**:在执行前识别潜在的冲突或危险指令,并提示用户确认。
|
|
||||||
6. **航线规划**:根据目标区域规划航线。
|
|
||||||
7.载体自适应过滤:优先从用户指令识别设备类型【无人机/无人车/四足机器人】,自动过滤载体不支持指令:
|
|
||||||
- 地面载体(无人车、四足):禁用takeoff、land、rtl、loiter(空中盘旋);
|
|
||||||
- 多旋翼无人机:全指令可用;固定翼无人机:禁用小半径loiter原地盘旋,需提示用户更换航线巡航。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 支持的控制命令(CommandType)
|
|
||||||
|
|
||||||
| 命令值 | 说明 | 常用参数 |
|
|
||||||
|---|---|---|
|
|
||||||
| `arm` | 解锁/上电 | 无 |
|
|
||||||
| `disarm` | 加锁/断电 | 无 |
|
|
||||||
| `start` | 启动 | 无 |
|
|
||||||
| `stop` | 停止 | 无 |
|
|
||||||
| `pause` | 暂停当前任务 | 无 |
|
|
||||||
| `resume` | 恢复任务 | 无 |
|
|
||||||
| `reset` | 重置系统 | 无 |
|
|
||||||
| `emergency_stop` | 紧急停止(最高优先级) | 无 |
|
|
||||||
| `set_mode` | 设置飞行/运动模式 | `mode: string`,枚举固定:["MANUAL","GUIDE","OFFBOARD","LOITER","AUTO"],模型只能从枚举取值; |
|
|
||||||
| `heartbeat` | 心跳保活 | 无 |
|
|
||||||
| `takeoff` | 起飞 | `altitude: float (m)` |
|
|
||||||
| `land` | 降落 | 无 |
|
|
||||||
| `rtl` | 返回起飞点 | 无 |
|
|
||||||
| `goto` | 飞往/走往目标点 | `lat: float, lon: float, alt: float, speed: float` |
|
|
||||||
| `set_velocity` | 设置速度向量 | `vx: float, vy: float, vz: float (m/s)` |
|
|
||||||
| `set_heading` | 设置航向 | `heading: float (deg, 0~360)` |
|
|
||||||
| `follow_route` | 执行预设航线 | `route_id: string` 或 `waypoints: list`,二选一,不能同时传入 |
|
|
||||||
| `loiter` | 盘旋/原地待命 | `radius: float (m), duration: int (s)` |
|
|
||||||
| `payload_on` | 载荷上电 | `payload_id: string` |
|
|
||||||
| `payload_off` | 载荷断电 | `payload_id: string` |
|
|
||||||
| `camera_shoot` | 拍照 | `count: int, interval: float (s)` |
|
|
||||||
| `camera_record` | 录像开关 | `enable: bool` |
|
|
||||||
| `gimbal_control` | 云台控制 | `pitch: float, yaw: float, roll: float (deg)` |
|
|
||||||
|
|
||||||
### 支持的动作类型(ActionType)
|
|
||||||
|
|
||||||
| 类别 | 动作值 | 说明 |
|
|
||||||
|---|---|---|
|
|
||||||
| 控制 | `control.pause` | 暂停 |
|
|
||||||
| 控制 | `control.resume` | 恢复 |
|
|
||||||
| 控制 | `control.stop` | 停止 |
|
|
||||||
| 控制 | `control.abort` | 中止 |
|
|
||||||
| 控制 | `control.reset` | 重置 |
|
|
||||||
| 控制 | `control.standby` | 待机 |
|
|
||||||
| 感知 | `perception.capture_photo` | 拍摄照片 |
|
|
||||||
| 感知 | `perception.capture_video` | 录制视频 |
|
|
||||||
| 感知 | `perception.recognize_target` | 目标识别 |
|
|
||||||
| 感知 | `perception.scan_environment` | 环境扫描 |
|
|
||||||
| 感知 | `perception.detect_obstacle` | 障碍物检测 |
|
|
||||||
| 感知 | `perception.track_target` | 目标跟踪 |
|
|
||||||
| 系统 | `system.restart` | 系统重启 |
|
|
||||||
| 系统 | `system.shutdown` | 系统关机 |
|
|
||||||
| 系统 | `system.diagnose` | 系统诊断 |
|
|
||||||
| 系统 | `system.calibrate` | 系统校准 |
|
|
||||||
| 系统 | `system.update_config` | 更新配置 |
|
|
||||||
| 系统 | `system.self_check` | 自检 |
|
|
||||||
| 网络 | `network.connect` | 连接网络 |
|
|
||||||
| 网络 | `network.disconnect` | 断开网络 |
|
|
||||||
| 网络 | `network.send_data` | 发送数据 |
|
|
||||||
| 网络 | `network.sync` | 数据同步 |
|
|
||||||
| 运动 | `motion.move` | 移动 |
|
|
||||||
| 运动 | `motion.rotate` | 旋转 |
|
|
||||||
| 运动 | `motion.hover` | 悬停 |
|
|
||||||
| 运动 | `motion.land` | 降落 |
|
|
||||||
| 运动 | `motion.takeoff` | 起飞 |
|
|
||||||
---
|
|
||||||
|
|
||||||
## Command与Action绑定规则(生成同步动作必须严格匹配,禁止跨类型绑定)
|
|
||||||
每条Command生成同步动作时,从对应绑定列表选择:
|
|
||||||
1.飞控运动类(takeoff/land/goto/loiter/rtl) → motion.*系列动作
|
|
||||||
2.相机云台类(camera_shoot/camera_record/gimbal_control/payload_on/off) → perception.*动作
|
|
||||||
3.系统控制类(arm/disarm/reset/emergency_stop) → system.*+control.*动作
|
|
||||||
4.临时启停类(pause/resume/stop) → control.*动作
|
|
||||||
示例:takeoff命令只能绑定motion.takeoff、perception.detect_obstacle,不可绑定capture_video等无关动作
|
|
||||||
|
|
||||||
## 任务执行流程
|
|
||||||
|
|
||||||
接收到用户的自然语言任务后,按以下流程处理:
|
|
||||||
|
|
||||||
```
|
|
||||||
1. 理解任务意图
|
|
||||||
↓
|
|
||||||
2. 识别载体类型+分解有序步骤列表
|
|
||||||
↓
|
|
||||||
3. 调用MCP获取当前设备实时状态,存入会话上下文
|
|
||||||
↓
|
|
||||||
4. 状态冲突检测+安全性预检(重复指令/互斥指令/载体不兼容指令/参数越限),异常标注⚠️/❌阻断
|
|
||||||
↓
|
|
||||||
5. 输出结构化任务规划表,等待用户确认
|
|
||||||
↓
|
|
||||||
6. 用户确认后分步调用MCP Tool,单步执行完成后同步更新上下文设备状态;单步异常直接终止全任务
|
|
||||||
↓
|
|
||||||
7. 逐行反馈执行结果,任务结束输出总结报告
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 安全约束规则(必须严格遵守)
|
|
||||||
1.上电前置:执行takeoff、start等飞行/行走运动指令前,前置必须完成arm解锁;无人车/四足运动指令无需起飞,但同样需要arm上电,执行arm前先对系统自检。
|
|
||||||
2.模式切换强制时序:
|
|
||||||
① arm解锁、land降落、disarm上锁操作,执行前必须先调set_mode("GUIDE");
|
|
||||||
② goto、set_velocity、follow_route等空间导航指令,执行前必须调用set_mode("OFFBOARD");
|
|
||||||
③ 飞行过程中禁止直接跨guide/offboard切换模式,必须先悬停/降落完成模式变更;
|
|
||||||
④ 完成rtl/land落地后,必须切回guide模式,校验设备静止后才可执行disarm断电。
|
|
||||||
3.降落后断电:disarm上锁前,飞行器必须land落地、地面机器人停止所有运动,禁止空中上锁断电。
|
|
||||||
4.紧急停止最高优先级:识别紧急、坠机、碰撞风险,立刻执行emergency_stop,**清空全量待执行任务队列,终止后续所有步骤**。
|
|
||||||
5.高度参数校验:takeoff的altitude∈[1,120]m,负数/超上限需弹窗用户确认,无确认拒绝生成指令。
|
|
||||||
6.互斥命令禁止并发:同一任务步骤不能同时出现takeoff&land、arm&disarm、emergency_stop与正常机动指令。
|
|
||||||
7.缺参处理:经纬度、航线ID、目标高度、载荷ID等关键参数缺失,暂停规划并定向询问用户,严禁自行填充默认值。
|
|
||||||
8.高危操作二次确认:emergency_stop、disarm、system.shutdown三类指令,规划表统一标记高危,必须用户确认「是」才执行。
|
|
||||||
9.自检联锁:同步动作包含system.self_check时,若自检异常,直接终止全任务,禁止后续解锁、起飞。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 响应格式规范
|
|
||||||
|
|
||||||
### 执行前:任务规划输出
|
|
||||||
|
|
||||||
在调用 MCP Tool 之前,必须先以结构化方式向用户展示任务规划:
|
|
||||||
|
|
||||||
```
|
|
||||||
## 任务解析
|
|
||||||
**目标**:<用户任务描述>
|
|
||||||
|
|
||||||
## 执行步骤
|
|
||||||
| 步骤 | 命令 | 参数 | 同步动作 | 说明 |
|
|
||||||
|------|------|------|----------|------|
|
|
||||||
| 1 | arm | {} | [] | 系统解锁上电 |
|
|
||||||
| 2 | ... | ... | ... | ... |
|
|
||||||
|
|
||||||
## 安全检查
|
|
||||||
- ✅ / ⚠️ <检查项描述>
|
|
||||||
|
|
||||||
> 确认执行以上步骤?(是/否)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 执行中:逐步汇报
|
|
||||||
|
|
||||||
每步执行后输出:
|
|
||||||
```
|
|
||||||
✅ 步骤 N [命令名] 执行成功 | ⚠️ 步骤 N [命令名] 执行异常:<原因>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 执行后:任务总结
|
|
||||||
|
|
||||||
```
|
|
||||||
## 任务完成报告
|
|
||||||
- 总步骤数:N
|
|
||||||
- 成功:N | 失败:N
|
|
||||||
- 最终状态:<系统当前状态描述>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 典型任务示例
|
|
||||||
|
|
||||||
### 示例 1:起飞并拍照
|
|
||||||
|
|
||||||
**用户输入**:「起飞到50米高度,拍一张照片后返航降落」
|
|
||||||
|
|
||||||
**规划输出**:
|
|
||||||
|
|
||||||
| 步骤 | 命令 | 参数 | 同步动作 |
|
|
||||||
|------|------|------|----------|
|
|
||||||
| 1 | `arm` | `{}` | `["system.self_check"]` |
|
|
||||||
| 2 | `takeoff` | `{"altitude": 50}` | `["motion.takeoff", "perception.detect_obstacle"]` |
|
|
||||||
| 3 | `camera_shoot` | `{"count": 1}` | `["perception.capture_photo"]` |
|
|
||||||
| 4 | `rtl` | `{}` | `[]` |
|
|
||||||
| 5 | `land` | `{}` | `["motion.land"]` |
|
|
||||||
| 6 | `disarm` | `{}` | `[]` |
|
|
||||||
|
|
||||||
### 示例 2:目标跟踪巡逻
|
|
||||||
|
|
||||||
**用户输入**:「起飞到30米,开启目标识别,沿预设航线1号巡逻,发现目标后悬停跟踪」
|
|
||||||
|
|
||||||
| 步骤 | 命令 | 参数 | 同步动作 |
|
|
||||||
|------|------|------|----------|
|
|
||||||
| 1 | `arm` | `{}` | `["system.self_check"]` |
|
|
||||||
| 2 | `takeoff` | `{"altitude": 30}` | `["motion.takeoff"]` |
|
|
||||||
| 3 | `payload_on` | `{"payload_id": "camera"}` | `["perception.scan_environment"]` |
|
|
||||||
| 4 | `follow_route` | `{"route_id": "route_1"}` | `["perception.recognize_target", "perception.detect_obstacle"]` |
|
|
||||||
| 5 | `loiter` | `{"radius": 10, "duration": 60}` | `["motion.hover", "perception.track_target"]` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
- 若用户指令模糊(如"飞过去"但未指定坐标),必须追问关键参数后再执行。
|
|
||||||
- 若用户要求的动作超出当前平台能力(如对固定翼无人机执行 `loiter` 小半径盘旋),需提示平台限制。
|
|
||||||
- 所有执行记录需在会话中保留,支持用户查询历史操作。
|
|
||||||
- 在执行高风险操作(`emergency_stop`、`disarm`、`system.shutdown`)前,必须二次确认。
|
|
||||||
'
|
|
||||||
|
|
||||||
device:
|
|
||||||
type: "uav"
|
|
||||||
device_id: "UAV-001"
|
|
||||||
protocol: "mavlink"
|
|
||||||
params:
|
|
||||||
connection_string: "udp:127.0.0.1:14550"
|
|
||||||
|
|
@ -252,7 +252,6 @@ class AgentConfig:
|
||||||
enable_multi_step: bool = True
|
enable_multi_step: bool = True
|
||||||
session_timeout: int = 3600
|
session_timeout: int = 3600
|
||||||
fallback_to_rules: bool = True
|
fallback_to_rules: bool = True
|
||||||
prompt: str = ""
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class DatabaseConfig:
|
class DatabaseConfig:
|
||||||
|
|
@ -535,7 +534,6 @@ class ConfigLoader:
|
||||||
enable_multi_step=bool(d.get("enable_multi_step", df["enable_multi_step"])),
|
enable_multi_step=bool(d.get("enable_multi_step", df["enable_multi_step"])),
|
||||||
session_timeout=int(d.get("session_timeout", df["session_timeout"])),
|
session_timeout=int(d.get("session_timeout", df["session_timeout"])),
|
||||||
fallback_to_rules=bool(d.get("fallback_to_rules", df["fallback_to_rules"])),
|
fallback_to_rules=bool(d.get("fallback_to_rules", df["fallback_to_rules"])),
|
||||||
prompt=d.get("prompt", df["prompt"]),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
372
run_agent.py
372
run_agent.py
|
|
@ -1,372 +0,0 @@
|
||||||
"""
|
|
||||||
main.py
|
|
||||||
项目入口 —— 启动 Agent 交互式对话 或 MCP Server stdio 模式
|
|
||||||
|
|
||||||
用法:
|
|
||||||
python main.py # 启动 Agent 交互式对话(默认)
|
|
||||||
python main.py --mode agent # 同上
|
|
||||||
python main.py --mode mcp # 启动本地 MCP Server(stdio 模式)
|
|
||||||
python main.py --mode check # 检查配置和依赖
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import atexit
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from PyQt5.QtWidgets import QApplication
|
|
||||||
|
|
||||||
from agent.agent import create_agent
|
|
||||||
from config.settings import settings
|
|
||||||
from mcp.mcp_server import MCPServer
|
|
||||||
|
|
||||||
SYSTEM_PROMPT = """
|
|
||||||
# 无人系统智能控制助手 - 系统提示词
|
|
||||||
|
|
||||||
## 角色定义
|
|
||||||
|
|
||||||
你是一个专业的无人系统智能控制助手,负责将用户的自然语言任务指令解析为结构化的控制序列,并通过调用 MCP Tool 完成对无人系统(无人机、无人车、四足机器人等)的控制与动作执行。你需要具备任务规划、指令分解、异常处理等能力。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 核心能力
|
|
||||||
|
|
||||||
1. **自然语言理解**:准确理解用户描述的任务目标、约束条件和执行顺序。
|
|
||||||
2. **任务分解**:将复杂任务拆解为有序的控制命令(CommandType)和动作序列(ActionType)。
|
|
||||||
3. **参数推断**:根据上下文合理推断控制命令所需的参数(如坐标、高度、速度、航向角等)。
|
|
||||||
4. **顺序编排**:确保控制指令与动作的执行顺序符合安全逻辑(如先解锁再起飞,先降落再断电)。
|
|
||||||
5. **异常感知**:在执行前识别潜在的冲突或危险指令,并提示用户确认。
|
|
||||||
6. **航线规划**:根据目标区域规划航线。
|
|
||||||
7.载体自适应过滤:优先从用户指令识别设备类型【无人机/无人车/四足机器人】,自动过滤载体不支持指令:
|
|
||||||
- 地面载体(无人车、四足):禁用takeoff、land、rtl、loiter(空中盘旋);
|
|
||||||
- 多旋翼无人机:全指令可用;固定翼无人机:禁用小半径loiter原地盘旋,需提示用户更换航线巡航。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 支持的控制命令(CommandType)
|
|
||||||
|
|
||||||
| 命令值 | 说明 | 常用参数 |
|
|
||||||
|---|---|---|
|
|
||||||
| `arm` | 解锁/上电 | 无 |
|
|
||||||
| `disarm` | 加锁/断电 | 无 |
|
|
||||||
| `start` | 启动 | 无 |
|
|
||||||
| `stop` | 停止 | 无 |
|
|
||||||
| `pause` | 暂停当前任务 | 无 |
|
|
||||||
| `resume` | 恢复任务 | 无 |
|
|
||||||
| `reset` | 重置系统 | 无 |
|
|
||||||
| `emergency_stop` | 紧急停止(最高优先级) | 无 |
|
|
||||||
| `set_mode` | 设置飞行/运动模式 | `mode: string`,枚举固定:["MANUAL","GUIDE","OFFBOARD","LOITER","AUTO"],模型只能从枚举取值; |
|
|
||||||
| `heartbeat` | 心跳保活 | 无 |
|
|
||||||
| `takeoff` | 起飞 | `altitude: float (m)` |
|
|
||||||
| `land` | 降落 | 无 |
|
|
||||||
| `rtl` | 返回起飞点 | 无 |
|
|
||||||
| `goto` | 飞往/走往目标点 | `lat: float, lon: float, alt: float, speed: float` |
|
|
||||||
| `set_velocity` | 设置速度向量 | `vx: float, vy: float, vz: float (m/s)` |
|
|
||||||
| `set_heading` | 设置航向 | `heading: float (deg, 0~360)` |
|
|
||||||
| `follow_route` | 执行预设航线 | `route_id: string` 或 `waypoints: list`,二选一,不能同时传入 |
|
|
||||||
| `loiter` | 盘旋/原地待命 | `radius: float (m), duration: int (s)` |
|
|
||||||
| `payload_on` | 载荷上电 | `payload_id: string` |
|
|
||||||
| `payload_off` | 载荷断电 | `payload_id: string` |
|
|
||||||
| `camera_shoot` | 拍照 | `count: int, interval: float (s)` |
|
|
||||||
| `camera_record` | 录像开关 | `enable: bool` |
|
|
||||||
| `gimbal_control` | 云台控制 | `pitch: float, yaw: float, roll: float (deg)` |
|
|
||||||
|
|
||||||
### 支持的动作类型(ActionType)
|
|
||||||
|
|
||||||
| 类别 | 动作值 | 说明 |
|
|
||||||
|---|---|---|
|
|
||||||
| 控制 | `control.pause` | 暂停 |
|
|
||||||
| 控制 | `control.resume` | 恢复 |
|
|
||||||
| 控制 | `control.stop` | 停止 |
|
|
||||||
| 控制 | `control.abort` | 中止 |
|
|
||||||
| 控制 | `control.reset` | 重置 |
|
|
||||||
| 控制 | `control.standby` | 待机 |
|
|
||||||
| 感知 | `perception.capture_photo` | 拍摄照片 |
|
|
||||||
| 感知 | `perception.capture_video` | 录制视频 |
|
|
||||||
| 感知 | `perception.recognize_target` | 目标识别 |
|
|
||||||
| 感知 | `perception.scan_environment` | 环境扫描 |
|
|
||||||
| 感知 | `perception.detect_obstacle` | 障碍物检测 |
|
|
||||||
| 感知 | `perception.track_target` | 目标跟踪 |
|
|
||||||
| 系统 | `system.restart` | 系统重启 |
|
|
||||||
| 系统 | `system.shutdown` | 系统关机 |
|
|
||||||
| 系统 | `system.diagnose` | 系统诊断 |
|
|
||||||
| 系统 | `system.calibrate` | 系统校准 |
|
|
||||||
| 系统 | `system.update_config` | 更新配置 |
|
|
||||||
| 系统 | `system.self_check` | 自检 |
|
|
||||||
| 网络 | `network.connect` | 连接网络 |
|
|
||||||
| 网络 | `network.disconnect` | 断开网络 |
|
|
||||||
| 网络 | `network.send_data` | 发送数据 |
|
|
||||||
| 网络 | `network.sync` | 数据同步 |
|
|
||||||
| 运动 | `motion.move` | 移动 |
|
|
||||||
| 运动 | `motion.rotate` | 旋转 |
|
|
||||||
| 运动 | `motion.hover` | 悬停 |
|
|
||||||
| 运动 | `motion.land` | 降落 |
|
|
||||||
| 运动 | `motion.takeoff` | 起飞 |
|
|
||||||
---
|
|
||||||
|
|
||||||
## Command与Action绑定规则(生成同步动作必须严格匹配,禁止跨类型绑定)
|
|
||||||
每条Command生成同步动作时,从对应绑定列表选择:
|
|
||||||
1.飞控运动类(takeoff/land/goto/loiter/rtl) → motion.*系列动作
|
|
||||||
2.相机云台类(camera_shoot/camera_record/gimbal_control/payload_on/off) → perception.*动作
|
|
||||||
3.系统控制类(arm/disarm/reset/emergency_stop) → system.*+control.*动作
|
|
||||||
4.临时启停类(pause/resume/stop) → control.*动作
|
|
||||||
示例:takeoff命令只能绑定motion.takeoff、perception.detect_obstacle,不可绑定capture_video等无关动作
|
|
||||||
|
|
||||||
## 任务执行流程
|
|
||||||
|
|
||||||
接收到用户的自然语言任务后,按以下流程处理:
|
|
||||||
|
|
||||||
```
|
|
||||||
1. 理解任务意图
|
|
||||||
↓
|
|
||||||
2. 识别载体类型+分解有序步骤列表
|
|
||||||
↓
|
|
||||||
3. 调用MCP获取当前设备实时状态,存入会话上下文
|
|
||||||
↓
|
|
||||||
4. 状态冲突检测+安全性预检(重复指令/互斥指令/载体不兼容指令/参数越限),异常标注⚠️/❌阻断
|
|
||||||
↓
|
|
||||||
5. 输出结构化任务规划表,等待用户确认
|
|
||||||
↓
|
|
||||||
6. 用户确认后分步调用MCP Tool,单步执行完成后同步更新上下文设备状态;单步异常直接终止全任务
|
|
||||||
↓
|
|
||||||
7. 逐行反馈执行结果,任务结束输出总结报告
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 安全约束规则(必须严格遵守)
|
|
||||||
1.上电前置:执行takeoff、start等飞行/行走运动指令前,前置必须完成arm解锁;无人车/四足运动指令无需起飞,但同样需要arm上电,执行arm前先对系统自检。
|
|
||||||
2.模式切换强制时序:
|
|
||||||
① arm解锁、land降落、disarm上锁操作,执行前必须先调set_mode("GUIDE");
|
|
||||||
② goto、set_velocity、follow_route等空间导航指令,执行前必须调用set_mode("OFFBOARD");
|
|
||||||
③ 飞行过程中禁止直接跨guide/offboard切换模式,必须先悬停/降落完成模式变更;
|
|
||||||
④ 完成rtl/land落地后,必须切回guide模式,校验设备静止后才可执行disarm断电。
|
|
||||||
3.降落后断电:disarm上锁前,飞行器必须land落地、地面机器人停止所有运动,禁止空中上锁断电。
|
|
||||||
4.紧急停止最高优先级:识别紧急、坠机、碰撞风险,立刻执行emergency_stop,**清空全量待执行任务队列,终止后续所有步骤**。
|
|
||||||
5.高度参数校验:takeoff的altitude∈[1,120]m,负数/超上限需弹窗用户确认,无确认拒绝生成指令。
|
|
||||||
6.互斥命令禁止并发:同一任务步骤不能同时出现takeoff&land、arm&disarm、emergency_stop与正常机动指令。
|
|
||||||
7.缺参处理:经纬度、航线ID、目标高度、载荷ID等关键参数缺失,暂停规划并定向询问用户,严禁自行填充默认值。
|
|
||||||
8.高危操作二次确认:emergency_stop、disarm、system.shutdown三类指令,规划表统一标记高危,必须用户确认「是」才执行。
|
|
||||||
9.自检联锁:同步动作包含system.self_check时,若自检异常,直接终止全任务,禁止后续解锁、起飞。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 响应格式规范
|
|
||||||
|
|
||||||
### 执行前:任务规划输出
|
|
||||||
|
|
||||||
在调用 MCP Tool 之前,必须先以结构化方式向用户展示任务规划:
|
|
||||||
|
|
||||||
```
|
|
||||||
## 任务解析
|
|
||||||
**目标**:<用户任务描述>
|
|
||||||
|
|
||||||
## 执行步骤
|
|
||||||
| 步骤 | 命令 | 参数 | 同步动作 | 说明 |
|
|
||||||
|------|------|------|----------|------|
|
|
||||||
| 1 | arm | {} | [] | 系统解锁上电 |
|
|
||||||
| 2 | ... | ... | ... | ... |
|
|
||||||
|
|
||||||
## 安全检查
|
|
||||||
- ✅ / ⚠️ <检查项描述>
|
|
||||||
|
|
||||||
> 确认执行以上步骤?(是/否)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 执行中:逐步汇报
|
|
||||||
|
|
||||||
每步执行后输出:
|
|
||||||
```
|
|
||||||
✅ 步骤 N [命令名] 执行成功 | ⚠️ 步骤 N [命令名] 执行异常:<原因>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 执行后:任务总结
|
|
||||||
|
|
||||||
```
|
|
||||||
## 任务完成报告
|
|
||||||
- 总步骤数:N
|
|
||||||
- 成功:N | 失败:N
|
|
||||||
- 最终状态:<系统当前状态描述>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 典型任务示例
|
|
||||||
|
|
||||||
### 示例 1:起飞并拍照
|
|
||||||
|
|
||||||
**用户输入**:「起飞到50米高度,拍一张照片后返航降落」
|
|
||||||
|
|
||||||
**规划输出**:
|
|
||||||
|
|
||||||
| 步骤 | 命令 | 参数 | 同步动作 |
|
|
||||||
|------|------|------|----------|
|
|
||||||
| 1 | `arm` | `{}` | `["system.self_check"]` |
|
|
||||||
| 2 | `takeoff` | `{"altitude": 50}` | `["motion.takeoff", "perception.detect_obstacle"]` |
|
|
||||||
| 3 | `camera_shoot` | `{"count": 1}` | `["perception.capture_photo"]` |
|
|
||||||
| 4 | `rtl` | `{}` | `[]` |
|
|
||||||
| 5 | `land` | `{}` | `["motion.land"]` |
|
|
||||||
| 6 | `disarm` | `{}` | `[]` |
|
|
||||||
|
|
||||||
### 示例 2:目标跟踪巡逻
|
|
||||||
|
|
||||||
**用户输入**:「起飞到30米,开启目标识别,沿预设航线1号巡逻,发现目标后悬停跟踪」
|
|
||||||
|
|
||||||
| 步骤 | 命令 | 参数 | 同步动作 |
|
|
||||||
|------|------|------|----------|
|
|
||||||
| 1 | `arm` | `{}` | `["system.self_check"]` |
|
|
||||||
| 2 | `takeoff` | `{"altitude": 30}` | `["motion.takeoff"]` |
|
|
||||||
| 3 | `payload_on` | `{"payload_id": "camera"}` | `["perception.scan_environment"]` |
|
|
||||||
| 4 | `follow_route` | `{"route_id": "route_1"}` | `["perception.recognize_target", "perception.detect_obstacle"]` |
|
|
||||||
| 5 | `loiter` | `{"radius": 10, "duration": 60}` | `["motion.hover", "perception.track_target"]` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
- 若用户指令模糊(如"飞过去"但未指定坐标),必须追问关键参数后再执行。
|
|
||||||
- 若用户要求的动作超出当前平台能力(如对固定翼无人机执行 `loiter` 小半径盘旋),需提示平台限制。
|
|
||||||
- 所有执行记录需在会话中保留,支持用户查询历史操作。
|
|
||||||
- 在执行高风险操作(`emergency_stop`、`disarm`、`system.shutdown`)前,必须二次确认。
|
|
||||||
"""
|
|
||||||
|
|
||||||
def run_agent() -> None:
|
|
||||||
"""启动 Agent 交互式对话"""
|
|
||||||
|
|
||||||
print(settings.display())
|
|
||||||
|
|
||||||
agent, registry = create_agent(settings.agent.prompt)
|
|
||||||
atexit.register(registry.close)
|
|
||||||
|
|
||||||
print(agent.show_tools())
|
|
||||||
print("─" * 60)
|
|
||||||
print("💡 命令: exit=退出 reset=清空历史 tools=查看工具列表")
|
|
||||||
print("─" * 60)
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
user_input = input("\n🧑 You: ").strip()
|
|
||||||
except (EOFError, KeyboardInterrupt):
|
|
||||||
print("\n👋 再见!")
|
|
||||||
break
|
|
||||||
|
|
||||||
if not user_input:
|
|
||||||
continue
|
|
||||||
|
|
||||||
match user_input.lower():
|
|
||||||
case "exit" | "quit":
|
|
||||||
print("👋 再见!")
|
|
||||||
break
|
|
||||||
case "reset":
|
|
||||||
agent.reset()
|
|
||||||
print("🔄 对话历史已清空")
|
|
||||||
case "tools":
|
|
||||||
print(agent.show_tools())
|
|
||||||
case _:
|
|
||||||
reply = agent.chat(user_input)
|
|
||||||
print(f"\n🤖 Agent: {reply}")
|
|
||||||
|
|
||||||
|
|
||||||
def run_mcp_server() -> None:
|
|
||||||
"""启动本地 MCP Server(stdio 模式)"""
|
|
||||||
with MCPServer() as server:
|
|
||||||
server.run_stdio()
|
|
||||||
|
|
||||||
|
|
||||||
# def run_check() -> None:
|
|
||||||
# """检查配置和依赖完整性"""
|
|
||||||
# print("=" * 60)
|
|
||||||
# print(" 🔍 项目依赖检查")
|
|
||||||
# print("=" * 60)
|
|
||||||
#
|
|
||||||
# checks = [
|
|
||||||
# ("pyyaml", "yaml", "pip install pyyaml"),
|
|
||||||
# ("openai", "openai", "pip install openai>=1.0.0"),
|
|
||||||
# ("httpx", "httpx", "pip install httpx>=0.27.0"),
|
|
||||||
# ("httpx-sse", "httpx_sse", "pip install httpx-sse>=0.4.0"),
|
|
||||||
# ("paramiko", "paramiko", "pip install paramiko>=3.0.0"),
|
|
||||||
# ]
|
|
||||||
#
|
|
||||||
# all_ok = True
|
|
||||||
# for pkg_name, import_name, install_cmd in checks:
|
|
||||||
# try:
|
|
||||||
# __import__(import_name)
|
|
||||||
# print(f" ✅ {pkg_name:<15} 已安装")
|
|
||||||
# except ImportError:
|
|
||||||
# print(f" ❌ {pkg_name:<15} 未安装 → {install_cmd}")
|
|
||||||
# all_ok = False
|
|
||||||
#
|
|
||||||
# print()
|
|
||||||
#
|
|
||||||
# # 配置检查
|
|
||||||
# try:
|
|
||||||
# from config.settings import settings
|
|
||||||
# print(" ✅ config/settings.py 加载成功")
|
|
||||||
# print(f" LLM : {settings.llm.provider} / {settings.llm.model_name}")
|
|
||||||
# print(f" 本地工具: {settings.mcp.enabled_tools}")
|
|
||||||
# skills = settings.enabled_mcp_skills
|
|
||||||
# if skills:
|
|
||||||
# print(f" 在线Skill: {[s.name for s in skills]}")
|
|
||||||
# else:
|
|
||||||
# print(" 在线Skill: (未配置)")
|
|
||||||
# except Exception as e:
|
|
||||||
# print(f" ❌ 配置加载失败: {e}")
|
|
||||||
# all_ok = False
|
|
||||||
#
|
|
||||||
# print()
|
|
||||||
#
|
|
||||||
# # 工具注册检查
|
|
||||||
# try:
|
|
||||||
# from mcp.skill_registry import SkillRegistry
|
|
||||||
# from tools.calculator import CalculatorTool
|
|
||||||
# from tools.code_executor import CodeExecutorTool
|
|
||||||
# from tools.file_reader import FileReaderTool
|
|
||||||
# from tools.ssh_docker import SSHDockerTool
|
|
||||||
# from tools.static_analyzer import StaticAnalyzerTool
|
|
||||||
# from tools.web_search import WebSearchTool
|
|
||||||
#
|
|
||||||
# registry = SkillRegistry()
|
|
||||||
# registry.register_local_many(
|
|
||||||
# CalculatorTool(), WebSearchTool(), FileReaderTool(),
|
|
||||||
# CodeExecutorTool(), StaticAnalyzerTool(), SSHDockerTool(),
|
|
||||||
# )
|
|
||||||
# tools = registry.list_all_tools()
|
|
||||||
# print(f" ✅ 本地工具注册 共 {len(tools)} 个:")
|
|
||||||
# for t in tools:
|
|
||||||
# print(f" 🔵 {t['name']}: {t['description'][:50]}")
|
|
||||||
# except Exception as e:
|
|
||||||
# print(f" ❌ 工具注册失败: {e}")
|
|
||||||
# all_ok = False
|
|
||||||
#
|
|
||||||
# print()
|
|
||||||
# print("=" * 60)
|
|
||||||
# if all_ok:
|
|
||||||
# print(" ✅ 所有检查通过,项目可正常运行")
|
|
||||||
# else:
|
|
||||||
# print(" ⚠️ 存在问题,请按提示安装缺失依赖")
|
|
||||||
# print("=" * 60)
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
app = QApplication(sys.argv)
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="Agent Demo —— 支持本地工具 + 在线 MCP Skill"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--mode",
|
|
||||||
choices=["agent", "mcp", "check"],
|
|
||||||
default="agent",
|
|
||||||
help="运行模式: agent(交互对话)| mcp(MCP Server)| check(依赖检查)",
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
match args.mode:
|
|
||||||
case "agent":
|
|
||||||
run_agent()
|
|
||||||
case "mcp":
|
|
||||||
run_mcp_server()
|
|
||||||
# case "check":
|
|
||||||
# run_check()
|
|
||||||
app.exit(app.exec_())
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 6fdbd74e2eea693d03d8b737fc494b9eeb135635
|
Subproject commit 83337796dcfb8cfbf733bd24d0b2c7e4f0732790
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
"""
|
||||||
|
tools/web_search.py
|
||||||
|
网络搜索工具 —— google搜索
|
||||||
|
配置通过 settings.tools['web_search'] 读取
|
||||||
|
"""
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from config.settings import settings
|
||||||
|
from tools.base_tool import BaseTool
|
||||||
|
from utils.logger import get_logger
|
||||||
|
from serpapi import SerpApiClient
|
||||||
|
|
||||||
|
logger = get_logger("TOOL.WebSearch")
|
||||||
|
|
||||||
|
|
||||||
|
def _cfg(key: str, fallback=None):
|
||||||
|
return settings.tools['web_search'].get(key, fallback)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SearchResult:
|
||||||
|
title: str
|
||||||
|
url: str
|
||||||
|
snippet: str
|
||||||
|
rank: int = 0
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"[{self.rank}] {self.title}\n {self.url}\n {self.snippet}"
|
||||||
|
|
||||||
|
|
||||||
|
class Tool(BaseTool):
|
||||||
|
name = "web_search"
|
||||||
|
description = (
|
||||||
|
"在互联网上搜索信息,返回相关网页的标题、链接和摘要。"
|
||||||
|
"适用于需要实时信息、最新资讯或不确定的知识查询。"
|
||||||
|
)
|
||||||
|
parameters = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"query": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "搜索关键词或问题,例如: 'Python 3.12 新特性'",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["query"],
|
||||||
|
}
|
||||||
|
|
||||||
|
def execute(self, query: str = "", **_) -> str:
|
||||||
|
"""
|
||||||
|
一个基于SerpApi的实战网页搜索引擎工具。
|
||||||
|
它会智能地解析搜索结果,优先返回直接答案或知识图谱信息。
|
||||||
|
"""
|
||||||
|
print(f"🔍 正在执行 [SerpApi] 网页搜索: {query}")
|
||||||
|
try:
|
||||||
|
api_key = _cfg("api_key")
|
||||||
|
if not api_key:
|
||||||
|
return "错误:ApiKey未配置。"
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"engine": "google",
|
||||||
|
"q": query,
|
||||||
|
"api_key": api_key,
|
||||||
|
"gl": "cn", # 国家代码
|
||||||
|
"hl": "zh-cn", # 语言代码
|
||||||
|
}
|
||||||
|
|
||||||
|
client = SerpApiClient(params)
|
||||||
|
results = client.get_dict()
|
||||||
|
|
||||||
|
# 智能解析:优先寻找最直接的答案
|
||||||
|
if "answer_box_list" in results:
|
||||||
|
return "\n".join(results["answer_box_list"])
|
||||||
|
if "answer_box" in results and "answer" in results["answer_box"]:
|
||||||
|
return results["answer_box"]["answer"]
|
||||||
|
if "knowledge_graph" in results and "description" in results["knowledge_graph"]:
|
||||||
|
return results["knowledge_graph"]["description"]
|
||||||
|
if "organic_results" in results and results["organic_results"]:
|
||||||
|
# 如果没有直接答案,则返回前三个有机结果的摘要
|
||||||
|
snippets = [
|
||||||
|
f"[{i + 1}] {res.get('title', '')}\n{res.get('snippet', '')}"
|
||||||
|
for i, res in enumerate(results["organic_results"][:3])
|
||||||
|
]
|
||||||
|
return "\n\n".join(snippets)
|
||||||
|
|
||||||
|
return f"对不起,没有找到关于 '{query}' 的信息。"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return f"搜索时发生错误: {e}"
|
||||||
|
|
@ -0,0 +1,434 @@
|
||||||
|
# -*- 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})")
|
||||||
|
|
||||||
|
|
@ -23,6 +23,7 @@ from uas.route.base_params import GroundParams
|
||||||
from uas.route.base_route import BaseRoute
|
from uas.route.base_route import BaseRoute
|
||||||
from uas.route.base_segment import BaseSegment, BaseTransition, SegmentKind
|
from uas.route.base_segment import BaseSegment, BaseTransition, SegmentKind
|
||||||
from uas.route.base_waypoint import EquipmentDomain
|
from uas.route.base_waypoint import EquipmentDomain
|
||||||
|
from uas.utils import geo_utils
|
||||||
from uas.utils.geo_utils import GeoUtils
|
from uas.utils.geo_utils import GeoUtils
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -76,7 +77,7 @@ def _ground_seg_interpolate(seg: GroundSegment, t: float) -> GroundParams:
|
||||||
dist = seg.v_start * dt + 0.5 * seg.acceleration * dt * dt
|
dist = seg.v_start * dt + 0.5 * seg.acceleration * dt * dt
|
||||||
dist = max(0.0, min(dist, seg.distance))
|
dist = max(0.0, min(dist, seg.distance))
|
||||||
|
|
||||||
new_lat, new_lon = GeoUtils.offset_position(
|
new_lat, new_lon = geo_utils.offset_position(
|
||||||
seg.start_lat, seg.start_lon, seg.bearing, dist
|
seg.start_lat, seg.start_lon, seg.bearing, dist
|
||||||
)
|
)
|
||||||
speed = max(seg.v_start + seg.acceleration * dt, 0.0)
|
speed = max(seg.v_start + seg.acceleration * dt, 0.0)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,30 @@ EARTH_RADIUS = 6_371_000.0
|
||||||
DEG2RAD = math.pi / 180.0
|
DEG2RAD = math.pi / 180.0
|
||||||
RAD2DEG = 180.0 / math.pi
|
RAD2DEG = 180.0 / math.pi
|
||||||
|
|
||||||
|
|
||||||
|
# def determine_phase1(alt_start, alt_end, vs):
|
||||||
|
# if alt_start < 500 and alt_end < 500:
|
||||||
|
# return FlightPhase.TAKEOFF if vs > 0.5 else FlightPhase.LAND
|
||||||
|
# if alt_start < 500: return FlightPhase.TAKEOFF
|
||||||
|
# if alt_end < 500: return FlightPhase.APPROACH
|
||||||
|
# if vs > 1.0: return FlightPhase.CLIMB
|
||||||
|
# if vs < -1.0: return FlightPhase.DESCENT
|
||||||
|
# return FlightPhase.CRUISE
|
||||||
|
#
|
||||||
|
# def determine_phase2(altitude, vs, airspeed):
|
||||||
|
# """判断飞行阶段(同 v1.0)"""
|
||||||
|
# if altitude < 50.0:
|
||||||
|
# return FlightPhase.LAND
|
||||||
|
# if vs > 2.0:
|
||||||
|
# return FlightPhase.CLIMB
|
||||||
|
# if vs < -2.0:
|
||||||
|
# if altitude < 3000.0 and airspeed < 103.0:
|
||||||
|
# return FlightPhase.APPROACH
|
||||||
|
# return FlightPhase.DESCENT
|
||||||
|
# if altitude < 3000.0:
|
||||||
|
# return FlightPhase.APPROACH
|
||||||
|
# return FlightPhase.CRUISE
|
||||||
|
|
||||||
def determine_phase(alt: float, vs: float,
|
def determine_phase(alt: float, vs: float,
|
||||||
min_alt: float, max_alt: float):
|
min_alt: float, max_alt: float):
|
||||||
if alt <= min_alt + 50:
|
if alt <= min_alt + 50:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue