Skip to content

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_glove

4.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_glove

4.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 数据格式和单位

关节索引映射

手指关节数量索引范围访问方式
拇指50-4status.thumb[0-4]
食指45-8status.index[0-3]
中指49-12status.middle[0-3]
无名指413-16status.ring[0-3]
小指417-20status.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;
}