SHA256
234 lines
7.0 KiB
C++
234 lines
7.0 KiB
C++
#pragma once
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <unistd.h>
|
|
|
|
#include <unitree/robot/channel/channel_factory.hpp>
|
|
#include <unitree/robot/channel/channel_publisher.hpp>
|
|
#include <unitree/robot/channel/channel_subscriber.hpp>
|
|
#include <unitree/robot/b2/motion_switcher/motion_switcher_client.hpp>
|
|
#include <unitree/idl/hg/LowCmd_.hpp>
|
|
#include <unitree/idl/hg/LowState_.hpp>
|
|
|
|
namespace r1_vision {
|
|
|
|
using unitree_hg::msg::dds_::LowCmd_;
|
|
using unitree_hg::msg::dds_::LowState_;
|
|
using namespace unitree::robot;
|
|
|
|
inline constexpr int kNumMotors = 12;
|
|
|
|
inline constexpr std::array<int, kNumMotors> kJointId = {
|
|
15, 16, 17, 18, 19,
|
|
22, 23, 24, 25, 26,
|
|
29, 30
|
|
};
|
|
|
|
inline constexpr std::array<const char*, kNumMotors> kJointName = {
|
|
"left_shoulder_pitch",
|
|
"left_shoulder_roll",
|
|
"left_shoulder_yaw",
|
|
"left_elbow",
|
|
"left_wrist_roll",
|
|
"right_shoulder_pitch",
|
|
"right_shoulder_roll",
|
|
"right_shoulder_yaw",
|
|
"right_elbow",
|
|
"right_wrist_roll",
|
|
"head_pitch",
|
|
"head_yaw"
|
|
};
|
|
|
|
inline constexpr std::array<float, kNumMotors> kKp = {
|
|
100.f, 100.f, 100.f, 100.f, 50.f,
|
|
100.f, 100.f, 100.f, 100.f, 50.f,
|
|
50.f, 10.f
|
|
};
|
|
|
|
inline constexpr std::array<float, kNumMotors> kKd = {
|
|
2.f, 2.f, 2.f, 2.f, 2.f,
|
|
2.f, 2.f, 2.f, 2.f, 2.f,
|
|
2.f, 0.1f
|
|
};
|
|
|
|
inline uint32_t Crc32Core(uint32_t* ptr, uint32_t len) {
|
|
uint32_t xbit = 0;
|
|
uint32_t data = 0;
|
|
uint32_t crc = 0xFFFFFFFFu;
|
|
constexpr uint32_t polynomial = 0x04c11db7u;
|
|
|
|
for (uint32_t i = 0; i < len; ++i) {
|
|
xbit = 1u << 31;
|
|
data = ptr[i];
|
|
for (uint32_t bits = 0; bits < 32; ++bits) {
|
|
if (crc & 0x80000000u) {
|
|
crc <<= 1;
|
|
crc ^= polynomial;
|
|
} else {
|
|
crc <<= 1;
|
|
}
|
|
if (data & xbit) crc ^= polynomial;
|
|
xbit >>= 1;
|
|
}
|
|
}
|
|
return crc;
|
|
}
|
|
|
|
class RobotController {
|
|
public:
|
|
explicit RobotController(const std::string& network_interface) {
|
|
ChannelFactory::Instance()->Init(0, network_interface);
|
|
|
|
motion_switcher_ = std::make_shared<unitree::robot::b2::MotionSwitcherClient>();
|
|
motion_switcher_->SetTimeout(5.0f);
|
|
motion_switcher_->Init();
|
|
|
|
std::string form, name;
|
|
while (true) {
|
|
motion_switcher_->CheckMode(form, name);
|
|
if (name.empty()) break;
|
|
std::cout << "[SYS] Releasing motion mode: " << name << std::endl;
|
|
if (motion_switcher_->ReleaseMode()) {
|
|
std::cerr << "[WARN] ReleaseMode failed" << std::endl;
|
|
}
|
|
::sleep(2);
|
|
}
|
|
|
|
lowcmd_ = std::make_shared<ChannelPublisher<LowCmd_>>("rt/lowcmd");
|
|
lowcmd_->InitChannel();
|
|
|
|
lowstate_ = std::make_shared<ChannelSubscriber<LowState_>>("rt/lowstate");
|
|
lowstate_->InitChannel(
|
|
std::bind(&RobotController::StateHandler, this, std::placeholders::_1), 1);
|
|
|
|
std::cout << "[SYS] Waiting for LowState..." << std::endl;
|
|
while (!state_received_.load()) ::usleep(10000);
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
target_q_ = current_q_;
|
|
}
|
|
|
|
running_.store(true);
|
|
writer_thread_ = std::thread(&RobotController::CommandLoop, this);
|
|
std::cout << "[SYS] Low-level controller running at 500 Hz." << std::endl;
|
|
}
|
|
|
|
~RobotController() {
|
|
HoldCurrent();
|
|
running_.store(false);
|
|
if (writer_thread_.joinable()) writer_thread_.join();
|
|
}
|
|
|
|
void StateHandler(const void* message) {
|
|
const auto& state = *static_cast<const LowState_*>(message);
|
|
const uint32_t crc = Crc32Core(
|
|
reinterpret_cast<uint32_t*>(const_cast<LowState_*>(&state)),
|
|
(sizeof(LowState_) >> 2) - 1);
|
|
if (state.crc() != crc) return;
|
|
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
for (int i = 0; i < kNumMotors; ++i) {
|
|
current_q_[i] = state.motor_state().at(kJointId[i]).q();
|
|
}
|
|
mode_machine_ = state.mode_machine();
|
|
state_received_.store(true);
|
|
}
|
|
|
|
void CommandLoop() {
|
|
auto next = std::chrono::steady_clock::now();
|
|
while (running_.load()) {
|
|
next += std::chrono::milliseconds(2);
|
|
|
|
LowCmd_ cmd;
|
|
{
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
cmd.mode_pr() = 0;
|
|
cmd.mode_machine() = mode_machine_;
|
|
for (int i = 0; i < kNumMotors; ++i) {
|
|
auto& motor = cmd.motor_cmd().at(kJointId[i]);
|
|
motor.mode() = 1;
|
|
motor.q() = target_q_[i];
|
|
motor.dq() = 0.0f;
|
|
motor.kp() = kKp[i];
|
|
motor.kd() = kKd[i];
|
|
motor.tau() = 0.0f;
|
|
}
|
|
}
|
|
|
|
cmd.crc() = Crc32Core(
|
|
reinterpret_cast<uint32_t*>(&cmd),
|
|
(sizeof(cmd) >> 2) - 1);
|
|
lowcmd_->Write(cmd);
|
|
std::this_thread::sleep_until(next);
|
|
}
|
|
}
|
|
|
|
std::array<float, kNumMotors> CurrentQ() const {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
return current_q_;
|
|
}
|
|
|
|
float CurrentJoint(int joint) const {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
return current_q_.at(joint);
|
|
}
|
|
|
|
void SetTargets(const std::array<float, kNumMotors>& q) {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
target_q_ = q;
|
|
}
|
|
|
|
void SetJointTarget(int joint, float q) {
|
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
target_q_.at(joint) = q;
|
|
}
|
|
|
|
void MoveJointRelative(int joint, float delta_rad, double seconds = 0.6) {
|
|
const float start = CurrentJoint(joint);
|
|
const float target = start + delta_rad;
|
|
const auto t0 = std::chrono::steady_clock::now();
|
|
|
|
while (true) {
|
|
const double elapsed = std::chrono::duration<double>(
|
|
std::chrono::steady_clock::now() - t0).count();
|
|
const double a = std::clamp(elapsed / seconds, 0.0, 1.0);
|
|
const double s = a * a * (3.0 - 2.0 * a);
|
|
SetJointTarget(joint, static_cast<float>(start + delta_rad * s));
|
|
if (a >= 1.0) break;
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
|
}
|
|
|
|
SetJointTarget(joint, target);
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(300));
|
|
}
|
|
|
|
void HoldCurrent() {
|
|
SetTargets(CurrentQ());
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
|
}
|
|
|
|
private:
|
|
std::shared_ptr<unitree::robot::b2::MotionSwitcherClient> motion_switcher_;
|
|
ChannelPublisherPtr<LowCmd_> lowcmd_;
|
|
ChannelSubscriberPtr<LowState_> lowstate_;
|
|
std::thread writer_thread_;
|
|
std::atomic<bool> running_{false};
|
|
std::atomic<bool> state_received_{false};
|
|
mutable std::mutex mutex_;
|
|
std::array<float, kNumMotors> current_q_{};
|
|
std::array<float, kNumMotors> target_q_{};
|
|
uint8_t mode_machine_{0};
|
|
};
|
|
|
|
} // namespace r1_vision
|