#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 kJointId = { 15, 16, 17, 18, 19, 22, 23, 24, 25, 26, 29, 30 }; inline constexpr std::array 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 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 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(); 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>("rt/lowcmd"); lowcmd_->InitChannel(); lowstate_ = std::make_shared>("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 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(message); const uint32_t crc = Crc32Core( reinterpret_cast(const_cast(&state)), (sizeof(LowState_) >> 2) - 1); if (state.crc() != crc) return; std::lock_guard 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 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(&cmd), (sizeof(cmd) >> 2) - 1); lowcmd_->Write(cmd); std::this_thread::sleep_until(next); } } std::array CurrentQ() const { std::lock_guard lock(mutex_); return current_q_; } float CurrentJoint(int joint) const { std::lock_guard lock(mutex_); return current_q_.at(joint); } void SetTargets(const std::array& q) { std::lock_guard lock(mutex_); target_q_ = q; } void SetJointTarget(int joint, float q) { std::lock_guard 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( 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(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 motion_switcher_; ChannelPublisherPtr lowcmd_; ChannelSubscriberPtr lowstate_; std::thread writer_thread_; std::atomic running_{false}; std::atomic state_received_{false}; mutable std::mutex mutex_; std::array current_q_{}; std::array target_q_{}; uint8_t mode_machine_{0}; }; } // namespace r1_vision