Add initial project structure with CMake configuration, source files, and README documentation for Unitree GO2 Custom Controller
This commit is contained in:
575
src/controller.cpp
Normal file
575
src/controller.cpp
Normal file
@@ -0,0 +1,575 @@
|
||||
#include "controller.hpp"
|
||||
#include "config.hpp"
|
||||
#include "logger.hpp"
|
||||
#include <unitree/robot/channel/channel_factory.hpp>
|
||||
#include <unitree/common/thread/thread.hpp>
|
||||
|
||||
namespace custom {
|
||||
|
||||
Controller::Controller(const std::string& networkInterface)
|
||||
: networkInterface_(networkInterface), running_(false), initialized_(false),
|
||||
emergencyStopActive_(false) {
|
||||
|
||||
auto& config = Config::getInstance().getConfig();
|
||||
controlPeriod_ = std::chrono::milliseconds(static_cast<int>(1000.0 / config.control_frequency));
|
||||
statePeriod_ = std::chrono::milliseconds(static_cast<int>(1000.0 / config.state_publish_frequency));
|
||||
}
|
||||
|
||||
Controller::~Controller() {
|
||||
shutdown();
|
||||
}
|
||||
|
||||
bool Controller::initialize() {
|
||||
try {
|
||||
LOG_INFO("Initializing robot controller with interface: " + networkInterface_);
|
||||
|
||||
// Initialize channel factory
|
||||
unitree::robot::ChannelFactory::Instance()->Init(0, networkInterface_);
|
||||
|
||||
// Initialize sport client
|
||||
sportClient_ = std::make_unique<unitree::robot::go2::SportClient>();
|
||||
sportClient_->SetTimeout(10.0f);
|
||||
sportClient_->Init();
|
||||
|
||||
// Initialize obstacle avoidance client
|
||||
obstacleClient_ = std::make_unique<unitree::robot::go2::ObstaclesAvoidClient>();
|
||||
obstacleClient_->Init();
|
||||
|
||||
// Initialize video client
|
||||
videoClient_ = std::make_unique<unitree::robot::go2::VideoClient>();
|
||||
videoClient_->Init();
|
||||
|
||||
// Initialize VUI client
|
||||
vuiClient_ = std::make_unique<unitree::robot::go2::VuiClient>();
|
||||
vuiClient_->Init();
|
||||
|
||||
// Initialize state subscribers
|
||||
sportStateSubscriber_.reset(
|
||||
new unitree::robot::ChannelSubscriber<unitree_go::msg::dds_::SportModeState_>("rt/sportmodestate")
|
||||
);
|
||||
sportStateSubscriber_->InitChannel(
|
||||
std::bind(&Controller::updateSportModeState, this, std::placeholders::_1), 1
|
||||
);
|
||||
|
||||
lowStateSubscriber_.reset(
|
||||
new unitree::robot::ChannelSubscriber<unitree_go::msg::dds_::LowState_>("rt/lowstate")
|
||||
);
|
||||
lowStateSubscriber_->InitChannel(
|
||||
std::bind(&Controller::updateLowLevelState, this, std::placeholders::_1), 1
|
||||
);
|
||||
|
||||
// Initialize robot state
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(stateMutex_);
|
||||
currentState_ = RobotState{};
|
||||
currentState_.timestamp = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
initialized_ = true;
|
||||
LOG_INFO("Robot controller initialized successfully");
|
||||
return true;
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("Failed to initialize robot controller: " + std::string(e.what()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::shutdown() {
|
||||
if (running_) {
|
||||
stop();
|
||||
}
|
||||
|
||||
LOG_INFO("Shutting down robot controller");
|
||||
|
||||
// Reset all clients
|
||||
sportClient_.reset();
|
||||
obstacleClient_.reset();
|
||||
videoClient_.reset();
|
||||
vuiClient_.reset();
|
||||
|
||||
// Reset subscribers
|
||||
sportStateSubscriber_.reset();
|
||||
lowStateSubscriber_.reset();
|
||||
|
||||
initialized_ = false;
|
||||
}
|
||||
|
||||
bool Controller::start() {
|
||||
if (!initialized_) {
|
||||
LOG_ERROR("Cannot start: robot controller not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (running_) {
|
||||
LOG_WARN("Robot controller already running");
|
||||
return true;
|
||||
}
|
||||
|
||||
LOG_INFO("Starting robot controller");
|
||||
|
||||
running_ = true;
|
||||
emergencyStopActive_ = false;
|
||||
lastCommandTime_ = std::chrono::steady_clock::now();
|
||||
|
||||
// Start control threads
|
||||
controlThread_ = std::thread(&Controller::controlLoop, this);
|
||||
stateThread_ = std::thread(&Controller::stateUpdateLoop, this);
|
||||
|
||||
LOG_INFO("Robot controller started successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Controller::stop() {
|
||||
if (!running_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
LOG_INFO("Stopping robot controller");
|
||||
|
||||
// Stop all motion first
|
||||
emergencyStop();
|
||||
|
||||
running_ = false;
|
||||
commandCondition_.notify_all();
|
||||
|
||||
// Wait for threads to finish
|
||||
if (controlThread_.joinable()) {
|
||||
controlThread_.join();
|
||||
}
|
||||
if (stateThread_.joinable()) {
|
||||
stateThread_.join();
|
||||
}
|
||||
|
||||
LOG_INFO("Robot controller stopped");
|
||||
return true;
|
||||
}
|
||||
|
||||
RobotState Controller::getCurrentState() const {
|
||||
std::lock_guard<std::mutex> lock(stateMutex_);
|
||||
return currentState_;
|
||||
}
|
||||
|
||||
void Controller::setStateCallback(StateCallback callback) {
|
||||
stateCallback_ = callback;
|
||||
}
|
||||
|
||||
void Controller::setErrorCallback(ErrorCallback callback) {
|
||||
errorCallback_ = callback;
|
||||
}
|
||||
|
||||
bool Controller::executeCommand(const MotionCommand& command) {
|
||||
if (!running_) {
|
||||
LOG_ERROR("Cannot execute command: robot controller not running");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (emergencyStopActive_) {
|
||||
LOG_WARN("Cannot execute command: emergency stop active");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!validateCommand(command)) {
|
||||
LOG_ERROR("Invalid motion command");
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(commandMutex_);
|
||||
commandQueue_.push(command);
|
||||
lastCommandTime_ = std::chrono::steady_clock::now();
|
||||
}
|
||||
commandCondition_.notify_one();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Controller::emergencyStop() {
|
||||
LOG_WARN("Emergency stop activated");
|
||||
|
||||
emergencyStopActive_ = true;
|
||||
|
||||
try {
|
||||
if (sportClient_) {
|
||||
sportClient_->StopMove();
|
||||
sportClient_->Damp();
|
||||
}
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("Emergency stop failed: " + std::string(e.what()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Controller::recoveryStand() {
|
||||
LOG_INFO("Executing recovery stand");
|
||||
|
||||
try {
|
||||
if (sportClient_) {
|
||||
int32_t result = sportClient_->RecoveryStand();
|
||||
if (result == 0) {
|
||||
emergencyStopActive_ = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("Recovery stand failed: " + std::string(e.what()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Basic motions
|
||||
bool Controller::standUp() {
|
||||
LOG_INFO("Standing up");
|
||||
try {
|
||||
return sportClient_ && sportClient_->StandUp() == 0;
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("Stand up failed: " + std::string(e.what()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Controller::standDown() {
|
||||
LOG_INFO("Standing down");
|
||||
try {
|
||||
return sportClient_ && sportClient_->StandDown() == 0;
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("Stand down failed: " + std::string(e.what()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Controller::sit() {
|
||||
LOG_INFO("Sitting");
|
||||
try {
|
||||
return sportClient_ && sportClient_->Sit() == 0;
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("Sit failed: " + std::string(e.what()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Controller::lie() {
|
||||
LOG_INFO("Lying down");
|
||||
try {
|
||||
return sportClient_ && sportClient_->StandDown() == 0;
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("Lie down failed: " + std::string(e.what()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Controller::damp() {
|
||||
LOG_INFO("Damping");
|
||||
try {
|
||||
return sportClient_ && sportClient_->Damp() == 0;
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("Damp failed: " + std::string(e.what()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Movement
|
||||
bool Controller::move(double vx, double vy, double vyaw) {
|
||||
try {
|
||||
auto& config = Config::getInstance().getConfig();
|
||||
|
||||
// Apply safety limits
|
||||
vx = std::max(-config.max_linear_velocity, std::min(config.max_linear_velocity, vx));
|
||||
vy = std::max(-config.max_linear_velocity, std::min(config.max_linear_velocity, vy));
|
||||
vyaw = std::max(-config.max_angular_velocity, std::min(config.max_angular_velocity, vyaw));
|
||||
|
||||
return sportClient_ && sportClient_->Move(vx, vy, vyaw) == 0;
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("Move failed: " + std::string(e.what()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Controller::moveToPosition(double x, double y, double yaw) {
|
||||
try {
|
||||
return obstacleClient_ && obstacleClient_->MoveToAbsolutePosition(x, y, yaw) == 0;
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("Move to position failed: " + std::string(e.what()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Controller::stop() {
|
||||
try {
|
||||
return sportClient_ && sportClient_->StopMove() == 0;
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("Stop failed: " + std::string(e.what()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Body control
|
||||
bool Controller::setBodyPose(double roll, double pitch, double yaw, double height) {
|
||||
try {
|
||||
if (sportClient_) {
|
||||
sportClient_->Euler(roll, pitch, yaw);
|
||||
sportClient_->BodyHeight(height);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("Set body pose failed: " + std::string(e.what()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Controller::balanceStand() {
|
||||
try {
|
||||
return sportClient_ && sportClient_->BalanceStand() == 0;
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("Balance stand failed: " + std::string(e.what()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Special actions
|
||||
bool Controller::dance1() {
|
||||
try {
|
||||
return sportClient_ && sportClient_->Dance1() == 0;
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("Dance1 failed: " + std::string(e.what()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Controller::dance2() {
|
||||
try {
|
||||
return sportClient_ && sportClient_->Dance2() == 0;
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("Dance2 failed: " + std::string(e.what()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Controller::hello() {
|
||||
try {
|
||||
return sportClient_ && sportClient_->Hello() == 0;
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("Hello failed: " + std::string(e.what()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Control loops
|
||||
void Controller::controlLoop() {
|
||||
LOG_INFO("Control loop started");
|
||||
|
||||
while (running_) {
|
||||
auto startTime = std::chrono::steady_clock::now();
|
||||
|
||||
try {
|
||||
processCommands();
|
||||
checkSafetyLimits();
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("Control loop error: " + std::string(e.what()));
|
||||
handleError(e.what());
|
||||
}
|
||||
|
||||
auto endTime = std::chrono::steady_clock::now();
|
||||
auto elapsed = endTime - startTime;
|
||||
|
||||
if (elapsed < controlPeriod_) {
|
||||
std::this_thread::sleep_for(controlPeriod_ - elapsed);
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INFO("Control loop stopped");
|
||||
}
|
||||
|
||||
void Controller::stateUpdateLoop() {
|
||||
LOG_INFO("State update loop started");
|
||||
|
||||
while (running_) {
|
||||
auto startTime = std::chrono::steady_clock::now();
|
||||
|
||||
try {
|
||||
// State is updated via callbacks, just trigger callback here
|
||||
if (stateCallback_) {
|
||||
RobotState state = getCurrentState();
|
||||
stateCallback_(state);
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
LOG_ERROR("State update error: " + std::string(e.what()));
|
||||
}
|
||||
|
||||
auto endTime = std::chrono::steady_clock::now();
|
||||
auto elapsed = endTime - startTime;
|
||||
|
||||
if (elapsed < statePeriod_) {
|
||||
std::this_thread::sleep_for(statePeriod_ - elapsed);
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INFO("State update loop stopped");
|
||||
}
|
||||
|
||||
void Controller::processCommands() {
|
||||
std::unique_lock<std::mutex> lock(commandMutex_);
|
||||
|
||||
while (!commandQueue_.empty()) {
|
||||
auto command = commandQueue_.front();
|
||||
commandQueue_.pop();
|
||||
lock.unlock();
|
||||
|
||||
executeMotionCommand(command);
|
||||
|
||||
lock.lock();
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::executeMotionCommand(const MotionCommand& command) {
|
||||
switch (command.type) {
|
||||
case MotionCommand::VELOCITY:
|
||||
move(command.linear_velocity[0], command.linear_velocity[1], command.angular_velocity[2]);
|
||||
break;
|
||||
|
||||
case MotionCommand::POSITION:
|
||||
moveToPosition(command.target_position[0], command.target_position[1], command.target_orientation[2]);
|
||||
break;
|
||||
|
||||
case MotionCommand::BODY_POSE:
|
||||
setBodyPose(command.body_roll, command.body_pitch, command.body_yaw, command.body_height);
|
||||
break;
|
||||
|
||||
case MotionCommand::SPECIAL_ACTION:
|
||||
performAction(command.action_name, command.action_params);
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_WARN("Unknown motion command type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool Controller::validateCommand(const MotionCommand& command) {
|
||||
auto& config = Config::getInstance().getConfig();
|
||||
|
||||
// Check velocity limits
|
||||
if (command.type == MotionCommand::VELOCITY) {
|
||||
double vx = std::abs(command.linear_velocity[0]);
|
||||
double vy = std::abs(command.linear_velocity[1]);
|
||||
double vyaw = std::abs(command.angular_velocity[2]);
|
||||
|
||||
if (vx > config.max_linear_velocity || vy > config.max_linear_velocity ||
|
||||
vyaw > config.max_angular_velocity) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Controller::updateSportModeState(const void* message) {
|
||||
auto* state = static_cast<const unitree_go::msg::dds_::SportModeState_*>(message);
|
||||
|
||||
std::lock_guard<std::mutex> lock(stateMutex_);
|
||||
|
||||
// Update position
|
||||
currentState_.position[0] = state->position()[0];
|
||||
currentState_.position[1] = state->position()[1];
|
||||
currentState_.position[2] = state->position()[2];
|
||||
|
||||
// Update orientation
|
||||
currentState_.orientation[0] = state->imu_state().rpy()[0];
|
||||
currentState_.orientation[1] = state->imu_state().rpy()[1];
|
||||
currentState_.orientation[2] = state->imu_state().rpy()[2];
|
||||
|
||||
// Update velocity
|
||||
currentState_.velocity[0] = state->velocity()[0];
|
||||
currentState_.velocity[1] = state->velocity()[1];
|
||||
currentState_.velocity[2] = state->velocity()[2];
|
||||
|
||||
// Update IMU
|
||||
for (int i = 0; i < 3; i++) {
|
||||
currentState_.imu_acc[i] = state->imu_state().accelerometer()[i];
|
||||
currentState_.imu_gyro[i] = state->imu_state().gyroscope()[i];
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
currentState_.imu_quat[i] = state->imu_state().quaternion()[i];
|
||||
}
|
||||
|
||||
currentState_.timestamp = std::chrono::steady_clock::now();
|
||||
currentState_.is_connected = true;
|
||||
}
|
||||
|
||||
void Controller::updateLowLevelState(const void* message) {
|
||||
auto* state = static_cast<const unitree_go::msg::dds_::LowState_*>(message);
|
||||
|
||||
std::lock_guard<std::mutex> lock(stateMutex_);
|
||||
|
||||
// Update motor states
|
||||
for (int i = 0; i < 20 && i < state->motor_state().size(); i++) {
|
||||
currentState_.motor_positions[i] = state->motor_state()[i].q();
|
||||
currentState_.motor_velocities[i] = state->motor_state()[i].dq();
|
||||
currentState_.motor_torques[i] = state->motor_state()[i].tau_est();
|
||||
currentState_.motor_temperatures[i] = state->motor_state()[i].temperature();
|
||||
}
|
||||
|
||||
// Update battery info
|
||||
if (!state->bms_state().empty()) {
|
||||
currentState_.battery_voltage = state->bms_state()[0].voltage();
|
||||
currentState_.battery_current = state->bms_state()[0].current();
|
||||
currentState_.battery_percentage = state->bms_state()[0].soc();
|
||||
}
|
||||
|
||||
currentState_.timestamp = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
void Controller::checkSafetyLimits() {
|
||||
auto& config = Config::getInstance().getConfig();
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
// Check command timeout
|
||||
auto timeSinceLastCommand = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
now - lastCommandTime_).count();
|
||||
|
||||
if (timeSinceLastCommand > config.emergency_stop_timeout && !emergencyStopActive_) {
|
||||
LOG_WARN("Command timeout exceeded, activating emergency stop");
|
||||
emergencyStop();
|
||||
}
|
||||
|
||||
// Check battery level
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(stateMutex_);
|
||||
if (currentState_.battery_percentage < 10) {
|
||||
currentState_.low_battery = true;
|
||||
if (!emergencyStopActive_) {
|
||||
LOG_WARN("Low battery detected, activating emergency stop");
|
||||
emergencyStop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::handleError(const std::string& error) {
|
||||
if (errorCallback_) {
|
||||
errorCallback_(error);
|
||||
}
|
||||
}
|
||||
|
||||
bool Controller::performAction(const std::string& actionName, const nlohmann::json& params) {
|
||||
LOG_INFO("Performing action: " + actionName);
|
||||
|
||||
if (actionName == "dance1") return dance1();
|
||||
if (actionName == "dance2") return dance2();
|
||||
if (actionName == "hello") return hello();
|
||||
if (actionName == "sit") return sit();
|
||||
if (actionName == "stand_up") return standUp();
|
||||
if (actionName == "stand_down") return standDown();
|
||||
if (actionName == "damp") return damp();
|
||||
if (actionName == "balance_stand") return balanceStand();
|
||||
if (actionName == "recovery_stand") return recoveryStand();
|
||||
|
||||
LOG_WARN("Unknown action: " + actionName);
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace custom
|
||||
Reference in New Issue
Block a user