2026-08-04 16:09:17 +04:00

171 lines
6.5 KiB
C++

/**
* @file arm_raise.cpp
* @brief SAFE standalone test: raise the G1 RIGHT arm via the arm_sdk interface.
*
* Based on unitree_sdk2 example g1_arm7_sdk_dds_example.cpp, hardened for safety:
* - aborts if rt/lowstate is not received (never commands from bogus zeros)
* - ENTER-gated phases (engage -> raise -> lower)
* - Ctrl+C ramps the blend weight to 0 (gracefully hands the arm back)
*
* Moves ONLY the right arm; left arm + waist are held at their measured pose.
*
* ./arm_raise [networkInterface] (default: eth0)
*
* !!! THE ARM PHYSICALLY MOVES. Clear the space, stop any other arm controller,
* keep the E-stop in hand. !!!
*/
#include <array>
#include <algorithm>
#include <atomic>
#include <chrono>
#include <csignal>
#include <cstring>
#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";
enum JointIndex {
kLeftHipPitch, kLeftHipRoll, kLeftHipYaw, kLeftKnee, kLeftAnkle, kLeftAnkleRoll,
kRightHipPitch, kRightHipRoll, kRightHipYaw, kRightKnee, kRightAnkle, kRightAnkleRoll,
kWaistYaw, kWaistRoll, kWaistPitch,
kLeftShoulderPitch, kLeftShoulderRoll, kLeftShoulderYaw, kLeftElbow,
kLeftWristRoll, kLeftWristPitch, kLeftWristYaw,
kRightShoulderPitch, kRightShoulderRoll, kRightShoulderYaw, kRightElbow,
kRightWristRoll, kRightWristPitch, kRightWristYaw,
kNotUsedJoint, kNotUsedJoint1, kNotUsedJoint2, kNotUsedJoint3, kNotUsedJoint4, kNotUsedJoint5
};
static std::atomic<bool> g_stop{false};
static void onSigint(int) { g_stop = true; }
int main(int argc, char **argv)
{
const std::string iface = argc > 1 ? argv[1] : "eth0";
std::signal(SIGINT, onSigint);
unitree::robot::ChannelFactory::Instance()->Init(0, iface);
unitree::robot::ChannelPublisherPtr<unitree_hg::msg::dds_::LowCmd_> pub;
pub.reset(new unitree::robot::ChannelPublisher<unitree_hg::msg::dds_::LowCmd_>(kTopicArmSDK));
pub->InitChannel();
static unitree_hg::msg::dds_::LowState_ state;
std::atomic<bool> got{false};
unitree::robot::ChannelSubscriberPtr<unitree_hg::msg::dds_::LowState_> sub;
sub.reset(new unitree::robot::ChannelSubscriber<unitree_hg::msg::dds_::LowState_>(kTopicState));
sub->InitChannel([&](const void *m) {
std::memcpy(&state, m, sizeof(state));
got = true;
}, 1);
// SAFETY 1: require fresh state before touching the arm.
std::cout << "Waiting for " << kTopicState << " on '" << iface << "' ..." << std::endl;
for (int i = 0; i < 300 && !got; i++) std::this_thread::sleep_for(std::chrono::milliseconds(10));
if (!got) {
std::cerr << "ERROR: no " << kTopicState << " received. Is the robot controller up, and is '"
<< iface << "' the interface to the robot? Aborting (no arm command sent)." << std::endl;
return 1;
}
const std::array<JointIndex, 17> arm = {
kLeftShoulderPitch, kLeftShoulderRoll, kLeftShoulderYaw, kLeftElbow,
kLeftWristRoll, kLeftWristPitch, kLeftWristYaw,
kRightShoulderPitch, kRightShoulderRoll, kRightShoulderYaw, kRightElbow,
kRightWristRoll, kRightWristPitch, kRightWristYaw,
kWaistYaw, kWaistRoll, kWaistPitch};
const int RIGHT0 = 7; // right arm occupies arm[7..13]
const float kp = 60.f, kd = 1.5f, dt = 0.02f;
const float max_delta = 0.5f * dt; // 0.5 rad/s velocity limit
const auto sleep_t = std::chrono::milliseconds(20);
std::array<float, 17> start{};
for (int i = 0; i < 17; i++) start[i] = state.motor_state().at(arm[i]).q();
// Right-arm raise target [ShPitch, ShRoll, ShYaw, Elbow, WrRoll, WrPitch, WrYaw].
// Conservative greeting raise (arm out + forearm up). Tune here.
const std::array<float, 7> rraise = {0.0f, -1.0f, 0.0f, 1.4f, 0.0f, 0.0f, 0.0f};
std::array<float, 17> target = start;
for (int j = 0; j < 7; j++) target[RIGHT0 + j] = rraise[j];
std::array<float, 17> des = start;
float weight = 0.f;
unitree_hg::msg::dds_::LowCmd_ msg;
auto publish = [&](float w) {
msg.motor_cmd().at(kNotUsedJoint).q(w);
for (int i = 0; i < 17; i++) {
auto &mc = msg.motor_cmd().at(arm[i]);
mc.q(des[i]); mc.dq(0.f); mc.kp(kp); mc.kd(kd); mc.tau(0.f);
}
pub->Write(msg);
};
auto release = [&]() {
std::cout << "Releasing arm (weight -> 0) ..." << std::endl;
while (weight > 0.f) {
weight = std::max(0.f, weight - 0.015f);
publish(weight);
std::this_thread::sleep_for(sleep_t);
}
publish(0.f);
};
// Move 'des' toward 'goal' (velocity limited). Returns false if interrupted.
auto move_to = [&](const std::array<float, 17> &goal, int hold_steps) -> bool {
for (int step = 0; step < 600; step++) {
if (g_stop) return false;
float maxerr = 0.f;
for (int i = 0; i < 17; i++) {
float d = std::clamp(goal[i] - des[i], -max_delta, max_delta);
des[i] += d;
maxerr = std::max(maxerr, std::abs(goal[i] - des[i]));
}
publish(1.f);
std::this_thread::sleep_for(sleep_t);
if (maxerr < 1e-3f) { // arrived -> hold
for (int h = 0; h < hold_steps && !g_stop; h++) {
publish(1.f);
std::this_thread::sleep_for(sleep_t);
}
return !g_stop;
}
}
return !g_stop;
};
std::cout << "\n*** G1 RIGHT-ARM RAISE TEST ***\n"
<< "The right arm WILL move. Ensure clear space + E-stop ready.\n"
<< "Press ENTER to engage the arm (no motion yet) ... " << std::flush;
std::cin.get();
if (g_stop) { release(); return 0; }
// Phase 1: engage — ramp weight to 1 while commanding the measured pose (no motion).
while (weight < 1.f && !g_stop) {
weight = std::min(1.f, weight + 0.015f);
publish(weight);
std::this_thread::sleep_for(sleep_t);
}
if (g_stop) { release(); return 0; }
std::cout << "Engaged. Press ENTER to RAISE the right arm ... " << std::flush;
std::cin.get();
if (g_stop || !move_to(target, 100)) { release(); return 0; }
std::cout << "Raised. Press ENTER to LOWER the arm ... " << std::flush;
std::cin.get();
move_to(start, 25);
release();
std::cout << "Done." << std::endl;
return 0;
}