Expand description
§unitree_sdk2_rs
Rust bindings for the Unitree SDK2.
DDS message types are generated from msgs/*.msg, with CDR serialization,
topic publish/subscribe and RPC support.
Call init_dds once before any subscribe/publish/RPC operation.
§unitree_sdk2_rs
Rust bindings for the Unitree SDK2, providing DDS message types, topic publish/subscribe and RPC.
This crate wraps the official unitree_sdk2
C++ SDK via a cxx bridge. It uses the same DDS message types and CDR wire format,
so it interoperates with other unitree_sdk2-based programs on the robot.
- Message types: generated from
msgs/*.msg, one-to-one with the C++ SDK DDS types - Subscribe / Publish:
subscribe::<T>/Publisher::<T> - RPC:
rpc::RpcClient/rpc::RpcServer
§Installation
Add the crate to your Cargo.toml:
[dependencies]
unitree_sdk2_rs = { version = "0.3.0" }The crate ships no feature flags — all functionality is enabled by default.
§Quick Start
use unitree_sdk2_rs::{init_dds, subscribe};
use unitree_sdk2_rs::unitree_hg::msg::dds_::BmsState;
// Initialize DDS (domain ID, network interface, config file; the last two can be empty)
init_dds(0, "eth0", "");
// Subscribe to the battery state topic; rx holds the latest frame (None until received)
let (_sub, rx) = subscribe::<BmsState>("rt/lf/bmsstate");
if let Some(s) = rx.borrow().as_ref() {
println!("SOC = {}%", s.soc);
}More usage (publish, RPC server/client) is in the sections below — embedded in this page on
docs.rs; the same files live in docs/ in the repo.
Online docs are available on docs.rs.
§Environment Variables
The build fetches the unitree_sdk2 C++ SDK (the repo ships prebuilt libraries; no cmake build). The source is chosen by priority:
| Variable | Description |
|---|---|
UNITREE_SDK2_PATH=<dir> | Use a local SDK directly (no clone, no build) |
UNITREE_SDK2_URL=<git url> | Shallow-clone into UNITREE_SDK2_DIR |
| default | Clone unitree_sdk2 from GitHub |
You can export these variables, or set them in .cargo/config.toml:
# relative = true: the value is relative to this config file's directory (project root),
# so cargo resolves it correctly from any directory
[env]
UNITREE_SDK2_PATH = { value = "unitree_sdk2", relative = true }Priority: UNITREE_SDK2_PATH > UNITREE_SDK2_URL > default GitHub. Clones land in the build
temporary directory (OUT_DIR); the source directory stays untouched.
§Subscribe & Publish
§Initialization
Call init_dds once before any subscribe/publish/RPC operation:
domain_id: DDS domain IDnetwork_interface: network interface to bind (e.g."eth0", empty = default)config_file: CycloneDDS config file path (empty = default config)
§Subscribe
subscribe::<T>(topic) subscribes to a topic and returns (Subscriber, watch::Receiver<Option<T>>):
- the
Subscriberkeeps the subscription alive while held and unsubscribes on drop; - the
Receiverholds the latest message (Noneuntil the first one arrives); read it withrx.borrow().
use unitree_sdk2_rs::{init_dds, subscribe};
use unitree_sdk2_rs::unitree_hg::msg::dds_::BmsState;
init_dds(0, "eth0", "");
let (_sub, rx) = subscribe::<BmsState>("rt/lf/bmsstate");
if let Some(state) = rx.borrow().as_ref() {
println!("SOC={}% voltage={}mV", state.soc, state.bmsvoltage[0]);
}§Publish
Create a publisher with Publisher::<T>::new(topic) and publish one message with publish(&msg):
use unitree_sdk2_rs::{init_dds, Publisher};
use unitree_sdk2_rs::unitree_hg::msg::dds_::BmsState;
init_dds(0, "eth0", "");
let pub_ = Publisher::<BmsState>::new("rt/lf/bmsstate").expect("failed to create publisher");
let bms = BmsState {
version_high: 1, version_low: 0, r#fn: 0,
cell_vol: [4000; 40],
bmsvoltage: [42000, 0, 0],
current: -2500, soc: 85, soh: 100,
temperature: [32; 12],
cycle: 1, manufacturer_date: 0,
bmsstate: [0; 5], reserve: [0; 3],
};
pub_.publish(&bms);§Message Types
Message types are accessed as <package>::msg::dds_::<TypeName>, one-to-one with the .msg
files under msgs/; fields match the .msg definitions:
| .msg file | Rust type |
|---|---|
msgs/unitree_hg/msg/BmsState.msg | unitree_hg::msg::dds_::BmsState |
msgs/unitree_go/msg/SportModeState.msg | unitree_go::msg::dds_::SportModeState |
msgs/unitree_api/msg/Request.msg | unitree_api::msg::dds_::Request |
msgs/std_msgs/msg/String.msg | std_msgs::msg::dds_::String |
Topic names (e.g. rt/lf/bmsstate) and their message formats follow the unitree_sdk2 official docs.
§RPC (Server / Client)
§Server
Create a server with RpcServer::new(service), register API handlers with register_handler,
then start listening with start:
use std::sync::Arc;
use unitree_sdk2_rs::rpc::RpcServer;
#[tokio::main]
async fn main() {
unitree_sdk2_rs::init_dds(0, "eth0", "");
let server = Arc::new(RpcServer::new("rpc_test").expect("failed to create RpcServer"));
// The handler receives the request string; the string it returns is sent back as the response.
server.register_handler(1001, |req| format!(r#"{{"echo":"{}"}}"#, req));
server.register_handler(1002, |_| r#"{"result":"ok"}"#.to_string());
eprintln!("RPC server starting (rpc_test, api=1001,1002)");
server.clone().start().await;
}§Client
Connect with RpcClient::new(service) (the service name must match the server), register the
APIs to call with register_api, then make calls with call:
use unitree_sdk2_rs::rpc::RpcClient;
#[tokio::main]
async fn main() {
unitree_sdk2_rs::init_dds(0, "eth0", "");
let client = RpcClient::new("rpc_test").expect("failed to create RpcClient");
client.register_api(1001);
client.register_api(1002);
// Returns (code, data): code is the return code (0 = success), data is the response payload.
let resp = client.call(1001, "hello".to_string()).await.expect("call 1001");
println!("api=1001 code={} data={}", resp.code, resp.data);
let resp = client.call(1002, String::new()).await.expect("call 1002");
println!("api=1002 code={} data={}", resp.code, resp.data);
}§Robot Service APIs
The robot’s RPC services (sport / audio / config / video / arm / loco / …)
are exposed as robot_api::<model>::<service> modules with API_ID_* constants and
serde request payloads, generated from the unitree_sdk2 headers:
use unitree_sdk2_rs::robot_api::go2::sport::SportClient;
#[tokio::main]
async fn main() {
unitree_sdk2_rs::init_dds(0, "eth0", "");
// mirrors the C++ client: registers all sport api ids on connect
let client = SportClient::new().expect("failed to create SportClient");
// {"vx":0.3,"vy":0.0,"vyaw":0.0}
let resp = client.move_to(0.3, 0.0, 0.0).await.expect("RPC call failed");
println!("code={} data={}", resp.code, resp.data);
}All 8 robot models (go2 / g1 / h1 / h2 / b2 / a2 / as2 / r1) are
covered; each model has one file under gen/robot_api/ (robot_api::<model>::<service>),
hand-verified against the unitree_sdk2 headers.
§unitree_sdk2_rs
Unitree SDK2 的 Rust 绑定,提供 DDS 消息类型、话题订阅/发布与 RPC 接口。
本项目通过 cxx 桥接封装官方 unitree_sdk2
C++ SDK,复用相同的 DDS 消息类型与 CDR 线格式,可与机器人上其他基于 unitree_sdk2 的程序互通。
- 消息类型:由
msgs/*.msg自动生成,与 C++ SDK 的 DDS 类型一一对应 - 订阅/发布:
subscribe::<T>/Publisher::<T>泛型接口 - RPC:
rpc::RpcClient/rpc::RpcServer
§安装
在 Cargo.toml 中添加依赖:
[dependencies]
unitree_sdk2_rs = { version = "0.3.0" }该 crate 无 feature 开关,默认即启用全部功能。
§快速上手
use unitree_sdk2_rs::{init_dds, subscribe};
use unitree_sdk2_rs::unitree_hg::msg::dds_::BmsState;
// 初始化 DDS(域 ID、网卡、配置文件,后两项可留空)
init_dds(0, "eth0", "");
// 订阅电池状态话题,rx 保存最新一帧(未收到时为 None)
let (_sub, rx) = subscribe::<BmsState>("rt/lf/bmsstate");
if let Some(s) = rx.borrow().as_ref() {
println!("SOC = {}%", s.soc);
}更多用法(发布、RPC 服务端/客户端)见下方章节(docs.rs 本页已内嵌,对应文件在仓库 docs/ 目录):
在线文档可在 docs.rs 查看。
§环境变量
构建时获取 unitree_sdk2 C++ SDK(仓库自带预编译库,不做 cmake 编译)。来源按优先级:
| 环境变量 | 说明 |
|---|---|
UNITREE_SDK2_PATH=<目录> | 直接用本地已有 SDK(不 clone、不编译) |
UNITREE_SDK2_URL=<git 地址> | 浅克隆到 UNITREE_SDK2_DIR |
| 默认 | 从 GitHub 克隆 unitree_sdk2 |
您可直接 export 设置环境变量,也可在 .cargo/config.toml 下添加,示例如下:
# relative = true:值相对于本配置文件所在目录(项目根),任何目录下运行 cargo 都解析正确
[env]
UNITREE_SDK2_PATH = { value = "unitree_sdk2", relative = true }优先级:UNITREE_SDK2_PATH > UNITREE_SDK2_URL > 默认 GitHub。克隆目录统一在构建临时目录(OUT_DIR)下,源目录零写入。
§订阅与发布
§初始化
所有订阅/发布/RPC 之前,先调用 init_dds 初始化 DDS:
domain_id:DDS 域 IDnetwork_interface:绑定网卡名(如"eth0",留空用默认)config_file:CycloneDDS 配置文件路径(留空用默认配置)
§订阅
subscribe::<T>(topic) 订阅话题,返回 (Subscriber, watch::Receiver<Option<T>>):
Subscriber持有期间持续订阅,释放后自动退订;Receiver保存最新一帧,未收到数据时为None,用rx.borrow()读取。
use unitree_sdk2_rs::{init_dds, subscribe};
use unitree_sdk2_rs::unitree_hg::msg::dds_::BmsState;
init_dds(0, "eth0", "");
let (_sub, rx) = subscribe::<BmsState>("rt/lf/bmsstate");
if let Some(state) = rx.borrow().as_ref() {
println!("SOC={}% 电压={}mV", state.soc, state.bmsvoltage[0]);
}§发布
Publisher::<T>::new(topic) 创建发布器,publish(&msg) 发布一帧消息:
use unitree_sdk2_rs::{init_dds, Publisher};
use unitree_sdk2_rs::unitree_hg::msg::dds_::BmsState;
init_dds(0, "eth0", "");
let pub_ = Publisher::<BmsState>::new("rt/lf/bmsstate").expect("创建发布器失败");
let bms = BmsState {
version_high: 1, version_low: 0, r#fn: 0,
cell_vol: [4000; 40],
bmsvoltage: [42000, 0, 0],
current: -2500, soc: 85, soh: 100,
temperature: [32; 12],
cycle: 1, manufacturer_date: 0,
bmsstate: [0; 5], reserve: [0; 3],
};
pub_.publish(&bms);§消息类型
消息类型按 包名::msg::dds_::类型名 访问,与 msgs/ 下的 .msg 文件一一对应,字段与 .msg 定义一致:
| .msg 文件 | Rust 类型 |
|---|---|
msgs/unitree_hg/msg/BmsState.msg | unitree_hg::msg::dds_::BmsState |
msgs/unitree_go/msg/SportModeState.msg | unitree_go::msg::dds_::SportModeState |
msgs/unitree_api/msg/Request.msg | unitree_api::msg::dds_::Request |
msgs/std_msgs/msg/String.msg | std_msgs::msg::dds_::String |
话题名(如 rt/lf/bmsstate)及各话题的消息格式见 unitree_sdk2 官方文档。
§RPC(服务端 / 客户端)
§服务端
RpcServer::new(service) 创建服务端,register_handler 注册 API,start 启动监听:
use std::sync::Arc;
use unitree_sdk2_rs::rpc::RpcServer;
#[tokio::main]
async fn main() {
unitree_sdk2_rs::init_dds(0, "eth0", "");
let server = Arc::new(RpcServer::new("rpc_test").expect("创建 RpcServer 失败"));
// handler 接收请求字符串,返回的字符串作为响应数据
server.register_handler(1001, |req| format!(r#"{{"echo":"{}"}}"#, req));
server.register_handler(1002, |_| r#"{"result":"ok"}"#.to_string());
eprintln!("RPC 服务端启动中 (rpc_test, api=1001,1002)");
server.clone().start().await;
}§客户端
RpcClient::new(service) 连接服务(服务名须与服务端一致),register_api 注册要调用的 API,call 发起调用:
use unitree_sdk2_rs::rpc::RpcClient;
#[tokio::main]
async fn main() {
unitree_sdk2_rs::init_dds(0, "eth0", "");
let client = RpcClient::new("rpc_test").expect("创建 RpcClient 失败");
client.register_api(1001);
client.register_api(1002);
// 返回 (code, data):code 为返回码(0 表示成功),data 为响应内容
let resp = client.call(1001, "hello".to_string()).await.expect("call 1001");
println!("api=1001 code={} data={}", resp.code, resp.data);
let resp = client.call(1002, String::new()).await.expect("call 1002");
println!("api=1002 code={} data={}", resp.code, resp.data);
}§机器人服务 API
机器人的 RPC 服务(sport / audio / config / video / arm / loco 等)
以 robot_api::<机型>::<服务> 模块暴露,与 C++ 客户端调用方式一致
(new() 自动注册全部 api,每个方法内部组装 JSON 后发起 RPC):
use unitree_sdk2_rs::robot_api::go2::sport::SportClient;
#[tokio::main]
async fn main() {
unitree_sdk2_rs::init_dds(0, "eth0", "");
// 与 C++ SportClient 一致:连接时注册 sport 服务全部 api
let client = SportClient::new().expect("创建 SportClient 失败");
// 序列化为 {"vx":0.3,"vy":0.0,"vyaw":0.0}
let resp = client.move_to(0.3, 0.0, 0.0).await.expect("RPC 调用失败");
println!("code={} data={}", resp.code, resp.data);
}覆盖全部 8 个机型(go2 / g1 / h1 / h2 / b2 / a2 / as2 / r1);
每个机型一个文件(gen/robot_api/ 下),逐一对照 unitree_sdk2 头文件人工核对。
Modules§
Structs§
- Byte
Handler - Publisher
- Topic publisher.
- RpcRequest
Handler - Subscriber
- Subscription handle: keeps the subscription alive while held, unsubscribes on drop.
Traits§
- DdsType
- DDS type name of a message type.
- Topic
Publish - Internal publisher interface, implemented by the code generator for every message type.