御坂10032号 发表于 2024-10-15 00:45

【2024 DigiKey 创意大赛】ESP-32C6- 室内数据采集 + MQTT上报

本帖最后由 御坂10032号 于 2024-10-15 00:48 编辑

<p><strong><span style="font-size:22px;">简介</span></strong></p>

<p>&nbsp;</p>

<p>在这次的活动中除了服务端的树莓派一共还买了一块ESP32-C6.&nbsp; ESP32C6主要在这个任务中扮演的角色就是数据的采集和上报,同时根据订阅的主题的MQTT消息实现继电器的吸合从而来控制其他的电器。</p>

<p><br />
&nbsp;</p>

<p>&nbsp;</p>

<p><span style="font-size:22px;"><strong>正文</strong></span></p>

<p>&nbsp;</p>

<p>本章节主要是使用ESP32-C6 来使用I2C驱动好传感器AHT10, 然后将数据上传到部署了HA和MQTT的树莓派5, 同时通过绑定对应的MQTT主题接收回调来根据订阅主题发送的消息从而切换继电器的状态。 这里可以被提供切换的在卧室场景中有: 接入BH1750当环境光照降低到一定的阈值的时候,出发继电器打开台灯,来给环境补光。</p>

<p>&nbsp;</p>

<p>当前所使用的物料如下:</p>

<ol>
        <li>ESP32-C6</li>
        <li>树莓派5</li>
        <li>AHT10</li>
        <li>继电器</li>
</ol>

<p>&nbsp;</p>

<p><strong>接线图如下:</strong></p>

<p>&nbsp;</p>

<p> &nbsp;</p>

<p>那么为了能够达到上文中提及的效果, 这里需要一共有几个问题需要我们解决</p>

<p>&nbsp;</p>

<p>1- WIFI连接</p>

<p>2- MQTT连接</p>

<p>3- 传感器数据的读取</p>

<p>4- 数据的上传和回调的处理</p>

<p>&nbsp;</p>

<p>&nbsp;</p>

<p><strong>1)&nbsp; -&nbsp; 首先我们来解决第一个问题&nbsp;<span style="font-size:22px;">WIFI连接&nbsp;</span></strong></p>

<p>&nbsp;</p>

<p>在正常在Arduino IDE中安装好ESP32的支持后, 可以很轻松的在示例工程中找到ESP32-C6 wifi connect的代码。</p>

<p>&nbsp;</p>

<p> &nbsp;</p>

<p>&nbsp;</p>

<p>&nbsp;</p>

<p>上述代码的主要功能是Wifi的扫描和连接等,我们可以在上述示例代码中修改SSID 和&nbsp;password 成你本地的WIFI即可正确连接。它的核心代码主要是在 setup阶段的while循环中</p>

<p>&nbsp;</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 &lt;= 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>&nbsp;</p>

<p>注意,这里无法连接5G信号的WIFI, 如果一切无误,程序正常连接的话,那么这个while 循环的最后一个 <strong>&lsquo;}&rsquo; </strong>的代码是不会被执行的, 因此如果需要做其他的初始化工作的话。 在不修改它代码结构的前提下最好将代码放到WIFI got ip 之后。 否则将会造成无法初始化的异常。</p>

<p>&nbsp;</p>

<p>关于程序中的按键状态的读取来断开WIFI的功能,我们并不需要可以直接删除。 那么当程序成功连接到WIFI之后则会输出下图的消息</p>

<p>&nbsp;</p>

<p> &nbsp;&nbsp;</p>

<p>此时WIFI连接的任务我们已经完成了。</p>

<p>&nbsp;</p>

<p>&nbsp;</p>

<p><strong>2)&nbsp; -&nbsp; 第二个问题我们需要解决的则为 <span style="font-size:22px;">连接MQTT服务器</span></strong></p>

<p>&nbsp;</p>

<p>连接MQTT服务的基本步骤为,在连接好WIFI之后, 通过WIFI client 对象来初始化一个MQTT client 对象, 然后通过这个MQTT client 对象去连接 MQTT服务器。那么如果你在安装MQTT服务的时候设置了运行当前的MQTT服务器被匿名登录的话, 那么则可以不需要输入密码直接访问MQTT,但是如果你并没有设置的话, 那么还需要额外使用MQTTClient对象调用它的 setUsernamePassword(username, password) 来设置MQTT的登陆信息。</p>

<p>&nbsp;</p>

<p>&nbsp;</p>

<p>如果想要连接MQTT的话,则需要使用Arduino提供的&nbsp;<a aria-current="page" data-analytics-event="{&quot;category&quot;:&quot;SiteHeaderComponent&quot;,&quot;action&quot;:&quot;context_region_crumb&quot;,&quot;label&quot;:&quot;ArduinoMqttClient&quot;,&quot;screen_size&quot;:&quot;full&quot;}" data-view-component="true" href="https://github.com/arduino-libraries/ArduinoMqttClient">ArduinoMqttClient</a>&nbsp;库, 它可以在库管理器中直接被找到, 如下图所示。</p>

<p>&nbsp;</p>

<p> &nbsp;</p>

<p>&nbsp;</p>

<p>在成功安装完这个库之后, 可以在demo中找到MQTT Client的使用示例。</p>

<p>&nbsp;</p>

<p> &nbsp;</p>

<p>上述Demo最主要的功能主要是 :&nbsp;</p>

<p>&nbsp; &nbsp; 1 - 引入头文件支持</p>

<p>&nbsp; &nbsp; 2- 定义MQTT Client ,通过wificlient 创建</p>

<p>&nbsp; &nbsp; 3- 定义MQTT连接信息</p>

<p>&nbsp; &nbsp; 4- 连接MQTT</p>

<p>&nbsp; &nbsp; 5- 绑定接受回调(订阅)</p>

<p>&nbsp;</p>

<p> &nbsp;</p>

<p>以及下图的发送和绑定消息回调。</p>

<p>&nbsp;</p>

<p> &nbsp;</p>

<p>我上图圈出来的功能为核心代码, 分别是连接MQTT,&nbsp; 设置接受回调函数, 以及设置接受回调函数下方的订阅主题(没有标注), 主循环中的<strong>mqttClient.poll();&nbsp;</strong>用来保证MQTT可以正确的接收到订阅的消息。 以及最后的回调函数等。</p>

<p>&nbsp;</p>

<p>我们将代码稍微整合一下,那么就变成了这样。</p>

<pre>
<code class="language-cpp">#include &lt;WiFi.h&gt;
#include &lt;ArduinoMqttClient.h&gt;
#include &lt;Arduino_JSON.h&gt;

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 &lt;= 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>&nbsp;</p>

<p>那么在上图的代码中我们即可实现WIFI的连接, 消息的订阅和MQTT的回调。</p>

<p>&nbsp;</p>

<p>现在我们只需要来读取AHT10的消息,并且将其发送至MQTT即可。我们在上述的代码上做一下修改,引入</p>

<p>&nbsp;</p>

<ol>
        <li>#include &lt;Wire.h&gt;</li>
        <li>#include &lt;Adafruit_AHTX0.h&gt;</li>
        <li>#include &lt;Arduino_JSON.h&gt;</li>
</ol>

<p>Wire主要是用来保证IIC通讯, AHTX0的这个库需要自己在库管理器中搜索安装, 而Arduino_JSON 主要适用于JSON的解析和构建。</p>

<p>&nbsp;</p>

<pre>
<code class="language-cpp">#include &lt;Wire.h&gt;
#include &lt;Adafruit_AHTX0.h&gt;
#include &lt;WiFi.h&gt;
#include &lt;ArduinoMqttClient.h&gt;
#include &lt;Arduino_JSON.h&gt;

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 &lt;= 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(&amp;humidity, &amp;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>&nbsp;</p>

<p>上述的代码则为修改后的代码。 实现的功能则为, 将环境数据上传到Sensor的主题内, 同时又订阅了 Switch的主题, 那么外部接入的传感器为光照传感器的时候, 便可以&ldquo;感知&rdquo; 环境的变化,然后开启室内的补光。那么在下个章节我将将上面的程序集成进HA , 然后根据光照强度来配置一个卧室室内的感知自动化。</p>

<p>&nbsp;</p>

<p><strong>消息发布的截图</strong></p>

<p>&nbsp;</p>

<p> &nbsp;</p>

<p>&nbsp;</p>

<p><strong>MQTT Sesonr 主题的数据</strong></p>

<p>&nbsp;</p>

<p> &nbsp;</p>

<p>MQTT 继电器主题的控制:</p>

<p>&nbsp;</p>

<p>393b4622d47cc87059771489aebe629a<br />
&nbsp;</p>

秦天qintian0303 发表于 2024-10-15 13:07

<p>树莓派部署HA,后还能进行其他功能吗?&nbsp; &nbsp;&nbsp;</p>

御坂10032号 发表于 2024-10-15 23:45

秦天qintian0303 发表于 2024-10-15 13:07
树莓派部署HA,后还能进行其他功能吗?&nbsp; &nbsp;&nbsp;

<p>后续要通过它实现自动化控制.</p>

秦天qintian0303 发表于 2024-10-16 09:29

御坂10032号 发表于 2024-10-15 23:45
后续要通过它实现自动化控制.

<p>我说的是树莓派部署完HA后,树莓派是不是就不能做其他的用了,能不能同时跑多个</p>

御坂10032号 发表于 2024-10-16 12:55

秦天qintian0303 发表于 2024-10-16 09:29
我说的是树莓派部署完HA后,树莓派是不是就不能做其他的用了,能不能同时跑多个

<p>当然可以跑多个了, 树莓派并不是跑到HA的系统,而是在容器中跑的, docker 里可以运行多个容器, 所以是隔离不冲突的</p>
页: [1]
查看完整版本: 【2024 DigiKey 创意大赛】ESP-32C6- 室内数据采集 + MQTT上报