天天看點

基于STM32L431設計的雲端綠化管理系統(ESP8266+騰訊物聯網雲平台)

一、環境介紹

MCU: 采用意法半導體低功耗晶片 STM32L431RCT6

編譯軟體:  Keil5 + CubeMX

雲平台: 采用騰訊物聯網雲平台

工程完整源代碼與配套資料下載下傳位址:

https://download.csdn.net/download/xiaolong1126626497/19246016
基于STM32L431設計的雲端綠化管理系統(ESP8266+騰訊物聯網雲平台)
基于STM32L431設計的雲端綠化管理系統(ESP8266+騰訊物聯網雲平台)

二、功能與硬體介紹

2.1 功能介紹

這是采用STM32L431 + ES8266設計的雲端綠化管理系統,可以通過ESP8266 WIFI連接配接騰訊雲物聯網平台,使用微信小程式遠端進行綠化管理,比如:實時擷取光照強度、溫度、濕度、遠端控制水泵進行澆水灌溉,在任何地方都可以給自己種的花花草草澆水,了解周邊環境情況。

騰訊物聯網平台支援微信小程式+WIFI一鍵配網,想學習如何配網的請看這裡:

https://blog.csdn.net/xiaolong1126626497/article/details/117339434

2.2 硬體介紹

開發闆采用的是小熊開發闆,包括完成綠化管理系統的所有功能都是采用小熊派開發闆的配套套件完成。

小熊開發闆闆載了一個stlink調試器(就是STM32F103C8T6實作的),程式下載下傳非常友善。序列槽1用來調試列印資料,ESP8266是接在序列槽LPUART1上的。

基于STM32L431設計的雲端綠化管理系統(ESP8266+騰訊物聯網雲平台)

小熊派開發闆本身自帶的例子程式也比較豐富,自帶例子裡采用的雲平台是華為的物聯網雲平台,工程比較龐大使用了LiteOS作業系統。本文裡的工程是重新編寫的代碼,使用裸機完成項目功能,沒有跑作業系統,MQTT協定和ESP8266驅動代碼都是重新編寫,架構、邏輯比較清晰,代碼量也較少,适合初學者入門學習。

相關傳感器子產品型号: (采用的是小熊開發闆配套的E53_IA1擴充闆)

基于STM32L431設計的雲端綠化管理系統(ESP8266+騰訊物聯網雲平台)

WIFI采用:ESP8266

溫濕度檢測傳感器采用:SHT30

光照強度檢測傳感器采用:BH1750

電機采用:微型直流電機

基于STM32L431設計的雲端綠化管理系統(ESP8266+騰訊物聯網雲平台)
基于STM32L431設計的雲端綠化管理系統(ESP8266+騰訊物聯網雲平台)

三、騰訊物聯網雲平台

關于騰訊物聯網平台的建立已經介紹很多篇了,如果之前沒有使用過騰訊物聯網雲平台,先參考這裡學習了解一下:

https://blog.csdn.net/xiaolong1126626497/article/details/116902653

下面就截圖介紹一下雲端綠化管理系統用到的産品功能。

用到的功能屬性:

基于STM32L431設計的雲端綠化管理系統(ESP8266+騰訊物聯網雲平台)
小程式面闆配置:
基于STM32L431設計的雲端綠化管理系統(ESP8266+騰訊物聯網雲平台)
手機微信小程式運作效果:
基于STM32L431設計的雲端綠化管理系統(ESP8266+騰訊物聯網雲平台)
序列槽列印的提示:
基于STM32L431設計的雲端綠化管理系統(ESP8266+騰訊物聯網雲平台)

四、STM32源代碼

STM32的代碼主要分為以下幾個部分:

1.  ESP8266底層驅動代碼:   完成ESP8266模式配置、資料發送,應答檢測等底層網絡接口。

2.  MQTT協定代碼:這是參考标準MQTT編寫C語言版本MQTT協定架構代碼,實作了重要的幾個接口(主題訂閱、主題釋出、心跳包、登入MQTT伺服器),底層采用ESP8266發送資料。 這個MQTT協定不是使用ESP8266本身的SDK,是根據MQTT協定自己實作的,是以如果使用其他的網卡,移植也很友善,不挑網卡裝置。

3.  傳感器初始化代碼: 完成溫濕度傳感器、光照強度傳感器的驅動代碼編寫。

4.  LCD屏代碼:  LCD是SPI接口的,可以顯示溫濕度、光照強度資料。

5.  main函數:  完成整個邏輯代碼編寫,檢測微信小程式是否有下發的指令,進行分析,完成水泵的開關控制;當溫室和濕度到達某個閥值,自動控制水泵澆水,并上報給微信小程式;主程式裡1秒檢測一次溫濕度、光照強度、電機狀态主動上報給微信小程式;在裝置端按下按鍵(模拟現場實體開關)也可以控制水泵澆水或者關閉,這些狀态都會實時上報給雲平台,微信小程式。

程式的模闆是使用CubeMX生成的。

基于STM32L431設計的雲端綠化管理系統(ESP8266+騰訊物聯網雲平台)
4.1  main.c代碼:  邏輯代碼

/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  ** This notice applies to any and all portions of this file
  * that are not between comment pairs USER CODE BEGIN and
  * USER CODE END. Other portions of this file, whether 
  * inserted by the user or by software development tools
  * are owned by their respective copyright owners.
  *
  * COPYRIGHT(c) 2019 STMicroelectronics
  *
  * Redistribution and use in source and binary forms, with or without modification,
  * are permitted provided that the following conditions are met:
  *   1. Redistributions of source code must retain the above copyright notice,
  *      this list of conditions and the following disclaimer.
  *   2. Redistributions in binary form must reproduce the above copyright notice,
  *      this list of conditions and the following disclaimer in the documentation
  *      and/or other materials provided with the distribution.
  *   3. Neither the name of STMicroelectronics nor the names of its contributors
  *      may be used to endorse or promote products derived from this software
  *      without specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
  ******************************************************************************
  */
/* Includes ------------------------------------------------------------------*/
#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"     //将要連接配接的路由器密碼
 
 
//騰訊物聯網伺服器的裝置資訊
#define MQTT_ClientID "6142CX41XESmartAgriculture"
#define MQTT_UserName "6142CX41XESmartAgriculture;12010126;HUA2G;1624271589"
#define MQTT_PassWord "a8aadebe9721f70e6f9e14fe56ff1d2b5cac9625fa1f96af2f0e0098597fe78b;hmacsha256"
 
//訂閱與釋出的主題
#define SET_TOPIC  "$thing/down/property/6142CX41XE/SmartAgriculture"  //訂閱
#define POST_TOPIC "$thing/up/property/6142CX41XE/SmartAgriculture"  //釋出
 
 
//儲存溫濕度、光照強度
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();
    
    //初始化硬體 STM32L431RC_BearPiBH1750_I2C1\STM32L431RC_BearPiBH1750_I2C1.axf: Error: L6218E: Undefined symbol printf (referred from main.o).
    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,"{\"method\":\"report\",\"clientToken\":\"123\",\"params\":{\"temperature\":%d,\"humidity\":%d,\"machine\":%d,\"illumination\":%d}}",
            (int)E53_IA1_Data.Temperature,(int)E53_IA1_Data.Humidity,motor_state,(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 */
 
/**
  * @}
  */
 
/**
  * @}
  */
 
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/      

4.2  mqtt.c : MQTT協定代碼

#include "mqtt.h"
 
u8 *mqtt_rxbuf;
u8 *mqtt_txbuf;
u16 mqtt_rxlen;
u16 mqtt_txlen;
u8 _mqtt_txbuf[256];//發送資料緩存區
u8 _mqtt_rxbuf[256];//接收資料緩存區
 
typedef enum
{
    //名字        值           封包流動方向  描述
    M_RESERVED1 =0  ,   //  禁止  保留
    M_CONNECT       ,   //  用戶端到服務端 用戶端請求連接配接服務端
    M_CONNACK       ,   //  服務端到用戶端 連接配接封包确認
    M_PUBLISH       ,   //  兩個方向都允許 釋出消息
    M_PUBACK        ,   //  兩個方向都允許 QoS 1消息釋出收到确認
    M_PUBREC        ,   //  兩個方向都允許 釋出收到(保證傳遞第一步)
    M_PUBREL        ,   //  兩個方向都允許 釋出釋放(保證傳遞第二步)
    M_PUBCOMP       ,   //  兩個方向都允許 QoS 2消息釋出完成(保證互動第三步)
    M_SUBSCRIBE     ,   //  用戶端到服務端 用戶端訂閱請求
    M_SUBACK        ,   //  服務端到用戶端 訂閱請求封包确認
    M_UNSUBSCRIBE   ,   //  用戶端到服務端 用戶端取消訂閱請求
    M_UNSUBACK      ,   //  服務端到用戶端 取消訂閱封包确認
    M_PINGREQ       ,   //  用戶端到服務端 心跳請求
    M_PINGRESP      ,   //  服務端到用戶端 心跳響應
    M_DISCONNECT    ,   //  用戶端到服務端 用戶端斷開連接配接
    M_RESERVED2     ,   //  禁止  保留
}_typdef_mqtt_message;
 
//連接配接成功伺服器回應 20 02 00 00
//用戶端主動斷開連接配接 e0 00
const u8 parket_connetAck[] = {0x20,0x02,0x00,0x00};
const u8 parket_disconnet[] = {0xe0,0x00};
const u8 parket_heart[] = {0xc0,0x00};
const u8 parket_heart_reply[] = {0xc0,0x00};
const u8 parket_subAck[] = {0x90,0x03};
 
void MQTT_Init(void)
{
    //緩沖區指派
    mqtt_rxbuf = _mqtt_rxbuf;
    mqtt_rxlen = sizeof(_mqtt_rxbuf);
    mqtt_txbuf = _mqtt_txbuf;
    mqtt_txlen = sizeof(_mqtt_txbuf);
    memset(mqtt_rxbuf,0,mqtt_rxlen);
    memset(mqtt_txbuf,0,mqtt_txlen);
    
//  //無條件先主動斷開
//  MQTT_Disconnect();
//    HAL_Delay(100);
//  MQTT_Disconnect();
//    HAL_Delay(100);
}
 
/*
函數功能: 登入伺服器
函數傳回值: 0表示成功 1表示失敗
*/
u8 MQTT_Connect(char *ClientID,char *Username,char *Password)
{
    u8 i,j;
    int ClientIDLen = strlen(ClientID);
    int UsernameLen = strlen(Username);
    int PasswordLen = strlen(Password);
    int DataLen;
    mqtt_txlen=0;
    //可變報頭+Payload  每個字段包含兩個位元組的長度辨別
    DataLen = 10 + (ClientIDLen+2) + (UsernameLen+2) + (PasswordLen+2);
    
    //固定報頭
    //控制封包類型
    mqtt_txbuf[mqtt_txlen++] = 0x10;        //MQTT Message Type CONNECT
    //剩餘長度(不包括固定頭部)
    do
    {
        u8 encodedByte = DataLen % 128;
        DataLen = DataLen / 128;
        // if there are more data to encode, set the top bit of this byte
        if ( DataLen > 0 )
            encodedByte = encodedByte | 128;
        mqtt_txbuf[mqtt_txlen++] = encodedByte;
    }while ( DataLen > 0 );
        
    //可變報頭
    //協定名
    mqtt_txbuf[mqtt_txlen++] = 0;           // Protocol Name Length MSB    
    mqtt_txbuf[mqtt_txlen++] = 4;           // Protocol Name Length LSB    
    mqtt_txbuf[mqtt_txlen++] = 'M';         // ASCII Code for M    
    mqtt_txbuf[mqtt_txlen++] = 'Q';         // ASCII Code for Q    
    mqtt_txbuf[mqtt_txlen++] = 'T';         // ASCII Code for T    
    mqtt_txbuf[mqtt_txlen++] = 'T';         // ASCII Code for T    
    //協定級别
    mqtt_txbuf[mqtt_txlen++] = 4;               // MQTT Protocol version = 4   對于 3.1.1 版協定,協定級别字段的值是 4(0x04)   
    //連接配接标志
    mqtt_txbuf[mqtt_txlen++] = 0xc2;            // conn flags 
    mqtt_txbuf[mqtt_txlen++] = 0;               // Keep-alive Time Length MSB    
    mqtt_txbuf[mqtt_txlen++] = 100;         // Keep-alive Time Length LSB  100S心跳包    保活時間
    
    mqtt_txbuf[mqtt_txlen++] = BYTE1(ClientIDLen);// Client ID length MSB    
    mqtt_txbuf[mqtt_txlen++] = BYTE0(ClientIDLen);// Client ID length LSB   
    memcpy(&mqtt_txbuf[mqtt_txlen],ClientID,ClientIDLen);
    mqtt_txlen += ClientIDLen;
    
    if(UsernameLen > 0)
    {   
        mqtt_txbuf[mqtt_txlen++] = BYTE1(UsernameLen);      //username length MSB    
        mqtt_txbuf[mqtt_txlen++] = BYTE0(UsernameLen);      //username length LSB    
        memcpy(&mqtt_txbuf[mqtt_txlen],Username,UsernameLen);
        mqtt_txlen += UsernameLen;
    }
    
    if(PasswordLen > 0)
    {    
        mqtt_txbuf[mqtt_txlen++] = BYTE1(PasswordLen);      //password length MSB    
        mqtt_txbuf[mqtt_txlen++] = BYTE0(PasswordLen);      //password length LSB  
        memcpy(&mqtt_txbuf[mqtt_txlen],Password,PasswordLen);
        mqtt_txlen += PasswordLen; 
    }    
    
  
    memset(mqtt_rxbuf,0,mqtt_rxlen);
    
    ESP8266_Recv_flag=0;
    ESP8266_Recv_cnt=0;
    MQTT_SendBuf(mqtt_txbuf,mqtt_txlen);
    HAL_Delay(200);
    
    memcpy((char *)mqtt_rxbuf,ESP8266_RecvBuf,ESP8266_Recv_cnt);
    for(i=0;i<ESP8266_Recv_cnt;i++)printf("%#x ",ESP8266_RecvBuf[i]);
 
    //CONNECT
    if(mqtt_rxbuf[0]==parket_connetAck[0] && mqtt_rxbuf[1]==parket_connetAck[1]) //連接配接成功            
    {
        return 0;//連接配接成功
    }
    
    return 1;
}
 
/*
函數功能: MQTT訂閱/取消訂閱資料打包函數
函數參數:
    topic       主題   
    qos         消息等級 0:最多分發一次  1: 至少分發一次  2: 僅分發一次
    whether     訂閱/取消訂閱請求包 (1表示訂閱,0表示取消訂閱)
傳回值: 0表示成功 1表示失敗
*/
u8 MQTT_SubscribeTopic(char *topic,u8 qos,u8 whether)
{    
    u8 i,j;
    mqtt_txlen=0;
    int topiclen = strlen(topic);
    
    int DataLen = 2 + (topiclen+2) + (whether?1:0);//可變報頭的長度(2位元組)加上有效載荷的長度
    //固定報頭
    //控制封包類型
    if(whether)mqtt_txbuf[mqtt_txlen++] = 0x82; //消息類型和标志訂閱
    else    mqtt_txbuf[mqtt_txlen++] = 0xA2;    //取消訂閱
 
    //剩餘長度
    do
    {
        u8 encodedByte = DataLen % 128;
        DataLen = DataLen / 128;
        // if there are more data to encode, set the top bit of this byte
        if ( DataLen > 0 )
            encodedByte = encodedByte | 128;
        mqtt_txbuf[mqtt_txlen++] = encodedByte;
    }while ( DataLen > 0 ); 
    
    //可變報頭
    mqtt_txbuf[mqtt_txlen++] = 0;           //消息辨別符 MSB
    mqtt_txbuf[mqtt_txlen++] = 0x0A;        //消息辨別符 LSB
    //有效載荷
    mqtt_txbuf[mqtt_txlen++] = BYTE1(topiclen);//主題長度 MSB
    mqtt_txbuf[mqtt_txlen++] = BYTE0(topiclen);//主題長度 LSB   
    memcpy(&mqtt_txbuf[mqtt_txlen],topic,topiclen);
    mqtt_txlen += topiclen;
    
    if(whether)
    {
       mqtt_txbuf[mqtt_txlen++] = qos;//QoS級别
    }
    
    ESP8266_Recv_flag=0;
    ESP8266_Recv_cnt=0;
    MQTT_SendBuf(mqtt_txbuf,mqtt_txlen);
    HAL_Delay(200);
    
    memcpy((char *)mqtt_rxbuf,ESP8266_RecvBuf,ESP8266_Recv_cnt);
    for(i=0;i<ESP8266_Recv_cnt;i++)printf("%#x ",ESP8266_RecvBuf[i]);
    
    if(mqtt_rxbuf[0]==parket_subAck[0] && mqtt_rxbuf[1]==parket_subAck[1]) //訂閱成功              
    {
        return 0;//訂閱成功
    }
    return 1; //失敗
}
 
//MQTT釋出資料打包函數
//topic   主題 
//message 消息
//qos     消息等級 
u8 MQTT_PublishData(char *topic, char *message, u8 qos)
{  
    int topicLength = strlen(topic);    
    int messageLength = strlen(message);     
    static u16 id=0;
    int DataLen;
    mqtt_txlen=0;
    //有效載荷的長度這樣計算:用固定報頭中的剩餘長度字段的值減去可變報頭的長度
    //QOS為0時沒有辨別符
    //資料長度             主題名   封包辨別符   有效載荷
    if(qos) DataLen = (2+topicLength) + 2 + messageLength;       
    else    DataLen = (2+topicLength) + messageLength;   
 
    //固定報頭
    //控制封包類型
    mqtt_txbuf[mqtt_txlen++] = 0x30;    // MQTT Message Type PUBLISH  
 
    //剩餘長度
    do
    {
        u8 encodedByte = DataLen % 128;
        DataLen = DataLen / 128;
        // if there are more data to encode, set the top bit of this byte
        if ( DataLen > 0 )
            encodedByte = encodedByte | 128;
        mqtt_txbuf[mqtt_txlen++] = encodedByte;
    }while ( DataLen > 0 ); 
    
    mqtt_txbuf[mqtt_txlen++] = BYTE1(topicLength);//主題長度MSB
    mqtt_txbuf[mqtt_txlen++] = BYTE0(topicLength);//主題長度LSB 
    memcpy(&mqtt_txbuf[mqtt_txlen],topic,topicLength);//拷貝主題
    mqtt_txlen += topicLength;
        
    //封包辨別符
    if(qos)
    {
        mqtt_txbuf[mqtt_txlen++] = BYTE1(id);
        mqtt_txbuf[mqtt_txlen++] = BYTE0(id);
        id++;
    }
    memcpy(&mqtt_txbuf[mqtt_txlen],message,messageLength);
    mqtt_txlen += messageLength;
        
    MQTT_SendBuf(mqtt_txbuf,mqtt_txlen);
    return mqtt_txlen;
}
 
void MQTT_SentHeart(void)
{
    MQTT_SendBuf((u8 *)parket_heart,sizeof(parket_heart));
}
 
void MQTT_Disconnect(void)
{
    MQTT_SendBuf((u8 *)parket_disconnet,sizeof(parket_disconnet));
}
 
void MQTT_SendBuf(u8 *buf,u16 len)
{
     AT_SendData(buf,len);
}         

繼續閱讀