跳到主要内容

完整调用流程与客户端示例

22.1 从建图到导航​

步骤调用必须观察的结果
1systems/device → auth/me设备 SN 正确,按实际启动模式确认认证行为
2services/status;按部署启动扫描/运动依赖相关服务为 running,导航环境已停止
3mapping/container/start,提供新 scene_name记录 task_id,不因网络超时直接重发
4mapping/status等到 mapping,观察 perceptions_available/map_available,按现场流程完成采集
5mapping/container/stop仅表示结束请求受理
6tasks/{task_id}等到 Status=2;失败读取 ErrorMsg / ResultJSON
7maps、maps/{name}/files地图及核心文件已存在;启用版本时核对保存/安装内容
8nav/startup,填写地图及实际初始位姿检查完成步骤;若失败按 failed_step 恢复
9nav/params/apply_status、nav/navigation_status参数应用结果满足需求,定位与场景正确
10nav/missions记录 mission_id
11nav/missions/{id} 或 MQTT task/uplinkcompleted 才是完成;failed/cancelled 分别处理
12按需取消任务,再停止导航容器软件取消与容器停止分别确认

初始位姿必须来自实际机器人位置或可靠重定位结果,示例原点不是适用于所有设备的安全默认值。应用的“停止当前任务”操作与现场硬件急停用途不同。

22.2 Python REST 客户端​

示例使用 Python requests。通过环境变量提供 IP,启用 Bearer 时另提供 ORSUS_TOKEN;免 token 模式可不设置该变量。不要把生产凭据写入源码。函数区分 HTTP/JSON/业务错误,轮询有本地截止时间;超时不会偷偷重复提交或自动取消设备任务。

import os
import time
from urllib.parse import quote
import requests


class OrsusError(RuntimeError):
def __init__(self, status, code, message, data=None):
super().__init__(f"HTTP {status}, code={code}: {message}")
self.status, self.code, self.data = status, code, data


class OrsusClient:
def __init__(self, origin, token=None):
self.base = origin.rstrip('/') + '/v1/api'
self.session = requests.Session()
if token:
self.session.headers['Authorization'] = 'Bearer ' + token

def call(self, method, path, *, body=None, timeout=(5, 30)):
response = self.session.request(
method, self.base + path, json=body, timeout=timeout
)
try:
result = response.json()
except ValueError as exc:
raise OrsusError(response.status_code, None, '响应不是 JSON') from exc
if not isinstance(result, dict) or 'code' not in result:
raise OrsusError(response.status_code, None, '响应不是 Orsus 信封')
if not response.ok or result['code'] != 0:
raise OrsusError(response.status_code, result['code'],
result.get('msg', ''), result.get('data'))
return result.get('data')

def start_environment(self, scene_name, initial_pose):
return self.call('POST', '/nav/startup', timeout=(5, 360), body={
'pose': initial_pose,
'nav_container': {
'scene_name': scene_name, 'bringup_mode': 'navigation',
'use_relocalization': True,
},
})

def submit_goal(self, target):
return self.call('POST', '/nav/missions', body={
'mode': 'standard', 'frame_id': 'map', 'target': target,
})

def wait_mission(self, mission_id, max_wait_seconds=300):
path = '/nav/missions/' + quote(mission_id, safe='')
deadline = time.monotonic() + max_wait_seconds
while time.monotonic() < deadline:
state = self.call('GET', path)
if state['status'] in {'completed', 'failed', 'cancelled'}:
return state
time.sleep(1)
raise TimeoutError(f'本地等待超时,任务 {mission_id} 可能仍在执行,请查询现状')

def cancel_mission(self, mission_id):
return self.call('DELETE', '/nav/missions/' + quote(mission_id, safe=''))

def close(self):
self.session.close()


if __name__ == '__main__':
client = OrsusClient(os.environ['ORSUS_ORIGIN'], os.environ.get('ORSUS_TOKEN'))
try:
print(client.call('GET', '/auth/me'))
print(client.call('GET', '/maps'))
# 完成现场环境和位姿确认后,由业务显式调用以下函数:
# client.start_environment('office_1f', {'x': 0, 'y': 0, 'theta': 0})
# mission = client.submit_goal({'x': 3.2, 'y': 1.5, 'theta': 0})
# state = client.wait_mission(mission['mission_id'])
# 若 state['status'] != 'completed',按失败/取消流程处理。
finally:
client.close()

遇到 requests 超时或连接异常时保留已知任务 ID,用查询恢复;如果创建请求没有拿到 ID,先检查 current/list,再决定是否创建新任务。不要用简单的“所有 POST 自动重试”HTTP 中间件。

22.3 浏览器 fetch 读取系统 SSE​

此示例处理分段、UTF-8 和多行 data,并通过 AbortController 结束连接。断开后由业务层安排退避重连,不在代码中隐藏无限重连。

async function consumeSystemEvents(origin, token, onEvent, signal) {
const response = await fetch(new URL('/v1/api/systems/stream', origin), {
headers: { Accept: 'text/event-stream', ...(token ? { Authorization: `Bearer ${token}` } : {}) },
signal,
});
if (!response.ok || !response.headers.get('content-type')?.includes('text/event-stream')) {
throw new Error(`SSE 连接失败: HTTP ${response.status}`);
}
if (!response.body) throw new Error('浏览器不支持流式响应');
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
let eventName = 'message';
let dataLines = [];
const processLine = line => {
if (line.endsWith('\r')) line = line.slice(0, -1);
if (line === '') {
if (dataLines.length) onEvent(eventName, JSON.parse(dataLines.join('\n')));
eventName = 'message';
dataLines = [];
return;
}
if (line.startsWith(':')) return;
const colon = line.indexOf(':');
const field = colon < 0 ? line : line.slice(0, colon);
let value = colon < 0 ? '' : line.slice(colon + 1);
if (value.startsWith(' ')) value = value.slice(1);
if (field === 'event') eventName = value;
if (field === 'data') dataLines.push(value);
};
try {
while (true) {
const { done, value } = await reader.read();
buffer += done ? decoder.decode() : decoder.decode(value, { stream: true });
let newline;
while ((newline = buffer.indexOf('\n')) >= 0) {
processLine(buffer.slice(0, newline));
buffer = buffer.slice(newline + 1);
}
if (done) break;
}
} finally {
await reader.cancel().catch(() => {});
reader.releaseLock();
}
}

// const controller = new AbortController();
// consumeSystemEvents(origin, token, (event, data) => updatePanel(event, data), controller.signal)
// .catch(error => showConnectionError(error));
// controller.abort(); // 页面关闭时调用

系统 SSE data 一律是 JSON;ASR 可能含 [DONE],不能直接复用此示例而不增加上游格式判断。

22.4 MQTT 订阅与恢复​

使用部署方指定的 MQTT 客户端、TLS、认证和 ACL。推荐先建立订阅再创建任务,避免漏掉最初事件。

CONNECT broker(部署指定地址、凭据及 TLS)
SUBSCRIBE sys/{region}/{tenant_id}/{device_id}/task/uplink,QoS 1
SUBSCRIBE sys/{region}/{tenant_id}/{device_id}/uplink,QoS 1
接收:按 topic 和 msg_type 区分载荷;关联 task_id / command_id
断连:标记数据已过期,保留任务 ID
重连:重新订阅,再 GET /v1/api/tasks/{id} 获取持久化终态

云端下行命令需要确保 command_id 唯一,expires_at 为实际未来时间。应用不能只等待 command_result=succeeded 就显示“已到达”。

22.5 常见问题定位​

现象优先检查
浏览器能打开设备页,API 401是否附带唯一 Bearer 头;终端 token 与 API token 是否混用
SSE 一直 401是否错误使用原生 EventSource;查询参数 token 不适用于系统 SSE
startup 超时容器、导航 API、参数 API 分段等待;查询是否已有部分服务启动,不直接重试运动任务
code=0 但参数无效success 分项、offline_save、启动回放结果;是否只保存离线值
code=0 但容器没启动后台容器日志、互斥策略、Docker 状态;HTTP 受理不等于启动完成
导航暂停但 mission 仍 running任务没有 paused 状态,结合 NavStatus
建图 stop 后地图不可用继续查看建图任务 ResultJSON/ErrorMsg 和文件校验,不提前导航
导入失败ZIP 名称与四个核心文件同名;布局、PGM/PCD/YAML/path 格式与配额
地图已安装但显示旧环境installed_release_id 只说明磁盘,确认运行中的 navigation_session 与重启/重定位流程
RTK running 但无差分效果三条连接状态、lastValidGgaAt/lastRtcmAt/lastPublishedAt 和帧计数
日志列表 500先查 logs/source,使用有效 module;不要依赖空 module 的默认值