【CrazySwarm】CrazySwarm代码解读-Crazyradio基类

目录


今天核心任务是梳理清楚 Crazyradio的构造过程和发送 packet的核心机理。

class Crazyradio: public ITransport, public USBDevice
{
public:
    enum Datarate
    {
        Datarate_250KPS = 0,
        Datarate_1MPS = 1,
        Datarate_2MPS = 2,
    };
    
    enum Power 
    {
        Power_M18DBM = 0,
        Power_M12DBM = 1,
        Power_M6DBM = 2,
        Power_0DBM = 3,
    };
}

从上述Crazyradio定义中可以看出,该类有两个基类ITransportUSBDevice,两个枚举变量DataratePower

  1. ITransport
    1. 一个抽象基类,定义了数据传输的接口Crazyradio 类需要实现这些接口来完成数据的收发操作。
  2. USBDevice
    1. 一个表示 USB 设备的基类,Crazyradio 作为一个 USB 设备,继承该类可以复用一些 USB 设备的通用功能,如 USB 连接、数据读写等。

接下来,对上述两个类进行深扒:

USBDevice.h

#pragma once
#include <stdint.h>  // 包含了uint8_t、uint16_t、uint32_t等

struct libusb_context;  				// USB设备的上下文
struct libusb_device_handle;	 // USB设备句柄
class USBDevice
{
protected:
    libusb_context* m_ctx;							// USB设备的上下文
    libusb_device_handle* m_hanlde;	 // USB设备句柄
    .
    float m_version; // USB设备的版本号
private:
    uint16_t m_idVendor;   // 存储USB设备的厂商ID
    uint16_t m_idProduct;  // 存储USB设备的产品ID
        
}

从上可知,USBDevice的核心变量包括:

  1. USB设备的上下文
  2. USB设备的句柄
  3. USB设备的版本号
  4. USB设备的厂商ID
  5. USB设备的产品ID
class USBDevice
{
public:
    USBDevice(uint16_t idVendor, uint16_t idProduct);
    virtual ~USBDevice();
protected:
    void open(uint32_t devid);  // 打开指定devid的USB设备
    void sendVendorSetup(
        uint8_t request,
        uint16_t value,
        uint16_t index,
        const unsigned char* data,
        uint16_t length); 			// 向 USB 设备发送控制请求
    static uint32_t numDevices(uint16_t idVendor,uint16_t idProduct);
}

[!NOTE]

  • 静态特性:由于该函数是静态的,这意味着你不需要创建 USBDevice 类的对象就可以直接调用它。静态成员函数属于类本身,而不是类的某个具体对象。可以通过类名直接调用,调用方式为 USBDevice::numDevices(idVendor, idProduct)
  • 优势体现:在程序的初始化阶段或者一些全局的操作中,可能还没有创建 USBDevice 对象,但又需要知道特定 USB 设备的数量,这时静态成员函数就非常有用。它提供了一种方便的方式来获取设备信息,而不需要额外创建对象,节省了资源和代码复杂度。

上述USBDevice类设计的细节来看的话,该类的作用是在系统的libusb基库的基础上向上封装了一些接口类。

设备的初始化、打开、配置等基础操作

USBDevice.cpp

#include "USBDevice.h"

#include <sstream>
#include <stdexcept>

#include <libusb-1.0/libusb.h>

C++ 标准库 -

C++ 标准库

  1. <sstream>库的简介:
    1. <sstream> 头文件提供了用于字符串流操作的类和函数
    2. 字符串流允许你像操作输入输出流(如 std::cinstd::cout)一样操作字符串
    3. 将字符串作为流进行处理,方便进行数据的格式化输入输出
    4. 常用于将不同类型的数据转换为字符串,或者从字符串中提取不同类型的数据
  2. <sstream>库的接口:
    1. std::istringstream
      1. 用于从字符串中读取数据,类似于 std::cin 从标准输入读取数据。
      2. 它可以将字符串解析为不同类型的数据,例如将字符串中的数字提取出来
    2. std::ostringstream
      1. 用于向字符串中写入数据,类似于 std::cout 向标准输出写入数据。
      2. 它可以将不同类型的数据格式化为字符串
    3. std::stringstream
      1. 既可以从字符串中读取数据,也可以向字符串中写入数据
      2. 结合了 std::istringstreamstd::ostringstream 的功能。

C++异常捕获(标准库stdexcept)

浅谈C++ 标准库中的异常 —— stdexcept类

【C++ 异常】深入探究C++的stdexcept类库

  1. <stdexcept> 库的简介:
    1. 定义了一系列用于异常处理的标准异常类。
  2. <stdexcept> 库的接口:
    1. std::runtime_error
      1. 表示运行时错误,通常用于表示在程序运行过程中发生的错误,例如文件打开失败、网络连接中断等。
    2. std::logic_error
      1. 表示逻辑错误,通常是由于程序逻辑上的错误导致的,例如传递给函数的参数不符合要求
    3. std::out_of_range
      1. 表示越界错误,常用于表示访问数组、容器等时索引超出了有效范围。
    4. std::invalid_argument
      1. 表示无效参数错误,当传递给函数的参数无效时抛出该异常。

从头文件可知:

class USBDevice
{
public:
    USBDevice(uint16_t idVendor, uint16_t idProduct)
    {
        libusb_init(&m_ctx); // 初始化usb的内容?
    }
    virtual ~USBDevice();
protected:
    // 打开指定devid的USB设备
    void open(uint32_t devid) 
    {
        libusb_get_device_list(NULL, &list);
        libusb_free_device_list(list, 1);
        libusb_open(found, &m_handle);  // 打开指定devid的USB设备并返回到句柄m_handle
        libusb_set_configuration(m_handle, 1);
    }
    // 向USB设备发送厂商自定义的设置请求
    void sendVendorSetup(
        uint8_t request,  // 控制请求的类型,不同请求有不同作用
        uint16_t value,   // 传递与请求相关的特定值
        uint16_t index,   // 指定请求的索引,比如接口号、端点号等
        const unsigned char* data,  // 指向无符号字符数组的常量指针
        uint16_t length); 			// 要发送的数据的长度 
    {
        int status = libusb_control_transfer(
            m_handle,  // usb设备句柄
            LIBUSB_REQUEST_TYPE_VENDOR,
            request,
            value,
            index,
            (unsigned char*)data,
            length,
            /*timeout*/ 1000); // 请求的超时时间
        if (status != LIBUSB_SUCCESS) 
        		throw std::runtime_error(libusb_error_name(status));
    }
    
    static uint32_t numDevices(uint16_t idVendor,uint16_t idProduct);
protected:
    libusb_context* m_ctx; 		//usb设备的上下文信息
    libusb_device_handle *m_handle;  // usb设备的句柄
}

​ 从上述具体函数实现可知,USBDevice类主要是封装了打开USB设备的open,核心是libusb_open,另一个就是通过USB 设备发送数据的sendVendorSetup,核心接口是libusb_control_transfer

ITransport

#include <stdint.h>
#include <fstream>

其头文件的头文件很简单,从后面可知,其似乎封装上了std::ofstreamm_file.

class ITransport
{
public:
    struct ACK{}__attribute__((packed));
protected:
    bool m_enableLogging;
    std::ofstream m_file;
}

这里有个出现频率很高的私有变量m_enableLogging,以及之前找很久的log文件:std::ofstream类型的m_file

除此之外,还有比较感兴趣的ACK

class ITransport
{
public:
    struct Ack
    {
        Ack(): ack(0), size(0){} //默认构造函数

        uint8_t ack:1;  		// 位域,使用1bit来存储ACK的数值,0:未确认,1:已确认
        uint8_t powerDet:1;
        uint8_t retry:4;  // 位域,使用4bit来存储retry的数值,传输失败后重传的次数
        uint8_t data[32]; // 存储与确认信息相关的数据

        uint8_t size;  // 记录 data 数组中实际有效的数据长度
    }__attribute__((packed));  // GCC 编译器的一个扩展属性,不对结构体进行字节对齐优化。

此处与上面看到的,Ack.ack似乎有一些关系,其实从ack.retry中可以直接读出来ACK重传的次数。

class ITransport
{
public:
    ITransport():m_enableLogging(false){}
    virtual ~ITransport() {}
    // 纯虚函数
    virtual void sendPacket(const uint8_t* data, uint32_t length, Ack& result) = 0;
    virtual void sendPacketNoAck(const uint8_t* data, uint32_t length) = 0;
    void enableLogging(bool enable);
    
protected:
    void logPacket(const uint8_t* data, uint32_t length);
    void logAck(const Ack& ack);
}

从上可以看出,该ITransport的核心作用是一个定义传输层抽象基类,通常用于为派生类提供统一的接口规范

ITransport::sendPacketITransport::sendPacketNoAck,这两个虚函数都是留给顶层派生类进行实现的,此处是CPP的一个知识点,包含纯虚函数的类为抽象基类,不能直接实例化,其派生类必须实现纯虚函数才能被实例化。

由于纯虚函数没有实现,接下来只需要关心其中三个有函数实现的内容就可以了。


void ITransport::enableLogging(bool enable)
{
    m_enableLogging = enable;
    if (m_enableLogging) {
        m_file.open("transport.log");
    } else {
        m_file.close();
    }
}

从上可知,如果使能了m_enableLogging,会在本文件所在目录内打开日志文件transport.log?但是我在项目中并没有见过该文件,此ITransport类似乎跟我之前写Log时遇到的文件不好处理的问题有些关系,后续可回头再看。

[!NOTE]

牢记,日志文件只有在m_enableLogging时才会创建。

// 用于记录发送的数据包信息到日志文件中
void ITransport::logPacket(
    const uint8_t* data, // 要发送的数据包数据的指针
    uint32_t length) // 数据包的长度
{
    m_file << "sendPacket: ";
    for (size_t i = 0; i < length; ++i) 
        m_file << std::hex << (int)data[i] << " ";
    
    if (length > 0) {
        uint8_t byte = data[0]; // 取出包头
        int port = ((byte >> 4) & 0xF); // 提取高4位作为端口号。
        int channel = ((byte >> 0) & 0x3); // 提取低2位作为通道号
        m_file << " (port: " << port << " channel: " << channel << ")";
    }
    m_file << std::endl;
}
// 日志信息示例
sendPacket: 12 34 56 78  (port: 1 channel: 2)
void ITransport::logAck(const Ack& ack)  // 将ack信息写到日志文件中
{
    m_file << "received: ";
    for (size_t i = 0; i < ack.size; ++i) {
        m_file << std::hex << (int)ack.data[i] << " ";
    }
    if (ack.size > 0) {
      uint8_t byte = ack.data[0];
      int port = ((byte >> 4) & 0xF);
      int channel = ((byte >> 0) & 0x3);
      m_file << " (port: " << port << " channel: " << channel << ")";
    }
    m_file << std::endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

后厂村路小狗蛋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值