163浏览
查看: 163|回复: 1

[K10项目分享] MQTT Plus用户库:对Mind+的MQTT功能进行增强

[复制链接]
本帖最后由 神一样的老师 于 2025-1-9 07:20 编辑

引言Mind+的MQTT模块接入华为云是因为MQTT模块不能设置clientid,它是随机生成的,而华为云的clientid和用户名、密码构成加密三元组,随便设置clientid是不行的。所以需要找到一种办法能够设置clientid。我提出用手工编写代码的方式实现华为云的接入。这种方法虽然验证了我的想法,但是不是一个很好的方法,因为每次积木生成代码之后都要改,比较麻烦。今天介绍一个我编写的用户库MQTT Plus,它可以创建两个积木:设置Clientid的命令块和发送保留消息的命令块,可以增强官方MQTT模块的能力。后面,也会继续对这个用户库进行增强。
MQTT Plus用户库:对Mind+的MQTT功能进行增强图1
​​
Mind+ MQTT功能实现的分析通过阅读Mind+自带的Arduino库的代码,可以看到MQTT相关的功能存在在DFRobot_Iot类和PubSubClient类中。
DFRobot_Iot类主要负责WiFi联网和MQTT服务器的连接工作。MQTT初始化积木调用init函数,其中又调用SetConfig函数,此时将clientid赋给DFRobot对象的_clientid成员。MQTT连接的积木调用connect函数,它改变状态机的状态。在状态机内部实现了连接MQTT服务器的功能。要实现对MQTT的clientid的修改,就需要再SetConfig函数之后,而在connect之前完成clientid的修改,所以我增加的MQTT修改clientid的积木需要放在这两个系统提供的积木中间。

  1. /*alculate the connection username and password, etc.*/
  2. void DFRobot_Iot::setConfig(){
  3.     if(this->_UseServer == ONENET){
  4.         String tempSERVER = this->_MQTTSERVER;
  5.         uint8_t len = tempSERVER.length();
  6.         if(this->_mqttServer == NULL){
  7.             this->_mqttServer = (char *) malloc(len);
  8.         }
  9.         strcpy(this->_mqttServer,tempSERVER.c_str());
  10.         String tempID = this->_DeviceID;
  11.         len = tempID.length();
  12.         if(len == 0)
  13.         {
  14.             this->_clientId = "";
  15.         }
  16.         else{
  17.             if(this->_clientId == NULL){
  18.                 this->_clientId = (char *) malloc(len);
  19.             }
  20.             strcpy(this->_clientId,tempID.c_str());
  21.         }
  22.         String tempName = this->_ProductID;
  23.         len = tempName.length();
  24.         this->_username = (char * )malloc(len);
  25.         strcpy(this->_username,tempName.c_str());
  26.    
  27.         String tempPass = this->_ApiKey;
  28.         if(this->_password == NULL){
  29.             this->_password = (char *) malloc(tempPass.length());
  30.         }
  31.         strcpy(this->_password,tempPass.c_str());
  32.     }else if(this->_UseServer == ALIYUN){
  33.         String tempSERVER = (this->_ProductKey + "." + this->_MQTTSERVER);
  34.         uint8_t len = tempSERVER.length();
  35.         uint16_t timestamp = 49;
  36.         if(this->_mqttServer == NULL){
  37.             this->_mqttServer = (char *) malloc(len);
  38.         }
  39.         strcpy(this->_mqttServer,tempSERVER.c_str());
  40.         String tempID = (this->_ClientId +
  41.                          "|securemode=3"+
  42.                          ",signmethod=" + "hmacsha1"+
  43.                          ",timestamp="+(String)timestamp+"|");
  44.         len = tempID.length();
  45.         if(this->_clientId == NULL){
  46.             this->_clientId = (char *) malloc(len);
  47.         }
  48.         strcpy(this->_clientId,tempID.c_str());
  49.         String Data = ("clientId" + this->_ClientId +
  50.                          "deviceName" + this->_DeviceName +
  51.                          "productKey" + this->_ProductKey +
  52.                          "timestamp" + (String)timestamp);
  53.         BYTE tempPassWord[20];
  54.         char tempSecret[this->_DeviceSecret.length()];
  55.         char tempData[Data.length()];
  56.         String tempName = (this->_DeviceName + "&" + this->_ProductKey);
  57.         len = tempName.length();
  58.         this->_username = (char * )malloc(len);
  59.         strcpy(this->_username,tempName.c_str());
  60.    
  61.         strcpy(tempData,Data.c_str());
  62.         strcpy(tempSecret,this->_DeviceSecret.c_str());
  63.         MyHmac_Sha1.HMAC_SHA1((BYTE * )tempData,Data.length(),(BYTE * )tempSecret,this->_DeviceSecret.length(),tempPassWord);
  64.         String tempPass = byteToHexStr(tempPassWord,sizeof(tempPassWord));
  65.         if(this->_password == NULL){
  66.             this->_password = (char *) malloc(tempPass.length());
  67.         }
  68.         strcpy(this->_password,tempPass.c_str());
  69.     }else if(this->_UseServer == ONENET_NEW){
  70.         String tempSERVER = this->_MQTTSERVER;
  71.         uint8_t len = tempSERVER.length();
  72.         if(this->_mqttServer == NULL){
  73.             this->_mqttServer = (char *) malloc(len);
  74.         }
  75.         strcpy(this->_mqttServer,tempSERVER.c_str());
  76.    
  77.         String tempID = this->_DeviceID;
  78.         len = tempID.length();
  79.         if(this->_clientId == NULL){
  80.             this->_clientId = (char *) malloc(len);
  81.         }
  82.         strcpy(this->_clientId,tempID.c_str());
  83.         String tempName = this->_ProductID;
  84.         len = tempName.length();
  85.         this->_username = (char * )malloc(len);
  86.         strcpy(this->_username,tempName.c_str());
  87.         char authorization[128];
  88.         String pid = this->_ProductID;
  89.         pid += "/devices/";
  90.         pid += this->_DeviceID;
  91.         char buf1[pid.length()+1];
  92.         pid.toCharArray(buf1, pid.length()+1);
  93.         char buf2[this->_ApiKey.length()+1];
  94.         this->_ApiKey.toCharArray(buf2, this->_ApiKey.length()+1);
  95.         token.TOKEN_Authorization("2018-10-31",buf1,1767077900,buf2,authorization,128);
  96.         String tempPass = String(authorization);
  97.         if(this->_password == NULL){
  98.             this->_password = (char *) malloc(tempPass.length());
  99.         }
  100.         strcpy(this->_password,tempPass.c_str());
  101.     }
  102. }
复制代码
  1.        switch(currentMqttStatus){
  2.             case iotStatusProtocol::MqttConnectStart:
  3.                 mqttPt->iotDebug.message = "Start connecting mqtt";
  4.                 mqttPt->iotDebug.error = 0;
  5.                 currentMqttStatus = iotStatusProtocol::MqttConnecting;
  6.                 executeCallbackTask();
  7.                 timeOut = millis();
  8.                 while(!client.connect(mqttPt->_clientId,mqttPt->_username,mqttPt->_password)) {
  9.                     if(millis() - timeOut > 3000)
  10.                         break;
  11.                     delay(10);
  12.                 }
  13.                 subscribe();
复制代码
PubSubClient类主要负责连接MQTT云,并完成发布和订阅工作。它其实提供了一个publish函数,第3个参数就是retain。只是这个函数没有对应的积木。
  1. boolean PubSubClient::publish(const char* topic, const char* payload, boolean retained) {
  2.     return publish(topic,(const uint8_t*)payload,strlen(payload),retained);
  3. }
复制代码
好在PubSub类有个全局变量client,所以可以在我的用户库里面直接调用client.publish实现发布保留消息的功能。
  1. PubSubClient client(espClient);
  2. WiFiClient espClient;
复制代码
功能增强对Clientid的支持

在setClientId函数中实现了设置Clientid功能,采用的方法是直接修改了DFRobot对象的_clientid成员。

  1. /// @brief 设置MQTT的Clientid
  2. /// @param myIot
  3. /// @param clientID
  4. void setClientId(DFRobot_Iot * myIot, String clientID)
  5. {
  6.    int len = clientID.length();
  7.    printf("len = %d\r\n", len);
  8.    if(len == 0)
  9.    {
  10.       return;
  11.    }
  12.    if(myIot == NULL)
  13.    {
  14.       return;
  15.    }
  16.    printf("clientid = %s\r\n", clientID.c_str());
  17.    // if(myIot->_clientId != NULL)
  18.    // {
  19.    //    free(myIot->_clientId);
  20.    //    myIot->_clientId = NULL;
  21.    // }
  22.    myIot->_clientId = (char *) malloc(len + 1);
  23.    if(!myIot->_clientId)
  24.       return;
  25.    strcpy(myIot->_clientId, clientID.c_str());
  26.    return;
  27. }
复制代码
对保留消息的支持

在sendRetainedMessage函数中实现发送保留消息,调用了官方库函数的client.publish,把第3个参数设置成1即可。

  1. /// @brief MQTT 发送保留消息
  2. /// @param myIot DFRobot_Iot实例
  3. /// @param topic 主题
  4. /// @param message 消息内容
  5. /// @param qos QoS
  6. void sendRetainedMessage(DFRobot_Iot * myIot, String topic, String message, int qos)
  7. {
  8.    if(myIot == NULL)
  9.    {
  10.       return;
  11.    }
  12.     if(!myIot->wifiStatus() || !myIot->connected())
  13.         return;
  14.     client.setPublishQos(qos > 0 ? MQTTQOS1: MQTTQOS0);
  15.     client.publish(topic.c_str(), message.c_str(), 1);
  16.     delay(250);
  17.     return;
  18. }
复制代码

用户库的编写

完整的代码参见:ext-mqtt-plus: Mind+的用户扩展库,增强了MQTT的能力:https://gitee.com/zealsoft/ext-mqtt-plus

有关用户库编写的基本规则,可以参考:Mind+软件用户扩展库详细教程 - Mind+教程(https://mindplus.dfrobot.com.cn/ext-api)。这里仅谈谈我的具体实现。

基本结构

用户库的基本结构如下图所示。其中mpext文件是导出结果,如果不想修改代码,直接安装库,选这个文件即可。

MQTT Plus用户库:对Mind+的MQTT功能进行增强图2

config.json

config.json文件是库的基本配置文件,如果修改了代码,就要导入这个文件,而不是mpext文件。

文件的主要内容如下:

  1. {
  2.   "name": {
  3.     "zh-cn": "MQTT Plus",
  4.     "en": "MQTT Plus"
  5.   },
  6.   "description": {
  7.     "zh-cn": "增强MQTT的能力。",
  8.     "en": "Enhance the capabilities of MQTT"
  9.   },
  10.   "author": "Hai Li",
  11.   "email": "zealsoft@gmail.com",
  12.   "license": "MIT",
  13.   "isBoard": false,
  14.   "id": "mqtt_plus",
  15.   "platform": ["win", "mac", "web"],
  16. "version": "0.1.0",
  17.   "asset": {
  18.     "arduinoC": {
  19.       "dir": "arduinoC/",
  20.       
  21.       "board": [
  22.         "esp32s3bit"
  23.       ],
  24.       "main": "main.ts"
  25.     }
  26.   }
  27. }
复制代码
main.ts

main.ts是定义每个积木块的,是用户库的核心。

  1. //% color="#007FFF" iconWidth=40 iconHeight=40
  2. namespace mqtt_plus {
  3.   //% block="MQTT set ClientId [CLIENTID]"  blockType="command"
  4.   //% CLIENTID.shadow="string"   CLIENTID.defl="Undefined"
  5.   export function setClientId(parameter: any, block: any) {
  6.     let cid = parameter.CLIENTID.code;
  7.     Generator.addInclude("includeMqttPlus", "#include <mqtt_plus.h>");
  8.     // Generator.addObject("IotObject", "DFRobot_Iot",`myIot;`);
  9.     Generator.addCode(`setClientId(&myIot, ${cid});`);
  10.   }
  11.   //% block="MQTT Send retained message [DATA] to the topic [TOPIC] with QoS [QOS]" blockType="command"
  12.   //% DATA.shadow="string"   DATA.defl="Hello"
  13.   //% TOPIC.shadow="string"   TOPIC.defl="topic/a"
  14.   //% QOS.shadow="dropdown"  QOS.options="QOS"
  15.   export function sendRetainedMsg(parameter: any, block: any) {
  16.     let msg = parameter.DATA.code;
  17.     let topic = parameter.TOPIC.code;
  18.     let qos = parameter.QOS.code;
  19.     Generator.addInclude("includeMqttPlus", "#include <mqtt_plus.h>");
  20.     Generator.addCode(`sendRetainedMessage(&myIot, ${topic}, ${msg}, ${qos});`);
  21.   }
  22. }
复制代码

libraries

此文件夹是放置生成代码需要调用的arduino库文件。注意:不要讲C++文件直接放在这个目录,必须先建一个子目录(名字任意),然后把代码放到子目录中,否则会出现ardiuno builder找不到文件的问题。


示例程序

巴法云

巴法云登录时设置clientid会比较简单。巴法云支持保留消息。示例文件bemfatest.mp,运行例子前请设置自己的WiFi密码和巴法云的Clientid。

MQTT Plus用户库:对Mind+的MQTT功能进行增强图3

使用MQTTX订阅device004主题,可以看到办法云转发的消息都带有Retained字样。

MQTT Plus用户库:对Mind+的MQTT功能进行增强图4

华为云

华为云登录时必须要设置clientid,否则无法成功。华为云不支持保留消息,仅当作普通消息处理。示例文件huaweitest.mp,运行例子前请设置自己的WiFi密码和华为云的接入参数Clientid。

从华为云的控制台可以看到客户端已经成功登录,并上传数据。

结语

这是我第一次为Mind+写用户模块,感觉还比较容易。不过写用户模块,要求开发者熟悉C++语言,要求还是比写Scratch高很多。


hnyzcj  版主

发表于 昨天 12:28

果然是神一样的老师
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4 备案 沪公网安备31011502402448

© 2013-2025 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail