diff --git a/sdk/arduino/Arduino-ESP32/.vscode/arduino.json b/sdk/arduino/Arduino-ESP32/.vscode/arduino.json new file mode 100644 index 00000000..c7102375 --- /dev/null +++ b/sdk/arduino/Arduino-ESP32/.vscode/arduino.json @@ -0,0 +1,7 @@ +{ + "port": "COM3", + "configuration": "FlashFreq=80,UploadSpeed=921600", + "board": "esp32:esp32:nano32", + "output": "./build", + "sketch": "Arduino-Esp32.ino" +} \ No newline at end of file diff --git a/sdk/arduino/Arduino-ESP32/Arduino-ESP32.ino b/sdk/arduino/Arduino-ESP32/Arduino-ESP32.ino new file mode 100644 index 00000000..806bf20a --- /dev/null +++ b/sdk/arduino/Arduino-ESP32/Arduino-ESP32.ino @@ -0,0 +1,85 @@ +/*********************************************************** + * author: LaoHuang + * create: 2022-04-14 + * email:rememberyousaid@163.com + * source:https://github.com/kerwincui/wumei-smart + * board:esp32 1.0.6 + ***********************************************************/ + +#include "Helper.h" + +long lastMqttConn; // 上次mqtt连接时间 +long lastPublishMonitor; // 上次发布监测数据时间 +long lastTimerMonitor; // 上次定时发布监测数据 + +/** + * 启动 + */ +void setup() +{ + //打开串行端口: + Serial.begin(115200); + printMsg("wumei smart device starting..."); + connectWifi(); + connectMqtt(); +} + +/** + * 循环执行 + */ +void loop() +{ + // Wifi掉线重连 + if (WiFi.status() != WL_CONNECTED) + { + connectWifi(); + } + + // 非阻塞Mqtt重连,间隔30秒 + if (WiFi.status() == WL_CONNECTED) + { + long now = millis(); + if (!mqttClient.connected()) + { + if (now - lastMqttConn > 30000) + { + lastMqttConn = now; + connectMqtt(); + } + } + else + { + mqttClient.loop(); + } + } + + // 非阻塞发布实时监测数据,间隔默认1秒 + if(WiFi.status() == WL_CONNECTED && monitorCount>0){ + long now = millis(); + if (now - lastPublishMonitor > monitorInterval) + { + lastPublishMonitor = now; + monitorCount--; + publishMonitor(); + } + } + + // 非阻塞定时上报,测试用,60秒发布一次 + if(WiFi.status() == WL_CONNECTED){ + long now = millis(); + if (now - lastTimerMonitor > 60000) + { + lastTimerMonitor = now; + printMsg("执行定时上报"); + // 发布事件 + publishEvent(); + // 发布时钟同步 + publishNtp(); + + // 发布属性(监测值) + String msg=randomPropertyData(); + publishProperty(msg); + } + } + +} diff --git a/sdk/arduino/wumei/Base64.cpp b/sdk/arduino/Arduino-ESP32/Base64.cpp similarity index 100% rename from sdk/arduino/wumei/Base64.cpp rename to sdk/arduino/Arduino-ESP32/Base64.cpp diff --git a/sdk/arduino/wumei/Base64.h b/sdk/arduino/Arduino-ESP32/Base64.h similarity index 100% rename from sdk/arduino/wumei/Base64.h rename to sdk/arduino/Arduino-ESP32/Base64.h diff --git a/sdk/arduino/wumei/Helper.cpp b/sdk/arduino/Arduino-ESP32/Helper.cpp similarity index 89% rename from sdk/arduino/wumei/Helper.cpp rename to sdk/arduino/Arduino-ESP32/Helper.cpp index 15078231..39333168 100644 --- a/sdk/arduino/wumei/Helper.cpp +++ b/sdk/arduino/Arduino-ESP32/Helper.cpp @@ -1,13 +1,14 @@ /*********************************************************** - * author: kerwincui [物美智能 wumei-smart] - * create: 2022-02-20 - * email:164770707@qq.com + * author: LaoHuang + * create: 2022-04-14 + * email:rememberyousaid@163.com * source:https://github.com/kerwincui/wumei-smart - * board:esp8266 core for arduino v3.0.2 + * board:esp32 ***********************************************************/ #include "Helper.h" +String g_time; WiFiClient wifiClient; PubSubClient mqttClient; float rssi = 0; @@ -17,8 +18,8 @@ long monitorInterval = 1000; //==================================== 这是需要配置的项 =============================== // Wifi配置 -char *wifiSsid = "wifi账号"; -char *wifiPwd = "wifi密码"; +char *wifiSsid = "tp-six"; +char *wifiPwd = "clh15108665817"; // 设备信息配置 String deviceNum = "D6329VL54419L1Y0"; @@ -59,7 +60,8 @@ void processProperty(String payload) { StaticJsonDocument<1024> doc; DeserializationError error = deserializeJson(doc, payload); - if (error) { + if (error) + { Serial.print(F("deserializeJson() failed: ")); Serial.println(error.f_str()); return; @@ -67,8 +69,8 @@ void processProperty(String payload) for (JsonObject object : doc.as()) { // 匹配云端定义的属性(不包含属性中的监测数据) - const char* id = object["id"]; - const char* value = object["value"]; + const char *id = object["id"]; + const char *value = object["value"]; printMsg((String)id + ":" + (String)value); } // 最后发布属性,服务端订阅存储(重要) @@ -80,7 +82,8 @@ void processFunction(String payload) { StaticJsonDocument<1024> doc; DeserializationError error = deserializeJson(doc, payload); - if (error) { + if (error) + { Serial.print(F("deserializeJson() failed: ")); Serial.println(error.f_str()); return; @@ -88,11 +91,11 @@ void processFunction(String payload) for (JsonObject object : doc.as()) { // 匹配云端定义的功能 - const char* id = object["id"]; - const char* value = object["value"]; + const char *id = object["id"]; + const char *value = object["value"]; if (strcmp(id, "switch") == 0) { - printMsg("开关 switch:" + (String) value); + printMsg("开关 switch:" + (String)value); } else if (strcmp(id, "gear") == 0) { @@ -105,8 +108,10 @@ void processFunction(String payload) else if (strcmp(id, "message") == 0) { printMsg("屏显消息 message:" + (String)value); - }else if(strcmp(id,"report_monitor")==0){ - String msg=randomPropertyData(); + } + else if (strcmp(id, "report_monitor") == 0) + { + String msg = randomPropertyData(); printMsg("订阅到上报监测数据指令,上报数据:"); printMsg(msg); publishProperty(msg); @@ -143,7 +148,7 @@ void callback(char *topic, byte *payload, unsigned int length) Serial.println(error.f_str()); return; } - // 计算设备当前时间:(${serverRecvTime} + ${serverSendTime} + ${deviceRecvTime} - ${deviceSendTime}) / 2 + float deviceSendTime = doc["deviceSendTime"]; float serverSendTime = doc["serverSendTime"]; float serverRecvTime = doc["serverRecvTime"]; @@ -174,7 +179,7 @@ void callback(char *topic, byte *payload, unsigned int length) } monitorCount = doc["count"]; monitorInterval = doc["interval"]; - } + } } // 连接wifi @@ -202,6 +207,7 @@ void connectMqtt() String password = generationPwd(); String encryptPassword = encrypt(password, mqttSecret, wumei_iv); printMsg("密码(已加密):" + encryptPassword); + mqttClient.setClient(wifiClient); mqttClient.setServer(mqttHost, mqttPort); mqttClient.setCallback(callback); @@ -306,28 +312,29 @@ void publishEvent() // 6.发布实时监测数据 void publishMonitor() { - String msg=randomPropertyData(); + String msg = randomPropertyData(); // 发布为实时监测数据,不会存储 - printMsg("发布实时监测数据:"+msg); + printMsg("发布实时监测数据:" + msg); mqttClient.publish(pMonitorTopic.c_str(), msg.c_str()); } // 随机生成监测值 -String randomPropertyData(){ +String randomPropertyData() +{ // 匹配云端定义的监测数据,随机数代替监测结果 float randFloat = 0; - int randInt=0; + int randInt = 0; StaticJsonDocument<1024> doc; JsonObject objTmeperature = doc.createNestedObject(); objTmeperature["id"] = "temperature"; - randFloat = random(1000, 3000) ; - objTmeperature["value"] = (String)(randFloat/100); + randFloat = random(1000, 3000); + objTmeperature["value"] = (String)(randFloat / 100); objTmeperature["remark"] = (String)millis(); - JsonObject objHumidity = doc.createNestedObject(); + JsonObject objHumidity = doc.createNestedObject(); objHumidity["id"] = "humidity"; randFloat = random(3000, 6000); - objHumidity["value"] = (String)(randFloat/100); + objHumidity["value"] = (String)(randFloat / 100); objHumidity["remark"] = (String)millis(); JsonObject objCo2 = doc.createNestedObject(); @@ -353,6 +360,8 @@ String randomPropertyData(){ String generationPwd() { String jsonTime = getTime(); + printMsg("getTime()= " + jsonTime); + // 128字节内存池容量 StaticJsonDocument<128> doc; // 解析JSON @@ -369,10 +378,12 @@ String generationPwd() float serverRecvTime = doc["serverRecvTime"]; float deviceRecvTime = millis(); float now = (serverSendTime + serverRecvTime + deviceRecvTime - deviceSendTime) / 2; + // 过期时间 = 当前时间 + 1小时 float expireTime = now + 1 * 60 * 60 * 1000; String password = (String)mqttPwd + "&" + userId + "&" + String(expireTime, 0); printMsg("密码(未加密):" + password); + return password; } @@ -383,6 +394,7 @@ String getTime() { HTTPClient http; printMsg("获取时间..."); + if (http.begin(wifiClient, (ntpServer + (String)millis()).c_str())) { // 发送请求 @@ -391,9 +403,10 @@ String getTime() { if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) { + g_time = http.getString(); printMsg("获取时间成功,data:"); - Serial.print(http.getString()); - return http.getString(); + Serial.print(g_time); + return g_time; } } else @@ -440,6 +453,7 @@ String encrypt(String plain_data, char *wumei_key, char *wumei_iv) int i; // pkcs7padding填充 Block Size : 16 int len = plain_data.length(); + int n_blocks = len / 16 + 1; uint8_t n_padding = n_blocks * 16 - len; uint8_t data[n_blocks * 16]; @@ -449,16 +463,24 @@ String encrypt(String plain_data, char *wumei_key, char *wumei_iv) data[i] = n_padding; } uint8_t key[16], iv[16]; + uint8_t crypt_data[3 * 16] = {0}; + memcpy(key, wumei_key, 16); memcpy(iv, wumei_iv, 16); - // 加密 - br_aes_big_cbcenc_keys encCtx; - br_aes_big_cbcenc_init(&encCtx, key, 16); - br_aes_big_cbcenc_run(&encCtx, iv, data, n_blocks * 16); - // Base64编码 + + memset(crypt_data, 0, 48); + len = n_blocks * 16; + // 加密 + mbedtls_aes_context aes_ctx; + mbedtls_aes_init(&aes_ctx); + mbedtls_aes_setkey_enc(&aes_ctx, key, 128); + mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_ENCRYPT, len, iv, data, crypt_data); + + // Base64编码 char encoded_data[base64_enc_len(len)]; - base64_encode(encoded_data, (char *)data, len); + base64_encode(encoded_data, (char *)crypt_data, len); + return String(encoded_data); } @@ -474,14 +496,20 @@ String decrypt(String encoded_data_str, char *wumei_key, char *wumei_iv) memcpy(key, wumei_key, 16); memcpy(iv, wumei_iv, 16); int n_blocks = len / 16; - br_aes_big_cbcdec_keys decCtx; - br_aes_big_cbcdec_init(&decCtx, key, 16); - br_aes_big_cbcdec_run(&decCtx, iv, data, n_blocks * 16); - // PKCS#7 Padding 填充 + uint8_t n_padding = data[n_blocks * 16 - 1]; len = n_blocks * 16 - n_padding; char plain_data[len + 1]; - memcpy(plain_data, data, len); + + //密文空间 + mbedtls_aes_context aes_ctx; + mbedtls_aes_init(&aes_ctx); + + //设置解密密钥 + mbedtls_aes_setkey_dec(&aes_ctx, key, 128); + mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_DECRYPT, 48, iv, (unsigned char *)encoded_data, (unsigned char *)plain_data); + mbedtls_aes_free(&aes_ctx); + // PKCS#7 Padding 填充 plain_data[len] = '\0'; return String(plain_data); } diff --git a/sdk/arduino/Arduino-ESP32/Helper.h b/sdk/arduino/Arduino-ESP32/Helper.h new file mode 100644 index 00000000..b7cc202e --- /dev/null +++ b/sdk/arduino/Arduino-ESP32/Helper.h @@ -0,0 +1,79 @@ +/*********************************************************** + * author: LaoHuang + * create: 2022-04-14 + * email:rememberyousaid@163.com + * source:https://github.com/kerwincui/wumei-smart + * board:esp32 + ***********************************************************/ +#ifndef _HELPER_H +#define _HELPER_H + +#include +#include "Base64.h" +#include +//#include +#include +#include // 版本2.8.0 +#include // 版本6.19.4 + +extern WiFiClient wifiClient; +extern PubSubClient mqttClient; + +extern String deviceNum ; // 设备编号(重要,同时是Mqtt的clientId) +extern String userId; // 用户ID +extern String productId; // 产品ID +extern float rssi; // 信号强度(信号极好4格[-55— 0],信号好3格[-70— -55],信号一般2格[-85— -70],信号差1格[-100— -85]) +extern String firmwareVersion; // 固件版本 +extern char *wifiSsid; // WIFI的SSID +extern char *wifiPwd; // WIFI的密码 +extern char *mqttHost; // Mqtt消息服务器地址 +extern int mqttPort; // Mqtt消息服务器端口 +extern char *mqttUserName; // Mqtt消息服务器账号 +extern char *mqttPwd; // Mqtt消息服务器密码 +extern char mqttSecret[17]; // Mqtt秘钥,16位 +extern char wumei_iv[17]; // AES加密偏移量,固定值16位 +extern String ntpServer; // NTP服务地址,用于获取当前时间 +extern int monitorCount; // 发布监测数据的最大次数 +extern long monitorInterval; // 发布监测数据的间隔,默认1000毫秒 + + +// 连接wifi +void connectWifi(); +// 连接mqtt +void connectMqtt(); +// Mqtt回调 +void callback(char *topic, byte *payload, unsigned int length); + +// 发布设备信息 +void publishInfo(); +// 发布时钟同步信息 +void publishNtp(); +// 发布事件 +void publishEvent(); +// 发布实时监测数据 +void publishMonitor(); +// 随机生成监测值 +String randomPropertyData(); +// 发布属性 +void publishProperty(String msg); +// 发布功能 +void publishFunction(String msg); +// 属性处理 +void processProperty(String payload); +// 功能处理 +void processFunction(String payload); + +// 生成密码 +String generationPwd(); +// 获取时间 +String getTime(); +// AES加密 +String encrypt(String plain_data,char *wumei_key,char *wumei_iv); +// AES解密 +String decrypt(String encoded_data_str,char *wumei_key,char *wumei_iv); +//打印提示信息 +void printMsg(String tips); +// 控制指示灯闪烁 +void blink(); + +#endif diff --git a/sdk/arduino/wumei/.vscode/settings.json b/sdk/arduino/Arduino-ESP8266/.vscode/settings.json similarity index 100% rename from sdk/arduino/wumei/.vscode/settings.json rename to sdk/arduino/Arduino-ESP8266/.vscode/settings.json diff --git a/sdk/arduino/wumei/wumei.ino b/sdk/arduino/Arduino-ESP8266/Arduino-ESP8266.ino similarity index 100% rename from sdk/arduino/wumei/wumei.ino rename to sdk/arduino/Arduino-ESP8266/Arduino-ESP8266.ino diff --git a/sdk/arduino/Arduino-ESP8266/Base64.cpp b/sdk/arduino/Arduino-ESP8266/Base64.cpp new file mode 100644 index 00000000..71c40863 --- /dev/null +++ b/sdk/arduino/Arduino-ESP8266/Base64.cpp @@ -0,0 +1,137 @@ +#include "Base64.h" + +#if (defined(__AVR__)) +#include +#else +#include +#endif + +const char PROGMEM b64_alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +/* 'Private' declarations */ +inline void a3_to_a4(unsigned char * a4, unsigned char * a3); +inline void a4_to_a3(unsigned char * a3, unsigned char * a4); +inline unsigned char b64_lookup(char c); + +int base64_encode(char *output, char *input, int inputLen) { + int i = 0, j = 0; + int encLen = 0; + unsigned char a3[3]; + unsigned char a4[4]; + + while(inputLen--) { + a3[i++] = *(input++); + if(i == 3) { + a3_to_a4(a4, a3); + + for(i = 0; i < 4; i++) { + output[encLen++] = pgm_read_byte(&b64_alphabet[a4[i]]); + } + + i = 0; + } + } + + if(i) { + for(j = i; j < 3; j++) { + a3[j] = '\0'; + } + + a3_to_a4(a4, a3); + + for(j = 0; j < i + 1; j++) { + output[encLen++] = pgm_read_byte(&b64_alphabet[a4[j]]); + } + + while((i++ < 3)) { + output[encLen++] = '='; + } + } + output[encLen] = '\0'; + return encLen; +} + +int base64_decode(char * output, char * input, int inputLen) { + int i = 0, j = 0; + int decLen = 0; + unsigned char a3[3]; + unsigned char a4[4]; + + + while (inputLen--) { + if(*input == '=') { + break; + } + + a4[i++] = *(input++); + if (i == 4) { + for (i = 0; i <4; i++) { + a4[i] = b64_lookup(a4[i]); + } + + a4_to_a3(a3,a4); + + for (i = 0; i < 3; i++) { + output[decLen++] = a3[i]; + } + i = 0; + } + } + + if (i) { + for (j = i; j < 4; j++) { + a4[j] = '\0'; + } + + for (j = 0; j <4; j++) { + a4[j] = b64_lookup(a4[j]); + } + + a4_to_a3(a3,a4); + + for (j = 0; j < i - 1; j++) { + output[decLen++] = a3[j]; + } + } + output[decLen] = '\0'; + return decLen; +} + +int base64_enc_len(int plainLen) { + int n = plainLen; + return (n + 2 - ((n + 2) % 3)) / 3 * 4; +} + +int base64_dec_len(char * input, int inputLen) { + int i = 0; + int numEq = 0; + for(i = inputLen - 1; input[i] == '='; i--) { + numEq++; + } + + return ((6 * inputLen) / 8) - numEq; +} + +inline void a3_to_a4(unsigned char * a4, unsigned char * a3) { + a4[0] = (a3[0] & 0xfc) >> 2; + a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4); + a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6); + a4[3] = (a3[2] & 0x3f); +} + +inline void a4_to_a3(unsigned char * a3, unsigned char * a4) { + a3[0] = (a4[0] << 2) + ((a4[1] & 0x30) >> 4); + a3[1] = ((a4[1] & 0xf) << 4) + ((a4[2] & 0x3c) >> 2); + a3[2] = ((a4[2] & 0x3) << 6) + a4[3]; +} + +inline unsigned char b64_lookup(char c) { + if(c >='A' && c <='Z') return c - 'A'; + if(c >='a' && c <='z') return c - 71; + if(c >='0' && c <='9') return c + 4; + if(c == '+') return 62; + if(c == '/') return 63; + return -1; +} diff --git a/sdk/arduino/Arduino-ESP8266/Base64.h b/sdk/arduino/Arduino-ESP8266/Base64.h new file mode 100644 index 00000000..56d3271e --- /dev/null +++ b/sdk/arduino/Arduino-ESP8266/Base64.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2013 Adam Rudd. + * See LICENSE for more information + */ +#ifndef _BASE64_H +#define _BASE64_H + +/* b64_alphabet: + * Description: Base64 alphabet table, a mapping between integers + * and base64 digits + * Notes: This is an extern here but is defined in Base64.c + */ +extern const char b64_alphabet[]; + +/* base64_encode: + * Description: + * Encode a string of characters as base64 + * Parameters: + * output: the output buffer for the encoding, stores the encoded string + * input: the input buffer for the encoding, stores the binary to be encoded + * inputLen: the length of the input buffer, in bytes + * Return value: + * Returns the length of the encoded string + * Requirements: + * 1. output must not be null or empty + * 2. input must not be null + * 3. inputLen must be greater than or equal to 0 + */ +int base64_encode(char *output, char *input, int inputLen); + +/* base64_decode: + * Description: + * Decode a base64 encoded string into bytes + * Parameters: + * output: the output buffer for the decoding, + * stores the decoded binary + * input: the input buffer for the decoding, + * stores the base64 string to be decoded + * inputLen: the length of the input buffer, in bytes + * Return value: + * Returns the length of the decoded string + * Requirements: + * 1. output must not be null or empty + * 2. input must not be null + * 3. inputLen must be greater than or equal to 0 + */ +int base64_decode(char *output, char *input, int inputLen); + +/* base64_enc_len: + * Description: + * Returns the length of a base64 encoded string whose decoded + * form is inputLen bytes long + * Parameters: + * inputLen: the length of the decoded string + * Return value: + * The length of a base64 encoded string whose decoded form + * is inputLen bytes long + * Requirements: + * None + */ +int base64_enc_len(int inputLen); + +/* base64_dec_len: + * Description: + * Returns the length of the decoded form of a + * base64 encoded string + * Parameters: + * input: the base64 encoded string to be measured + * inputLen: the length of the base64 encoded string + * Return value: + * Returns the length of the decoded form of a + * base64 encoded string + * Requirements: + * 1. input must not be null + * 2. input must be greater than or equal to zero + */ +int base64_dec_len(char *input, int inputLen); + +#endif // _BASE64_H diff --git a/sdk/arduino/Arduino-ESP8266/Helper.cpp b/sdk/arduino/Arduino-ESP8266/Helper.cpp new file mode 100644 index 00000000..39333168 --- /dev/null +++ b/sdk/arduino/Arduino-ESP8266/Helper.cpp @@ -0,0 +1,515 @@ +/*********************************************************** + * author: LaoHuang + * create: 2022-04-14 + * email:rememberyousaid@163.com + * source:https://github.com/kerwincui/wumei-smart + * board:esp32 + ***********************************************************/ + +#include "Helper.h" + +String g_time; +WiFiClient wifiClient; +PubSubClient mqttClient; +float rssi = 0; +char wumei_iv[17] = "wumei-smart-open"; +int monitorCount = 0; +long monitorInterval = 1000; + +//==================================== 这是需要配置的项 =============================== +// Wifi配置 +char *wifiSsid = "tp-six"; +char *wifiPwd = "clh15108665817"; + +// 设备信息配置 +String deviceNum = "D6329VL54419L1Y0"; +String userId = "1"; +String productId = "2"; +String firmwareVersion = "1.0"; + +// Mqtt配置 +char *mqttHost = "wumei.live"; +int mqttPort = 1883; +char *mqttUserName = "wumei-smart"; +char *mqttPwd = "P5FJKZJHIR82GNB2"; +char mqttSecret[17] = "K63C4EA3AI5TER97"; + +// NTP地址(用于获取时间,可选的修改为自己部署项目的地址) +String ntpServer = "http://wumei.live:8080/iot/tool/ntp?deviceSendTime="; +//==================================================================================== + +// 订阅的主题 +String prefix = "/" + productId + "/" + deviceNum; +String sOtaTopic = prefix + "/ota/get"; +String sNtpTopic = prefix + "/ntp/get"; +String sPropertyTopic = prefix + "/property/get"; +String sFunctionTopic = prefix + "/function/get"; +String sPropertyOnline = prefix + "/property-online/get"; +String sFunctionOnline = prefix + "/function-online/get"; +String sMonitorTopic = prefix + "/monitor/get"; +// 发布的主题 +String pInfoTopic = prefix + "/info/post"; +String pNtpTopic = prefix + "/ntp/post"; +String pPropertyTopic = prefix + "/property/post"; +String pFunctionTopic = prefix + "/function/post"; +String pMonitorTopic = prefix + "/monitor/post"; +String pEventTopic = prefix + "/event/post"; + +// 物模型-属性处理 +void processProperty(String payload) +{ + StaticJsonDocument<1024> doc; + DeserializationError error = deserializeJson(doc, payload); + if (error) + { + Serial.print(F("deserializeJson() failed: ")); + Serial.println(error.f_str()); + return; + } + for (JsonObject object : doc.as()) + { + // 匹配云端定义的属性(不包含属性中的监测数据) + const char *id = object["id"]; + const char *value = object["value"]; + printMsg((String)id + ":" + (String)value); + } + // 最后发布属性,服务端订阅存储(重要) + publishProperty(payload); +} + +// 物模型-功能处理 +void processFunction(String payload) +{ + StaticJsonDocument<1024> doc; + DeserializationError error = deserializeJson(doc, payload); + if (error) + { + Serial.print(F("deserializeJson() failed: ")); + Serial.println(error.f_str()); + return; + } + for (JsonObject object : doc.as()) + { + // 匹配云端定义的功能 + const char *id = object["id"]; + const char *value = object["value"]; + if (strcmp(id, "switch") == 0) + { + printMsg("开关 switch:" + (String)value); + } + else if (strcmp(id, "gear") == 0) + { + printMsg("档位 gear:" + (String)value); + } + else if (strcmp(id, "light_color") == 0) + { + printMsg("灯光颜色 light_color:" + (String)value); + } + else if (strcmp(id, "message") == 0) + { + printMsg("屏显消息 message:" + (String)value); + } + else if (strcmp(id, "report_monitor") == 0) + { + String msg = randomPropertyData(); + printMsg("订阅到上报监测数据指令,上报数据:"); + printMsg(msg); + publishProperty(msg); + } + } + // 最后发布功能,服务端订阅存储(重要) + publishFunction(payload); +} + +// Mqtt回调 +void callback(char *topic, byte *payload, unsigned int length) +{ + blink(); + printMsg("接收数据:"); + String data = ""; + for (int i = 0; i < length; i++) + { + Serial.print((char)payload[i]); + data += (char)payload[i]; + } + + if (strcmp(topic, sOtaTopic.c_str()) == 0) + { + printMsg("订阅到设备升级指令..."); + } + else if (strcmp(topic, sNtpTopic.c_str()) == 0) + { + printMsg("订阅到NTP时间..."); + StaticJsonDocument<256> doc; + DeserializationError error = deserializeJson(doc, payload); + if (error) + { + Serial.print(F("deserializeJson() failed: ")); + Serial.println(error.f_str()); + return; + } + + float deviceSendTime = doc["deviceSendTime"]; + float serverSendTime = doc["serverSendTime"]; + float serverRecvTime = doc["serverRecvTime"]; + float deviceRecvTime = millis(); + float now = (serverSendTime + serverRecvTime + deviceRecvTime - deviceSendTime) / 2; + printMsg("当前时间:" + String(now, 0)); + } + else if (strcmp(topic, sPropertyTopic.c_str()) == 0 || strcmp(topic, sPropertyOnline.c_str()) == 0) + { + printMsg("订阅到属性指令..."); + processProperty(data); + } + else if (strcmp(topic, sFunctionTopic.c_str()) == 0 || strcmp(topic, sFunctionOnline.c_str()) == 0) + { + printMsg("订阅到功能指令..."); + processFunction(data); + } + else if (strcmp(topic, sMonitorTopic.c_str()) == 0) + { + printMsg("订阅到实时监测指令..."); + StaticJsonDocument<128> doc; + DeserializationError error = deserializeJson(doc, payload); + if (error) + { + Serial.print(F("deserializeJson() failed: ")); + Serial.println(error.f_str()); + return; + } + monitorCount = doc["count"]; + monitorInterval = doc["interval"]; + } +} + +// 连接wifi +void connectWifi() +{ + printMsg("连接 "); + Serial.print(wifiSsid); + WiFi.mode(WIFI_STA); + WiFi.begin(wifiSsid, wifiPwd); + while (WiFi.status() != WL_CONNECTED) + { + delay(500); + Serial.print("."); + } + printMsg("WiFi连接成功"); + printMsg("IP地址: "); + Serial.print(WiFi.localIP()); +} + +// 连接mqtt +void connectMqtt() +{ + printMsg("连接Mqtt服务器..."); + // 生成mqtt认证密码(密码 = mqtt密码 & 用户ID & 过期时间) + String password = generationPwd(); + String encryptPassword = encrypt(password, mqttSecret, wumei_iv); + printMsg("密码(已加密):" + encryptPassword); + + mqttClient.setClient(wifiClient); + mqttClient.setServer(mqttHost, mqttPort); + mqttClient.setCallback(callback); + mqttClient.setBufferSize(1024); + mqttClient.setKeepAlive(10); + //连接(客户端ID = 设备编号 & 产品ID) + String clientId = deviceNum + "&" + productId; + bool connectResult = mqttClient.connect(clientId.c_str(), mqttUserName, encryptPassword.c_str()); + if (connectResult) + { + printMsg("连接成功"); + // 订阅(OTA、NTP、属性、功能、实时监测) + mqttClient.subscribe(sOtaTopic.c_str(), 1); + mqttClient.subscribe(sNtpTopic.c_str(), 1); + mqttClient.subscribe(sPropertyTopic.c_str(), 1); + mqttClient.subscribe(sFunctionTopic.c_str(), 1); + mqttClient.subscribe(sPropertyOnline.c_str(), 1); + mqttClient.subscribe(sFunctionOnline.c_str(), 1); + mqttClient.subscribe(sMonitorTopic.c_str(), 1); + printMsg("订阅主题:" + sOtaTopic); + printMsg("订阅主题:" + sNtpTopic); + printMsg("订阅主题:" + sPropertyTopic); + printMsg("订阅主题:" + sFunctionTopic); + printMsg("订阅主题:" + sPropertyOnline); + printMsg("订阅主题:" + sFunctionOnline); + printMsg("订阅主题:" + sMonitorTopic); + // 发布设备信息 + publishInfo(); + } + else + { + printMsg("连接失败, rc="); + Serial.print(mqttClient.state()); + } +} + +// 1.发布设备信息 +void publishInfo() +{ + StaticJsonDocument<256> doc; + doc["rssi"] = WiFi.RSSI(); + doc["firmwareVersion"] = firmwareVersion; + doc["status"] = 3; // (1-未激活,2-禁用,3-在线,4-离线) + doc["userId"] = (String)userId; + + printMsg("发布设备信息:"); + serializeJson(doc, Serial); + String output; + serializeJson(doc, output); + mqttClient.publish(pInfoTopic.c_str(), output.c_str()); +} + +// 2.发布时钟同步信,用于获取当前时间(可选) +void publishNtp() +{ + StaticJsonDocument<128> doc; + doc["deviceSendTime"] = millis(); + + printMsg("发布NTP信息:"); + serializeJson(doc, Serial); + String output; + serializeJson(doc, output); + mqttClient.publish(pNtpTopic.c_str(), output.c_str()); +} + +// 3.发布属性 +void publishProperty(String msg) +{ + printMsg("发布属性:" + msg); + mqttClient.publish(pPropertyTopic.c_str(), msg.c_str()); +} + +// 4.发布功能 +void publishFunction(String msg) +{ + printMsg("发布功能:" + msg); + mqttClient.publish(pFunctionTopic.c_str(), msg.c_str()); +} + +// 5.发布事件 +void publishEvent() +{ + // 匹配云端的事件 + StaticJsonDocument<512> doc; + JsonObject objTmeperature = doc.createNestedObject(); + objTmeperature["id"] = "height_temperature"; + objTmeperature["value"] = "40"; + objTmeperature["remark"] = "温度过高警告"; + + JsonObject objException = doc.createNestedObject(); + objException["id"] = "exception"; + objException["value"] = "异常消息,消息内容XXXXXXXX"; + objException["remark"] = "设备发生错误"; + + printMsg("发布事件:"); + serializeJson(doc, Serial); + String output; + serializeJson(doc, output); + mqttClient.publish(pEventTopic.c_str(), output.c_str()); +} + +// 6.发布实时监测数据 +void publishMonitor() +{ + String msg = randomPropertyData(); + // 发布为实时监测数据,不会存储 + printMsg("发布实时监测数据:" + msg); + mqttClient.publish(pMonitorTopic.c_str(), msg.c_str()); +} + +// 随机生成监测值 +String randomPropertyData() +{ + // 匹配云端定义的监测数据,随机数代替监测结果 + float randFloat = 0; + int randInt = 0; + StaticJsonDocument<1024> doc; + JsonObject objTmeperature = doc.createNestedObject(); + objTmeperature["id"] = "temperature"; + randFloat = random(1000, 3000); + objTmeperature["value"] = (String)(randFloat / 100); + objTmeperature["remark"] = (String)millis(); + + JsonObject objHumidity = doc.createNestedObject(); + objHumidity["id"] = "humidity"; + randFloat = random(3000, 6000); + objHumidity["value"] = (String)(randFloat / 100); + objHumidity["remark"] = (String)millis(); + + JsonObject objCo2 = doc.createNestedObject(); + objCo2["id"] = "co2"; + randInt = random(400, 1000); + objCo2["value"] = (String)(randInt); + objCo2["remark"] = (String)millis(); + + JsonObject objBrightness = doc.createNestedObject(); + objBrightness["id"] = "brightness"; + randInt = random(1000, 10000); + objBrightness["value"] = (String)(randInt); + objBrightness["remark"] = (String)millis(); + + printMsg("随机生成监测数据值:"); + serializeJson(doc, Serial); + String output; + serializeJson(doc, output); + return output; +} + +// 生成密码 +String generationPwd() +{ + String jsonTime = getTime(); + printMsg("getTime()= " + jsonTime); + + // 128字节内存池容量 + StaticJsonDocument<128> doc; + // 解析JSON + DeserializationError error = deserializeJson(doc, jsonTime); + if (error) + { + printMsg("Json解析失败:"); + Serial.print(error.f_str()); + return ""; + } + // 获取当前时间 + float deviceSendTime = doc["deviceSendTime"]; + float serverSendTime = doc["serverSendTime"]; + float serverRecvTime = doc["serverRecvTime"]; + float deviceRecvTime = millis(); + float now = (serverSendTime + serverRecvTime + deviceRecvTime - deviceSendTime) / 2; + + // 过期时间 = 当前时间 + 1小时 + float expireTime = now + 1 * 60 * 60 * 1000; + String password = (String)mqttPwd + "&" + userId + "&" + String(expireTime, 0); + printMsg("密码(未加密):" + password); + + return password; +} + +// HTTP获取时间 +String getTime() +{ + while (WiFi.status() == WL_CONNECTED) + { + HTTPClient http; + printMsg("获取时间..."); + + if (http.begin(wifiClient, (ntpServer + (String)millis()).c_str())) + { + // 发送请求 + int httpCode = http.GET(); + if (httpCode > 0) + { + if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) + { + g_time = http.getString(); + printMsg("获取时间成功,data:"); + Serial.print(g_time); + return g_time; + } + } + else + { + printMsg("获取时间失败,error:"); + Serial.printf(http.errorToString(httpCode).c_str()); + } + http.end(); + } + else + { + printMsg("连接Http失败"); + } + delay(500); + } +} + +//打印提示信息 +void printMsg(String msg) +{ + Serial.print("\r\n["); + Serial.print(millis()); + Serial.print("ms]"); + Serial.print(msg); +} + +// 控制指示灯闪烁 +void blink() +{ + printMsg("指示灯闪烁..."); + pinMode(15, OUTPUT); + for (int i = 0; i < 2; i++) + { + digitalWrite(15, HIGH); + delay(200); + digitalWrite(15, LOW); + delay(200); + } +} + +// 加密 (AES-CBC-128-pkcs5padding) +String encrypt(String plain_data, char *wumei_key, char *wumei_iv) +{ + int i; + // pkcs7padding填充 Block Size : 16 + int len = plain_data.length(); + + int n_blocks = len / 16 + 1; + uint8_t n_padding = n_blocks * 16 - len; + uint8_t data[n_blocks * 16]; + memcpy(data, plain_data.c_str(), len); + for (i = len; i < n_blocks * 16; i++) + { + data[i] = n_padding; + } + uint8_t key[16], iv[16]; + uint8_t crypt_data[3 * 16] = {0}; + + memcpy(key, wumei_key, 16); + memcpy(iv, wumei_iv, 16); + + memset(crypt_data, 0, 48); + + len = n_blocks * 16; + // 加密 + mbedtls_aes_context aes_ctx; + mbedtls_aes_init(&aes_ctx); + mbedtls_aes_setkey_enc(&aes_ctx, key, 128); + mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_ENCRYPT, len, iv, data, crypt_data); + + // Base64编码 + char encoded_data[base64_enc_len(len)]; + base64_encode(encoded_data, (char *)crypt_data, len); + + return String(encoded_data); +} + +// 解密 (AES-CBC-128-pkcs5padding) +String decrypt(String encoded_data_str, char *wumei_key, char *wumei_iv) +{ + int input_len = encoded_data_str.length(); + char *encoded_data = const_cast(encoded_data_str.c_str()); + int len = base64_dec_len(encoded_data, input_len); + uint8_t data[len]; + base64_decode((char *)data, encoded_data, input_len); + uint8_t key[16], iv[16]; + memcpy(key, wumei_key, 16); + memcpy(iv, wumei_iv, 16); + int n_blocks = len / 16; + + uint8_t n_padding = data[n_blocks * 16 - 1]; + len = n_blocks * 16 - n_padding; + char plain_data[len + 1]; + + //密文空间 + mbedtls_aes_context aes_ctx; + mbedtls_aes_init(&aes_ctx); + + //设置解密密钥 + mbedtls_aes_setkey_dec(&aes_ctx, key, 128); + mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_DECRYPT, 48, iv, (unsigned char *)encoded_data, (unsigned char *)plain_data); + mbedtls_aes_free(&aes_ctx); + // PKCS#7 Padding 填充 + plain_data[len] = '\0'; + return String(plain_data); +} diff --git a/sdk/arduino/wumei/Helper.h b/sdk/arduino/Arduino-ESP8266/Helper.h similarity index 100% rename from sdk/arduino/wumei/Helper.h rename to sdk/arduino/Arduino-ESP8266/Helper.h