【2024 DigiKey 创意大赛】ESP-32C6- 室内数据采集 + MQTT上报
本帖最后由 御坂10032号 于 2024-10-15 00:48 编辑<p><strong><span style="font-size:22px;">简介</span></strong></p>
<p> </p>
<p>在这次的活动中除了服务端的树莓派一共还买了一块ESP32-C6. ESP32C6主要在这个任务中扮演的角色就是数据的采集和上报,同时根据订阅的主题的MQTT消息实现继电器的吸合从而来控制其他的电器。</p>
<p><br />
</p>
<p> </p>
<p><span style="font-size:22px;"><strong>正文</strong></span></p>
<p> </p>
<p>本章节主要是使用ESP32-C6 来使用I2C驱动好传感器AHT10, 然后将数据上传到部署了HA和MQTT的树莓派5, 同时通过绑定对应的MQTT主题接收回调来根据订阅主题发送的消息从而切换继电器的状态。 这里可以被提供切换的在卧室场景中有: 接入BH1750当环境光照降低到一定的阈值的时候,出发继电器打开台灯,来给环境补光。</p>
<p> </p>
<p>当前所使用的物料如下:</p>
<ol>
<li>ESP32-C6</li>
<li>树莓派5</li>
<li>AHT10</li>
<li>继电器</li>
</ol>
<p> </p>
<p><strong>接线图如下:</strong></p>
<p> </p>
<p> </p>
<p>那么为了能够达到上文中提及的效果, 这里需要一共有几个问题需要我们解决</p>
<p> </p>
<p>1- WIFI连接</p>
<p>2- MQTT连接</p>
<p>3- 传感器数据的读取</p>
<p>4- 数据的上传和回调的处理</p>
<p> </p>
<p> </p>
<p><strong>1) - 首先我们来解决第一个问题 <span style="font-size:22px;">WIFI连接 </span></strong></p>
<p> </p>
<p>在正常在Arduino IDE中安装好ESP32的支持后, 可以很轻松的在示例工程中找到ESP32-C6 wifi connect的代码。</p>
<p> </p>
<p> </p>
<p> </p>
<p> </p>
<p>上述代码的主要功能是Wifi的扫描和连接等,我们可以在上述示例代码中修改SSID 和 password 成你本地的WIFI即可正确连接。它的核心代码主要是在 setup阶段的while循环中</p>
<p> </p>
<pre>
<code class="language-cpp">// Wait for the WiFi event
while (true) {
switch (WiFi.status()) {
case WL_NO_SSID_AVAIL: Serial.println(" SSID not found"); break;
case WL_CONNECT_FAILED:
Serial.print(" Failed - WiFi not connected! Reason: ");
return;
break;
case WL_CONNECTION_LOST: Serial.println(" Connection was lost"); break;
case WL_SCAN_COMPLETED:Serial.println(" Scan is completed"); break;
case WL_DISCONNECTED: Serial.println(" WiFi is disconnected"); break;
case WL_CONNECTED:
Serial.println(" WiFi is connected!");
Serial.print(" IP address: ");
Serial.println(WiFi.localIP());
return;
break;
default:
Serial.print(" WiFi Status: ");
Serial.println(WiFi.status());
break;
}
delay(tryDelay);
if (numberOfTries <= 0) {
Serial.print(" Failed to connect to WiFi!");
// Use disconnect function to force stop trying to connect
WiFi.disconnect();
return;
} else {
numberOfTries--;
}
}</code></pre>
<p> </p>
<p>注意,这里无法连接5G信号的WIFI, 如果一切无误,程序正常连接的话,那么这个while 循环的最后一个 <strong>‘}’ </strong>的代码是不会被执行的, 因此如果需要做其他的初始化工作的话。 在不修改它代码结构的前提下最好将代码放到WIFI got ip 之后。 否则将会造成无法初始化的异常。</p>
<p> </p>
<p>关于程序中的按键状态的读取来断开WIFI的功能,我们并不需要可以直接删除。 那么当程序成功连接到WIFI之后则会输出下图的消息</p>
<p> </p>
<p> </p>
<p>此时WIFI连接的任务我们已经完成了。</p>
<p> </p>
<p> </p>
<p><strong>2) - 第二个问题我们需要解决的则为 <span style="font-size:22px;">连接MQTT服务器</span></strong></p>
<p> </p>
<p>连接MQTT服务的基本步骤为,在连接好WIFI之后, 通过WIFI client 对象来初始化一个MQTT client 对象, 然后通过这个MQTT client 对象去连接 MQTT服务器。那么如果你在安装MQTT服务的时候设置了运行当前的MQTT服务器被匿名登录的话, 那么则可以不需要输入密码直接访问MQTT,但是如果你并没有设置的话, 那么还需要额外使用MQTTClient对象调用它的 setUsernamePassword(username, password) 来设置MQTT的登陆信息。</p>
<p> </p>
<p> </p>
<p>如果想要连接MQTT的话,则需要使用Arduino提供的 <a aria-current="page" data-analytics-event="{"category":"SiteHeaderComponent","action":"context_region_crumb","label":"ArduinoMqttClient","screen_size":"full"}" data-view-component="true" href="https://github.com/arduino-libraries/ArduinoMqttClient">ArduinoMqttClient</a> 库, 它可以在库管理器中直接被找到, 如下图所示。</p>
<p> </p>
<p> </p>
<p> </p>
<p>在成功安装完这个库之后, 可以在demo中找到MQTT Client的使用示例。</p>
<p> </p>
<p> </p>
<p>上述Demo最主要的功能主要是 : </p>
<p> 1 - 引入头文件支持</p>
<p> 2- 定义MQTT Client ,通过wificlient 创建</p>
<p> 3- 定义MQTT连接信息</p>
<p> 4- 连接MQTT</p>
<p> 5- 绑定接受回调(订阅)</p>
<p> </p>
<p> </p>
<p>以及下图的发送和绑定消息回调。</p>
<p> </p>
<p> </p>
<p>我上图圈出来的功能为核心代码, 分别是连接MQTT, 设置接受回调函数, 以及设置接受回调函数下方的订阅主题(没有标注), 主循环中的<strong>mqttClient.poll(); </strong>用来保证MQTT可以正确的接收到订阅的消息。 以及最后的回调函数等。</p>
<p> </p>
<p>我们将代码稍微整合一下,那么就变成了这样。</p>
<pre>
<code class="language-cpp">#include <WiFi.h>
#include <ArduinoMqttClient.h>
#include <Arduino_JSON.h>
const char *ssid = "XXXX";
const char *password = "XXXX";
#define CONTROL_PIN 4// 定义控制引脚,用于输出高低电平
WiFiClient wifiClient;
MqttClient mqttClient(wifiClient);
const char broker[] = "192.168.1.142";
int port = 1883;
const char switchTopic[] = "switch"; // 主题1
const char sensorTopic[] = "sensor"; // 主题2
void setup() {
Serial.begin(115200);// 初始化串口,用于调试
WiFi.begin(ssid, password);
pinMode(CONTROL_PIN, OUTPUT);// 设置控制引脚为输出模式
digitalWrite(CONTROL_PIN, LOW);// 初始状态设为低电平
int tryDelay = 500;
int numberOfTries = 20;
// Wait for the WiFi event
while (true) {
switch (WiFi.status()) {
case WL_NO_SSID_AVAIL: Serial.println(" SSID not found"); break;
case WL_CONNECT_FAILED:
Serial.println(" Failed - WiFi not connected! Reason: ");
return;
break;
case WL_CONNECTION_LOST: Serial.println(" Connection was lost"); break;
case WL_DISCONNECTED: Serial.println(" WiFi is disconnected"); break;
case WL_CONNECTED:
Serial.println(" WiFi is connected!");
Serial.print(" IP address: ");
Serial.println(WiFi.localIP());
Serial.print("Attempting to connect to the MQTT broker: ");
Serial.println(broker);
mqttClient.setUsernamePassword("root", "mazha1997");
if (!mqttClient.connect(broker, port)) {
Serial.print("MQTT connection failed! Error code = ");
Serial.println(mqttClient.connectError());
while (1);
}
Serial.println("You're connected to the MQTT broker!");
mqttClient.onMessage(onMqttMessage);// 设置消息接收回调
Serial.print("Subscribing to topic: ");
Serial.println(switchTopic);
mqttClient.subscribe(switchTopic);// 订阅开关主题
return;
default:
Serial.print(" WiFi Status: ");
Serial.println(WiFi.status());
break;
}
delay(tryDelay);
if (numberOfTries <= 0) {
Serial.println(" Failed to connect to WiFi!");
WiFi.disconnect();
return;
} else {
numberOfTries--;
}
}
}
void loop() {
mqttClient.poll();
delay(1000);// 每秒刷新一次
}
// MQTT消息接收回调函数
void onMqttMessage(int messageSize) {
// 获取当前消息的主题
String topic = mqttClient.messageTopic();
Serial.print("Received a message with topic '");
Serial.print(topic);
Serial.print("', length ");
Serial.print(messageSize);
Serial.println(" bytes:");
String message = "";
while (mqttClient.available()) {
message += (char)mqttClient.read();
}
Serial.println("Message content: " + message);
// 根据不同的主题处理消息
if (topic == switchTopic) {
Serial.println("Message from topic: switchTopic");
// 将接收到的消息转换为 JSON
JSONVar parsedMessage = JSON.parse(message);
// 检查解析是否成功
if (JSON.typeof(parsedMessage) == "undefined") {
Serial.println("Parsing input failed!");
return;
}
// 获取 status 字段
String status = (const char*)parsedMessage["status"];
if (status == "ON") {
digitalWrite(CONTROL_PIN, HIGH);// 设置 IO4 输出高电平
Serial.println("Switch turned ON, IO4 set to HIGH");
} else if (status == "OFF") {
digitalWrite(CONTROL_PIN, LOW);// 设置 IO4 输出低电平
Serial.println("Switch turned OFF, IO4 set to LOW");
} else {
Serial.println("Unknown status value");
}
}
}
</code></pre>
<p> </p>
<p>那么在上图的代码中我们即可实现WIFI的连接, 消息的订阅和MQTT的回调。</p>
<p> </p>
<p>现在我们只需要来读取AHT10的消息,并且将其发送至MQTT即可。我们在上述的代码上做一下修改,引入</p>
<p> </p>
<ol>
<li>#include <Wire.h></li>
<li>#include <Adafruit_AHTX0.h></li>
<li>#include <Arduino_JSON.h></li>
</ol>
<p>Wire主要是用来保证IIC通讯, AHTX0的这个库需要自己在库管理器中搜索安装, 而Arduino_JSON 主要适用于JSON的解析和构建。</p>
<p> </p>
<pre>
<code class="language-cpp">#include <Wire.h>
#include <Adafruit_AHTX0.h>
#include <WiFi.h>
#include <ArduinoMqttClient.h>
#include <Arduino_JSON.h>
const char *ssid = "XXX";
const char *password = "XXX";
#define SDA_PIN 21// 定义 SDA 引脚
#define SCL_PIN 22// 定义 SCL 引脚
#define CONTROL_PIN 4// 定义控制引脚,用于输出高低电平
Adafruit_AHTX0 aht;
WiFiClient wifiClient;
MqttClient mqttClient(wifiClient);
const char broker[] = "192.168.1.142";
int port = 1883;
const char switchTopic[] = "switch"; // 主题1
const char sensorTopic[] = "sensor"; // 主题2
void setup() {
Serial.begin(115200);// 初始化串口,用于调试
pinMode(CONTROL_PIN, OUTPUT);// 设置控制引脚为输出模式
digitalWrite(CONTROL_PIN, LOW);// 初始状态设为低电平
// 初始化 I2C,使用自定义引脚
Wire.begin(SDA_PIN, SCL_PIN);
if (!aht.begin()) {
Serial.println("Could not find AHT? Check wiring");
while (1) delay(10);
}
Serial.println("AHT10 or AHT20 found");
WiFi.begin(ssid, password);
int tryDelay = 500;
int numberOfTries = 20;
// Wait for the WiFi event
while (true) {
switch (WiFi.status()) {
case WL_NO_SSID_AVAIL: Serial.println(" SSID not found"); break;
case WL_CONNECT_FAILED:
Serial.println(" Failed - WiFi not connected! Reason: ");
return;
break;
case WL_CONNECTION_LOST: Serial.println(" Connection was lost"); break;
case WL_DISCONNECTED: Serial.println(" WiFi is disconnected"); break;
case WL_CONNECTED:
Serial.println(" WiFi is connected!");
Serial.print(" IP address: ");
Serial.println(WiFi.localIP());
Serial.print("Attempting to connect to the MQTT broker: ");
Serial.println(broker);
mqttClient.setUsernamePassword("root", "mazha1997");
if (!mqttClient.connect(broker, port)) {
Serial.print("MQTT connection failed! Error code = ");
Serial.println(mqttClient.connectError());
while (1);
}
Serial.println("You're connected to the MQTT broker!");
mqttClient.onMessage(onMqttMessage);// 设置消息接收回调
Serial.print("Subscribing to topic: ");
Serial.println(switchTopic);
mqttClient.subscribe(switchTopic);// 订阅开关主题
return;
default:
Serial.print(" WiFi Status: ");
Serial.println(WiFi.status());
break;
}
delay(tryDelay);
if (numberOfTries <= 0) {
Serial.println(" Failed to connect to WiFi!");
WiFi.disconnect();
return;
} else {
numberOfTries--;
}
}
}
void loop() {
// 保证处理 MQTT 消息
mqttClient.poll();
// 获取传感器数据
sensors_event_t humidity, temp;
aht.getEvent(&humidity, &temp);// 获取温度和湿度数据
// 创建JSON对象来存储数据
JSONVar sensorData;
sensorData["temperature"] = String(temp.temperature, 2);// 保留两位小数
sensorData["humidity"] = String(humidity.relative_humidity, 2);// 保留两位小数
// 将JSON数据转换为字符串并发布到"sensor"主题
String payload = JSON.stringify(sensorData);
mqttClient.beginMessage(sensorTopic);
mqttClient.print(payload);
mqttClient.endMessage();
delay(1000);// 每秒刷新一次
}
// MQTT消息接收回调函数
void onMqttMessage(int messageSize) {
// 获取当前消息的主题
String topic = mqttClient.messageTopic();
Serial.print("Received a message with topic '");
Serial.print(topic);
Serial.print("', length ");
Serial.print(messageSize);
Serial.println(" bytes:");
String message = "";
while (mqttClient.available()) {
message += (char)mqttClient.read();
}
Serial.println("Message content: " + message);
// 根据不同的主题处理消息
if (topic == switchTopic) {
Serial.println("Message from topic: switchTopic");
// 将接收到的消息转换为 JSON
JSONVar parsedMessage = JSON.parse(message);
// 检查解析是否成功
if (JSON.typeof(parsedMessage) == "undefined") {
Serial.println("Parsing input failed!");
return;
}
// 获取 status 字段
String status = (const char*)parsedMessage["status"];
if (status == "ON") {
digitalWrite(CONTROL_PIN, HIGH);// 设置 IO4 输出高电平
Serial.println("Switch turned ON, IO4 set to HIGH");
} else if (status == "OFF") {
digitalWrite(CONTROL_PIN, LOW);// 设置 IO4 输出低电平
Serial.println("Switch turned OFF, IO4 set to LOW");
} else {
Serial.println("Unknown status value");
}
}
}
</code></pre>
<p> </p>
<p>上述的代码则为修改后的代码。 实现的功能则为, 将环境数据上传到Sensor的主题内, 同时又订阅了 Switch的主题, 那么外部接入的传感器为光照传感器的时候, 便可以“感知” 环境的变化,然后开启室内的补光。那么在下个章节我将将上面的程序集成进HA , 然后根据光照强度来配置一个卧室室内的感知自动化。</p>
<p> </p>
<p><strong>消息发布的截图</strong></p>
<p> </p>
<p> </p>
<p> </p>
<p><strong>MQTT Sesonr 主题的数据</strong></p>
<p> </p>
<p> </p>
<p>MQTT 继电器主题的控制:</p>
<p> </p>
<p>393b4622d47cc87059771489aebe629a<br />
</p>
<p>树莓派部署HA,后还能进行其他功能吗? </p>
秦天qintian0303 发表于 2024-10-15 13:07
树莓派部署HA,后还能进行其他功能吗?
<p>后续要通过它实现自动化控制.</p>
御坂10032号 发表于 2024-10-15 23:45
后续要通过它实现自动化控制.
<p>我说的是树莓派部署完HA后,树莓派是不是就不能做其他的用了,能不能同时跑多个</p>
秦天qintian0303 发表于 2024-10-16 09:29
我说的是树莓派部署完HA后,树莓派是不是就不能做其他的用了,能不能同时跑多个
<p>当然可以跑多个了, 树莓派并不是跑到HA的系统,而是在容器中跑的, docker 里可以运行多个容器, 所以是隔离不冲突的</p>
页:
[1]