cpp对齐官方
This commit is contained in:
50
src/loop.cpp
Normal file
50
src/loop.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#include "unitree_legged_sdk/loop.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#if defined(__linux__)
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
#endif
|
||||
|
||||
namespace UNITREE_LEGGED_SDK {
|
||||
|
||||
Loop::~Loop() { shutdown(); }
|
||||
|
||||
void Loop::start() {
|
||||
if (_running_atomic.exchange(true)) return;
|
||||
_isrunning = true;
|
||||
_thread = std::thread(&Loop::entryFunc, this);
|
||||
}
|
||||
|
||||
void Loop::shutdown() {
|
||||
_running_atomic.store(false);
|
||||
_isrunning = false;
|
||||
if (_thread.joinable()) _thread.join();
|
||||
}
|
||||
|
||||
void Loop::entryFunc() {
|
||||
#if defined(__linux__)
|
||||
if (_bindCPU >= 0) {
|
||||
cpu_set_t cpu_set;
|
||||
CPU_ZERO(&cpu_set);
|
||||
CPU_SET(_bindCPU, &cpu_set);
|
||||
_bind_cpu_flag = pthread_setaffinity_np(
|
||||
pthread_self(), sizeof(cpu_set), &cpu_set) == 0;
|
||||
}
|
||||
#else
|
||||
(void)_bindCPU;
|
||||
_bind_cpu_flag = false;
|
||||
#endif
|
||||
using Clock = std::chrono::steady_clock;
|
||||
const auto period = std::chrono::duration_cast<Clock::duration>(
|
||||
std::chrono::duration<double>(_period));
|
||||
auto next = Clock::now();
|
||||
while (_running_atomic.load()) {
|
||||
next += period;
|
||||
functionCB();
|
||||
std::this_thread::sleep_until(next);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace UNITREE_LEGGED_SDK
|
||||
391
src/pro_codec.cpp
Normal file
391
src/pro_codec.cpp
Normal file
@@ -0,0 +1,391 @@
|
||||
#include "pro_codec.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <limits.h>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <mach-o/dyld.h>
|
||||
#elif defined(__linux__)
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifndef GO1_PRO_SOURCE_STATE_FILE
|
||||
#define GO1_PRO_SOURCE_STATE_FILE ""
|
||||
#endif
|
||||
|
||||
#ifndef GO1_PRO_INSTALL_STATE_FILE
|
||||
#define GO1_PRO_INSTALL_STATE_FILE ""
|
||||
#endif
|
||||
|
||||
namespace go1_pro_internal {
|
||||
namespace {
|
||||
|
||||
constexpr std::size_t kStateSize = 4168;
|
||||
constexpr std::size_t kMotorOffset = 22;
|
||||
constexpr std::size_t kMotorWireSize = 27;
|
||||
constexpr std::size_t kCrcOffset = 612;
|
||||
constexpr uint32_t kCrcPoly = 0x04c11db7u;
|
||||
|
||||
uint16_t GetU16Le(const uint8_t* p) {
|
||||
return static_cast<uint16_t>(p[0]) |
|
||||
static_cast<uint16_t>(static_cast<uint16_t>(p[1]) << 8);
|
||||
}
|
||||
|
||||
int16_t GetI16Le(const uint8_t* p) {
|
||||
return static_cast<int16_t>(GetU16Le(p));
|
||||
}
|
||||
|
||||
uint32_t GetU32Le(const uint8_t* p) {
|
||||
return static_cast<uint32_t>(p[0]) |
|
||||
(static_cast<uint32_t>(p[1]) << 8) |
|
||||
(static_cast<uint32_t>(p[2]) << 16) |
|
||||
(static_cast<uint32_t>(p[3]) << 24);
|
||||
}
|
||||
|
||||
int32_t GetI32Le(const uint8_t* p) {
|
||||
return static_cast<int32_t>(GetU32Le(p));
|
||||
}
|
||||
|
||||
float GetF32Le(const uint8_t* p) {
|
||||
const uint32_t raw = GetU32Le(p);
|
||||
float result;
|
||||
std::memcpy(&result, &raw, sizeof(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
void PutU16Le(uint8_t* p, uint16_t value) {
|
||||
p[0] = static_cast<uint8_t>(value);
|
||||
p[1] = static_cast<uint8_t>(value >> 8);
|
||||
}
|
||||
|
||||
void PutU32Le(uint8_t* p, uint32_t value) {
|
||||
p[0] = static_cast<uint8_t>(value);
|
||||
p[1] = static_cast<uint8_t>(value >> 8);
|
||||
p[2] = static_cast<uint8_t>(value >> 16);
|
||||
p[3] = static_cast<uint8_t>(value >> 24);
|
||||
}
|
||||
|
||||
void PutF32Le(uint8_t* p, float value) {
|
||||
uint32_t raw;
|
||||
static_assert(sizeof(raw) == sizeof(value), "float must be 32-bit");
|
||||
std::memcpy(&raw, &value, sizeof(raw));
|
||||
PutU32Le(p, raw);
|
||||
}
|
||||
|
||||
double RoundHalfEven(double value, double scale) {
|
||||
const double scaled = value * scale;
|
||||
const double floor_value = std::floor(scaled);
|
||||
const double fraction = scaled - floor_value;
|
||||
constexpr double kEpsilon = 1e-12;
|
||||
double rounded = floor_value;
|
||||
if (fraction > 0.5 + kEpsilon) {
|
||||
rounded += 1.0;
|
||||
} else if (fraction >= 0.5 - kEpsilon && std::fmod(floor_value, 2.0) != 0.0) {
|
||||
rounded += 1.0;
|
||||
}
|
||||
return rounded / scale;
|
||||
}
|
||||
|
||||
void EncodeTau(uint8_t* p, double tau) {
|
||||
tau = RoundHalfEven(tau, 100.0);
|
||||
int integer = static_cast<int>(tau);
|
||||
const double fraction = tau - static_cast<double>(integer);
|
||||
bool negative = tau < 0.0;
|
||||
if (negative) integer = 255 + integer;
|
||||
if (fraction == 0.0) negative = false;
|
||||
int encoded_fraction = static_cast<int>(fraction * 256.0);
|
||||
if (negative) encoded_fraction = 256 + encoded_fraction;
|
||||
p[0] = static_cast<uint8_t>(encoded_fraction);
|
||||
p[1] = static_cast<uint8_t>(integer);
|
||||
}
|
||||
|
||||
void EncodeKp(uint8_t* p, double kp) {
|
||||
const double base_double = std::floor(kp);
|
||||
const int base = static_cast<int>(base_double);
|
||||
const int fraction = static_cast<int>(RoundHalfEven(kp - base_double, 10.0) * 10.0);
|
||||
const int value = fraction < 5 ? base * 32 + fraction * 3
|
||||
: base * 32 + (fraction - 1) * 3 + 4;
|
||||
PutU16Le(p, static_cast<uint16_t>(value));
|
||||
}
|
||||
|
||||
void EncodeKd(uint8_t* p, double kd) {
|
||||
static constexpr uint8_t kFraction[10] = {0x0, 0x1, 0x3, 0x4, 0x6,
|
||||
0x8, 0x9, 0xb, 0xc, 0xe};
|
||||
const int integer = static_cast<int>(kd);
|
||||
const int fraction = static_cast<int>(RoundHalfEven(kd - integer, 10.0) * 10.0);
|
||||
const uint8_t nibble = fraction >= 0 && fraction < 10 ? kFraction[fraction] : 0;
|
||||
PutU16Le(p, static_cast<uint16_t>((integer << 4) | nibble));
|
||||
}
|
||||
|
||||
void EncodeMotor(uint8_t* p, const UNITREE_LEGGED_SDK::MotorCmd& motor) {
|
||||
p[0] = motor.mode;
|
||||
PutF32Le(p + 1, motor.q);
|
||||
PutF32Le(p + 5, motor.dq);
|
||||
EncodeTau(p + 9, motor.tau);
|
||||
EncodeKp(p + 11, motor.Kp);
|
||||
EncodeKd(p + 13, motor.Kd);
|
||||
for (std::size_t i = 0; i < motor.reserve.size(); ++i) {
|
||||
PutU32Le(p + 15 + i * 4, motor.reserve[i]);
|
||||
}
|
||||
}
|
||||
|
||||
bool ReadableFile(const std::string& path) {
|
||||
if (path.empty()) return false;
|
||||
std::ifstream stream(path, std::ios::binary);
|
||||
return stream.good();
|
||||
}
|
||||
|
||||
std::string ExecutableDirectory() {
|
||||
#if defined(__APPLE__)
|
||||
uint32_t size = 0;
|
||||
_NSGetExecutablePath(nullptr, &size);
|
||||
std::string path(size, '\0');
|
||||
if (_NSGetExecutablePath(&path[0], &size) != 0) return {};
|
||||
path.resize(std::strlen(path.c_str()));
|
||||
#elif defined(__linux__)
|
||||
char buffer[PATH_MAX] = {};
|
||||
const ssize_t size = readlink("/proc/self/exe", buffer, sizeof(buffer) - 1);
|
||||
if (size <= 0) return {};
|
||||
buffer[size] = '\0';
|
||||
std::string path(buffer);
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
const std::size_t slash = path.find_last_of("/");
|
||||
return slash == std::string::npos ? std::string{} : path.substr(0, slash);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void InitLowCmd(UNITREE_LEGGED_SDK::LowCmd& cmd) {
|
||||
std::memset(&cmd, 0, sizeof(cmd));
|
||||
cmd.head = {{0xfe, 0xef}};
|
||||
cmd.levelFlag = UNITREE_LEGGED_SDK::LOWLEVEL;
|
||||
cmd.bandWidth = 0x3ac0;
|
||||
for (auto& motor : cmd.motorCmd) {
|
||||
motor.mode = 0x0a;
|
||||
motor.q = static_cast<float>(UNITREE_LEGGED_SDK::PosStopF);
|
||||
motor.dq = static_cast<float>(UNITREE_LEGGED_SDK::VelStopF);
|
||||
}
|
||||
}
|
||||
|
||||
void InitHighCmd(UNITREE_LEGGED_SDK::HighCmd& cmd) {
|
||||
std::memset(&cmd, 0, sizeof(cmd));
|
||||
cmd.head = {{0xfe, 0xef}};
|
||||
cmd.levelFlag = UNITREE_LEGGED_SDK::HIGHLEVEL;
|
||||
}
|
||||
|
||||
ProCodec::ProCodec(const std::string& state_path) {
|
||||
std::ifstream stream(state_path, std::ios::binary);
|
||||
if (!stream) {
|
||||
throw std::runtime_error("cannot open Go1 PRO Blowfish state file: " + state_path);
|
||||
}
|
||||
std::array<uint8_t, kStateSize> data{};
|
||||
stream.read(reinterpret_cast<char*>(data.data()), data.size());
|
||||
if (stream.gcount() != static_cast<std::streamsize>(data.size())) {
|
||||
throw std::runtime_error("Go1 PRO Blowfish state file must contain at least 4168 bytes: " + state_path);
|
||||
}
|
||||
for (std::size_t i = 0; i < p_.size(); ++i) p_[i] = GetU32Le(data.data() + i * 4);
|
||||
const uint8_t* boxes = data.data() + p_.size() * 4;
|
||||
for (std::size_t box = 0; box < s_.size(); ++box) {
|
||||
for (std::size_t i = 0; i < s_[box].size(); ++i) {
|
||||
s_[box][i] = GetU32Le(boxes + (box * 256 + i) * 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string ProCodec::FindStateFile() {
|
||||
const char* environment = std::getenv("GO1_PRO_BLOWFISH_STATE");
|
||||
const std::string executable_dir = ExecutableDirectory();
|
||||
const std::string candidates[] = {
|
||||
environment ? environment : "",
|
||||
GO1_PRO_SOURCE_STATE_FILE,
|
||||
GO1_PRO_INSTALL_STATE_FILE,
|
||||
executable_dir.empty() ? "" : executable_dir + "/../share/go1_pro_sdk/blowfish_state.bin",
|
||||
executable_dir.empty() ? "" : executable_dir + "/share/go1_pro_sdk/blowfish_state.bin",
|
||||
"go1_pro_sdk/_data/blowfish_state.bin",
|
||||
"../share/go1_pro_sdk/blowfish_state.bin",
|
||||
"/usr/local/share/go1_pro_sdk/blowfish_state.bin",
|
||||
};
|
||||
for (const auto& candidate : candidates) {
|
||||
if (ReadableFile(candidate)) return candidate;
|
||||
}
|
||||
throw std::runtime_error(
|
||||
"Go1 PRO Blowfish state not found; set GO1_PRO_BLOWFISH_STATE to blowfish_state.bin");
|
||||
}
|
||||
|
||||
uint32_t ProCodec::Crc32(const uint8_t* data, std::size_t size) {
|
||||
static const std::array<uint32_t, 256> table = [] {
|
||||
std::array<uint32_t, 256> result{};
|
||||
for (uint32_t byte = 0; byte < 256; ++byte) {
|
||||
uint32_t crc = byte << 24;
|
||||
for (int bit = 0; bit < 8; ++bit) {
|
||||
crc = (crc & 0x80000000u) ? (crc << 1) ^ kCrcPoly : crc << 1;
|
||||
}
|
||||
result[byte] = crc;
|
||||
}
|
||||
return result;
|
||||
}();
|
||||
uint32_t crc = 0xffffffffu;
|
||||
for (std::size_t offset = 0; offset + 4 <= size; offset += 4) {
|
||||
const uint32_t word = GetU32Le(data + offset);
|
||||
for (int shift = 24; shift >= 0; shift -= 8) {
|
||||
crc = (crc << 8) ^ table[((crc >> 24) ^ (word >> shift)) & 0xffu];
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
uint32_t ProCodec::F(uint32_t value) const {
|
||||
return ((s_[0][(value >> 24) & 0xffu] + s_[1][(value >> 16) & 0xffu]) ^
|
||||
s_[2][(value >> 8) & 0xffu]) + s_[3][value & 0xffu];
|
||||
}
|
||||
|
||||
void ProCodec::EncryptBlock(const uint8_t* input, uint8_t* output) const {
|
||||
uint32_t left = GetU32Le(input);
|
||||
uint32_t right = GetU32Le(input + 4);
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
left ^= p_[i];
|
||||
right ^= F(left);
|
||||
const uint32_t temporary = left; left = right; right = temporary;
|
||||
}
|
||||
const uint32_t temporary = left; left = right; right = temporary;
|
||||
right ^= p_[16];
|
||||
left ^= p_[17];
|
||||
PutU32Le(output, left);
|
||||
PutU32Le(output + 4, right);
|
||||
}
|
||||
|
||||
void ProCodec::DecryptBlock(const uint8_t* input, uint8_t* output) const {
|
||||
uint32_t left = GetU32Le(input);
|
||||
uint32_t right = GetU32Le(input + 4);
|
||||
for (int i = 17; i > 1; --i) {
|
||||
left ^= p_[i];
|
||||
right ^= F(left);
|
||||
const uint32_t temporary = left; left = right; right = temporary;
|
||||
}
|
||||
const uint32_t temporary = left; left = right; right = temporary;
|
||||
right ^= p_[1];
|
||||
left ^= p_[0];
|
||||
PutU32Le(output, left);
|
||||
PutU32Le(output + 4, right);
|
||||
}
|
||||
|
||||
void ProCodec::Encrypt(const uint8_t* input, uint8_t* output, std::size_t size) const {
|
||||
if (size % 8 != 0) throw std::invalid_argument("Blowfish input must be 8-byte aligned");
|
||||
for (std::size_t offset = 0; offset < size; offset += 8) {
|
||||
EncryptBlock(input + offset, output + offset);
|
||||
}
|
||||
}
|
||||
|
||||
void ProCodec::Decrypt(const uint8_t* input, uint8_t* output, std::size_t size) const {
|
||||
if (size % 8 != 0) throw std::invalid_argument("Blowfish input must be 8-byte aligned");
|
||||
for (std::size_t offset = 0; offset < size; offset += 8) {
|
||||
DecryptBlock(input + offset, output + offset);
|
||||
}
|
||||
}
|
||||
|
||||
std::array<uint8_t, kLowCmdWireSize> ProCodec::EncodeLowCmd(
|
||||
const UNITREE_LEGGED_SDK::LowCmd& cmd) const {
|
||||
std::array<uint8_t, kLowCmdWireSize> plain{};
|
||||
plain[0] = cmd.head[0];
|
||||
plain[1] = cmd.head[1];
|
||||
plain[2] = cmd.levelFlag;
|
||||
plain[3] = cmd.frameReserve;
|
||||
for (std::size_t i = 0; i < 2; ++i) {
|
||||
PutU32Le(plain.data() + 4 + i * 4, cmd.SN[i]);
|
||||
PutU32Le(plain.data() + 12 + i * 4, cmd.version[i]);
|
||||
}
|
||||
plain[20] = static_cast<uint8_t>(cmd.bandWidth >> 8);
|
||||
plain[21] = static_cast<uint8_t>(cmd.bandWidth);
|
||||
for (std::size_t i = 0; i < cmd.motorCmd.size(); ++i) {
|
||||
EncodeMotor(plain.data() + kMotorOffset + i * kMotorWireSize, cmd.motorCmd[i]);
|
||||
}
|
||||
plain[562] = cmd.bms.off;
|
||||
std::copy(cmd.bms.reserve.begin(), cmd.bms.reserve.end(), plain.begin() + 563);
|
||||
std::copy(cmd.wirelessRemote.begin(), cmd.wirelessRemote.end(), plain.begin() + 566);
|
||||
PutU32Le(plain.data() + 606, cmd.reserve);
|
||||
PutU32Le(plain.data() + kCrcOffset, Crc32(plain.data(), kCrcOffset));
|
||||
|
||||
std::array<uint8_t, kLowCmdWireSize> encrypted{};
|
||||
Encrypt(plain.data(), encrypted.data(), encrypted.size());
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
bool ProCodec::DecodeLowState(const uint8_t* encrypted, std::size_t size,
|
||||
UNITREE_LEGGED_SDK::LowState& state) const {
|
||||
if (size < kLowStateParsedSize) return false;
|
||||
const std::size_t aligned = (size / 8) * 8;
|
||||
if (aligned < kLowStateParsedSize) return false;
|
||||
std::vector<uint8_t> data(aligned);
|
||||
Decrypt(encrypted, data.data(), aligned);
|
||||
if (data[0] != 0xfe || data[1] != 0xef ||
|
||||
data[2] != UNITREE_LEGGED_SDK::LOWLEVEL || data[3] != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::memset(&state, 0, sizeof(state));
|
||||
state.head = {{data[0], data[1]}};
|
||||
state.levelFlag = data[2];
|
||||
state.frameReserve = data[3];
|
||||
for (std::size_t i = 0; i < 2; ++i) {
|
||||
state.SN[i] = GetU32Le(data.data() + 4 + i * 4);
|
||||
state.version[i] = GetU32Le(data.data() + 12 + i * 4);
|
||||
}
|
||||
state.bandWidth = GetU16Le(data.data() + 20);
|
||||
for (std::size_t i = 0; i < 4; ++i) state.imu.quaternion[i] = GetF32Le(data.data() + 22 + i * 4);
|
||||
for (std::size_t i = 0; i < 3; ++i) {
|
||||
state.imu.gyroscope[i] = GetF32Le(data.data() + 38 + i * 4);
|
||||
state.imu.accelerometer[i] = GetF32Le(data.data() + 50 + i * 4);
|
||||
state.imu.rpy[i] = GetF32Le(data.data() + 62 + i * 4);
|
||||
}
|
||||
state.imu.temperature = static_cast<int8_t>(data[74]);
|
||||
|
||||
for (std::size_t i = 0; i < state.motorState.size(); ++i) {
|
||||
const uint8_t* motor = data.data() + 75 + i * 32;
|
||||
auto& output = state.motorState[i];
|
||||
output.mode = motor[0];
|
||||
output.q = GetF32Le(motor + 1);
|
||||
output.dq = GetF32Le(motor + 5);
|
||||
output.ddq = static_cast<float>(GetI16Le(motor + 9));
|
||||
output.tauEst = static_cast<float>(GetI16Le(motor + 11)) * 0.00390625f;
|
||||
output.q_raw = GetF32Le(motor + 13);
|
||||
output.dq_raw = GetF32Le(motor + 17);
|
||||
output.ddq_raw = static_cast<float>(GetI16Le(motor + 21));
|
||||
output.temperature = static_cast<int8_t>(motor[23]);
|
||||
output.reserve[0] = GetU32Le(motor + 24);
|
||||
output.reserve[1] = GetU32Le(motor + 28);
|
||||
}
|
||||
|
||||
const uint8_t* bms = data.data() + 715;
|
||||
state.bms.version_h = bms[0];
|
||||
state.bms.version_l = bms[1];
|
||||
state.bms.bms_status = bms[2];
|
||||
state.bms.SOC = bms[3];
|
||||
state.bms.current = GetI32Le(bms + 4);
|
||||
state.bms.cycle = GetU16Le(bms + 8);
|
||||
state.bms.BQ_NTC = {{static_cast<int8_t>(bms[10]), static_cast<int8_t>(bms[11])}};
|
||||
state.bms.MCU_NTC = {{static_cast<int8_t>(bms[12]), static_cast<int8_t>(bms[13])}};
|
||||
for (std::size_t i = 0; i < 5; ++i) state.bms.cell_vol[i] = GetU16Le(bms + 14 + i * 2);
|
||||
|
||||
// The PRO state packs these fields differently from the public EDU struct.
|
||||
// Keep the reverse-engineered mapping used by the Python implementation.
|
||||
const std::size_t force_offsets[4] = {739, 751, 753, 755};
|
||||
const std::size_t force_est_offsets[4] = {747, 759, 761, 763};
|
||||
for (std::size_t i = 0; i < 4; ++i) {
|
||||
state.footForce[i] = GetI16Le(data.data() + force_offsets[i]);
|
||||
state.footForceEst[i] = GetI16Le(data.data() + force_est_offsets[i]);
|
||||
}
|
||||
std::copy(data.begin() + 759, data.begin() + 799, state.wirelessRemote.begin());
|
||||
state.reserve = GetU32Le(data.data() + 799);
|
||||
state.crc = GetU32Le(data.data() + 803);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace go1_pro_internal
|
||||
48
src/pro_codec.h
Normal file
48
src/pro_codec.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#ifndef GO1_PRO_SDK_PRO_CODEC_H_
|
||||
#define GO1_PRO_SDK_PRO_CODEC_H_
|
||||
|
||||
#include "unitree_legged_sdk/comm.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace go1_pro_internal {
|
||||
|
||||
constexpr std::size_t kLowCmdWireSize = 616;
|
||||
constexpr std::size_t kLowStateDatagramSize = 858;
|
||||
constexpr std::size_t kLowStateEncryptedSize = 856;
|
||||
constexpr std::size_t kLowStateParsedSize = 807;
|
||||
|
||||
void InitLowCmd(UNITREE_LEGGED_SDK::LowCmd& cmd);
|
||||
void InitHighCmd(UNITREE_LEGGED_SDK::HighCmd& cmd);
|
||||
|
||||
class ProCodec {
|
||||
public:
|
||||
explicit ProCodec(const std::string& state_path);
|
||||
|
||||
static std::string FindStateFile();
|
||||
static uint32_t Crc32(const uint8_t* data, std::size_t size);
|
||||
|
||||
std::array<uint8_t, kLowCmdWireSize> EncodeLowCmd(
|
||||
const UNITREE_LEGGED_SDK::LowCmd& cmd) const;
|
||||
bool DecodeLowState(const uint8_t* encrypted, std::size_t size,
|
||||
UNITREE_LEGGED_SDK::LowState& state) const;
|
||||
|
||||
void Encrypt(const uint8_t* input, uint8_t* output, std::size_t size) const;
|
||||
void Decrypt(const uint8_t* input, uint8_t* output, std::size_t size) const;
|
||||
|
||||
private:
|
||||
uint32_t F(uint32_t value) const;
|
||||
void EncryptBlock(const uint8_t* input, uint8_t* output) const;
|
||||
void DecryptBlock(const uint8_t* input, uint8_t* output) const;
|
||||
|
||||
std::array<uint32_t, 18> p_{};
|
||||
std::array<std::array<uint32_t, 256>, 4> s_{};
|
||||
};
|
||||
|
||||
} // namespace go1_pro_internal
|
||||
|
||||
#endif
|
||||
25
src/quadruped.cpp
Normal file
25
src/quadruped.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#include "unitree_legged_sdk/quadruped.h"
|
||||
#include "unitree_legged_sdk/comm.h"
|
||||
|
||||
#if defined(__unix__) || defined(__APPLE__)
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
namespace UNITREE_LEGGED_SDK {
|
||||
|
||||
const int HIGH_CMD_LENGTH = sizeof(HighCmd);
|
||||
const int HIGH_STATE_LENGTH = sizeof(HighState);
|
||||
const int LOW_CMD_LENGTH = 616;
|
||||
const int LOW_STATE_LENGTH = 858;
|
||||
|
||||
std::string VersionSDK() { return "go1-pro-compat-3.8.6"; }
|
||||
|
||||
int InitEnvironment() {
|
||||
#if defined(__unix__) || defined(__APPLE__)
|
||||
return mlockall(MCL_CURRENT | MCL_FUTURE);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace UNITREE_LEGGED_SDK
|
||||
77
src/safety.cpp
Normal file
77
src/safety.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#include "unitree_legged_sdk/safety.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace UNITREE_LEGGED_SDK {
|
||||
namespace {
|
||||
|
||||
constexpr double kTorqueLimit[12] = {
|
||||
23.7, 23.7, 35.55, 23.7, 23.7, 35.55,
|
||||
23.7, 23.7, 35.55, 23.7, 23.7, 35.55,
|
||||
};
|
||||
|
||||
bool IsPositionStop(float value) {
|
||||
return std::fabs(static_cast<double>(value)) >= PosStopF * 0.1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Safety::Safety(LeggedType type)
|
||||
: WattLimit(0), Wcount(0),
|
||||
Hip_max(0.78), Hip_min(-0.78),
|
||||
Thigh_max(3.50), Thigh_min(-0.60),
|
||||
Calf_max(-0.95), Calf_min(-2.70) {
|
||||
(void)type;
|
||||
}
|
||||
|
||||
Safety::~Safety() = default;
|
||||
|
||||
void Safety::PositionLimit(LowCmd& cmd) {
|
||||
for (std::size_t i = 0; i < 12; ++i) {
|
||||
auto& motor = cmd.motorCmd[i];
|
||||
if (IsPositionStop(motor.q)) continue;
|
||||
const std::size_t joint = i % 3;
|
||||
const double minimum = joint == 0 ? Hip_min : (joint == 1 ? Thigh_min : Calf_min);
|
||||
const double maximum = joint == 0 ? Hip_max : (joint == 1 ? Thigh_max : Calf_max);
|
||||
motor.q = static_cast<float>(std::max(minimum, std::min(maximum, static_cast<double>(motor.q))));
|
||||
}
|
||||
}
|
||||
|
||||
int Safety::PowerProtect(LowCmd& cmd, LowState& state, int factor) {
|
||||
if (factor < 1 || factor > 10) return -1;
|
||||
WattLimit = factor;
|
||||
int limited = 0;
|
||||
for (std::size_t i = 0; i < 12; ++i) {
|
||||
const double maximum = kTorqueLimit[i];
|
||||
if (std::fabs(static_cast<double>(cmd.motorCmd[i].tau)) > maximum * 5.0 ||
|
||||
std::fabs(static_cast<double>(state.motorState[i].tauEst)) > maximum) {
|
||||
return -1;
|
||||
}
|
||||
const double limit = maximum * static_cast<double>(factor) / 10.0;
|
||||
const float bounded = static_cast<float>(
|
||||
std::max(-limit, std::min(limit, static_cast<double>(cmd.motorCmd[i].tau))));
|
||||
if (bounded != cmd.motorCmd[i].tau) {
|
||||
cmd.motorCmd[i].tau = bounded;
|
||||
++limited;
|
||||
}
|
||||
}
|
||||
Wcount += limited;
|
||||
return limited;
|
||||
}
|
||||
|
||||
int Safety::PositionProtect(LowCmd& cmd, LowState& state, double limit) {
|
||||
int protected_count = 0;
|
||||
for (std::size_t i = 0; i < 12; ++i) {
|
||||
auto& motor = cmd.motorCmd[i];
|
||||
if (IsPositionStop(motor.q)) continue;
|
||||
if (std::fabs(static_cast<double>(motor.q - state.motorState[i].q)) > limit) {
|
||||
motor.Kp = 0.0f;
|
||||
motor.Kd = 0.0f;
|
||||
++protected_count;
|
||||
}
|
||||
}
|
||||
return protected_count;
|
||||
}
|
||||
|
||||
} // namespace UNITREE_LEGGED_SDK
|
||||
310
src/udp.cpp
Normal file
310
src/udp.cpp
Normal file
@@ -0,0 +1,310 @@
|
||||
#include "unitree_legged_sdk/udp.h"
|
||||
|
||||
#include "pro_codec.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <arpa/inet.h>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <netinet/in.h>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
namespace UNITREE_LEGGED_SDK {
|
||||
namespace {
|
||||
|
||||
char* Duplicate(const char* value) {
|
||||
if (!value) return nullptr;
|
||||
const std::size_t size = std::strlen(value) + 1;
|
||||
char* result = new char[size];
|
||||
std::memcpy(result, value, size);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::runtime_error SocketError(const char* operation) {
|
||||
return std::runtime_error(std::string(operation) + ": " + std::strerror(errno));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct UDP::Impl {
|
||||
int socket_fd = -1;
|
||||
int send_length = 0;
|
||||
int recv_length = 0;
|
||||
RecvEnum recv_type = RecvEnum::nonBlock;
|
||||
bool pro_low_level = false;
|
||||
bool target_configured = false;
|
||||
std::mutex send_mutex;
|
||||
std::mutex recv_mutex;
|
||||
std::mutex socket_recv_mutex;
|
||||
std::mutex state_mutex;
|
||||
std::unique_ptr<go1_pro_internal::ProCodec> codec;
|
||||
std::vector<uint8_t> send_buffer;
|
||||
std::vector<uint8_t> recv_buffer;
|
||||
std::vector<uint8_t> wire_recv_buffer;
|
||||
std::size_t received_size = 0;
|
||||
LowState low_state{};
|
||||
bool have_low_state = false;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename ImplType>
|
||||
void ConfigureBlocking(ImplType& impl) {
|
||||
const int flags = fcntl(impl.socket_fd, F_GETFL, 0);
|
||||
if (flags < 0) throw SocketError("fcntl(F_GETFL)");
|
||||
const int next = impl.recv_type == RecvEnum::nonBlock ? flags | O_NONBLOCK : flags & ~O_NONBLOCK;
|
||||
if (fcntl(impl.socket_fd, F_SETFL, next) < 0) throw SocketError("fcntl(F_SETFL)");
|
||||
}
|
||||
|
||||
template <typename ImplType>
|
||||
void BindSocket(UDP& udp, ImplType& impl, uint16_t port) {
|
||||
impl.socket_fd = ::socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (impl.socket_fd < 0) throw SocketError("socket");
|
||||
const int reuse = 1;
|
||||
setsockopt(impl.socket_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
|
||||
sockaddr_in local{};
|
||||
local.sin_family = AF_INET;
|
||||
local.sin_port = htons(port);
|
||||
local.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
if (::bind(impl.socket_fd, reinterpret_cast<sockaddr*>(&local), sizeof(local)) < 0) {
|
||||
throw SocketError("bind");
|
||||
}
|
||||
udp.localPort = port;
|
||||
udp.localIP = Duplicate("0.0.0.0");
|
||||
ConfigureBlocking(impl);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
UDP::UDP(uint8_t level, uint16_t port, const char* ip, uint16_t target_port)
|
||||
: udpState{}, targetIP(nullptr), targetPort(0), localIP(nullptr), localPort(0),
|
||||
impl_(new Impl) {
|
||||
try {
|
||||
if (level != LOWLEVEL) {
|
||||
throw std::invalid_argument("Go1 PRO compatibility library supports LOWLEVEL UDP only");
|
||||
}
|
||||
impl_->pro_low_level = true;
|
||||
impl_->send_length = static_cast<int>(go1_pro_internal::kLowCmdWireSize);
|
||||
impl_->recv_length = static_cast<int>(go1_pro_internal::kLowStateDatagramSize);
|
||||
impl_->send_buffer.resize(impl_->send_length);
|
||||
impl_->recv_buffer.resize(2048);
|
||||
impl_->wire_recv_buffer.resize(2048);
|
||||
impl_->codec.reset(new go1_pro_internal::ProCodec(
|
||||
go1_pro_internal::ProCodec::FindStateFile()));
|
||||
BindSocket(*this, *impl_, port);
|
||||
SetIpPort(ip, target_port);
|
||||
} catch (...) {
|
||||
if (impl_->socket_fd >= 0) ::close(impl_->socket_fd);
|
||||
delete[] localIP;
|
||||
delete impl_;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
UDP::UDP(uint16_t port, const char* ip, uint16_t target_port,
|
||||
int send_length, int recv_length, bool initiative_disconnect, RecvEnum recv_type)
|
||||
: udpState{}, targetIP(nullptr), targetPort(0), localIP(nullptr), localPort(0),
|
||||
impl_(new Impl) {
|
||||
(void)initiative_disconnect;
|
||||
try {
|
||||
impl_->send_length = send_length;
|
||||
impl_->recv_length = recv_length;
|
||||
impl_->recv_type = recv_type;
|
||||
impl_->send_buffer.resize(send_length);
|
||||
impl_->recv_buffer.resize(recv_length);
|
||||
impl_->wire_recv_buffer.resize(recv_length);
|
||||
BindSocket(*this, *impl_, port);
|
||||
SetIpPort(ip, target_port);
|
||||
} catch (...) {
|
||||
if (impl_->socket_fd >= 0) ::close(impl_->socket_fd);
|
||||
delete[] localIP;
|
||||
delete impl_;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
UDP::UDP(uint16_t port, int send_length, int recv_length,
|
||||
bool initiative_disconnect, RecvEnum recv_type, bool set_ip_port)
|
||||
: udpState{}, targetIP(nullptr), targetPort(0), localIP(nullptr), localPort(0),
|
||||
impl_(new Impl) {
|
||||
(void)initiative_disconnect;
|
||||
(void)set_ip_port;
|
||||
try {
|
||||
impl_->send_length = send_length;
|
||||
impl_->recv_length = recv_length;
|
||||
impl_->recv_type = recv_type;
|
||||
impl_->send_buffer.resize(send_length);
|
||||
impl_->recv_buffer.resize(recv_length);
|
||||
impl_->wire_recv_buffer.resize(recv_length);
|
||||
BindSocket(*this, *impl_, port);
|
||||
} catch (...) {
|
||||
if (impl_->socket_fd >= 0) ::close(impl_->socket_fd);
|
||||
delete[] localIP;
|
||||
delete impl_;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
UDP::~UDP() {
|
||||
if (impl_) {
|
||||
if (impl_->socket_fd >= 0) ::close(impl_->socket_fd);
|
||||
delete impl_;
|
||||
}
|
||||
delete[] targetIP;
|
||||
delete[] localIP;
|
||||
}
|
||||
|
||||
void UDP::SetIpPort(const char* ip, uint16_t port) {
|
||||
if (!ip) throw std::invalid_argument("targetIP must not be null");
|
||||
sockaddr_in target{};
|
||||
target.sin_family = AF_INET;
|
||||
target.sin_port = htons(port);
|
||||
if (inet_pton(AF_INET, ip, &target.sin_addr) != 1) {
|
||||
throw std::invalid_argument(std::string("invalid IPv4 target: ") + ip);
|
||||
}
|
||||
if (::connect(impl_->socket_fd, reinterpret_cast<sockaddr*>(&target), sizeof(target)) < 0) {
|
||||
throw SocketError("connect");
|
||||
}
|
||||
delete[] targetIP;
|
||||
targetIP = Duplicate(ip);
|
||||
targetPort = port;
|
||||
impl_->target_configured = true;
|
||||
}
|
||||
|
||||
void UDP::SetRecvTimeout(int time) {
|
||||
timeval timeout{};
|
||||
timeout.tv_sec = time / 1000;
|
||||
timeout.tv_usec = (time % 1000) * 1000;
|
||||
if (setsockopt(impl_->socket_fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) {
|
||||
throw SocketError("setsockopt(SO_RCVTIMEO)");
|
||||
}
|
||||
impl_->recv_type = RecvEnum::blockTimeout;
|
||||
ConfigureBlocking(*impl_);
|
||||
}
|
||||
|
||||
void UDP::SetDisconnectTime(float callback_dt, float disconnectTime) {
|
||||
(void)callback_dt;
|
||||
(void)disconnectTime;
|
||||
}
|
||||
|
||||
void UDP::SetAccessibleTime(float callback_dt, float accessibleTime) {
|
||||
(void)callback_dt;
|
||||
(void)accessibleTime;
|
||||
}
|
||||
|
||||
int UDP::Send() {
|
||||
{
|
||||
std::lock_guard<std::mutex> state_lock(impl_->state_mutex);
|
||||
++udpState.TotalCount;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(impl_->send_mutex);
|
||||
if (!impl_->target_configured || impl_->send_buffer.empty()) {
|
||||
std::lock_guard<std::mutex> state_lock(impl_->state_mutex);
|
||||
++udpState.SendError;
|
||||
return -1;
|
||||
}
|
||||
const ssize_t sent = ::send(impl_->socket_fd, impl_->send_buffer.data(),
|
||||
impl_->send_buffer.size(), 0);
|
||||
if (sent < 0) {
|
||||
std::lock_guard<std::mutex> state_lock(impl_->state_mutex);
|
||||
++udpState.SendError;
|
||||
return -1;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> state_lock(impl_->state_mutex);
|
||||
++udpState.SendCount;
|
||||
}
|
||||
return static_cast<int>(sent);
|
||||
}
|
||||
|
||||
int UDP::Recv() {
|
||||
{
|
||||
std::lock_guard<std::mutex> state_lock(impl_->state_mutex);
|
||||
++udpState.TotalCount;
|
||||
}
|
||||
std::lock_guard<std::mutex> socket_lock(impl_->socket_recv_mutex);
|
||||
const ssize_t received = ::recv(impl_->socket_fd, impl_->wire_recv_buffer.data(),
|
||||
impl_->wire_recv_buffer.size(), 0);
|
||||
if (received < 0) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) return -1;
|
||||
return -1;
|
||||
}
|
||||
LowState decoded{};
|
||||
if (impl_->pro_low_level) {
|
||||
if (!impl_->codec->DecodeLowState(impl_->wire_recv_buffer.data(),
|
||||
static_cast<std::size_t>(received), decoded)) {
|
||||
std::lock_guard<std::mutex> state_lock(impl_->state_mutex);
|
||||
++udpState.FlagError;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(impl_->recv_mutex);
|
||||
++udpState.TotalCount;
|
||||
impl_->received_size = static_cast<std::size_t>(received);
|
||||
impl_->recv_buffer.assign(impl_->wire_recv_buffer.begin(),
|
||||
impl_->wire_recv_buffer.begin() + received);
|
||||
if (impl_->pro_low_level) {
|
||||
impl_->low_state = decoded;
|
||||
impl_->have_low_state = true;
|
||||
}
|
||||
accessible = true;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> state_lock(impl_->state_mutex);
|
||||
++udpState.RecvCount;
|
||||
}
|
||||
return static_cast<int>(received);
|
||||
}
|
||||
|
||||
void UDP::InitCmdData(LowCmd& cmd) {
|
||||
go1_pro_internal::InitLowCmd(cmd);
|
||||
}
|
||||
|
||||
void UDP::InitCmdData(HighCmd& cmd) {
|
||||
go1_pro_internal::InitHighCmd(cmd);
|
||||
}
|
||||
|
||||
int UDP::SetSend(char* data) {
|
||||
if (!data) return -1;
|
||||
std::lock_guard<std::mutex> lock(impl_->send_mutex);
|
||||
std::memcpy(impl_->send_buffer.data(), data, impl_->send_buffer.size());
|
||||
return 0;
|
||||
}
|
||||
|
||||
int UDP::SetSend(LowCmd& cmd) {
|
||||
if (!impl_->pro_low_level || !impl_->codec) return -1;
|
||||
const auto encoded = impl_->codec->EncodeLowCmd(cmd);
|
||||
std::lock_guard<std::mutex> lock(impl_->send_mutex);
|
||||
impl_->send_buffer.assign(encoded.begin(), encoded.end());
|
||||
return 0;
|
||||
}
|
||||
|
||||
int UDP::SetSend(HighCmd& cmd) {
|
||||
(void)cmd;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void UDP::GetRecv(char* data) {
|
||||
std::lock_guard<std::mutex> lock(impl_->recv_mutex);
|
||||
if (!data || impl_->received_size == 0) return;
|
||||
std::memcpy(data, impl_->recv_buffer.data(),
|
||||
std::min(impl_->received_size, static_cast<std::size_t>(impl_->recv_length)));
|
||||
}
|
||||
|
||||
void UDP::GetRecv(LowState& state) {
|
||||
std::lock_guard<std::mutex> lock(impl_->recv_mutex);
|
||||
if (impl_->have_low_state) state = impl_->low_state;
|
||||
}
|
||||
|
||||
void UDP::GetRecv(HighState& state) { (void)state; }
|
||||
|
||||
} // namespace UNITREE_LEGGED_SDK
|
||||
Reference in New Issue
Block a user