From 517109498897449df26b7250af3e9908bcf1f1be Mon Sep 17 00:00:00 2001 From: kerwincui <164770707@qq.com> Date: Mon, 1 Aug 2022 01:31:37 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9EArduino=E5=9B=BA=E4=BB=B6-?= =?UTF-8?q?=E5=BE=85=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sdk/Arduino/ApConfig/ApConfig.ino | 14 +- sdk/Arduino/WumeiArduino/Apconfig.cpp | 164 ++++++++++++++ sdk/Arduino/WumeiArduino/Apconfig.h | 15 ++ sdk/Arduino/WumeiArduino/Auth.cpp | 182 ++++++++++++++++ sdk/Arduino/WumeiArduino/Auth.h | 28 +++ sdk/Arduino/WumeiArduino/Base64.cpp | 137 ++++++++++++ sdk/Arduino/WumeiArduino/Base64.h | 79 +++++++ sdk/Arduino/WumeiArduino/Common.cpp | 120 +++++++++++ sdk/Arduino/WumeiArduino/Common.h | 50 +++++ sdk/Arduino/WumeiArduino/Mqtt.cpp | 250 ++++++++++++++++++++++ sdk/Arduino/WumeiArduino/Mqtt.h | 55 +++++ sdk/Arduino/WumeiArduino/WumeiArduino.ino | 93 ++++++++ 12 files changed, 1180 insertions(+), 7 deletions(-) create mode 100644 sdk/Arduino/WumeiArduino/Apconfig.cpp create mode 100644 sdk/Arduino/WumeiArduino/Apconfig.h create mode 100644 sdk/Arduino/WumeiArduino/Auth.cpp create mode 100644 sdk/Arduino/WumeiArduino/Auth.h create mode 100644 sdk/Arduino/WumeiArduino/Base64.cpp create mode 100644 sdk/Arduino/WumeiArduino/Base64.h create mode 100644 sdk/Arduino/WumeiArduino/Common.cpp create mode 100644 sdk/Arduino/WumeiArduino/Common.h create mode 100644 sdk/Arduino/WumeiArduino/Mqtt.cpp create mode 100644 sdk/Arduino/WumeiArduino/Mqtt.h create mode 100644 sdk/Arduino/WumeiArduino/WumeiArduino.ino diff --git a/sdk/Arduino/ApConfig/ApConfig.ino b/sdk/Arduino/ApConfig/ApConfig.ino index 054ea362..c204e5ff 100644 --- a/sdk/Arduino/ApConfig/ApConfig.ino +++ b/sdk/Arduino/ApConfig/ApConfig.ino @@ -8,7 +8,7 @@ const char *ap_password = ""; //开放式网络 char sta_ssid[32] = {0}; char sta_password[64] = {0}; -char sta_user_name[64] = {0}; +char sta_user_id[32] = {0}; IPAddress local_IP(192, 168, 4, 1); IPAddress gateway(192, 168, 4, 1); @@ -99,20 +99,20 @@ void connectWifi() void handleConfig() { printMsg("进入配网......"); - // wifi名称、wifi密码、用户名 - if (server.hasArg("SSID") && server.hasArg("password") && server.hasArg("userName")) + // wifi名称、wifi密码、用户编号 + if (server.hasArg("SSID") && server.hasArg("password") && server.hasArg("userId")) { strcpy(sta_ssid, server.arg("SSID").c_str()); strcpy(sta_password, server.arg("password").c_str()); - strcpy(sta_user_name, server.arg("userName").c_str()); + strcpy(sta_user_id, server.arg("userId").c_str()); printMsg("收到WIFI名称:" + (String)sta_ssid); printMsg("收到WIFI密码:" + (String)sta_password); - printMsg("收到用户名称:" + (String)sta_user_name); + printMsg("收到用户编号:" + (String)sta_user_id); } else { - printMsg("配网必须传递用户名、WIFI名称和WIFI密码,配网失败"); - server.send(500, "text/plain;charset=utf-8", "配网必须传递用户名、WIFI名称和WIFI密码,配网失败"); + printMsg("配网必须传递用户编号、WIFI名称和WIFI密码,配网失败"); + server.send(500, "text/plain;charset=utf-8", "配网必须传递用户编号、WIFI名称和WIFI密码,配网失败"); return; } // 可选字段 diff --git a/sdk/Arduino/WumeiArduino/Apconfig.cpp b/sdk/Arduino/WumeiArduino/Apconfig.cpp new file mode 100644 index 00000000..e6153fa1 --- /dev/null +++ b/sdk/Arduino/WumeiArduino/Apconfig.cpp @@ -0,0 +1,164 @@ +// /*********************************************************** +// * function: 设备配网 +// * board: esp8266 core for arduino v3.0.2 +// * library: PubSubClient2.8.0 & ArduinoJson6.19.1 +// * source: https://github.com/kerwincui/wumei-smart +// ***********************************************************/ + +// #include +// #include + +// String randomName="wumei-device"+(String)random(1000); +// const char *ap_ssid =randomName.c_str(); +// const char *ap_password = ""; //开放式网络 + +// char sta_ssid[32] = {0}; +// char sta_password[64] = {0}; +// char sta_user_id[32] = {0}; + +// IPAddress local_IP(192, 168, 4, 1); +// IPAddress gateway(192, 168, 4, 1); +// IPAddress subnet(255, 255, 255, 0); + +// void initApConfig(); +// void initWebServer(); +// void handleConfig(); +// void handleStatus(); +// void handleNotFound(); + +// ESP8266WebServer server(80); + +// // void setup(void) +// // { +// // //打开串行端口: +// // Serial.begin(115200); +// // // AP模式 +// // initApConfig(); +// // // web服务 +// // initWebServer(); +// // } + +// // void loop(void) +// // { +// // // Web服务端 +// // server.handleClient(); +// // } + +// /** +// * AP模式 +// */ +// void initApConfig() +// { +// WiFi.mode(WIFI_AP_STA); +// WiFi.softAPConfig(local_IP, gateway, subnet); +// WiFi.softAP(ap_ssid, ap_password); +// printMsg("已启动AP配网,IP地址:" + WiFi.softAPIP().toString()+", 热点名称:"+(String)ap_ssid); +// } + +// /** +// * 初始化webserver配置 +// */ +// void initWebServer() +// { +// server.on("/status", HTTP_GET, handleStatus); +// server.on("/config", HTTP_POST, handleConfig); +// server.onNotFound(handleNotFound); +// server.enableCORS(true); +// server.begin(); +// printMsg("HTTP服务已启动"); +// } + +// /** +// * 连接WIFI +// */ +// void connectWifi() +// { +// printMsg("连接WIFI"); +// WiFi.begin(sta_ssid, sta_password); +// int cnt = 0; +// while (WiFi.status() != WL_CONNECTED) +// { +// delay(500); +// cnt++; +// Serial.print("."); +// if (cnt >= 30) +// { +// printMsg("设备连接WIFI超时,请重新配网"); +// return; +// } +// } +// server.close(); +// WiFi.softAPdisconnect(false); +// printMsg("Http服务和热点已关闭,设备已连接WIFI"); +// } + +// /** +// * 检测设备状态 +// */ +// void handleStatus(){ +// server.send(200, "text/plain;charset=utf-8", "AP配网已准备就绪"); +// } + +// /** +// * 配网:下发配置信息 +// */ +// void handleConfig() +// { +// printMsg("进入配网......"); +// // wifi名称、wifi密码、用户编号 +// if (server.hasArg("SSID") && server.hasArg("password") && server.hasArg("userId")) +// { +// strcpy(sta_ssid, server.arg("SSID").c_str()); +// strcpy(sta_password, server.arg("password").c_str()); +// strcpy(sta_user_id, server.arg("userId").c_str()); +// printMsg("收到WIFI名称:" + (String)sta_ssid); +// printMsg("收到WIFI密码:" + (String)sta_password); +// printMsg("收到用户编号:" + (String)sta_user_id); +// } +// else +// { +// printMsg("配网必须传递用户编号、WIFI名称和WIFI密码,配网失败"); +// server.send(500, "text/plain;charset=utf-8", "配网必须传递用户编号、WIFI名称和WIFI密码,配网失败"); +// return; +// } +// // 可选字段 +// if (server.hasArg("deviceNum")) +// { +// printMsg("收到设备编号:" + server.arg("deviceNum")); +// } +// if (server.hasArg("extra")) +// { +// printMsg("收到补充信息:" + server.arg("extra")); +// } +// // TODO 可增加设备连接WIFI测试 + +// server.send(200, "text/plain;charset=utf-8", "设备已更新WIFI配置,开始连接WIFI..."); +// connectWifi(); +// } + +// void handleNotFound() +// { +// printMsg("进入预检请求或请求地址找不到"); +// if (server.method() == HTTP_OPTIONS) +// { +// // 处理浏览器跨域问题 +// server.sendHeader("Access-Control-Max-Age", "10000"); +// server.sendHeader("Access-Control-Allow-Methods", "PUT,POST,GET,OPTIONS"); +// server.sendHeader("Access-Control-Allow-Headers", "*"); +// server.send(204); +// } +// else +// { +// server.send(404, "text/plain;charset=utf-8", "请求的地址找不到或无法访问"); +// } +// } + +// //打印提示信息 +// void printMsg(String msg) +// { +// Serial.print("\r\n["); +// Serial.print(millis()); +// Serial.print("ms]"); +// Serial.print(msg); +// } + diff --git a/sdk/Arduino/WumeiArduino/Apconfig.h b/sdk/Arduino/WumeiArduino/Apconfig.h new file mode 100644 index 00000000..dbe2f842 --- /dev/null +++ b/sdk/Arduino/WumeiArduino/Apconfig.h @@ -0,0 +1,15 @@ +/*********************************************************** + * function: 设备配网 + * board: esp8266 core for arduino v3.0.2 + * library: PubSubClient2.8.0 & ArduinoJson6.19.1 + * source: https://github.com/kerwincui/wumei-smart + ***********************************************************/ + +#ifndef _APCONFIG_H +#define _APCONFIG_H + +#include "Base64.h" +#include + + +#endif diff --git a/sdk/Arduino/WumeiArduino/Auth.cpp b/sdk/Arduino/WumeiArduino/Auth.cpp new file mode 100644 index 00000000..03322f13 --- /dev/null +++ b/sdk/Arduino/WumeiArduino/Auth.cpp @@ -0,0 +1,182 @@ +/*********************************************************** + * function: 设备认证 + * board: esp8266 core for arduino v3.0.2 + * library: PubSubClient2.8.0 & ArduinoJson6.19.1 + * source: https://github.com/kerwincui/wumei-smart + ***********************************************************/ + +#include "Auth.h" + +/* + * 连接MQTT,加密认证 + */ +void connectMqtt() +{ + printMsg("连接Mqtt服务器..."); + mqttClient.setClient(wifiClient); + mqttClient.setServer(mqttHost, mqttPort); + mqttClient.setBufferSize(1024); + mqttClient.setKeepAlive(5); + // 设置Mqtt回调函数 + mqttClient.setCallback(mqttCallback); + + // 生成mqtt加密密码 + String aesPassword = generationAESPwd(); + // 连接 设备mqtt客户端Id格式为:认证类型(E=加密、S=简单) & 设备编号 & 产品ID & 用户ID + String clientId = "E&" + deviceNum + "&" + productId + "&" + userId; + bool connectResult = mqttClient.connect(clientId.c_str(), mqttUserName, aesPassword.c_str()); + if (connectResult) + { + printMsg("连接Mqtt成功"); + // 订阅(OTA、NTP、属性、功能、实时监测、信息) + mqttClient.subscribe(sInfoTopic.c_str(), 1); + 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("订阅主题完成"); + // 发布设备信息,设备上电都需要发布一次 + publishInfo(); + } + else + { + printMsg("连接失败, rc="); + Serial.print(mqttClient.state()); + } +} + +/* + * 生成加密密码 + */ +String generationAESPwd() +{ + // 获取NTP时间 + String jsonTime = getTime(); + StaticJsonDocument<128> doc; + DeserializationError error = deserializeJson(doc, jsonTime); + if (error) + { + printMsg("Json解析失败:"); + Serial.print(error.f_str()); + return ""; + } + // 获取NTP时间=(设备发送消息时间 + 服务器接收消息时间 + 服务器发送消息时间 - 设备接收时间 最后除于2 ) + 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; + // 加密认证,密码格式为:mqtt密码 & 过期时间 & 授权码(可选),如果产品启用了授权码就必须加上 + String password = ""; + if (authCode == "") + { + password = (String)mqttPwd + "&" + String(expireTime, 0); + } + else + { + password = (String)mqttPwd + "&" + String(expireTime, 0) + "&" + authCode; + } + // 密码加密 + printMsg("密码(未加密):" + password); + String encryptPassword = encrypt(password, mqttSecret, wumei_iv); + printMsg("密码(已加密):" + encryptPassword); + return encryptPassword; +} + +/* + * 通过HTTP获取NTP时间 + */ +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) + { + printMsg("获取时间成功,data:"); + Serial.print(http.getString()); + return http.getString(); + } + } + else + { + printMsg("获取时间失败,error:"); + Serial.printf(http.errorToString(httpCode).c_str()); + } + http.end(); + } + else + { + printMsg("连接Http失败"); + } + delay(500); + } +} + +/* + * AES加密 (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]; + 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编码 + len = n_blocks * 16; + char encoded_data[base64_enc_len(len)]; + base64_encode(encoded_data, (char *)data, len); + return String(encoded_data); +} + +/* + * AES解密 (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; + 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); + plain_data[len] = '\0'; + return String(plain_data); +} diff --git a/sdk/Arduino/WumeiArduino/Auth.h b/sdk/Arduino/WumeiArduino/Auth.h new file mode 100644 index 00000000..4a118810 --- /dev/null +++ b/sdk/Arduino/WumeiArduino/Auth.h @@ -0,0 +1,28 @@ +/*********************************************************** + * function: 设备认证 + * board: esp8266 core for arduino v3.0.2 + * library: PubSubClient2.8.0 & ArduinoJson6.19.1 + * source: https://github.com/kerwincui/wumei-smart + ***********************************************************/ + +#ifndef _AUTH9_H +#define _AUTH9_H + +#include "Common.h" +#include "Mqtt.h" +#include "Base64.h" +#include +#include + +// 连接mqtt, AES加密认证 +void connectMqtt(); +// 生成加密密码 +String generationAESPwd(); +// 获取时间 +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); + +#endif diff --git a/sdk/Arduino/WumeiArduino/Base64.cpp b/sdk/Arduino/WumeiArduino/Base64.cpp new file mode 100644 index 00000000..71c40863 --- /dev/null +++ b/sdk/Arduino/WumeiArduino/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/WumeiArduino/Base64.h b/sdk/Arduino/WumeiArduino/Base64.h new file mode 100644 index 00000000..56d3271e --- /dev/null +++ b/sdk/Arduino/WumeiArduino/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/WumeiArduino/Common.cpp b/sdk/Arduino/WumeiArduino/Common.cpp new file mode 100644 index 00000000..e1cce087 --- /dev/null +++ b/sdk/Arduino/WumeiArduino/Common.cpp @@ -0,0 +1,120 @@ +/*********************************************************** + * function: 设备交互 + * board: esp8266 core for arduino v3.0.2 + * library: PubSubClient2.8.0 & ArduinoJson6.19.1 + * source: https://github.com/kerwincui/wumei-smart + ***********************************************************/ + +#include "Common.h" + +WiFiClient wifiClient; +PubSubClient mqttClient; +float rssi = 0; +char wumei_iv[17] = "wumei-smart-open"; +int monitorCount = 0; +long monitorInterval = 1000; + +//==================================== 这是需要配置的项 =============================== +// Wifi配置 +char *wifiSsid = "wumei"; +char *wifiPwd = "wumei-smart"; + +// 设备信息配置 +String deviceNum = "D6329VL548866"; +String userId = "1"; +String productId = "41"; +float firmwareVersion = 1.0; +// 经度和纬度可选,如果产品使用设备定位,则必须传 +float latitude=0; +float longitude=0; + +// Mqtt配置 +char *mqttHost = "wumei.live"; +int mqttPort = 1883; +char *mqttUserName = "wumei-smart"; +char *mqttPwd = "PHYFED93WSFF1DAS"; +char mqttSecret[17] = "K2V5DE28XNUU3497"; +// 产品启用授权码,则授权码不能为空 +String authCode=""; + +// NTP地址(用于获取时间,修改为自己部署项目的接口地址) +String ntpServer = "http://wumei.live:8080/iot/tool/ntp?deviceSendTime="; + + +// 连接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()); +} + +// 随机生成监测值 +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; +} + +//打印提示信息 +void printMsg(String msg) +{ + Serial.print("\r\n["); + Serial.print(millis()); + Serial.print("ms]"); + Serial.print(msg); +} + +// 控制指示灯闪烁 +void blink() +{ + printMsg("指示灯闪烁..."); + int led=15; + pinMode(led, OUTPUT); + for (int i = 0; i < 2; i++) + { + digitalWrite(led, HIGH); + delay(100); + digitalWrite(led, LOW); + delay(100); + } +} diff --git a/sdk/Arduino/WumeiArduino/Common.h b/sdk/Arduino/WumeiArduino/Common.h new file mode 100644 index 00000000..73cbd6c0 --- /dev/null +++ b/sdk/Arduino/WumeiArduino/Common.h @@ -0,0 +1,50 @@ +/*********************************************************** + * function: 设备交互 + * board: esp8266 core for arduino v3.0.2 + * library: PubSubClient2.8.0 & ArduinoJson6.19.1 + * source: https://github.com/kerwincui/wumei-smart + ***********************************************************/ + +#ifndef _COMMON_H +#define _COMMON_H + +#include "Base64.h" +#include +#include +#include +#include // 版本2.8.0 +#include // 版本6.19.1 + +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 float firmwareVersion; // 固件版本 +extern float latitude; // 设备精度 +extern float longitude; // 设备维度 +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 authCode; // 产品授权码,产品未启用时为空字符串 +extern String ntpServer; // NTP服务地址,用于获取当前时间 +extern int monitorCount; // 发布监测数据的最大次数 +extern long monitorInterval; // 发布监测数据的间隔,默认1000毫秒 + +// 连接WIFI +void connectWifi(); +// 随机生成监测值 +String randomPropertyData(); +//打印提示信息 +void printMsg(String tips); +// 控制指示灯闪烁 +void blink(); + +#endif diff --git a/sdk/Arduino/WumeiArduino/Mqtt.cpp b/sdk/Arduino/WumeiArduino/Mqtt.cpp new file mode 100644 index 00000000..23715fb1 --- /dev/null +++ b/sdk/Arduino/WumeiArduino/Mqtt.cpp @@ -0,0 +1,250 @@ +/*********************************************************** + * function: 设备交互 + * board: esp8266 core for arduino v3.0.2 + * library: PubSubClient2.8.0 & ArduinoJson6.19.1 + * source: https://github.com/kerwincui/wumei-smart + ***********************************************************/ + +#include "Mqtt.h" + +// 订阅的主题 +String prefix = "/" + productId + "/" + deviceNum; +String sInfoTopic = prefix + "/info/get"; +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 mqttCallback(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("订阅到设备升级指令..."); + StaticJsonDocument<256> doc; + DeserializationError error = deserializeJson(doc, payload); + if (error) + { + Serial.print(F("deserializeJson() failed: ")); + Serial.println(error.f_str()); + return; + } + String newVersion = doc["version"]; + String downloadUrl = doc["downloadUrl"]; + printMsg("固件版本:"+newVersion); + printMsg("下载地址:"+downloadUrl); + } + else if (strcmp(topic, sInfoTopic.c_str()) == 0) + { + printMsg("订阅到设备信息..."); + // 发布设备信息 + publishInfo(); + } + 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; + } + // 计算设备当前时间:(${serverRecvTime} + ${serverSendTime} + ${deviceRecvTime} - ${deviceSendTime}) / 2 + 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"]; + } +} + +// 1.发布设备信息 +void publishInfo() +{ + StaticJsonDocument<256> doc; + doc["rssi"] = WiFi.RSSI(); + doc["firmwareVersion"] = firmwareVersion; + doc["status"] = 3; // (1-未激活,2-禁用,3-在线,4-离线) + doc["userId"] = (String)userId; + doc["longitude"] = longitude; //经度 可选 + doc["latitude"] = latitude; // 纬度 可选 + // 设备摘要,可选(自定义配置信息) + JsonObject summary = doc.createNestedObject("summary"); + summary["name"]="wumei-smart"; + summary["chip"]="esp8266"; + summary["author"]="kerwincui"; + summary["version"]=1.6; + summary["create"]="2022-06-06"; + + 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()); +} diff --git a/sdk/Arduino/WumeiArduino/Mqtt.h b/sdk/Arduino/WumeiArduino/Mqtt.h new file mode 100644 index 00000000..9ece9de8 --- /dev/null +++ b/sdk/Arduino/WumeiArduino/Mqtt.h @@ -0,0 +1,55 @@ +/*********************************************************** + * function: 设备交互 + * board: esp8266 core for arduino v3.0.2 + * library: PubSubClient2.8.0 & ArduinoJson6.19.1 + * source: https://github.com/kerwincui/wumei-smart + ***********************************************************/ + +#ifndef _MQTT_H +#define _MQTT_H + +#include "Common.h" +#include +#include +#include +#include // 版本2.8.0 +#include // 版本6.19.1 + +// 订阅的主题 +extern String sInfoTopic; // 订阅设备信息 +extern String sOtaTopic; // 订阅OTA升级 +extern String sNtpTopic; // 订阅NTP时间 +extern String sPropertyTopic; // 订阅属性 +extern String sFunctionTopic; // 订阅功能 +extern String sPropertyOnline; // 订阅属性-在线模式 +extern String sFunctionOnline; // 订阅功能-在线模式 +extern String sMonitorTopic; // 订阅实时监测 +// 发布的主题 +extern String pInfoTopic; // 发布设备信息 +extern String pNtpTopic; // 发布NTP时间 +extern String pPropertyTopic; // 发布属性 +extern String pFunctionTopic; // 发布功能 +extern String pMonitorTopic; // 发布实时监测数据 +extern String pEventTopic; // 发布事件 + +// 发布设备信息 +void publishInfo(); +// 发布时钟同步信息 +void publishNtp(); +// 发布事件 +void publishEvent(); +// 发布实时监测数据 +void publishMonitor(); +// 发布属性 +void publishProperty(String msg); +// 发布功能 +void publishFunction(String msg); + +// Mqtt回调 +void mqttCallback(char *topic, byte *payload, unsigned int length); +// 属性处理 +void processProperty(String payload); +// 功能处理 +void processFunction(String payload); + +#endif diff --git a/sdk/Arduino/WumeiArduino/WumeiArduino.ino b/sdk/Arduino/WumeiArduino/WumeiArduino.ino new file mode 100644 index 00000000..b64f685e --- /dev/null +++ b/sdk/Arduino/WumeiArduino/WumeiArduino.ino @@ -0,0 +1,93 @@ +/*********************************************************** + * function: 程序入口 + * board: esp8266 core for arduino v3.0.2 + * library: PubSubClient2.8.0 & ArduinoJson6.19.1 + * source: https://github.com/kerwincui/wumei-smart + ***********************************************************/ + +#include "Common.h" +#include "Auth.h" +#include "Mqtt.h" + +long lastMqttConn; // 上次mqtt连接时间 +long lastPublishMonitor; // 上次发布监测数据时间 +long lastPublishSimulateData; // 上次发布测试数据时间 + +/** + * 启动 + */ +void setup() +{ + //打开串行端口: + Serial.begin(115200); + printMsg("wumei smart device starting..."); + // 连接Wifi + connectWifi(); + // 连接Mqtt + connectMqtt(); +} + +/** + * 循环执行 + */ +void loop() +{ + // Wifi重连 + wifiReconnectionClient(); + // 发布实时监测数据 + publicMonitorClient(); + // 发布模拟数据,测试用 + publishSimulateDataClient(); +} + +/* + * Wifi掉线重连 + */ +void wifiReconnectionClient() +{ + if (WiFi.status() != WL_CONNECTED) + { + connectWifi(); + } +} + +/* + * 发布实时监测数据(非阻塞、间隔默认1秒) + */ +void publicMonitorClient() +{ + if (WiFi.status() == WL_CONNECTED && monitorCount > 0) + { + long now = millis(); + if (now - lastPublishMonitor > monitorInterval) + { + lastPublishMonitor = now; + monitorCount--; + publishMonitor(); + } + } +} + +/* + * 发布模拟数据(非阻塞、仅用于测试,间隔60秒) + */ +void publishSimulateDataClient() +{ + if (WiFi.status() == WL_CONNECTED) + { + long now = millis(); + if (now - lastPublishSimulateData > 60000) + { + lastPublishSimulateData = now; + printMsg("执行定时上报"); + // 发布事件 + publishEvent(); + // 发布时钟同步 + publishNtp(); + + // 发布属性(监测值) + String msg = randomPropertyData(); + publishProperty(msg); + } + } +}