Skip to main content

Crate unitree_sdk2_rs

Crate unitree_sdk2_rs 

Source
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

English | 简体中文

Crates.io docs.rs License: BSD-3-Clause CI MSRV: 1.88

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:

VariableDescription
UNITREE_SDK2_PATH=<dir>Use a local SDK directly (no clone, no build)
UNITREE_SDK2_URL=<git url>Shallow-clone into UNITREE_SDK2_DIR
defaultClone 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 ID
  • network_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 Subscriber keeps the subscription alive while held and unsubscribes on drop;
  • the Receiver holds the latest message (None until the first one arrives); read it with 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={}% 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 fileRust type
msgs/unitree_hg/msg/BmsState.msgunitree_hg::msg::dds_::BmsState
msgs/unitree_go/msg/SportModeState.msgunitree_go::msg::dds_::SportModeState
msgs/unitree_api/msg/Request.msgunitree_api::msg::dds_::Request
msgs/std_msgs/msg/String.msgstd_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

English | 简体中文

Crates.io docs.rs License: BSD-3-Clause CI MSRV: 1.88

Unitree SDK2 的 Rust 绑定,提供 DDS 消息类型、话题订阅/发布与 RPC 接口。

本项目通过 cxx 桥接封装官方 unitree_sdk2 C++ SDK,复用相同的 DDS 消息类型与 CDR 线格式,可与机器人上其他基于 unitree_sdk2 的程序互通。

  • 消息类型:由 msgs/*.msg 自动生成,与 C++ SDK 的 DDS 类型一一对应
  • 订阅/发布subscribe::<T> / Publisher::<T> 泛型接口
  • RPCrpc::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 域 ID
  • network_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.msgunitree_hg::msg::dds_::BmsState
msgs/unitree_go/msg/SportModeState.msgunitree_go::msg::dds_::SportModeState
msgs/unitree_api/msg/Request.msgunitree_api::msg::dds_::Request
msgs/std_msgs/msg/String.msgstd_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§

robot_api
rpc
rpc_ffi
std_msgs
unitree_api
unitree_go
unitree_hg

Structs§

ByteHandler
Publisher
Topic publisher.
RpcRequestHandler
Subscriber
Subscription handle: keeps the subscription alive while held, unsubscribes on drop.

Traits§

DdsType
DDS type name of a message type.
TopicPublish
Internal publisher interface, implemented by the code generator for every message type.

Functions§

init_dds
Initializes the DDS runtime. Call once before any subscribe/publish/RPC.
subscribe
Subscribes to a topic and receives the latest message.