摘要:本文講解如何利用華為雲IOT物聯網平台實踐搭建一個智慧農業智慧大腦。
本文分享自華為雲社區《物聯網應用開發實踐案例-智慧農業【玩轉華為雲】》,作者: DS小龍哥。
1. 設計需求、硬件環境介紹
1.1 項目背景
近幾年,物聯網、智能家居、AI人工智能技術發送非常迅速。在物聯網技術的支撐下,如今農業逐漸走向現代化,自動化、現在智能化的農業生產成為了主流。告別“刀耕火種”的傳統農業后,現代農業也正在向智慧型轉變,當前智慧農業模式已經深入到農業生產的各個環節,灌溉、施肥、植保等細分領域都將與物聯網、信息技術等先進科技相結合,效率、效果也將得到大大提高。
要知道,所謂的“智慧農業”就是充分應用現代信息技術成果,集成應用計算機技術與網絡技術、物聯網技術、無線通信技術以及專家智慧與知識等,實現農業可視化遠程診斷、遠程控制、災變預警等智能管理。那么融入物聯網的智慧農業的有以下幾個優點:
1、低成本化
眾所周知,目前想要購買一套全面的智慧農業設備的成本都較高,這是普通農戶難以承受的,因此,想要實現全面智慧農業,那么低成本的智慧農業設備將成為智慧農業趨勢之一。
2、操作簡單化 智慧農業的根本是服務於農業、服務於農戶,所以想要做到讓農戶更快地與智慧農業接軌就必須要把系統做得易操作、易學。要知道,當前我國農民普遍文化程度較低,只有將操作簡單化才能夠讓每個農民都能熟練操作。
智慧農業也是一個大范圍,比如: 智慧魚塘、智慧大棚、智慧園林、城市綠化、智能果園等等都屬於智慧農業的范圍。
有了智能設備的加持:可以實現自動澆水灌溉、實時檢測土壤養分、水分、環境溫度、自動補光等一系列聯動操作。
本篇文章就利用華為雲IOT物聯網平台實踐搭建一個智慧農業智慧大腦,設備平台采用小熊開發板,搭載的CPU是意法半導體的STM32L431芯片,這是意法半導體推出的低功耗芯片;配合外部的一些專業傳感器,能夠獲取空氣中的溫濕度數據,光照度數據等,根據種植區的空氣溫濕度數據,判斷是否進行灌溉。
1.2 實現功能
本項目是利用意法半導體的STM32L431+ESP8266 WIFI ,配合華為雲物聯網平台服務器,組建一個智慧農業控制系統,結合外部傳感器采集的數據,並利用這些數據判斷是否進行灌溉,補光等信息提示。
考慮到以學習、實踐為目的,當前項目采用了ESP8266無線WIFI網卡作為聯網設備,ESP8266價錢便宜,支持串口編程,有標准的一套AT資料,資料多,作為學習而言,非常適合。可以通過對ESP8266的編程實驗,了解TCP、MQTT網絡編程相關知識點。
當前項目主要分為六個功能模塊,分別是:基礎系統模塊、溫度采集模塊、濕度采集模塊、光照采集模塊、無線傳感器網絡模塊、OLED顯示屏模塊。
(1)基礎系統模塊:進行各個數據的接收與轉發,控制掃水作業是否進行,澆水作業是采用板載的電機模擬
(2)溫度采集模塊:采集監測區域的溫度數據,傳輸到微控制器
(3)濕度采集模塊:采集監測區域的濕度數據,傳輸到微控制器
(4)光照采集模塊:采集監測區域的光照數據,傳輸到微控制器
(5)無線傳感器網絡模塊:數據上傳至雲平台,數據下發交互等
(6)LCD顯示屏模塊:實時顯示所監測到的各項數據
小熊開發板的擴展板上自帶了光敏傳感器、溫濕度傳感器、直流電機模塊,可以很方便的實現上面的這些功能需求。
本項目設備的源代碼里,連接華為雲的MQTT協議是按照MQTT的官方中文手冊編寫的,不依賴任何外部SDK,不依賴ESP8266設備,只要能聯網的設備都可以連接華為雲IOT,非常適合移植到其他單片機平台;不管是采用51,STM32F1系列,都可以直接參考代碼移植。
華為雲物聯網平台提供了API接口,可以通過API開發配套的上位機,方便實現數據查看,手動灌溉等操作。
提供的API除了可以查詢設備屬性信息之外,還可以創建產品、設備、對開發上位機來講非常方便,可以開發出從底層設備到雲端服務器、再到應用APP軟件,完成3層數據交互。
下面是開發的上位機APP運行效果。
當前文章主要完成3個任務的實踐:
(1)雲端產品的創建、設備的創建
(2)設備上雲,完成服務器登錄、數據上傳
(3)手機APP、電腦上位機軟件的開發,可以通過雲端API接口與設備、服務器之前通訊
1.3 設備實物圖
目前聯網的設備采用的ESP8266(手上沒有現成的NBIOT模塊,暫時使用ESP8266代替,核心原理是一樣的),正常項目里會使用NBIOT模塊聯網作為數據傳輸源。
小熊開發板的設備相關實物圖如下:
2. 創建IOT服務器端產品
需要先創建產品、在產品下再創建設備。產品是一個大框架,產品下的設備可以有很多,在應用層,可以通過華為雲平台提供的API創建設備,刪除設備,查詢設備屬性,在做產品時,軟件端可以做一個設備注冊的引導界面,完成產品下的設備注冊,再將數據傳遞給設備端,這個過程叫“配網“,具體邏輯需要配合設備端完成。最終完成自動化設備創建,注冊,上線等操作。
下面先介紹如何手動創建產品,創建設備,了解創建產品創建設備的過程中需要填充什么參數,理解之后,再使用API時才更加理解參數含義。
2.1 創建產品
直接打開物聯網產品頁面: https://www.huaweicloud.com/product/iothub.html
打開產品頁面,選擇右上角創建產品。
根據自己情況填寫信息。就是填寫自己產品的一些參數信息。
創建成功后打開產品詳情頁面,拉到最下面,點擊創建自定義模型文件。
這里創建模型文件主要就是為了MQTT客戶端能夠正確的上傳傳感器數據上來,每個傳感器設置一個屬性,這個屬性就是表示了傳感器的數據值類型。
比如: 先添加一個電機,這個電機就是澆水電機,能上報開關狀態,雲端也能下發命令控制電機,所以需要添加屬性和下發的命令。
添加屬性:
添加命令: 因為電機需要雲端遠程控制。
接下來就創建溫度、濕度、光照度傳感器的屬性,這些傳感器只是向雲端上傳數據,不需要下發指令控制,選擇只讀就可以了,電機要先實現遠程澆灌控制,屬性就選擇讀寫。
創建完畢效果,一共有4個屬性,電機、溫度、濕度、光強度:
2.2 創建設備
選擇設備頁面,注冊設備。
創建后保持設備密匙等信息,接下來登錄服務器時,生成MQTT賬號密匙需要用到這些參數。
當前創建的設備信息如下:
{ "device_id": "61cd1d97078a93029b84e7b6_1126626497", "secret": "1126626497" }
2.3 生成MQTT登錄賬號信息
官微提供的在線小工具: https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/
按照提示填入數據,生成,非常方便。
當前生成的信息如下:
ClientId 61cd1d97078a93029b84e7b6_1126626497_0_0_2021123003
Username 61cd1d97078a93029b84e7b6_1126626497
Password b219f3a0099fa0284a2671a5c699b67a7cf6d5f7355d9ee8190011f3b64f71b5
3. 使用MQTT客戶端模擬測試
為了驗證服務器配置是否OK,先使用MQTT客戶端軟件進行連接測試。
3.1 華為雲IOT服務器地址與端口
端口: 1883 域名: a161a58a78.iot-mqtts.cn-north-4.myhuaweicloud.com IP地址: 121.36.42.100
3.2 訂閱主題
在產品頁面,可以看到主題管理頁面,能看到當前設備可以訂閱的主題有哪些。
一般訂閱下發的數據:
格式: $oc/devices/{device_id}/sys/messages/down //訂閱主題: 平台下發消息給設備 $oc/devices/61cd1d97078a93029b84e7b6_1126626497/sys/messages/down
3.3 上報主題數據
官方文檔介紹: https://support.huaweicloud.com/devg-iothub/iot_01_2127.html
服務ID,屬性ID在產品頁面查看,2.1小節創建產品里就講了這個屬性的作用。
每次可以單個屬性上報,也可以一起上報。
格式: $oc/devices/{device_id}/sys/properties/report //設備上報主題請求 $oc/devices/61cd1d97078a93029b84e7b6_1126626497/sys/properties/report //上報的數據格式如下 //電機開狀態反饋 {"services": [{"service_id": "motor","properties":{"motor":1}}]} //電機關狀態反饋 {"services": [{"service_id": "motor","properties":{"motor":0}}]} //溫度上報 {"services": [{"service_id": "motor","properties":{"SHT30_H":14}}]} //濕度上報 {"services": [{"service_id": "motor","properties":{"SHT30_L":70}}]} //光照強度上報 {"services": [{"service_id": "motor","properties":{"BH1750":80}}]} //也可以一起上報 {"services": [{"service_id": "motor","properties":{"motor":1}},{"service_id": "motor","properties":{"SHT30_H":15}},{"service_id": "motor","properties":{"SHT30_L":70}},{"service_id": "motor","properties":{"BH1750":80}}]}
3.4 登錄服務器
按照軟件提示,填入相關數據即可。
如需要也需要使用和我一樣的同款軟件,打開百度搜索MQTT客戶端_v2.4(協議3.1.1).exe 即可找到下載地址。
發送數據后查看雲端,已經登錄成功,數據已經上傳成功。
3.5 下發命令
電機設備支持讀寫,支持下發命令,在設備頁面測試。
點擊確定之后,參看MQTT客戶端軟件,已經收到了下發的數據。
len:174,Data:l$oc/devices/61cd1d97078a93029b84e7b6_1126626497/sys/commands/request_id=390ce15d-6e69-4021-b83a-5e953eea874c{"paras":{"motor":1},"service_id":"motor","command_name":"motor"}
4. 設備端上華為雲IOT
4.1 安裝keil軟件
MCU采用的STM32芯片,設備端代碼編寫開發就采用的keil5。
keil5安裝包下載地址: http://www.myir-tech.com/download.asp
安裝keil時,軟件要放在英文目錄下,電腦的用戶名必須是英文,否則會出現一些奇怪問題。
安裝過程中,根據提示下一步下一步點擊即可。
4.2 編寫代碼
工程代碼:
工程代碼較多,這里就貼出main.c全部代碼:
#include "main.h" #include "stm32l4xx_hal.h" #include "i2c.h" #include "usart.h" #include "gpio.h" #include "E53_IA1.h" #include "lcd.h" #include "spi.h" #include "mqtt.h" #include "esp8266.h" /* USER CODE BEGIN Includes */ #include "stdio.h" /* USER CODE END Includes */ void SystemClock_Config(void); #define ESP8266_WIFI_AP_SSID "CMCC-Cqvn" //將要連接的路由器名稱 --不要出現中文、空格等特殊字符 #define ESP8266_AP_PASSWORD "99pu58cb" //將要連接的路由器密碼 //華為雲IOT物聯網服務器的設備信息 #define MQTT_ClientID "61cd1d97078a93029b84e7b6_1126626497_0_0_2021123003" #define MQTT_UserName "61cd1d97078a93029b84e7b6_1126626497" #define MQTT_PassWord "b219f3a0099fa0284a2671a5c699b67a7cf6d5f7355d9ee8190011f3b64f71b5" //訂閱與發布的主題 #define SET_TOPIC "$oc/devices/61cd1d97078a93029b84e7b6_1126626497/sys/messages/down" //訂閱 #define POST_TOPIC "$oc/devices/61cd1d97078a93029b84e7b6_1126626497/sys/properties/report" //發布 //保存溫濕度、光照強度 E53_IA1_Data_TypeDef E53_IA1_Data; //顯示文本 char lcd_text_str[50]; UART_HandleTypeDef at_usart; //低功耗串口初始化 int32_t at_usart_init(void) { at_usart.Instance = LPUART1; at_usart.Init.BaudRate = 115200; at_usart.Init.WordLength = UART_WORDLENGTH_8B; at_usart.Init.StopBits = UART_STOPBITS_1; at_usart.Init.Parity = UART_PARITY_NONE; at_usart.Init.HwFlowCtl = UART_HWCONTROL_NONE; at_usart.Init.Mode = UART_MODE_RX | UART_MODE_TX; if(HAL_UART_Init(&at_usart) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } // __HAL_UART_CLEAR_FLAG(usart, UART_FLAG_TC); __HAL_UART_ENABLE_IT(&at_usart, UART_IT_IDLE); __HAL_UART_ENABLE_IT(&at_usart, UART_IT_RXNE); HAL_NVIC_EnableIRQ(LPUART1_IRQn); //使能USART1中斷通道 HAL_NVIC_SetPriority(LPUART1_IRQn, 3, 3); //搶占優先級3,子優先級3 return 0; } unsigned char ESP8266_RecvBuf[MAX_RECV_CNT]; unsigned int ESP8266_Recv_cnt=0; unsigned int ESP8266_Recv_flag=0; void LPUART1_IRQHandler() { //接收到數據 if(__HAL_UART_GET_FLAG(&at_usart, UART_FLAG_RXNE) != RESET) { if(ESP8266_Recv_cnt<MAX_RECV_CNT-1) { ESP8266_RecvBuf[ESP8266_Recv_cnt++] = (uint8_t)(at_usart.Instance->RDR & 0x00FF); } else { ESP8266_Recv_flag=1; } } else if (__HAL_UART_GET_FLAG(&at_usart, UART_FLAG_IDLE) != RESET) { __HAL_UART_CLEAR_IDLEFLAG(&at_usart); ESP8266_Recv_flag=1; } } void AT_SendData(unsigned char *p,unsigned int len) { int i=0; for(i=0;i<len;i++) { while((LPUART1->ISR & 0X40) == 0); //循環發送,直到發送完畢 LPUART1->TDR = p[i]; } } char mqtt_message[200]; int main(void) { int i=0; int cnt=0; int motor_state=0; HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_I2C1_Init(); MX_SPI2_Init(); MX_USART1_UART_Init(); at_usart_init(); //初始化硬件 Init_E53_IA1(); LCD_Init(); LCD_Clear(BLACK);//清屏為黑色 LCD_ShowString(0, 00, 240, 32, 32, "Init ESP8266");//顯示字符串,字體大小32*32 if(ESP8266_Init()) { printf("ESP8266硬件檢測錯誤.\n"); LCD_Clear(BLACK);//清屏為黑色 LCD_ShowString(0, 00, 240, 32, 32, "ESP8266 ERROR");//顯示字符串,字體大小32*32 } else { LCD_Clear(BLACK);//清屏為黑色 LCD_ShowString(0, 00, 240, 32, 32, "ESP8266 OK");//顯示字符串,字體大小32*32 printf("准備連接到指定的服務器.\n"); //非加密端口 printf("WIFI:%d\r\n",ESP8266_STA_TCP_Client_Mode(ESP8266_WIFI_AP_SSID,ESP8266_AP_PASSWORD,"106.55.124.154",1883,1)); } //2. MQTT協議初始化 MQTT_Init(); //3. 連接華為雲IOT服務器 while(MQTT_Connect(MQTT_ClientID,MQTT_UserName,MQTT_PassWord)) { printf("服務器連接失敗,正在重試...\n"); HAL_Delay(500); } printf("服務器連接成功.\n"); //3. 訂閱主題 if(MQTT_SubscribeTopic(SET_TOPIC,0,1)) { printf("主題訂閱失敗.\n"); } else { printf("主題訂閱成功.\n"); } while (1) { if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin)==GPIO_PIN_RESET)//查詢按鍵KEY1低電平 { HAL_Delay(10);//消抖 if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin)==GPIO_PIN_RESET)//查詢按鍵KEY1低電平 { HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_SET);//亮 //補光燈亮 HAL_GPIO_WritePin(IA1_Light_GPIO_Port, IA1_Light_Pin, GPIO_PIN_SET); //電機轉 HAL_GPIO_WritePin(IA1_Motor_GPIO_Port, IA1_Motor_Pin, GPIO_PIN_SET); motor_state=1; } } if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin)==GPIO_PIN_RESET)//查詢按鍵KEY2低電平 { HAL_Delay(10);//消抖 if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin)==GPIO_PIN_RESET)//查詢按鍵KEY2低電平 { HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_RESET);//滅 //補光燈滅 HAL_GPIO_WritePin(IA1_Light_GPIO_Port, IA1_Light_Pin, GPIO_PIN_RESET); //電機停 HAL_GPIO_WritePin(IA1_Motor_GPIO_Port, IA1_Motor_Pin, GPIO_PIN_RESET); motor_state=0; } } cnt++; HAL_Delay(10); if(cnt>=100) { cnt=0; E53_IA1_Read_Data(); printf("光照強度:%d %%\r\n", (int)E53_IA1_Data.Lux); printf("濕度:%d %%\r\n",(int)E53_IA1_Data.Humidity); printf("溫度:%d ℃\r\n", (int)E53_IA1_Data.Temperature); sprintf(lcd_text_str,"L: %d %%",(int)E53_IA1_Data.Lux); LCD_ShowString(40, 50+10+32*1, 240, 32, 32,lcd_text_str); sprintf(lcd_text_str,"H: %d %%",(int)E53_IA1_Data.Humidity); LCD_ShowString(40, 50+10+32*2, 240, 32, 32,lcd_text_str); sprintf(lcd_text_str,"T: %d C",(int)E53_IA1_Data.Temperature); LCD_ShowString(40, 50+10+32*3, 240, 32, 32,lcd_text_str); //切換引腳的狀態 HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin); //上傳數據 sprintf(mqtt_message,"{\"services\": [{\"service_id\": \"motor\",\"properties\":{\"motor\":%d}}," "{\"service_id\": \"motor\",\"properties\":{\"SHT30_H\":%d}},{\"service_id\": \"motor\",\"properties\":" "{\"SHT30_L\":%d}},{\"service_id\": \"motor\",\"properties\":{\"BH1750\":%d}}]}", motor_state,(int)E53_IA1_Data.Humidity,(int)E53_IA1_Data.Temperature,(int)E53_IA1_Data.Lux); MQTT_PublishData(POST_TOPIC,mqtt_message,0); //根據濕度自動灌溉 if((int)E53_IA1_Data.Humidity<50) //小於50自動灌溉 { printf("自動灌溉....\n"); motor_state=1; //電機狀態更新 //電機轉 HAL_GPIO_WritePin(IA1_Motor_GPIO_Port, IA1_Motor_Pin, GPIO_PIN_SET); } } //接收到數據 if(ESP8266_Recv_flag) { //如果是下發了屬性,判斷是開鎖還是關鎖 if(ESP8266_Recv_cnt>5) { ESP8266_RecvBuf[ESP8266_Recv_cnt]='\0'; //使用字符串查找函數 if(strstr((char*)&ESP8266_RecvBuf[5],"\"machine\":1")) { motor_state=1; //電機狀態更新 //電機轉 HAL_GPIO_WritePin(IA1_Motor_GPIO_Port, IA1_Motor_Pin, GPIO_PIN_SET); printf("開啟電機...\n"); } else if(strstr((char*)&ESP8266_RecvBuf[5],"\"machine\":0")) { //電機停 HAL_GPIO_WritePin(IA1_Motor_GPIO_Port, IA1_Motor_Pin, GPIO_PIN_RESET); motor_state=0; printf("關閉電機...\n"); } for(i=0;i<ESP8266_Recv_cnt;i++)printf("%c",ESP8266_RecvBuf[i]); ESP8266_Recv_cnt=0; } ESP8266_Recv_flag=0; } } } void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct; RCC_ClkInitTypeDef RCC_ClkInitStruct; RCC_PeriphCLKInitTypeDef PeriphClkInit; /**Initializes the CPU, AHB and APB busses clocks */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_MSI; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.HSICalibrationValue = 16; RCC_OscInitStruct.MSIState = RCC_MSI_ON; RCC_OscInitStruct.MSICalibrationValue = 0; RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_6; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_MSI; RCC_OscInitStruct.PLL.PLLM = 1; RCC_OscInitStruct.PLL.PLLN = 40; RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7; RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2; RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } /**Initializes the CPU, AHB and APB busses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1|RCC_PERIPHCLK_I2C1; PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2; PeriphClkInit.I2c1ClockSelection = RCC_I2C1CLKSOURCE_HSI; if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } /**Configure the main internal regulator output voltage */ if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } /**Configure the Systick interrupt time */ HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000); /**Configure the Systick */ HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK); /* SysTick_IRQn interrupt configuration */ HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0); } /* USER CODE BEGIN 4 */ /* USER CODE END 4 */ /** * @brief This function is executed in case of error occurrence. * @param file: The file name as string. * @param line: The line in file as a number. * @retval None */ void _Error_Handler(char *file, int line) { /* USER CODE BEGIN Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ while(1) { } /* USER CODE END Error_Handler_Debug */ } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t* file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */
5. 上位機軟件開發
上位機與設備之間通信,需要通過服務器完成,服務器提供了對應的API接口,所以對於上位機而言通信主要是對HTTP請求進行處理,返回的數據進行解析等操作。
當前的軟件采用是采用QT設計的,實現了產品注冊、設備注冊、獲取在線設備,獲取設備屬性,遠程指令發送等主要功能。
訪問華為雲的接口都需要填一個X-Auth-Token參數,這個參數獲取需要IAM賬號,下面第一步就先介紹,如何創建IAM賬號,如何獲取X-Auth-Token參數。
5.1 創建IAM賬戶
創建一個IAM賬戶,方便接下來使用API接口訪問華為雲服務時,生成token登錄密匙。
地址: https://console.huaweicloud.com/iam/?region=cn-north-4#/iam/users
賬戶創建好之后,代碼里就可以編寫一個獲取Token的函數。
/* 功能: 獲取token */ void Widget::GetToken() { //表示獲取token function_select=3; QString requestUrl; QNetworkRequest request; //設置請求地址 QUrl url; //獲取token請求地址 requestUrl = QString("https://iam.%1.myhuaweicloud.com/v3/auth/tokens") .arg(SERVER_ID); //自己創建的TCP服務器,測試用 //requestUrl="http://10.0.0.6:8080"; //設置數據提交格式 request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json;charset=UTF-8")); //構造請求 url.setUrl(requestUrl); request.setUrl(url); QString text =QString("{\"auth\":{\"identity\":{\"methods\":[\"password\"],\"password\":" "{\"user\":{\"domain\": {" "\"name\":\"%1\"},\"name\": \"%2\",\"password\": \"%3\"}}}," "\"scope\":{\"project\":{\"name\":\"%4\"}}}}") .arg(MAIN_USER) .arg(IAM_USER) .arg(IAM_PASSWORD) .arg(SERVER_ID); //發送請求 manager->post(request, text.toUtf8()); }
5.2 查詢設備列表
幫助文檔地址: https://support.huaweicloud.com/api-iothub/iot_06_v5_0048.html
官方提供了API接口,可以直接獲取產品下面的所有設備詳細信息返回。
關於請求參數,返回結果的字段含義,在幫助文檔里有詳細介紹。
URL格式: /v5/iot/{project_id}/devices 示例: https://iotda.cn-north-4.myhuaweicloud.com/v5/iot/0f2d61e43600f4e22f74c003616710bc/devices?product_id=6210e8acde9933029be8facf&is_cascade_query=false&limit=10&marker=ffffffffffffffffffffffff&offset=0
接口的在線調試地址:https://apiexplorer.developer.huaweicloud.com/apiexplorer/debug?product=IoTDA&api=ListDevices
返回的結果:
{ "devices": [ { "app_id": "1af45e3938bb4482bc0be0a5cb3089e3", "app_name": "DefaultApp_620esbs1", "device_id": "6210e8acde9933029be8facf_dev2", "node_id": "dev2", "gateway_id": "6210e8acde9933029be8facf_dev2", "device_name": "dev2", "node_type": "GATEWAY", "description": null, "fw_version": null, "sw_version": null, "device_sdk_version": null, "product_id": "6210e8acde9933029be8facf", "product_name": "DHT11", "status": "INACTIVE", "tags": [] }, { "app_id": "1af45e3938bb4482bc0be0a5cb3089e3", "app_name": "DefaultApp_620esbs1", "device_id": "6210e8acde9933029be8facf_dev1", "node_id": "dev1", "gateway_id": "6210e8acde9933029be8facf_dev1", "device_name": "dev1", "node_type": "GATEWAY", "description": null, "fw_version": null, "sw_version": null, "device_sdk_version": null, "product_id": "6210e8acde9933029be8facf", "product_name": "DHT11", "status": "OFFLINE", "tags": [] } ], "page": { "count": 2, "marker": "6210efa980c60c11be19ead1" } }
上面的返回結果里通過JSON數組保存了設備信息,每一個設備就是一個獨立的對象,上面的數據里返回了兩個設備的信息,說明產品的目錄下創建了兩個設備。
應用層編寫代碼完成設備列表獲取:
//查詢所有設備 void Widget::Get_AllDevice() { //查詢設備列表 function_select=1; QString requestUrl; QNetworkRequest request; //設置請求地址 QUrl url; //設備列表請求地址 requestUrl = QString("https://iotda.%1.myhuaweicloud.com/v5/iot/%2/devices?product_id=%3&is_cascade_query=false&limit=10&marker=ffffffffffffffffffffffff&offset=0") .arg(SERVER_ID) .arg(PROJECT_ID) .arg(Product_id); //設置數據提交格式 request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json")); //設置token request.setRawHeader("X-Auth-Token",Token); //構造請求 url.setUrl(requestUrl); request.setUrl(url); //發送請求 manager->get(request); }
服務器返回的結果解析:
//查詢設備列表 if(function_select==1) { //清空原來的設備列表 ui->comboBox->clear(); device_id_lis.clear(); //解析數據 QJsonParseError json_error; QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error); if(json_error.error == QJsonParseError::NoError) { QJsonObject obj = document.object(); //判斷是否是對象,然后開始解析數據 if(document.isObject()) { QJsonObject obj = document.object(); if(obj.contains("devices")) { QJsonArray array=obj.take("devices").toArray(); for(int i=0;i<array.size();i++) { QJsonObject obj1=array.at(i).toObject(); //得到設備ID if(obj1.contains("device_id")) { QString device_id=obj1.take("device_id").toString(); device_id_lis.append(device_id); ui->comboBox->addItem(device_id); qDebug()<<"device_id:"<<device_id; } } } } } return; }
5.3 查詢設備屬性
(1)應用端查詢設備屬性的請求
幫助文檔地址: https://support.huaweicloud.com/api-iothub/iot_06_v5_0034.html
(2)在線調試地址
接口的在線調試地址: https://apiexplorer.developer.huaweicloud.com/apiexplorer/debug?product=IoTDA&api=ListProperties
(3)設備端響應的數據格式
幫助文檔: https://support.huaweicloud.com/api-iothub/iot_06_v5_3011.html
(4)使用總結
上位機APP向設備端請求查詢設備屬性時,設備端會收到如下的消息:
$oc/devices/6210e8acde9933029be8facf_dev1/sys/properties/get/request_id=5f359b5c-542f-460e-9f51-85e82150ff4a{"service_id":"DHT11"}
設備端需要解析這個字符串,得到里面的request_id=5f359b5c-542f-460e-9f51-85e82150ff4a 值。在向服務器回應時,要帶上這個請求ID。
設備端響應的主題格式: $oc/devices/{device_id}/sys/properties/get/response/request_id={request_id} 示 例: $oc/devices/6210e8acde9933029be8facf_dev1/sys/properties/get/response/request_id=6c36c85e-68e1-4d01-a2a3-b89f09bd0427 設備端響應的數據格式: {"services": [{"service_id": "gps","properties":{"DHT11_t":12,"DHT11_h":33}}]} 應用端上位機收到設備端的響應數據: "{\"response\":{\"services\":[{\"service_id\":\"temp\",\"properties\":{\"DHT11_t\":13,\"DHT11_h\":33.345}}]}}"
(5)應用端獲取設備屬性
//查詢設備屬性 void Widget::Get_device_properties() { //表示獲取token function_select=0; QString requestUrl; QNetworkRequest request; //設置請求地址 QUrl url; if(device_id_lis.size()<=0) { //顯示錯誤代碼 QMessageBox::information(this,"提示","未選擇設備,請先獲取設備列表\n選擇設備后重試.",QMessageBox::Ok,QMessageBox::Ok); return; } //獲取token請求地址 requestUrl = QString("https://iotda.%1.myhuaweicloud.com/v5/iot/%2/devices/%3/properties?service_id=%4") .arg(SERVER_ID) .arg(PROJECT_ID) .arg(device_id_lis.at(ui->comboBox->currentIndex())) .arg(service_id); //自己創建的TCP服務器,測試用 //requestUrl="http://10.0.0.6:8080"; //設置數據提交格式 request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json")); //設置token request.setRawHeader("X-Auth-Token",Token); //構造請求 url.setUrl(requestUrl); request.setUrl(url); //發送請求 manager->get(request); }
(6)應用端解析數據
//查詢設備屬性 if(function_select==0) { //解析數據 QJsonParseError json_error; QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error); if(json_error.error == QJsonParseError::NoError) { //判斷是否是對象,然后開始解析數據 if(document.isObject()) { QJsonObject obj = document.object(); if(obj.contains("response")) { QJsonObject obj1=obj.take("response").toObject(); if(obj1.contains("services")) { QJsonArray array=obj1.take("services").toArray(); } } } } return; }
5.4 上位機開發環境搭建
上位機軟件是采用QT開發的,Qt是一個1991年由QtCompany開發的跨平台C++圖形用戶界面應用程序開發框架。它既可以開發GUI程序,也可用於開發非GUI程序,比如控制台工具和服務器。QT在發布 Qt 4.6 的同時,作為 Qt 開發跨平台 IDE 的Qt Creator也發布了更新版本。Qt Creator是一個用於Qt開發的輕量級跨平台集成開發環境。Qt Creator可帶來兩大關鍵益處:提供首個專為支持跨平台開發而設計的集成開發環境 (IDE),並確保首次接觸Qt框架的開發人員能迅速上手和操作。即使不開發Qt應用程序,Qt Creator也是一個簡單易用且功能強大的IDE。
目前QT在嵌入式領域、桌面端都用的非常多,開發桌面,嵌入式的上位機還是非常方便。 嵌入式領域包括: 車機主機、嵌入式Linux設備等。
QT官網: https://resources.qt.io/cn
QT5.12.6安裝包下載地址: https://download.qt.io/archive/qt/5.12/5.12.6/
QT學習專欄: https://blog.csdn.net/xiaolong1126626497/category_11400392.html
QT環境搭建文章:https://xiaolong.blog.csdn.net/article/details/120654599
6. 總結
整個項目的實現主要分為兩個大部分:1. 設備上雲 2. 應用側的軟件開發
(1)設備上雲: 目前通過STM32、ESP8266已經完了華為雲物聯網雲平台的連接,ESP8266上雲的過程主要是MQTT協議的理解,目前采用的ESP8266沒有內置MQTT協議相關的AT指令,需要自己實現MQTT協議,這個過程稍微麻煩一點,需要安裝官網的MQTT協議手冊拼接結構完成協議構造。對於設備端而言,只要是通信采用標准的MQTT協議,不管連接哪一個物聯網雲平台,過程是沒有多大區別的。
(2)應用層軟件開發: 應用側軟件開發主要是方便遠程管理設備,目前華為雲物聯網平台沒有提供在線web設計功能、沒有提供公版的APP;所以,在設備上雲之后,想要方便的對設備的屬性進行查看,管理,都需要自己開發上位機才行。