initial release
This commit is contained in:
commit
b52c204997
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"ros.distro": "noetic"
|
||||
}
|
||||
5
CHANGELOG.md
Normal file
5
CHANGELOG.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 1.0.1
|
||||
|
||||
+ Initial release.
|
||||
30
CMakeLists.txt
Normal file
30
CMakeLists.txt
Normal file
@ -0,0 +1,30 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(h1_inspire_service VERSION 0.1.0)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Debug)
|
||||
endif()
|
||||
|
||||
if (CMAKE_BUILD_TYPE MATCHES "Debug")
|
||||
message("Debug mode")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -g -O0 -fPIC")
|
||||
else()
|
||||
message("Release mode")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -O3 -DNDEBUG -fPIC")
|
||||
endif()
|
||||
|
||||
find_package(Boost COMPONENTS program_options REQUIRED)
|
||||
|
||||
include_directories(
|
||||
/usr/local/include/ddscxx
|
||||
/usr/local/include/iceoryx/v2.0.2
|
||||
include
|
||||
)
|
||||
|
||||
link_libraries(unitree_sdk2 ddsc ddscxx rt pthread)
|
||||
link_libraries(unitree_idl libboost_program_options.a)
|
||||
|
||||
add_executable(inspire_hand inspire_ctrl.cpp)
|
||||
|
||||
# example
|
||||
add_executable(h1_hand_example example/inspire_hand.cpp)
|
||||
BIN
bin/x86_64/h1_hand_example
Executable file
BIN
bin/x86_64/h1_hand_example
Executable file
Binary file not shown.
BIN
bin/x86_64/inspire_hand
Executable file
BIN
bin/x86_64/inspire_hand
Executable file
Binary file not shown.
80
doc/Inspire-Hand.md
Normal file
80
doc/Inspire-Hand.md
Normal file
@ -0,0 +1,80 @@
|
||||
## Dexterous Hand
|
||||
|
||||
Unitree H1 can be equipped with [Inspire Robotics](https://inspire-robots.com/product/frwz/)'s dexterous hand, which has **six degrees of freedom** and 12 motion joints to mimic the human hand for complex movements.
|
||||
|
||||

|
||||
|
||||
##
|
||||
|
||||
The user can control the hand by publish the **"unitree_go::msg::dds::MotorCmds_"** message to the topic **"rt/inspire/cmd"**, and get the hand state by subscribe the **"unitree_go::msg::dds::MotorStates_"** message from the topic **"rt/inspire/state"**.
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A(user) --rt/inspire/cmd--> B(H1)
|
||||
B --rt/inspire/state--> A
|
||||
```
|
||||
|
||||
+ IDL Message Type
|
||||
|
||||
Motor data in array format, containing 12 motor data for both hands.
|
||||
|
||||
!!!note
|
||||
Currently the dexterous hand only supports joint control, i.e. only the parameter q makes sense in the idl format. The others are reserved.
|
||||
!!!
|
||||
|
||||
|
||||
```bash
|
||||
# namespace unitree_go::msg::dds_
|
||||
|
||||
# unitree_go::msg::dds_::MotorCmds_
|
||||
struct MotorCmds_
|
||||
{
|
||||
sequence<unitree_go::msg::dds_::MotorCmd_> cmds;
|
||||
};
|
||||
|
||||
# unitree_go::msg::dds_::MotorStates_
|
||||
struct MotorCmds_
|
||||
{
|
||||
sequence<unitree_go::msg::dds_::MotorState> states;
|
||||
};
|
||||
```
|
||||
+ Joint Motor Sequence
|
||||
|
||||
<div style="text-align: center;">
|
||||
<table border="1">
|
||||
<tr>
|
||||
<td>Id</td>
|
||||
<td>0</td>
|
||||
<td>1</td>
|
||||
<td>2</td>
|
||||
<td>3</td>
|
||||
<td>4</td>
|
||||
<td>5</td>
|
||||
<td>6</td>
|
||||
<td>7</td>
|
||||
<td>8</td>
|
||||
<td>9</td>
|
||||
<td>10</td>
|
||||
<td>11</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">Joint</td>
|
||||
<td colspan="6">Right Hand</td>
|
||||
<td colspan="6">Left Hand</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>pinky</td>
|
||||
<td>ring</td>
|
||||
<td>middle</td>
|
||||
<td>index</td>
|
||||
<td>thumb-bend</td>
|
||||
<td>thumb-rotation</td>
|
||||
<td>pinky</td>
|
||||
<td>ring</td>
|
||||
<td>middle</td>
|
||||
<td>index</td>
|
||||
<td>thumb-bend</td>
|
||||
<td>thumb-rotation</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
BIN
doc/img/RH56.png
Normal file
BIN
doc/img/RH56.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 90 KiB |
80
doc/灵巧手开发.md
Normal file
80
doc/灵巧手开发.md
Normal file
@ -0,0 +1,80 @@
|
||||
## 灵巧手介绍
|
||||
|
||||
H1可搭载[Inspire Robotics](https://inspire-robots.com/product/frwz/)的仿人五指灵巧手,该灵巧手具有6个自由度和12个运动关节,可以模拟人手实现复杂动作。
|
||||

|
||||
|
||||
## 接口说明
|
||||
|
||||
**示例程序** [h1_example_hand.cpp]()
|
||||
|
||||
用户向 **"rt/inspire/cmd"** 话题发送 **"unitree_go::msg::dds::MotorCmds_"** 消息控制灵巧手。
|
||||
从 **"rt/inspire/state"** 话题接受 **"unitree_go::msg::dds::MotorStates_"** 消息获取灵巧手状态。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A(user) --rt/inspire/cmd--> B(H1)
|
||||
B --rt/inspire/state--> A
|
||||
```
|
||||
|
||||
+ IDL数据格式
|
||||
采用数组格式的电机数据,内部包含双手12个电机数据。
|
||||
```note
|
||||
当前灵巧手只支持关节控制,即在idl格式中只有参数q有意义。其他保留。
|
||||
```
|
||||
|
||||
```bash
|
||||
# namespace unitree_go::msg::dds_
|
||||
|
||||
# unitree_go::msg::dds_::MotorCmds_
|
||||
struct MotorCmds_
|
||||
{
|
||||
sequence<unitree_go::msg::dds_::MotorCmd_> cmds;
|
||||
};
|
||||
|
||||
# unitree_go::msg::dds_::MotorStates_
|
||||
struct MotorCmds_
|
||||
{
|
||||
sequence<unitree_go::msg::dds_::MotorState> states;
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
+ IDL中的关节顺序
|
||||
<div style="text-align: center;">
|
||||
<table border="1">
|
||||
<tr>
|
||||
<td>Id</td>
|
||||
<td>0</td>
|
||||
<td>1</td>
|
||||
<td>2</td>
|
||||
<td>3</td>
|
||||
<td>4</td>
|
||||
<td>5</td>
|
||||
<td>6</td>
|
||||
<td>7</td>
|
||||
<td>8</td>
|
||||
<td>9</td>
|
||||
<td>10</td>
|
||||
<td>11</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">Joint</td>
|
||||
<td colspan="6">Right Hand</td>
|
||||
<td colspan="6">Left Hand</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>pinky</td>
|
||||
<td>ring</td>
|
||||
<td>middle</td>
|
||||
<td>index</td>
|
||||
<td>thumb-bend</td>
|
||||
<td>thumb-rotation</td>
|
||||
<td>pinky</td>
|
||||
<td>ring</td>
|
||||
<td>middle</td>
|
||||
<td>index</td>
|
||||
<td>thumb-bend</td>
|
||||
<td>thumb-rotation</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
163
example/inspire_hand.cpp
Normal file
163
example/inspire_hand.cpp
Normal file
@ -0,0 +1,163 @@
|
||||
/**
|
||||
* @file inspire_hand.cpp
|
||||
* @brief This is an example of how to control the Unitree H1 (Inspire) Hand using unitree_sdk2.
|
||||
*/
|
||||
|
||||
// Inspire Hand Topic IDL Types
|
||||
#include <unitree/idl/go2/MotorCmds_.hpp>
|
||||
#include <unitree/idl/go2/MotorStates_.hpp>
|
||||
// DDS Channel
|
||||
#include <unitree/robot/channel/channel_publisher.hpp>
|
||||
#include <unitree/robot/channel/channel_subscriber.hpp>
|
||||
#include <unitree/common/thread/thread.hpp>
|
||||
|
||||
#include <eigen3/Eigen/Dense>
|
||||
#include <unordered_map>
|
||||
|
||||
/**
|
||||
* @brief Unitree H1 Hand Controller
|
||||
* The user can subscribe to "rt/inspire/state" to get the current state of the hand and publish to "rt/inspire/cmd" to control the hand.
|
||||
*
|
||||
* IDL Types
|
||||
* user ---(unitree_go::msg::dds_::MotorCmds_)---> "rt/inspire/cmd"
|
||||
* user <--(unitree_go::msg::dds_::MotorStates_)-- "rt/inspire/state"
|
||||
*
|
||||
* @attention Currently the hand only supports position control, which means only the `q` field in idl is used.
|
||||
*/
|
||||
class H1HandController
|
||||
{
|
||||
public:
|
||||
H1HandController()
|
||||
{
|
||||
this->InitDDS_();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Control the hand to a specific label
|
||||
*/
|
||||
void ctrl(std::string label)
|
||||
{
|
||||
if(labels.find(label) != labels.end())
|
||||
{
|
||||
this->ctrl(labels[label], labels[label]);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "Invalid label: " << label << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move the fingers to the specified angles
|
||||
*
|
||||
* @note The angles should be in the range [0, 1]
|
||||
* 0: close 1: open
|
||||
*/
|
||||
void ctrl(
|
||||
const Eigen::Matrix<float, 6, 1>& right_angles,
|
||||
const Eigen::Matrix<float, 6, 1>& left_angles)
|
||||
{
|
||||
for(size_t i(0); i<6; i++)
|
||||
{
|
||||
cmd.cmds()[i].q() = right_angles(i);
|
||||
cmd.cmds()[i+6].q() = left_angles(i);
|
||||
}
|
||||
handcmd->Write(cmd);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the right hand angles
|
||||
*
|
||||
* Joint order: [pinky, ring, middle, index, thumb_bend, thumb_rotation]
|
||||
*/
|
||||
Eigen::Matrix<float, 6, 1> getRightQ()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
Eigen::Matrix<float, 6, 1> q;
|
||||
for(size_t i(0); i<6; i++)
|
||||
{
|
||||
q(i) = state.states()[i].q();
|
||||
}
|
||||
return q;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the left hand angles
|
||||
*
|
||||
* Joint order: [pinky, ring, middle, index, thumb_bend, thumb_rotation]
|
||||
*/
|
||||
Eigen::Matrix<float, 6, 1> getLeftQ()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
Eigen::Matrix<float, 6, 1> q;
|
||||
for(size_t i(0); i<6; i++)
|
||||
{
|
||||
q(i) = state.states()[i+6].q();
|
||||
}
|
||||
return q;
|
||||
}
|
||||
|
||||
unitree_go::msg::dds_::MotorCmds_ cmd;
|
||||
unitree_go::msg::dds_::MotorStates_ state;
|
||||
private:
|
||||
void InitDDS_()
|
||||
{
|
||||
handcmd = std::make_shared<unitree::robot::ChannelPublisher<unitree_go::msg::dds_::MotorCmds_>>(
|
||||
"rt/inspire/cmd");
|
||||
handcmd->InitChannel();
|
||||
cmd.cmds().resize(12);
|
||||
handstate = std::make_shared<unitree::robot::ChannelSubscriber<unitree_go::msg::dds_::MotorStates_>>(
|
||||
"rt/inspire/state");
|
||||
handstate->InitChannel([this](const void *message){
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
state = *(unitree_go::msg::dds_::MotorStates_*)message;
|
||||
});
|
||||
state.states().resize(12);
|
||||
}
|
||||
|
||||
// DDS parameters
|
||||
std::mutex mtx;
|
||||
unitree::robot::ChannelPublisherPtr<unitree_go::msg::dds_::MotorCmds_> handcmd;
|
||||
unitree::robot::ChannelSubscriberPtr<unitree_go::msg::dds_::MotorStates_> handstate;
|
||||
|
||||
// Saved labels
|
||||
std::unordered_map<std::string, Eigen::Matrix<float, 6, 1>> labels = {
|
||||
{"open", Eigen::Matrix<float, 6, 1>::Ones()},
|
||||
{"close", Eigen::Matrix<float, 6, 1>::Zero()},
|
||||
{"half", Eigen::Matrix<float, 6, 1>::Constant(0.5)},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Main Function
|
||||
*/
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
std::cout << " --- Unitree Robotics --- \n";
|
||||
std::cout << " H1 Hand Example \n\n";
|
||||
|
||||
// Target label
|
||||
std::string label = "close"; // You change this value to other labels
|
||||
|
||||
// Initialize the DDS Channel
|
||||
std::string networkInterface = argc > 1 ? argv[1] : "";
|
||||
unitree::robot::ChannelFactory::Instance()->Init(0, networkInterface);
|
||||
|
||||
// Create the H1 Hand Controller
|
||||
auto h1hand = std::make_shared<H1HandController>();
|
||||
|
||||
int cnt = 0;
|
||||
while (true)
|
||||
{
|
||||
usleep(100000);
|
||||
if(cnt++ % 10 == 0)
|
||||
label = label == "close" ? "open" : "close";
|
||||
h1hand->ctrl(label); // Control the hand
|
||||
std::cout << "-- Hand State --\n";
|
||||
std::cout << " R: " << h1hand->getRightQ().transpose() << std::endl;
|
||||
std::cout << " L: " << h1hand->getLeftQ().transpose() << std::endl;
|
||||
std::cout << "\033[3A"; // Move cursor up 3 lines
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
154
include/SerialPort.h
Normal file
154
include/SerialPort.h
Normal file
@ -0,0 +1,154 @@
|
||||
#ifndef SERIAL_PORT_H
|
||||
#define SERIAL_PORT_H
|
||||
|
||||
#include <termios.h>
|
||||
#include <sys/select.h>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/serial.h>
|
||||
#include <unistd.h>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
#include <queue>
|
||||
|
||||
void print_data(const uint8_t* data, uint8_t len)
|
||||
{
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
printf("%02x ", data[i]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
class SerialPort
|
||||
{
|
||||
public:
|
||||
using SharedPtr = std::shared_ptr<SerialPort>;
|
||||
|
||||
SerialPort(std::string port, speed_t baudrate, int timeout_ms = 2)
|
||||
{
|
||||
set_timeout(timeout_ms);
|
||||
Init(port, baudrate);
|
||||
}
|
||||
|
||||
~SerialPort()
|
||||
{
|
||||
close(fd_);
|
||||
}
|
||||
|
||||
ssize_t send(const uint8_t* data, size_t len)
|
||||
{
|
||||
// tcflush(fd_, TCIFLUSH);
|
||||
ssize_t ret = ::write(fd_, data, len);
|
||||
// tcdrain(fd_);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssize_t recv(uint8_t* data, size_t len)
|
||||
{
|
||||
FD_ZERO(&rSet_);
|
||||
FD_SET(fd_, &rSet_);
|
||||
ssize_t recv_len = 0;
|
||||
|
||||
switch (select(fd_ + 1, &rSet_, NULL, NULL, &timeout_))
|
||||
{
|
||||
case -1: // error
|
||||
// std::cout << "communication error" << std::endl;
|
||||
break;
|
||||
case 0: // timeout
|
||||
// std::cout << "timeout" << std::endl;
|
||||
break;
|
||||
default:
|
||||
recv_len = ::read(fd_, data, len);
|
||||
break;
|
||||
}
|
||||
|
||||
return recv_len;
|
||||
}
|
||||
|
||||
void recv(uint8_t* data, uint8_t head, ssize_t len)
|
||||
{
|
||||
// 存入队列
|
||||
ssize_t recv_len = this->recv(recv_buf.data(), len);
|
||||
for (int i = 0; i < recv_len; i++)
|
||||
{
|
||||
recv_queue.push(recv_buf[i]);
|
||||
}
|
||||
|
||||
// 查找帧头
|
||||
while (recv_queue.size() >= len)
|
||||
{
|
||||
if(recv_queue.front() != head)
|
||||
{
|
||||
recv_queue.pop();
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if(recv_queue.size() < len) return;
|
||||
|
||||
// 读取数据
|
||||
for(int i = 0; i < len; i++)
|
||||
{
|
||||
data[i] = recv_queue.front();
|
||||
recv_queue.pop();
|
||||
}
|
||||
}
|
||||
|
||||
void set_timeout(int timeout_ms)
|
||||
{
|
||||
timeout_.tv_sec = timeout_ms / 1000;
|
||||
timeout_.tv_usec = (timeout_ms % 1000) * 1000;
|
||||
}
|
||||
|
||||
private:
|
||||
void Init(std::string port, speed_t baudrate)
|
||||
{
|
||||
int ret;
|
||||
// Open serial port
|
||||
fd_ = open(port.c_str(), O_RDWR | O_NOCTTY);
|
||||
if (fd_ < 0)
|
||||
{
|
||||
printf("Open serial port %s failed\n", port.c_str());
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
// Set attributes
|
||||
struct termios option;
|
||||
memset(&option, 0, sizeof(option));
|
||||
ret = tcgetattr(fd_, &option);
|
||||
|
||||
option.c_oflag = 0;
|
||||
option.c_lflag = 0;
|
||||
option.c_iflag = 0;
|
||||
|
||||
cfsetispeed(&option, baudrate);
|
||||
cfsetospeed(&option, baudrate);
|
||||
|
||||
option.c_cflag &= ~CSIZE;
|
||||
option.c_cflag |= CS8; // 8
|
||||
option.c_cflag &= ~PARENB; // no parity
|
||||
option.c_iflag &= ~INPCK; // no parity
|
||||
option.c_cflag &= ~CSTOPB; // 1 stop bit
|
||||
|
||||
option.c_cc[VTIME] = 0;
|
||||
option.c_cc[VMIN] = 0;
|
||||
option.c_lflag |= CBAUDEX;
|
||||
|
||||
ret = tcflush(fd_, TCIFLUSH);
|
||||
ret = tcsetattr(fd_, TCSANOW, &option);
|
||||
}
|
||||
|
||||
int fd_;
|
||||
fd_set rSet_;
|
||||
timeval timeout_;
|
||||
|
||||
std::queue<uint8_t> recv_queue;
|
||||
std::array<uint8_t, 1024> recv_buf;
|
||||
};
|
||||
|
||||
#endif // SERIAL_PORT_H
|
||||
262
include/inspire.h
Normal file
262
include/inspire.h
Normal file
@ -0,0 +1,262 @@
|
||||
#ifndef INSPIRE_H
|
||||
#define INSPIRE_H
|
||||
|
||||
#include <eigen3/Eigen/Dense>
|
||||
#include <vector>
|
||||
|
||||
#include "SerialPort.h"
|
||||
|
||||
namespace inspire
|
||||
{
|
||||
|
||||
class InspireHand
|
||||
{
|
||||
public:
|
||||
InspireHand(SerialPort::SharedPtr serial = nullptr, id_t id = 0)
|
||||
: serial_(serial), id(id)
|
||||
{
|
||||
if(!serial)
|
||||
serial_ = std::make_shared<SerialPort>("/dev/ttyUSB0", B115200);
|
||||
|
||||
}
|
||||
|
||||
void ChangeID(uint8_t before, uint8_t now)
|
||||
{
|
||||
std::vector<uint8_t> cmd = {0xEB, 0x90, before, 0x04, 0x12, 0xE8, 0x03, now, 0x00};
|
||||
cmd.back() = CheckSum(cmd.data(), cmd.size());
|
||||
serial_->send(cmd.data(), cmd.size());
|
||||
|
||||
usleep(5000);
|
||||
serial_->recv(recvBuff, 9);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief 设置各自由度的位置
|
||||
* -1: 不变
|
||||
* [0, 1] 0: 闭合
|
||||
* 小拇指、无名指、中指、食指、大拇指弯曲、大拇指旋转
|
||||
*/
|
||||
int16_t SetPosition(const Eigen::Matrix<double, 6, 1> & q)
|
||||
{
|
||||
// 将q限制在0到1
|
||||
Eigen::Matrix<int16_t, 6, 1> q_int16 = (q * 1000).cast<int16_t>().cwiseMax(0).cwiseMin(1000);
|
||||
|
||||
|
||||
uint8_t cmd[20];
|
||||
cmd[0] = 0xEB; // head
|
||||
cmd[1] = 0x90;
|
||||
cmd[2] = id; // ID
|
||||
cmd[3] = 0x0F; // data length
|
||||
cmd[4] = 0x12; // write
|
||||
cmd[5] = 0xCE; // address
|
||||
cmd[6] = 0x05;
|
||||
|
||||
cmd[7] = q_int16(0) & 0xFF;
|
||||
cmd[8] = (q_int16(0) >> 8) & 0xFF;
|
||||
cmd[9] = q_int16(1) & 0xFF;
|
||||
cmd[10] = (q_int16(1) >> 8) & 0xFF;
|
||||
cmd[11] = q_int16(2) & 0xFF;
|
||||
cmd[12] = (q_int16(2) >> 8) & 0xFF;
|
||||
cmd[13] = q_int16(3) & 0xFF;
|
||||
cmd[14] = (q_int16(3) >> 8) & 0xFF;
|
||||
cmd[15] = q_int16(4) & 0xFF;
|
||||
cmd[16] = (q_int16(4) >> 8) & 0xFF;
|
||||
cmd[17] = q_int16(5) & 0xFF;
|
||||
cmd[18] = (q_int16(5) >> 8) & 0xFF;
|
||||
|
||||
cmd[19] = CheckSum(cmd, 20);
|
||||
|
||||
serial_->send(cmd, 20);
|
||||
usleep(5000);
|
||||
serial_->recv(recvBuff, 9);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读取当前各自由度的角度位置
|
||||
*
|
||||
* 小拇指、无名指、中指、食指、大拇指弯曲、大拇指旋转
|
||||
*/
|
||||
int16_t GetPosition(Eigen::Matrix<double, 6, 1> & q)
|
||||
{
|
||||
std::vector<uint8_t> cmd = {0xEB, 0x90, id, 0x04, 0x11, 0x0A, 0x06, 0x0C, 0x00};
|
||||
cmd.back() = CheckSum(cmd.data(), cmd.size());
|
||||
serial_->send(cmd.data(), cmd.size());
|
||||
|
||||
usleep(5000);
|
||||
size_t len = serial_->recv(recvBuff, 20);
|
||||
|
||||
if(len != 20) return 1;
|
||||
if(recvBuff[19] != CheckSum(recvBuff, 20)) return 2;
|
||||
|
||||
q(0) = (recvBuff[7] | (recvBuff[8] << 8)) / 1000.;
|
||||
q(1) = (recvBuff[9] | (recvBuff[10] << 8)) / 1000.;
|
||||
q(2) = (recvBuff[11] | (recvBuff[12] << 8)) / 1000.;
|
||||
q(3) = (recvBuff[13] | (recvBuff[14] << 8)) / 1000.;
|
||||
q(4) = (recvBuff[15] | (recvBuff[16] << 8)) / 1000.;
|
||||
q(5) = (recvBuff[17] | (recvBuff[18] << 8)) / 1000.;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置各自由度的速度
|
||||
*
|
||||
* 设置完速度后会立即生效,断电再上电不保存。
|
||||
*/
|
||||
void SetVelocity(int16_t v0, int16_t v1, int16_t v2, int16_t v3, int16_t v4, int16_t v5)
|
||||
{
|
||||
uint8_t cmd[20];
|
||||
cmd[0] = 0xEB; // head
|
||||
cmd[1] = 0x90;
|
||||
cmd[2] = id; // ID
|
||||
cmd[3] = 0x0F; // data length
|
||||
cmd[4] = 0x12; // write
|
||||
cmd[5] = 0xF2; // address
|
||||
cmd[6] = 0x05;
|
||||
|
||||
cmd[7] = v0 & 0xFF;
|
||||
cmd[8] = (v0 >> 8) & 0xFF;
|
||||
cmd[9] = v1 & 0xFF;
|
||||
cmd[10] = (v1 >> 8) & 0xFF;
|
||||
cmd[11] = v2 & 0xFF;
|
||||
cmd[12] = (v2 >> 8) & 0xFF;
|
||||
cmd[13] = v3 & 0xFF;
|
||||
cmd[14] = (v3 >> 8) & 0xFF;
|
||||
cmd[15] = v4 & 0xFF;
|
||||
cmd[16] = (v4 >> 8) & 0xFF;
|
||||
cmd[17] = v5 & 0xFF;
|
||||
cmd[18] = (v5 >> 8) & 0xFF;
|
||||
|
||||
cmd[19] = CheckSum(cmd, 20);
|
||||
|
||||
serial_->send(cmd, 20);
|
||||
usleep(5000);
|
||||
serial_->recv(recvBuff, 9);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 各自由度的力控阈值设置值
|
||||
*
|
||||
* [0, 1000] Unit: g
|
||||
*/
|
||||
void SetForce(uint16_t f0, uint16_t f1, uint16_t f2, uint16_t f3, uint16_t f4, uint16_t f5)
|
||||
{
|
||||
uint8_t cmd[20];
|
||||
cmd[0] = 0xEB; // head
|
||||
cmd[1] = 0x90;
|
||||
cmd[2] = id; // ID
|
||||
cmd[3] = 0x0F; // data length
|
||||
cmd[4] = 0x12; // write
|
||||
cmd[5] = 0xDA; // address
|
||||
cmd[6] = 0x05;
|
||||
|
||||
cmd[7] = f0 & 0xFF;
|
||||
cmd[8] = (f0 >> 8) & 0xFF;
|
||||
cmd[9] = f1 & 0xFF;
|
||||
cmd[10] = (f1 >> 8) & 0xFF;
|
||||
cmd[11] = f2 & 0xFF;
|
||||
cmd[12] = (f2 >> 8) & 0xFF;
|
||||
cmd[13] = f3 & 0xFF;
|
||||
cmd[14] = (f3 >> 8) & 0xFF;
|
||||
cmd[15] = f4 & 0xFF;
|
||||
cmd[16] = (f4 >> 8) & 0xFF;
|
||||
cmd[17] = f5 & 0xFF;
|
||||
cmd[18] = (f5 >> 8) & 0xFF;
|
||||
|
||||
cmd[19] = CheckSum(cmd, 20);
|
||||
|
||||
serial_->send(cmd, 20);
|
||||
usleep(5000);
|
||||
serial_->recv(recvBuff, 9);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 各手指的实际受力
|
||||
*
|
||||
* 原本单位为 g, [0 - 1000], 转为 N
|
||||
*/
|
||||
int16_t GetForce(Eigen::Matrix<double, 6, 1> & f)
|
||||
{
|
||||
std::vector<uint8_t> cmd = {0xEB, 0x90, id, 0x04, 0x11, 0x2E, 0x06, 0x0C, 0x00};
|
||||
cmd.back() = CheckSum(cmd.data(), cmd.size());
|
||||
serial_->send(cmd.data(), cmd.size());
|
||||
|
||||
usleep(5000);
|
||||
size_t len = serial_->recv(recvBuff, 20);
|
||||
|
||||
if(len != 20) return 1;
|
||||
if(recvBuff[19] != CheckSum(recvBuff, 20)) return 2;
|
||||
|
||||
f(0) = int16_t(recvBuff[7] | (recvBuff[8] << 8)) / 1000. * 9.8;
|
||||
f(1) = int16_t(recvBuff[9] | (recvBuff[10] << 8)) / 1000. * 9.8;
|
||||
f(2) = int16_t(recvBuff[11] | (recvBuff[12] << 8)) / 1000. * 9.8;
|
||||
f(3) = int16_t(recvBuff[13] | (recvBuff[14] << 8)) / 1000. * 9.8;
|
||||
f(4) = int16_t(recvBuff[15] | (recvBuff[16] << 8)) / 1000. * 9.8;
|
||||
f(5) = int16_t(recvBuff[17] | (recvBuff[18] << 8)) / 1000. * 9.8;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清除错误
|
||||
*
|
||||
* 当灵巧手发生堵转,过流,异常等故障可通过清除故障指令恢复正常运行
|
||||
*/
|
||||
void ClearError()
|
||||
{
|
||||
uint8_t cmd[9];
|
||||
cmd[0] = 0xEB; // head
|
||||
cmd[1] = 0x90;
|
||||
cmd[2] = id; // ID
|
||||
cmd[3] = 0x04; // data length
|
||||
cmd[4] = 0x12; // write
|
||||
cmd[5] = 0xEC; // address
|
||||
cmd[6] = 0x03;
|
||||
cmd[7] = 0x01;
|
||||
cmd[8] = CheckSum(cmd, 9);
|
||||
|
||||
serial_->send(cmd, 9);
|
||||
usleep(5000);
|
||||
serial_->recv(recvBuff, 9);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 受力传感器校准
|
||||
*
|
||||
* 校准时需保证灵巧手处于空载状态
|
||||
*/
|
||||
void Calibration()
|
||||
{
|
||||
std::vector<uint8_t> cmd = {0xEB, 0x90, id, 0x04, 0x12, 0x2F, 0x06, 0x01, 0x00};
|
||||
cmd.back() = CheckSum(cmd.data(), cmd.size());
|
||||
serial_->send(cmd.data(), cmd.size());
|
||||
|
||||
usleep(5000);
|
||||
serial_->recv(recvBuff, 9); // 第一帧
|
||||
sleep(10); // 整个过程大约需要6s
|
||||
serial_->recv(recvBuff, 9); // 第二帧
|
||||
std::cout << "Calibration: ";
|
||||
print_data(recvBuff, 9);
|
||||
}
|
||||
|
||||
uint8_t id = 1;
|
||||
private:
|
||||
uint8_t CheckSum(const uint8_t* data, uint8_t len)
|
||||
{
|
||||
uint8_t sum = 0;
|
||||
// 除应答帧头外其余数据的累加和的低字节
|
||||
for (int i = 2; i < len - 1; i++)
|
||||
{
|
||||
sum += data[i];
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
SerialPort::SharedPtr serial_;
|
||||
uint8_t recvBuff[1024];
|
||||
};
|
||||
|
||||
} // namespace inspire
|
||||
#endif // INSPIRE_H
|
||||
58
include/param.h
Normal file
58
include/param.h
Normal file
@ -0,0 +1,58 @@
|
||||
#ifndef PARAM_H
|
||||
#define PARAM_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <boost/program_options.hpp>
|
||||
|
||||
namespace param
|
||||
{
|
||||
|
||||
namespace po = boost::program_options;
|
||||
|
||||
inline std::string serial_port;
|
||||
inline std::string network;
|
||||
inline std::string ns;
|
||||
inline float threhold;
|
||||
|
||||
po::variables_map helper(int argc, char** argv)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
spdlog::set_level(spdlog::level::debug);
|
||||
#else
|
||||
spdlog::set_level(spdlog::level::info);
|
||||
#endif
|
||||
|
||||
|
||||
po::options_description desc("Unitree H1 Inspire Hand Serial to DDS");
|
||||
desc.add_options()
|
||||
("help,h", "produce help message")
|
||||
("serial,s", po::value<std::string>(&serial_port)->default_value("/dev/h1_newhand"), "serial port")
|
||||
("network", po::value<std::string>(&network)->default_value(""), "DDS network interface")
|
||||
("namespace", po::value<std::string>(&ns)->default_value("inspire"), "DDS topic namespace")
|
||||
;
|
||||
|
||||
po::variables_map vm;
|
||||
po::store(po::parse_command_line(argc, argv, desc), vm);
|
||||
po::notify(vm);
|
||||
|
||||
if (vm.count("help"))
|
||||
{
|
||||
std::cout << desc << std::endl;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if(ns.empty())
|
||||
{
|
||||
spdlog::error("Namespace cannot be empty");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return vm;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // PARAM_H
|
||||
116
inspire_ctrl.cpp
Normal file
116
inspire_ctrl.cpp
Normal file
@ -0,0 +1,116 @@
|
||||
#include "inspire.h"
|
||||
#include <unitree/dds_wrapper/go2/go2_pub.h>
|
||||
#include <unitree/dds_wrapper/go2/go2_sub.h>
|
||||
#include "param.h"
|
||||
|
||||
class InspireRunner
|
||||
{
|
||||
public:
|
||||
InspireRunner()
|
||||
{
|
||||
serial = std::make_shared<SerialPort>(param::serial_port, B115200);
|
||||
|
||||
// inspire
|
||||
righthand = std::make_shared<inspire::InspireHand>(serial, 1); // 按邵博格式,先右后左
|
||||
lefthand = std::make_shared<inspire::InspireHand>(serial, 2);
|
||||
|
||||
// dds
|
||||
handcmd = std::make_shared<unitree::robot::SubscriptionBase<unitree_go::msg::dds_::MotorCmds_>>(
|
||||
"rt/" + param::ns + "/cmd");
|
||||
handcmd->msg_.cmds().resize(12);
|
||||
handstate = std::make_unique<unitree::robot::go2::publisher::RealTimeMotorStates>(
|
||||
"rt/" + param::ns + "/state");
|
||||
handstate->msg_.states().resize(12);
|
||||
|
||||
// Start running
|
||||
thread = std::make_shared<unitree::common::RecurrentThread>(
|
||||
10000, std::bind(&InspireRunner::run, this)
|
||||
);
|
||||
}
|
||||
|
||||
void run()
|
||||
{
|
||||
// Set command
|
||||
if(!handcmd->isTimeout())
|
||||
{
|
||||
for(int i(0); i<12; i++)
|
||||
{
|
||||
qcmd(i) = handcmd->msg_.cmds()[i].q();
|
||||
}
|
||||
righthand->SetPosition(qcmd.block<6, 1>(0, 0)); // 先右后左
|
||||
lefthand->SetPosition(qcmd.block<6, 1>(6, 0));
|
||||
}
|
||||
|
||||
// Recv state
|
||||
Eigen::Matrix<double, 6, 1> qtemp;
|
||||
if(righthand->GetPosition(qtemp) == 0)
|
||||
{
|
||||
qstate.block<6, 1>(0, 0) = qtemp;
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int i(0); i<6; i++)
|
||||
{
|
||||
handstate->msg_.states()[i].lost()++;
|
||||
}
|
||||
spdlog::debug("Failed to get right hand state");
|
||||
}
|
||||
if(lefthand->GetPosition(qtemp) == 0)
|
||||
{
|
||||
qstate.block<6, 1>(6, 0) = qtemp;
|
||||
}
|
||||
else
|
||||
{
|
||||
for(int i(0); i<6; i++)
|
||||
{
|
||||
handstate->msg_.states()[i+6].lost()++;
|
||||
}
|
||||
spdlog::debug("Failed to get left hand state");
|
||||
}
|
||||
if(handstate->trylock())
|
||||
{
|
||||
for(int i(0); i<12; i++)
|
||||
{
|
||||
handstate->msg_.states()[i].q() = qstate(i);
|
||||
}
|
||||
handstate->unlockAndPublish();
|
||||
}
|
||||
}
|
||||
|
||||
unitree::common::ThreadPtr thread;
|
||||
|
||||
// inspire
|
||||
SerialPort::SharedPtr serial;
|
||||
std::shared_ptr<inspire::InspireHand> lefthand;
|
||||
std::shared_ptr<inspire::InspireHand> righthand;
|
||||
Eigen::Matrix<double, 12, 1> qcmd, qstate;
|
||||
|
||||
// dds
|
||||
std::shared_ptr<unitree::robot::SubscriptionBase<unitree_go::msg::dds_::MotorCmds_>> handcmd;
|
||||
std::unique_ptr<unitree::robot::go2::publisher::RealTimeMotorStates> handstate;
|
||||
};
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
auto vm = param::helper(argc, argv);
|
||||
if (param::network.empty())
|
||||
{
|
||||
// 在H1上需使用master service启动DDS,不带参数,查找程序同级目录下的parameter.json
|
||||
// 否则会报错
|
||||
unitree::robot::ChannelFactory::Instance()->Init();
|
||||
}
|
||||
else {
|
||||
unitree::robot::ChannelFactory::Instance()->Init(0, param::network);
|
||||
}
|
||||
|
||||
std::cout << " --- Unitree Robotics --- " << std::endl;
|
||||
std::cout << " Inspire Hand Controller " << std::endl;
|
||||
|
||||
InspireRunner runner;
|
||||
|
||||
while (true)
|
||||
{
|
||||
sleep(1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user