STM32HAL库+ESP8266+cJSON+微信小程序_连接华为云物联网平台
实验使用资源:正点原子F407
USART1:PA9P、A10(串口打印调试)
USART3:PB10、PB11(WiFi模块)
DHT11:PG9(采集数据、上报)
LED0、1:PF9、PF10(根据收到的命令,控制亮灭)
显示屏(可有可无)
0 前置内容准备
以下内容请参考之前写过的博客
1 华为云物联网平台创建产品
1.1新建产品
- 在华为云设备接入IoTDA平台,点击左上角的【创建产品】,参考下图填写产品信息。
- 创建完成后进入创建的产品,进行产品模型设置。首先先创建一个服务,服务ID自己根据实际写。
- 点击【新增属性】,根据实际需求,添加需要交互的数据及其类型和访问方式。
- 点击【添加命令】,进行指令的设置,【下发参数】就是下发指令控制设备,【响应参数】就是读取设备上传信息。
1.2 新建设备
在【所有设备】界面,点击的【注册设备】添加设备
1.3 获取MQTT三元素
在新创建的设备中,点击查看MQTT连接参数,即可获取三元素
1.4 获取订阅Topic
在之前创建的产品中,Topic管理里面包含我们需要订阅的Topic
2 usart模块
- 将【stm32f1xx_it.c】里面的
void USART1_IRQHandler(void)
和void USART3_IRQHandler(void)
函数注释掉
- 将下面的代码粘贴到【usart.c】中的最下面的
/* USER CODE BEGIN 1 */
和/* USER CODE END 1 */
之间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
|
void atk_mw8266d_uart_printf(char *fmt, ...) { va_list ap; uint16_t len; va_start(ap, fmt); vsprintf((char *)g_uart_tx_buf, fmt, ap); va_end(ap); len = strlen((const char *)g_uart_tx_buf); HAL_UART_Transmit(&huart3, g_uart_tx_buf, len, HAL_MAX_DELAY); }
void atk_mw8266d_uart_rx_restart(void) { g_uart_rx_frame.sta.len = 0; g_uart_rx_frame.sta.finsh = 0; }
uint8_t *atk_mw8266d_uart_rx_get_frame(void) { if (g_uart_rx_frame.sta.finsh == 1) { g_uart_rx_frame.buf[g_uart_rx_frame.sta.len] = '\0'; return g_uart_rx_frame.buf; } else { return NULL; } }
uint16_t atk_mw8266d_uart_rx_get_frame_len(void) { if (g_uart_rx_frame.sta.finsh == 1) { return g_uart_rx_frame.sta.len; } else { return 0; } }
void USART1_IRQHandler(void) { #if SYS_SUPPORT_OS OSIntEnter(); #endif HAL_UART_IRQHandler(&huart1);
while (HAL_UART_Receive_IT(&huart1, (uint8_t *)g_rx_buffer, RXBUFFERSIZE) != HAL_OK) { }
#if SYS_SUPPORT_OS OSIntExit(); #endif }
void USART3_IRQHandler(void) {
HAL_UART_IRQHandler(&huart3); uint8_t tmp; if (__HAL_UART_GET_FLAG(&huart3, UART_FLAG_ORE) != RESET) { __HAL_UART_CLEAR_OREFLAG(&huart3); (void)huart3.Instance->SR; (void)huart3.Instance->DR; } if (__HAL_UART_GET_FLAG(&huart3, UART_FLAG_RXNE) != RESET) { HAL_UART_Receive(&huart3, &tmp, 1, HAL_MAX_DELAY); if (g_uart_rx_frame.sta.len < (256 - 1))
{ g_uart_rx_frame.buf[g_uart_rx_frame.sta.len] = tmp; g_uart_rx_frame.sta.len++; } else { g_uart_rx_frame.sta.len = 0; g_uart_rx_frame.buf[g_uart_rx_frame.sta.len] = tmp; g_uart_rx_frame.sta.len++; } } if (__HAL_UART_GET_FLAG(&huart3, UART_FLAG_IDLE) != RESET) { g_uart_rx_frame.sta.finsh = 1; __HAL_UART_CLEAR_IDLEFLAG(&huart3); } }
|
- 在【usart.c】上面的的
/* USER CODE BEGIN 0 */
和/* USER CODE END 0 */
之间加入下面的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| #include <stdarg.h> #include <stdio.h> #include <string.h>
#if 1 #pragma import(__use_no_semihosting)
struct __FILE { int handle;
};
FILE __stdout;
void _sys_exit(int x) { x = x; }
int fputc(int ch, FILE *f) { while((USART1->SR&0X40)==0); USART1->DR = (uint8_t) ch; return ch; } #endif
uint8_t g_rx_buffer[RXBUFFERSIZE];
|
- 在【usart.c】中的
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
函数中调整中断优先级,WiFi的usart3的高于串口的,同时添加usar使能
- 在【usart.h】中的
/* USER CODE BEGIN Private defines */
和/* USER CODE END Private defines */
之间加入下面的代码
1 2 3 4 5 6 7 8 9 10 11
| static struct { uint8_t buf[256]; struct { uint16_t len : 15; uint16_t finsh : 1; } sta; } g_uart_rx_frame = {0}; static uint8_t g_uart_tx_buf[1024]; #define RXBUFFERSIZE 1
|
- 在【usart.h】中的
/* USER CODE BEGIN Prototypes */
和/* USER CODE END Prototypes */
之间加入下面的代码
1 2 3 4
| void atk_mw8266d_uart_printf(char *fmt, ...); void atk_mw8266d_uart_rx_restart(void); uint8_t *atk_mw8266d_uart_rx_get_frame(void); uint16_t atk_mw8266d_uart_rx_get_frame_len(void);
|
3 WiFi模块
将提前编写好的esp8266.c/.h
文件分别加入Src和Inc文件夹,然后再在keil里将esp8266.c
文件加入工程。
4 CJSON模块移植
移植过程查看之前写的另一篇博客【CJSON模块】
5 WIFI连接华为云
- 创建wifi_HW.c/.h文件。
- 在wifi_HW.h中宏定义连接华为云相关参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #define WIFI_User "***" #define WIFI_Pass "***"
#define ESP8266_UserName "***" #define ESP8266_PassWord "***" #define ESP8266_ClientID "***" #define ESP8266_Domain_Name "***" #define ESP8266_Port 1883 #define ESP8266_Reconnect 1
#define HUAWEI_MQTT_ServiceID "***" #define HUAWEI_MQTT_DeviceID "***" #define HUAWEI_MQTT_commands "$oc/devices/***/sys/commands/#" #define HUAWEI_MQTT_commands_response "$oc/devices/***/sys/commands/response/request_id=" #define HUAWEI_MQTT_report "$oc/devices/***/sys/properties/report"
|
- 编写wifi_HW.c文件,调用esp8266.c中编写的函数连接华为云,然后将该函数名加入wifi_HW.h中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| void wifi_init(void){ uint8_t ret = 0; ret = ESP8266_Reset(); printf("1: %d\r\n",ret); ret = ESP8266_ATE(0); printf("2: %d\r\n",ret); ret = ESP8066_Mode(1); printf("3: %d\r\n",ret); ret = ESP8266_WiFi(WIFI_User, WIFI_Pass); printf("4: %d\r\n",ret); ret = ESP8266_MQTTUSERCFG(ESP8266_UserName, ESP8266_PassWord); printf("5: %d\r\n",ret); ret = ESP8266_MQTTCLIENTID(ESP8266_ClientID); printf("6: %d\r\n",ret); ret = ESP8266_MQTTCONN(ESP8266_Domain_Name,ESP8266_Port,ESP8266_Reconnect); printf("7: %d\r\n",ret); ret = ESP8266_MQTTSUB(HUAWEI_MQTT_commands); printf("8: %d\r\n",ret); }
|
6 数据交互
6.1 设备属性上报
参考华为云官方给出的MQTT属性上报样例,在wifi_HW.c中编写report_Json
函数,使用cJSON模块封装传感器数据,然后将其发送到云平台。
可以看出该消息的JSON格式:
(1) 在根对象中,有一个名为services的键,其值是一个数组。
(2) 在services数组中,有一个服务对象,它包含两个键:service_id和properties
(3) 在properties对象中,存放的键值对就是在产品中创建的属性。
根据这种格式,在report_Json
函数中封装JSON消息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| void report_Json(uint8_t temperature, uint8_t humidity,uint8_t adcx){ uint8_t cmd[1024]; char *str = NULL; int i = 0; uint8_t params_buf[1024]; uint16_t move_num = 0; cJSON *json = cJSON_CreateObject(); cJSON *properties_cjson = cJSON_CreateObject(); cJSON *service = cJSON_CreateObject(); cJSON *services_array = cJSON_CreateArray(); cJSON_AddNumberToObject(properties_cjson, "temperature", temperature); cJSON_AddNumberToObject(properties_cjson, "humidity", humidity); cJSON_AddNumberToObject(properties_cjson, "light", adcx); cJSON_AddStringToObject(service, "service_id", "yun"); cJSON_AddItemToObject(service, "properties", properties_cjson); cJSON_AddItemToObject(json, "services", services_array); cJSON_AddItemToArray(services_array, service); str = cJSON_PrintUnformatted(json); for(i = 0; *str != '\0'; i++){ params_buf[i] = *str; if(*(str + 1) == '"' || *(str + 1) == ','){ params_buf[++i] = '\\'; } str++; move_num++; } str = str - move_num; sprintf((char *)cmd,"AT+MQTTPUB=0,\""HUAWEI_MQTT_report"\",\"%s\",0,0\r\n",params_buf);
ESP8266_Sent_AT(cmd, "OK", 500); cJSON_Delete(json); if(str != NULL){ free(str); str = NULL; } }
|
6.2 云端命令下发
参考华为云官方给出的MQTT下行请求样例,在wifi_HW.c中编写rcv_json函数,解析云平台下发的JSON消息字符串,如果成功接收后,需要给云平台返回接收成功的消息命令。
从给出的样例中可以看出,下发的指令在paras键所对应的JSON值中,只需分析其中的键的名字,然后读取相匹配的键的值,即可获取下发的命令。
参考下图响应参数的格式,可知响应参数只需发送订阅的topic和下发命令中的request_id的值即可,后面的JSON消息体均可省略。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| void rcv_json(void){ uint8_t *ret = NULL; cJSON *cjson = NULL; char topic_buff[1024]; int num; char recv_buffer[1024]; char request_id[37]; char device_id[256]; uint8_t cmd[1024]; ret = atk_mw8266d_uart_rx_get_frame(); atk_mw8266d_uart_rx_restart(); char *ptr_recv = strstr((const char *)ret,"+MQTTSUBRECV"); if(ptr_recv!=NULL){ memset(device_id,0,sizeof(device_id)); memset(request_id,0,sizeof(request_id)); sscanf((char *)ret, "+MQTTSUBRECV:0,\"$oc/devices/%255[^/]/sys/commands/request_id=%36s\",%d,%255s", device_id, request_id, &num, recv_buffer); if(strstr(device_id,HUAWEI_MQTT_DeviceID)) { cjson = cJSON_Parse(recv_buffer); } if(cjson==NULL) printf("cjson 解析错误\r\n"); else{ cJSON *json_data = cJSON_GetObjectItem(cjson,"paras"); if(json_data==NULL){ printf("cjson 没有数据\r\n"); return; } else{ if(cJSON_GetObjectItem(json_data,"led_flag")!=NULL) { LED_Switch = cJSON_GetObjectItem(json_data,"led_flag")->valueint; printf("csjon解析成功 LED_Switch = %d\r\n",LED_Switch);
} if(cJSON_GetObjectItem(json_data,"temp_flag")!=NULL) { temp_LED_Switch = cJSON_GetObjectItem(json_data,"temp_flag")->valueint; printf("csjon解析成功 temp_LED_Switch = %d\r\n",temp_LED_Switch); } char full_topic[256]; snprintf(full_topic, sizeof(full_topic), "%s%s", HUAWEI_MQTT_commands_response, request_id); sprintf((char *)cmd, "AT+MQTTPUB=0,\"%s\",\"\",0,0\r\n", full_topic); ESP8266_Sent_AT(cmd, "OK", 500); } cJSON_Delete(cjson); } } }
|
7 微信小程序连接华为云
7.1 开发方式
- 使用微信开发工具创建微信小程序项目
- 微信小程序调用应用侧API,获取云平台数据
- 将获得的数据进行显示
7.2 微信小程序创建
【略…】
7.3 创建IAM账户(云平台)
- 统一身份认证(Identity and Access Management,简称IAM)是华为云提供权限管理的基础服务,可以安全地控制云服务和资源的访问权限。应用侧需要通过IAM服务鉴权,获取token。因此在开发之前需要先创建IAM用户。
- 在【统一身份认证服务】页面,点击【创建用户】,参考下图创建账号,里面填写的自定义密码需要记住,然后将其加入【admin】用户组。
- 创建成功后,退出登录华为云,使用IAM账号登录华为云,验证是否成功。
7.3 获取Token(小程序)
- 参考华为云提供的请求示例,发送获取Token的http请求消息
- 华为云认证通过后向应用服务器返回鉴权令牌
X-Subject-Token
,接口返回的响应消息头中X-Subject-Token
就是需要获取的用户Token。根据上述示例,在微信开发工具中,使用wx.request
发送请求,然后分析响应消息头,获取Token。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| gettoken(){ var that=this; wx.request({ url: 'https://iam.cn-north-4.myhuaweicloud.com/v3/auth/tokens', data:'{"auth": { "identity": {"methods": ["password"],"password": {"user": {"name": "***","password": "***","domain": {"name": "***"}}}},"scope": {"project": {"name": "cn-north-4"}}}}', method: 'POST', header: {'content-type': 'application/json' }, success: function(res){ var token=''; token=JSON.stringify(res.header['X-Subject-Token']); token=token.replaceAll("\"", ""); wx.setStorageSync('token',token); }, }); }
|
- 获取Token后,再调用其他接口时,需要在请求消息头中添加
X-Auth-Token
,其值为获取到的Token。例如Token值为“ABCDEFJ…”,则调用接口时将“X-Auth-Token: ABCDEFJ…”加到请求消息头即可,如下所示。
7.4 获取设备影子(小程序)
- 参考请求示例,发送请求获取设备影子数据。
- 查看返回的HTTP响应消息,查看所需要的数据所在位置,如下图所示,可以发现我们需要的数据在data里面的shadow[0]中的reported下的properties里面。
- 根据分析的结果,在微信开发工具中编写代码,获取数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| getinfo(){ var that = this var token = wx.getStorageSync('token'); wx.request({ url: 'https://653a151f50.st1.iotda-app.cn-north-4.myhuaweicloud.com:443/v5/iot/661f377e2ccc1a583881a678/devices/661f377e2ccc1a583881a678_yun_dht11/shadow', data:'', method: 'GET', header: {'content-type': 'application/json','X-Auth-Token':token }, success: function (res) { that.setData({ humi:res.data.shadow[0].reported.properties.humidity, temp:res.data.shadow[0].reported.properties.temperature, light:res.data.shadow[0].reported.properties.light }) } }); }
|
7.5 下发设备命令(小程序)
参考请求示例,在微信开发工具中编写代码下发命令。
1 2 3 4 5 6 7 8 9 10 11
| if(that.data.lset > that.data.light){ wx.request({ url: 'https://653a151f50.st1.iotda-app.cn-north-4.myhuaweicloud.com:443/v5/iot/661f377e2ccc1a583881a678/devices/661f377e2ccc1a583881a678_yun_dht11/commands', data:'{"service_id": "yun","command_name": "led","paras": {"led_flag": true}}', method: 'POST', header: {'content-type': 'application/json','X-Auth-Token':token }, success: function(res){ console.log("下发命令成功"); }, }); }
|
8 小程序界面设计
8.1 主页数据显示
在wxml中设置布局,在wxss中设置样式,如下图所示。
8.2 Echarts数据可视化
ECharts一个使用JavaScript实现的开源可视化库。ECharts提供了常规的折线图、柱状图、散点图、饼图、K线图,用于统计的盒形图,用于地理数据可视化的地图、热力图、线图,用于关系数据可视化的关系图、treemap、旭日图,多维数据可视化的平行坐标,还有用于BI的漏斗图,仪表盘,并且支持图与图之间的混搭。
微信小程序使用Echarts进行可视化的步骤如下所示。
- 将Echarts下载到本地,并放入的微信小程序工程文件夹中,如下图所示。
- 在lineset.json中引入ec-canvas组件,如下图所示。
-
参考官网给出的折线图堆叠示例,编写该图像的显示函数function line_set(chart,time_data,hum_data,tem_data,ligh_data)。
-
初始化图表,同时调用line_set函数显示。
1 2 3 4 5 6 7 8 9 10 11 12
| init_chart: function (time_data,hum_data,tem_data,ligh_data) { this.oneComponent.init((canvas, width, height, dpr) => { const chart = echarts.init(canvas, null, { width: width, height: height, devicePixelRatio: dpr }); line_set(chart,time_data,hum_data,tem_data,ligh_data) this.chart = chart; return chart; }); }
|
- 将获得的影子设备的数据存入数组中,然后将其传入init_chart(date,humi,temp,ligh)中,进行数据实时可视化显示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| temp = that.data.Temperature; humi = that.data.Humidity; ligh = that.data.Light; date = that.data.Time; temp.push(res.data.shadow[0].reported.properties.temperature); humi.push(res.data.shadow[0].reported.properties.humidity); ligh.push(res.data.shadow[0].reported.properties.light); date.push(formattedTime); if (temp.length > 7) { temp.shift(); humi.shift(); ligh.shift(); date.shift(); }
|
32工程源码(CSDN):【免费】STM32HAL库+ESP8266+cJSON+微信小程序-连接华为云物联网平台.zip资源-CSDN文库
微信小程序源码(CSDN):【免费】STM32HAL库+ESP8266+cJSON+微信小程序-连接华为云物联网平台(微信小程序侧)资源-CSDN文库
32工程+微信小程序源码(GitHub):STM32HAL库+ESP8266+cJSON+微信小程序_连接华为云物联网平台