171 lines
6.5 KiB
C++
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;
|
|
}
|