QGC二次开发笔记 一:Mavlink生成和MockLink模拟收发通讯

da11

一、xml文件修改

文件位置:F:\QGC\mavlink\mavlink\message_definitions\v1.0\ardupilotmega.xml

修改内容:仿照原有格式,自定义ID236号 添加在226号之后,如下:

 <message id="226" name="RPM">
      <description>RPM sensor output.</description>
      <field type="float" name="rpm1">RPM Sensor1.</field>
      <field type="float" name="rpm2">RPM Sensor2.</field>
		
		<!--标记:参考CSDN文章-->
    </message><!--示例文件CSDN TEXT-->
	  <!--定义消息id为236,名称为“CSDN TEXT"-->
	  <message id="236" name="CSDN_TEST">
		  <!--对消息的描述,表明它是 CSDN TEXT。-->
		  <description>CSDN TEXT.</description>
		  <!--定义名为 "byte1" 的字段,类型为无符号8位整数(uint8_t),并提供了描述FRIST BYTE.-->
		  <field type="uint8_t" name="byte1">FRIST BYTE.</field>
		  <!--定义名为 "byte2" 的字段,类型为无符号8位整数(uint8_t),并提供了描述SECOND BYTE.-->
		  <field type="uint8_t" name="byte2">SECOND BYTE.</field>
      <!--定义了消息的结束标签。-->
	  </message> 

定义了byte1、byte2两个字节的数据,数据内容为所需传递的信息

二、MavLink文件夹内进行的操作与操作目的讲解

1.MavLink源码来源:https://github.com/mavlink/mavlink.git

2.MavLink作用:在QGC二次开发过程中调用MavLink生成器

使用方法:

        进入下载的MavLink的根文件夹内 F:\QGC\mavlink\mavlink,win+R输入CMD打开终端并输入 python mavgenerate.py启动MavLink生成器。(注意python 3.0版本)

 注:XML:F:/QGC/mavlink/mavlink/message_definitions/v1.0/ardupilotmega.xml

        Out:新建文件夹 用于存储新生成的MavLink文件 不建议直接覆盖MavLink文件夹中的原有MavLink文件

        Language:选择 C

        Protocol : 选择 2.0 

最后点击generate生成新的MavLink文件,存储在Out所选位置。

所生成的MavLink文件如下:F:\QGC\mavlink\OUT1

到此为止,需要在MavLink文件夹中进行的操作已全部结束,其目的就是利用mavgenerate.py更方便的生成我们所需要的MavLink文件,即上图内容。 

三、复制生成的MavLink文件到QGC的mavlink文件夹进行添加自定义

在上一步生成的MavLink文件中(路径:F:\QGC\mavlink\OUT1\ardupilotmega\mavlink_msg_csdn_test.h)

复制mavlink_msg_csdn_test.h到qgc源码文件夹的对应位置(路径:F:\QGC\qgroundcontrol\qgroundcontrol\libs\mavlink\include\mavlink\v2.0\ardupilotmega)

 之后修改qgc源码中mavlink的ardupilotmega.h文件中的三个地方,目的是通过mavlink通讯校验。

1、修改qgc源码中mavlink的ardupilotmege.h中的#define MAVLINK_MESSAGE_CRCS校验处,增加了236号的ID,如下图所示。

(从mavlink文件夹中的ardupilotmege.h中的#define MAVLINK_MESSAGE_CRCS校验处复制生成的236号ID校验码,粘贴到qgc源码中mavlink的ardupilotmege.h中的#define MAVLINK_MESSAGE_CRCS校验处)

 2.include mavlink_msg_csdn_test.h文件

在qgc源码中mavlink的ardupilotmege.h中#include mavlink_msg_csdn_test.h文件

 3.在qgc源码中mavlink的ardupilotmege.h中修改MAVLINK_MESSAGE_INFO:添加了MAVLINK_MESSAGE_INFO_CSDN_TEST

 4.在qgc源码中mavlink的ardupilotmege.h中修改MAVLINK_MESSAGE_NAMES:添加了{ "CSDN_TEST", 236 }

四、认识相关类以及理清功能逻辑

4.1QGC页面发送显示

文件:FlightDisplayView.qml

发送功能关键函数: activeVehicle.testSendToVehicle(protectTextField1.text,protectTextField2.text)

 //QML 中矩形对象 sendRect 的属性设置;发送部分设置
    Rectangle{
        //指定矩形对象的唯一标识符
        id:                             sendRect
        //将矩形对象的顶部边缘与其父级元素的顶部边缘对齐
        anchors.top:                    parent.top
        //设置矩形对象与其父级元素顶部的间距为 20 像素
        anchors.topMargin:              20
        //将矩形对象的水平中心与其父级元素的水平中心对齐
        anchors.horizontalCenter:       parent.horizontalCenter
        //设置矩形对象的高度为 col 元素的高度的 1.1 倍
        height:                         col.height*1.2
        //设置矩形对象的宽度为 col 元素的宽度的 1.1 倍
        width:                          col.width*1.2
        //设置矩形对象的圆角半径为 2
        radius:                         2
        //设置矩形对象的颜色为黑色
        color:                          "black"


        //定义了一个垂直布局的 Column 容器,用于包含多个子元素
        Column{
            id:                                     col
            //设置子元素之间的垂直间距为 4 像素
            spacing:                                4
            //将 Column 容器居中放置在其父级元素中心位置
            anchors.centerIn:                      parent
            //QGCLabel 组件,用于显示文本内容
            QGCLabel {
                //将 QGCLabel 组件水平居中放置在 Column 容器中
                anchors.horizontalCenter:           parent.horizontalCenter
                //设置 QGCLabel 组件显示的文本内容为 "Mavlink 发送测试!"
                text:                               "Mavlink 发送测试!"
                //设置字体大小为 12
                font.pointSize:                     12
                //设置文字为粗体显示
                font.bold:                          true
                //设置文字颜色为红色
                color:                              "red"
            }
            //定义了一个水平布局的 Row 容器:Row容器是一种布局容器,用于将多个子元素水平排列在一行上
            Row{
                //将 Row 容器的左边缘与其父级元素的左边缘对齐
                anchors.left:                       parent.left
                //将 Row 容器的右边缘与其父级元素的右边缘对齐
                anchors.right:                      parent.right
                //QGCLabel组件是一个用于显示文本标签的控件
                QGCLabel {
                   id:                               tipsLabel
                    //将 QGCLabel 组件的垂直中心与 protectTextField1 组件的垂直中心对齐
                    anchors.verticalCenter:          protectTextField1.verticalCenter
                    //设置 QGCLabel 组件显示的文本内容为 "第一个字节:"
                    text:                            qsTr("第一个字节:")
                    //设置字体大小为 11
                    font.pointSize:                  11
                    //设置文字为粗体显示
                    font.bold:                       true
                    //设置文字颜色为 "#B7FF4A":亮绿色
                    color:                           "#B7FF4A"
                    }
                //QGCTextField组件是一个用于接收用户输入的文本框控件。它具有各种属性和事件,用于设置文本框的外观和行为。
                QGCTextField {
                    id:                             protectTextField1
                    //设置宽度为父组件的宽度(parent.width)减去父组件的间距(parent.spacing)和tipsLabel的宽度(tipsLabel.width)
                    width:                          parent.width - parent.spacing - tipsLabel.width
                    //高度为tipsLabel高度的1.5倍
                    height:                         tipsLabel.implicitHeight*1.5
                    //置文本框中显示的默认文本为 "1"
                    text:                           "1"
                    //设置输入法提示,使用 Qt.ImhFormattedNumbersOnly 表示只允许格式化的数字输入
                    //InputMethodHints:               Qt.ImhFormattedNumbersOnly
                    //设置文本框初始化时是否获取焦点,默认为true,即初始化后自动获得焦点
                    focus:                          true
                    //事件处理程序:当编辑完成时触发此事件处理程序。在此处,如果输入的文本大于256,则将文本设置为256,并弹出一个警告对话框显示消息"输入必须小于等于256";如果文本小于0,则将文本设置为0,并弹出一个警告对话框显示消息"输入必须大于等于0"
                    onEditingFinished:{
                        if(text>256){
                            text = 256
                            mainWindow.showMessageDialog(qsTr("警告"),qsTr("输入必须小于等于256"))
                        }
                        else if(text < 0 ){
                            text = 0
                            mainWindow.showMessageDialog(qsTr("警告"),qsTr("输入必须大于等于0"))
                        }


                    }
                }
            }
            //Row容器是一种布局容器,用于将多个子元素水平排列在一行上
            Row{
                anchors.left:                       parent.left
                anchors.right:                      parent.right
                //QGCTextField组件是一个用于接收用户输入的文本框控件。它具有各种属性和事件,用于设置文本框的外观和行为。
                QGCLabel {
                   id:                               tipsLabel2
                    //将 QGCLabel 组件的垂直中心与 protectTextField1 组件的垂直中心对齐
                    anchors.verticalCenter:          protectTextField2.verticalCenter
                    //设置 QGCLabel 组件显示的文本内容为 "第二个字节:"
                    text:                            qsTr("第二个字节:")
                    //设置字体大小为 11
                    font.pointSize:                  11
                    //设置文字为粗体显示
                    font.bold:                       true
                    //设置文字颜色为 "#B7FF4A":亮绿色
                    color:                           "#B7FF4A"
                    }
                //QGCTextField组件是一个用于接收用户输入的文本框控件。它具有各种属性和事件,用于设置文本框的外观和行为。:用于保护距离的输入
                QGCTextField {
                    id:                             protectTextField2
                    //设置宽度为父组件的宽度(parent.width)减去父组件的间距(parent.spacing)和tipsLabel的宽度(tipsLabel.width)
                    width:                          parent.width - parent.spacing - tipsLabel2.width
                    //高度为tipsLabel高度的1.5倍
                    height:                         tipsLabel2.implicitHeight*1.5
                    //置文本框中显示的默认文本为 "1"
                    text:                           "1"
                    //设置输入法提示,使用 Qt.ImhFormattedNumbersOnly 表示只允许格式化的数字输入(用不了)

                    //InputMethodHints:               Qt.ImhFormattedNumbersOnly

                    //设置文本框初始化时是否获取焦点,默认为true,即初始化后自动获得焦点
                    focus:                          true
                    //事件处理程序:当编辑完成时触发此事件处理程序。在此处,如果输入的文本大于256,则将文本设置为256,并弹出一个警告对话框显示消息"输入必须小于等于256";如果文本小于0,则将文本设置为0,并弹出一个警告对话框显示消息"输入必须大于等于0"
                    onEditingFinished:{
                        if(text>256){
                            text = 256
                            mainWindow.showMessageDialog(qsTr("警告"),qsTr("输入必须小于等于256"))
                        }
                        else if(text < 0 ){
                            text = 0
                            mainWindow.showMessageDialog(qsTr("警告"),qsTr("输入必须大于等于0"))
                        }


                    }
                }
            }
            //QGCButton一种按钮控件,用于触发操作、提交表单、触发事件等
            QGCButton {
                id:                                 mavTestButton
                //设置按钮的背景圆角半径为高度的一半,使按钮呈现圆形的外观。
                backRadius:                         height/2
                //置按钮显示的文本内容为 "MAVLINK消息发送测试",这是按钮上显示的文字。
                text:                               qsTr("MAVLINK消息发送测试")
                //设置按钮是否可用的状态为 activeVehicle 的值,即只有当 activeVehicle 为 true 时按钮才可用。
                enabled:                             activeVehicle
                //当按钮被点击时触发的事件处理函数。在函数内部,首先检查 activeVehicle 是否为真,如果是,则调用 activeVehicle 的 testSendToVehicle 方法,传递 protectTextField1.text 和 protectTextField2.text 作为参数。
                onClicked:{
                    if(activeVehicle)
                        activeVehicle.testSendToVehicle(protectTextField1.text,protectTextField2.text)
                }
            }
        }

    }

4.2QGC接收页面显示

接收关键:        text:                           activeVehicle ? activeVehicle.rcvByte1:""

                          text:                           activeVehicle ? activeVehicle.rcvByte2:""

//接收显示
    Rectangle{
        //将矩形对象的顶部边缘与其父级元素的顶部边缘对齐
        anchors.top:                    sendRect.bottom
        //设置矩形对象与其父级元素顶部的间距为 20 像素
        anchors.topMargin:              20
        //将矩形对象的水平中心与其父级元素的水平中心对齐
        anchors.horizontalCenter:       parent.horizontalCenter
        //设置矩形对象的高度为 col 元素的高度的 1.2 倍
        height:                         colSendRect.height*1.4
        //设置矩形对象的宽度为 col 元素的宽度的 1.2 倍
        width:                          colSendRect.width*1.4
        //设置矩形对象的圆角半径为 2
        radius:                         2
        //设置矩形对象的颜色为黑色
        color:                          "black"

        Column{
            id:                         colSendRect
            spacing:                    4
            anchors.centerIn:           parent
            //QGCTextField组件是一个用于接收用户输入的文本框控件。它具有各种属性和事件,用于设置文本框的外观和行为。
            QGCLabel{
                anchors.horizontalCenter:       parent.horizontalCenter
                text:                           "MAVLink 接收测试!"
                font.pointSize:                 12
                font.bold:                      true
                color:                          "red"
            }
            //Row容器是一种布局容器,用于将多个子元素水平排列在一行上
            Row {
                id:                             row1
                anchors.horizontalCenter:       parent.horizontalCenter
                height:                         label1.height
                //QGCTextField组件是一个用于接收用户输入的文本框控件。它具有各种属性和事件,用于设置文本框的外观和行为。
                QGCLabel{
                    id:                         label1
                    anchors.verticalCenter:     parent.verticalCenter
                    text:                       qsTr("接收到的第一个字节 :")
                    font.pointSize:                 11
                    font.bold:                      true
                    color:                          "#B7FF4A"
                    //设置层级
                    //z:                              _panel.z + 4
                }

                //QGCTextField组件是一个用于接收用户输入的文本框控件。它具有各种属性和事件,用于设置文本框的外观和行为。
                QGCLabel{
                    anchors.verticalCenter:         parent.verticalCenter
                    //设置文本内容为 activeVehicle.rcvByte1 的值。如果 activeVehicle 存在且 rcvByte1 有值,文本内容将设置为 activeVehicle.rcvByte1;否则,文本内容将为空字符串。
                    text:                           activeVehicle ? activeVehicle.rcvByte1:"  "
                    font.pointSize:                  11
                    font.bold:                      true
                    color:                          "#B7FF4A"
                }
            }
            Row{
                anchors.left:                       row1.left
                height:                             label2.hight
                QGCLabel{
                    id:                             label2
                    anchors.verticalCenter:         parent.verticalCenter
                    text:                           qsTr("接收到的第二个字节 : ")
                    font.pointSize:                 11
                    font.bold:                      true
                    color:                          "#B7FF4A"
                }
                QGCLabel{
                    anchors.verticalCenter:         parent.verticalCenter
                    //设置文本内容为 activeVehicle.rcvByte1 的值。如果 activeVehicle 存在且 rcvByte1 有值,文本内容将设置为 activeVehicle.rcvByte1;否则,文本内容将为空字符串。
                    text:                           activeVehicle ? activeVehicle.rcvByte2:"  "
                    font.pointSize:                  11
                    font.bold:                      true
                    color:                          "#B7FF4A"
                }
            }
        }
    }

五、C++后端

5.1QGC发送

关键函数: activeVehicle.testSendToVehicle(protectTextField1.text,protectTextField2.text)

5.1.1        Vehicle.h头文件声明

                找到class Vehicle : public FactGroup,在public:类中添加一下内容:Q_INVOKABLE void testSendToVehicle(quint8 byte1,quint8 byte2);

class Vehicle : public FactGroup
{
    Q_OBJECT

public:
...
 Q_INVOKABLE void testSendToVehicle(quint8 byte1,quint8 byte2);

5.1.2        Vehicle.cc文件         

                QGC发送函数:void Vehicle::testSendToVehicle(quint8 byte1,quint8 byte2)

//QGC开发测试内容
    //范围解析运算符::  类::成员函数        用于在类的外部定义类的成员函数
    //定义了一个成员函数,用于QGC发送数据
void Vehicle::testSendToVehicle(quint8 byte1,quint8 byte2)
{
    // 定义一个 MAVLink 消息变量
    mavlink_message_t                       msg;
    // 定义一个 CSDN 测试消息变量
    mavlink_csdn_test_t                     csdn_test_t;

     // 将用户传入的数据填充到结构体中
    csdn_test_t.byte1 = byte1;
    csdn_test_t.byte2 = byte2;

    // 输出填充后的 byte1和byte2 数据,用于调试和确认
    qDebug() << "csdn_test_t.byte1 :" << csdn_test_t.byte1;
    qDebug() << "csdn_test_t.byte2 :" << csdn_test_t.byte2;

    // 使用 MAVLink 库中的函数将数据编码进消息中
    mavlink_msg_csdn_test_encode_chan(_mavlink->getSystemId(),
                                      _mavlink->getComponentId(),
                                      priorityLink()->mavlinkChannel(),
                                      &msg,
                                      &csdn_test_t);


    sendMessageOnLink(priorityLink(),msg);
}

5.2        MockLink接收

5.2.1        //MockLink.cc        

MockLink定义接受函数:void MockLink::_handleCSDNTest(const mavlink_message_t& msg)

    //MockLink接收函数
void MockLink::_handleCSDNTest(const mavlink_message_t& msg)
{
    mavlink_csdn_test_t csdn_test_t;

    mavlink_msg_csdn_test_decode(&msg, &csdn_test_t);

    qDebug() << "[MOCKLINK] Rcv byte1 : " <<csdn_test_t.byte1;
    qDebug() << "[MOCKLINK] Rcv byte2 : " <<csdn_test_t.byte2;
}

5.2.2       //MockLink.cc 

                调用MockLink接收函数:

                在void MockLink::_handleIncomingMavlinkBytes(const uint8_t* bytes, int cBytes)函数中添加一下内容:

//QGC二次开发:调用MockLink接收函数
        case MAVLINK_MSG_ID_CSDN_TEST:
            _handleCSDNTest(msg);
            break;

void MockLink::_handleIncomingMavlinkBytes(const uint8_t* bytes, int cBytes)
{
...
        //参照格式添加
        //QGC二次开发:调用MockLink接收函数
        case MAVLINK_MSG_ID_CSDN_TEST:
            _handleCSDNTest(msg);
            break;
...

5.2.3        MockLink接收函数声明

                //MockLink.h

                在class MockLink : public LinkInterface的public类中添加一下内容:

                //MockLinke接收函数声明
                void _handleCSDNTest(const mavlink_message_t& msg);

class MockLink : public LinkInterface
{
    Q_OBJECT

public:
...
//MockLinke接收函数声明
    void _handleCSDNTest(const mavlink_message_t& msg);
...

5.3        MockLink发送部分

            5.3.1 //MockLink.h

            MockLink发送声明

            在class MockLink : public LinkInterface的公共类public中添加一下内容:

                    //MockLink发送两字节数据的声明
                    void _sendCSDNTest(void);

                    uint8_t _testByte1;
                    uint8_t _testByte2;

class MockLink : public LinkInterface
{
    Q_OBJECT

public:
...
//MockLink发送两字节数据的声明
    void _sendCSDNTest(void);

    uint8_t _testByte1;
    uint8_t _testByte2;
...

         5.3.2        //MockLink.cc

                        MockLink数据初始化

                        在MockLink::MockLink(SharedLinkConfigurationPointer& config)中添加一下内容:

                    //MockLink发送数据函数 数据初始化
                    , _testByte1                            (0)
                    , _testByte2                            (0)

MockLink::MockLink(SharedLinkConfigurationPointer& config){

...
//MockLink发送数据函数 数据初始化
    , _testByte1                            (0)
    , _testByte2                            (0)
...
}

        5.3.3         //MockLink.cc

                        MockLink定义发送函数:

void MockLink::_sendCSDNTest(void)
{
        mavlink_message_t msg;
        _testByte1++;
        _testByte2--;
        mavlink_msg_csdn_test_pack_chan(_vehicleSystemId,       //车辆系统ID
                                        _vehicleComponentId,    //车辆组件ID
                                        _mavlinkChannel,        //Mavlink通道
                                        &msg,
                                        _testByte1,
                                        _testByte2
                                        );
        respondWithMavlinkMessage(msg);
}

        5.3.4        模拟发送所需定时器设置

                在void MockLink::_run1HzTasks(void)中添加:

                //借助定时器模拟发送,频率1Hz
                _sendCSDNTest();

void MockLink::_run1HzTasks(void)
{
...
//借助定时器模拟发送,频率1Hz
            _sendCSDNTest();
...
}

 5.4        QGC接收

       5.4.1                  //Vehicle.h

                        QGC接收函数声明:分为三部分,分别在public、signals、private中添加对应部分

在class Vehicle : public FactGroup中:

public:

//QGC二次开发 :新增QGC接收变量和函数声明
    Q_PROPERTY(quint8 rcvByte1                      READ rcvByte1               NOTIFY rcvByte1Changed)
    Q_PROPERTY(quint8 rcvByte2                      READ rcvByte2               NOTIFY rcvByte2Changed)

    quint8      rcvByte1            () {return _rcvByte1;}
    quint8      rcvByte2            () {return _rcvByte2;}

signals:

         void    rcvByte1Changed             (quint8 rcvByte1);
         void    rcvByte2Changed             (quint8 rcvByte2);

privates:

    //QGC二次开发 :新增QGC接收变量和函数声明
    void _handleCSDNTestRcv(mavlink_message_t& message);

    quint8          _rcvByte1;
    quint8          _rcvByte2;

class Vehicle : public FactGroup
{
    Q_OBJECT

public:
...
//QGC二次开发 :新增QGC接收变量和函数声明
    Q_PROPERTY(quint8 rcvByte1                      READ rcvByte1               NOTIFY rcvByte1Changed)
    Q_PROPERTY(quint8 rcvByte2                      READ rcvByte2               NOTIFY rcvByte2Changed)

    quint8      rcvByte1            () {return _rcvByte1;}
    quint8      rcvByte2            () {return _rcvByte2;}
...
signals:
...
    void    rcvByte1Changed             (quint8 rcvByte1);
    void    rcvByte2Changed             (quint8 rcvByte2);
...
private:
...
//QGC二次开发 :新增QGC接收变量和函数声明
    void _handleCSDNTestRcv(mavlink_message_t& message);

    quint8          _rcvByte1;
    quint8          _rcvByte2;
...
}

        5.4.2        QGC接收函数定义

//QGC接收
void Vehicle::_handleCSDNTestRcv(mavlink_message_t& message)
{
    mavlink_csdn_test_t csdn_test_t;
    mavlink_msg_csdn_test_decode(&message, &csdn_test_t);

    _rcvByte1 = csdn_test_t.byte1;
    _rcvByte2 = csdn_test_t.byte2;

    //触发信号,在QML前端中接收
    emit rcvByte1Changed(_rcvByte1);
    emit rcvByte2Changed(_rcvByte2);
}

        5.4.3        调用QGC接收函数

在void Vehicle::_mavlinkMessageReceived(LinkInterface* link, mavlink_message_t message)
中的switch (message.msgid)中添加以下内容:

            //QGC二次开发 修改 调用QGC接收函数
            case MAVLINK_MSG_ID_CSDN_TEST:
            _handleCSDNTestRcv(message);
            break;

void Vehicle::_mavlinkMessageReceived(LinkInterface* link, mavlink_message_t message)
{
...
switch (message.msgid) {
...
//QGC二次开发 修改 调用QGC接收函数
    case MAVLINK_MSG_ID_CSDN_TEST:
        _handleCSDNTestRcv(message);
        break;
...
}

        5.4.4        QGC接收函数的变量初始化

        在Vehicle::Vehicle(LinkInterface*             link,
                 ...
                 JoystickManager*           joystickManager)
    : FactGroup(...)中添加以下内容:

                    //QGC接收变量初始化
                    , _rcvByte1(0)
                    , _rcvByte2(0)

Vehicle::Vehicle(LinkInterface*             link,
                 int                        vehicleId,
                 int                        defaultComponentId,
                 MAV_AUTOPILOT              firmwareType,
                 MAV_TYPE                   vehicleType,
                 FirmwarePluginManager*     firmwarePluginManager,
                 JoystickManager*           joystickManager)
    : FactGroup(_vehicleUIUpdateRateMSecs, ":/json/Vehicle/VehicleFact.json")
...
    //QGC接收变量初始化
    , _rcvByte1(0)
    , _rcvByte2(0)
...

                 

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值