ESP32(Arduino)通过PubSubClient连接到阿里云MQTT


一、阿里云微服务MQTT的购买与配置

请参考以下链接
玩转MQTT-阿里云之MQTT使用(上)
请一步步按照这两个链接来完成配置,每一步都很重要,尤其是创建TopicGroup ,这两个的配置很重要,后面Java客户端和esp32客户端的都要输入这两个参数。

二、查看阿里云官方DEMO

如果你已经完成了阿里云的MQTT配置,那接下来就可以查看阿里云的官方demo
阿里云开发手册:阿里云微服务MQTT DEMO工程
我在这里以Java的实现为例:
阿里云微服务MQTT_java版单独使用 MQTT 消息收发示例

public class MQ4IoTSendMessageToMQ4IoTUseSignatureMode {
    
    
    public static void main(String[] args) throws Exception {
    
    
        /**
         * MQ4IOT 实例 ID,购买后控制台获取
         */
        String instanceId = "XXXXX";
        /**
         * 接入点地址,购买 MQ4IOT 实例,且配置完成后即可获取,接入点地址必须填写分配的域名,不得使用 IP 地址直接连接,否则可能会导致客户端异常。
         */
        String endPoint = "XXXXX.mqtt.aliyuncs.com";
        /**
         * 账号 accesskey,从账号系统控制台获取
         */
        String accessKey = "XXXXX";
        /**
         * 账号 secretKey,从账号系统控制台获取,仅在Signature鉴权模式下需要设置
         */
        String secretKey = "XXXXX";
        /**
         * MQ4IOT clientId,由业务系统分配,需要保证每个 tcp 连接都不一样,保证全局唯一,如果不同的客户端对象(tcp 连接)使用了相同的 clientId 会导致连接异常断开。
         * clientId 由两部分组成,格式为 GroupID@@@DeviceId,其中 groupId 在 MQ4IOT 控制台申请,DeviceId 由业务方自己设置,clientId 总长度不得超过64个字符。
         */
        String clientId = "GID_XXXXX@@@XXXXX";
        /**
         * MQ4IOT 消息的一级 topic,需要在控制台申请才能使用。
         * 如果使用了没有申请或者没有被授权的 topic 会导致鉴权失败,服务端会断开客户端连接。
         */
        final String parentTopic = "XXXXX";
        /**
         * MQ4IOT支持子级 topic,用来做自定义的过滤,此处为示意,可以填写任何字符串,具体参考https://help.aliyun.com/document_detail/42420.html?spm=a2c4g.11186623.6.544.1ea529cfAO5zV3
         * 需要注意的是,完整的 topic 长度不得超过128个字符。
         */
        final String mq4IotTopic = parentTopic + "/" + "testMq4Iot";
        /**
         * QoS参数代表传输质量,可选0,1,2,根据实际需求合理设置,具体参考 https://help.aliyun.com/document_detail/42420.html?spm=a2c4g.11186623.6.544.1ea529cfAO5zV3
         */
        final int qosLevel = 0;
        ......
    }

上面的instanceId,endPoint参数可以在阿里云控制台创建MQTT实例后看到,而accessKey,secretKey则是在用户的RAM控制访问中获得,parentTopic则是在控制台的Topic管理手动创建后才能使用

①在控制台的实例详情获得instanceId,endPoint:

在这里插入图片描述

②在用户的RAM访问控制中新建accessKey和secretKey

按照第一步的阿里云MQTT那个博客的步骤新建就行了,值得注意的是,如果我们在创建的时候选择只通过程序访问就不用输入电话号码的了;创建好了要及时保存accessKey和secretKey,退出控制台后这些信息就会消失!!!**
在这里插入图片描述

③获得clientId

clientId的格式为 GroupID@@@DeviceId,GroupID可在Group管理中新建
在这里插入图片描述

④parentTopic可在Topic管理中找到

在这里插入图片描述

三、Arduino环境下ESP32连接MQTT

1.引入PubSubClient库

可参照下面教程安装此库:
MQTT之 PubSubClient 库
不过安装完了还不是能立刻用!!!还有很重要的事情!!!
还要修改PubSubClient.h文件里面的MQTT_MAX_PACKET_SIZE和MQTT_KEEPALIVE,重要的事情说三遍
MQTT_MAX_PACKET_SIZE的值改成1024,MQTT_KEEPALIVE的值改成60
MQTT_MAX_PACKET_SIZE的值改成1024,MQTT_KEEPALIVE的值改成60
MQTT_MAX_PACKET_SIZE的值改成1024,MQTT_KEEPALIVE的值改成60
否则会连不上阿里云的MQTT
在这里插入图片描述

2.获得MQTT的客户端连接的签名鉴权

官方手册链接:签名鉴权模式
阿里云在官方手册上是这样写的:
在这里插入图片描述
也就是说用户登录到MQTT的用户名和密码是经过特殊处理的,Username的格式很简单,复杂的是Password,这个Password是经过HMAC-SHA1加密处理的,然后经过Base64编码得到,在官方给的Java Demo也能看出UserName和Password确实是经过处理的:
在这里插入图片描述
除了在代码中实现,也可以在控制台查看数据的加解密结果:
在这里插入图片描述

那问题来了,这些数据处理如何在Arduino环境下处理呢,总不能改一次clientID就在网页版看一下加密的结果吧,这也太繁琐了,而且不适合商用,那我们怎么在在Arduino里实现HMAC-SHA1和Base64呢? 一般情况下我们会想在官方给的C/C++例程中看看是如何处理数据的,我也是这样想的,但我下载了官方手册看到的是官方引入了C语言版的OpenSSL库,这个可是很难在Arduino环境下移植的啊,于是就在网上搜索C语言实现HMAC-SHA1,果然皇天不负有心人,在一个大佬的博客里找到了:
HMAC-SHA1C/C++ HmacSha1加密算法 Base64处理
不过这位大佬写的C语言版的HMAC-SHA1C有点儿小乱,而且没有给C语言版的Base64处理例程,我就给排版了一下,并且去掉了一些函数,把集成好的数据放在了csha1.h/csha1.c代码中,等下可以在下载链接中获得完整的Arduino工程和在Visual Studio运行环境下的HmacSha1示例工程。

3.Arduino主代码:

#include <WiFi.h>
#include <PubSubClient.h>
#include "csha1.h"
#include "cbase64.h"

#define BUILTIN_LED 23

const char* ssid = "wifi-ssid";
const char* password = "wifi-password";
const char* mqtt_server = "mqtt-cn-xxxxxxxxxxx.mqtt.aliyuncs.com"; // 使用HIVEMQ 的信息中转服务
const char* TOPIC = "my_mqtt/testMq4Iot";                     // 订阅信息主题
const char* client_id = "GID_my_mqtt@@@esp32_my";                   // 标识当前设备的客户端编号

/**
* MQ4IOT 实例 ID,购买后控制台获取
*/
String instanceId = "mqtt-cn-xxxxxxxxxxx";
/**
 * 账号 accesskey,从账号系统控制台获取
 */
String accessKey = "xxxxxxxxxxxxxxxxxxxxxxxx";
/**
 * 账号 secretKey,从账号系统控制台获取,仅在Signature鉴权模式下需要设置
 */
String secretKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

const String get_password(const char* data_text,const String secretKey);
const String passWord = get_password(client_id,secretKey);

WiFiClient espClient;             // 定义wifiClient实例
PubSubClient client(espClient);   // 定义PubSubClient的实例
long lastMsg = 0;           // 记录上一次发送信息的时长

void setup() {
    
    
  pinMode(BUILTIN_LED, OUTPUT);    // 定义板载LED灯为输出方式
  Serial.begin(115200); 
  setup_wifi();             //执行Wifi初始化,下文有具体描述
  client.setServer(mqtt_server, 1883);    //设定MQTT服务器与使用的端口,1883是默认的MQTT端口
  client.setCallback(callback);   //设定回调方式,当ESP8266收到订阅消息时会调用此方法
}

const String get_password(const char* data_text,const String secretKey){
    
    
    unsigned char out_bytes[50];
    int secretKeyLength, data_length;
    char base64Char[50]; //注意长度要给够
    int hmac_len = 0;

    secretKeyLength = strlen(secretKey.c_str());
    data_length = strlen(data_text);

    memset(out_bytes, '\0', sizeof(out_bytes));   
    hmac_len = hmac_sha(secretKey.c_str(), secretKeyLength, data_text, data_length, out_bytes, 20);

    memset(base64Char, '\0', sizeof(base64Char));
    cbase64_encode((const char*)out_bytes, hmac_len, base64Char);
    //printf("%s, based64_encoded_len = %d\r\n", base64Char, strlen(base64Char));
    const String ret_data(base64Char);
    return ret_data;
}

void setup_wifi() {
    
    

  delay(10);
  // 板子通电后要启动,稍微等待一下让板子点亮
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    
    
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void callback(char* topic, byte* payload, unsigned int length) {
    
    
  Serial.print("Message arrived [");
  Serial.print(topic);   // 打印主题信息
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    
    
    Serial.print((char)payload[i]); // 打印主题内容
  }
  Serial.println();

  if ((char)payload[0] == '1') {
    
    
    digitalWrite(BUILTIN_LED, HIGH);   // 亮灯
  } else {
    
    
    digitalWrite(BUILTIN_LED, LOW);   // 熄灯
  }
}

void reconnect() {
    
    
  while (!client.connected()) {
    
    
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect   (String("Signature|")+accessKey+"|"+instanceId).c_str();  "Signature|LTAI4GBNhdf2xLp8Wb9jVsJP|mqtt-cn-09k1y2eej04"
    if (client.connect(client_id, (String("Signature|")+accessKey+"|"+instanceId).c_str(), passWord.c_str())) {
    
    
      Serial.println("connected !!!!!");
      // 连接成功时订阅主题
      client.subscribe(TOPIC);
    } else {
    
    
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void loop() {
    
    

  if (!client.connected()) {
    
    
    reconnect();
  }
  client.loop();

  unsigned char result[1024];
  memset(result,'\0',sizeof(result));
  while(Serial.available())//判断串口缓冲器是否有数据装入
  {
    
    
    Serial.readBytes(result,1024);
    client.publish("my_mqtt/testMq4Iot",(const char*)result);
  }
  
}

代码中获得password签名结果的代码使用的是get_password()函数,函数内通过调用csha1.h里面的HMAC-SHA1加密处理从而得到结果,然后经过base64处理后获取密码签名值,虽然Arduino有自带的Base64库,但考虑到以后可能会移植到其他平台,还是从网上找了个能用的C语言版的Base64函数如下面的代码所示,不过由于csha1.h/csha1.c文件太长了,贴出来影响阅读,就不放出来了,等下大家可以从下载链接中获取。
cbase64.h

#pragma once

#ifndef cbase64_h
#define cbase64_h

#include <stdio.h>

#if __cplusplus
extern "C" {
    
    
#endif

    char* cbase64_encode(const char* buf, const long size, char* base64Char);
    char* cbase64_decode(const char* base64Char, const long base64CharSize, char* originChar, long originCharSize);

#if __cplusplus
}
#endif

#endif /* base64_h */

cbase64.c

#include "cbase64.h"

#include <stdio.h>
#include <stdlib.h>

static const char* ALPHA_BASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";


char* cbase64_encode(const char* buf, const long size, char* base64Char) {
    
    
    int a = 0;
    int i = 0;
    while (i < size) {
    
    
        char b0 = buf[i++];
        char b1 = (i < size) ? buf[i++] : 0;
        char b2 = (i < size) ? buf[i++] : 0;

        int int63 = 0x3F; //  00111111
        int int255 = 0xFF; // 11111111
        base64Char[a++] = ALPHA_BASE[(b0 >> 2) & int63];
        base64Char[a++] = ALPHA_BASE[((b0 << 4) | ((b1 & int255) >> 4)) & int63];
        base64Char[a++] = ALPHA_BASE[((b1 << 2) | ((b2 & int255) >> 6)) & int63];
        base64Char[a++] = ALPHA_BASE[b2 & int63];
    }
    switch (size % 3) {
    
    
    case 1:
        base64Char[--a] = '=';
    case 2:
        base64Char[--a] = '=';
    }
    return base64Char;
}

char* cbase64_decode(const char* base64Char, const long base64CharSize, char* originChar, long originCharSize) {
    
    
    int toInt[128] = {
    
     -1 };
    for (int i = 0; i < 64; i++) {
    
    
        toInt[ALPHA_BASE[i]] = i;
    }
    int int255 = 0xFF;
    int index = 0;
    for (int i = 0; i < base64CharSize; i += 4) {
    
    
        int c0 = toInt[base64Char[i]];
        int c1 = toInt[base64Char[i + 1]];
        originChar[index++] = (((c0 << 2) | (c1 >> 4)) & int255);
        if (index >= originCharSize) {
    
    
            return originChar;
        }
        int c2 = toInt[base64Char[i + 2]];
        originChar[index++] = (((c1 << 4) | (c2 >> 2)) & int255);
        if (index >= originCharSize) {
    
    
            return originChar;
        }
        int c3 = toInt[base64Char[i + 3]];
        originChar[index++] = (((c2 << 6) | c3) & int255);
    }
    return originChar;
}

四、工程下载链接

1.【免积分】Arduino完整工程

[免积分]Arduino连上阿里云MQTT示例工程

2.【免积分】C/C++实现HMAC-SHA1和base64编码

【免积分】C/C++实现HMAC-SHA1和base64编码


# 参考链接: C/C++ Java HmacSha1加密算法 Base64处理 https://www.cnblogs.com/Bachelor/archive/2013/01/22/2870180.html

猜你喜欢

转载自blog.csdn.net/loveliveoil/article/details/111032876