1. 驾驶舱
插件化手册
  • FH2 插件开发手册
    • 产品简介
    • 典型场景
    • 发版说明
    • 快速开始
      • 快速开始总览
      • 第一个插件
      • 后端插件
      • 全栈闭环
    • AI辅助开发
      • AI 开发 Skill
      • 安装与复验
      • 第一个工程
      • 真实需求示例:现场问题上报
      • 改造已有工程
      • 调试与交付
    • 开发指南
      • 开发指南
      • 基础概念
        • 基础概念总览
        • 什么是插件开发
        • 平台架构
        • 前端宿主、扩展点与能力
        • 后端运行时、入口与权限
        • 开发流程
        • 安全与隔离
        • 支持范围与问题反馈
      • 开发指南
        • 开发指南总览
        • 前端插件
          • 前端插件深度指南
          • 前端工程结构
          • 插件清单(fh2-plugin.json)
          • 入口与生命周期
          • 界面与状态
          • 多宿主插件
          • 跨宿主通信服务
          • HTTP、WebSocket 与事件
          • 存储、Blob 与定时器
          • 工具链与本地调试页面
        • 后端插件
          • 后端开发深度指南
          • 后端插件的结构
          • 后端源清单 fh2-plugin.json
          • HTTP 接口处理函数
          • 消息订阅与事件处理函数
          • ctx 与能力注入
          • 数据存储与缓存
          • 流程编排
          • 触发方式
          • 连接外部系统
          • 流程编排练习(DAG)
        • 前后端协作
          • 前后端协作总览
          • 前后端共享契约
          • 端到端幂等
          • 事件、通知与状态
          • 统一错误模型
          • 链路标识与诊断日志
        • 完整练习
          • 环境准备
          • 第一个前端插件
          • 第一个后端插件
          • 第一个全栈插件
      • 能力总览
        • 能力地图
        • 能力可用性矩阵
        • 前端能力
          • 平台通用能力
            • 平台通用能力总览
            • 基础运行时:HTTP、事件、WebSocket 与存储
            • 导航、位置与通知
            • 弹窗、音频与剪贴板
            • 地图能力
            • 项目设备能力
            • WPMZ 与航线预览
            • 受控对象上传
          • 项目宿主
            • 项目宿主总览
            • 项目上下文
            • 项目侧边栏入口
            • 项目插件路由
            • 项目工作区
          • 消息宿主
            • 消息宿主总览
            • 消息告警弹层
            • 消息导航与通知
          • 计划库宿主
            • 计划库宿主总览
            • 计划库表单上下文
            • 计划库表单扩展
            • 校验、提交与 `biz_ext`
          • 驾驶舱
            • 驾驶舱插件能力总览
            • 驾驶舱扩展点
            • 上下文与遥测
            • 相机、检测与 AI
            • 场景几何与直播指针
            • 飞控、云台与任务
            • 驾驶舱平台事件
        • 后端能力
          • 后端能力地图
          • 后端能力总览
          • 配置、受管出网与集成连接
          • 事件与流程
          • 飞行任务与违停巡逻
          • 数据存储
          • 媒体与前端通知
          • 资源租约
          • 作业、目标轨迹与视频处理
      • 调试与测试
        • 调试与测试总览
        • 快速入门排错
        • 本地开发
        • Web IDE:打开工程与保存改动
        • 工作流编辑器:节点、接线与交付
        • 知识与 MCP:先判断需要哪一层事实
        • 前端本地调试
        • 后端运行时与流程调试
        • 模拟数据与测试数据
        • 集成测试与 E2E
        • 日志与链路追踪
        • 常见问题排查
      • 完整示例
        • 示例与案例总览
        • 项目侧边栏与工作区
        • 消息告警卡片
        • 计划表单任务清单
        • 驾驶舱动作与左侧面板
        • 最小后端插件
        • 最小全栈插件
        • 多宿主插件实战
        • 事故快处
        • 交通抓拍
        • 违停巡逻完整案例
      • 构建发布与安全
        • 构建、发布与安全
        • 质量门禁
        • 行业包构建与打包
        • 能力版本发布
        • 安装、升级与回滚
        • 安全基线
      • API参考
        • API 参考总览
        • 前端
          • 前端 API 参考
          • 前端插件清单结构
          • 前端 SDK API
          • 前端扩展点目录
          • 前端能力目录
          • 前端平台事件目录
          • 跨宿主通信服务 API
          • 工具链命令行参考(`fh2-plugin`)
        • 后端
          • 后端 API 参考
          • 后端插件源清单
          • 后端 HTTP 接口参考
          • 后端事件消费参考
          • 后端脚本上下文(`ctx`)
          • 后端数据存储
          • 流程编排语言
          • 流程触发器参考
          • 后端能力目录
          • 后端 HTTP API
        • 共享契约
          • 共享契约参考
          • 类型与契约目录
          • 错误模型
          • 版本兼容性
      • 附录
        • 错误码
        • 术语表
        • 常见问题
  1. 驾驶舱

相机、检测与 AI

这一页覆盖 4 个能力:cockpit:camera(拍照录像截帧)、cockpit:detection(AI 检测帧)、cockpit:ai-config(配置识别模型)、cockpit:smart-track(智能跟踪)。它们都要求在清单里声明同名权限,获取后判空使用。
⚠️ 相机和 AI 类命令由宿主串行执行,以避免并发命令误读设备状态。Promise 返回可能表示完成或确认,具体语义以各方法说明为准。

cockpit:camera:拍照、录像、截帧#

状态#

interface CameraState {
  availability: { status: 'available' | 'unavailable' | 'unsupported'; reason: string | null }
  mode: 'photo' | 'record' | null
  lens: 'wide' | 'zoom' | null
  zoom: number | null
  recording: boolean
}
availability.status !== 'available' 时其余字段为空,先处理不可用分支。

方法与完成条件#

ensureMode(mode: 'photo' | 'record'): Promise<void>
switchLens(lens: 'wide' | 'zoom'): Promise<void>
ensureZoom(factor: number): Promise<void>
boundingZoom(rect: { x: number; y: number; width: number; height: number }): Promise<void>
takePhoto(): Promise<{ accepted: boolean; capturedAt: number }>
acquireRecording({ takeover?: boolean }): Promise<CameraRecordingHandle>
captureFrame({ scale, mime, quality?, requireGeometry? }): Promise<{
  blob: Blob; width: number; height: number; capturedAt: number; geometrySnapshotId: string | null
}>
每个方法"resolve"的含义不一样,这是最容易写错的地方:
方法resolve 代表超时
ensureMode设备 camera_mode 已到达目标模式(已是目标模式则直接返回)3s
switchLens设备镜头已切换3s
ensureZoom变焦误差小于 0.1(已满足则直接返回)3s
boundingZoom仅 SDK 调用返回,不等变焦终态SDK 决定
takePhoto已观察到设备完整走完拍照状态(photo_state: 1 -> 0);不代表媒体已上传3s
acquireRecording录像已启动并确认(recording_state=1);handle.stop() 等到录像停止5s
captureFrame拿到当前直播画面的一帧 Blob;这不是设备拍照SDK 决定

录像的所有权模型#

录像句柄是"独占"的,规则要记牢:
设备已在录像且你没传 takeover: true → 抛 COCKPIT_SDK_ERROR。
新句柄接管后,旧句柄收到 superseded 并立即失效——旧句柄再调 stop() 不影响设备。
设备自己停止录像 → 当前句柄收到 device-state-changed。
句柄 dispose() 通常不停止设备录像,只是放弃所有权。

截帧与几何快照#

captureFrame 默认 requireGeometry: true:宿主会在截帧前冻结一份几何快照(用于之后做"同帧投影",见 场景几何与直播指针),快照 ID 放在返回的 geometrySnapshotId。取不到几何时直接抛 CAPABILITY_NOT_AVAILABLE,不截帧。设为 false 可以拿纯截图(geometrySnapshotId 为 null)。
快照按插件隔离,全局最多 64 个,插件运行时结束自动回收;用完要主动 releaseSnapshot()。

示例#

const camera = await ctx.capabilities.get('cockpit:camera')
if (!camera || (await camera.getState()).availability.status !== 'available') return

await camera.ensureZoom(2)
const photo = await camera.takePhoto()          // 设备完成拍照
ctx.logger.info('设备拍照完成', photo)

const frame = await camera.captureFrame({ scale: 1, mime: 'image/jpeg', quality: 0.9 })
// frame.geometrySnapshotId 可交给 cockpit:scene-geometry 做同帧投影,用完释放

cockpit:detection:AI 检测帧流#

帧结构#

interface DetectionFrame {
  frameId: string
  capturedAt: number              // 来自 AI SEI,不是浏览器时钟
  frameTimestampUs: number | null
  geometrySnapshotId: string | null
  targets: readonly {
    targetId: string
    type: string
    state: 'tracked' | 'vision-beacon-fused' | 'auxiliary-tracked' |
      'not-confident' | 'lost-predicting' | 'lost' | 'redetected' | 'invalid' | 'unknown'
    bbox: { x: number; y: number; width: number; height: number }   // 有效画幅内归一化坐标
  }[]
}
宿主会把设备原生的中心点框转成左上角 x/y/width/height,目标 ID/type 转成字符串;固件返回未知状态时映射为 unknown。

订阅频率规则#

onFrame(handler, { maxFps?: 1 | 2 | 5 | 10 | 30 }): Disposable
运行在隔离环境(iframe 沙箱)里的插件:最高 10fps,上一帧没处理完时只保留最新帧。
请求超过当前上限会抛 CAPABILITY_RATE_UNSUPPORTED,不会静默降频。

帧坐标投影#

每帧自带一个宿主私有几何快照(最多保留 2 秒、64 个)。用 frameId 把画面坐标投到地面(或反向):
projectToGround(frameId, points, { groundHeight }): Promise<{ capturedAt; points } | null>
projectFromGround(frameId, points): Promise<{ capturedAt; points } | null>
快照过期或不存在的帧返回 null;一批点里个别点投不出去时,该位置返回 null,不影响其他点。

示例:跟踪检测目标的地面位置#

const detection = await ctx.capabilities.get('cockpit:detection')
if (!detection) return

const off = await detection.onFrame(async (frame) => {
  const target = frame.targets.find(item => item.state === 'tracked')
  if (!target) return
  const center = { x: target.bbox.x + target.bbox.width / 2, y: target.bbox.y + target.bbox.height / 2 }
  const projected = await detection.projectToGround(frame.frameId, [center], { groundHeight: 0 })
  if (projected?.points[0]) ctx.logger.debug('同帧目标位置', projected.points[0])
}, { maxFps: 10 })
注意:检测帧是机载 AI 的逐帧输出流,和"把截图传给后端识别"是两回事。

cockpit:ai-config:配置识别模型与目标#

状态与方法#

interface AiConfigState {
  availability: CapabilityAvailability
  model: string | null
  recognitionEnabled: boolean
  targetTypes: readonly string[]
}

ensure({
  model?: 'dji-visible'
  recognitionEnabled?: boolean
  targetTypes?: readonly string[]
}): Promise<{ confirmed: boolean; reason: string | null }>
当前只支持 visible 槽的 dji-visible 模型。目标类型的语义映射:person=2、vehicle=3、boat=4、moving-target=34;⚠️ visible 链路明确拒绝 moving-target(它只适用于红外)。

ensure 的行为#

ensure() 按"选模型 → 开关识别 → 替换目标类型全集"串行执行,每步最多等 5 秒权威状态;状态已满足就不重复下发。targetTypes: [] 表示清空全部类型,不等于关闭识别。
返回 { confirmed: false, reason } 表示业务前提不满足(比如 visible 用了 moving-target、识别没开就配类型),这是正常返回不是异常;SDK 错误、超时、宿主销毁才走异常。

示例#

const ai = await ctx.capabilities.get('cockpit:ai-config')
if (!ai) return
const result = await ai.ensure({
  model: 'dji-visible',
  recognitionEnabled: true,
  targetTypes: ['person', 'vehicle'],
})
if (!result.confirmed) ctx.logger.warn(result.reason ?? 'AI 配置未确认')

cockpit:smart-track:智能跟踪#

状态与方法#

interface SmartTrackState {
  availability: CapabilityAvailability
  phase: 'off' | 'selecting' | 'confirming' | 'tracking' |
    'lost' | 'stopping' | 'error' | 'unknown'
  targetId: string | null
  exitReason: string | null
}

start(target:
  | { kind: 'detection-target'; targetId: string }
  | { kind: 'rect'; bbox: { x: number; y: number; width: number; height: number } }
): Promise<SmartTrackState>
stop(): Promise<SmartTrackState>
start() 两种用法:
从检测目标接续:传 detection-target + 目标 ID(必须是无符号整数的规范字符串)。下发后要求连续稳定 tracking 1 秒才算成功(总等待 5.5 秒);目标短暂丢失时会在最新检测帧里选同类型、框最大的目标接力(最多重试 3 次)。
从画框开始:传 rect + 归一化框,走 selectRect → confirm → tracking。
stop() 会先呈现 stopping,最终必须回到 phase=off(最多等 5 秒)。targetId 只在 tracking/lost 阶段有值。

示例#

const tracking = await ctx.capabilities.get('cockpit:smart-track')
if (!tracking) return
const terminal = await tracking.start({ kind: 'detection-target', targetId: target.targetId })
if (terminal.phase !== 'tracking') throw new Error('未进入跟踪终态')
// 业务完成时:
await tracking.stop()
注意:没有 updateTarget 方法——换目标就是重新 start()。

常见问题#

现象原因与处理
takePhoto 返回但消息中心没图拍照完成 ≠ 媒体已上传;媒体文件要等上传后通过后端/媒体能力查询
想要"当前画面截图"却触发了设备拍照用 captureFrame(只截直播画面),不要用 takePhoto
captureFrame 抛 CAPABILITY_NOT_AVAILABLErequireGeometry: true 但当前拿不到几何;不需要投影就设 false
请求 30fps 报错你的插件运行时上限是 10fps;改用 maxFps: 10
录像句柄突然失效被其他调用者 takeover 了;监听句柄的 superseded/device-state-changed 做善后
ensure 报 moving-target 不支持visible 链路不支持该类型;红外能力当前未开放
修改于 2026-10-22 07:06:55
上一页
上下文与遥测
下一页
场景几何与直播指针
Built with