feat: 初始化 Go2 导航相关代码与配置

This commit is contained in:
2026-10-15 09:40:29 +08:00
commit 07426fcc09
1282 changed files with 331220 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
add_executable(h1_loco_client high_level/h1_loco_client_example.cpp)
target_link_libraries(h1_loco_client unitree_sdk2)
add_executable(h1_low_level_example low_level/humanoid.cpp)
target_link_libraries(h1_low_level_example unitree_sdk2)
add_executable(h1_27dof_example low_level/h1_27dof_example.cpp)
target_link_libraries(h1_27dof_example unitree_sdk2)
add_executable(h1_2_ankle_track low_level/h1_2_ankle_track.cpp)
target_link_libraries(h1_2_ankle_track unitree_sdk2)
add_executable(h1_arm_sdk_dds_example high_level/h1_arm_sdk_dds_example.cpp)
target_link_libraries(h1_arm_sdk_dds_example unitree_sdk2)
add_executable(h1_2_arm_sdk_dds_example high_level/h1_2_arm_sdk_dds_example.cpp)
target_link_libraries(h1_2_arm_sdk_dds_example unitree_sdk2)

View File

@@ -0,0 +1,111 @@
# Unitree H1_2 Parallel Mechanism Control
English | [简体中文](README_zh.md)
## Parallel Mechanism Control Interface
The Unitree H1_2 robot's parallel mechanism involves the ankle joints of its left and right legs. Since the left and right ankle joints are symmetrical, the following explanation will use the H1_2 left ankle joint as an example:
<p align="center"><img src="doc/images/ankle.png" width="30%"/></p>
On the hardware side, the H1_2 left ankle joint uses a parallel mechanism consisting of four joints:
* Parallel joints: Joint A, Joint B
* Serial joints: Pitch joint (P joint), Roll joint (R joint)
Only the A and B joints can be directly driven by motors, while the P and R joints cannot be directly controlled. The robot's URDF model and motion control algorithms typically only consider the serial P and R joints. To control these joints, we control the A and B joints indirectly, which allows us to achieve control over the P and R joints. On the software side, we provide an equivalent serial joint control interface, called `PR Mode`, enabling users to directly control the P and R joints. Similar to the typical joint control method, in `PR Mode`, each serial joint receives the following commands:
| Command Name | Variable |
| ----------------------- | -------- |
| Feedforward Torque | `tau` |
| Target Angle | `q` |
| Target Angular Velocity | `dq` |
| Joint Stiffness | `kp` |
| Joint Damping | `kd` |
The total torque applied to the serial joint is calculated as `T = kp * (q - q_m) + kd * (dq - dq_m) + tau`. To improve the control precision of the serial joints, the H1_2 robot internally converts the feedforward torque, target angle, target angular velocity, joint stiffness, and joint damping commands for the P and R joints into actual commands for the A and B joints based on kinematic and dynamic relationships.
## Serial Joint Tracking Experiment
To test the performance of the H1_2 ankle in `PR Mode`, we have the P and R joints track a sinusoidal curve. You can refer to the [example](https://github.com/unitreerobotics/unitree_sdk2/blob/main/example/h1/low_level/h1_2_ankle_track.cpp). The core code is as follows:
**Enabling PR Mode and Generating Sinusoidal Curve**
```c++
// [Stage 2]: swing ankle's PR
mode_ = PR; // Enable PR mode
// generate sin/cos trajectory
double max_P = 0.25; // [rad]
double max_R = 0.25; // [rad]
double t = time_ - duration_;
double L_P_des = max_P * std::cos(2.0 * M_PI * t);
double L_R_des = max_R * std::sin(2.0 * M_PI * t);
double R_P_des = max_P * std::cos(2.0 * M_PI * t);
double R_R_des = -max_R * std::sin(2.0 * M_PI * t);
```
**Setting Ankle Joint Commands**
```c++
// update ankle joint position targets
float Kp_Pitch = 80;
float Kd_Pitch = 1;
float Kp_Roll = 80;
float Kd_Roll = 1;
dds_low_command.motor_cmd().at(4).q() = L_P_des; // 4: LeftAnklePitch
dds_low_command.motor_cmd().at(4).dq() = 0;
dds_low_command.motor_cmd().at(4).kp() = Kp_Pitch;
dds_low_command.motor_cmd().at(4).kd() = Kd_Pitch;
dds_low_command.motor_cmd().at(4).tau() = 0;
dds_low_command.motor_cmd().at(5).q() = L_R_des; // 5: LeftAnkleRoll
dds_low_command.motor_cmd().at(5).dq() = 0;
dds_low_command.motor_cmd().at(5).kp() = Kp_Roll;
dds_low_command.motor_cmd().at(5).kd() = Kd_Roll;
dds_low_command.motor_cmd().at(5).tau() = 0;
dds_low_command.motor_cmd().at(10).q() = R_P_des; // 10: RightAnklePitch
dds_low_command.motor_cmd().at(10).dq() = 0;
dds_low_command.motor_cmd().at(10).kp() = Kp_Pitch;
dds_low_command.motor_cmd().at(10).kd() = Kd_Pitch;
dds_low_command.motor_cmd().at(10).tau() = 0;
dds_low_command.motor_cmd().at(11).q() = R_R_des; // 11: RightAnkleRoll
dds_low_command.motor_cmd().at(11).dq() = 0;
dds_low_command.motor_cmd().at(11).kp() = Kp_Roll;
dds_low_command.motor_cmd().at(11).kd() = Kd_Roll;
dds_low_command.motor_cmd().at(11).tau() = 0;
```
**Printing Desired and Measured Values to Terminal**
```c++
float L_P_m = low_state_.motor_state().at(4).q();
float L_R_m = low_state_.motor_state().at(5).q();
float R_P_m = low_state_.motor_state().at(10).q();
float R_R_m = low_state_.motor_state().at(11).q();
printf("%f,%f,%f,%f,%f,%f,%f,%f\n", L_P_des, L_P_m, L_R_des, L_R_m, R_P_des, R_P_m, R_R_des, R_R_m);
```
| L_P_des | L_P_m | L_R_des | L_R_m | R_P_des | R_P_m | R_R_des | R_R_m |
| ------------------ | ------------------- | ----------------- | ------------------ | ------------------- | -------------------- | ------------------ | ------------------- |
| Left Pitch Desired | Left Pitch Measured | Left Roll Desired | Left Roll Measured | Right Pitch Desired | Right Pitch Measured | Right Roll Desired | Right Roll Measured |
To install and compile [unitree_sdk2](https://github.com/unitreerobotics/unitree_sdk2), ensure that the robot is **suspended first**, then run the test routine in the terminal:
```bash
h1_2_ankle_track network_interface
```
Once started, the robot will first reset to the zero position, then periodically swing its ankles while printing the desired and measured values for both ankle joints. The position tracking results are shown below:
<p float="middle">
<img src="doc/images/tracking.png" width="49%"/>
<img src="doc/images/tracking_circle.png" width="49%"/>
</p>
Definitions of the symbols in the figures:
* `L_Pitch_d`: Desired value of the left ankle's P joint
* `L_Pitch_m`: Measured value of the left ankle's P joint
* `L_Roll_d`: Desired value of the left ankle's R joint
* `L_Roll_m`: Measured value of the left ankle's R joint
The left graph shows that the serial Pitch and Roll joints can accurately track the sinusoidal target position commands, while the right graph illustrates the tracking performance of the serial joints in phase space.

View File

@@ -0,0 +1,111 @@
# Unitree H1_2 并联机构控制
[English](README.md) | 简体中文
## 并联机构控制接口
Unitree H1_2 机器人并联机构涉及机器人的左右腿的脚踝。因左右脚踝对称,故下面以 H1_2 左脚踝关节为例:
<p align="center"><img src="doc/images/ankle.png" width="30%"/></p>
硬件上,H1_2 左脚踝关节采用并联机构,包括四个关节:
* 并联关节:A 关节、B 关节
* 串联关节:Pitch 关节 (简称 P 关节)、Roll 关节 (简称 R 关)
其中只有 A、B 关节是可被电机直接驱动的关节,P、R 关节不能被直接控制。而机器人 URDF 模型和运动控制算法通常只考虑 P、R 串联关节。为了实现对 P、R 关节的控制,我们通过控制 A、B 关节从而间接实现对 P、R 关节的控制。具体到软件上,我们为用户提供了等价的串联关节控制接口,即 `PR 模式`,让用户能直接控制 P、R 关节。与普通关节控制方法一样,`PR 模式` 下每个串联关节接收以下指令:
| 指令名称 | 变量 |
| ---------- | ----- |
| 前馈力矩 | `tau` |
| 目标角度 | `q` |
| 目标角速度 | `dq` |
| 关节刚度 | `kp` |
| 关节阻尼 | `kd` |
最终串联关节执行总的力矩为 `T = kp * (q - q_m) + kd * (dq - dq_m) + tau`。为了提高串联关节控制精度,H1_2 机器人内部根据运动学和动力学关系,把 P、R 关节的前馈力矩、目标角度、目标角速度、关节刚度和关节阻尼指令转换为 A、B 实际关节执行单元。
## 串联关节跟踪实验
为测试 H1_2 脚踝 `PR 模式` 控制效果,我们让脚踝 P、R 关节跟踪正弦曲线,参考[测试例程](https://github.com/unitreerobotics/unitree_sdk2/blob/main/example/h1/low_level/h1_2_ankle_track.cpp)。核心代码段如下:
**启用 PR 模式并生成正弦曲线**
```c++
// [Stage 2]: swing ankle's PR
mode_ = PR; // Enable PR mode
// generate sin/cos trajectory
double max_P = 0.25; // [rad]
double max_R = 0.25; // [rad]
double t = time_ - duration_;
double L_P_des = max_P * std::cos(2.0 * M_PI * t);
double L_R_des = max_R * std::sin(2.0 * M_PI * t);
double R_P_des = max_P * std::cos(2.0 * M_PI * t);
double R_R_des = -max_R * std::sin(2.0 * M_PI * t);
```
**设置踝关节指令**
```c++
// update ankle joint position targets
float Kp_Pitch = 80;
float Kd_Pitch = 1;
float Kp_Roll = 80;
float Kd_Roll = 1;
dds_low_command.motor_cmd().at(4).q() = L_P_des; // 4: LeftAnklePitch
dds_low_command.motor_cmd().at(4).dq() = 0;
dds_low_command.motor_cmd().at(4).kp() = Kp_Pitch;
dds_low_command.motor_cmd().at(4).kd() = Kd_Pitch;
dds_low_command.motor_cmd().at(4).tau() = 0;
dds_low_command.motor_cmd().at(5).q() = L_R_des; // 5: LeftAnkleRoll
dds_low_command.motor_cmd().at(5).dq() = 0;
dds_low_command.motor_cmd().at(5).kp() = Kp_Roll;
dds_low_command.motor_cmd().at(5).kd() = Kd_Roll;
dds_low_command.motor_cmd().at(5).tau() = 0;
dds_low_command.motor_cmd().at(10).q() = R_P_des; // 10: RightAnklePitch
dds_low_command.motor_cmd().at(10).dq() = 0;
dds_low_command.motor_cmd().at(10).kp() = Kp_Pitch;
dds_low_command.motor_cmd().at(10).kd() = Kd_Pitch;
dds_low_command.motor_cmd().at(10).tau() = 0;
dds_low_command.motor_cmd().at(11).q() = R_R_des; // 11: RightAnkleRoll
dds_low_command.motor_cmd().at(11).dq() = 0;
dds_low_command.motor_cmd().at(11).kp() = Kp_Roll;
dds_low_command.motor_cmd().at(11).kd() = Kd_Roll;
dds_low_command.motor_cmd().at(11).tau() = 0;
```
**打印期望值和测量值到终端**
```c++
float L_P_m = low_state_.motor_state().at(4).q();
float L_R_m = low_state_.motor_state().at(5).q();
float R_P_m = low_state_.motor_state().at(10).q();
float R_R_m = low_state_.motor_state().at(11).q();
printf("%f,%f,%f,%f,%f,%f,%f,%f\n", L_P_des, L_P_m, L_R_des, L_R_m, R_P_des, R_P_m, R_R_des, R_R_m);
```
| L_P_des | L_P_m | L_R_des | L_R_m | R_P_des | R_P_m | R_R_des | R_R_m |
| ----------------- | ----------------- | ---------------- | ---------------- | ----------------- | ----------------- | ---------------- | ---------------- |
| 左脚 Pitch 期望值 | 左脚 Pitch 测量值 | 左脚 Roll 期望值 | 左脚 Roll 测量值 | 右脚 Pitch 期望值 | 右脚 Pitch 测量值 | 右脚 Roll 期望值 | 右脚 Roll 测量值 |
安装并编译 [unitree_sdk2](https://github.com/unitreerobotics/unitree_sdk2),务必先把机器人**悬挂起来**,然后终端运行测试例程:
```bash
h1_2_ankle_track network_interface
```
启动后,机器人会先恢复到零位,然后周期性摆动脚踝,并打印左右踝关节期望值和测量值,其位置跟踪效果如图:
<p float="middle">
<img src="doc/images/tracking.png" width="49%"/>
<img src="doc/images/tracking_circle.png" width="49%"/>
</p>
图中符号定义:
* `L_Pitch_d`:左脚踝 P 关节期望值
* `L_Pitch_m`:左脚踝 P 关节测量值
* `L_Roll_d`:左脚踝 R 关节期望值
* `L_Roll_m`:左脚踝 R 关节测量值
左图表示串联 Pitch、Roll 关节能较准确地跟踪正弦曲线目标位置指令;右图表示串联关节在相空间中的跟踪效果。

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@@ -0,0 +1,259 @@
#include <array>
#include <chrono>
#include <iostream>
#include <thread>
#include <unitree/idl/hg/LowCmd_.hpp>
#include <unitree/idl/hg/LowState_.hpp>
#include <unitree/robot/channel/channel_publisher.hpp>
#include <unitree/robot/channel/channel_subscriber.hpp>
static const std::string kTopicArmSDK = "rt/arm_sdk";
static const std::string kTopicState = "rt/lowstate";
constexpr float kPi = 3.141592654;
constexpr float kPi_2 = 1.57079632;
enum JointIndex {
// Left leg
kLeftHipYaw = 0,
kLeftHipPitch = 1,
kLeftHipRoll = 2,
kLeftKnee = 3,
kLeftAnkle = 4,
kLeftAnkleRoll = 5,
// Right leg
kRightHipYaw = 6,
kRightHipPitch = 7,
kRightHipRoll = 8,
kRightKnee = 9,
kRightAnkle = 10,
kRightAnkleRoll = 11,
kWaistYaw = 12,
// Left arm
kLeftShoulderPitch = 13,
kLeftShoulderRoll = 14,
kLeftShoulderYaw = 15,
kLeftElbow = 16,
kLeftWristRoll = 17,
kLeftWristPitch = 18,
kLeftWristYaw = 19,
// Right arm
kRightShoulderPitch = 20,
kRightShoulderRoll = 21,
kRightShoulderYaw = 22,
kRightElbow = 23,
kRightWristRoll = 24,
kRightWristPitch = 25,
kRightWristYaw = 26,
kNotUsedJoint = 27,
kNotUsedJoint1 = 28,
kNotUsedJoint2 = 29,
kNotUsedJoint3 = 30,
kNotUsedJoint4 = 31,
kNotUsedJoint5 = 32,
kNotUsedJoint6 = 33,
kNotUsedJoint7 = 34
};
int main(int argc, char const *argv[]) {
if (argc < 2) {
std::cout << "Usage: " << argv[0] << " networkInterface" << std::endl;
exit(-1);
}
unitree::robot::ChannelFactory::Instance()->Init(0, argv[1]);
unitree::robot::ChannelPublisherPtr<unitree_hg::msg::dds_::LowCmd_>
arm_sdk_publisher;
unitree_hg::msg::dds_::LowCmd_ msg;
arm_sdk_publisher.reset(
new unitree::robot::ChannelPublisher<unitree_hg::msg::dds_::LowCmd_>(
kTopicArmSDK));
arm_sdk_publisher->InitChannel();
unitree::robot::ChannelSubscriberPtr<unitree_hg::msg::dds_::LowState_>
low_state_subscriber;
// create subscriber
unitree_hg::msg::dds_::LowState_ state_msg;
low_state_subscriber.reset(
new unitree::robot::ChannelSubscriber<unitree_hg::msg::dds_::LowState_>(
kTopicState));
low_state_subscriber->InitChannel([&](const void *msg) {
auto s = ( const unitree_hg::msg::dds_::LowState_* )msg;
memcpy( &state_msg, s, sizeof( unitree_hg::msg::dds_::LowState_ ) );
}, 1);
std::array<JointIndex, 15> arm_joints = {
JointIndex::kLeftShoulderPitch, JointIndex::kLeftShoulderRoll,
JointIndex::kLeftShoulderYaw, JointIndex::kLeftElbow,
JointIndex::kLeftWristRoll, JointIndex::kLeftWristPitch, JointIndex::kLeftWristYaw,
JointIndex::kRightShoulderPitch, JointIndex::kRightShoulderRoll,
JointIndex::kRightShoulderYaw, JointIndex::kRightElbow,
JointIndex::kRightWristRoll, JointIndex::kRightWristPitch, JointIndex::kRightWristYaw,
JointIndex::kWaistYaw};
float weight = 0.f;
float weight_rate = 0.2f;
std::array<float, 15> kp_array = { 120, 120, 80, 50, 50, 50, 50,
120, 120, 80, 50, 50, 50, 50,
200 };
std::array<float, 15> kd_array = { 2.0, 2.0, 1.5, 1.0, 1.0, 1.0, 1.0,
2.0, 2.0, 1.5, 1.0, 1.0, 1.0, 1.0,
2.0 };
float dq = 0.f;
float tau_ff = 0.f;
float control_dt = 0.02f;
float max_joint_velocity = 0.5f;
float delta_weight = weight_rate * control_dt;
float max_joint_delta = max_joint_velocity * control_dt;
auto sleep_time =
std::chrono::milliseconds(static_cast<int>(control_dt / 0.001f));
// modify the initial position to avoid collision
std::array<float, 15> init_pos{0.f, 0.3, 0.f, 0, 0, 0, 0,
0.f, -0.3, 0.f, 0, 0, 0, 0,
0.f};
std::array<float, 15> target_pos = {0.f, kPi_2, 0.f, kPi_2, 0, 0, 0,
0.f, -kPi_2, 0.f, kPi_2, 0, 0, 0,
0.f};
// wait for init
std::cout << "Press ENTER to init arms ...";
std::cin.get();
// get current joint position
std::array<float, 15> current_jpos{};
std::cout<<"Current joint position: ";
for (int i = 0; i < arm_joints.size(); ++i) {
current_jpos.at(i) = state_msg.motor_state().at(arm_joints.at(i)).q();
std::cout << current_jpos.at(i) << " ";
}
std::cout << std::endl;
// set init pos
std::cout << "Initailizing arms ...";
float init_time = 2.0f;
int init_time_steps = static_cast<int>(init_time / control_dt);
for (int i = 0; i < init_time_steps; ++i) {
// set weight
weight = 1.0;
msg.motor_cmd().at(JointIndex::kNotUsedJoint).q(weight);
float phase = 1.0 * i / init_time_steps;
std::cout << "Phase: " << phase << std::endl;
// set control joints
for (int j = 0; j < init_pos.size(); ++j) {
msg.motor_cmd().at(arm_joints.at(j)).q(init_pos.at(j) * phase + current_jpos.at(j) * (1 - phase));
msg.motor_cmd().at(arm_joints.at(j)).dq(dq);
msg.motor_cmd().at(arm_joints.at(j)).kp(kp_array.at(j));
msg.motor_cmd().at(arm_joints.at(j)).kd(kd_array.at(j));
msg.motor_cmd().at(arm_joints.at(j)).tau(tau_ff);
}
// send dds msg
arm_sdk_publisher->Write(msg);
// sleep
std::this_thread::sleep_for(sleep_time);
}
std::cout << "Done!" << std::endl;
// wait for control
std::cout << "Press ENTER to start arm ctrl ..." << std::endl;
std::cin.get();
// start control
std::cout << "Start arm ctrl!" << std::endl;
float period = 5.f;
int num_time_steps = static_cast<int>(period / control_dt);
std::array<float, 15> current_jpos_des{0.f, 0.3, 0.f, 0, 0, 0, 0,
0.f, -0.3, 0.f, 0, 0, 0, 0,
0.f};
// lift arms up
for (int i = 0; i < num_time_steps; ++i) {
// update jpos des
for (int j = 0; j < init_pos.size(); ++j) {
current_jpos_des.at(j) +=
std::clamp(target_pos.at(j) - current_jpos_des.at(j),
-max_joint_delta, max_joint_delta);
}
// set control joints
for (int j = 0; j < init_pos.size(); ++j) {
msg.motor_cmd().at(arm_joints.at(j)).q(current_jpos_des.at(j));
msg.motor_cmd().at(arm_joints.at(j)).dq(dq);
msg.motor_cmd().at(arm_joints.at(j)).kp(kp_array.at(j));
msg.motor_cmd().at(arm_joints.at(j)).kd(kd_array.at(j));
msg.motor_cmd().at(arm_joints.at(j)).tau(tau_ff);
}
// send dds msg
arm_sdk_publisher->Write(msg);
// sleep
std::this_thread::sleep_for(sleep_time);
}
// put arms down
for (int i = 0; i < num_time_steps; ++i) {
// update jpos des
for (int j = 0; j < init_pos.size(); ++j) {
current_jpos_des.at(j) +=
std::clamp(init_pos.at(j) - current_jpos_des.at(j), -max_joint_delta,
max_joint_delta);
}
// set control joints
for (int j = 0; j < init_pos.size(); ++j) {
msg.motor_cmd().at(arm_joints.at(j)).q(current_jpos_des.at(j));
msg.motor_cmd().at(arm_joints.at(j)).dq(dq);
msg.motor_cmd().at(arm_joints.at(j)).kp(kp_array.at(j));
msg.motor_cmd().at(arm_joints.at(j)).kd(kd_array.at(j));
msg.motor_cmd().at(arm_joints.at(j)).tau(tau_ff);
}
// send dds msg
arm_sdk_publisher->Write(msg);
// sleep
std::this_thread::sleep_for(sleep_time);
}
// stop control
std::cout << "Stoping arm ctrl ...";
float stop_time = 2.0f;
int stop_time_steps = static_cast<int>(stop_time / control_dt);
for (int i = 0; i < stop_time_steps; ++i) {
// increase weight
weight -= delta_weight;
weight = std::clamp(weight, 0.f, 1.f);
// set weight
msg.motor_cmd().at(JointIndex::kNotUsedJoint).q(weight);
// send dds msg
arm_sdk_publisher->Write(msg);
// sleep
std::this_thread::sleep_for(sleep_time);
}
std::cout << "Done!" << std::endl;
return 0;
}

View File

@@ -0,0 +1,234 @@
#include <array>
#include <chrono>
#include <iostream>
#include <thread>
#include <unitree/idl/go2/LowCmd_.hpp>
#include <unitree/idl/hg/LowState_.hpp>
#include <unitree/robot/channel/channel_publisher.hpp>
#include <unitree/robot/channel/channel_subscriber.hpp>
static const std::string kTopicArmSDK = "rt/arm_sdk";
static const std::string kTopicState = "rt/lowstate";
constexpr float kPi = 3.141592654;
constexpr float kPi_2 = 1.57079632;
enum JointIndex {
// Right leg
kRightHipYaw = 8,
kRightHipRoll = 0,
kRightHipPitch = 1,
kRightKnee = 2,
kRightAnkle = 11,
// Left leg
kLeftHipYaw = 7,
kLeftHipRoll = 3,
kLeftHipPitch = 4,
kLeftKnee = 5,
kLeftAnkle = 10,
kWaistYaw = 6,
kNotUsedJoint = 9,
// Right arm
kRightShoulderPitch = 12,
kRightShoulderRoll = 13,
kRightShoulderYaw = 14,
kRightElbow = 15,
// Left arm
kLeftShoulderPitch = 16,
kLeftShoulderRoll = 17,
kLeftShoulderYaw = 18,
kLeftElbow = 19,
};
int main(int argc, char const *argv[]) {
if (argc < 2) {
std::cout << "Usage: " << argv[0] << " networkInterface" << std::endl;
exit(-1);
}
unitree::robot::ChannelFactory::Instance()->Init(0, argv[1]);
unitree::robot::ChannelPublisherPtr<unitree_go::msg::dds_::LowCmd_>
arm_sdk_publisher;
unitree_go::msg::dds_::LowCmd_ msg;
arm_sdk_publisher.reset(
new unitree::robot::ChannelPublisher<unitree_go::msg::dds_::LowCmd_>(
kTopicArmSDK));
arm_sdk_publisher->InitChannel();
unitree::robot::ChannelSubscriberPtr<unitree_hg::msg::dds_::LowState_>
low_state_subscriber;
// create subscriber
unitree_hg::msg::dds_::LowState_ state_msg;
low_state_subscriber.reset(
new unitree::robot::ChannelSubscriber<unitree_hg::msg::dds_::LowState_>(
kTopicState));
low_state_subscriber->InitChannel([&](const void *msg) {
auto s = ( const unitree_hg::msg::dds_::LowState_* )msg;
memcpy( &state_msg, s, sizeof( unitree_hg::msg::dds_::LowState_ ) );
}, 1);
std::array<JointIndex, 9> arm_joints = {
JointIndex::kLeftShoulderPitch, JointIndex::kLeftShoulderRoll,
JointIndex::kLeftShoulderYaw, JointIndex::kLeftElbow,
JointIndex::kRightShoulderPitch, JointIndex::kRightShoulderRoll,
JointIndex::kRightShoulderYaw, JointIndex::kRightElbow, JointIndex::kWaistYaw};
float weight = 0.f;
float weight_rate = 0.2f;
float kp = 60.f;
float kd = 1.5f;
float dq = 0.f;
float tau_ff = 0.f;
float control_dt = 0.02f;
float max_joint_velocity = 0.5f;
float delta_weight = weight_rate * control_dt;
float max_joint_delta = max_joint_velocity * control_dt;
auto sleep_time =
std::chrono::milliseconds(static_cast<int>(control_dt / 0.001f));
std::array<float, 9> init_pos{0, 0, 0, 0,
0, 0, 0, 0,
0};
std::array<float, 9> target_pos = {0.f, kPi_2, 0.f, kPi_2,
0.f, -kPi_2, 0.f, kPi_2,
0.f};
// wait for init
std::cout << "Press ENTER to init arms ...";
std::cin.get();
// get current joint position
std::array<float, 9> current_jpos{};
std::cout<<"Current joint position: ";
for (int i = 0; i < arm_joints.size(); ++i) {
current_jpos.at(i) = state_msg.motor_state().at(arm_joints.at(i)).q();
std::cout << current_jpos.at(i) << " ";
}
std::cout << std::endl;
// set init pos
std::cout << "Initailizing arms ...";
float init_time = 2.0f;
int init_time_steps = static_cast<int>(init_time / control_dt);
for (int i = 0; i < init_time_steps; ++i) {
// set weight
weight = 1.0;
msg.motor_cmd().at(JointIndex::kNotUsedJoint).q(weight);
float phase = 1.0 * i / init_time_steps;
std::cout << "Phase: " << phase << std::endl;
// set control joints
for (int j = 0; j < init_pos.size(); ++j) {
msg.motor_cmd().at(arm_joints.at(j)).q(init_pos.at(j) * phase + current_jpos.at(j) * (1 - phase));
msg.motor_cmd().at(arm_joints.at(j)).dq(dq);
msg.motor_cmd().at(arm_joints.at(j)).kp(kp);
msg.motor_cmd().at(arm_joints.at(j)).kd(kd);
msg.motor_cmd().at(arm_joints.at(j)).tau(tau_ff);
}
// send dds msg
arm_sdk_publisher->Write(msg);
// sleep
std::this_thread::sleep_for(sleep_time);
}
std::cout << "Done!" << std::endl;
// wait for control
std::cout << "Press ENTER to start arm ctrl ..." << std::endl;
std::cin.get();
// start control
std::cout << "Start arm ctrl!" << std::endl;
float period = 5.f;
int num_time_steps = static_cast<int>(period / control_dt);
std::array<float, 9> current_jpos_des{};
// lift arms up
for (int i = 0; i < num_time_steps; ++i) {
// update jpos des
for (int j = 0; j < init_pos.size(); ++j) {
current_jpos_des.at(j) +=
std::clamp(target_pos.at(j) - current_jpos_des.at(j),
-max_joint_delta, max_joint_delta);
}
// set control joints
for (int j = 0; j < init_pos.size(); ++j) {
msg.motor_cmd().at(arm_joints.at(j)).q(current_jpos_des.at(j));
msg.motor_cmd().at(arm_joints.at(j)).dq(dq);
msg.motor_cmd().at(arm_joints.at(j)).kp(kp);
msg.motor_cmd().at(arm_joints.at(j)).kd(kd);
msg.motor_cmd().at(arm_joints.at(j)).tau(tau_ff);
}
// send dds msg
arm_sdk_publisher->Write(msg);
// sleep
std::this_thread::sleep_for(sleep_time);
}
// put arms down
for (int i = 0; i < num_time_steps; ++i) {
// update jpos des
for (int j = 0; j < init_pos.size(); ++j) {
current_jpos_des.at(j) +=
std::clamp(init_pos.at(j) - current_jpos_des.at(j), -max_joint_delta,
max_joint_delta);
}
// set control joints
for (int j = 0; j < init_pos.size(); ++j) {
msg.motor_cmd().at(arm_joints.at(j)).q(current_jpos_des.at(j));
msg.motor_cmd().at(arm_joints.at(j)).dq(dq);
msg.motor_cmd().at(arm_joints.at(j)).kp(kp);
msg.motor_cmd().at(arm_joints.at(j)).kd(kd);
msg.motor_cmd().at(arm_joints.at(j)).tau(tau_ff);
}
// send dds msg
arm_sdk_publisher->Write(msg);
// sleep
std::this_thread::sleep_for(sleep_time);
}
// stop control
std::cout << "Stoping arm ctrl ...";
float stop_time = 2.0f;
int stop_time_steps = static_cast<int>(stop_time / control_dt);
for (int i = 0; i < stop_time_steps; ++i) {
// increase weight
weight -= delta_weight;
weight = std::clamp(weight, 0.f, 1.f);
// set weight
msg.motor_cmd().at(JointIndex::kNotUsedJoint).q(weight);
// send dds msg
arm_sdk_publisher->Write(msg);
// sleep
std::this_thread::sleep_for(sleep_time);
}
std::cout << "Done!" << std::endl;
return 0;
}

View File

@@ -0,0 +1,300 @@
#include <chrono>
#include <iostream>
#include <thread>
#include <unitree/robot/h1/loco/h1_loco_api.hpp>
#include <unitree/robot/h1/loco/h1_loco_client.hpp>
std::vector<float> stringToFloatVector(const std::string &str) {
std::vector<float> result;
std::stringstream ss(str);
float num;
while (ss >> num) {
result.push_back(num);
// ignore any trailing whitespace
ss.ignore();
}
return result;
}
int main(int argc, char const *argv[]) {
std::map<std::string, std::string> args = {{"network_interface", "lo"}};
std::map<std::string, std::string> values;
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if (arg.substr(0, 2) == "--") {
size_t pos = arg.find("=");
std::string key, value;
if (pos != std::string::npos) {
key = arg.substr(2, pos - 2);
value = arg.substr(pos + 1);
if (value.front() == '"' && value.back() == '"') {
value = value.substr(1, value.length() - 2);
}
} else {
key = arg.substr(2);
value = "";
}
if (args.find(key) != args.end()) {
args[key] = value;
} else {
args.insert({{key, value}});
}
}
}
unitree::robot::ChannelFactory::Instance()->Init(0, args["network_interface"]);
unitree::robot::h1::LocoClient client;
client.Init();
client.SetTimeout(10.f);
for (const auto &arg_pair : args) {
std::cout << "Processing command: [" << arg_pair.first << "] with param: [" << arg_pair.second << "] ..."
<< std::endl;
if (arg_pair.first == "network_interface") {
continue;
}
if (arg_pair.first == "get_fsm_id") {
int fsm_id;
client.GetFsmId(fsm_id);
std::cout << "current fsm_id: " << fsm_id << std::endl;
}
if (arg_pair.first == "get_fsm_mode") {
int fsm_mode;
client.GetFsmMode(fsm_mode);
std::cout << "current fsm_mode: " << fsm_mode << std::endl;
}
if (arg_pair.first == "get_balance_mode") {
int balance_mode;
client.GetBalanceMode(balance_mode);
std::cout << "current balance_mode: " << balance_mode << std::endl;
}
if (arg_pair.first == "get_swing_height") {
float swing_height;
client.GetSwingHeight(swing_height);
std::cout << "current swing_height: " << swing_height << std::endl;
}
if (arg_pair.first == "get_stand_height") {
float stand_height;
client.GetStandHeight(stand_height);
std::cout << "current stand_height: " << stand_height << std::endl;
}
if (arg_pair.first == "get_phase") {
std::vector<float> phase;
client.GetPhase(phase);
std::cout << "current phase: (";
for (const auto &p : phase) {
std::cout << p << ", ";
}
std::cout << ")" << std::endl;
}
if (arg_pair.first == "enable_odom") {
float x, y, yaw;
client.EnableOdom();
std::cout << "Send enable odom signal" << std::endl;
}
if (arg_pair.first == "disable_odom") {
float x, y, yaw;
client.DisableOdom();
std::cout << "Send disable odom signal" << std::endl;
}
if (arg_pair.first == "get_odom") {
float x, y, yaw;
client.GetOdom(x, y, yaw);
std::cout << "Get Odom: (" << x << ", " << y << ", " << yaw << ")" << std::endl;
}
if (arg_pair.first == "set_fsm_id") {
int fsm_id = std::stoi(arg_pair.second);
client.SetFsmId(fsm_id);
std::cout << "set fsm_id to " << fsm_id << std::endl;
}
if (arg_pair.first == "set_balance_mode") {
int balance_mode = std::stoi(arg_pair.second);
client.SetBalanceMode(balance_mode);
std::cout << "set balance_mode to " << balance_mode << std::endl;
}
if (arg_pair.first == "set_swing_height") {
float swing_height = std::stof(arg_pair.second);
client.SetSwingHeight(swing_height);
std::cout << "set swing_height to " << swing_height << std::endl;
}
if (arg_pair.first == "set_stand_height") {
float stand_height = std::stof(arg_pair.second);
client.SetStandHeight(stand_height);
std::cout << "set stand_height to " << stand_height << std::endl;
}
if (arg_pair.first == "set_velocity") {
std::vector<float> param = stringToFloatVector(arg_pair.second);
auto param_size = param.size();
float vx, vy, omega, duration;
if (param_size == 3) {
vx = param.at(0);
vy = param.at(1);
omega = param.at(2);
duration = 1.f;
} else if (param_size == 4) {
vx = param.at(0);
vy = param.at(1);
omega = param.at(2);
duration = param.at(3);
} else {
std::cerr << "Invalid param size for method SetVelocity: " << param_size << std::endl;
return 1;
}
client.SetVelocity(vx, vy, omega, duration);
std::cout << "set velocity to " << arg_pair.second << std::endl;
}
if (arg_pair.first == "set_phase") {
std::vector<float> param = stringToFloatVector(arg_pair.second);
auto param_size = param.size();
if (param_size == 2) {
client.SetPhase(param);
std::cout << "set phase to " << arg_pair.second << std::endl;
} else {
std::cerr << "Invalid param size for method SetPhase: " << param_size << std::endl;
return 1;
}
}
if (arg_pair.first == "set_target_pos") {
std::vector<float> param = stringToFloatVector(arg_pair.second);
client.SetTargetPos(param.at(0), param.at(1), param.at(2), false);
std::cout << "set_target_pos: " << arg_pair.second << std::endl;
}
if (arg_pair.first == "set_target_pos_relative") {
std::vector<float> param = stringToFloatVector(arg_pair.second);
client.SetTargetPos(param.at(0), param.at(1), param.at(2));
std::cout << "set_target_pos: " << arg_pair.second << std::endl;
}
if (arg_pair.first == "damp") {
client.Damp();
}
if (arg_pair.first == "start") {
client.Start();
}
if (arg_pair.first == "stand_up") {
client.StandUp();
}
if (arg_pair.first == "zero_torque") {
client.ZeroTorque();
}
if (arg_pair.first == "stop_move") {
client.StopMove();
}
if (arg_pair.first == "high_stand") {
client.HighStand();
}
if (arg_pair.first == "low_stand") {
client.LowStand();
}
if (arg_pair.first == "balance_stand") {
client.BalanceStand();
}
if (arg_pair.first == "continous_gait") {
bool flag;
if (arg_pair.second == "true") {
flag = true;
} else if (arg_pair.second == "false") {
flag = false;
} else {
std::cerr << "invalid argument: " << arg_pair.second << std::endl;
return 1;
}
client.ContinuousGait(flag);
}
if (arg_pair.first == "switch_move_mode") {
bool flag;
if (arg_pair.second == "true") {
flag = true;
} else if (arg_pair.second == "false") {
flag = false;
} else {
std::cerr << "invalid argument: " << arg_pair.second << std::endl;
return 1;
}
client.SwitchMoveMode(flag);
}
if (arg_pair.first == "move") {
std::vector<float> param = stringToFloatVector(arg_pair.second);
auto param_size = param.size();
float vx, vy, omega;
if (param_size == 3) {
vx = param.at(0);
vy = param.at(1);
omega = param.at(2);
} else {
std::cerr << "Invalid param size for method SetVelocity: " << param_size << std::endl;
return 1;
}
client.Move(vx, vy, omega);
}
if (arg_pair.first == "set_next_foot") {
bool flag;
if (arg_pair.second == "0") {
flag = true;
} else if (arg_pair.second == "1") {
flag = false;
} else {
std::cerr << "invalid argument: " << arg_pair.second << std::endl;
return 1;
}
client.SetNextFoot(flag);
}
if (arg_pair.first == "set_task_id") {
int task_id = std::stoi(arg_pair.second);
client.SetTaskId(task_id);
std::cout << "set task_id to " << task_id << std::endl;
}
if (arg_pair.first == "shake_hand") {
client.ShakeHand(0);
std::cout << "Shake hand starts! Waiting for 10 s for ending" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(10));
std::cout << "Shake hand ends!" << std::endl;
client.ShakeHand(1);
}
if (arg_pair.first == "wave_hand") {
client.WaveHand();
std::cout << "wave hand" << std::endl;
}
std::cout << "Done!" << std::endl;
}
return 0;
}

View File

@@ -0,0 +1,8 @@
#pragma once
#include <array>
struct BaseState {
std::array<float, 3> rpy = {};
std::array<float, 3> omega = {};
};

View File

@@ -0,0 +1,28 @@
#pragma once
#include <deque>
#include <memory>
#include <mutex>
#include <shared_mutex>
template <typename T> class DataBuffer {
public:
void SetData(const T &newData) {
std::unique_lock<std::shared_mutex> lock(mutex);
data = std::make_shared<T>(newData);
}
std::shared_ptr<const T> GetData() {
std::shared_lock<std::shared_mutex> lock(mutex);
return data ? data : nullptr;
}
void Clear() {
std::unique_lock<std::shared_mutex> lock(mutex);
data = nullptr;
}
private:
std::shared_ptr<T> data;
std::shared_mutex mutex;
};

View File

@@ -0,0 +1,367 @@
#include <algorithm>
#include <cmath>
#include <memory>
#include <mutex>
#include <shared_mutex>
// DDS
#include <unitree/robot/channel/channel_publisher.hpp>
#include <unitree/robot/channel/channel_subscriber.hpp>
// IDL
#include <unitree/idl/hg/LowCmd_.hpp>
#include <unitree/idl/hg/LowState_.hpp>
static const std::string HG_CMD_TOPIC = "rt/lowcmd";
static const std::string HG_STATE_TOPIC = "rt/lowstate";
using namespace unitree::common;
using namespace unitree::robot;
const int H1_NUM_MOTOR = 27;
template <typename T>
class DataBuffer {
public:
void SetData(const T &newData) {
std::unique_lock<std::shared_mutex> lock(mutex);
data = std::make_shared<T>(newData);
}
std::shared_ptr<const T> GetData() {
std::shared_lock<std::shared_mutex> lock(mutex);
return data ? data : nullptr;
}
void Clear() {
std::unique_lock<std::shared_mutex> lock(mutex);
data = nullptr;
}
private:
std::shared_ptr<T> data;
std::shared_mutex mutex;
};
struct ImuState {
std::array<float, 3> rpy = {};
std::array<float, 3> omega = {};
};
struct MotorCommand {
std::array<float, H1_NUM_MOTOR> q_target = {};
std::array<float, H1_NUM_MOTOR> dq_target = {};
std::array<float, H1_NUM_MOTOR> kp = {};
std::array<float, H1_NUM_MOTOR> kd = {};
std::array<float, H1_NUM_MOTOR> tau_ff = {};
};
struct MotorState {
std::array<float, H1_NUM_MOTOR> q = {};
std::array<float, H1_NUM_MOTOR> dq = {};
};
enum MotorType { GearboxS = 0, GearboxM = 1, GearboxL = 2 };
std::array<MotorType, H1_NUM_MOTOR> H1MotorType{
// clang-format off
// legs
GearboxM, GearboxM, GearboxM, GearboxL, GearboxS, GearboxS,
GearboxM, GearboxM, GearboxM, GearboxL, GearboxS, GearboxS,
// waist
GearboxM,
// arms
GearboxS, GearboxS, GearboxS, GearboxS, GearboxS, GearboxS, GearboxS,
GearboxS, GearboxS, GearboxS, GearboxS, GearboxS, GearboxS, GearboxS
// clang-format on
};
enum PRorAB { PR = 0, AB = 1 };
enum H1JointIndex {
// legs
LeftHipYaw = 0,
LeftHipPitch = 1,
LeftHipRoll = 2,
LeftKnee = 3,
LeftAnklePitch = 4,
LeftAnkleB = 4,
LeftAnkleRoll = 5,
LeftAnkleA = 5,
RightHipYaw = 6,
RightHipPitch = 7,
RightHipRoll = 8,
RightKnee = 9,
RightAnklePitch = 10,
RightAnkleB = 10,
RightAnkleRoll = 11,
RightAnkleA = 11,
// torso
WaistYaw = 12,
// arms
LeftShoulderPitch = 13,
LeftShoulderRoll = 14,
LeftShoulderYaw = 15,
LeftElbow = 16,
LeftWristRoll = 17,
LeftWristPitch = 18,
LeftWristYaw = 19,
RightShoulderPitch = 20,
RightShoulderRoll = 21,
RightShoulderYaw = 22,
RightElbow = 23,
RightWristRoll = 24,
RightWristPitch = 25,
RightWristYaw = 26
};
inline uint32_t Crc32Core(uint32_t *ptr, uint32_t len) {
uint32_t xbit = 0;
uint32_t data = 0;
uint32_t CRC32 = 0xFFFFFFFF;
const uint32_t dwPolynomial = 0x04c11db7;
for (uint32_t i = 0; i < len; i++) {
xbit = 1 << 31;
data = ptr[i];
for (uint32_t bits = 0; bits < 32; bits++) {
if (CRC32 & 0x80000000) {
CRC32 <<= 1;
CRC32 ^= dwPolynomial;
} else
CRC32 <<= 1;
if (data & xbit) CRC32 ^= dwPolynomial;
xbit >>= 1;
}
}
return CRC32;
};
float GetMotorKp(MotorType type) {
switch (type) {
case GearboxS:
return 80;
case GearboxM:
return 100;
case GearboxL:
return 200;
default:
return 0;
}
}
float GetMotorKd(MotorType type) {
switch (type) {
case GearboxS:
return 2;
case GearboxM:
return 3;
case GearboxL:
return 5;
default:
return 0;
}
}
class H1Example {
private:
double time_;
double control_dt_; // [2ms]
double duration_; // [3 s]
PRorAB mode_;
uint8_t mode_machine_;
DataBuffer<MotorState> motor_state_buffer_;
DataBuffer<MotorCommand> motor_command_buffer_;
DataBuffer<ImuState> imu_state_buffer_;
ChannelPublisherPtr<unitree_hg::msg::dds_::LowCmd_> lowcmd_publisher_;
ChannelSubscriberPtr<unitree_hg::msg::dds_::LowState_> lowstate_subscriber_;
ThreadPtr command_writer_ptr_, control_thread_ptr_;
public:
H1Example(std::string networkInterface)
: time_(0.0),
control_dt_(0.002),
duration_(3.0),
mode_(PR),
mode_machine_(0) {
ChannelFactory::Instance()->Init(0, networkInterface);
// create publisher
lowcmd_publisher_.reset(
new ChannelPublisher<unitree_hg::msg::dds_::LowCmd_>(HG_CMD_TOPIC));
lowcmd_publisher_->InitChannel();
// create subscriber
lowstate_subscriber_.reset(
new ChannelSubscriber<unitree_hg::msg::dds_::LowState_>(
HG_STATE_TOPIC));
lowstate_subscriber_->InitChannel(
std::bind(&H1Example::LowStateHandler, this, std::placeholders::_1), 1);
// create threads
command_writer_ptr_ =
CreateRecurrentThreadEx("command_writer", UT_CPU_ID_NONE, 2000,
&H1Example::LowCommandWriter, this);
control_thread_ptr_ = CreateRecurrentThreadEx(
"control", UT_CPU_ID_NONE, 2000, &H1Example::Control, this);
}
void ReportRPY() {
const std::shared_ptr<const ImuState> imu_tmp_ptr =
imu_state_buffer_.GetData();
if (imu_tmp_ptr) {
std::cout << "rpy: [" << imu_tmp_ptr->rpy.at(0) << ", "
<< imu_tmp_ptr->rpy.at(1) << ", " << imu_tmp_ptr->rpy.at(2)
<< "]" << std::endl;
}
}
void LowStateHandler(const void *message) {
unitree_hg::msg::dds_::LowState_ low_state =
*(const unitree_hg::msg::dds_::LowState_ *)message;
if (low_state.crc() !=
Crc32Core((uint32_t *)&low_state,
(sizeof(unitree_hg::msg::dds_::LowState_) >> 2) - 1)) {
std::cout << "low_state CRC Error" << std::endl;
return;
}
// get motor state
MotorState ms_tmp;
for (int i = 0; i < H1_NUM_MOTOR; ++i) {
ms_tmp.q.at(i) = low_state.motor_state()[i].q();
ms_tmp.dq.at(i) = low_state.motor_state()[i].dq();
if (low_state.motor_state()[i].motorstate())
std::cout << "[ERROR] motor " << i << " with code "
<< low_state.motor_state()[i].motorstate() << "\n";
}
motor_state_buffer_.SetData(ms_tmp);
// get imu state
ImuState imu_tmp;
imu_tmp.omega = low_state.imu_state().gyroscope();
imu_tmp.rpy = low_state.imu_state().rpy();
imu_state_buffer_.SetData(imu_tmp);
// update mode machine
if (mode_machine_ != low_state.mode_machine()) {
if (mode_machine_ == 0)
std::cout << "G1 type: " << unsigned(low_state.mode_machine())
<< std::endl;
mode_machine_ = low_state.mode_machine();
}
}
void LowCommandWriter() {
unitree_hg::msg::dds_::LowCmd_ dds_low_command;
dds_low_command.mode_pr() = mode_;
dds_low_command.mode_machine() = mode_machine_;
const std::shared_ptr<const MotorCommand> mc =
motor_command_buffer_.GetData();
if (mc) {
for (size_t i = 0; i < H1_NUM_MOTOR; i++) {
dds_low_command.motor_cmd().at(i).mode() = 1; // 1:Enable, 0:Disable
dds_low_command.motor_cmd().at(i).tau() = mc->tau_ff.at(i);
dds_low_command.motor_cmd().at(i).q() = mc->q_target.at(i);
dds_low_command.motor_cmd().at(i).dq() = mc->dq_target.at(i);
dds_low_command.motor_cmd().at(i).kp() = mc->kp.at(i);
dds_low_command.motor_cmd().at(i).kd() = mc->kd.at(i);
}
dds_low_command.crc() = Crc32Core((uint32_t *)&dds_low_command,
(sizeof(dds_low_command) >> 2) - 1);
lowcmd_publisher_->Write(dds_low_command);
}
}
void Control() {
ReportRPY();
MotorCommand motor_command_tmp;
const std::shared_ptr<const MotorState> ms = motor_state_buffer_.GetData();
if (ms) {
time_ += control_dt_;
if (time_ < duration_ * 1) {
// [Stage 1]: set robot to zero posture
for (int i = 0; i < H1_NUM_MOTOR; ++i) {
double ratio = std::clamp(time_ / duration_, 0.0, 1.0);
double q_des = 0;
motor_command_tmp.tau_ff.at(i) = 0.0;
motor_command_tmp.q_target.at(i) =
(q_des - ms->q.at(i)) * ratio + ms->q.at(i);
motor_command_tmp.dq_target.at(i) = 0.0;
motor_command_tmp.kp.at(i) = GetMotorKp(H1MotorType[i]);
motor_command_tmp.kd.at(i) = GetMotorKd(H1MotorType[i]);
}
} else if (time_ < duration_ * 2) {
// [Stage 2]: swing ankle's PR
mode_ = PR;
double max_P = M_PI * 30.0 / 180.0;
double max_R = M_PI * 10.0 / 180.0;
double t = time_ - duration_ * 1;
double L_P_des = +max_P * std::sin(2.0 * M_PI * t);
double L_R_des = +max_R * std::sin(2.0 * M_PI * t);
double R_P_des = +max_P * std::sin(2.0 * M_PI * t);
double R_R_des = -max_R * std::sin(2.0 * M_PI * t);
for (int i = 0; i < H1_NUM_MOTOR; ++i) {
motor_command_tmp.tau_ff.at(i) = 0.0;
motor_command_tmp.q_target.at(i) = 0.0;
motor_command_tmp.dq_target.at(i) = 0.0;
motor_command_tmp.kp.at(i) = GetMotorKp(H1MotorType[i]);
motor_command_tmp.kd.at(i) = GetMotorKd(H1MotorType[i]);
}
motor_command_tmp.q_target.at(LeftAnklePitch) = L_P_des;
motor_command_tmp.q_target.at(LeftAnkleRoll) = L_R_des;
motor_command_tmp.q_target.at(RightAnklePitch) = R_P_des;
motor_command_tmp.q_target.at(RightAnkleRoll) = R_R_des;
} else {
// [Stage 3]: swing ankle's AB
mode_ = AB;
double max_A = M_PI * 30.0 / 180.0;
double max_B = M_PI * 10.0 / 180.0;
double t = time_ - duration_ * 2;
double L_A_des = +max_A * std::sin(M_PI * t);
double L_B_des = +max_B * std::sin(M_PI * t + M_PI);
double R_A_des = -max_A * std::sin(M_PI * t);
double R_B_des = -max_B * std::sin(M_PI * t + M_PI);
for (int i = 0; i < H1_NUM_MOTOR; ++i) {
motor_command_tmp.tau_ff.at(i) = 0.0;
motor_command_tmp.q_target.at(i) = 0.0;
motor_command_tmp.dq_target.at(i) = 0.0;
motor_command_tmp.kp.at(i) = GetMotorKp(H1MotorType[i]);
motor_command_tmp.kd.at(i) = GetMotorKd(H1MotorType[i]);
}
motor_command_tmp.q_target.at(LeftAnkleA) = L_A_des;
motor_command_tmp.q_target.at(LeftAnkleB) = L_B_des;
motor_command_tmp.q_target.at(RightAnkleA) = R_A_des;
motor_command_tmp.q_target.at(RightAnkleB) = R_B_des;
}
motor_command_buffer_.SetData(motor_command_tmp);
}
}
};
int main(int argc, char const *argv[]) {
if (argc < 2) {
std::cout << "Usage: h1_27dof_example network_interface_name" << std::endl;
exit(0);
}
std::string networkInterface = argv[1];
H1Example custom(networkInterface);
while (true) sleep(10);
return 0;
}

View File

@@ -0,0 +1,175 @@
#include <cmath>
// DDS
#include <unitree/robot/channel/channel_publisher.hpp>
#include <unitree/robot/channel/channel_subscriber.hpp>
// IDL
#include <unitree/idl/hg/LowCmd_.hpp>
#include <unitree/idl/hg/LowState_.hpp>
static const std::string HG_CMD_TOPIC = "rt/lowcmd";
static const std::string HG_STATE_TOPIC = "rt/lowstate";
using namespace unitree::common;
using namespace unitree::robot;
const int H1_NUM_MOTOR = 27;
enum PRorAB { PR = 0, AB = 1 };
inline uint32_t Crc32Core(uint32_t *ptr, uint32_t len) {
uint32_t xbit = 0;
uint32_t data = 0;
uint32_t CRC32 = 0xFFFFFFFF;
const uint32_t dwPolynomial = 0x04c11db7;
for (uint32_t i = 0; i < len; i++) {
xbit = 1 << 31;
data = ptr[i];
for (uint32_t bits = 0; bits < 32; bits++) {
if (CRC32 & 0x80000000) {
CRC32 <<= 1;
CRC32 ^= dwPolynomial;
} else
CRC32 <<= 1;
if (data & xbit) CRC32 ^= dwPolynomial;
xbit >>= 1;
}
}
return CRC32;
};
class H1Example {
private:
double time_;
double control_dt_; // [2ms]
double duration_; // [3 s]
PRorAB mode_;
unitree_hg::msg::dds_::LowState_ low_state_;
ChannelPublisherPtr<unitree_hg::msg::dds_::LowCmd_> lowcmd_publisher_;
ChannelSubscriberPtr<unitree_hg::msg::dds_::LowState_> lowstate_subscriber_;
ThreadPtr command_writer_ptr_, control_thread_ptr_;
public:
H1Example(std::string networkInterface)
: time_(0.0), control_dt_(0.002), duration_(3.0), mode_(PR) {
ChannelFactory::Instance()->Init(0, networkInterface);
// create publisher
lowcmd_publisher_.reset(
new ChannelPublisher<unitree_hg::msg::dds_::LowCmd_>(HG_CMD_TOPIC));
lowcmd_publisher_->InitChannel();
// create subscriber
lowstate_subscriber_.reset(
new ChannelSubscriber<unitree_hg::msg::dds_::LowState_>(
HG_STATE_TOPIC));
lowstate_subscriber_->InitChannel(
std::bind(&H1Example::LowStateHandler, this, std::placeholders::_1), 1);
// create threads
control_thread_ptr_ = CreateRecurrentThreadEx(
"control", UT_CPU_ID_NONE, 2000, &H1Example::Control, this);
}
void LowStateHandler(const void *message) {
low_state_ = *(const unitree_hg::msg::dds_::LowState_ *)message;
if (low_state_.crc() !=
Crc32Core((uint32_t *)&low_state_,
(sizeof(unitree_hg::msg::dds_::LowState_) >> 2) - 1)) {
std::cout << "low_state CRC Error" << std::endl;
return;
}
}
void Control() {
unitree_hg::msg::dds_::LowCmd_ dds_low_command;
dds_low_command.mode_pr() = mode_;
dds_low_command.mode_machine() = low_state_.mode_machine();
for (int i = 0; i < H1_NUM_MOTOR; ++i) {
dds_low_command.motor_cmd().at(i).mode() = 1; // 1:Enable, 0:Disable
dds_low_command.motor_cmd().at(i).tau() = 0.0;
dds_low_command.motor_cmd().at(i).q() = 0.0;
dds_low_command.motor_cmd().at(i).dq() = 0.0;
dds_low_command.motor_cmd().at(i).kp() = (i < 13) ? 100.0 : 50.0;
dds_low_command.motor_cmd().at(i).kd() = 1.0;
}
time_ += control_dt_;
if (time_ < duration_) {
// [Stage 1]: set robot to zero posture
for (int i = 0; i < H1_NUM_MOTOR; ++i) {
double ratio = std::clamp(time_ / duration_, 0.0, 1.0);
dds_low_command.motor_cmd().at(i).q() =
(1. - ratio) * low_state_.motor_state().at(i).q();
}
} else {
// [Stage 2]: swing ankle's PR
mode_ = PR; // Enable PR mode
// generate sin/cos trajectory
double max_P = 0.25; // [rad]
double max_R = 0.25; // [rad]
double t = time_ - duration_;
double L_P_des = max_P * std::cos(2.0 * M_PI * t);
double L_R_des = max_R * std::sin(2.0 * M_PI * t);
double R_P_des = max_P * std::cos(2.0 * M_PI * t);
double R_R_des = -max_R * std::sin(2.0 * M_PI * t);
// update ankle joint position targets
float Kp_Pitch = 80;
float Kd_Pitch = 1;
float Kp_Roll = 80;
float Kd_Roll = 1;
dds_low_command.motor_cmd().at(4).q() = L_P_des; // 4: LeftAnklePitch
dds_low_command.motor_cmd().at(4).dq() = 0;
dds_low_command.motor_cmd().at(4).kp() = Kp_Pitch;
dds_low_command.motor_cmd().at(4).kd() = Kd_Pitch;
dds_low_command.motor_cmd().at(4).tau() = 0;
dds_low_command.motor_cmd().at(5).q() = L_R_des; // 5: LeftAnkleRoll
dds_low_command.motor_cmd().at(5).dq() = 0;
dds_low_command.motor_cmd().at(5).kp() = Kp_Roll;
dds_low_command.motor_cmd().at(5).kd() = Kd_Roll;
dds_low_command.motor_cmd().at(5).tau() = 0;
dds_low_command.motor_cmd().at(10).q() = R_P_des; // 10: RightAnklePitch
dds_low_command.motor_cmd().at(10).dq() = 0;
dds_low_command.motor_cmd().at(10).kp() = Kp_Pitch;
dds_low_command.motor_cmd().at(10).kd() = Kd_Pitch;
dds_low_command.motor_cmd().at(10).tau() = 0;
dds_low_command.motor_cmd().at(11).q() = R_R_des; // 11: RightAnkleRoll
dds_low_command.motor_cmd().at(11).dq() = 0;
dds_low_command.motor_cmd().at(11).kp() = Kp_Roll;
dds_low_command.motor_cmd().at(11).kd() = Kd_Roll;
dds_low_command.motor_cmd().at(11).tau() = 0;
float L_P_m = low_state_.motor_state().at(4).q();
float L_R_m = low_state_.motor_state().at(5).q();
float R_P_m = low_state_.motor_state().at(10).q();
float R_R_m = low_state_.motor_state().at(11).q();
// clang-format off
printf("%f,%f,%f,%f,%f,%f,%f,%f\n",
L_P_des, L_P_m, L_R_des, L_R_m,
R_P_des, R_P_m, R_R_des, R_R_m);
// clang-format on
}
dds_low_command.crc() = Crc32Core((uint32_t *)&dds_low_command,
(sizeof(dds_low_command) >> 2) - 1);
lowcmd_publisher_->Write(dds_low_command);
}
};
int main(int argc, char const *argv[]) {
if (argc < 2) {
std::cout << "Usage: h1_2_ankle_track network_interface_name" << std::endl;
exit(0);
}
std::string networkInterface = argv[1];
H1Example custom(networkInterface);
while (true) sleep(10);
return 0;
}

View File

@@ -0,0 +1,14 @@
#include "humanoid.hpp"
int main(int argc, char const *argv[]) {
if (argc < 2) {
std::cout << "Usage: " << argv[0] << " networkInterface" << std::endl;
exit(-1);
}
HumanoidExample example(argv[1]);
while (1) {
sleep(10);
}
return 0;
}

View File

@@ -0,0 +1,263 @@
#pragma once
#include <iostream>
#include <stdint.h>
#include <string>
#include "unitree/robot/channel/channel_publisher.hpp"
#include "unitree/robot/channel/channel_subscriber.hpp"
#include <unitree/common/thread/thread.hpp>
#include <unitree/idl/go2/LowCmd_.hpp>
#include <unitree/idl/go2/LowState_.hpp>
#include "base_state.h"
#include "data_buffer.hpp"
#include "motors.hpp"
#include <unitree/robot/b2/motion_switcher/motion_switcher_client.hpp>
using namespace unitree::robot::b2;
static const std::string kTopicLowCommand = "rt/lowcmd";
static const std::string kTopicLowState = "rt/lowstate";
class HumanoidExample {
public:
HumanoidExample(const std::string &networkInterface = "") {
unitree::robot::ChannelFactory::Instance()->Init(0, networkInterface);
std::cout << "Initialize channel factory." << std::endl;
msc.reset(new MotionSwitcherClient());
msc->SetTimeout(5.0F);
msc->Init();
/*Shut down motion control-related service*/
while(queryMotionStatus())
{
std::cout << "Try to deactivate the motion control-related service." << std::endl;
int32_t ret = msc->ReleaseMode();
if (ret == 0) {
std::cout << "ReleaseMode succeeded." << std::endl;
} else {
std::cout << "ReleaseMode failed. Error code: " << ret << std::endl;
}
sleep(5);
}
lowcmd_publisher_.reset(
new unitree::robot::ChannelPublisher<unitree_go::msg::dds_::LowCmd_>(
kTopicLowCommand));
lowcmd_publisher_->InitChannel();
command_writer_ptr_ = unitree::common::CreateRecurrentThreadEx(
"command_writer", UT_CPU_ID_NONE, 2000,
&HumanoidExample::LowCommandWriter, this);
lowstate_subscriber_.reset(
new unitree::robot::ChannelSubscriber<unitree_go::msg::dds_::LowState_>(
kTopicLowState));
lowstate_subscriber_->InitChannel(
std::bind(&HumanoidExample::LowStateHandler, this,
std::placeholders::_1),
1);
int control_period_us = control_dt_ * 1e6;
control_thread_ptr_ = unitree::common::CreateRecurrentThreadEx(
"control", UT_CPU_ID_NONE, control_period_us, &HumanoidExample::Control,
this);
int report_period_us = report_dt_ * 1e6;
report_rpy_ptr_ = unitree::common::CreateRecurrentThreadEx(
"report_rpy", UT_CPU_ID_NONE, report_period_us,
&HumanoidExample::ReportRPY, this);
}
~HumanoidExample() = default;
void LowCommandWriter() {
unitree_go::msg::dds_::LowCmd_ dds_low_command{};
dds_low_command.head()[0] = 0xFE;
dds_low_command.head()[1] = 0xEF;
dds_low_command.level_flag() = 0xFF;
dds_low_command.gpio() = 0;
const std::shared_ptr<const MotorCommand> mc_tmp_ptr =
motor_command_buffer_.GetData();
if (mc_tmp_ptr) {
for (int i = 0; i < kNumMotors; ++i) {
if (IsWeakMotor(i)) {
dds_low_command.motor_cmd().at(i).mode() = (0x01);
} else {
dds_low_command.motor_cmd().at(i).mode() = (0x0A);
}
dds_low_command.motor_cmd().at(i).tau() = mc_tmp_ptr->tau_ff.at(i);
dds_low_command.motor_cmd().at(i).q() = mc_tmp_ptr->q_ref.at(i);
dds_low_command.motor_cmd().at(i).dq() = mc_tmp_ptr->dq_ref.at(i);
dds_low_command.motor_cmd().at(i).kp() = mc_tmp_ptr->kp.at(i);
dds_low_command.motor_cmd().at(i).kd() = mc_tmp_ptr->kd.at(i);
}
dds_low_command.crc() = Crc32Core((uint32_t *)&dds_low_command,
(sizeof(dds_low_command) >> 2) - 1);
lowcmd_publisher_->Write(dds_low_command);
}
}
void LowStateHandler(const void *message) {
unitree_go::msg::dds_::LowState_ low_state =
*(unitree_go::msg::dds_::LowState_ *)message;
RecordMotorState(low_state);
RecordBaseState(low_state);
}
void Control() {
MotorCommand motor_command_tmp;
const std::shared_ptr<const MotorState> ms_tmp_ptr =
motor_state_buffer_.GetData();
if (ms_tmp_ptr) {
time_ += control_dt_;
time_ = std::clamp(time_, 0.f, init_duration_);
float ratio = time_ / init_duration_;
for (int i = 0; i < kNumMotors; ++i) {
motor_command_tmp.kp.at(i) = IsWeakMotor(i) ? kp_low_ : kp_high_;
motor_command_tmp.kd.at(i) = IsWeakMotor(i) ? kd_low_ : kd_high_;
motor_command_tmp.dq_ref.at(i) = 0.f;
motor_command_tmp.tau_ff.at(i) = 0.f;
float q_des = 0.f;
if (i == JointIndex::kLeftHipPitch || i == JointIndex::kRightHipPitch) {
q_des = hip_pitch_init_pos_;
}
if (i == JointIndex::kLeftKnee || i == JointIndex::kRightKnee) {
q_des = knee_init_pos_;
}
if (i == JointIndex::kLeftAnkle || i == JointIndex::kRightAnkle) {
q_des = ankle_init_pos_;
}
if (i == JointIndex::kLeftShoulderPitch ||
i == JointIndex::kRightShoulderPitch) {
q_des = shoulder_pitch_init_pos_;
}
q_des = (q_des - ms_tmp_ptr->q.at(i)) * ratio + ms_tmp_ptr->q.at(i);
motor_command_tmp.q_ref.at(i) = q_des;
}
motor_command_buffer_.SetData(motor_command_tmp);
}
}
void ReportRPY() {
const std::shared_ptr<const BaseState> bs_tmp_ptr =
base_state_buffer_.GetData();
if (bs_tmp_ptr) {
std::cout << "rpy: [" << bs_tmp_ptr->rpy.at(0) << ", "
<< bs_tmp_ptr->rpy.at(1) << ", " << bs_tmp_ptr->rpy.at(2) << "]"
<< std::endl;
}
}
private:
void RecordMotorState(const unitree_go::msg::dds_::LowState_ &msg) {
MotorState ms_tmp;
for (int i = 0; i < kNumMotors; ++i) {
ms_tmp.q.at(i) = msg.motor_state()[i].q();
ms_tmp.dq.at(i) = msg.motor_state()[i].dq();
}
motor_state_buffer_.SetData(ms_tmp);
}
void RecordBaseState(const unitree_go::msg::dds_::LowState_ &msg) {
BaseState bs_tmp;
bs_tmp.omega = msg.imu_state().gyroscope();
bs_tmp.rpy = msg.imu_state().rpy();
base_state_buffer_.SetData(bs_tmp);
}
inline bool IsWeakMotor(int motor_index) {
return motor_index == JointIndex::kLeftAnkle ||
motor_index == JointIndex::kRightAnkle ||
motor_index == JointIndex::kRightShoulderPitch ||
motor_index == JointIndex::kRightShoulderRoll ||
motor_index == JointIndex::kRightShoulderYaw ||
motor_index == JointIndex::kRightElbow ||
motor_index == JointIndex::kLeftShoulderPitch ||
motor_index == JointIndex::kLeftShoulderRoll ||
motor_index == JointIndex::kLeftShoulderYaw ||
motor_index == JointIndex::kLeftElbow;
}
std::string queryServiceName(std::string form,std::string name)
{
if(form == "0")
{
if(name == "normal" ) return "sport_mode";
if(name == "ai" ) return "ai_sport";
if(name == "advanced" ) return "advanced_sport";
}
else
{
if(name == "ai-w" ) return "wheeled_sport(go2W)";
if(name == "normal-w" ) return "wheeled_sport(b2W)";
}
return "";
}
int queryMotionStatus()
{
std::string robotForm,motionName;
int motionStatus;
int32_t ret = msc->CheckMode(robotForm,motionName);
if (ret == 0) {
std::cout << "CheckMode succeeded." << std::endl;
} else {
std::cout << "CheckMode failed. Error code: " << ret << std::endl;
}
if(motionName.empty())
{
std::cout << "The motion control-related service is deactivated." << std::endl;
motionStatus = 0;
}
else
{
std::string serviceName = queryServiceName(robotForm,motionName);
std::cout << "Service: "<< serviceName<< " is activate" << std::endl;
motionStatus = 1;
}
return motionStatus;
}
unitree::robot::ChannelPublisherPtr<unitree_go::msg::dds_::LowCmd_>
lowcmd_publisher_;
unitree::robot::ChannelSubscriberPtr<unitree_go::msg::dds_::LowState_>
lowstate_subscriber_;
DataBuffer<MotorState> motor_state_buffer_;
DataBuffer<MotorCommand> motor_command_buffer_;
DataBuffer<BaseState> base_state_buffer_;
std::shared_ptr<MotionSwitcherClient> msc;
// control params
float kp_low_ = 60.f;
float kp_high_ = 200.f;
float kd_low_ = 1.5f;
float kd_high_ = 5.f;
float control_dt_ = 0.01f;
float hip_pitch_init_pos_ = -0.5f;
float knee_init_pos_ = 1.f;
float ankle_init_pos_ = -0.5f;
float shoulder_pitch_init_pos_ = 0.4f;
float time_ = 0.f;
float init_duration_ = 10.f;
float report_dt_ = 0.1f;
// multithreading
unitree::common::ThreadPtr command_writer_ptr_;
unitree::common::ThreadPtr control_thread_ptr_;
unitree::common::ThreadPtr report_rpy_ptr_;
};

View File

@@ -0,0 +1,75 @@
#pragma once
#include <array>
#include <stdint.h>
#include <unitree/idl/go2/LowCmd_.hpp>
constexpr int kNumMotors = 20;
struct MotorCommand {
std::array<float, kNumMotors> q_ref = {};
std::array<float, kNumMotors> dq_ref = {};
std::array<float, kNumMotors> kp = {};
std::array<float, kNumMotors> kd = {};
std::array<float, kNumMotors> tau_ff = {};
};
struct MotorState {
std::array<float, kNumMotors> q = {};
std::array<float, kNumMotors> dq = {};
};
enum JointIndex {
// Right leg
kRightHipYaw = 8,
kRightHipRoll = 0,
kRightHipPitch = 1,
kRightKnee = 2,
kRightAnkle = 11,
// Left leg
kLeftHipYaw = 7,
kLeftHipRoll = 3,
kLeftHipPitch = 4,
kLeftKnee = 5,
kLeftAnkle = 10,
kWaistYaw = 6,
kNotUsedJoint = 9,
// Right arm
kRightShoulderPitch = 12,
kRightShoulderRoll = 13,
kRightShoulderYaw = 14,
kRightElbow = 15,
// Left arm
kLeftShoulderPitch = 16,
kLeftShoulderRoll = 17,
kLeftShoulderYaw = 18,
kLeftElbow = 19,
};
uint32_t Crc32Core(uint32_t *ptr, uint32_t len) {
uint32_t xbit = 0;
uint32_t data = 0;
uint32_t CRC32 = 0xFFFFFFFF;
const uint32_t dwPolynomial = 0x04c11db7;
for (uint32_t i = 0; i < len; i++) {
xbit = 1 << 31;
data = ptr[i];
for (uint32_t bits = 0; bits < 32; bits++) {
if (CRC32 & 0x80000000) {
CRC32 <<= 1;
CRC32 ^= dwPolynomial;
} else
CRC32 <<= 1;
if (data & xbit)
CRC32 ^= dwPolynomial;
xbit >>= 1;
}
}
return CRC32;
};