esp32+stm32+onenet+arduino 智能家居系统

第一次发帖,有什么不对的,麻烦指正一下。

以下是我最近完成的一个小项目,不过还有很大的改动优化空间,本篇只是带来一个大体的框架,以及我个人在这个项目所遇到的一些问题,以及我个人对这些问题的解决以及看法,(一定有错的地方,发出来也是希望大家帮忙看看 嘿嘿)

首先,介绍一下本项目,本项目所需的有3块esp32_wroom_32模块,以及温湿度传感器DHT11,继电器开关,光敏电阻,stm32f407,以及一个串口屏(淘晶驰TJC4832T135_001),不过这个串口屏也可以不要,看你自己吧。首先需要解决的是esp32间的数据传输问题,这个我使用的是arduino开发,然后用的是esp_now传输,这个有不了解的我建议去下面的链接了解一下

https://www.bilibili.com/video/BV1g3411y7ro/?spm_id_from=333.788&vd_source=de8713d22d3d70995910b8e33ffe061b

这是可以无基础的学习的一个视频资源,网上也有很多关于now的介绍,可以到乐鑫的官网去深入了解。

然后第一个肯定是arduino的配置 ,首先去arduino的官网下载那个安装

     文件——首选项——附加开发板管理器网址 :添加   https://dl.espressif.com/dl/package_esp32_index.json

           ——管理库:然后输入ESP32  (选那个带esp32的)然后进行下载就可以了

     工具——开发板:选择ESP32 Dev Module;然后选择对应的端口;

然后首先要实现的是三个esp32之间的通信,我选择的是esp32独有esp-now协议

ESP-NOW 是由乐鑫开发的另一款协议,可以使多个设备在没有或不使用 Wi-Fi 的情况下进行通信。这种协议类似常见于无线鼠标中的低功耗 2.4GHz 无线连接——设备在进行通信之前要进行配对。配对之后,设备之间的连接是持续的、点对点的,并且不需要握手协议。

ESP-NOW 广泛应用于智能照明、远程控制、传感器等领域。

对于这个协议想了解更深入的话,可以去以下链接

ESP-NOW - ESP32 - — ESP-IDF 编程指南 latest 文档 (espressif.com)

 首先要获取各个esp32的mac地址,因为now协议是通过其mac地址进行数据传输的

#include <WiFi.h>
void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  WiFi.mode(WIFI_MODE_STA);
  Serial.println(WiFi.macAddress());  //串口输出mac地址 
}

void loop() {
  // put your main code here, to run repeatedly:

}

将以上代码 编译上传至esp32,即可得到其mac地址

然后以下是now的基本创建过程

 #include <WiFi.h>
#include <esp_now.h>

uint8_t MACA[] = {0xC0,0x49,0xEF,0xD3,0x5F,0x28};//a 
uint8_t MACB[] = {0xC0,0x49,0xEF,0xD0,0xE2,0xC4};//b
uint8_t MACC[] = {0x40,0x22,0xD8,0x60,0xDD,0xB8};//c  
String success; //发送数据成功则存储变量

//接收数据结构体   接受 温湿度以及哪个地方
typedef struct Rdata{
  int id;
  float humidity;     //湿度
  float temperature;  //温度
  bool relay_status;         //继电器状态  true为开   false为关
}Rdata;

Rdata myData; 
Rdata board1;  //用于存储b板的数据
Rdata board2;   //用于存储c板的数据

//创建一个包含结构体的数组方便调用读写数据


//发送  结构体,可更改为打开或关闭继电器的信息,以及等等的控制信息
typedef struct Tdata{
  int id;
  bool relay;   //控制继电器状态
  int x;        //暂留开关
}Tdata;
//创建一个发送的结构体
Tdata IDdata;

//发送数据时的回调函数
void OnDataSend(const uint8_t *mac_addr, esp_now_send_status_t status) //发送数据时的回调函数
{
  Serial.print("\r\n status:\t");
  Serial.println(status == ESP_NOW_SEND_SUCCESS ? "tran succes~!" : "tran failed!");
}

//接受到数据时的回调函数  可在这个函数里选择接收到何种数据 作何等操作
void OnDataRecv(const uint8_t *mac_addr, const uint8_t *incomingData, int len)
{
  char macStr[18];   //定义一个字符串数组  用来存储是哪个发来的数据,其mac地址
  Serial.print("recv:");
  snprintf(macStr,sizeof(macStr),"%02x:%02x:%02x:%02x:%02x:%02x:",
            mac_addr[0],mac_addr[1],mac_addr[2],mac_addr[3],mac_addr[4],mac_addr[5]);
  Serial.println(macStr);     //拼接到macStr  而后打印出来

  memcpy(&myData, incomingData, sizeof(myData));   //将接收到的数据存储到mydata中 
  Serial.printf("id: %u size: %u \n",myData.id,len);  //打印出是哪个id发力的信息
  if(myData.id == 23)                //如果是id为23的发来  后面可以改成字符串  是哪个地方发来的
  {
    board1.humidity = myData.humidity;
    board1.temperature = myData.temperature;
    board1.relay_status = myData.relay_status;
    Serial.printf("1humidity :%0.2f\n",board1.humidity);
    Serial.printf("1temperature:%0.2f\n",board1.temperature);
    if(board1.relay_status == true)
      Serial.println("1relay_status: Open");
    else
      Serial.println("1relay_status: Close");
  }
  else if (myData.id == 26)
  {
    board2.humidity = myData.humidity;
    board2.temperature = myData.temperature; 
    Serial.printf("2humidity :%0.2f\n",board2.humidity);
    Serial.printf("2temperature:%0.2f\n",board2.temperature);
    if(board2.relay_status == true)
      Serial.println("2relay_status: Open");
    else
      Serial.println("2relay_status: Close");
  }
}


void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  if(esp_now_init() != ESP_OK)
  {
    Serial.printf("init esp_now error\n");
    return;
  }
  esp_now_register_send_cb(OnDataSend);  //ESPNow初始化成功,注册发送包,获取已发送数据包的状态
                                        // 获取状态是上面那个回调函数所做的是

  esp_now_peer_info_t peerInfo;   //注册对等点
  peerInfo.channel = 0; //对端点用于发送/接受数据的WiFi通道设置为0
  peerInfo.encrypt = false; //表示对端点发送/接受的数据是否加密对于本例,设置为false(不加密)
  memcpy(peerInfo.peer_addr,MACB,6); //对端点设备地址,将其设置为广播地址
  if(esp_now_add_peer(&peerInfo) != ESP_OK) //将“对等点”添加到列表中
  {
    Serial.println("add erreo");
    return;
  }

  memcpy(peerInfo.peer_addr,MACC,6); //对端点设备地址,将其设置为广播地址
  if(esp_now_add_peer(&peerInfo) != ESP_OK) //将“对等点”添加到列表中
  {
    Serial.println("add erreo");
    return;
  }  

  esp_now_register_recv_cb(OnDataRecv);
}

void loop() {
  // put your main code here, to run repeatedly:
  int i=0;
  while(1)
  {
    i++;
    if(i%2 == 0)
      IDdata.relay = true;
    else
      IDdata.relay = false;
    //发送的数据  本板id为1  后面可更改为字符串主板---x,y可为控制
    IDdata.id = 1;
    IDdata.x = 66;

    esp_err_t result = esp_now_send(MACC,(uint8_t *)&IDdata,sizeof(IDdata));
    if(result == ESP_OK)
      Serial.println("发送成功");
    else 
      Serial.println("发送失败");

    delay(10000);
  }
  
}

另外两块esp32的代码写法也是大同小异,我最后面都包含有的压缩包

esp间的通信完成后,该考虑的就是主esp32与onenet间的数据传输以及esp32与stm32的串口传输,然后onenet平台的搭建,推荐以下这篇博客

http://t.csdn.cn/d1ERj

连接完onenet后,可以先尝试一下esp32与其的连接,看看数据是否互通,以下代码也是在csdn上参考一位大哥的,但是现在找不到,挂不了其链接

#include <Arduino.h>
#include "WiFi.h"
#include "PubSubClient.h"
#include "Ticker.h"
 
 
const char *ssid = "ylmting_PC";               //wifi名
const char *password = "18811111";       //wifi密码
const char *mqtt_server = "mqtts.heclouds.com"; //onenet 的 IP地址
const int port = 1883;                     //端口号
 
 
#define mqtt_devid "homedev" //设备ID
#define mqtt_pubid "551578"        //产品ID
//鉴权信息
#define mqtt_password "version=2018-10-31&res=products%2F551578%2Fdevices%2Fhomedev&et=4096342304&method=md5&sign=CCdJNa17tk%2B88nKW0QWHIw%3D%3D" //鉴权信息
 
 
WiFiClient espClient;           //创建一个WIFI连接客户端
PubSubClient client(espClient); // 创建一个PubSub客户端, 传入创建的WIFI客户端
 
 
char msgJson[75]; //发送信息缓冲区
//信息模板
char dataTemplate[] = "{\"id\":123,\"dp\":{\"Temperature\":[{\"v\":%.2f}],\"Humidity\":[{\"v\":%.2f}]}}";  //
Ticker tim1; //定时器,用来循环上传数据
 
 
//连接WIFI相关函数
void setupWifi()
{
  delay(10);
  Serial.println("连接WIFI");
  WiFi.begin(ssid, password);
  while (!WiFi.isConnected())
  {
    Serial.print(".");
    delay(500);
  }
  Serial.println("OK");
  Serial.println("Wifi连接成功");
}
 
 
//收到主题下发的回调, 注意这个回调要实现三个形参 1:topic 主题, 2: payload: 传递过来的信息 3: length: 长度
void callback(char *topic, byte *payload, unsigned int length)
{
  Serial.println("message rev:");
  Serial.println(topic);
  for (size_t i = 0; i < length; i++)
  {
    Serial.print((char)payload[i]);
  }
  Serial.println();
}
 
 
//向主题发送模拟的温湿度数据
void sendTempAndHumi()
{
  if (client.connected())
  {
    snprintf(msgJson, 75, dataTemplate, 26.0, 50.0); //将模拟温湿度数据套入dataTemplate模板中, 生成的字符串传给msgJson 75为字节数
    
    Serial.print("public the data:");
    Serial.println(msgJson);
    client.publish("$sys/551578/homedev/dp/post/json", (uint8_t *)msgJson, strlen(msgJson));
    //发送数据到主题
  }
}
 
 
//重连函数, 如果客户端断线,可以通过此函数重连
void clientReconnect()
{
  while (!client.connected()) //再重连客户端
  {
    Serial.println("reconnect MQTT...");
    if (client.connect(mqtt_devid, mqtt_pubid, mqtt_password))
    {
      Serial.println("connected");
      client.subscribe("$sys/551578/homedev/cmd/request/+"); //订阅命令下发主题
    }
    else
    {
      Serial.println("failed");
      Serial.println(client.state());
      Serial.println("try again in 5 sec");
      delay(5000);
    }
  }
}
 
 
void setup()
{
  Serial.begin(115200);                                  //初始化串口
  delay(3000);                                           //这个延时是为了让我打开串口助手
  setupWifi();                                           //调用函数连接WIFI
  client.setServer(mqtt_server, port);                   //设置客户端连接的服务器,连接Onenet服务器, 使用6002端口
  client.connect(mqtt_devid, mqtt_pubid, mqtt_password); //客户端连接到指定的产品的指定设备.同时输入鉴权信息
  if (client.connected())
  {
    Serial.println("OneNet is connected!");//判断以下是不是连好了.
  }
  client.setCallback(callback);                                //设置好客户端收到信息是的回调
  client.subscribe("$sys/551578/homedev/cmd/request/+"); //订阅命令下发主题
  tim1.attach(10, sendTempAndHumi);                            //定时每10秒调用一次发送数据函数sendTempAndHumi
}
 
 
void loop()
{
  if (!WiFi.isConnected()) //先看WIFI是否还在连接
  {
    setupWifi();
  }
  if (!client.connected()) //如果客户端没连接ONENET, 重新连接
  {
    clientReconnect();
    delay(100);
  }
  client.loop(); //客户端循环检测  用于检测服务器是否有下发信息
}

 

 注意上面那些设备id那些要用自己的哦 

连接后,以上esp32-now连接的那仨可以取其中一个,来当作为主esp32用来连接onenet与stm32,将这个连接onenet的代码移植到now连接的代码即可,不过在这里我遇到了第1-2345第5个问题,就是将主esp32 连接wifi后 与另外两个esp32通信的时候,数据就会不稳定,传输时而成功时而失败,最后也只解决了一个次esp32发送数据到主esp32变得稳定.就是将esp32都连接上一个wifi,或者也可以将主esp32设置为ap+sta模式,然后次esp32直接连接主esp32的热点,也就是将那些处于一个wifi组网下。具体的代码资料等我会放在下面。

esp32间的联系完成以及连上onenet平台后实现数据传输后,就可以实现esp32与stm32的串口数据传输了在arduino上可以很方便的实现,

//初始串口
Serial2.begin(115200);                                 //于stm32进行串口连接--使用串口2

//然后在回调函数处,将数据传输过去即可,不过应当注意你数据传输的协议的
//usart2传输协议Temperature:00.00!
//            Humidity:00.00!(sprintf(usart2send,"Temperature:%0.2f!",board1.Temperature))

而在stm32端,我在这里就讲解普通的版本,在我的代码压缩包里面有一个freeRTOS版,以下是串口中断的处理函数,可以取得上面的Temperature:00.00  字符串

然后在下面调用哪个sscanf就可以得到温湿度的数据啦

void USART3_IRQHandler(void)
{
	
	//判断接收中断标志是否为1
	if(USART_GetITStatus(USART3, USART_IT_RXNE) == SET)
	{
		//清空标志位
		USART_ClearITPendingBit(USART3, USART_IT_RXNE);	
		//接受数据 先赋值再加
		buffer[count++] = USART_ReceiveData(USART3);
			
		//判断接受的字符是否为 '!'
		if(buffer[count-1] == '!')
		{
			//buffer循环赋值给rx_buffer, 但不需要赋值':'  过滤结束标志
			for(rx_i=0; rx_i<(count-1); rx_i++)
			{
				rx_buffer[rx_i] = buffer[rx_i];
			}
					
			memset(buffer, 0, sizeof(buffer));

			//确保下一帧存放位置从buffer[0]开始
			count = 0;
			
			rx_flag = 1;
		}	
	}
}

if(rx_flag == 1)
		{
			
			printf("rx_buffer:%s\r\n", rx_buffer);
			
			if(strstr(rx_buffer, "Temperature1"))
			{
				        sscanf(rx_buffer,"Temperature1:%f,Humidity1:%f",&Temperature1,&Humidity1);
				//拼接传输至串口屏的数据
				sprintf((char *)send_screen_buff,"DHT11.t0.txt=\"%.2f\"\xFF\xFF\xFF",Temperature1);
				//往串口屏传输数据 
				USART_SendString(USART2, send_screen_buff);
				//延时2ms,确保数据已到位
				delay_ms(2);
				//清空字符串
				memset(send_screen_buff, 0, sizeof(send_screen_buff));
				//拼接湿度数据
				sprintf((char *)send_screen_buff,"DHT11.t1.txt=\"%.2f\"\xFF\xFF\xFF",Humidity1);
				USART_SendString(USART2, send_screen_buff);
				memset(send_screen_buff, 0, sizeof(send_screen_buff));
			}
			if(strstr(rx_buffer, "Temperature2"))
			{
				sscanf(rx_buffer,"Temperature2:%f,Humidity2:%f",&Temperature2,&Humidity2);
				//拼接传输至串口屏的数据
				sprintf((char *)send_screen_buff,"DHT11.t2.txt=\"%.2f\"\xFF\xFF\xFF",Temperature2);
				//往串口屏传输数据 
				USART_SendString(USART2, send_screen_buff);
				//延时2ms,确保数据已到位
				delay_ms(2);
				//清空字符串
				memset(send_screen_buff, 0, sizeof(send_screen_buff));
				//拼接湿度数据
				sprintf((char *)send_screen_buff,"DHT11.t3.txt=\"%.2f\"\xFF\xFF\xFF",Humidity2);
				USART_SendString(USART2, send_screen_buff);
				memset(send_screen_buff, 0, sizeof(send_screen_buff));
			}			
			
			printf("Temperature1:%.2f,Humidity1:%.2f",Temperature1,Humidity1);
			
			//清空数组
			memset(rx_buffer, 0, sizeof(rx_buffer));
			
			rx_flag = 0;
		}

上面就是串口的处理函数,串口2是我连接的一个串口屏。

然后还有一个通过感受光照强度来控制led开关的,因为光敏电阻遇到光得话,阻值会降低,所有我们可以通过ADC来获取其变化,得到那个值,然后根据哪个值来控制灯得开关,这个比较简单。

然后还有一个串口屏传输数据过来时吗,用的中断大同小异,可以根据上面那个串口中断函数自行修改。

if(rx_flag_screen == 1)
		{
			
			printf("rx_buffer_screen:%s\r\n", rx_buffer_screen);
			USART_SendString(USART3, rx_buffer_screen);
			
			//清空数组
			memset(rx_buffer_screen, 0, sizeof(rx_buffer_screen));
			
			rx_flag_screen = 0;
		}

上面用到得一个串口传输字符串得函数是

void USART_SendString(USART_TypeDef * pUSARTx, char *str)
{
	unsigned int   k=0;
	while(*(str+k)!='\0')
	{
		// 发送一个字节数据到USART 
		USART_SendData(pUSARTx, *(str+k));
		// 等待发送数据寄存器为空
		while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
		k++;
	}
	while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET);   /* TC:传输完成标志 */
}

这个比较通用,可以用在所有串口传输的地方上,建议背诵全文哈哈。

以上还有很多没有讲到的地方。但是暂时没想起来  有什么不了解得欢迎过来一起交流

 这个是云平台得最终效果

知识无价,欢迎分享。

链接: https://pan.baidu.com/s/1-veegbGcjwog4RjVP3MMGQ?pwd=2326 提取码: 2326 复制这段内容后打开百度网盘手机App,操作更方便哦

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值