AliyunIoTSDK再度升级,支持给自定义主题发送数据(适用小程序数据传输场景)

忘记过去,超越自己

  • ❤️ 博客主页 单片机菜鸟哥,一个野生非专业硬件IOT爱好者 ❤️
  • ❤️ 本篇创建记录 2022-04-09 ❤️
  • ❤️ 本篇更新记录 2022-04-09 ❤️
  • 🎉 欢迎关注 🔎点赞 👍收藏 ⭐️留言📝
  • 🙏 此博客均由博主单独编写,不存在任何商业团队运营,如发现错误,请留言轰炸哦!及时修正!感谢支持!
  • 🔥 Arduino ESP8266教程累计帮助过超过1W+同学入门学习硬件网络编程,入选过选修课程,刊登过无线电杂志 🔥

手把手代码注释,完整案例讲解开发过程以及细节,一键式运行代码。
ESP保姆级付费专栏群 707958244,不喜勿加,凭借付费专栏订单号加入

1、背景说明

2、涉及内容

AliyunIoTSDK只有两个文件:
在这里插入图片描述

2.1 AliyunIoTSDK.h 增加发送数据到自定义主题的新方法

  /**
   * 发送数据到自定义主题
   * @param topic 自定义主题
   * @param payload 字符串形式的json 数据
   */
  static void sendTopicAndPayload(const char *topic,const char *payload);

2.2 AliyunIoTSDK.cpp 实现上面方法,非常简单

/**
 * 发送数据到自定义主题
 * @param topic 自定义主题
* @param payload 字符串形式的json 数据
 */
void AliyunIoTSDK::sendTopicAndPayload(const char *topic,const char *payload){
    boolean d = client->publish(topic, payload);
    Serial.print("publish:0 成功:");
    Serial.println(d);
}

3、具体实例使用(只是说明用法,无法编译通过运行)


/**
 * 功能: smartArduino
 * 详细描述:
 *   1、esp8266连接上wifi热点
 *   2、通过mqtt client方式连接上阿里云物联网平台
 *   3、上传数据到阿里云
 */

#include <ESP8266WiFi.h>  // 引入Arduino ESP8266核心库
#include "PubSubClient.h" // 引入MQTT处理库
#include "AliyunIoTSDK.h" // 引入阿里云 IoT SDK

const char* WIFI_SSID     = "TP-LINK_5344"; // WiFi账号密码,更改成自己的
const char* WIFI_PASSWORD = "xxxxxx"; // WiFi密码,更改成自己的

//-------- 设置产品和设备的信息,从阿里云生活物联网设备信息里查看 -------//
#define PRODUCT_KEY1 "xxxx"
#define DEVICE_NAME1 "xxxx"
#define DEVICE_SECRET "xxxxxx"
#define REGION_ID "cn-shanghai"
#define TOPIC_DATA "/xxxx/xxxxx/user/dataTopic"
//-------- 设置产品和设备的信息,从阿里云生活物联网设备信息里查看 -------//

/******************* 函数声明 **********************/
void doWiFiTick();
void delayRestart(float t);
/******************* 函数声明 **********************/

unsigned long previousMillis = 0; // 上一次时间
const long interval = 1000; // 读取时间间隔,默认1s
String data;
char param[200]; //缓存数据

static WiFiClient espClient; // TCP Client

void setup() {
  // 延时2s,让电源稍微稳定一下
  delay(2000);
  // 初始化串口
  Serial.begin(115200);
  Serial.println("");
  // 初始化网络
  wifi_station_set_auto_connect(0);//关闭自动连接
  ESP.wdtEnable(5000); //  启用看门狗
  doWiFiTick();
  // 初始化 iot,需传入 wifi 的 client,和设备产品信息
  AliyunIoTSDK::begin(espClient, PRODUCT_KEY1, DEVICE_NAME1, DEVICE_SECRET, REGION_ID);
}

void loop() {
  // 定时喂狗
  ESP.wdtFeed();
  // wifi连接状态检测以及重连
  doWiFiTick();
  // 解析出需要上报的数据
  float temp = 45.0;
  float humi =  45.0;
  int mq2 = 100; 
  int mq3 = 200; 
  int mq7 = 300; 
  // 数据协议:{"temp":xxxx,"humi":"xxxx","mq2":xxxx,"mq3":"xxxx","mq7":"xxxx"}
  sprintf(param, "{\"temp\":%.2f,\"humi\":%.2f,\"mq2\":%d,\"mq3\":%d,\"mq7\":%d}", String(temp).toFloat(),String(humi).toFloat(),mq2,mq3,mq7);
  // 发送数据到阿里云
  AliyunIoTSDK::sendTopicAndPayload(TOPIC_DATA,param);

  // Wifi处于连接状态
  if (WiFi.status() == WL_CONNECTED) {
    // 检测MQTT 阿里云
    AliyunIoTSDK::loop();
  }
  delay(1000);
}

/**
 * 功能:连接Wifi路由心跳函数
 */
void doWiFiTick() {
  static bool taskStarted = false;
  static bool startSTAFlag = false;
  static uint32_t lastWiFiCheckTick = 0;

  if (!startSTAFlag) {
    startSTAFlag = true;

    Serial.print("connect to ap:");
    Serial.println(WIFI_SSID);
    WiFi.disconnect();
    WiFi.mode(WIFI_STA);
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    Serial.printf("Heap size:%d\r\n", ESP.getFreeHeap());
    int cnt = 0;
    while (WiFi.status() != WL_CONNECTED) {
         delay(500);
         cnt++;
         Serial.print(".");
         if(cnt>=40){
             cnt = 0;
             //重启系统
             delayRestart(1);
         }
    }
  }

  //未连接1s重连
  if ( WiFi.status() != WL_CONNECTED ) {
    if (millis() - lastWiFiCheckTick > 1000) {
      lastWiFiCheckTick = millis();
      Serial.print(".");
    }
  }
  //连接成功建立
  else {
    if (taskStarted == false) {
      taskStarted = true;
      Serial.print("\r\nGet IP Address: ");
      Serial.println(WiFi.localIP());
    }
  }
}

Ticker delayTimer;

/*
 * 延时重启
*/
void delayRestart(float t) {
  Serial.print("Restart after ");
  Serial.print(t);
  Serial.println("s");
  delayTimer.attach(t, []() {
    Serial.println("\r\nRestart now!");
    ESP.restart();
  });
}

4、具体库(直接copy覆盖即可)

4.1 AliyunIoTSDK.h

#ifndef ALIYUN_IOT_SDK_H
#define ALIYUN_IOT_SDK_H
#include <Arduino.h>
#include <ArduinoJson.h>
#include "Client.h"

typedef void (*poniter_fun)(JsonVariant ele); //定义一个函数指针

typedef struct poniter_desc
{
  char *key;
  poniter_fun fp;
} poniter_desc, *p_poniter_desc;

// 最多绑定20个回调
static poniter_desc poniter_array[20];
// 最多绑定10个主题
static poniter_desc poniter_topic[10];
static p_poniter_desc p_poniter_array;

class AliyunIoTSDK
{
private:
  // mqtt 链接信息,动态生成的
  static char mqttPwd[256];
  static char clientId[256];
  static char mqttUsername[100];
  static char domain[150];
 
  // 定时检查 mqtt 链接
  static void mqttCheckConnect();

  static void messageBufferCheck();
  static void sendBuffer();
public:
  
  // 标记一些 topic 模板
  static char ALINK_TOPIC_PROP_POST[150];
  static char ALINK_TOPIC_PROP_SET[150];
  static char ALINK_TOPIC_EVENT[150];
  // 在主程序 loop 中调用,检查连接和定时发送信息
  static void loop();

  /**
   * 初始化程序
   * @param ssid wifi名
   * @param passphrase wifi密码
   */
  static void begin(Client &espClient,
                    const char *_productKey,
                    const char *_deviceName,
                    const char *_deviceSecret,
                    const char *_region);

  /**
   * 发送数据到自定义主题
   * @param topic 自定义主题
   * @param payload 字符串形式的json 数据
   */
  static void sendTopicAndPayload(const char *topic,const char *payload);

  /**
   * 发送数据
   * @param param 字符串形式的json 数据,例如 {"${key}":"${value}"}
   */
  static void send(const char *param);
  /**
   * 发送 float 格式数据
   * @param key 数据的 key
   * @param number 数据的值
   */
  static void send(char *key, float number);
  /**
   * 发送 int 格式数据
   * @param key 数据的 key
   * @param number 数据的值
   */
  static void send(char *key, int number);
  /**
   * 发送 double 格式数据
   * @param key 数据的 key
   * @param number 数据的值
   */
  static void send(char *key, double number);
  /**
   * 发送 string 格式数据
   * @param key 数据的 key
   * @param text 数据的值
   */
  static void send(char *key, char *text);

  /**
   * 发送事件到云平台(附带数据)
   * @param eventId 事件名,在阿里云物模型中定义好的
   * @param param 字符串形式的json 数据,例如 {"${key}":"${value}"}
   */
  static void sendEvent(const char *eventId, const char *param);
  /**
   * 发送事件到云平台(空数据)
   * @param eventId 事件名,在阿里云物模型中定义好的
   */
  static void sendEvent(const char *eventId);

  /**
   * 绑定回调,所有云服务下发的数据都会进回调
   */
  // static void bind(MQTT_CALLBACK_SIGNATURE);

  /**
   * 绑定事件回调,云服务下发的特定事件会进入回调
   * @param eventId 事件名
   */
  // static void bindEvent(const char * eventId, MQTT_CALLBACK_SIGNATURE);
  /**
   * 绑定属性回调,云服务下发的数据包含此 key 会进入回调,用于监听特定数据的下发
   * @param key 物模型的key
   */
  static int bindData(char *key, poniter_fun fp);
  /**
   * 卸载某个 key 的所有回调(慎用)
   * @param key 物模型的key
   */
  static int unbindData(char *key);

  static int bindTopic(char *topic, poniter_fun fp);
  /**
   * 卸载某个 key 的所有回调(慎用)
   * @param key 物模型的key
   */
  static int unbindTopic(char *topic);
};
#endif

4.2 AliyunIoTSDK.cpp


#include "AliyunIoTSDK.h"
#include "PubSubClient.h"
#include <SHA256.h>

#define CHECK_INTERVAL 10000
#define MESSAGE_BUFFER_SIZE 10
#define RETRY_CRASH_COUNT 5

static const char *deviceName = NULL;
static const char *productKey = NULL;
static const char *deviceSecret = NULL;
static const char *region = NULL;

struct DeviceProperty
{
    String key;
    String value;
};

DeviceProperty PropertyMessageBuffer[MESSAGE_BUFFER_SIZE];

#define MQTT_PORT 1883

#define SHA256HMAC_SIZE 32
#define DATA_CALLBACK_SIZE 20

#define ALINK_BODY_FORMAT "{\"id\":\"123\",\"version\":\"1.0\",\"method\":\"thing.event.property.post\",\"params\":%s}"
#define ALINK_EVENT_BODY_FORMAT "{\"id\": \"123\",\"version\": \"1.0\",\"params\": %s,\"method\": \"thing.event.%s.post\"}"

static unsigned long lastMs = 0;
static int retry_count = 0;

static PubSubClient *client = NULL;

char AliyunIoTSDK::clientId[256] = "";
char AliyunIoTSDK::mqttUsername[100] = "";
char AliyunIoTSDK::mqttPwd[256] = "";
char AliyunIoTSDK::domain[150] = "";

char AliyunIoTSDK::ALINK_TOPIC_PROP_POST[150] = "";
char AliyunIoTSDK::ALINK_TOPIC_PROP_SET[150] = "";
char AliyunIoTSDK::ALINK_TOPIC_EVENT[150] = "";

static String hmac256(const String &signcontent, const String &ds)
{
    byte hashCode[SHA256HMAC_SIZE];
    SHA256 sha256;

    const char *key = ds.c_str();
    size_t keySize = ds.length();

    sha256.resetHMAC(key, keySize);
    sha256.update((const byte *)signcontent.c_str(), signcontent.length());
    sha256.finalizeHMAC(key, keySize, hashCode, sizeof(hashCode));

    String sign = "";
    for (byte i = 0; i < SHA256HMAC_SIZE; ++i)
    {
        sign += "0123456789ABCDEF"[hashCode[i] >> 4];
        sign += "0123456789ABCDEF"[hashCode[i] & 0xf];
    }

    return sign;
}

static void parmPass(JsonVariant parm)
{
    //    const char *method = parm["method"];
    for (int i = 0; i < DATA_CALLBACK_SIZE; i++)
    {
        if (poniter_array[i].key)
        {
            bool hasKey = parm["params"].containsKey(poniter_array[i].key);
            if (hasKey)
            {
                poniter_array[i].fp(parm["params"]);
            }
        }
    }
}

static void topicPass(char *topic,JsonVariant parm)
{
    for (int i = 0; i < 10; i++)
    {
        if (poniter_topic[i].key){
          if (strstr(topic, poniter_topic[i].key))
          {
            poniter_topic[i].fp(parm);
          }
        }
    }
}

// 所有云服务的回调都会首先进入这里,例如属性下发
static void callback(char *topic, byte *payload, unsigned int length)
{
    Serial.print("Message arrived [");
    Serial.print(topic);
    Serial.print("] ");
    payload[length] = '\0';
    Serial.println((char *)payload);

    if (strstr(topic, AliyunIoTSDK::ALINK_TOPIC_PROP_SET))
    {
        StaticJsonDocument<200> doc;
        DeserializationError error = deserializeJson(doc, payload); //反序列化JSON数据

        if (!error) //检查反序列化是否成功
        {
            parmPass(doc.as<JsonVariant>()); //将参数传递后打印输出
        }
    } else {
        StaticJsonDocument<200> doc;
        DeserializationError error = deserializeJson(doc, payload); //反序列化JSON数据

        if (!error) //检查反序列化是否成功
        {
            topicPass(topic,doc.as<JsonVariant>()); //将参数传递后打印输出
        }
    }
}

static bool mqttConnecting = false;
void(* resetFunc) (void) = 0; //制造重启命令
void AliyunIoTSDK::mqttCheckConnect()
{
    if (client != NULL && !mqttConnecting)
    {
        if (!client->connected())
        {
            client->disconnect();
            Serial.println("Connecting to MQTT Server ...");
            mqttConnecting = true;
            if (client->connect(clientId, mqttUsername, mqttPwd))
            {
                // 自定义主题订阅
                for (int i = 0; i < 10; i++)
                {
                    if (poniter_topic[i].fp)
                    {
                        client->subscribe(poniter_topic[i].key);
                    }
                }
                Serial.println("MQTT Connected!");
            }
            else
            {
                Serial.print("MQTT Connect err:");
                Serial.println(client->state());
                retry_count++;
                if(retry_count > RETRY_CRASH_COUNT){
                    resetFunc();
                }
            }
            mqttConnecting = false;
        }
        else
        {
            Serial.println("state is connected");
            retry_count = 0;
        }
    }
}

void AliyunIoTSDK::begin(Client &espClient,
                         const char *_productKey,
                         const char *_deviceName,
                         const char *_deviceSecret,
                         const char *_region)
{

    client = new PubSubClient(espClient);
    productKey = _productKey;
    deviceName = _deviceName;
    deviceSecret = _deviceSecret;
    region = _region;
    long times = millis();
    String timestamp = String(times);

    sprintf(clientId, "%s|securemode=3,signmethod=hmacsha256,timestamp=%s|", deviceName, timestamp.c_str());

    String signcontent = "clientId";
    signcontent += deviceName;
    signcontent += "deviceName";
    signcontent += deviceName;
    signcontent += "productKey";
    signcontent += productKey;
    signcontent += "timestamp";
    signcontent += timestamp;

    String pwd = hmac256(signcontent, deviceSecret);

    strcpy(mqttPwd, pwd.c_str());

    sprintf(mqttUsername, "%s&%s", deviceName, productKey);
    sprintf(ALINK_TOPIC_PROP_POST, "/sys/%s/%s/thing/event/property/post", productKey, deviceName);
    sprintf(ALINK_TOPIC_PROP_SET, "/sys/%s/%s/thing/service/property/set", productKey, deviceName);
    sprintf(ALINK_TOPIC_EVENT, "/sys/%s/%s/thing/event", productKey, deviceName);

    sprintf(domain, "%s.iot-as-mqtt.%s.aliyuncs.com", productKey, region);
    client->setServer(domain, MQTT_PORT); /* 连接WiFi之后,连接MQTT服务器 */
    client->setCallback(callback);

    mqttCheckConnect();
}

void AliyunIoTSDK::loop()
{
    client->loop();
    if (millis() - lastMs >= CHECK_INTERVAL)
    {
        lastMs = millis();
        mqttCheckConnect();
        messageBufferCheck();
    }
}

void AliyunIoTSDK::sendEvent(const char *eventId, const char *param)
{
    char topicKey[156];
    sprintf(topicKey, "%s/%s/post", ALINK_TOPIC_EVENT, eventId);
    char jsonBuf[1024];
    sprintf(jsonBuf, ALINK_EVENT_BODY_FORMAT, param, eventId);
    Serial.println(jsonBuf);
    boolean d = client->publish(topicKey, jsonBuf);
    Serial.print("publish:0 成功:");
    Serial.println(d);
}
void AliyunIoTSDK::sendEvent(const char *eventId)
{
    sendEvent(eventId, "{}");
}
unsigned long lastSendMS = 0;

// 检查是否有数据需要发送
void AliyunIoTSDK::messageBufferCheck()
{
    int bufferSize = 0;
    for (int i = 0; i < MESSAGE_BUFFER_SIZE; i++)
    {
        if (PropertyMessageBuffer[i].key.length() > 0)
        {
            bufferSize++;
        }
    }
    // Serial.println("bufferSize:");
    // Serial.println(bufferSize);
    if (bufferSize > 0)
    {
        if (bufferSize >= MESSAGE_BUFFER_SIZE)
        {
            sendBuffer();
        }
        else
        {
            unsigned long nowMS = millis();
            // 3s 发送一次数据
            if (nowMS - lastSendMS > 5000)
            {
                sendBuffer();
                lastSendMS = nowMS;
            }
        }
    }
}

// 发送 buffer 数据
void AliyunIoTSDK::sendBuffer()
{
    int i;
    String buffer;
    for (i = 0; i < MESSAGE_BUFFER_SIZE; i++)
    {
        if (PropertyMessageBuffer[i].key.length() > 0)
        {
            buffer += "\"" + PropertyMessageBuffer[i].key + "\":" + PropertyMessageBuffer[i].value + ",";
            PropertyMessageBuffer[i].key = "";
            PropertyMessageBuffer[i].value = "";
        }
    }

    buffer = "{" + buffer.substring(0, buffer.length() - 1) + "}";
    send(buffer.c_str());
}

void addMessageToBuffer(char *key, String value)
{
    int i;
    for (i = 0; i < MESSAGE_BUFFER_SIZE; i++)
    {
        if (PropertyMessageBuffer[i].key.length() == 0)
        {
            PropertyMessageBuffer[i].key = key;
            PropertyMessageBuffer[i].value = value;
            break;
        }
    }
}

/**
 * 发送数据到自定义主题
 * @param topic 自定义主题
* @param payload 字符串形式的json 数据
 */
void AliyunIoTSDK::sendTopicAndPayload(const char *topic,const char *payload){
    boolean d = client->publish(topic, payload);
    Serial.print("publish:0 成功:");
    Serial.println(d);
}

void AliyunIoTSDK::send(const char *param)
{

    char jsonBuf[1024];
    sprintf(jsonBuf, ALINK_BODY_FORMAT, param);
    Serial.println(jsonBuf);
    boolean d = client->publish(ALINK_TOPIC_PROP_POST, jsonBuf);
    Serial.print("publish:0 成功:");
    Serial.println(d);
}
void AliyunIoTSDK::send(char *key, float number)
{
    addMessageToBuffer(key, String(number));
    messageBufferCheck();
}
void AliyunIoTSDK::send(char *key, int number)
{
    addMessageToBuffer(key, String(number));
    messageBufferCheck();
}
void AliyunIoTSDK::send(char *key, double number)
{
    addMessageToBuffer(key, String(number));
    messageBufferCheck();
}

void AliyunIoTSDK::send(char *key, char *text)
{
    addMessageToBuffer(key, "\"" + String(text) + "\"");
    messageBufferCheck();
}

int AliyunIoTSDK::bindData(char *key, poniter_fun fp)
{
    int i;
    for (i = 0; i < DATA_CALLBACK_SIZE; i++)
    {
        if (!poniter_array[i].fp)
        {
            poniter_array[i].key = key;
            poniter_array[i].fp = fp;
            return 0;
        }
    }
    return -1;
}

int AliyunIoTSDK::bindTopic(char *topic, poniter_fun fp)
{
    int i;
    for (i = 0; i < 10; i++)
    {
        if (!poniter_topic[i].fp)
        {
            poniter_topic[i].key = topic;
            poniter_topic[i].fp = fp;
            return 0;
        }
    }
    return -1;
}

int AliyunIoTSDK::unbindTopic(char *topic)
{
    int i;
    for (i = 0; i < 10; i++)
    {
        if (!strcmp(poniter_topic[i].key, topic))
        {
            poniter_topic[i].key = NULL;
            poniter_topic[i].fp = NULL;
            return 0;
        }
    }
    return -1;
}

int AliyunIoTSDK::unbindData(char *key)
{
    int i;
    for (i = 0; i < DATA_CALLBACK_SIZE; i++)
    {
        if (!strcmp(poniter_array[i].key, key))
        {
            poniter_array[i].key = NULL;
            poniter_array[i].fp = NULL;
            return 0;
        }
    }
    return -1;
}

AliyunIoTSDK库是阿里云推出的一款基于Mbed OS的物联网设备开发框架。它为开发人员提供了可靠、安全、高效的物联网设备开发工具,支持多种物联网协议的开发,并具有云端数据存储、设备管理等丰富的功能。 该库解析的过程中,需要以下几个步骤: 一、研究阿里云IoT平台的API文档,了解其开放的相关接口及参数; 二、下载AliyunIoTSDK库到本地,开启开发环境; 三、在代码中引入AliyunIoTSDK库,设置阿里云IoT平台访问参数; 四、编写设备与云端通信的逻辑代码; 五、测试设备与云端的通信是否正常。 在第一步中,需要研究阿里云IoT平台所提供的API文档,具体包括设备接入、消息路由、设备管理等相关接口及其参数。通过了解这些接口及其参数,可以更好地理解AliyunIoTSDK库的使用。 在第二步中,需要下载AliyunIoTSDK库到本地,并开启开发环境,以便使用该库进行开发。例如,可以使用Mbed Studio环境进行AliyunIoTSDK库的开发,包括库的下载、导入和配置等工作。 在第三步中,需要在代码中引入AliyunIoTSDK库,并设置阿里云IoT平台的访问参数,例如设备ID、设备密钥、物联网协议等信息。这些参数的配置可以通过阿里云IoT平台的控制台进行设置,也可以直接在开发代码中进行设置。 在第四步中,需要编写设备与云端通信的逻辑代码,例如设备的上报数据、云端命令的下发、设备与其他设备之间的通信等。具体的逻辑代码可以根据实际开发需求进行设计,同时可以参考AliyunIoTSDK库提供的代码示例进行开发。 在第五步中,需要进行设备与云端通信的测试,以验证设备与云端的通信是否正常。可以通过阿里云IoT平台的调试工具、命令下发等方式进行测试,同时也可以通过打印调试信息来进行开发调试。 综上所述,AliyunIoTSDK库的解析包括了阅读API文档、下载库、设置参数、编写代码和测试等多个步骤。只有通过深入学习和实践,才能更好地掌握AliyunIoTSDK库的使用,为开发物联网设备提供更加稳定、安全、高效的解决方案。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

单片机菜鸟哥

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

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

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

打赏作者

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

抵扣说明:

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

余额充值