4. SDK接口调用说明
4.1 核心类和方法
4.1.1 命名空间
cpp
namespace psi_glove {
class SerialInterface;
class PSIGloveController;
struct StatusMessage;
class CommunicationInterface; // 抽象基类
}4.1.2 SerialInterface 类
串口通信接口类,处理底层硬件通信。
cpp
#include <psi_glove_sdk/serial_interface.hpp>
namespace psi_glove {
class SerialInterface : public CommunicationInterface {
public:
// 构造函数
SerialInterface(
const std::string& port,
int baudrate = 115200,
std::chrono::milliseconds timeout = std::chrono::milliseconds(6),
bool auto_connect = false,
bool mock = false
);
// 析构函数(自动断开连接)
~SerialInterface() override;
// 连接/断开
bool Connect() override;
bool Disconnect() override;
bool IsConnected() const override;
// 数据传输
bool Write(const std::vector<uint8_t>& data) override;
bool Read(std::vector<uint8_t>& data, size_t size) override;
};
} // namespace psi_glove4.1.3 PSIGloveController 类
主控制器类,管理数据读取、解析和平滑处理。
cpp
#include <psi_glove_sdk/psi_glove_controller.hpp>
namespace psi_glove {
class PSIGloveController {
public:
// 构造函数
PSIGloveController(
std::unique_ptr<CommunicationInterface> interface,
size_t smoothing_window_size = 10
);
// 析构函数
~PSIGloveController();
// 连接管理
bool Connect();
bool Disconnect();
bool IsConnected() const;
// 数据读取
std::optional<StatusMessage> Loop();
std::optional<StatusMessage> GetLastStatus() const;
private:
// 内部实现(用户无需关注)
class Impl;
std::unique_ptr<Impl> pimpl_;
};
} // namespace psi_glove核心方法说明:
| 方法名称 | 返回类型 | 功能说明 |
|---|---|---|
Connect() | bool | 连接设备,成功返回 true |
Disconnect() | bool | 断开设备连接 |
IsConnected() | bool | 检查是否已连接 |
Loop() | std::optional<StatusMessage> | 读取并返回关节数据,失败返回 std::nullopt |
GetLastStatus() | std::optional<StatusMessage> | 获取最后一次成功读取的数据 |
4.1.4 StatusMessage 结构
包含 21 个关节位置的数据结构。
cpp
#include <psi_glove_sdk/types.hpp>
namespace psi_glove {
struct StatusMessage {
std::array<uint16_t, 5> thumb; // 拇指:5 个关节
std::array<uint16_t, 4> index; // 食指:4 个关节
std::array<uint16_t, 4> middle; // 中指:4 个关节
std::array<uint16_t, 4> ring; // 无名指:4 个关节
std::array<uint16_t, 4> pinky; // 小指:4 个关节
// 辅助方法
std::vector<uint16_t> ToVector() const; // 转换为扁平向量(21个值)
};
} // namespace psi_glove4.2 读取主手数据的完整流程
4.2.1 最小示例
cpp
#include <psi_glove_sdk/psi_glove.hpp>
#include <iostream>
#include <thread>
#include <chrono>
int main() {
// 1. 创建串口接口
auto serial = std::make_unique<psi_glove::SerialInterface>(
"/dev/ttyACM0", // 串口路径
115200 // 波特率
);
// 2. 创建控制器
psi_glove::PSIGloveController controller(std::move(serial), 10);
// 3. 连接设备
if (!controller.Connect()) {
std::cerr << "连接失败" << std::endl;
return 1;
}
// 4. 循环读取数据
for (int i = 0; i < 100; ++i) {
auto status = controller.Loop();
if (status) {
// 访问关节数据
std::cout << "拇指第一关节: " << status->thumb[0] << std::endl;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
// 5. 断开连接(或让析构函数自动处理)
controller.Disconnect();
return 0;
}4.2.2 完整示例(带错误处理)
cpp
#include <psi_glove_sdk/psi_glove.hpp>
#include <iostream>
#include <thread>
#include <chrono>
#include <signal.h>
#include <atomic>
// 全局退出标志
std::atomic<bool> g_running{true};
void SignalHandler(int signal) {
g_running.store(false);
std::cout << "\n正在退出..." << std::endl;
}
int main(int argc, char** argv) {
// 设置信号处理
signal(SIGINT, SignalHandler);
// 解析命令行参数
std::string port = (argc > 1) ? argv[1] : "/dev/ttyUSB0";
int baudrate = (argc > 2) ? std::atoi(argv[2]) : 115200;
try {
// 1. 创建串口接口
auto serial = std::make_unique<psi_glove::SerialInterface>(
port,
baudrate,
std::chrono::milliseconds(6),
false, // 不自动连接
false // 不使用模拟模式
);
// 2. 创建控制器
psi_glove::PSIGloveController controller(std::move(serial), 10);
// 3. 连接设备
std::cout << "正在连接到 " << port << "..." << std::endl;
if (!controller.Connect()) {
std::cerr << "错误: 无法连接到设备" << std::endl;
std::cerr << "请检查:" << std::endl;
std::cerr << " - 设备是否已连接" << std::endl;
std::cerr << " - 串口路径是否正确" << std::endl;
std::cerr << " - 是否有权限访问串口" << std::endl;
return 1;
}
std::cout << "连接成功!" << std::endl;
// 4. 主循环 - 读取关节数据
int frame_count = 0;
auto start_time = std::chrono::steady_clock::now();
while (g_running && controller.IsConnected()) {
// 读取数据
auto status = controller.Loop();
if (status) {
// 成功读取数据
if (frame_count % 10 == 0) {
std::cout << "\n--- 帧 #" << frame_count << " ---" << std::endl;
// 打印拇指数据
std::cout << "拇指: ";
for (size_t i = 0; i < status->thumb.size(); ++i) {
std::cout << status->thumb[i];
if (i < status->thumb.size() - 1) std::cout << ", ";
}
std::cout << std::endl;
// 打印食指数据
std::cout << "食指: ";
for (size_t i = 0; i < status->index.size(); ++i) {
std::cout << status->index[i];
if (i < status->index.size() - 1) std::cout << ", ";
}
std::cout << std::endl;
// 其他手指...
}
frame_count++;
} else {
// 读取失败
std::cerr << "警告: 读取失败,使用缓存数据" << std::endl;
// 尝试获取最后一次成功的数据
auto last_status = controller.GetLastStatus();
if (last_status) {
std::cout << "(使用上次的有效数据)" << std::endl;
}
}
// 控制循环频率 (约100Hz)
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
// 计算统计信息
auto end_time = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::seconds>(
end_time - start_time
);
std::cout << "\n统计信息:" << std::endl;
std::cout << " 总帧数: " << frame_count << std::endl;
std::cout << " 运行时间: " << duration.count() << " 秒" << std::endl;
if (duration.count() > 0) {
double fps = frame_count / static_cast<double>(duration.count());
std::cout << " 平均帧率: " << fps << " Hz" << std::endl;
}
// 5. 断开连接
controller.Disconnect();
std::cout << "已断开连接。" << std::endl;
} catch (const std::exception& e) {
std::cerr << "异常: " << e.what() << std::endl;
return 1;
}
return 0;
}4.3 数据格式和单位
关节索引映射
| 手指 | 关节数量 | 索引范围 | 访问方式 |
|---|---|---|---|
| 拇指 | 5 | 0-4 | status.thumb[0-4] |
| 食指 | 4 | 5-8 | status.index[0-3] |
| 中指 | 4 | 9-12 | status.middle[0-3] |
| 无名指 | 4 | 13-16 | status.ring[0-3] |
| 小指 | 4 | 17-20 | status.pinky[0-3] |
数据单位和范围
- 数据类型:
uint16_t(无符号16位整数) - 数值范围:
0 - 4095(12位 ADC) - 物理含义: 关节传感器的电压值(经过模数转换)
- 数值对应:
0: 传感器最小值(手指完全伸直)2048: 传感器中间值4095: 传感器最大值(手指完全弯曲)
数据处理示例
cpp
// 归一化到 0.0 - 1.0 范围
inline double NormalizeJoint(uint16_t value) {
return static_cast<double>(value) / 4095.0;
}
// 映射到角度(需要校准)
inline double ADCToAngle(
uint16_t value,
uint16_t min_adc,
uint16_t max_adc,
double min_angle,
double max_angle
) {
double normalized = static_cast<double>(value - min_adc) /
(max_adc - min_adc);
return min_angle + normalized * (max_angle - min_angle);
}
// 使用示例
auto status = controller.Loop();
if (status) {
// 归一化拇指第一关节
double normalized = NormalizeJoint(status->thumb[0]);
std::cout << "归一化值: " << normalized << std::endl;
// 映射到角度(假设校准范围)
double angle = ADCToAngle(status->thumb[0], 500, 3500, 0.0, 90.0);
std::cout << "关节角度: " << angle << "°" << std::endl;
}4.4 高级用法示例
4.4.1 批量数据采集
cpp
#include <psi_glove_sdk/psi_glove.hpp>
#include <fstream>
#include <vector>
#include <thread>
#include <chrono>
int main() {
// 初始化
auto serial = std::make_unique<psi_glove::SerialInterface>("/dev/ttyACM0", 115200);
psi_glove::PSIGloveController controller(std::move(serial), 10);
controller.Connect();
// 采集 1000 帧数据
std::vector<std::vector<uint16_t>> data_buffer;
data_buffer.reserve(1000);
for (int i = 0; i < 1000; ++i) {
auto status = controller.Loop();
if (status) {
data_buffer.push_back(status->ToVector());
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
// 保存到 CSV
std::ofstream file("glove_data.csv");
// 表头
for (int i = 0; i < 21; ++i) {
file << "joint_" << i;
if (i < 20) file << ",";
}
file << "\n";
// 数据
for (const auto& row : data_buffer) {
for (size_t i = 0; i < row.size(); ++i) {
file << row[i];
if (i < row.size() - 1) file << ",";
}
file << "\n";
}
std::cout << "已保存 " << data_buffer.size() << " 帧数据到 glove_data.csv" << std::endl;
controller.Disconnect();
return 0;
}4.4.2 多手套同时使用
cpp
#include <psi_glove_sdk/psi_glove.hpp>
#include <iostream>
#include <thread>
#include <chrono>
int main() {
// 左手
auto left_serial = std::make_unique<psi_glove::SerialInterface>("/dev/ttyACM0", 115200);
psi_glove::PSIGloveController left_controller(std::move(left_serial), 10);
left_controller.Connect();
// 右手
auto right_serial = std::make_unique<psi_glove::SerialInterface>("/dev/ttyACM1", 115200);
psi_glove::PSIGloveController right_controller(std::move(right_serial), 10);
right_controller.Connect();
// 同时读取双手数据
for (int i = 0; i < 100; ++i) {
auto left_status = left_controller.Loop();
auto right_status = right_controller.Loop();
if (left_status && right_status) {
std::cout << "左手拇指: " << left_status->thumb[0]
<< ", 右手拇指: " << right_status->thumb[0] << std::endl;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
left_controller.Disconnect();
right_controller.Disconnect();
return 0;
}