基於STM32F103的智能家居項目設計


前言

該項目是嵌入式課程學習最后的一個項目設計,做的不是很好(大佬勿噴😁),特別是STM32數據處理部分,因為剛學的STM32,並且對C語言的指針等的使用也有些生疏(雖然學過,但大部分都忘了),不敢隨便用,所以有些代碼設計不好,只能完成一些簡單功能。ESP8266使用的是NodeMCU開發板,用ArduinoIDE開發(因為有很多現成的庫,資料也多)。APP制作用的是Android Studio開發,從網上參考了很多人的代碼,最后修改成自己的。前后花了差不多2周時間(主要是中間還有課要上,一些知識也得現學),不過最后功能基本算實現了,也學到了不少東西。

具體實現的功能包括以下三個方面:

  1. TFTLCD可以顯示STM32通過DHT11模塊和光敏電阻模塊采集到的溫度(精度±2°C)、濕度(±5%RH)、光照(0~100,最暗為0,最亮為100)信息;以及顯示設置的報警溫度、最低光照;還有ESP8266WIFI模塊通過網絡獲取到的時間(年-月-日 時:分:秒)、當地城市的天氣信息(氣溫、濕度、天氣、風力)。

  2. STM32可以在室內溫度過高(超過報警溫度)時驅動蜂鳴器發出聲音,在室內亮度較低(低於設定的最低光照)時驅動LED點亮。

  3. 在任意可以連接網絡的地方,自己設計的手機APP可以顯示室內環境信息(溫度、濕度、光照強度),並且可以設置報警溫度(0~100°C)和最低光照(0~100)。

STM32端程序設計

功能介紹

  1. STM32單片機通過DHT11溫濕度傳感器采集溫濕度

  2. 通過模數轉換器ADC采集光敏電阻分壓后的電壓,然后轉成光照強度

  3. 串口接收ESP8266的信息,提取出時間、天氣信息,以及報警溫度、最低光照信息(來自APP,用於控制蜂鳴器、led的開閉)

  4. 當室內溫度超過報警溫度時,蜂鳴器啟動;當室內光照較暗(低於設定的最低光照)時,led點亮

總體框圖如下:

image-20211116235243617

具體實現

下面將主要介紹一些功能函數,完整代碼請看:

完整代碼開源地址-Gitee

完整代碼開源地址-Github

溫濕度采集

初始化DHT11模塊

//DHT11初始化 
//返回0:初始化成功,1:失敗
u8 DHT11_Init()
{
	GPIO_InitTypeDef GPIO_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG,ENABLE);

	GPIO_InitStructure.GPIO_Pin=DHT11;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIO_DHT11,&GPIO_InitStructure);
	GPIO_SetBits(GPIO_DHT11,DHT11);	   //拉高

	DHT11_Rst();	  
	return DHT11_Check();	
}

//復位DHT11
void DHT11_Rst()	   
{                 
	DHT11_IO_OUT(); 	//SET OUTPUT
    DHT11_DQ_OUT=0; 	//拉低DQ
    delay_ms(20);    	//拉低至少18ms
    DHT11_DQ_OUT=1; 	//DQ=1 
	delay_us(30);     	//主機拉高20~40us
}
//等待DHT11的回應
//返回1:未檢測到DHT11的存在
//返回0:存在
u8 DHT11_Check() 	   
{   
	u8 retry=0;
	DHT11_IO_IN();//SET INPUT	 
    while (DHT11_DQ_IN&&retry<100)//DHT11會拉低40~50us
	{
		retry++;
		delay_us(1);
	};	 
	if(retry>=100)return 1;
	else retry=0;
    while (!DHT11_DQ_IN&&retry<100)//DHT11拉低后會再次拉高40~50us
	{
		retry++;
		delay_us(1);
	};
	if(retry>=100)return 1;	    
	return 0;
}

讀取數據

//從DHT11讀取一個位
//返回值:1/0
u8 DHT11_Read_Bit(void) 			 
{
    u8 retry=0;
    while(DHT11_DQ_IN&&retry<100)//等待變為低電平 12-14us 開始
    {
        retry++;
        delay_us(1);
    }
    retry=0;
    while(!DHT11_DQ_IN&&retry<100)//等待變高電平	 26-28us表示0,116-118us表示1
    {
        retry++;
        delay_us(1);
    }
    delay_us(40);//等待40us
    if(DHT11_DQ_IN)return 1;
    else return 0;		   
}

//從DHT11讀取一個字節
//返回值:讀到的數據
u8 DHT11_Read_Byte(void)    
{        
    u8 i,dat;
    dat=0;
    for (i=0;i<8;i++) 
    {
       dat<<=1; 
       dat|=DHT11_Read_Bit();
    }						    
    return dat;
}

 //從DHT11讀取一次數據,存儲到參數變量temp、humi中
 //temp:溫度值(范圍:0~50°)
 //humi:濕度值(范圍:20%~90%)
 //返回值:0,正常;1,讀取失敗
 u8 DHT11_Read_Data(u8 *temp,u8 *humi)    
 {        
     u8 buf[5];
     u8 i;
     DHT11_Rst();
     if(DHT11_Check()==0)
     {
         for(i=0;i<5;i++)//讀取40位數據
         {
             buf[i]=DHT11_Read_Byte();
         }
         if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4])
         {
             *humi=buf[0];
             *temp=buf[2];
         }
         
     }else return 1;
     return 0;       
 }

光照采集

使用ADC3模數轉換器采集光敏電阻的分壓,然后轉換為光照強度(轉換過程把最亮的當作100,最暗當作0來作為最終結果)

首先初始化配置ADC3

//初始化光敏傳感器
void Lsens_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    ADC_InitTypeDef ADC_InitStructure; 

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF,ENABLE);//使能PORTF時鍾	
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC3, ENABLE );	  //使能ADC3通道時鍾
    RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC3, ENABLE);//ADC復位	
    RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC3, DISABLE);//復位結束

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;//PF8 anolog輸入
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;		//模擬輸入引腳
    GPIO_Init(GPIOF, &GPIO_InitStructure);	

    ADC_DeInit(ADC3);  //復位ADC3,將外設 ADC3的全部寄存器重設為缺省值

    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;	//ADC工作模式: 獨立模式
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;	//模數轉換工作在單通道模式
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;	//模數轉換工作在單次轉換模式
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//轉換由軟件而不是外部觸發啟動
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	//ADC數據右對齊
    ADC_InitStructure.ADC_NbrOfChannel = 1;	//順序進行規則轉換的ADC通道的數目
    ADC_Init(ADC3, &ADC_InitStructure);	//根據ADC_InitStruct中指定的參數初始化外設ADCx的寄存器  

    ADC_Cmd(ADC3, ENABLE);	//使能指定的ADC3

    ADC_ResetCalibration(ADC3);	//使能復位校准  

    while(ADC_GetResetCalibrationStatus(ADC3));	//等待復位校准結束

    ADC_StartCalibration(ADC3);	 //開啟AD校准

    while(ADC_GetCalibrationStatus(ADC3));	 //等待校准結束
}

然后采集數據

//獲得ADC3某個通道的值
//ch:通道值 0~16
//返回值:轉換結果
u16 Get_ADC3(u8 ch)   
{
    //設置指定ADC的規則組通道,一個序列,采樣時間
    ADC_RegularChannelConfig(ADC3, ch, 1, ADC_SampleTime_239Cycles5 );	//ADC3,ADC通道,采樣時間為239.5周期	  			    

    ADC_SoftwareStartConvCmd(ADC3, ENABLE);		//使能指定的ADC3的軟件轉換啟動功能	

    while(!ADC_GetFlagStatus(ADC3, ADC_FLAG_EOC ));//等待轉換結束

    return ADC_GetConversionValue(ADC3);	//返回最近一次ADC3規則組的轉換結果
} 


//讀取Light Sens的值
//0~100:0,最暗;100,最亮 
u8 Lsens_Get_Val(void)
{
    u32 temp_val=0;
    u8 t;
    for(t=0;t<LSENS_READ_TIMES;t++)
    {
            temp_val+=Get_ADC3(ADC_Channel_6);	//讀取ADC值
            delay_ms(5);
    }
    temp_val/=LSENS_READ_TIMES;//得到平均值 
    if(temp_val>4000)temp_val=4000;
    return (u8)(100-(temp_val/40));
}

接收並處理串口USART2數據

初始化配置USART2

void usart2_init( void )
{	
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE ); 
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
    GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PA2
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//復用推挽輸出
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;//PA3
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空輸入
    GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA 

    //USART2 初始化設置
    USART_InitStructure.USART_BaudRate = 115200;//串口波特率
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字長為8位數據格式
    USART_InitStructure.USART_StopBits = USART_StopBits_1;//一個停止位
    USART_InitStructure.USART_Parity = USART_Parity_No;//無奇偶校驗位
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//無硬件數據流控制
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收發模式
    USART_Init(USART2, &USART_InitStructure); //初始化串口2

    USART_ClearFlag(USART2, USART_FLAG_TC);

    USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//開啟串口接受和總線空閑中斷
    //USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);

    USART_Cmd(USART2, ENABLE);                    //使能串口2 

    //USART2 NVIC 配置
    NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//搶占優先級3
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子優先級3
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);	//根據指定的參數初始化VIC寄存器
}


接收USART2數據

char USART2_RX_BUF[RX_BUF_MAX_LEN];
u16 USART2_RX_STA = 0;     

void USART2_IRQHandler(void)
{
    u8 r;
    if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)  //接收中斷
    {
        r = USART_ReceiveData(USART2);//(USART2->DR);	//讀取接收到的數據	
        if((USART2_RX_STA&0x8000)==0)//接收未完成
        {
            if(USART2_RX_STA&0x4000)//接收到了0x0d(即'\r')
            {
                if(r!=0x0a)USART2_RX_STA=0;//接收錯誤(即沒有接收到'\n'),重新開始

                else {
                    //USART2_RX_STA|=0x8000;	//接收完成了 (接收到'\r'后接收到'\n')
                    USART2_RX_STA = 0;  //接收到\n, 重新等待下一次接收
                }
            }
            else //還沒收到0X0D(即'\r')
            {	
                if(r==0x0d)USART2_RX_STA|=0x4000; //如果接收到\r, 讓USART2_RX_STA的14位置1
                else
                {
                    USART2_RX_BUF[USART2_RX_STA&0X3FFF]=r;
                    USART2_RX_STA++;
                    if(USART2_RX_STA>(RX_BUF_MAX_LEN-1))USART2_RX_STA=0;//接收數據錯誤,重新開始接收	  
                }		 
            }
        } 		
    } 

}

處理USART2接收到的數據

提取時間、天氣信息;為了方便,ESP8266發給STM32的數據都是用逗號隔開的,如 時間,天氣,氣溫,濕度,……

天氣數據保存在結構體中

struct WeatherData{
    char datetime[20];
    char city[10];
    char weather[10];  //原本為10
    char temp[10];
    char humi[10];
    char windpower[10];  //風力等級	
};

對USART2接收的數據進行處理

struct WeatherData processWdata(char data[]){
    //char data[] = "2022-22-22 33:33:33,yin,47,48,<=7";
    u8 i=0, j=0, i0=0, k=0;
	u8 ind=0, jnd=0;
	
    int slen = strlen(data);
    struct WeatherData weather;
	
    //初始化
    for(ind=0; ind<8; ind++){
        switch(ind){
            case 0: {
                    for(jnd=0; jnd<20; jnd++){
                            weather.datetime[jnd]='\0';
                    }
            };break;
            case 1: {
                    for(jnd=0; jnd<10; jnd++){
                            weather.city[jnd]='\0';
                    }
            };break;
            case 2: {
                    for(jnd=0; jnd<10; jnd++){
                            weather.humi[jnd]='\0';
                    }
            };break;

            case 3: {
                    for(jnd=0; jnd<10; jnd++){
                            weather.temp[jnd]='\0';
                    }
            };break;
            case 4: {
                    for(jnd=0; jnd<10; jnd++){
                            weather.weather[jnd]='\0';
                    }
            };break;
            case 5: {
                    for(jnd=0; jnd<10; jnd++){
                            weather.windpower[jnd]='\0';
                    }
            };break;
            case 6: {
                    for(jnd=0; jnd<10; jnd++){
                            minLsens_str[jnd]='\0';
                    }
            };break;
            case 7: {
                    for(jnd=0; jnd<10; jnd++){
                            alarmTemp_str[jnd]='\0';
                    }
            };break;

        }
    }
    strcpy(weather.city, "西安");
    for(i=0; i<slen; i++){
        if(data[i]==',') {
            i0++;
            for(j=k; j<i; j++){

                if(i0==1) weather.datetime[j-k]=data[j];
                else if(i0==2) weather.weather[j-k]=data[j];
                else if(i0==3) weather.temp[j-k]=data[j];
                else if(i0==4) weather.humi[j-k]=data[j];
                else if(i0==5) weather.windpower[j-k]=data[j];
                else if(i0==6) alarmTemp_str[j-k]=data[j];
                else if(i0==7) minLsens_str[j-k]=data[j];
            }
            k=i+1;
        }

    }
    return weather;
}

字符串數字轉為整型數字

u8 str2int(char s[]){
    u8 n=0, i=0, len, sum=0;
    len = strlen(s);
    for(i=0; i<len; i++){
        if(47<s[i]<58){
            n = s[i] - 48;   //得到一位數字
            sum += (n * pow(10, len-1-i));
        }
    }
    return sum;	
}

發送數據

通過定時器TIM2定時1s發送一次數據(json字符串格式)給ESP8266模塊

初始化配置TIM2

//初始化TIM2
void TIM2_init(u16 arr, u16 psc){
    TIM_TimeBaseInitTypeDef TIM_Structure;
    NVIC_InitTypeDef NVIC_Structure;
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

    //延時時間(單位:s):arr/(72000000/psc)
    TIM_Structure.TIM_Period = arr - 1;  //裝載值
    TIM_Structure.TIM_Prescaler = psc-1;  //分頻系數
    TIM_Structure.TIM_ClockDivision = 0;
    TIM_Structure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_Structure);

    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //開啟定時器TIM2中斷
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    NVIC_Structure.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_Structure.NVIC_IRQChannelPreemptionPriority = 6; //搶占優先級
    NVIC_Structure.NVIC_IRQChannelSubPriority = 4;  //子優先級
    NVIC_Structure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_Structure);

    TIM_ClearFlag(TIM2, TIM_FLAG_Update);
    TIM_Cmd(TIM2, ENABLE);
}

定時時間(項目中使用1s)到達后產生定時器中斷,這時候進入中斷函數中發送數據

void TIM2_IRQHandler(void){
    if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET){
        ESP8266_Usart("{\"temp\":\"%d\",\"humi\":\"%d\",\"light\":\"%d\",\"ledsta\":\"%d\",\"beepsta\":\"%d\"}\r\n", dhtData.temp, dhtData.humi, lsens, ledSta, beepSta);//輸出【光照、溫度、濕度、led、beep狀態】到esp8266,再發送給手機
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    }
}

控制LED與蜂鳴器

void ledBeepOnOrNot(u8 lsens, u8 mlsens, u8 t, u8 alarmT){  //是否啟動LED、BEEP
    if(lsens < mlsens){
        LED1 = 0;  //點亮
        ledSta = 1;  
    }

    else{
        LED1 = 1;  //熄滅
        ledSta = 0;
    }

    if(t>alarmT){
        BEEP=1;         //叫
        beepSta = 1;
    }
    else{
        BEEP=0;			//不叫
        beepSta = 0;
    }
}

ESP8266端程序設計

ESP8266WIFI模塊通過MQTT協議連接服務器,數據發布到服務器后,Android客戶端可以向服務器訂閱主題來獲取到消息,然后進行展示。ESP8266客戶端通過訂閱也能獲取到Android客戶端發布的消息。

image-20211116233340883

功能介紹

  1. 連接WiFi
  2. 啟動后led點亮,連接上MQTT服務端后閃爍,未連接常亮
  3. 通過網絡獲取如下信息
    • 時間:使用【蘇寧的API】 http://quan.suning.com/getSysTime.do
    • 天氣:使用【高德的天氣API】http://restapi.amap.com/v3/weather/weatherInfo?key=xxxxx&city=610100)(參數key要自己獲取,city是城市編碼)
  4. 與STM32通過串口進行數據通信
  5. 建立與MQTT服務器的連接
  6. 向服務器指定的主題發布消息和接收消息
  7. json數據處理

具體實現

ESP8266使用NodeMCU模塊,ArduinoIDE開發

一些全局變量

//wifi配置
const char* ssid = "xxxxxx";
const char* password = "xxxxxxxx";

//mqtt服務器連接配置
const char *mqttBroker = "xxxxxx.iotcloud.tencentdevices.com";
const char *mqttUsername = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const char *mqttPassword = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
char *mqttClientId = "xxxxxxxxxxxesp8266";
const char *pubTopic = "xxxxxx/esp8266/data";
const char *subTopic = "xxxxxx/esp8266/data";
const int mqttPort = 1883;

//時間api
const String timeUrl = "http://quan.suning.com/getSysTime.do";
//高德地圖api(自己獲取key, city是城市編碼)
const String gaoDeUrl = "http://restapi.amap.com/v3/weather/weatherInfo?key=xxxxx&city=610100";

連接WiFi

void connectWifi(){
  WiFi.mode(WIFI_STA);  //WIFI_STA、WIFI_AP、WIFI_AP_STA
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
}


發送HTTP請求

String callHttp(String httpUrl) { 
  HTTPClient hClient;
  String message;
  hClient.begin(httpUrl); 
  
  int httpCode = hClient.GET();
  if (httpCode > 0) {
    if (httpCode == HTTP_CODE_OK) {
      String payload = hClient.getString();
      message = payload;
    }
  } else {
    message = "[1-HTTP]failed, error:" + String(hClient.errorToString(httpCode).c_str());
  }
  hClient.end();
  return message;
}

連接MQTT服務器

void connectMqttServer(){
  while (!mqttClient.connected()) {
    if (mqttClient.connect(mqttClientId, mqttUsername, mqttPassword)) {
        subscribeTopic(subTopic);  //訂閱主題
    } else {
        delay(2000);
//        Serial.println("連接失敗");
    }
  }  
}

發布信息到指定主題

void publishTopic(const char* topic, char* jsonMsg){
  mqttClient.publish(topic, jsonMsg, false);
}

訂閱主題

// 訂閱指定主題
void subscribeTopic(const char* topic){
  if(mqttClient.subscribe(topic)){

  } else {

  }  
}

回調函數(當收到服務器的消息后會調用)

void receiveCallback(char* topic, byte* payload, unsigned int length) {
  serverMsg = "";   //當下次調用時清空數據
  for (int i = 0; i < length; i++) {
//    Serial.print((char)payload[i]);
    serverMsg += (char)payload[i];
  }
//  Serial.println("\n");
//  Serial.println(serverMsg);
}

完整代碼

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <ESP8266HTTPClient.h>
#include <WiFiManager.h>  

//wifi配置
const char* ssid = "xxxxxx";
const char* password = "xxxxxxxx";

//mqtt服務器連接配置
const char *mqttBroker = "xxxxxx.iotcloud.tencentdevices.com";
const char *mqttUsername = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const char *mqttPassword = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
char *mqttClientId = "xxxxxxxxxxxesp8266";
const char *pubTopic = "xxxxxx/esp8266/data";
const char *subTopic = "xxxxxx/esp8266/data";
const int mqttPort = 1883;

WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);

struct Weather{
  String wea="";
  String temp="";
  String windpower="";  //風力,單位:級
  String humi="";
};

struct ServerMsg{
  String alarmTemp="";
  String minLight="";
};

//時間api
const String timeUrl = "http://quan.suning.com/getSysTime.do";
//高德地圖api(自己獲取key, city是城市編碼)
const String gaoDeUrl = "http://restapi.amap.com/v3/weather/weatherInfo?key=xxxxx&city=610100";
String timeMsg="";
String weatherMsg="";
String timeAfterParse="";
struct Weather weatherAfterParse;
String data2stm="";   //打包后發送給stm32的信息
String data2server="";  //發布到mqtt服務器的信息

String callHttp(String httpUrl);
String parseJsonTime(String tjson);
struct Weather parseJsonWeather(String wjson);
void ledOnOff();
String tqzh2py(String zhtq);    //中文天氣轉拼音
String windpowerFormat(String wp);

void connectWifi();
void setLedOnOff();
void publishTopic(const char* topic, char* jsonMsg);
void subscribeTopic(const char* topic);
struct ServerMsg parseServerMsg(String jsonMsg);
void getSerialDataAndProcess();

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);     // 設置板上LED引腳為輸出模式
  digitalWrite(LED_BUILTIN, LOW);  // 啟動后打開板上LED
  Serial.begin(115200);               // 啟動串口通訊

  connectWifi();
  mqttInit();
  connectMqttServer();

  timeMsg = callHttp(timeUrl);
  weatherMsg = callHttp(gaoDeUrl);
}

struct ServerMsg serverMsgAfterParse;
String serverMsg="";
String stm32Msg="";
int n = 0;

void loop() {

  delay(500);
  n++;
    
  //獲取網絡數據(時間、天氣)
  
  if(n % 2 == 0){
    ledOnOff();
    timeMsg = callHttp(timeUrl);
    if(n >= 20){   //10s獲取一次天氣
      weatherMsg = callHttp(gaoDeUrl);
      n=0;
    }
  }
  
  //獲取stm32數據,並發送到mqtt服務器
  getSerialDataAndProcess();

  //對網絡獲取到的數據處理
  if(timeMsg!=""){
    timeAfterParse = parseJsonTime(timeMsg);
  }
  if(weatherMsg!=""){
    weatherAfterParse = parseJsonWeather(weatherMsg);
  }
  if(serverMsg!=""){
    serverMsgAfterParse = parseServerMsg(serverMsg);
  }
  
  //打包發送到stm32
  if(timeMsg!="" && weatherMsg!=""){
      data2stm = (timeAfterParse + "," + tqzh2py(weatherAfterParse.wea) + "," + weatherAfterParse.temp + "," +weatherAfterParse.humi +"," + windpowerFormat(weatherAfterParse.windpower) + "," + serverMsgAfterParse.alarmTemp + "," + serverMsgAfterParse.minLight + "," + "\r\n"); //日期,天氣,溫度,濕度,風力等級,報警溫度,最低光照
      Serial.print(data2stm);
  }
  
  if (mqttClient.connected()) { // 如果開發板成功連接服務器
    mqttClient.loop();          // 處理信息以及心跳
  } else {                      // 如果開發板未能成功連接服務器
    connectMqttServer();        // 則嘗試連接服務器
  }
  
}

void mqttInit(){
  mqttClient.setServer(mqttBroker, mqttPort);
  // 設置MQTT訂閱回調函數
  mqttClient.setCallback(receiveCallback);
}

// 連接MQTT服務器並訂閱信息
void connectMqttServer(){
  while (!mqttClient.connected()) {
    if (mqttClient.connect(mqttClientId, mqttUsername, mqttPassword)) {
        subscribeTopic(subTopic);  //訂閱主題
    } else {
        delay(2000);
//        Serial.println("連接失敗");
    }
  }  
}

// 訂閱指定主題
void subscribeTopic(const char* topic){
  if(mqttClient.subscribe(topic)){

  } else {

  }  
}

// 收到信息后的回調函數
void receiveCallback(char* topic, byte* payload, unsigned int length) {
  serverMsg = "";   //當下次調用時清空數據
  for (int i = 0; i < length; i++) {
//    Serial.print((char)payload[i]);
    serverMsg += (char)payload[i];
  }
//  Serial.println("\n");
//  Serial.println(serverMsg);
}

struct ServerMsg parseServerMsg(String jsonMsg){
  struct ServerMsg smsg;
  const size_t capacity = 96;
  DynamicJsonDocument sdoc(capacity);
 
  // 反序列化數據
  deserializeJson(sdoc, jsonMsg);
  smsg.alarmTemp = sdoc["atemp"].as<String>();
  smsg.minLight = sdoc["mlight"].as<String>();
  return smsg;
}

void publishTopic(const char* topic, char* jsonMsg){
  mqttClient.publish(topic, jsonMsg, false);
}

void ledOnOff(){
  static bool LEDState=0;
  if(mqttClient.connected()){
    LEDState = !LEDState;   //連上,led閃爍
  }
  else{
    LEDState = 0;   //沒連上,led常亮
  }
  digitalWrite(LED_BUILTIN, LEDState);
}

String callHttp(String httpUrl) { 
  HTTPClient hClient;
  String message;
  hClient.begin(httpUrl); 
  
  int httpCode = hClient.GET();
  if (httpCode > 0) {
    if (httpCode == HTTP_CODE_OK) {
      String payload = hClient.getString();
      message = payload;
    }
  } else {
    message = "[1-HTTP]failed, error:" + String(hClient.errorToString(httpCode).c_str());
  }
  hClient.end();
  return message;
}

void getTimeMsg(){
  timeMsg = callHttp(timeUrl);
}

void getWeatherMsg(){
  weatherMsg = callHttp(gaoDeUrl);  
}

String parseJsonTime(String tjson){

  const size_t capacity = 96;
  DynamicJsonDocument tdoc(capacity);
 
  // 反序列化數據
  deserializeJson(tdoc, tjson);
 
  // 獲取解析后的數據信息
  String datetime = tdoc["sysTime2"].as<String>();

  return datetime;
}


//{"status":"1","count":"1","info":"OK","infocode":"10000","lives":[{"province":"陝西","city":"西安市","adcode":"610100","weather":"晴","temperature":"13","winddirection":"北","windpower":"≤3","humidity":"88","reporttime":"2021-10-29 21:03:10"}]}
struct Weather parseJsonWeather(String wjson){
  
  struct Weather weather;

  const size_t capacity = 512;
  DynamicJsonDocument wdoc(capacity);
 
  // 反序列化數據
  deserializeJson(wdoc, wjson);

  JsonObject lives_0 = wdoc["lives"][0];
  
  weather.wea = lives_0["weather"].as<String>(); // "晴"
  weather.temp = lives_0["temperature"].as<String>(); // "13"
  weather.humi = lives_0["humidity"].as<String>(); // "88"
  weather.windpower = lives_0["windpower"].as<String>(); // "≤3"
  return weather;
}


String tqzh2py(String zhtq){  

  String zh_cn[68] = {"晴","少雲","晴間多雲","多雲","陰","有風","平靜","微風","和風","清風","強風/勁風","疾風","大風","烈風","風暴","狂爆風","颶風","熱帶風暴","霾","中度霾","重度霾","嚴重霾","陣雨","雷陣雨","雷陣雨並伴有冰雹","小雨","中雨","大雨","暴雨","大暴雨","特大暴雨","強陣雨","強雷陣雨","極端降雨","毛毛雨/細雨","雨","小雨-中雨","中雨-大雨","大雨-暴雨","暴雨-大暴雨","大暴雨-特大暴雨","雨雪天氣","雨夾雪","陣雨夾雪","凍雨","雪","陣雪","小雪","中雪","大雪","暴雪","小雪-中雪","中雪-大雪","大雪-暴雪","浮塵","揚沙","沙塵暴","強沙塵暴","龍卷風","霧","濃霧","強濃霧","輕霧","大霧","特強濃霧","熱","冷","未知",};
  String py[68] = {"qing","shaoyun","qingjianduoyun","duoyun","yin","youfeng","pingjing","weifeng","hefeng","qingfeng","qiangfeng/jinfeng","jifeng","dafeng","liefeng","fengbao","kuangbaofeng","jufeng","redaifengbao","mai","zhongdumai","zhongdumai","yanzhongmai","zhenyu","leizhenyu","leizhenyubingbanyoubingbao","xiaoyu","zhongyu","dayu","baoyu","dabaoyu","tedabaoyu","qiangzhenyu","qiangleizhenyu","jiduanjiangyu","maomaoyu/xiyu","yu","xiaoyu-zhongyu","zhongyu-dayu","dayu-baoyu","baoyu-dabaoyu","dabaoyu-tedabaoyu","yuxuetianqi","yujiaxue","zhenyujiaxue","dongyu","xue","zhenxue","xiaoxue","zhongxue","daxue","baoxue","xiaoxue-zhongxue","zhongxue-daxue","daxue-baoxue","fuchen","yangsha","shachenbao","qiangshachenbao","longjuanfeng","wu","nongwu","qiangnongwu","qingwu","dawu","teqiangnongwu","re","leng","weizhi"};
  
  for(int i=0; i<68; i++){
    
   // if(strstr())
    if(zh_cn[i] == zhtq)
      return py[i];
    }
    
  return zhtq;  //沒有就返回中文
}
 
String windpowerFormat(String wp){
  
  if(wp=="≤3"){
    return "<=3";
  }
  return wp;
}

//得到數據后處理方式,把它當作一個串口(偽)中斷函數
void getSerialDataAndProcess(){
  if (Serial.available()) {    //串口讀取到的轉發到wifi,因為串口是一位一位的發送所以在這里緩存完再發送
    size_t countBytes = Serial.available();
    char sbuf[countBytes];
    Serial.readBytes(sbuf, countBytes);

//    Serial.write(sbuf, countBytes);
    publishTopic(pubTopic, sbuf);
    Serial.flush();
  }
}



void connectWifi(){
  
//  WiFiManager wifiManager;
//    
//  自動連接WiFi。以下語句的參數是連接ESP8266時的WiFi名稱
//  wifiManager.autoConnect("IamESP8266");

  WiFi.mode(WIFI_STA);  //WIFI_STA、WIFI_AP、WIFI_AP_STA
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
}


Android端程序設計

功能介紹

主要是通過MQTT服務器,獲取ESP8266發布的信息,然后進行處理顯示出來;同時APP也可以發布消息,用於修改報警溫度、最低光照值。

具體實現

使用Android Studio進行開發(具體安裝下載、環境配置請百度)

工程代碼已開源,前往GitHub請點擊前去Github,前往Gitee請點擊前去Gitee

添加MQTT依賴

  1. 在自己的[project]下的 build.gradle添加如下代碼
 repositories {
         google()
         mavenCentral()
         //添加下面這句代碼
         maven {
             url "https://repo.eclipse.org/content/repositories/paho-releases/"
         }
     }
  1. 在[project]/app下的build.gradle添加如下代碼
 dependencies {
     compile 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'
     compile 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.1'
 }

然后選擇頂部彈出的 Sync Now 信息

image-20211110232735634

權限配置

修改AndroidManifest.xml,添加權限

<!--允許程序打開網絡套接字-->
<uses-permission android:name="android.permission.INTERNET" />
<!--允許程序獲取網絡狀態-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

如下:
image-20211110233017614

顯示界面

修改文件activity_main.xml,實現如下顯示效果

image-20211110233204303

具體代碼如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

    <TextView
        android:layout_marginTop="20dp"
        android:layout_marginLeft="20dp"
        android:layout_marginBottom="10dp"
        android:id="@+id/house_env"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="室內環境"
        android:textStyle="bold"
        android:textSize="20dp" />
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:id="@+id/show_temp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:layout_marginLeft="20dp"
            android:layout_marginBottom="10dp"

            android:text="溫度(°C)"
            android:textSize="20dp" />

        <TextView
            android:id="@+id/temp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:layout_marginLeft="20dp"
            android:layout_marginBottom="10dp"
            android:text="0"
            android:textSize="20dp" />

        <TextView
            android:id="@+id/show_humi"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_marginTop="10dp"
            android:layout_marginLeft="20dp"
            android:layout_marginBottom="10dp"
            android:text="濕度(%RH)"
            android:textSize="20dp" />
        <TextView
            android:id="@+id/humi"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_marginTop="10dp"
            android:layout_marginLeft="20dp"
            android:layout_marginBottom="10dp"
            android:text="0"
            android:textSize="20dp" />

    </LinearLayout>

    <LinearLayout android:layout_width="match_parent"
        android:layout_height="wrap_content"
        >
        <TextView
            android:id="@+id/show_light"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_marginLeft="20dp"
            android:layout_marginTop="10dp"
            android:layout_marginBottom="10dp"
            android:text="光照強度"
            android:textSize="20dp" />
        <TextView
            android:id="@+id/light"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_marginLeft="20dp"
            android:layout_marginTop="10dp"
            android:layout_marginBottom="10dp"
            android:text="0"
            android:textSize="20dp" />

    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/ledsta1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_marginLeft="20dp"
            android:layout_marginTop="10dp"
            android:layout_marginBottom="10dp"
            android:text="LED狀態"
            android:textSize="20dp" />
        <TextView
            android:id="@+id/ledsta2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_marginLeft="20dp"
            android:layout_marginTop="10dp"
            android:layout_marginBottom="10dp"
            android:text="關"
            android:textSize="20dp" />

        <TextView
            android:id="@+id/beepsta1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_marginLeft="20dp"
            android:layout_marginTop="10dp"
            android:layout_marginBottom="10dp"
            android:text="蜂鳴器狀態"
            android:textSize="20dp" />
        <TextView
            android:id="@+id/beepsta2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_marginLeft="20dp"
            android:layout_marginTop="10dp"
            android:layout_marginBottom="10dp"
            android:text="關"
            android:textSize="20dp" />
    </LinearLayout>
    <TextView
        android:id="@+id/sets"
        android:layout_column="1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"

        android:layout_marginLeft="20dp"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="10dp"
        android:text="設置"
        android:textStyle="bold"
        android:textSize="20dp" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/set_temp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="20dp"
            android:layout_marginTop="10dp"
            android:layout_marginBottom="10dp"
            android:text="報警溫度(當前溫度超過該值蜂鳴器將報警)"
            android:textSize="15dp" />

        <TextView
            android:id="@+id/seekbarval1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:layout_marginLeft="10dp"
            android:text="40"/>
    </LinearLayout>
    <SeekBar
        android:id="@+id/seekBar1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="10dp"
        android:layout_gravity="center"
        android:max="100"
        android:progress="40"/>
<!--        android:progressDrawable="@drawable/seekbar_progress" />-->

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/set_tlight"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_marginLeft="20dp"
            android:layout_marginTop="10dp"
            android:layout_marginBottom="10dp"
            android:text="最低光照(當前光照小於該值將點亮led燈)"
            android:textSize="15dp" />

        <TextView
            android:id="@+id/seekbarval2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:layout_marginLeft="20dp"
            android:text="12"/>
    </LinearLayout>
    <SeekBar
        android:id="@+id/seekBar2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="10dp"
        android:layout_gravity="center"
        android:max="100"
        android:progress="12"/>
<!--        android:progressDrawable="@drawable/seekbar_progress2" />-->
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">


    </LinearLayout>
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        >
        <Button
            android:layout_marginLeft="100dp"
            android:layout_marginTop="20dp"
            android:id="@+id/connectbtn"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:textColor="@color/white"
            android:text="連接"/>

        <Button
            android:layout_marginTop="20dp"
            android:layout_marginRight="20dp"
            android:layout_marginLeft="20dp"
            android:id="@+id/exitbtn"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:textColor="@color/white"
            android:text="退出"/>

    </LinearLayout>
    <TextView
        android:layout_margin="5dp"
        android:id="@+id/msgTxt"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:scrollbars="vertical"
        android:text=""/>
</LinearLayout>

界面控制

接下來修改MainActivity.java,添加控制操作,讓數據可以通過TextView顯示,按下按鈕可以作出反應,上面只是完成一個靜態界面的創建

package com.ajream.mqttdemo4;

import android.annotation.SuppressLint;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;


public class MainActivity extends AppCompatActivity {

    //定義成員
    private Button connectbtn;
    private Button exitbtn;

    private TextView temptv;
    private TextView humitv;
    private TextView lighttv;
    private TextView ledtv;
    private TextView beeptv;
    private TextView bar1tv;
    private TextView bar2tv;
    private TextView showmsgtv;

    private SeekBar tempbar;
    private SeekBar lightbar;

    //MQTT客戶端配置所需信息
    private String host = "tcp://xxxxxxx.iotcloud.tencentdevices.com:1883";  //mqtt服務器(騰訊雲)地址、端口
    private String userName = "xxxxxxxxxx";       //用戶名(在騰訊雲查看自己的設備信息)
    private String passWord = "xxxxxxxxxx";			//密碼(在騰訊雲查看自己的設備信息)
    private String mqtt_id = "xxxxxxxxxxxxxxxxxxx";
    private String mqtt_sub_topic = "xxxxxxxxxx/AndroidClient/data";
    private String mqtt_pub_topic = "xxxxxxxxxx/AndroidClient/data";

    private ScheduledExecutorService scheduler;
    private MqttClient mqttClient;
    private MqttConnectOptions options;
    private Handler handler;

    private String msgToPublish = "";  //要發布的消息
    private String alarmTempMsg = "";
    private String minLightMsg = "";
    JSONObject msgGet;

    @SuppressLint("HandlerLeak")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //關聯控件
        connectbtn = findViewById(R.id.connectbtn);
        exitbtn = findViewById(R.id.exitbtn);
        temptv = findViewById(R.id.temp);
        humitv = findViewById(R.id.humi);
        lighttv = findViewById(R.id.light);
        ledtv = findViewById(R.id.ledsta2);
        beeptv = findViewById(R.id.beepsta2);
        bar1tv = findViewById(R.id.seekbarval1);
        bar2tv = findViewById(R.id.seekbarval2);
        showmsgtv = findViewById(R.id.msgTxt);

        tempbar = findViewById(R.id.seekBar1);
        lightbar = findViewById(R.id.seekBar2);

        alarmTempMsg = String.valueOf(tempbar.getProgress());
        minLightMsg = String.valueOf(lightbar.getProgress());

        /*點擊按鈕連接*/
        connectbtn.setOnClickListener(view -> {
            Mqtt_init();
            startReconnect();
        });

        exitbtn.setOnClickListener(view -> {
            android.os.Process.killProcess(android.os.Process.myPid());
        });


        tempbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                Toast.makeText(MainActivity.this, "設置報警溫度為: " + progress, Toast.LENGTH_LONG).show();
                bar1tv.setText(check(progress));
                alarmTempMsg = check(progress);
            }


            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
//                msgToPublish = alarmTempMsg + "," + minLightMsg;
                msgToPublish = "{\"atemp\":\"" + alarmTempMsg + "\",\"mlight\":\"" + minLightMsg+"\"}";
                publishMsg(mqtt_pub_topic, msgToPublish);

            }
        });

        lightbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
                Toast.makeText(MainActivity.this, "設置最低光照為: " + i, Toast.LENGTH_LONG).show();
                bar2tv.setText(check(i));
                minLightMsg = check(i);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                msgToPublish = "{\"atemp\":\"" + alarmTempMsg + "\",\"mlight\":\"" + minLightMsg+"\"}";
                publishMsg(mqtt_pub_topic, msgToPublish);
            }
        });

        handler = new Handler() {
            @SuppressLint("SetTextI18n")

            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    case 1: //開機校驗更新回傳
                        break;
                    case 2:  // 反饋回傳
                        break;
                    case 3: //MQTT 收到消息回傳 UTF8Buffer msg=newUTF8Buffer(object.toString());
//                        Toast.makeText(MainActivity.this,msg.obj.toString(),Toast.LENGTH_SHORT).show();
                        showmsgtv.setText(msg.obj.toString());
                        JSONObject msgGet = null;
                        try {
                            msgGet = new JSONObject(msg.obj.toString());
                            temptv.setText(msgGet.get("temp").toString());
                            humitv.setText(msgGet.get("humi").toString());
                            lighttv.setText(msgGet.get("light").toString());
                            if(Integer.parseInt(msgGet.get("ledsta").toString())==0) ledtv.setText("關");
                            else ledtv.setText("開");
                            if(msgGet.get("beepsta").toString().charAt(0)=='0') beeptv.setText("關");
                            else beeptv.setText("開");
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                        break;

                    case 30:  //連接失敗
                        Toast.makeText(MainActivity.this,"連接失敗" ,Toast.LENGTH_SHORT).show();
                        break;
                    case 31:   //連接成功
                        Toast.makeText(MainActivity.this,"連接成功" ,Toast.LENGTH_SHORT).show();
                        try {
                            mqttClient.subscribe(mqtt_sub_topic,1);
                        }
                        catch (MqttException e) {
                            e.printStackTrace();
                        }
                        break;
                    default:
                        break;
                }
            }
        };
    }

    private String check(int progress) {
        int curValue = 100 * progress/Math.abs(100);
        return String.valueOf(curValue);
    }

    private void publishMsg(String topic, String message2) {
        if (mqttClient == null || !mqttClient.isConnected()) {
            return;
        }
        MqttMessage message = new MqttMessage();
        message.setPayload(message2.getBytes());
        try {
            mqttClient.publish(topic, message);
        } catch (MqttException e) {
            e.printStackTrace();
        }
    }

    private void Mqtt_init() {
        try {
            //host為主機名,test為clientid即連接MQTT的客戶端ID,一般以客戶端唯一標識符表示,MemoryPersistence設置clientid的保存形式,默認為以內存保存
            mqttClient = new MqttClient(host, mqtt_id, new MemoryPersistence());

            //MQTT的連接設置
            options = new MqttConnectOptions();

            //設置是否清空session,這里如果設置為false表示服務器會保留客戶端的連接記錄,這里設置為true表示每次連接到服務器都以新的身份連接
            options.setCleanSession(false);

            options.setUserName(userName); //設置連接的用戶名

            options.setPassword(passWord.toCharArray()); //設置連接的密碼

            // 設置超時時間 單位為秒
            options.setConnectionTimeout(10);
            // 設置會話心跳時間 單位為秒 服務器會每隔1.5*20秒的時間向客戶端發送個消息判斷客戶端是否在線,但這個方法並沒有重連的機制
            options.setKeepAliveInterval(20);

            //設置回調函數
            mqttClient.setCallback(new MqttCallback() {
                @Override
                public void connectionLost(Throwable cause) {
                    //連接丟失后,一般在這里面進行重連
//                    System.out.println("connectionLost----------");
//                    startReconnect();
                }
                @Override
                public void deliveryComplete(IMqttDeliveryToken token) {
                    //publish后會執行到這里
                    System.out.println("deliveryComplete---------"
                            + token.isComplete());
                }
                @Override
                public void messageArrived(String topicName, MqttMessage message)
                        throws Exception {
                    //subscribe后得到的消息會執行到這里面
                    System.out.println("getMsg: ");
                    Message msg = new Message();
                    msg.what = 3;   //收到消息標志位
                    msg.obj = message.toString();
                    handler.sendMessage(msg);    // hander 回傳
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void Mqtt_connect() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    if(!(mqttClient.isConnected())) {
                        mqttClient.connect(options);
                        Message msg = new Message();
                        msg.what = 31;
                        handler.sendMessage(msg);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    Message msg = new Message();
                    msg.what = 30;
                    handler.sendMessage(msg);
                }
            }
        }).start();
    }
    private void startReconnect() {
        scheduler = Executors.newSingleThreadScheduledExecutor();
        scheduler.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                if (!mqttClient.isConnected()) {
                    Mqtt_connect();
                }
            }
        }, 0 * 1000, 10 * 1000, TimeUnit.MILLISECONDS);
    }
}

注意:MQTT服務端配置這里沒有介紹,可以使用一些公用的MQTT服務器(可以去百度搜索),也可以自己搭建一個,我這里使用的是自己通過騰訊雲配置的MQTT服務,具體搭建可以到我的博客查看或者看官方文檔

效果預覽

TFTLCD屏幕顯示

image-20211117003640156

Android APP界面

image-20211110234502485 image-20211110234549641


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM