| 文档版本 | 修订日期 | 修订内容 | 适用主控软件版本 (若不满足请前往官网下载中心获取最新版本主控软件包进行升级) |
|---|---|---|---|
| V1.0 | 2026.02.27 | 初版 | V2.1.21 及以上 |
| V1.1 | 2026.07.17 | 1. 删除 MCP Server 2. 新增关闭相机驱动自启动功能 |
V2.2.11 及以上 |
| V1.2 | 2026.07.30 | 1. 补充获取相机数据步骤说明 | V2.2.11 及以上 |
1 大模型调用接口
1.1 内置大模型
| 模型 | 备注 |
|---|---|
| qwen2.5:3b | 由阿里云推出的通义千问 2.5 系列模型,参数量为 30 亿。具备较强的语言理解和生成能力,适用于文本生成、对话交互等场景。 |
| qwen2.5:1.5b | 通义千问 2.5 系列中参数量为 15 亿的模型,相对轻量,在一些对计算资源要求不高的场景中也能有较好表现 。 |
| qwen2.5:0.5b | 参数量为 5 亿的轻量级模型,便于在终端设备或资源有限的环境下运行。 |
| llama3.2:3b | Meta 公司开发的大语言模型 LLaMA 3.2 版本中的 30 亿参数模型,在自然语言处理任务上表现出色,开源特性使得开发者可以基于它进行二次开发。 |
| llama3.2:1b | LLaMA 3.2 系列的 10 亿参数模型,模型规模较小,训练和推理速度相对较快。 |
| deepseek-r1:1.5b | DeepSeek 推出的模型,参数量 15 亿,在多种语言处理任务中具备一定的竞争力。 |
1.2 大模型调用
Oli 已通过 ollama 在本地完成上述大模型部署,只需将您的设备连接至与机器人相同的网络,即可按以下方法进行调用。
1.2.1 使用 Curl 调用
curl http://10.192.1.3:11434/api/generate \
-H "Content-Type: application/json" \
-d '{
"model": "qwen2.5:3b",
"prompt": "请写一首描述春天的四言绝句?",
"temperature": 0.7,
"max_tokens": 200,
"stream": false
}'
1.2.2 使用 Python 调用
import requests
url = "http://10.192.1.3:11434/api/generate"
data = {
"model": "qwen2.5:3b",
"prompt": "请写一首描述春天的四言绝句?",
"temperature": 0.7,
"max_tokens": 200,
"stream": False
}
response = requests.post(url, json=data)
if response.status_code == 200:
print(str(response.json()['response']))
else:
print(f"Request failed with status code: {response.status_code}, Error: {response.text}")
1.2.3 使用 C++ 调用
以 Ubuntu 20.04 及以上系统版本为例:
- 安装依赖
sudo apt-get install libcurl4-openssl-dev nlohmann-json3-dev
- 代码实现(llm_demo.cpp)
#include <iostream>
#include <string>
#include <curl/curl.h> // HTTP client library
#include <nlohmann/json.hpp> // JSON parsing
using json = nlohmann::json;
// Callback to handle HTTP response data
static size_t WriteCallback(void* data, size_t size, size_t nmemb, std::string* buf) {
buf->append((char*)data, size * nmemb);
return size * nmemb;
}
int main() {
CURL* curl = curl_easy_init();
if (!curl) {
std::cerr << "CURL init failed" << std::endl;
return 1;
}
// 1. Configure API endpoint
const std::string url = "http://10.192.1.3:11434/api/generate";
// 2. Prepare JSON payload
json req = {
{"model", "qwen2.5:3b"},
{"prompt", "请写一首描述春天的四言绝句?"},
{"temperature", 0.7},
{"max_tokens", 200},
{"stream", false}
};
std::string payload = req.dump();
// 3. Set CURL options
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload.c_str());
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, payload.size());
// 4. Add HTTP headers
struct curl_slist* headers = nullptr;
headers = curl_slist_append(headers, "Content-Type: application/json");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
// 5. Capture response
std::string response;
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
// 6. Execute request
CURLcode res = curl_easy_perform(curl);
// 7. Process result
if (res == CURLE_OK) {
try {
json resp_json = json::parse(response);
std::cout << "Result: " << resp_json["response"] << std::endl;
} catch (const json::exception& e) {
std::cerr << "JSON error: " << e.what() << std::endl;
}
} else {
std::cerr << "HTTP error: " << curl_easy_strerror(res) << std::endl;
}
// 8. Cleanup
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
return 0;
}
- 编译并运行
# Compile with C++11 support
g++ -std=c++11 -o llm_demo llm_demo.cpp -lcurl
# Execute
./llm_demo
2 通讯架构图
下图呈现了开发者的电脑与机器人本体的系统组成及交互关系。开发电脑部分涵盖运控算法节点和软件业务逻辑实现模块,通过 上层应用协议接口 和 limxsdk-lowlevel 的数据通讯控制机器人本体的运动。机器人本体由数据交换机、主控电脑及各类硬件组件构成,主控电脑负责协调各组件运行。
| 中文版 | 英文版 |
|---|---|
3 上层应用协议接口
机器人通过 WebSocket 通信端口 5000 来接收用户端请求指令,例如让机器人站起、蹲下、行走等。
WebSocket 是一种实时通信协议,在机器人和用户端之间建立长连接,以便快速有效地传输控制信息和数据。如下图所示:
3.1 坐标说明
- 如无特别说明,双臂末端的位置、姿态,均基于机器人的 base 坐标系。
- SN 开头为 HU 的人型机器人 base 坐标系的定义。
- base 坐标系原点为 URDF 文件中定义的 base_link,坐标系标准为右手坐标系 / REP-103 坐标系。
3.2 通信协议格式
当机器人通过 WebSocket 接收客户端指令时,采用 JSON 数据协议进行信息传递。
3.2.1 请求数据
| 请求数据格式包含字段 | 描述 |
|---|---|
accid |
机器人唯一序列号,标识机器人的唯一身份。 |
title |
指令名称,以“request_”为前缀。 |
timestamp |
指令发出时间戳,单位为毫秒。 |
guid |
指令的唯一标识符,用于区分不同的请求指令;如果是同步接口,则需要在“response_xxx”响应消息中通过 guid 字段将值带回给客户端;客户端接收到响应消息后,可以通过比较 guid 字段的值是否与请求指令中的值相同来判断指令是否执行完成。 |
data |
存放请求指令的数据内容。可以根据具体需求包含多个子字段,以存放请求指令所需的数据内容,例如执行动作的参数、发送消息的文本内容等等。 |
请求数据代码示例:
{
"accid": "HU_D02_001", # 机器人唯一序列号,标识机器人的唯一身份
"title": "request_xxx", # 指令名称,以“request_”为前缀
"timestamp": 1672373633989, # 指令发出时间戳,单位为毫秒
"guid": "746d937cd8094f6a98c9577aaf213d98", # 指令的唯一标识符,用于区分不同的请求指令
"data": {} # 存放请求指令的数据内容
}
3.2.2 响应数据
| 响应数据格式包含字段 | 描述 |
|---|---|
accid |
机器人唯一序列号,标识机器人的唯一身份。 |
title |
指令名称,以“response_”为前缀。 |
timestamp |
指令发出时间戳,单位为毫秒。 |
guid |
与对应请求指令的 guid 值相同。 |
data |
至少应该包含一个“result”子字段,用于存放请求指令的执行结果数据。如果有需要,还可以包含其他子字段,例如错误码、错误信息等用于描述操作结果的信息。 |
响应数据代码示例:
{
"accid": "HU_D02_001", # 机器人唯一序列号,标识机器人的唯一身份
"title": "response_xxx", # 指令名称,以“response_”为前缀
"timestamp": 1672373633989, # 指令发出时间戳,单位为毫秒
"guid": "746d937cd8094f6a98c9577aaf213d98", # 与对应请求指令的guid值相同
"data": { # 存放响应指令的具体数据内容
"result": "success" # “result” 用于存放请求指令处理是否成功,它的值为:“success 或 fail_xxx”
}
}
3.2.3 消息推送
机器人主动向客户端发送信息的过程。这些信息可以包括机器人的序列号、当前运行状态、执行的操作等数据。通过及时地向客户端发送这些信息,机器人可以帮助客户端更好地理解它的工作状态,从而更好地使用它提供的服务。
| 消息推送数据格式包含字段 | 描述 |
|---|---|
accid |
机器人唯一序列号,标识机器人的唯一身份。 |
title |
指令名称,以“notify_”为前缀。 |
timestamp |
消息发出时间戳,单位为毫秒。 |
guid |
消息的 guid 值,唯一标识这条消息。 |
data |
存放消息数据内容。可以根据具体需求包含多个子字段,以存放请求指令所需的数据内容。 |
消息推送代码示例:
{
"accid": "HU_D02_001", # 机器人唯一序列号,标识机器人的唯一身份
"title": "notify_xxx", # 消息名称,以“notify_”为前缀
"timestamp": 1672373633989, # 消息发出时间戳,单位为毫秒
"guid": "746d937cd8094f6a98c9577aaf213d98", # 消息的guid值,唯一标识这条消息
"data": { } # 存放消息数据内容
}
3.3 通信测试方法
Postman 是一个流行的 API 开发环境,可以用于测试 WebSocket 接口。
使用 Postman 测试 WebSocket 接口操作步骤:
- 安装 postman,下载地址:https://www.postman.com/downloads/?utm_source=postman-home;
- 打开 Postman,并创建一个 WebSocket 的请求;
- 连接机器人无线网络 1. 机器人开机完成后,使用个人电脑连接机器人 Wi-Fi,名称格式通常为「HU_D02_xxx」
- 输入 Wi-Fi 密码:
12345678 - 在请求的 URL 中输入 WebSocket 接口的地址,例如,“ws://10.192.1.2:5000”;
- 在“Message”中,输入要发送的指令请求;
- 单击“Send”按钮,发送请求指令;
- 发送指令后,可以从服务器接收响应消息。使用 Postman 的响应窗口查看服务器返回的数据,并检查是否符合预期结果。
3.4 基础功能协议接口
3.4.1 连接 Wi-Fi 热点
3.4.1.1 请求:request_connect_wifi
本协议用于向机器人路由器发起请求,指令路由器连接到指定 SSID 的 WiFi 热点并返回连接结果,支持机器人版本:2.1.3 及以上。
{
"accid": "HU_D04_01_001",
"title": "request_connect_wifi",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"wifi_band": "0", # WiFi频段:0=5GHz,1=2.4GHz
"wifi_ssid": "Limx-Guests", # 目标WiFi的SSID(WiFi名称),区分大小写,需与实际热点一致
"wifi_password": "LimX2024", # 目标WiFi的密码,WPA2-PSK加密方式的密码
"router_admin_password": "12345678" # 机器人路由器的管理员密码
}
}
3.4.1.2 响应:response_connect_wifi
{
"accid": "HU_D04_01_001",
"title": "response_connect_wifi",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # success: 成功
# fail_no_wifi_band: 没有指定频段
# fail_no_wifi_ssid: 没有指定ssid
# fail_no_wifi_password: 没有指定密码
# fail_no_router_admin_password: 没有指定密码
}
}
3.4.1.3 消息推送:无
3.4.2 查询 Wi-Fi 连接状态
本协议用于客户端向机器人路由器发起 WiFi 连接状态查询请求,路由器接收请求后反馈当前已连接 WiFi 的核心状态信息,包括关联 SSID、信号强度及连接结果,支撑客户端实时感知设备网络连接状态。客户端发起 WiFi 连接状态查询,需携带机器人路由器管理员密码完成身份校验。
3.4.2.1 请求:request_wifi_connection_status
{
"accid": "HU_D04_01_001",
"title": "request_wifi_connection_status",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"router_admin_password": "12345678" # 机器人路由器的管理员密码
}
}
3.4.2.2 响应:response_wifi_connection_status
机器人路由器接收查询请求后,返回当前 WiFi 实际连接状态,供客户端解析展示或后续业务处理。
{
"accid": "HU_D04_01_001",
"title": "response_wifi_connection_status",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"ssid": "Limx-Guests",
"signal": -56, # 单位dBm
"result": "success" # success: 成功
# fail_disconnected
}
}
3.4.2.3 消息推送:无
3.4.3 进入准备状态
机器人缓慢摆出准备姿势。
3.4.3.1 请求:request_prepare
控制机器人进入“站姿”,可接受速度指令控制行走。
{
"accid": "HU_D04_01_001",
"title": "request_prepare",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": { }
}
3.4.3.2 响应:response_prepare
{
"accid": "HU_D04_01_001",
"title": "response_prepare",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # success: 成功, fail_motor: 电机错误
}
}
3.4.3.3 消息推送:无
3.4.4 控制机器人行走
3.4.4.1 进入行走模式
机器人进入行走模式,可以接收速度指令。
3.4.4.1.1 请求:request_set_walk_mode
{
"accid": "HU_D04_01_001",
"title": "request_set_walk_mode",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {}
}
3.4.4.1.2 响应:response_set_walk_mode
{
"accid": "HU_D04_01_001",
"title": "response_set_walk_mode",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # success: 成功, fail_motor: 电机错误
}
}
3.4.4.1.3 消息推送:无
3.4.4.2 控制机器人行走
在移动操作模式下,通过此协议控制机器人行走。请注意,在全身操作模式下,此协议接口无效。
3.4.4.2.1 请求:request_set_walk_vel
{
"accid": "HU_D04_01_001",
"title": "request_set_walk_vel",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"x": 0.0, # 前进后退速度比值,取值范围[-1, 1]
"y": 0.0, # 横向行走速度比值,取值范围[-1, 1]
"yaw": 0.0 # 旋转角速度比值,取值范围[-1, 1]
}
}
3.4.4.2.2 响应:response_set_walk_vel
指令执行失败时返回此消息,成功执行则无返回。
{
"accid": "HU_D04_01_001",
"title": "response_set_walk_vel",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "fail_motor" # fail_imu: IMU 错误, fail_motor: 电机错误
}
}
3.4.4.2.3 消息推送:无
3.4.5 进入阻尼模式
机器人所有电机停止主动运动,摆动时有明显阻尼感。
3.4.5.1 请求:request_damping
{
"accid": "HU_D04_01_001",
"title": "request_damping",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {}
}
3.4.5.2 响应:response_damping
{
"accid": "HU_D04_01_001",
"title": "response_damping",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # fail_motor: 电机错误
}
}
3.4.5.3 消息推送:无
3.4.6 进入零力矩模式
机器人所有电机停止主动运动,摆动时没有阻尼感。
3.4.6.1 请求:request_zero_torque
{
"accid": "HU_D04_01_001",
"title": "request_zero_torque",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {}
}
3.4.6.2 响应:response_zero_torque
{
"accid": "HU_D04_01_001",
"title": "response_zero_torque",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # fail_motor: 电机错误
}
}
3.4.6.3 消息推送:无
3.4.7 进入坐下指令
3.4.7.1 请求:request_from_stand_to_sit
{
"accid": "HU_D04_01_001",
"title": "request_from_stand_to_sit",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {}
}
3.4.7.2 响应:response_from_stand_to_sit
{
"accid": "HU_D04_01_001",
"title": "response_from_stand_to_sit",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # fail_motor: 电机错误
}
}
3.4.7.3 消息推送:无
3.4.8 进入站立指令
提示:
接口功能:启动机器人使用,机器人开机后调用该接口进入站立状态。
参数 mode:[lying:机器人躺着 hanging:机器人吊着 sit: 机器人坐着]
返回:已站起后返回
3.4.8.1 请求:request_standup
{
"accid": "HU_D04_01_001",
"title": "request_standup",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"mode": "lying" // "lying"/"sitting":机器人当前躺着/坐着 or
// "hanging":机器人当前状态吊着
// 若没有"mode" 字段 默认机器人状态是"sitting"坐着
}
}
3.4.8.2 响应:response_standup
{
"accid": "HU_D04_01_001",
"title": "response_standup",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # fail_motor: 电机错误
# fail_invalid_cmd:参数错误
# fail_invalid_mode:机器人状态错误
# fail_timeout:执行超时错误
}
}
3.4.8.3 消息推送:无
3.4.9 进入躺着指令
3.4.9.1 请求:request_lie_down
Walk 状态下可以调用该接口
{
"accid": "HU_D04_01_001",
"title": "request_lie_down",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {}
}
3.4.9.2 响应:response_lie_down
{
"accid": "HU_D04_01_001",
"title": "response_lie_down",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # fail_motor: 电机错误
}
}
3.4.9.3 消息推送:无
3.4.10 机器人校零指令
3.4.10.1 请求:request_calibrate
{
"accid": "HU_D04_01_001",
"title": "request_calibrate",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {}
}
3.4.10.2 响应:response_calibrate
{
"accid": "HU_D04_01_001",
"title": "response_calibrate",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # fail_motor: 电机错误
}
}
3.4.10.3 消息推送:notify_calibrate
校零完成后,推送此消息。
{
"accid": "HU_D04_01_001",
"title": "notify_calibrate",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success"
}
}
3.4.11 机器人舞蹈
3.4.11.1 切换机器人到舞蹈模式
3.4.11.1.1 请求:request_enter_dance_mode
{
"accid": "HU_D04_01_001",
"title": "request_enter_dance_mode",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
# 0:退出舞蹈模式
# 1:进入舞蹈模式
"mode": 0
}
}
3.4.11.1.2 响应:response_enter_dance_mode
{
"accid": "HU_D04_01_001",
"title": "response_enter_dance_mode",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # fail_motor
}
}
3.4.11.1.3 消息推送:无
3.4.11.2 获取舞蹈列表
3.4.11.2.1 请求:request_get_dance_list
{
"accid": "HU_D04_01_001",
"title": "request_get_dance_list",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {}
}
3.4.11.2.2 响应:response_get_dance_list
{
"accid": "HU_D04_01_001",
"title": "response_get_dance_list",
"guid": "746d937cd8094f6a98c9577aaf213d98",
"timestamp": 1672373633989,
"data": {
"result": "success",
"code": 0,
"dances": [
{
"id": "DAN-14",
"index": 0,
"name": "\u70ed\u70c8",
"english_name": "One and Only Dance",
"rc_mapping": "one_and_only_dance"
},
{
"id": "DAN-08",
"index": 1,
"name": "\u4f4e\u4fd7\u5c0f\u8bf4",
"english_name": "Pulp Fiction Dance",
"rc_mapping": "pulp_fiction_dance"
}
]
}
}
3.4.11.3 机器人跳舞
3.4.11.3.1 请求:request_dance
提示:
- 执行前置条件:当前处于动作库模式
- 适用于主控 V2.1.21 及以上版本
{
"accid": "HU_D04_01_001",
"title": "request_dance",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"name": "one_and_only_dance" # rc_mapping 字段的舞蹈名称
}
}
3.4.11.3.2 响应:response_dance
{
"accid": "HU_D04_01_001",
"title": "response_dance",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # fail_motor
}
}
3.4.11.3.3 消息推送:notify_dance
跳完舞蹈或执行过程中失败推送此消息。
{
"accid": "HU_D04_01_001",
"title": "notify_dance",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # fail_motor
}
}
3.4.12 机器人原地踏步
3.4.12.1 开启原地踏步
3.4.12.1.1 请求:request_start_walktoggle
{
"accid": "HU_D04_01_001",
"title": "request_start_walktoggle",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {}
}
3.4.12.1.2 响应:response_start_walktoggle
{
"accid": "HU_D04_01_001",
"title": "response_start_walktoggle",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # fail_motor
}
}
3.4.12.1.3 消息推送:无
3.4.12.2 停止原地踏步
3.4.12.2.1 请求:request_stop_walktoggle
{
"accid": "HU_D04_01_001",
"title": "request_stop_walktoggle",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {}
}
3.4.12.2.2 响应:response_stop_walktoggle
{
"accid": "HU_D04_01_001",
"title": "response_stop_walktoggle",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # fail_motor
}
}
3.4.13 机器人动作库
3.4.13.1 动作打断
3.4.13.1.1 请求:request_interrupt_action_joystick
{
"accid": "HU_D04_01_001",
"title": "request_interrupt_action_joystick",
"timestamp": 1779355330784,
"guid": "32cef03a-5563-4b21-9bbb-3e65a8c9ae9e",
"data": {}
}
3.4.13.1.2 响应:response_interrupt_action_joystick
{
"accid": "HU_D04_01_001",
"title": "response_interrupt_action_joystick",
"guid": "32cef03a-5563-4b21-9bbb-3e65a8c9ae9e",
"timestamp": 1779355330784,
"data": {
"result": "success"
}
}
3.4.13.2 获取动作库状态
接口说明:
- 机器人进入动作库之后,处于动作库/原子执行/舞蹈中将显示:"action_library_mode": "action_library"
- 当机器人在执行原子动作中或者舞蹈中将显示: "action_library_state": "running"
3.4.13.2.1 请求:request_get_action_library_status
{
"accid": "HU_D04_01_001",
"title": "request_get_action_library_status",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {}
}
3.4.13.2.2 响应:response_get_action_library_status
{
"accid": "HU_D04_01_001",
"title": "get_action_library_status",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"action_library_mode": "action_library" //"remote_control"
"action_library_state": "running" //"idle"
"result": "success" # fail_motor
}
}
3.4.13.3 执行动作库
接口说明:
- 不在 Menu 会自动进入 Menu,动作执行完会保持 Menu;
- 需配合
request_get_action_library_status的action_library_state使用。
3.4.13.3.1 请求:request_action_sync_stay_menu
{
"accid": "HU_D04_01_001",
"title": "request_action_sync_stay_menu",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"name": "one_and_only_dance"
}
}
3.4.13.3.2 响应:response_action_sync_stay_menu
{
"accid": "HU_D04_01_001",
"title": "response_action_sync_stay_menu",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # fail_motor
}
}
3.4.13.4 执行动作库(同步接口)
接口说明:
- 当机器人处于不可执行动作库时,在 100ms 内返回失败响应;
- 当机器人处于可执行动作库时(walk/motion library),在执行动作库之后返回响应。
- 同步接口,会自动回到Walk,结束状态通过判断是否再Walk结束。
3.4.13.4.1 请求:request_action_sync
{
"accid": "HU_D04_01_001",
"title": "request_action_sync",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"name": "one_and_only_dance,this_way_please"
# name:多个舞蹈/多个动作/舞蹈与动作混合,用“,”隔开
}
}
3.4.13.4.2 响应:response_action_sync
{
"accid": "HU_D04_01_001",
"title": "response_action_sync",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # fail_motor
}
}
3.4.13.5 切换机器人到动作库模式
3.4.13.5.1 请求:request_set_motion_engine
{
"accid": "HU_D04_01_001",
"title": "request_set_motion_engine",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
# 0:退出动作库模式
# 1:进入动作库模式
"mode": 0
}
}
3.4.13.5.2 响应:response_set_motion_engine
{
"accid": "HU_D04_01_001",
"title": "response_set_motion_engine",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # fail_motor
}
}
3.4.13.5.3 消息推送:无
3.4.13.6 获取动作库列表
3.4.13.6.1 请求:request_get_atomic_motion_list
{
"accid": "HU_D04_01_001",
"title": "request_get_atomic_motion_list",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {}
}
3.4.13.6.2 响应:response_get_atomic_motion_list
{
"accid": "HU_D04_01_001",
"title": "response_get_atomic_motion_list",
"guid": "746d937cd8094f6a98c9577aaf213d98",
"timestamp": 287883835,
"data": {
"result": "success",
"motion_list": [
{
"motion_index": 0,
"motion_name_cn": "静止站立",
"motion_name_en": "stand"
},
{
"motion_index": 1,
"motion_name_cn": "指引请这边走",
"motion_name_en": "this_way_please"
}
......
],
"count": 2
}
}
3.4.13.7 执行机器人动作库动作
设置机器人为动作库模式后,可以执行机器人动作库中的动作。
3.4.13.7.1 请求:request_execute_atomic_motion
{
"accid": "HU_D04_01_001",
"title": "request_execute_atomic_motion",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
# 动作名称
"motion_name": "wave_greet_bye"
}
}
3.4.13.7.2 响应:response_execute_atomic_motion
{
"accid": "HU_D04_01_001",
"title": "response_execute_atomic_motion",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # fail_motor
}
}
3.4.13.7.3 消息推送:notify_execute_atomic_motion
动作执行完成或执行过程中失败推送此消息。
{
"accid": "HU_D04_01_001",
"title": "notify_execute_atomic_motion",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # fail_motor
}
}
3.4.14 机器人移动操作
3.4.14.1 移动操作模式切换
在移动操作模式下您还可以控制机器人行走,但不能控制机器人的身高及腰部运动。
3.4.14.1.1 请求:request_set_ub_manip_mode
{
"accid": "HU_D04_01_001",
"title": "request_set_ub_manip_mode",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"mode": 0 # 0: 准备进入模式 1: 操作模式,开始跟踪末端位置 2: 准备退出模式
}
}
3.4.14.1.2 响应:response_set_ub_manip_mode
{
"accid": "HU_D04_01_001",
"title": "response_set_ub_manip_mode",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # success: 成功, fail_motor: 电机错误
}
}
3.4.14.1.3 消息推送:无
3.4.14.2 移动操作控制
需要通过协议接口 request_set_ub_manip_mode,功能才生效。
- 参考坐标系示意图:
- 位置 —— base_link的原点(髋部下方正中间)
- 坐标轴定义:红色为x方向:机器人前进方向;绿色为y方向:正方向向左;蓝色为z方向:竖直朝上
| |
|
3.4.14.2.1 请求:request_set_ub_manip_ee_pose
{
"accid": "HU_D04_01_001",
"title": "request_set_ub_manip_ee_pose",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
# 参考坐标系定义:
# 原点:base_link坐标系
# 方向:x方向对齐机器人正前方,y方向朝向机器人左侧,z方向竖直向上
#
# 参数定义:
# 头相对于参考坐标系的姿态,四元数[x,y,z,w]
"head_quat": [0.0, 0.0, 0.0, 1.0],
# 左手相对于参考坐标系的位置,单位为米
"left_hand_pos": [0.0, 0.0, 0.0],
# 左手相对于参考坐标系的姿态,四元数[x,y,z,w]
"left_hand_quat": [0.0, 0.0, 0.0, 1.0],
# 右手相对于参考坐标系的位置,单位为米
"right_hand_pos": [0.0, 0.0, 0.0],
# 右手相对于参考坐标系的姿态,四元数[x,y,z,w]
"right_hand_quat": [0.0, 0.0, 0.0, 1.0]
}
}
3.4.14.2.2 响应:response_set_ub_manip_ee_pose
{
"accid": "HU_D04_01_001",
"title": "response_set_ub_manip_ee_pose",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # success: 成功, fail_motor: 电机错误, fail_invalid_cmd: 非法指令
}
}
3.4.14.2.3 消息推送:无
3.4.14.3 获取移动操作位姿信息
3.4.14.3.1 请求:request_get_ub_manip_ee_pose
{
"accid": "HU_D04_01_001",
"title": "request_get_ub_manip_ee_pose",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
}
}
3.4.14.3.2 响应:response_get_ub_manip_ee_pose
{
"accid": "HU_D04_01_001",
"title": "response_get_ub_manip_ee_pose",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
# 参考坐标系定义:
# 原点:base_link坐标系
# 方向:x方向对齐机器人正前方,y方向朝向机器人左侧,z方向竖直向上
#
# 参数定义:
# 头相对于参考坐标系的位置,单位为米
"head_pos": [0.0, 0.0, 0.0],
# 头相对于参考坐标系的姿态,四元数[x,y,z,w]
"head_quat": [0.0, 0.0, 0.0, 1.0],
# 左手相对于参考坐标系的位置,单位为米
"left_hand_pos": [0.0, 0.0, 0.0],
# 左手相对于参考坐标系的姿态,四元数[x,y,z,w]
"left_hand_quat": [0.0, 0.0, 0.0,1.0],
# 右手相对于参考坐标系的位置,单位为米
"right_hand_pos": [0.0, 0.0, 0.0],
# 右手相对于参考坐标系的姿态,四元数[x,y,z,w]
"right_hand_quat": [0.0, 0.0, 0.0,1.0],
"result": "success" # success: 成功, fail_motor: 电机错误, fail_invalid_cmd: 非法指令
}
}
3.4.15 机器人原地操作
3.4.15.1 进入原地操作模式
原地操作模式下,您可控制机器人的身体运动,但无法操控其行走功能。
3.4.15.1.1 请求:request_set_wb_manip_mode
{
"accid": "HU_D04_01_001",
"title": "request_set_wb_manip_mode",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"mode": 0 # 0: 准备进入模式 1: 操作模式,开始跟踪末端位置 2: 准备退出模式
}
}
3.4.15.1.2 响应:response_set_wb_manip_mode
{
"accid": "HU_D04_01_001",
"title": "response_set_wb_manip_mode",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # success: 成功, fail_motor: 电机错误
}
}
3.4.15.1.3 消息推送:无
3.4.15.2 原地操作控制
需要通过协议接口 request_set_wb_manip_mode,mode 为 1 进入原地操作模式下,功能才生效。
- 参考坐标系示意图:
- 位置 —— left_ankle_roll_link 与 right_ankle_roll_link 原点连线的中点
- 姿态 —— yaw方向为 left_ankle_roll_link 与 right_ankle_roll_link 转向的中间值
- 坐标轴定义:红色为x方向:由机器人双脚朝向决定;绿色为y方向:可根据右手坐标系规则确定;蓝色为z方向:竖直朝上。
3.4.15.2.1 请求:request_set_wb_manip_ee_pose
{
"accid": "HU_D04_01_001",
"title": "request_set_wb_manip_ee_pose",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
# 参考坐标系定义:
# 原点:为左脚以及右脚的中心位置在地面上的投影(z=0)
# 方向:x方向对齐机器人正前方,y方向朝向机器人左侧,z方向竖直向上
#
# 参数定义:
# 左手相对于参考坐标系的位置,单位为米
"left_hand_pos": [0.0, 0.3, 0.8],
# 左手相对于参考坐标系的姿态,四元数[x,y,z,w]
"left_hand_quat": [0.0, 0.0, 0.0, 1.0],
# 右手相对于参考坐标系的位置,单位为米
"right_hand_pos": [0.0, -0.3, 0.8],
# 右手相对于参考坐标系的姿态,四元数[x,y,z,w]
"right_hand_quat": [0.0, 0.0, 0.0, 1.0]
}
}
3.4.15.2.2 响应:response_set_wb_manip_ee_pose
{
"accid": "HU_D04_01_001",
"title": "response_set_wb_ee_pose",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # success: 成功, fail_motor: 电机错误, fail_invalid_cmd: 非法指令
}
}
3.4.15.2.3 消息推送:无
3.4.15.3 获取原地操作位姿信息
3.4.15.3.1 请求:request_get_wb_manip_ee_pose
{
"accid": "HU_D04_01_001",
"title": "request_get_wb_manip_ee_pose",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
}
}
3.4.15.3.2 响应:response_get_wb_manip_ee_pose
{
"accid": "HU_D04_01_001",
"title": "response_get_wb_manip_ee_pose",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
# 参考坐标系定义:
# 原点:为左脚以及右脚的中心位置在地面上的投影(z=0)
# 方向:x方向对齐机器人正前方,y方向朝向机器人左侧,z方向竖直向上
#
# 参数定义:
# 左手相对于参考坐标系的位置,单位为米
"left_hand_pos": [0.0, 0.0, 0.0],
# 左手相对于参考坐标系的姿态,四元数[x,y,z,w]
"left_hand_quat": [0.0, 0.0, 0.0, 1.0],
# 右手相对于参考坐标系的位置,单位为米
"right_hand_pos": [0.0, 0.0, 0.0],
# 右手相对于参考坐标系的姿态,四元数[x,y,z,w]
"right_hand_quat": [0.0, 0.0, 0.0, 1.0],
"result": "success" # success: 成功, fail_motor: 电机错误, fail_invalid_cmd: 非法指令
}
}
3.4.15.3.3 消息推送:无
3.4.16 双臂协同 Move 控制
3.4.16.1 切换 Move 控制模式
3.4.16.1.1 请求:request_set_move_mode
{
"accid": "HU_D04_01_001",
"title": "request_set_move_mode",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
# 0: 退出Move控制
# 1: 移动Move模式
# 2: 原地Move模式
"mode": 0
}
}
3.4.16.1.2 响应:response_set_move_mode
{
"accid": "HU_D04_01_001",
"title": "response_set_move_mode",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # success: 成功, fail_motor: 电机错误
}
}
3.4.16.1.3 消息推送:无
3.4.16.2 MoveJ 控制指令
3.4.16.2.1 请求:request_moveJ
{
"accid": "HU_D04_01_001",
"title": "request_moveJ",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
# 各关节位置范围根据对应型号机器人URDF获取
# 模型文件下载地址:https://github.com/limxdynamics/humanoid-description
# 如您同时给出以下数据,则会控制双臂运动(目标位置,单位弧度)
# 左臂关节顺序:
# - "left_shoulder_pitch_joint"
# - "left_shoulder_roll_joint"
# - "left_shoulder_yaw_joint"
# - "left_elbow_joint"
# - "left_wrist_yaw_joint"
# - "left_wrist_pitch_joint"
# - "left_wrist_roll_joint"
"left": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
# 右臂关节顺序:
# - "right_shoulder_pitch_joint"
# - "right_shoulder_roll_joint"
# - "right_shoulder_yaw_joint"
# - "right_elbow_joint"
# - "right_wrist_yaw_joint"
# - "right_wrist_pitch_joint"
# - "right_wrist_roll_joint"
"right": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
# 仅在原地MoveJ可控,如您同时给出以下数据,则会控制躯干姿态
"torso_height": 0, # 调整身高比例值,取值范围[-1, 1]
"torso_pitch": 0, # Pitch方向运动比例值,取值范围[-1, 1]
"torso_roll": 0, # Roll方向运动比例值,取值范围[-1, 1]
"torso_yaw": 0, # Yaw方向运动比例值,取值范围[-1, 1]
# 如您同时给出以下数据,则会控制头运动
"head_pitch": 0.0, # pitch 关节的目标位置,单位为弧度
"head_yaw": 0.0, # yaw 关节的目标位置,单位为弧度
"speed": 0.2 # 运动速度,取值范围为 0 到 0.5 弧度 / 秒,控制双臂运动的快慢
}
}
3.4.16.2.2 响应:response_moveJ
{
"accid": "HU_D04_01_001",
"title": "response_moveJ",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # success: 成功, fail_motor: 电机错误
}
}
3.4.16.2.3 消息推送:notify_moveJ
执行完成或失败,主动推送此消息。
{
"accid": "HU_D04_01_001",
"title": "notify_moveJ",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # success: 执行完成, fail_motor: 电机错误, fail_invalid_speed: 非法速度值
}
}
3.4.16.3 MoveP 控制指令
- 参考坐标系示意图:
- 位置 —— waist_pitch_link 的原点
- 坐标轴定义:红色为x方向:机器人前进方向;绿色为y方向:正方向向左;蓝色为z方向:竖直朝上
3.4.16.3.1 请求:request_moveP
{
"accid": "HU_D04_01_001",
"title": "request_moveP",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
# 如您同时给出以下数据,则会控制双臂运动
"left_position": [0.0, 0.0, 0.0], # 表示左臂末端要移动到的目标位置,单位为米,顺序为 x, y, z
"left_quat": [0.0, 0.0, 0.0, 1.0], # 用四元数(x, y, z, w)表示左臂末端的目标姿态
"right_position": [0.0, 0.0, 0.0], # 表示右臂末端要移动到的目标位置,单位为米,顺序为 x, y, z
"right_quat": [0.0, 0.0, 0.0, 1.0], # 用四元数(x, y, z, w)表示右臂末端的目标姿态
# 仅在原地MoveP可控,如您同时给出以下数据,则会控制躯干姿态
"torso_height": 0, # 调整身高比例值,取值范围[-1, 1]
"torso_pitch": 0, # Pitch方向运动比例值,取值范围[-1, 1]
"torso_roll": 0, # Roll方向运动比例值,取值范围[-1, 1]
"torso_yaw": 0, # Yaw方向运动比例值,取值范围[-1, 1]
# 如您同时给出以下数据,则会控制头运动
"head_pitch": 0.0, # pitch 关节的目标位置,单位为弧度
"head_yaw": 0.0, # yaw 关节的目标位置,单位为弧度
"speed": 0.2 # 运动速度,取值范围为 0 到 0.5 弧度 / 秒,控制双臂运动的快慢
}
}
3.4.16.3.2 响应:response_moveP
{
"accid": "HU_D04_01_001",
"title": "response_moveP",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # success: 成功, fail_motor: 电机错误
}
}
3.4.16.3.3 消息推送:notify_moveP
执行完成或失败,主动推送此消息。
{
"accid": "HU_D04_01_001",
"title": "notify_moveP",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # success: 执行完成, fail_motor: 电机错误, fail_invalid_speed: 非法速度值
}
}
3.4.16.4 获取双臂末端位姿
3.4.16.4.1 请求:request_get_move_pose
通过此接口获取机器人双臂的末端位姿信息。
{
"accid": "HU_D04_01_001",
"title": "request_get_move_pose",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {}
}
3.4.16.4.2 响应:response_get_move_pose
接收到请求后,返回双臂当前位姿的相关信息。
{
"accid": "HU_D04_01_001",
"title": "response_get_move_pose",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"timestamp": 1672373633989, # 表示数据时戳,单位为毫秒
"left_position": [0.0, 0.0, 0.0], # 表示左臂末端的位置,单位为米,顺序为 x, y, z
"left_quat": [0.0, 0.0, 0.0, 1.0], # 表示左臂末端的姿态,以四元数表示,顺序为 x, y, z, w
"right_position": [0.0, 0.0, 0.0], # 表示右臂末端的位置,单位为米,顺序为 x, y, z
"right_quat": [0.0, 0.0, 0.0, 1.0], # 表示右臂末端的姿态,以四元数表示,顺序为 x, y, z, w
"result": "success" # fail_not_data
}
}
3.4.17 双臂协同 Servo 控制
3.4.17.1 切换 Servo 控制模式
3.4.17.1.1 请求:request_set_servo_mode
{
"accid": "HU_D04_01_001",
"title": "request_set_servo_mode",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
# 0: 退出Servo控制
# 1: 移动Servo模式
# 2: 原地Servo模式
"mode": 0
}
}
3.4.17.1.2 响应:response_set_servo_mode
{
"accid": "HU_D04_01_001",
"title": "response_set_servo_mode",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # success: 成功, fail_motor: 电机错误
}
}
3.4.17.1.3 消息推送:无
3.4.17.2 ServoJ 控制指令
3.4.17.2.1 请求:request_servoJ
- 推荐在实时系统中按控制频率 >= 500Hz 要求来控制机械臂运动,以保证控制效果和稳定性。
{
"accid": "HU_D04_01_001",
"title": "request_servoJ",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
# 各关节位置范围根据对应型号机器人URDF获取
# 模型文件下载地址:https://github.com/limxdynamics/humanoid-description
# 如您同时给出以下数据,则会控制双臂运动(目标位置,单位弧度)
# 左臂关节顺序:
# - "left_shoulder_pitch_joint"
# - "left_shoulder_roll_joint"
# - "left_shoulder_yaw_joint"
# - "left_elbow_joint"
# - "left_wrist_yaw_joint"
# - "left_wrist_pitch_joint"
# - "left_wrist_roll_joint"
"left": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
# 右臂关节顺序:
# - "right_shoulder_pitch_joint"
# - "right_shoulder_roll_joint"
# - "right_shoulder_yaw_joint"
# - "right_elbow_joint"
# - "right_wrist_yaw_joint"
# - "right_wrist_pitch_joint"
# - "right_wrist_roll_joint"
"right": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
# 仅在原地ServoJ可控,如您同时给出以下数据,则会控制躯干姿态
"torso_height": 0, # 调整身高比例值,取值范围[-1, 1]
"torso_pitch": 0, # Pitch方向运动比例值,取值范围[-1, 1]
"torso_roll": 0, # Roll方向运动比例值,取值范围[-1, 1]
"torso_yaw": 0, # Yaw方向运动比例值,取值范围[-1, 1]
# 如您同时给出以下数据,则会控制头运动
"head_yaw": 0.0, # yaw 关节的目标位置,单位为弧度
"head_pitch": 0.0 # picth 关节的目标位置,单位为弧度
}
}
3.4.17.2.2 响应:无
3.4.17.2.3 消息推送:notify_servoJ
当 ServoJ 控制操作执行失败时,服务器会主动推送此消息,告知客户端失败原因。
{
"accid": "HU_D04_01_001",
"title": "notify_servoJ",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "fail_invalid_cmd" # fail_invalid_cmd: 非法指令, fail_motor: 电机错误
}
}
3.4.17.3 ServoP 控制指令
- 参考坐标系示意图:
- 位置 —— waist_pitch_link 的原点
- 坐标轴定义:红色为x方向:机器人前进方向;绿色为y方向:正方向向左;蓝色为z方向:竖直朝上。
3.4.17.3.1 请求:request_servoP
- 推荐在实时系统中按控制频率 >= 500Hz 要求来控制机械臂运动,以保证控制效果和稳定性。
{
"accid": "HU_D04_01_001",
"title": "request_servoP",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
# 如您同时给出以下数据,则会控制双臂运动
"left_position": [0.0, 0.0, 0.0], # 表示左臂末端的目标位置,单位为米,顺序为 x,y, z
"left_quat": [0.0, 0.0, 0.0, 1.0], # 以四元数(x, y, z, w)形式表示左臂末端的目标姿态
"right_position": [0.0, 0.0, 0.0], # 表示右臂末端的目标位置,单位为米,顺序为 x,y, z
"right_quat": [0.0, 0.0, 0.0, 1.0] # 以四元数(x, y, z, w)形式表示右臂末端的目标姿态
# 仅在原地ServoP可控,如您同时给出以下数据,则会控制躯干姿态
"torso_height": 0, # 调整身高比例值,取值范围[-1, 1]
"torso_pitch": 0, # Pitch方向运动比例值,取值范围[-1, 1]
"torso_roll": 0, # Roll方向运动比例值,取值范围[-1, 1]
"torso_yaw": 0, # Yaw方向运动比例值,取值范围[-1, 1]
# 如您同时给出以下数据,则会控制头运动
"head_yaw": 0.0, # yaw 关节的目标位置,单位为弧度
"head_pitch": 0.0 # picth 关节的目标位置,单位为弧度
}
}
3.4.17.3.2 响应:无
3.4.17.3.3 消息推送:notify_servoP
当 ServoP 控制操作执行失败时,服务器会主动推送此消息,告知客户端失败原因。
{
"accid": "HU_D04_01_001",
"title": "notify_servoP",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "fail_invalid_cmd" # fail_invalid_cmd: 非法指令, fail_motor: 电机错误
}
}
3.4.17.4 获取双臂末端位姿
3.4.17.4.1 请求:request_get_servo_pose
通过此接口获取机器人双臂的末端位姿信息。
{
"accid": "HU_D04_01_001",
"title": "request_get_servo_pose",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {}
}
3.4.17.4.2 响应:response_get_servo_pose
接收到请求后,返回双臂当前位姿的相关信息。
{
"accid": "HU_D04_01_001",
"title": "response_get_servo_pose",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"timestamp": 1672373633989, # 表示数据时戳,单位为毫秒
"left_position": [0.0, 0.0, 0.0], # 表示左臂末端的位置,单位为米,顺序为 x, y, z
"left_quat": [0.0, 0.0, 0.0, 1.0], # 表示左臂末端的姿态,以四元数表示,顺序为 x, y, z, w
"right_position": [0.0, 0.0, 0.0], # 表示右臂末端的位置,单位为米,顺序为 x, y, z
"right_quat": [0.0, 0.0, 0.0, 1.0], # 表示右臂末端的姿态,以四元数表示,顺序为 x, y, z, w
"result": "success" # fail_not_data
}
}
3.4.17.4.3 消息推送:无
3.4.18 机器人关节状态
3.4.18.1 请求:request_get_joint_state
此请求用于获取机器人的各个关节状态。
{
"accid": "HU_D04_01_001",
"title": "request_get_joint_state",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {}
}
3.4.18.2 响应:response_get_joint_state
接收到请求后,返回机器人各个关节当前状态。
{
"accid": "HU_D04_01_001",
"title": "response_get_joint_state",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"names": [], # 各个关节的名称
"q": [], # 各个关节的位置
"dq": [], # 各个关节的速度
"tau": [], # 各个关节的扭矩
"result": "success" # fail_not_data
}
}
3.4.18.3 消息推送:无
3.4.19 获取 IMU 数据
3.4.19.1 请求:request_get_imu_data
{
"accid": "HU_D04_01_001",
"title": "request_get_imu_data",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {}
}
3.4.19.2 响应:response_get_imu_data
{
"accid": "HU_D04_01_001",
"title": "response_get_imu_data",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success", # fail_no_data
"euler": [0.0, 0.0, 0.0], // 欧拉角 [roll, pitch, yaw] in degrees
"acc": [0.0, 0.0, 0.0], // 加速度 [x, y, z] in m/s²
"gyro": [0.0, 0.0, 0.0], // 陀螺仪角速度 [x, y, z] in rad/s
"quat": [0.0, 0.0, 0.0, 0.0] // 四元数 [w, x, y, z]
}
}
3.4.19.3 消息推送:无
3.5 灵巧手及夹爪协议接口
3.5.1 逐际二指夹爪
3.5.1.1 夹爪控制指令
3.5.1.1.1 请求:request_set_limx_2fclaw_cmd
此协议控制夹爪的抓取动作。
{
"accid": "HU_D04_01_001",
"title": "request_set_limx_2fclaw_cmd",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
# 如您同时给出以下数据,则会控制左夹爪运动
"left_opening": 50, # 开口度,0-100,无量纲(0对应最小闭合,100对应张开到最大)
"left_speed": 50, # 夹爪速度,0~100 无量纲(数值越大速度越快)
"left_force": 50, #力,夹爪夹持力,0~100 无单位(数值越大力越大)
# 如您同时给出以下数据,则会控制右夹爪运动
"right_opening": 50, # 开口度,0-100,无量纲(0对应最小闭合,100对应张开到最大)
"right_speed": 50, # 夹爪速度,0~100 无量纲(数值越大速度越快)
"right_force": 50, #力,夹爪夹持力,0~100 无单位(数值越大力越大)
}
}
3.5.1.1.2 响应:response_set_limx_2fclaw_cmd
{
"accid": "HU_D04_01_001",
"title": "response_set_limx_2fclaw_cmd",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # success: 成功, fail_motor: 电机错误
}
}
3.5.1.1.3 消息推送:无
3.5.1.2 获取夹爪状态信息
3.5.1.2.1 请求:request_get_limx_2fclaw_state
{
"accid": "HU_D04_01_001",
"title": "request_get_limx_2fclaw_state",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
}
}
3.5.1.2.2 响应:response_get_limx_2fclaw_state
返回夹爪状态信息
3.5.1.2.3 消息推送:无
3.5.2 逐际三指夹爪
3.5.2.1 夹爪控制指令
3.5.2.1.1 请求:request_set_limx_3fclaw_cmd
此协议控制夹爪的抓取动作。
3.5.2.1.2 响应:response_set_limx_3fclaw_cmd
{
"accid": "HU_D04_01_001",
"title": "response_set_limx_3fclaw_cmd",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # success: 成功, fail_motor: 电机错误
}
}
3.5.2.1.3 消息推送:无
3.5.2.2 获取夹爪状态信息
3.5.2.2.1 请求:request_get_limx_3fclaw_state
{
"accid": "HU_D04_01_001",
"title": "request_get_limx_3fclaw_state",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
}
}
3.5.2.2.2 响应:response_get_limx_3fclaw_state
返回夹爪状态信息
3.5.2.2.3 消息推送:无
3.5.3 因时二指夹爪
3.5.3.1 夹爪控制指令
3.5.3.1.1 请求:request_set_claw_cmd
此协议控制夹爪的抓取动作。
3.5.3.1.2 响应:response_set_claw_cmd
{
"accid": "HU_D04_01_001",
"title": "response_set_claw_cmd",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # success: 成功, fail_motor: 电机错误
}
}
3.5.3.1.3 消息推送:无
3.5.3.2 获取夹爪状态信息
3.5.3.2.1 请求:request_get_claw_state
{
"accid": "HU_D04_01_001",
"title": "request_get_claw_state",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
}
}
3.5.3.2.2 响应:response_get_claw_state
返回夹爪状态信息
3.5.3.2.3 消息推送:无
3.5.4 强脑 1 代灵巧手
3.5.4.1 灵巧手控制指令
3.5.4.1.1 请求:request_set_brainco_hand_cmd
{
"accid": "HU_D04_01_001",
"title": "request_set_brainco_hand_cmd",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
# 如您给定以下数据,则会控制左手运动
"left_thumb": 50, # 左手大拇指弯曲角度,0-100,无量纲
"left_thumb_aux": 50, # 左手大拇指内收角度,0-100,无量纲
"left_index": 50, # 左手食指弯曲角度,0-100,无量纲
"left_middle": 50, # 左手中指弯曲角度,0-100,无量纲
"left_ring": 50, # 左手无名指弯曲角度,0-100,无量纲
"left_pinky": 50, # 左手小指弯曲角度,0-100,无量纲
"left_mode": 3, # 力量等级 1:小 2:中 3:大。默认值为:2
# 如您给定以下数据,则会控制右手运动
"right_thumb": 50, # 右手大拇指弯曲角度,0-100,无量纲
"right_thumb_aux": 50, # 右手大拇指内收角度,0-100,无量纲
"right_index": 50, # 右手食指弯曲角度,0-100,无量纲
"right_middle": 50, # 右手中指弯曲角度,0-100,无量纲
"right_ring": 50, # 右手无名指弯曲角度,0-100,无量纲
"right_pinky": 50, # 右手小指弯曲角度,0-100,无量纲
"right_mode": 3 # 力量等级 1:小 2:中 3:大。默认值为:2
}
}
3.5.4.1.2 响应:response_set_brainco_hand_cmd
{
"accid": "HU_D04_01_001",
"title": "response_set_brainco_hand_cmd",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # success: 成功, fail_motor: 电机错误
}
}
3.5.4.1.3 消息推送:无
3.5.4.2 获取灵巧手状态
3.5.4.2.1 请求:request_get_brainco_hand_state
{
"accid": "HU_D04_01_001",
"title": "request_get_brainco_hand_state",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
}
}
3.5.4.2.2 响应:response_get_brainco_hand_state
{
"accid": "HU_D04_01_001",
"title": "response_get_brainco_hand_state",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"timestamp": 1672373633989, # 表示数据时戳,单位为毫秒
"left_thumb": 50, # 左手大拇指弯曲角度,0-100,无量纲
"left_thumb_aux": 50, # 左手大拇指内收角度,0-100,无量纲
"left_index": 50, # 左手食指弯曲角度,0-100,无量纲
"left_middle": 50, # 左手中指弯曲角度,0-100,无量纲
"left_ring": 50, # 左手无名指弯曲角度,0-100,无量纲
"left_pinky": 50, # 左手小指弯曲角度,0-100,无量纲
"right_thumb": 50, # 右手大拇指弯曲角度,0-100,无量纲
"right_thumb_aux": 50, # 右手大拇指内收角度,0-100,无量纲
"right_index": 50, # 右手食指弯曲角度,0-100,无量纲
"right_middle": 50, # 右手中指弯曲角度,0-100,无量纲
"right_ring": 50, # 右手无名指弯曲角度,0-100,无量纲
"right_pinky": 50 # 右手小指弯曲角度,0-100,无量纲
"result": "success" # success: 成功, fail_motor: 电机错误
}
}
3.5.4.2.3 消息推送:无
3.5.5 强脑 2 代灵巧手
3.5.5.1 灵巧手控制指令
3.5.5.1.1 请求:request_set_brainco2_hand_cmd
{
"accid": "HU_D04_01_001",
"title": "request_set_brainco2_hand_cmd",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
# 左手:
# 索引从0-5分别对应:拇指尖、拇指根、食指、中指、无名指、小指
# left_mode: 控制模式
# 0:退出控制
# 1:位置时间模式, 必须指定 left_pos、left_time 的值
# 2:位置速度模式, 必须指定 left_pos、left_vel 的值
# 3:力控模式, 必须指定 left_current 的值
# left_pos: 每个手指的目标位置, 单位为rad
# 范围分别为0-1.0297、0-1.5707、0-1.4137、0-1.4137、0-1.4137、0-1.4137
# left_vel: 每个手指的目标速度, 单位为rad/s
# 范围分别为0-2.5367、0-2.6180、0-2.2689、0-2.2689、0-2.2689、0-2.2689
# left_current: 每个手指的目标电流, 单位为mA, 范围为±1000mA
# left_time: 每个手指的控制时间, 单位为ms, 范围为1-2000ms
"left_mode": 1,
"left_pos": [0.5, 0.5, 0.5, 0.5, 0.5, 0.5],
"left_vel": [0.5, 0.5, 0.5, 0.5, 0.5, 0.5],
"left_current": [500, 500, 500, 500, 500, 500],
"left_time": [1000, 1000, 1000, 1000, 1000, 1000],
# 右手:
# 索引从0-5分别对应:拇指尖、拇指根、食指、中指、无名指、小指
# right_mode: 控制模式
# 0:退出控制
# 1:位置时间模式, 必须指定 right_pos、right_time 的值
# 2:位置速度模式, 必须指定 right_pos、right_vel 的值
# 3:力控模式, 必须指定 right_current 的值
# right_pos: 每个手指的目标位置, 单位为rad
# 范围分别为0-1.0297、0-1.5707、0-1.4137、0-1.4137、0-1.4137、0-1.4137
# right_vel: 每个手指的目标速度, 单位为rad/s
# 范围分别为0-2.5367、0-2.6180、0-2.2689、0-2.2689、0-2.2689、0-2.2689
# right_current: 每个手指的目标电流, 单位为mA, 范围为±1000mA
# right_time: 每个手指的控制时间, 单位为ms, 范围为1-2000ms
"right_mode": 1,
"right_pos": [0.5, 0.5, 0.5, 0.5, 0.5, 0.5],
"right_vel": [0.5, 0.5, 0.5, 0.5, 0.5, 0.5],
"right_current": [500, 500, 500, 500, 500, 500],
"right_time": [1000, 1000, 1000, 1000, 1000, 1000]
}
}
3.5.5.1.2 响应:response_set_brainco2_hand_cmd
{
"accid": "HU_D04_01_001",
"title": "response_set_brainco2_hand_cmd",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": "success" # success: 成功, fail_motor: 电机错误, fail_invalid_cmd: 非法指令
}
}
3.5.5.1.3 消息推送:无
3.5.5.2 获取灵巧手状态
3.5.5.2.1 请求:request_get_brainco2_hand_state
{
"accid": "HU_D04_01_001",
"title": "request_get_brainco2_hand_state",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
}
}
3.5.5.2.2 响应:response_get_brainco2_hand_state
{
"accid": "HU_D04_01_001",
"title": "response_get_brainco2_hand_state",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"timestamp": 1672373633989,
"left_mode": 1,
"left_pos": [0.5, 0.5, 0.5, 0.5, 0.5, 0.5],
"left_vel": [0.5, 0.5, 0.5, 0.5, 0.5, 0.5],
"left_current": [500, 500, 500, 500, 500, 500],
"left_time": [1000, 1000, 1000, 1000, 1000, 1000],
"right_mode": 1,
"right_pos": [0.5, 0.5, 0.5, 0.5, 0.5, 0.5],
"right_vel": [0.5, 0.5, 0.5, 0.5, 0.5, 0.5],
"right_current": [500, 500, 500, 500, 500, 500],
"right_time": [1000, 1000, 1000, 1000, 1000, 1000]
}
}
3.5.5.2.3 消息推送:无
3.6 全局消息协议接口
3.6.1 机器人状态信息
此协议将定时上报机器人状态信息。
| 上报状态信息 | 描述 |
|---|---|
accid |
机器人序列号 |
title |
notify_robot_info |
timestamp |
消息发出时间戳,单位为毫秒 |
guid |
消息的 guid 值,唯一标识这条消息 |
data |
存放消息内容 |
示例:
{
"accid": "HU_D04_01_001",
"title": "notify_robot_info",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": []
}
}
3.6.1.1 电池数据
{
"accid": "HU_D04_01_001",
"title": "notify_robot_info",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": [
......
{
"level": 0,
"name": "peripheral",
"message": "OK",
"hardware_id": "peripheral",
"values": [
{
"key": "bmsconn",
"value": "ON"
},
{
"key": "bat_chg",
"value": "OFF"
},
{
"key": "bat_off",
"value": "OFF"
},
{
"key": "bat_prt",
"value": "0"
},
{
"key": "bat_vol",
"value": "48830"
},
{
"key": "bat_cur",
"value": "2870"
},
{
"key": "battery",
"value": "29"
},
{
"key": "bat_temp0",
"value": "430"
},
{
"key": "bat_temp2",
"value": "430"
},
{
"key": "bat_temp4",
"value": "400"
},
{
"key": "battery_capacity",
"value": "9000mAh"
}
]
},
]
}
}
| 字段 | 含义 |
|---|---|
| bmsconn | 电池连接状态 :【OFF:未连接,ON:已连接】 |
| bat_chg | 电池充电器状态 :【OFF:未连接,ON:已连接】 |
| bat_off | 电池预关机状态 :【OFF:1s 后断电,ON:正常】 |
| bat_prt | 电池故障码:【 0:正常, 非 0:异常】 |
| bat_vol | 电池实时电压 单位:mV |
| bat_cur | 电池实时电流 单位:mA |
| battery | 电池电量百分比 0~100 |
| bat_temp0 | 电池温度 0~100 单位:x10℃ |
| bat_temp2 | 电池温度 0~100 单位:x10℃ |
| bat_temp4 | 电池温度 0~100 单位:x10℃ |
3.6.1.2 系统信息
{
"accid": "HU_D04_01_001",
"title": "notify_robot_info",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": [
{
"level": 0,
"name": "system_info",
"message": "system info",
"hardware_id": "system_info",
"values": [
{
"key": "ability_running",
"value": "ZeroTorque"
},
{
"key": "ecm_version",
"value": "1.1.2"
},
{
"key": "mode",
"value": "Remote"
},
{
"key": "motor_version",
"value": "1: 0.0.9; 2: 0.0.9; 3: 0.0.9; 4: 0.0.9; 5: 0.0.9; 6: 0.0.9; 7: 0.0.9; 8: 0.0.9; 9: 0.0.9; 10: 0.0.9; 11: 0.0.9; 12: 0.0.9; 13: 0.0.9; 14: 0.0.9; 15: 0.0.9; 16: 0.0.9; "
},
{
"key": "pms_version",
"value": "2.1.8"
},
{
"key": "robot_status",
"value": "ZeroTorque"
},
{
"key": "version",
"value": "robot-hu-d-2.1.0.20251225062343"
},
{
"key": "sn",
"value": "HU_D04_01_131"
}
]
}
]
}
}
| 字段 | 含义 |
|---|---|
| version | 主控版本 |
| ecm_version | 主站版本 |
| pms_version | 分电板版本 |
| motor_version | 电机版本 |
| sn | 机器人序列号 |
| robot_status | 机器人当前状态 |
| ability_running | 机器人当前运行的控制器 |
3.6.1.3 电机状态信息
{
"accid": "HU_D04_01_001",
"title": "notify_robot_info",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"result": [
{
"level": 1,
"name": "ethercatCommunicationExp",
"message": "WARN",
"hardware_id": "ethercat",
"values": [
{
"key": "ethercatCommunicationExp",
"value": "motor 17 MOTOR_LOST triggered HALF_STAND"
},
{
"key": "ethercatResetNormal",
"value": "ok!"
}
]
}
]
}
}
| 字段 | 含义 |
|---|---|
| level | 异常等级[0:ok 1:warn 2:error] |
| name | 异常类型 |
| message | 等级字符串 |
| hardware_id | 硬件 id |
| values | 该硬件的所有异常集合 |
3.6.2 遥控器数据
此协议将上报机器人遥控器数据。
| 上报数据信息 | 描述 |
|---|---|
accid |
机器人序列号 |
title |
notify_joy_data |
timestamp |
消息发出时间戳,单位为毫秒 |
guid |
消息的 guid 值,唯一标识这条消息 |
data |
存放消息内容 |
示例:
{
"accid": "HU_D04_01_001",
"title": "notify_joy_data",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {
"axes": [], # 遥感数据
"buttons": [] # 按键数据
}
}
3.7 协议接口调用示例
3.7.1 Python 示例
- 环境准备: 以 Ubuntu 20.04 系统为例,安装下面依赖
sudo apt install python3-dev python3-pip
sudo pip install websocket-client==1.8.0
- 运行脚本
python humanoid.py
- humanoid.py 实现
- ACCID:替换为真实的软件 SN
- ROBOT_IP: 一般情况,仿真为 127.0.0.1,真机为 10.192.1.2
import json
import uuid
import threading
import time
import websocket
from datetime import datetime
# Replace this ACCID value with your robot's actual serial number (SN)
ACCID = None
# Replace it with the real IP address of the robot.
# Usually, for simulation, it is: 127.0.0.1
# for a real machine, it is: 10.192.1.2
ROBOT_IP = "10.192.1.2"
# Atomic flag for graceful exit
should_exit = False
# WebSocket client instance
ws_client = None
# Generate dynamic GUID
def generate_guid():
return str(uuid.uuid4())
# Send WebSocket request with title and data
def send_request(title, data=None):
global ACCID
if data is None:
data = {}
# Create message structure with necessary fields
message = {
"accid": ACCID,
"title": title,
"timestamp": int(time.time() * 1000), # Current timestamp in milliseconds
"guid": generate_guid(),
"data": data
}
message_str = json.dumps(message)
# Send the message through WebSocket if client is connected
if ws_client:
ws_client.send(message_str)
# Handle user commands
def handle_commands():
global should_exit
while not should_exit:
command = input("Enter command ('prepare', 'servo', 'movej', 'movel', 'movep', 'head', 'waist', 'state', 'claw_cmd', 'claw_state', 'damping', 'zero') or 'exit' to quit:\n")
if command == "exit":
should_exit = True # Set exit flag to stop the loop
break
elif command == "prepare":
send_request("request_prepare") # request_prepare
elif command == "servo":
# Servo control mode flag from user
mode_input = input("Enable mode (0/1/2):").strip()
mode_value = int(mode_input) if mode_input in ('0','1','2') else 0
send_request("request_set_move_mode", {"mode": mode_value})
elif command == "movej":
send_request("request_moveJ", { # request_moveJ
"left": [-1.44532, 0.0987686, 0.179059, -1.64716, -0.0537614, 0.200834, -0.236136],
"right": [0.10103,-0.0987769,-0.179462,-1.64705,0.0527488,0.198867,0.235933],
"speed": 0.2
})
elif command == "movep":
send_request("request_moveP", { # request_moveP
"left_position": [0.089644,0.428712,0.0519788],
"left_quat": [0.269296,-0.119683,-0.489868,0.820478],
"right_position": [0.0835307,-0.531453,0.13568],
"right_quat": [-0.436152,-0.285065,0.265969,0.81103],
"speed": 0.1
})
elif command == "head":
send_request("request_moveJ", { # request_moveJ
"head_pitch": 0.5854,
"head_yaw": 0.5854,
"speed": 0.1
})
elif command == "waist":
send_request("request_moveJ", { # request_set_waist_and_height
"torso_height": 0.0,
"torso_pitch": 0.0,
"torso_roll": 0.0,
"torso_yaw": 0.0
})
elif command == "claw_cmd":
send_request("request_set_claw_cmd", { # request_set_claw_cmd
"left_opening": 100,
"left_speed": 500,
"left_force": 500,
"left_mode": 1,
"right_opening": 100,
"right_speed": 500,
"right_force": 500,
"right_mode": 1
})
elif command == "claw_state":
send_request("request_get_claw_state")
elif command == "state":
send_request("request_get_move_pose") # request_get_move_pose
elif command == "damping":
send_request("request_damping") # request_damping
elif command == "zero":
send_request("request_zero_torque") # request_zero_torque
# WebSocket on_open callback
def on_open(ws):
print("Connected!")
# Start handling commands in a separate thread
threading.Thread(target=handle_commands, daemon=True).start()
# WebSocket on_message callback
def on_message(ws, message):
global ACCID
root = json.loads(message)
title = root.get("title", "")
ACCID = root.get("accid", None)
if title != "notify_robot_info":
print(f"Received message: {message}") # Print the received message
# WebSocket on_close callback
def on_close(ws, close_status_code, close_msg):
print("Connection closed.")
# Close WebSocket connection
def close_connection(ws):
ws.close()
def main():
global ws_client
# Create WebSocket client instance
ws_client = websocket.WebSocketApp(
f"ws://{ROBOT_IP}:5000", # WebSocket server URI
on_open=on_open,
on_message=on_message,
on_close=on_close
)
# Configure socket send and receive buffer sizes
# Increase send buffer size to 2MB (default is typically much smaller)
# This helps prevent data loss when sending large messages or high-frequency data
ws_client.sock_opt = [("socket", "SO_SNDBUF", 2 * 1024 * 1024)]
# Increase receive buffer size to 2MB
# This allows handling larger incoming messages without truncation
ws_client.sock_opt.append(("socket", "SO_RCVBUF", 2 * 1024 * 1024))
# Run WebSocket client loop
print("Press Ctrl+C to exit.")
ws_client.run_forever()
if __name__ == "__main__":
main()
3.7.2 Linux C++ 示例
- 环境准备: 以 Ubuntu 20.04 系统为例,安装 websocketpp、nlohmann/json 和 boost 依赖:
sudo apt-get install libboost-all-dev libwebsocketpp-dev nlohmann-json3-dev
- 编译代码
g++ -std=c++11 humanoid humanoid.cpp -o humanoid humanoid -lssl -lcrypto -lboost_system -lpthread
- 运行程序
./humanoid
- humanoid.cpp 实现
#include <iostream>
#include <atomic>
#include <string>
#include <thread>
#include <chrono>
#include <websocketpp/client.hpp>
#include <websocketpp/config/asio.hpp>
#include <nlohmann/json.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
using json = nlohmann::json;
using websocketpp::client;
using websocketpp::connection_hdl;
// Replace this value with the actual serial number (SN) of the robot.
static std::string ACCID = "";
// Replace it with the real IP address of the robot.
// Usually, for simulation, it is: 127.0.0.1
// for a real machine, it is: 10.192.1.2
const std::string ROBOT_IP = "10.192.1.2";
// WebSocket client instance
static client<websocketpp::config::asio> ws_client;
// Atomic flag for graceful exit
static std::atomic<bool> should_exit(false);
// Connection handle for sending messages
static connection_hdl current_hdl;
// Generate dynamic GUID
static std::string generate_guid() {
boost::uuids::random_generator gen;
boost::uuids::uuid u = gen();
return boost::uuids::to_string(u);
}
// Send WebSocket request with title and data
static void send_request(const std::string& title, const json& data = json::object()) {
json message;
// Adding necessary fields to the message
message["accid"] = ACCID;
message["title"] = title;
message["timestamp"] = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
message["guid"] = generate_guid();
message["data"] = data;
std::string message_str = message.dump();
// Send the message through WebSocket
ws_client.send(current_hdl, message_str, websocketpp::frame::opcode::text);
}
// Handle user commands
void handle_commands() {
std::cout << "Enter command ('prepare', 'servo', 'movej', 'movel', 'movep', 'head', 'state', 'waist', 'claw_cmd', 'claw_state', 'damping', 'zero') or 'exit' to quit:\n";
while (!should_exit) {
std::string command;
std::cin >> command;
if (command == "exit") {
should_exit = true;
return;
} else if (command == "prepare") {
send_request("request_prepare");
} else if (command == "servo") {
int mode_value;
std::cout << "Enable mode (0/1/2): ";
if (!(std::cin >> mode_value)) {
std::cerr << "Error: Invalid input. Please enter 0, 1, or 2." << std::endl;
return;
}
if (mode_value < 0 || mode_value > 2) {
std::cerr << "Error: Invalid input. Please enter 0, 1, or 2." << std::endl;
return;
}
nlohmann::json data = {{"mode", mode_value}};
send_request("request_set_move_mode", data);
} else if (command == "movej") {
nlohmann::json data = {
{"left", {-1.44532, 0.0987686, 0.179059, -1.64716, -0.0537614, 0.200834, -0.236136}},
{"right", {0.10103,-0.0987769,-0.179462,-1.64705,0.0527488,0.198867,0.235933}},
{"speed", 0.2}
};
send_request("request_moveJ", data);
} else if (command == "movep") {
nlohmann::json data = {
{"left_position", {0.089644,0.428712,0.0519788}},
{"left_quat", {0.269296,-0.119683,-0.489868,0.820478}},
{"right_position", {0.0835307,-0.531453,0.13568}},
{"right_quat", {-0.436152,-0.285065,0.265969,0.81103}},
{"speed", 0.1}
};
send_request("request_moveP", data);
} else if (command == "head") {
nlohmann::json data = {
{"head_yaw", 0.5854},
{"head_pitch", 0.5854},
{"speed", 0.1}
};
send_request("request_moveJ", data);
} else if (command == "waist") {
nlohmann::json data = {
{"torso_height", 0.0},
{"torso_pitch", 0.0},
{"torso_roll", 0.0},
{"torso_yaw", 0.0}
};
send_request("request_moveJ", data);
} else if (command == "claw_cmd") {
nlohmann::json data = {
{"left_opening", 100},
{"left_speed", 500},
{"left_force", 500},
{"left_mode", 1},
{"right_opening", 100},
{"right_speed", 500},
{"right_force", 500},
{"right_mode", 1}
};
send_request("request_set_claw_cmd", data);
} else if (command == "claw_state") {
send_request("request_get_claw_state");
} else if (command == "state") {
send_request("request_get_move_pose");
} else if (command == "damping") {
send_request("request_damping");
} else if (command == "zero") {
send_request("request_zero_torque");
}
sleep(1);
std::cout << "\nEnter command ('prepare', 'servo', 'movej', 'movel', 'movep', 'servop', 'head', 'waist', 'state', 'damping', 'zero') or 'exit' to quit:\n";
}
}
// WebSocket open callback
static void on_open(connection_hdl hdl) {
std::cout << "Connected!" << std::endl;
// Save connection handle for sending messages later
current_hdl = hdl;
// Start handling commands in a separate thread
std::thread(handle_commands).detach();
}
// WebSocket TCP initialization handler
static void on_tcp_init(connection_hdl hdl)
{
auto con = ws_client.get_con_from_hdl(hdl);
// Obtain the underlying TCP socket
auto& socket = con->get_socket().lowest_layer();
// Configure socket options
try {
boost::system::error_code ec;
// Set send buffer size (e.g., 2MB)
const size_t sendBufferSize = 2 * 1024 * 1024;
socket.set_option(websocketpp::lib::asio::socket_base::send_buffer_size(sendBufferSize), ec);
if (ec)
{
printf("Failed to set send buffer size: %s", ec.message().c_str());
}
// Set receive buffer size (e.g., 2MB)
const size_t recvBufferSize = 2 * 1024 * 1024;
socket.set_option(websocketpp::lib::asio::socket_base::receive_buffer_size(recvBufferSize), ec);
if (ec)
{
printf("Failed to set receive buffer size: %s", ec.message().c_str());
}
// Disable Nagle's algorithm to reduce latency
socket.set_option(websocketpp::lib::asio::ip::tcp::no_delay(true), ec);
if (ec)
{
printf("Failed to disable Nagle's algorithm: %s", ec.message().c_str());
}
} catch (const std::exception& e) {
printf("Socket configuration exception: %s", e.what());
}
}
// WebSocket message callback
static void on_message(connection_hdl hdl, client<websocketpp::config::asio>::message_ptr msg) {
// Parse JSON data from message payload
json data = json::parse(msg->get_payload());
// Extract 'accid' field if present
if (data.contains("accid") && data["accid"].is_string() && ACCID.empty()) {
ACCID = data["accid"].get<std::string>();
}
if (msg->get_payload().find("notify_robot_info") == std::string::npos) {
std::cout << "Received message: " << msg->get_payload() << std::endl;
}
}
// WebSocket close callback
static void on_close(connection_hdl hdl) {
std::cout << "Connection closed." << std::endl;
}
// Close WebSocket connection
static void close_connection(connection_hdl hdl) {
ws_client.close(hdl, websocketpp::close::status::normal, "Normal closure"); // Close connection normally
}
int main() {
ws_client.init_asio(); // Initialize ASIO for WebSocket client
ws_client.set_access_channels(websocketpp::log::alevel::none);
// Set WebSocket event handlers
ws_client.set_open_handler(&on_open); // Set open handler
ws_client.set_message_handler(&on_message); // Set message handler
ws_client.set_close_handler(&on_close); // Set close handler
ws_client.set_tcp_init_handler(&on_tcp_init); // Set tcp init handler
std::string server_uri = "ws://" + ROBOT_IP + ":5000"; // WebSocket server URI
websocketpp::lib::error_code ec;
client<websocketpp::config::asio>::connection_ptr con = ws_client.get_connection(server_uri, ec); // Get connection pointer
if (ec) {
std::cout << "Error: " << ec.message() << std::endl;
return 1; // Exit if connection error occurs
}
connection_hdl hdl = con->get_handle(); // Get connection handle
ws_client.connect(con); // Connect to server
std::cout << "Press Ctrl+C to exit." << std::endl;
// Run the WebSocket client loop
ws_client.run();
return 0;
}
3.7.3 JavaScript 示例
- 运行 humanoid.html: 将 humanoid.html 文件保存到电脑中,然后在浏览器中打开 humanoid.html 运行。
- humanoid.html 实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket Robot Control</title>
<style>
#commandInput {
width: 700px;
padding: 10px;
font-size: 14px;
}
</style>
</head>
<body>
<h2>Dual ARM Commands</h2>
<input type="text" id="commandInput" placeholder="Enter command ('prepare', 'servo', 'movej', 'movel', 'movep', 'head', 'waist', 'claw_cmd', 'claw_state', 'state', 'damping', 'zero', 'exit')">
<p>Type a command and press Enter.</p>
<script>
// Replace this ACCID value with your robot's actual serial number (SN)
let ACCID = "";
// WebSocket client instance
let wsClient = null;
// Generate dynamic GUID
function generateGuid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0,
v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
// Send WebSocket request with title and data
function sendRequest(title, data = {}) {
const message = {
accid: ACCID,
title: title,
timestamp: Date.now(),
guid: generateGuid(),
data: data
};
if (wsClient && wsClient.readyState === WebSocket.OPEN) {
wsClient.send(JSON.stringify(message));
}
}
// Handle user commands
function handleCommands() {
const commandInput = document.getElementById('commandInput');
commandInput.addEventListener('keydown', function(event) {
if (event.key === 'Enter') {
const command = commandInput.value.trim();
commandInput.value = '';
switch (command) {
case 'prepare':
sendRequest('request_prepare');
break;
case 'servo':
const modeInput = prompt("Enable Servo (0/1/2):").trim();
let modeValue = 0;
// 尝试把输入解析为整数
const n = parseInt(modeInput, 10);
if (!Number.isNaN(n) && (n === 0 || n === 1 || n === 2)) {
modeValue = n;
} else {
// 非法输入,给出错误并返回
alert("Error: Invalid input. Please enter 0, 1, or 2.");
return;
}
sendRequest('request_set_move_mode', { mode: modeValue });
break;
case 'movej':
sendRequest('request_moveJ', {
left: [-1.44532, 0.0987686, 0.179059, -1.64716, -0.0537614, 0.200834, -0.236136],
right: [0.10103,-0.0987769,-0.179462,-1.64705,0.0527488,0.198867,0.235933],
speed: 0.2
});
break;
case 'movep':
sendRequest('request_moveP', {
left_position: [0.089644,0.428712,0.0519788],
left_quat: [0.269296,-0.119683,-0.489868,0.820478],
right_position: [0.0835307,-0.531453,0.13568],
right_quat: [-0.436152,-0.285065,0.265969,0.81103],
speed: 0.1
});
break;
case 'head':
sendRequest('request_moveJ', {
head_yaw: 0.5854,
head_pitch: 0.5854,
speed: 0.1
});
break;
case 'waist':
sendRequest('request_moveJ', {
torso_height: 0.0,
torso_pitch: 0.0,
torso_roll: 0.0,
torso_yaw: 0.0
});
break;
case 'claw_cmd':
sendRequest('request_set_claw_cmd', {
left_opening: 100,
left_speed: 500,
left_force: 500,
left_mode: 1,
right_opening: 100,
right_speed: 500,
right_force: 500,
right_mode: 1
});
break;
case 'claw_state':
sendRequest('request_get_claw_state');
break;
case 'state':
sendRequest('request_get_move_pose');
break;
case 'damping':
sendRequest('request_damping');
break;
case 'zero':
sendRequest('request_zero_torque');
break;
case 'exit':
wsClient.close();
break;
default:
alert("Invalid command. Try again.");
}
}
});
}
// WebSocket onOpen callback
function onOpen() {
console.log("Connected!");
handleCommands();
}
// WebSocket onMessage callback
function onMessage(event) {
try {
const message = JSON.parse(event.data);
// Dynamically set ACCID from message if not already set
if (!ACCID && message.accid) {
ACCID = message.accid;
console.log(`ACCID set to: ${ACCID}`);
}
} catch (error) {
console.log("Failed to parse message:", error);
}
if (event.data.includes('notify_robot_info')) return;
console.log("Received message:", event.data);
}
// WebSocket onClose callback
function onClose(event) {
console.log("Connection closed.");
}
// Initialize WebSocket client
function initWebSocket() {
// Replace it with the real IP address of the robot.
// Usually, for simulation, it is: 127.0.0.1
// for a real machine, it is: 10.192.1.2
wsClient = new WebSocket('ws://10.192.1.2:5000');
wsClient.onopen = onOpen;
wsClient.onmessage = onMessage;
wsClient.onclose = onClose;
console.log("Press Ctrl+C to exit.");
}
// Start WebSocket connection when the page loads
window.onload = initWebSocket;
</script>
</body>
</html>
4 底层运动控制开发接口
跨平台底层运动控制开发接口库提供统一的 C++/Python API,兼容 ROS1、ROS2 及非 ROS 系统,实现运动控制算法的快速移植与部署。通过硬件抽象层和标准化通信协议,开发者可无缝切换仿真与真实硬件环境,显著降低多平台适配成本。
注意:
- 使用底层控制开发接口时,需通过按键
R1+START切换开发者模式,此时高层开发接口会被禁用,机器人只响应上下电和校零遥控器指令。- 底层接口代码调用示例可参考 RL 部署训练。
- 切换到开发者模式后,掉电模式会保留,退出开发者模式按键:
L2+○
4.1 C++ 运动控制开发接口
4.1.1 getInstance 接口
| 函数名 | getInstance |
|---|---|
| 函数原型 | static Humanoid* getInstance(); |
| 功能概述 | 获取 Humanoid 机器人类单例实例的指针 |
| 参数 | 无 |
| 返回值 | Humanoid*,指向 Humanoid 实例的指针 |
| 备注 | 使用了单例模式,确保 Humanoid 类只有一个实例存在于程序中 |
代码示例:
#include <thread>
// 包含 limxsdk::Humanoid 头文件,用于引入 Humanoid 类
#include "limxsdk/humanoid.h"
// 使用 limxsdk 命名空间,简化对 Humanoid 类的引用
using namespace limxsdk;
int main(int argc, char *argv[]){
// 获取 Humanoid 类的单例实例
Humanoid* robot = Humanoid::getInstance();
// 无限循环以保持程序运行
while (true)
{
// 休眠 1000 毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
return 0;
}
4.1.2 init 接口
| 函数名 | init |
|---|---|
| 函数原型 | bool init(const std::string& robot_ip_address = "127.0.0.1"); |
| 功能概述 | 初始化运动控制算法程序的通信运行环境,通常在主函数中调用其它接口之前调用,完成初始化工作。 |
| 参数 | robot_ip_address:机器人的 IP 地址。对于仿真,通常设置为 "127.0.0.1",而对于真实机器人,可能设置为 "10.192.1.2"。 |
| 返回值 | 如果初始化成功,则返回 true;否则返回 false。 |
| 备注 | 无 |
代码示例:
#include <thread>
// 包含 limxsdk::Humanoid 头文件,用于引入 Humanoid 类
#include "limxsdk/humanoid.h"
// 使用 limxsdk 命名空间,简化对 Humanoid 类的引用
using namespace limxsdk;
int main(int argc, char *argv[]){
// 获取 Humanoid 类的单例实例
Humanoid* robot = Humanoid::getInstance();
// 默认机器人 IP 地址
std::string robot_ip = "127.0.0.1";
if (argc > 1)
{
// 如果提供了命令行参数,则使用命令行参数作为机器人 IP 地址
robot_ip = argv[1];
}
// 初始化运动控制算法程序的通信运行环境
if (!robot->init(robot_ip))
{
// 如果初始化失败,则退出程序
exit(1);
}
// 无限循环以保持程序运行
while (true)
{
// 休眠 1000 毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
return 0;
}
4.1.3 getMotorNumber 接口
| 函数名 | getMotorNumber |
|---|---|
| 函数原型 | uint32_t getMotorNumber(); |
| 功能概述 | 获取机器人中的电机数量。 |
| 参数 | 无 |
| 返回值 | 返回一个无符号整数,表示机器人中的总电机数量。 |
| 备注 | 无 |
代码示例:
#include <thread>
// 包含 limxsdk::Humanoid 头文件,用于引入 Humanoid 类
#include "limxsdk/humanoid.h"
// 使用 limxsdk 命名空间,简化对 Humanoid 类的引用
using namespace limxsdk;
int main(int argc, char *argv[]){
// 获取 Humanoid 类的单例实例
Humanoid* robot = Humanoid::getInstance();
// 默认机器人 IP 地址
std::string robot_ip = "127.0.0.1";
if (argc > 1)
{
// 如果提供了命令行参数,则使用命令行参数作为机器人 IP 地址
robot_ip = argv[1];
}
// 初始化运动控制算法程序的通信运行环境
if (!robot->init(robot_ip))
{
// 如果初始化失败,则退出程序
exit(1);
}
// 获取机器人中的电机数量
uint32_t motor_num = robot->getMotorNumber();
// 无限循环以保持程序运行
while (true)
{
// 休眠 1000 毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
return 0;
}
4.1.4 subscribeImuData 接口
| 函数名 | subscribeImuData |
|---|---|
| 函数原型 | void subscribeImuData(std::function<void(const ImuDataConstPtr&)> cb); |
| 功能概述 | 订阅机器人的 IMU数据,并在接收到新的 IMU 数据时调用指定的回调函数。 |
| 参数 | cb: 用于处理新 IMU 数据的回调函数。 |
| 返回值 | 无 |
备注:ImuData 数据结构原型如下:
/**
* @struct ImuData
*
* @brief 表示基于传感器反馈的机器人 IMU 数据的结构体。
*
* 此结构体封装了 IMU 数据,包括加速度计、陀螺仪和四元数。
*/
struct ImuData {
uint64_t stamp; // 时间戳,以纳秒为单位,通常表示记录或生成此数据时的时间。
float acc[3]; // 用于存储 IMU 加速度计数据,以跟踪沿三个轴(X、Y、Z)的线性加速度。
float gyro[3]; // 用于存储 IMU 陀螺仪数据,以跟踪沿三个轴(X、Y、Z)的角速度或旋转速度。
float quat[4]; // 用于存储 IMU 四元数数据,表示在三维空间中的方向(w、x、y、z)。
};
// 智能指针类型别名
typedef std::shared_ptr<ImuData> ImuDataPtr;
typedef std::shared_ptr<ImuData const> ImuDataConstPtr;
代码示例:
#include <thread>
// 包含 limxsdk::Humanoid 头文件,用于引入 Humanoid 类
#include "limxsdk/humanoid.h"
// 使用 limxsdk 命名空间,简化对 Humanoid 类的引用
using namespace limxsdk;
int main(int argc, char *argv[]){
// 获取 Humanoid 类的单例实例
Humanoid* robot = Humanoid::getInstance();
// 默认机器人 IP 地址
std::string robot_ip = "127.0.0.1";
if (argc > 1)
{
// 如果提供了命令行参数,则使用命令行参数作为机器人 IP 地址
robot_ip = argv[1];
}
// 初始化运动控制算法程序的通信运行环境
if (!robot->init(robot_ip))
{
// 如果初始化失败,则退出程序
exit(1);
}
// 订阅机器人状态更新,并指定回调函数
robot->subscribeImuData([&](const ImuDataConstPtr& msg) {
// 在这里处理接收到的 ImuData 数据
// 注意:回调函数会在收到ImuData时被调用
});
// 无限循环以保持程序运行
while (true)
{
// 休眠 1000 毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
return 0;
}
4.1.5 subscribeRobotState 接口
| 函数名 | subscribeRobotState |
|---|---|
| 函数原型 | void subscribeRobotState(std::function<void(const RobotStateConstPtr&)> cb); |
| 功能概述 | 订阅接收关于机器人状态的更新。 |
| 参数 | cb:回调函数,当接收到机器人状态更新时将被调用。回调函数参数指向 RobotState 对象的常量指针。 |
| 返回值 | 无 |
备注:RobotState 数据结构原型如下:
/**
* @struct RobotState
*
* @brief 代表基于传感器反馈的机器人状态的结构体。
*
* 此结构封装了各种数据点,可用于监控和控制机器人,包括 IMU 数据(加速度计、陀螺仪、四元数)、输出扭矩、当前角度和速度等。
*/
struct RobotState {
// 默认构造函数
RobotState() { }
// 带参数的构造函数,用于初始化向量大小为 motor_num 的 tau、q、dq 向量,初始值均为 0.0
RobotState(int motor_num)
: tau(motor_num, 0.0)
, q(motor_num, 0.0)
, dq(motor_num, 0.0)
, motor_names(motor_num, "") { }
uint64_t stamp; // 时间戳,通常表示记录或生成这些数据的时间,以纳秒为单位
std::vector<float> tau; // 用于存储当前估计的输出扭矩(以牛顿米为单位)的向量
std::vector<float> q; // 用于存储当前角度(以弧度为单位)的向量
std::vector<float> dq; // 用于存储当前速度(以弧度每秒为单位)的向量
std::vector<std::string> motor_names; // 用于存储机器人各个关节名称的向量
};
// 智能指针类型别名
typedef std::shared_ptr<RobotState> RobotStatePtr;
typedef std::shared_ptr<RobotState const> RobotStateConstPtr;
代码示例:
#include <thread>
// 包含 limxsdk::Humanoid 头文件,用于引入 Humanoid 类
#include "limxsdk/humanoid.h"
// 使用 limxsdk 命名空间,简化对 Humanoid 类的引用
using namespace limxsdk;
int main(int argc, char *argv[]){
// 获取 Humanoid 类的单例实例
Humanoid* robot = Humanoid::getInstance();
// 默认机器人 IP 地址
std::string robot_ip = "127.0.0.1";
if (argc > 1)
{
// 如果提供了命令行参数,则使用命令行参数作为机器人 IP 地址
robot_ip = argv[1];
}
// 初始化运动控制算法程序的通信运行环境
if (!robot->init(robot_ip))
{
// 如果初始化失败,则退出程序
exit(1);
}
// 订阅机器人状态更新,并指定回调函数
robot->subscribeRobotState([&](const RobotStateConstPtr& msg) {
// 在这里处理接收到的 RobotState 数据
// 注意:回调函数会在收到机器人状态更新时被调用
});
// 无限循环以保持程序运行
while (true)
{
// 休眠 1000 毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
return 0;
}
4.1.6 publishRobotCmd 接口
| 函数名 | publishRobotCmd |
|---|---|
| 函数原型 | bool publishRobotCmd(const RobotCmd& cmd); |
| 功能概述 | 发布一个命令来控制机器人的动作。 |
| 参数 | cmd:表示所需机器人命令的 RobotCmd 对象。 |
| 返回值 | 无 |
备注:RobotCmd 数据结构原型如下:
/**
* @struct RobotCmd
*
* @brief 代表控制机器人的命令的结构体。
*
* 这个结构体包含了可以用于控制机器人的各种命令,包括期望的工作模式、期望的角度、期望的速度、期望的输出扭矩、期望的位置刚度和期望的速度刚度。
*/
struct RobotCmd {
RobotCmd() { }
RobotCmd(int motor_num)
: mode(motor_num, 0)
, q(motor_num, 0.0)
, dq(motor_num, 0.0)
, tau(motor_num, 0.0)
, Kp(motor_num, 0.0)
, Kd(motor_num, 0.0)
, motor_names(motor_num, "") { }
uint64_t stamp; // 时间戳(以纳秒为单位),通常表示记录或生成数据时的时间。
std::vector<uint8_t> mode; // 0: 力矩模式控制;1:速度模式控制;2:位置模式控制,默认设置为:0
std::vector<float> q; // 存储期望角度的向量(以弧度为单位)。
std::vector<float> dq; // 存储期望速度的向量(以弧度每秒为单位)。
std::vector<float> tau; // 存储期望输出扭矩的向量(以牛顿米为单位)。
std::vector<float> Kp; // 存储期望位置刚度的向量(以牛顿米每弧度为单位)。
std::vector<float> Kd; // 存储期望速度刚度的向量(以牛顿米每弧度每秒为单位)。
std::vector<std::string> motor_names; // 存储期望控制的机器人关节名称
};
// 智能指针类型别名
typedef std::shared_ptr<RobotCmd> RobotCmdPtr;
typedef std::shared_ptr<RobotCmd const> RobotCmdConstPtr;
代码示例:
#include <thread>
// 包含 limxsdk::Humanoid 头文件,用于引入 Humanoid 类
#include "limxsdk/humanoid.h"
// 使用 limxsdk 命名空间,简化对 Humanoid 类的引用
using namespace limxsdk;
int main(int argc, char *argv[]){
// 获取 Humanoid 类的单例实例
Humanoid* robot = Humanoid::getInstance();
// 默认机器人 IP 地址
std::string robot_ip = "127.0.0.1";
if (argc > 1)
{
// 如果提供了命令行参数,则使用命令行参数作为机器人 IP 地址
robot_ip = argv[1];
}
// 初始化运动控制算法程序的通信运行环境
if (!robot->init(robot_ip))
{
// 如果初始化失败,则退出程序
exit(1);
}
// 获取机器人中的电机数量
uint32_t motor_num = robot->getMotorNumber();
// 创建一个包含机器人电机数量的 RobotCmd 对象
RobotCmd cmd(motor_num);
// 发布控制指令
robot->publishRobotCmd(cmd);
// 无限循环以保持程序运行
while (true)
{
// 休眠 1000 毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
return 0;
}
4.1.7 subscribeSensorJoy 接口
| 函数名 | subscribeSensorJoy |
|---|---|
| 函数原型 | void subscribeSensorJoy(std::function<void(const SensorJoyConstPtr&)> cb); |
| 功能概述 | 在真机部署中,该方法用于订阅来自机器人遥控器的数据。当机器人接收到遥控器的数据时,将会调用指定的回调函数,并传递包含遥控器数据的 SensorJoy 结构体常量指针给回调函数进行处理。 |
| 参数 | cb: 表示回调函数,用于接收机器人遥控器的数据。回调函数的参数类型为 SensorJoyConstPtr,即指向 SensorJoy 结构体常量的共享指针。 |
| 返回值 | 无 |
备注:SensorJoy 数据结构原型如下:
/**
* @struct SensorJoy
*
* @brief 机器人遥控器数据的结构体。
*
* 该结构体包含了与遥控器相关的时间戳信息,以及摇杆和按钮值。
*/
struct SensorJoy {
uint64_t stamp; // 与传感器输入相关的时间戳,单位为纳秒。
std::vector<float> axes; // 表示操纵摇杆的值。
std::vector<int32_t> buttons; // 表示操纵按钮状态的值。
};
// SensorJoy 智能指针类型的定义
typedef std::shared_ptr<SensorJoy> SensorJoyPtr;
typedef std::shared_ptr<const SensorJoy> SensorJoyConstPtr;
代码示例:
#include <thread>
// 包含 limxsdk::Humanoid 头文件,用于引入 Humanoid 类
#include "limxsdk/humanoid.h"
// 使用 limxsdk 命名空间,简化对 Humanoid 类的引用
using namespace limxsdk;
int main(int argc, char *argv[]){
// 获取 Humanoid 类的单例实例
Humanoid* robot = Humanoid::getInstance();
// 默认机器人 IP 地址
std::string robot_ip = "127.0.0.1";
if (argc > 1)
{
// 如果提供了命令行参数,则使用命令行参数作为机器人 IP 地址
robot_ip = argv[1];
}
// 初始化运动控制算法程序的通信运行环境
if (!robot->init(robot_ip))
{
// 如果初始化失败,则退出程序
exit(1);
}
// 订阅机器人遥控器数据
robot->subscribeSensorJoy([&](const limxsdk::SensorJoyConstPtr &joy) {
// L1 & R1 按下
if (joy->buttons[4] == 1 && joy->buttons[7] == 1)
{
// 在这里执行相关操作
}
// 处理摇杆数据
double axes_left_horizontal = joy->axes[0];
double axes_left_vertical = joy->axes[1];
double axes_right_horizontal = joy->axes[2];
double axes_right_vertical = joy->axes[3];
});
// 无限循环以保持程序运行
while (true)
{
// 休眠 1000 毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
return 0;
}
4.1.8 subscribeDiagnosticValue 接口
| 函数名 | subscribeDiagnosticValue |
|---|---|
| 函数原型 | void subscribeDiagnosticValue(std::function<void(const DiagnosticValueConstPtr&)> cb); |
| 功能概述 | 在真机部署中,该方法用于订阅机器人的诊断值和状态信息。当机器人发出诊断值时,系统会调用指定的回调函数,并传递包含诊断值的 DiagnosticValue 结构体常量指针给回调函数进行处理。这可以帮助实时监控机器人的健康状态,并及时做出反应以处理可能的问题。 |
| 参数 | cb: 用于接收机器人诊断值的回调函数,其参数类型为 DiagnosticValueConstPtr,即指向 DiagnosticValue 结构体常量的共享指针。DiagnosticValue 结构体包含了机器人诊断值的信息,包括时间戳、级别、名称、代码和消息字段。 |
| 返回值 | 无 |
备注:DiagnosticValue 数据结构原型如下:
/**
* @struct DiagnosticValue
*
* @brief 结构体,表示诊断值。
*
* 此结构体包含有关诊断级别、名称、代码和消息的信息。
*/
struct DiagnosticValue {
enum { OK = 0 }; // 正常状态的诊断级别
enum { WARN = 1 }; // 警告状态的诊断级别
enum { ERROR = 2 }; // 错误状态的诊断级别
uint64_t stamp; // 时间戳,单位为纳秒。
int32_t level; // 与诊断值相关联的级别。
std::string name; // 标识诊断值的名称。
int32_t code; // 与诊断值对应的代码。
std::string message; // 与诊断值相关的详细消息。
};
// DiagnosticValue 智能指针类型的定义
typedef std::shared_ptr<DiagnosticValue> DiagnosticValuePtr;
typedef std::shared_ptr<DiagnosticValue const> DiagnosticValueConstPtr;
代码示例:
#include <thread>
// 包含 limxsdk::Humanoid 头文件,用于引入 Humanoid 类
#include "limxsdk/humanoid.h"
// 使用 limxsdk 命名空间,简化对 Humanoid 类的引用
using namespace limxsdk;
int main(int argc, char *argv[]){
// 获取 Humanoid 类的单例实例
Humanoid* robot = Humanoid::getInstance();
// 默认机器人 IP 地址
std::string robot_ip = "127.0.0.1";
if (argc > 1)
{
// 如果提供了命令行参数,则使用命令行参数作为机器人 IP 地址
robot_ip = argv[1];
}
// 初始化运动控制算法程序的通信运行环境
if (!robot->init(robot_ip))
{
// 如果初始化失败,则退出程序
exit(1);
}
// 订阅机器人诊断数据
robot->subscribeDiagnosticValue([&](const DiagnosticValueConstPtr& msg) {
// 在这里处理机器人诊断值
// 例如,可以根据诊断值的级别和消息来采取相应的措施
std::cout << "Diagnostic Value: " << msg->name << std::endl;
std::cout << "Level: " << msg->level << std::endl;
std::cout << "Code: " << msg->code << std::endl;
std::cout << "Message: " << msg->message << std::endl;
});
// 无限循环以保持程序运行
while (true)
{
// 休眠 1000 毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
return 0;
}
4.1.9 publishJsonMessage 接口
| 函数名 | publishJsonMessage |
|---|---|
| 函数原型 | void publishJsonMessage(const std::string &json_payload); |
| 功能概述 | 向机器人发送 "上层应用协议接口" 的 JSON 格式消息。 |
| 参数 | json_payload: 符合 "上层应用协议接口" 协议的 JSON 字符串。 示例:{"accid": "xxx", "title": "request_xxx", "timestamp": xxx, "guid": "xxx", "data": {}} |
| 返回值 | 无 |
| 备注 | 高阶开发模式下有效 |
代码示例
#include <thread>
#include "limxsdk/humanoid.h"
using namespace limxsdk;
int main(int argc, char *argv[]) {
Humanoid* robot = Humanoid::getInstance();
std::string robot_ip = "10.192.1.2";
if (argc > 1) {
robot_ip = argv[1];
}
if (!robot->init(robot_ip)) {
exit(1);
}
std::string json_payload = R"({
"accid": "HU_D03_01",
"title": "request_get_joint_state",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {}
})";
robot->publishJsonMessage(json_payload);
while (true) {
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
return 0;
}
4.1.10 subscribeJsonMessage 接口
| 函数名 | subscribeJsonMessage |
|---|---|
| 函数原型 | void subscribeJsonMessage(std::function<void(const std::string &)> cb); |
| 功能概述 | 注册一个回调函数,用于处理来自机器人的 "上层应用协议接口" 调用的响应和通知。 该回调函数会在以下两种场景中被调用: 1. 当机器人对之前发送的 JSON 命令(publishJsonMessage)返回响应时 2. 当机器人主动发起通知(未经请求的消息)时 |
| 参数 | cb: 回调函数,原型为 void(const std::string &json_payload) 其中 json_payload 包含: - 命令响应:{"accid": "xxx", "title": "response_xxx", "timestamp": xxx, "guid": "xxx", "data": {}} - 通知:{"accid": "xxx", "title": "notify_xxx", "timestamp": xxx, "guid": "xxx", "data": {}} |
| 返回值 | 无 |
| 备注 | 高阶开发模式下有效 |
代码示例:
#include <thread>
#include "limxsdk/humanoid.h"
using namespace limxsdk;
int main(int argc, char *argv[]) {
Humanoid* robot = Humanoid::getInstance();
std::string robot_ip = "10.192.1.2";
if (argc > 1) {
robot_ip = argv[1];
}
if (!robot->init(robot_ip)) {
exit(1);
}
robot->subscribeJsonMessage([&](const std::string & json_payload) {
std::cout << json_payload << std::endl;
});
while (true) {
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
return 0;
}
4.1.11 参考例程
Github: https://github.com/limxdynamics/humanoid-rl-deploy-ros
4.2 Python 运动控制开发接口
提供与 C++ 相同功能的 Python 运动控制算法开发接口,使得不熟悉 C++ 编程语言的开发者能够使用 Python 进行运动控制算法的开发。
4.2.1 安装运动控制开发库
- Linux x86_64 环境
pip install python3/amd64/limxsdk-*-py3-none-any.whl
- Linux aarch64 环境
pip install python3/aarch64/limxsdk-*-py3-none-any.whl
- Windows 环境
pip install python3/win/limxsdk-*-py3-none-any.whl
4.2.2 init 接口
| 函数名 | init |
|---|---|
| 函数原型 | def init(self, robot_type: robot.RobotType) |
| 功能概述 | 在初始化时,指定机器人的类型,并创建一个相应类型的本地机器人实例。 |
| 参数 | robot_type:表示机器人类型的枚举值,点足为RobotType.Humanoid。 |
| 返回值 | 无 |
| 备注 | 无 |
代码示例:
import sys
import limxsdk.robot.Robot as Robot
import limxsdk.robot.RobotType as RobotType
if __name__ == '__main__':
# 创建一个类型为Humanoid的Robot实例
robot = Robot(RobotType.Humanoid)
4.2.3 init 接口
| 函数名 | init |
|---|---|
| 函数原型 | def init(self, robot_ip: str = "127.0.0.1") |
| 功能概述 | 初始化运动控制算法程序的通信运行环境,通常在主函数中调用其它接口之前调用,完成初始化工作。 |
| 参数 | robot_ip:机器人的 IP 地址。对于仿真,通常设置为 "127.0.0.1",而对于真实机器人,可能设置为 "10.192.1.2"。 |
| 返回值 | 成功:返回True 失败:返回False |
| 备注 | 无 |
代码示例:
import sys
import limxsdk.robot.Robot as Robot
import limxsdk.robot.RobotType as RobotType
if __name__ == '__main__':
# 创建一个类型为Humanoid的Robot实例
robot = Robot(RobotType.Humanoid)
robot_ip = "127.0.0.1"
# 检查是否提供了命令行参数作为机器人IP
if len(sys.argv) > 1:
robot_ip = sys.argv[1]
# 使用IP地址初始化机器人的通信运行环境
if not robot.init(robot_ip):
sys.exit()
4.2.4 getMotorNumber 接口
| 函数名 | getMotorNumber |
|---|---|
| 函数原型 | def getMotorNumber(self) |
| 功能概述 | 获取机器人中的电机数量。 |
| 参数 | 无 |
| 返回值 | 返回一个无符号整数,表示机器人中的总电机数量。 |
| 备注 | 通常情况下,点足机器人的电机数量为6个 |
代码示例:
import sys
import limxsdk.robot.Robot as Robot
import limxsdk.robot.RobotType as RobotType
if __name__ == '__main__':
# 创建一个 Humanoid 类型的 Robot 实例
robot = Robot(RobotType.Humanoid)
robot_ip = "127.0.0.1"
# 检查是否提供了机器人 IP 的命令行参数
if len(sys.argv) > 1:
robot_ip = sys.argv[1]
# 使用 robot_ip 初始化机器人
if not robot.init(robot_ip):
sys.exit()
# 获取机器人中的电机数量
motor_number = robot.getMotorNumber()
4.2.5 subscribeImuData 接口
| 函数名 | subscribeImuData |
|---|---|
| 函数原型 | def subscribeImuData(self, callback: Callable[[datatypes.ImuData], Any]) |
| 功能概述 | 订阅机器人的 IMU数据,并在接收到新的 IMU 数据时调用指定的回调函数。 |
| 参数 | callback:用于处理新 IMU 数据的回调函数。 |
| 返回值 | 成功:返回True 失败:返回False |
备注:datatypes.ImuData 数据结构原型如下:
import sys
class ImuData(object):
__slots__ = ['stamp','acc','gyro','quat']
def __init__(self):
self.stamp = 0 # 时间戳,通常表示记录或生成这些数据的时间,以纳秒为单位
self.acc = [0. for x in range(0, 3)] # 用于存储 IMU(惯性测量单元)加速度计数据,用于跟踪三个轴上的线性加速度
self.gyro = [0. for x in range(0, 3)] # 用于存储 IMU 陀螺仪数据,用于跟踪角速度或旋转速度
self.quat = [0. for x in range(0, 4)] # 用于存储 IMU 四元数数据,表示在三维空间中的方向
代码示例:
import sys
from functools import partial
import limxsdk.robot.Robot as Robot
import limxsdk.robot.RobotType as RobotType
import limxsdk.datatypes as datatypes
class RobotReceiver:
# 订阅机器人的 IMU数据
def imuDataCallback(self, imu: datatypes.ImuData):
print("\n------\nrobot_state:" + \
"\n stamp: " + str(imu.stamp) + \
"\n acc: " + str(imu.acc) + \
"\n gyro: " + str(imu.gyro) + \
"\n quat: " + str(imu.quat))
if __name__ == '__main__':
# 创建一个 Robot 实例,类型为 Humanoid
robot = Robot(RobotType.Humanoid)
robot_ip = "127.0.0.1"
# 检查是否提供了机器人 IP 的命令行参数
if len(sys.argv) > 1:
robot_ip = sys.argv[1]
# 使用 robot_ip 初始化机器人
if not robot.init(robot_ip):
sys.exit()
# 创建一个 RobotReceiver 实例来处理回调
receiver = RobotReceiver()
# 创建回调函数的 partial 函数
imuDataCallback = partial(receiver.imuDataCallback)
# 订阅机器人IMU数据
robot.subscribeImuData(imuDataCallback)
4.2.6 subscribeRobotState 接口
| 函数名 | subscribeRobotState |
|---|---|
| 函数原型 | def subscribeRobotState(self, callback: Callable[[datatypes.RobotState], Any]) |
| 功能概述 | 订阅接收关于机器人状态的更新。 |
| 参数 | callback:回调函数,当接收到机器人状态更新时将被调用。回调函数参数指向datatypes.RobotState 对象。 - datatypes.RobotState数据结构字段: - stamp:时间戳,通常表示记录或生成这些数据的时间。 - tau:用于存储当前估计的输出扭矩(以牛顿米为单位)的向量。 - q:用于存储当前角度(以弧度为单位)的向量。 - dq:用于存储当前速度(以弧度每秒为单位)的向量。 - motor_names:用于存储对应的关节名称。 |
| 返回值 | 成功:返回True 失败:返回False |
备注:datatypes.RobotState 数据结构原型如下:
import sys
class RobotState(object):
__slots__ = ['stamp','tau','q','dq']
def __init__(self):
self.stamp = 0 # 时间戳,通常表示记录或生成这些数据的时间,以纳秒为单位
self.tau = [] # 用于存储当前估计的输出扭矩(以牛顿米为单位)的向量
self.q = [] # 用于存储当前角度(以弧度为单位)的向量
self.dq = [] # 用于存储当前速度(以弧度每秒为单位)的向量
self.motor_names = [] # 用于存储对应的关节名称。
代码示例:
import sys
from functools import partial
import limxsdk.robot.Robot as Robot
import limxsdk.robot.RobotType as RobotType
import limxsdk.datatypes as datatypes
class RobotReceiver:
# 用于接收机器人状态的回调函数
def robotStateCallback(self, robot_state: datatypes.RobotState):
print("
------
robot_state:" + \
"
stamp: " + str(robot_state.stamp) + \
"
tau: " + str(robot_state.tau) + \
"
q: " + str(robot_state.q) + \
"
dq: " + str(robot_state.dq))
if __name__ == '__main__':
# 创建一个 Robot 实例,类型为 Humanoid
robot = Robot(RobotType.Humanoid)
robot_ip = "127.0.0.1"
# 检查是否提供了机器人 IP 的命令行参数
if len(sys.argv) > 1:
robot_ip = sys.argv[1]
# 使用 robot_ip 初始化机器人
if not robot.init(robot_ip):
sys.exit()
# 创建一个 RobotReceiver 实例来处理回调
receiver = RobotReceiver()
# 创建回调函数的 partial 函数
robotStateCallback = partial(receiver.robotStateCallback)
# 订阅机器人状态
robot.subscribeRobotState(robotStateCallback)
4.2.7 publishRobotCmd 接口
| 函数名 | publishRobotCmd |
|---|---|
| 函数原型 | def publishRobotCmd (self, cmd: datatypes.RobotCmd) |
| 功能概述 | 发布一个命令来控制机器人的动作。 |
| 参数 | cmd:表示所需机器人命令的 datatypes.RobotCmd 对象,包含以下字段: - stamp:记录或生成数据时的时间戳,以纳秒为单位。 - q:存储所需的关节角度(以弧度为单位)的向量。 - dq:存储所需的关节速度(以弧度每秒为单位)的向量。 - tau:存储所需的输出扭矩(以牛顿米为单位)的向量。 - Kp:存储所需的位置刚度(以牛顿米每弧度为单位)的向量。 - Kd:存储所需的速度刚度(以牛顿米每弧度每秒为单位)的向量。 - motor_names:存储需要控制的关节名称。 |
| 参数取值 | - q、dq、tau:在urdf中已定义范围,举例如下: - joint的字段中描述了每个关节的q、dq、tau取值范围: - lower和upper共同对应"q",表示控制位置范围(单位为弧度); - effort对应"tau",表示扭矩峰值(单位为牛顿米); - velocity对应"dq",表示速度峰值(单位为弧度每秒); - Kp、Kd可用值(用户自行开发控制器时需根据实际效果调整取值): |
| 返回值 | 成功:返回True 失败:返回False |
备注:datatypes.RobotCmd 数据结构原型如下:
import sys
class RobotCmd(object):
__slots__ = ['stamp','mode','q','dq','tau','Kp','Kd']
def __init__(self):
self.stamp = 0 # 时间戳(以纳秒为单位),表示记录或生成数据时的时间。
self.mode = [] # 机器人的期望工作模式。
self.q = [] # 存储期望角度的向量(以弧度为单位)
self.dq = [] # 存储期望速度的向量(以弧度每秒为单位)。
self.tau = [] # 存储期望输出扭矩的向量(以牛顿米为单位)。
self.Kp = [] # 存储期望位置刚度的向量(以牛顿米每弧度为单位)。
self.Kd = [] # 存储期望速度刚度的向量(以牛顿米每弧度每秒为单位)。
self.motor_names = [] # 存储需要控制的关节名称。
代码示例:
import sys
import time
import limxsdk.robot.Rate as Rate
import limxsdk.robot.Robot as Robot
import limxsdk.robot.RobotType as RobotType
import limxsdk.datatypes as datatypes
if __name__ == '__main__':
# 创建一个 Robot 实例,类型为 Humanoid
robot = Robot(RobotType.Humanoid)
robot_ip = "127.0.0.1"
# 检查是否提供了机器人 IP 的命令行参数
if len(sys.argv) > 1:
robot_ip = sys.argv[1]
# 使用 robot_ip 初始化机器人
if not robot.init(robot_ip):
sys.exit()
# 获取关节偏移、关节限制和电机数量信息
joint_offset = robot.getJointOffset()
joint_limit = robot.getJointLimit()
motor_number = robot.getMotorNumber()
# 主循环以连续发布机器人命令
rate = Rate(500) # 1500 Hz
cmd_msg = datatypes.RobotCmd()
while True:
# 设置时间戳、控制模式、关节位置、速度、力矩、Kp 和 Kd 的默认值
cmd_msg.stamp = time.time_ns()
cmd_msg.mode = [1.0 for _ in range(motor_number)]
cmd_msg.q = [1.0 for _ in range(motor_number)]
cmd_msg.dq = [1.0 for _ in range(motor_number)]
cmd_msg.tau = [1.0 for _ in range(motor_number)]
cmd_msg.Kp = [1.0 for _ in range(motor_number)]
cmd_msg.Kd = [1.0 for _ in range(motor_number)]
robot.publishRobotCmd(cmd_msg) # 发布机器人命令
rate.sleep() # 控制循环频率
4.2.8 subscribeSensorJoy 接口
| 函数名 | subscribeSensorJoy |
|---|---|
| 函数原型 | def subscribeSensorJoy(self, callback: Callable[[datatypes.SensorJoy], Any]) |
| 功能概述 | 在真机部署中,该方法用于订阅来自机器人遥控器的数据。当机器人接收到遥控器的数据时,将会调用指定的回调函数,并传递包含遥控器数据的 datatypes.SensorJoy 结构体常量指针给回调函数进行处理。 |
| 参数 | callback: 表示回调函数,用于接收机器人遥控器的数据。回调函数的参数类型为 datatypes.SensorJoy |
| 返回值 | 成功:返回True 失败:返回False |
备注: datatypes.SensorJoy 数据结构原型如下:
import sys
class SensorJoy(object):
__slots__ = ['stamp','axes','buttons']
def __init__(self):
self.stamp = 0 # 与传感器输入相关的时间戳,单位为纳秒。
self.axes = [] # 表示操纵摇杆的值。
self.buttons = [] # 表示操纵按钮状态的值。
代码示例:
import sys
import time
from functools import partial
import limxsdk.robot.Robot as Robot
import limxsdk.robot.RobotType as RobotType
import limxsdk.datatypes as datatypes
class RobotReceiver:
# 用于接收遥控器数据的回调函数
def sensorJoyCallback(self, sensor_joy: datatypes.SensorJoy):
print("
------
sensor_joy:" + \
"
stamp: " + str(sensor_joy.stamp) + \
"
axes: " + str(sensor_joy.axes) + \
"
buttons: " + str(sensor_joy.buttons))
if __name__ == '__main__':
# 创建一个 Robot 实例,类型为 Humanoid
robot = Robot(RobotType.Humanoid)
robot_ip = "127.0.0.1"
# 检查是否提供了机器人 IP 的命令行参数
if len(sys.argv) > 1:
robot_ip = sys.argv[1]
# 使用 robot_ip 初始化机器人
if not robot.init(robot_ip):
sys.exit()
# 创建一个 RobotReceiver 实例来处理回调
receiver = RobotReceiver()
# 创建回调函数的 partial 函数
sensorJoyCallback = partial(receiver.sensorJoyCallback)
# 订阅机器人遥控数据
robot.subscribeSensorJoy(sensorJoyCallback)
4.2.9 subscribeDiagnosticValue 接口
| 函数名 | subscribeDiagnosticValue |
|---|---|
| 函数原型 | def subscribeDiagnosticValue(self, callback: Callable[[datatypes.DiagnosticValue], Any]) |
| 功能概述 | 在真机部署中,该方法用于订阅机器人的诊断值和状态信息。当机器人发出诊断值时,系统会调用指定的回调函数,并传递包含诊断值的 datatypes.DiagnosticValue 结构对象给回调函数进行处理。这可以帮助实时监控机器人的健康状态,并及时做出反应以处理可能的问题。 |
| 参数 | callback: 用于接收机器人诊断值的回调函数,其参数类型为 datatypes.DiagnosticValue。datatypes.DiagnosticValue 结构体包含了机器人诊断值的信息,包括时间戳、级别、名称、代码和消息字段。 |
| 返回值 | 成功:返回True 失败:返回False |
备注: datatypes.DiagnosticValue 数据结构原型如下:
import sys
class DiagnosticValue(object):
__slots__ = ['stamp','level','name','code','message']
def __init__(self):
self.stamp = 0 # 时间戳,单位为纳秒。
self.level = 0 # 与诊断值相关联的级别 - 0: OK, 1: WARN, 2: ERROR
self.name = '' # 标识诊断值的名称。
self.code = 0 # 与诊断值对应的代码。
self.message = '' # 与诊断值相关的详细消息。
代码示例:
import sys
from functools import partial
import limxsdk.robot.Robot as Robot
import limxsdk.robot.RobotType as RobotType
import limxsdk.datatypes as datatypes
class RobotReceiver:
# 用于接收诊断值的回调函数
def diagnosticValueCallback(self, diagnostic_value: datatypes.DiagnosticValue):
print("
------
diagnostic_value:" + \
"
stamp: " + str(diagnostic_value.stamp) + \
"
name: " + diagnostic_value.name + \
"
level: " + str(diagnostic_value.level) + \
"
code: " + str(diagnostic_value.code) + \
"
message: " + diagnostic_value.message)
if __name__ == '__main__':
# 创建一个 Robot 实例,类型为 Humanoid
robot = Robot(RobotType.Humanoid)
robot_ip = "127.0.0.1"
# 检查是否提供了机器人 IP 的命令行参数
if len(sys.argv) > 1:
robot_ip = sys.argv[1]
# 使用 robot_ip 初始化机器人
if not robot.init(robot_ip):
sys.exit()
# 创建一个 RobotReceiver 实例来处理回调
receiver = RobotReceiver()
# 创建回调函数的 partial 函数
diagnosticValueCallback = partial(receiver.diagnosticValueCallback)
# 订阅机器人诊断信息
robot.subscribeDiagnosticValue(diagnosticValueCallback)
4.2.10 publishJsonMessage 接口
| 函数名 | publishJsonMessage |
|---|---|
| 函数原型 | def publishJsonMessage(self, json_payload: str) |
| 功能概述 | 向机器人发送 "上层应用协议接口" 的 JSON 格式消息。 |
| 参数 | json_payload: 符合 "上层应用协议接口" 协议的 JSON 字符串。 示例:{"accid": "xxx", "title": "request_xxx", "timestamp": xxx, "guid": "xxx", "data": {}} |
| 返回值 | 无 |
| 备注 | 高阶开发模式下有效 |
代码示例:
import sys
import time
import limxsdk.robot.Rate as Rate
import limxsdk.robot.Robot as Robot
import limxsdk.robot.RobotType as RobotType
import limxsdk.datatypes as datatypes
if __name__ == '__main__':
# 创建一个 Robot 实例,类型为 Humanoid
robot = Robot(RobotType.Humanoid)
robot_ip = "10.192.1.2"
# 检查是否提供了机器人 IP 的命令行参数
if len(sys.argv) > 1:
robot_ip = sys.argv[1]
# 使用 robot_ip 初始化机器人
if not robot.init(robot_ip):
sys.exit()
# 设置要发送的协议内容
json_payload = '''{
"accid": "HU_D03_01", # 替换为真实机器人SN
"title": "request_get_joint_state",
"timestamp": 1672373633989,
"guid": "746d937cd8094f6a98c9577aaf213d98",
"data": {}
}'''
# 发送JSON协议
robot.publishJsonMessage(json_payload)
# 保持程序运行
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("程序被用户中断")
4.2.11 subscribeJsonMessage 接口
| 函数名 | subscribeJsonMessage |
|---|---|
| 函数原型 | def subscribeJsonMessage(self, callback: Callable[[str], Any]) |
| 功能概述 | 注册一个回调函数,用于处理来自机器人的 "上层应用协议接口" 调用的响应和通知。 该回调函数会在以下两种场景中被调用: 1. 当机器人对之前发送的 JSON 命令(publishJsonMessage)返回响应时 2. 当机器人主动发起通知(未经请求的消息)时 |
| 参数 | callback: 回调函数, 其中 json_payload 包含: - 命令响应:{"accid": "xxx", "title": "response_xxx", "timestamp": xxx, "guid": "xxx", "data": {}} - 通知:{"accid": "xxx", "title": "notify_xxx", "timestamp": xxx, "guid": "xxx", "data": {}} |
| 返回值 | 无 |
| 备注 | 高阶开发模式下有效 |
代码示例:
import sys
import time
from functools import partial
import limxsdk.robot.Robot as Robot
import limxsdk.robot.RobotType as RobotType
import limxsdk.datatypes as datatypes
class RobotReceiver:
# 处理来自机器人的 "上层应用协议接口" 调用的响应和通知
def jsonMessageCallback(self, json_payload: str):
print("
------
json_payload:" + json_payload)
if __name__ == '__main__':
# 创建一个 Robot 实例,类型为 Humanoid
robot = Robot(RobotType.Humanoid)
robot_ip = "10.192.1.2"
# 检查是否提供了机器人 IP 的命令行参数
if len(sys.argv) > 1:
robot_ip = sys.argv[1]
# 使用 robot_ip 初始化机器人
if not robot.init(robot_ip):
sys.exit()
# 创建一个 RobotReceiver 实例来处理回调
receiver = RobotReceiver()
# 创建回调函数的 partial 函数
jsonMessageCallback = partial(receiver.jsonMessageCallback)
# 订阅机器人遥控数据
robot.subscribeJsonMessage(jsonMessageCallback)
4.2.12 参考例程
Github: https://github.com/limxdynamics/humanoid-rl-deploy-python
5 查看/设置机器人型号
在编译和运行RL训练、控制算法及仿真器程序时,选择正确的机器人型号至关重要。通过查看机器人型号并将其设置到环境变量 ROBOT_TYPE 中,确保在不同任务中准确识别并应用相应的机器人模型。
查看和配置机器人型号步骤:
- 选择并连接您机器人Wi-Fi 热点,密码为:
12345678
- 在浏览器中输入
http://10.192.1.2:8080进入“机器人信息页”,查看机器人信息。如下图所示,页面中显示的SN (序列号) 为HU_D03_03_001,其中HU_D03_03为机器人型号。
- 设置机器人型号:打开 Bash 终端,输入以下 Shell 命令来设置机器人型号。这样在二次开发时,您将能获取到正确的机器人型号信息。
echo 'export ROBOT_TYPE=HU_D03_03' >> ~/.bashrc && source ~/.bashrc
6 机器人仿真器
MuJoCo 是一款轻量级且高性能的物理仿真器,专为多关节机器人和机械系统设计。它具备高效的物理引擎,能够精确处理接触和摩擦,且无需依赖 ROS,可独立运行。凭借其高速计算能力,MuJoCo 被广泛应用于机器人仿真和强化学习,尤其适合对仿真效率要求较高的场景。
6.1 运行仿真器步骤
-
运行环境:推荐 Pyhon 3.8 及以上版本
-
打开一个 Bash 终端。
-
下载 MuJoCo 仿真器代码:
git clone --recurse git@github.com:limxdynamics/humanoid-mujoco-sim.git
- 安装运动控制开发库:
- Linux x86_64 环境
pip install humanoid-mujoco-sim/limxsdk-lowlevel/python3/amd64/limxsdk-*-py3-none-any.whl
- Linux aarch64 环境
pip install humanoid-mujoco-sim/limxsdk-lowlevel/python3/aarch64/limxsdk-*-py3-none-any.whl
- 设置机器人型号:请参考"查看/设置机器人型号"章节,查看您的机器人型号。如果尚未设置,请按照以下步骤进行设置。
- 通过 Shell 命令
tree -L 3 -P "meshes" -I "urdf|world|xml|usd" humanoid-mujoco-sim/humanoid-description列出可用的机器人类型:
limx@limx:~$ tree -L 3 -P "meshes" -I "urdf|world|xml|usd" humanoid-mujoco-sim/humanoid-description
humanoid-mujoco-sim/humanoid-description
├── HU_D03_description
│ └── meshes
│ └── HU_D03_03
└── HU_D04_description
└── meshes
└── HU_D04_01
- 以
HU_D04_01(请根据实际机器人类型进行替换)为例,设置机器人型号类型:
echo 'export ROBOT_TYPE=HU_D04_01' >> ~/.bashrc && source ~/.bashrc
- 运行 MuJoCo 仿真器:
python humanoid-mujoco-sim/simulator.py
6.2 演示效果(请以实际为准)
7 RL 算法部署
7.1 基于标准C++进行部署
Github项目地址:https://github.com/limxdynamics/humanoid-rl-deploy-cpp,它是一个在标准C++中实现的轻量级算法框架。无需 ROS1/ROS2 即可快速部署训练的模型。
7.2 基于Python进行部署
Github项目地址:https://github.com/limxdynamics/humanoid-rl-deploy-python,它是一种基于Python的强化学习部署算法框架,它简化了在Oli机器人上部署训练模型的过程。
7.3 基于ROS2 进行部署
Github项目地址:https://github.com/limxdynamics/humanoid-rl-deploy-ros2,它是基于 ROS2 的强化学习部署框架,可在 Oli 机器人上快速部署经过训练的模型。
7.4 基于ROS1 进行部署
Github项目地址:https://github.com/limxdynamics/humanoid-rl-deploy-ros,它是基于ROS1的强化学习部署框架,可以在Oli机器人上快速部署经过训练的模型。
8 日志和数据包
-
机器人系统自动录制数据: IMU数据(ImuData)、状态数据(/joint/state)、控制数据(/joint/cmd)、运行日志数据等重要数据,这些数据对于机器人的运动控制分析至关重要。
-
访问并下载机器人数据: 机器人将会记录运行时的日志数据,以便在需要时进行故障排查和性能优化,电脑与机器人 WiFi 热点连接后浏览器输入
http://10.192.1.2:8090,可自行选择下载所需数据。
8.1 数据包可视化分析方法
- 数据包下载: 下载
.bag文件后,可以使用PlotJuggler可视化工具加载这些数据包进行分析。需要特别注意的是,如果下载的是.bag.active文件,需要使用如下Shell命令把它重新索引.bag.active文件,生成一个新的.bag文件,以便PlotJuggler加载。
rosbag reindex your_file.bag.active
mv your_file.bag.active your_file.bag
- 可视化查看: 通过Shell命令
rosrun plotjuggler plotjuggler -n启动PlotJuggler可视化工具。如下图所示加载数据包进行可视化分析。
8.2 日志及诊断埋点数据
如下图所示分别为日志和埋点结构化数据。在需要时可以用于故障排查和性能优化。
9 机器人软件升级
软件升级注意事项:
- 请确保设备电量不低于 30%。升级过程中如发生断电,可能导致设备无法正常使用
- 请提前将机器人切换至零力矩模式或阻尼模式。升级完成后设备将自动重启,若机器人处于站立状态,可能存在跌倒风险。
通过浏览器进入机器人管理页面,选择本地提前下载好的机器人软件版本进行升级。具体步骤如下:
-
连接 Wi-Fi:
- 选择并连接机器人的 Wi-Fi 热点,密码为:12345678
-
访问管理页面:
- 在浏览器地址栏输入:
http://10.192.1.2:8080进入机器人管理页面。
- 在浏览器地址栏输入:
-
选择并升级软件:
- 依次选择"版本管理 -> 浏览 -> 升级"。
- 升级完成后,机器人主控电脑将自动重启。
10 开发者电脑
开发者电脑主要用于开发机器人相关算法及应用程序。可以通过 WiFi 连接机器人本体系统登录到开发者电脑,具体步骤如下:
-
连接Wi-Fi:
- 选择并连接机器人的 Wi-Fi 热点,密码为:12345678
-
通过 SSH 登录开发者电脑系统:
- 登录地址:10.192.1.3
- 登录密码:123456
- 在终端中输入以下命令(首次连接需确认指纹):
ssh guest@10.192.1.3
- 开发者电脑系统配置:
- 操作系统:Ubuntu 22.04 (Jetpack 6.2.1)
- ROS2:系统默认安装ROS2 Humble 版本机器人系统
- ROS1:系统默认安装ROS1 Noetic 版本机器人系统Docker,进入方法如下:
sudo docker exec -it ros_noetic /bin/bash
11 Realsense 相机数据获取
注意事项:
- 机器人开机后默认会自动启动相机驱动。因此,在进行以下操作前,请先关闭相机驱动的自动启动功能。关闭方法请参考下文说明。该功能仅支持主控版本 V2.0.33 及以上。
- 关闭相机驱动自动启动功能后,我司配套的数据采集套件(数采套件)将无法正常使用。请根据实际需求决定是否执行该操作。
11.1 关闭相机驱动自启动
-
在浏览器地址栏输入
http://10.192.1.2:8080,进入以下界面。 -
设置关闭并保存。
11.2 获取相机数据
-
登录开发者电脑:
- 请按照“开发者电脑”章节中的步骤登录该电脑。
-
启动 Realsense 的 ROS 节点以获取相机数据:
- 启动方式参考链接:realsense-ros
- 电脑已预装 Realsense 相机的 SDK:
- 版本为 v2.56.3,官方下载链接:librealsense v2.56.3,可基于此官方 SDK 自主开发应用程序来获取数据。
-
示例代码说明:
- 相机命名规则:
- 默认命名:脚本会将多个相机的 topic 前缀命名为
camera加上序号(如camera0、camera1)。 - 自定义命名:可通过修改脚本,根据相机的序列号(Serial Number)指定 topic 前缀,而非使用默认的
camera + counter命名规则。
- 默认命名:脚本会将多个相机的 topic 前缀命名为
- 相机命名规则:
以下代码示例展示了如何获取多个相机的数据:
- 通过 SSH 登录开发者电脑系统
- 登录 ROS1 系统
sudo docker exec -it ros_noetic /bin/bash
- 将下面的脚本保存为
rs_camera.sh:
#!/bin/bash
source /opt/ros/noetic/setup.bash
# Function to detect connected RealSense cameras
detect_cameras() {
# List all connected RealSense cameras, excluding Asic Serial Number
serial_numbers=($(rs-enumerate-devices | grep "Serial Number" | grep -v "Asic" | awk '{print $NF}'))
echo "${serial_numbers[@]}"
}
# Loop to check for the launch flag file and connected cameras
while true; do
serial_numbers=($(detect_cameras))
# Check if any cameras were found
if [ ${#serial_numbers[@]} -gt 0 ]; then
echo "Detected ${#serial_numbers[@]} cameras."
break # Exit the loop if cameras are detected
else
echo "No RealSense cameras detected. Retrying in 5 seconds..."
sleep 5 # Wait for a while before retrying
fi
done
# Automatically start a ROS node for each detected camera
if [ ${#serial_numbers[@]} -gt 0 ]; then
for i in "${!serial_numbers[@]}"; do
serial=${serial_numbers[$i]}
# Default camera naming using index (camera0, camera1, ...)
# Customize camera naming by modifying the code below
camera_topic="camera$i"
# Example: Custom camera naming based on serial number
# Uncomment and replace with your actual serial numbers
# if [[ "$serial" == "0123456789" ]]; then
# camera_topic="head" # Name specific camera as "head"
# elif [[ "$serial" == "9876543210" ]]; then
# camera_topic="chest" # Name another camera as "chest"
# fi
echo "Starting ROS node for camera $serial (topic prefix: $camera_topic)..."
roslaunch realsense2_camera rs_camera.launch \
serial_no:=$serial \
camera:=$camera_topic \
enable_pointcloud:=True \
enable_accel:=True \
enable_gyro:=True \
enable_sync:=True \
unite_imu_method:=linear_interpolation &
sleep 10 # Optional: wait a bit before starting the next camera
done
# Wait for all background processes to finish
wait
else
echo "No cameras to start."
fi
- 在终端执行脚本,启动相机节点:
/bin/bash rs_camera.sh
- 在另一个终端中,通过
rostopic list验证结果:
rostopic list