忘记过去,超越自己
- ❤️ 博客主页 单片机菜鸟哥,一个野生非专业硬件IOT爱好者 ❤️
- ❤️ 本篇创建记录 2022-04-09 ❤️
- ❤️ 本篇更新记录 2022-04-09 ❤️
- 🎉 欢迎关注 🔎点赞 👍收藏 ⭐️留言📝
- 🙏 此博客均由博主单独编写,不存在任何商业团队运营,如发现错误,请留言轰炸哦!及时修正!感谢支持!
- 🔥 Arduino ESP8266教程累计帮助过超过1W+同学入门学习硬件网络编程,入选过选修课程,刊登过无线电杂志 🔥
快速导读
手把手代码注释,完整案例讲解开发过程以及细节,一键式运行代码。
ESP保姆级付费专栏群707958244
,不喜勿加,凭借付费专栏订单号加入
1、背景说明
- 之前使用AliyunIoTSDK的时候发现不支持订阅自定义主题,所以干脆就升级一下,方便使用 这里可以参考 AliyunIoTSDK升级,支持绑定自定义主题
- 后面使用esp8266和小程序一起开发的时候经常发现需要创建自定义主题以及传输数据,所以也干脆再度升级(二度升级,基于 AliyunIoTSDK升级,支持绑定自定义主题 ),方便使用
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;
}