🤖 作者:包瑞清(richie bao): lastmod: 2025-02-10T22:36:16+08:00

该部分并不做详细的阐述,但通过对 Arduino 开源硬件嵌入式开发平台、用于物联网(Internet of Things,IoT)的 ESP8266 NodeMCU (基于 ESP8266 Wi-Fi 模块 的开发板)的介绍,和案例演示展示编程语言 C/C++ 在嵌入式系统和物联网上的使用;并通过示例帮助理解这些工具和平台如何支持嵌入式系统开发及与 IoT系统进行连接。

12.1 Arduino

Arduino 是一个开源的硬件和软件平台,易于使用,简便了嵌入式系统的设计,并具有丰富的开发工具和支持社区,广泛应用于教育、艺术、科学研究,及 DIY 项目和嵌入式系统、IoT 等项目开发中。Arduino 的组成包括 Arduino 开发板(开源硬件)、集成开发环境 Arduino IDE 和 Arduino 库。Arduino 开发板是一个包含微控制器的硬件平台,能够与外部电子组件(如传感器、LED、马达等)进行交互。常见的开发板有 Arduino Uno、Arduino Mega、Arduino Nano、Arduino MKR 等。其中 Arduino Uno 是基于 ATmega328P 入门级的微控制器,有 14 个数字输入/输出引脚(其中 6 个可用于 PWM 输出),6 个模拟输入,一个 16 MHz 陶瓷谐振器(CSTCE16M0V53-R0),一个 USB 连接,一个电源插孔,一个 ICSP 头和一个复位按钮,如图12-1。Arduino Uno 包含了支持微控制器所需的一切,只需用 USB 连接到电脑,或用直流适配器、电池为其供电就可以使用。”Uno“ 在意大利语中是“一”的意思,开发者用来标志 Arduino 软件(IDE) 1.0 的发布。Uno 板 和 1.0 版本的 Arduino IDE 是 Arduino 的参考版本,是一系列 USB Arduino 板中的第一款,也是 Arduino 平台的参考模型。

PYC icon

图 12-1 Arduino Uno 开发板

Arduino IDE 是一个用于编写、编译和上传代码到 Arduino 开发板的软件工具,使用的是类似 C/C++ 的编程语言;并提供了许多库和函数,简化与各种传感器、显示器、马达、网络通信模块等外部硬件的交互,且可以直接在代码中使用,减少了编程的复杂性。

12.1.1 Hello World!

在 Arduino 中,经典的“Hello World”程序通常是点亮一个 LED 来实现。这是最简单的程序之一,可以帮助学习者理解 Arduino 编程的基本概念和控制硬件的方式。试验中仅使用了一块 Arduino Uno 开发板,闪烁板载的 13引脚 LED 指示灯,设置在 1 秒中内闪烁一次。从 Arduino 官网下载 Arduino IDE,界面和程序如图12-2。

PYC icon

图 12-2 Arduino IDE 和 Hello World 程序

将 Arduino Uno 开发板通过 USB 线连接到电脑;打开 Arduino IDE, 选择Tools->Board: ->Arduino AVR Boards->Arduino Uno开发板型号;选择Tools->Port: ->COMx(Arduino Uno)端口;打开File->Examples->Built-in example->01.Basics->Blink,可以直接获得该程序。在顶部工具栏选择 Upload(右侧箭头图标按钮),直接编译后上载至 Arduino Uno 开发板后,可以观察到板载的 LED 灯开始闪烁。编译和上载的信息会在底部的Output窗口显示,详细信息如下。从中可以查看到开发板硬件的相关信息,如所用端口、微控制器型号、内存等。

  Sketch uses 924 bytes (2%) of program storage space. Maximum is 32256 bytes.
Global variables use 9 bytes (0%) of dynamic memory, leaving 2039 bytes for local variables. Maximum is 2048 bytes.
"C:\Users\richi\AppData\Local\Arduino15\packages\arduino\tools\avrdude\6.3.0-arduino17/bin/avrdude" "-CC:\Users\richi\AppData\Local\Arduino15\packages\arduino\tools\avrdude\6.3.0-arduino17/etc/avrdude.conf" -v -V -patmega328p -carduino "-PCOM5" -b115200 -D "-Uflash:w:C:\Users\richi\AppData\Local\arduino\sketches\77D3D2018251B895496ACB24FED5C8CC/Blink.ino.hex:i"

avrdude: Version 6.3-20190619
         Copyright (c) 2000-2005 Brian Dean, http://www.bdmicro.com/
         Copyright (c) 2007-2014 Joerg Wunsch

         System wide configuration file is "C:\Users\richi\AppData\Local\Arduino15\packages\arduino\tools\avrdude\6.3.0-arduino17/etc/avrdude.conf"

         Using Port                    : COM5
         Using Programmer              : arduino
         Overriding Baud Rate          : 115200
         AVR Part                      : ATmega328P
         Chip Erase delay              : 9000 us
         PAGEL                         : PD7
         BS2                           : PC2
         RESET disposition             : dedicated
         RETRY pulse                   : SCK
         serial program mode           : yes
         parallel program mode         : yes
         Timeout                       : 200
         StabDelay                     : 100
         CmdexeDelay                   : 25
         SyncLoops                     : 32
         ByteDelay                     : 0
         PollIndex                     : 3
         PollValue                     : 0x53
         Memory Detail                 :

                                  Block Poll               Page                       Polled
           Memory Type Mode Delay Size  Indx Paged  Size   Size #Pages MinW  MaxW   ReadBack
           ----------- ---- ----- ----- ---- ------ ------ ---- ------ ----- ----- ---------
           eeprom        65    20     4    0 no       1024    4      0  3600  3600 0xff 0xff
           flash         65     6   128    0 yes     32768  128    256  4500  4500 0xff 0xff
           lfuse          0     0     0    0 no          1    0      0  4500  4500 0x00 0x00
           hfuse          0     0     0    0 no          1    0      0  4500  4500 0x00 0x00
           efuse          0     0     0    0 no          1    0      0  4500  4500 0x00 0x00
           lock           0     0     0    0 no          1    0      0  4500  4500 0x00 0x00
           calibration    0     0     0    0 no          1    0      0     0     0 0x00 0x00
           signature      0     0     0    0 no          3    0      0     0     0 0x00 0x00

         Programmer Type : Arduino
         Description     : Arduino
         Hardware Version: 3
         Firmware Version: 4.4
         Vtarget         : 0.3 V
         Varef           : 0.3 V
         Oscillator      : 28.800 kHz
         SCK period      : 3.3 us

avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.00s

avrdude: Device signature = 0x1e950f (probably m328p)
avrdude: reading input file "C:\Users\richi\AppData\Local\arduino\sketches\77D3D2018251B895496ACB24FED5C8CC/Blink.ino.hex"
avrdude: writing flash (924 bytes):

Writing | ################################################## | 100% 0.16s

avrdude: 924 bytes of flash written

avrdude done.  Thank you.
  

🤖 代码解读

  1. setup()是一个特殊的函数,在程序启动时运行一次,或者按下开发板上的reset按钮及为板子通电时自动运行,通常用于初始化设置,比如配置引脚模式、初始化串口通信等。
  2. pinMode(LED_BUILTIN, OUTPUT);pinMode()函数用于设置一个引脚的工作模式。LED_BUILTIN 是 Arduino 板子上的一个内置 LED 引脚(通常是 13 号引脚)。这行代码将LED_BUILTIN引脚设置为输出模式,运行程序通过该引脚控制 LED 等的开关。OUTPUT表示该引脚作为输出使用,程序可以通过该引脚发送电平信号(高或低)来控制外部设备(如 LED)。
  3. loop()是另一个特殊的函数,在setup()执行完后会反复执行。意味着,loop()中的代码会一直运行,直到 Arduino 被断电或重启;是一个无限循环,用来执行需要反复进行的任务。
  4. digitalWrite()函数用来将一个引脚设置为高电平(HIGH)或低电平(LOW)。这里,LED_BUILTIN引脚先被设置为高电平,意味着 LED 被点亮。用delay(1000)函数延迟程序执行 1000 毫秒(即 1 秒)后,再将LED_BUILTIN引脚设置为低电平,熄灭 LED 灯。

12.1.2 读取温湿度传感器 DHT22

DHT22 是一款常用于物联网(IoT)和嵌入式系统中的温湿度传感器,可以测量温度和湿度,温度精度在 ±0.5°C,湿度精度在 ±2% RH。获取 DHT22 测量值,需要在 Arduino IDE 中安装DHT库读取温湿度值。可以在左侧工具栏,点开LIBRARY MANAGER(库管理器),搜索 DHT sensor libray(by Adafruit)并直接安装。试验中的开发板使用 Arduino Uno。

面包版图和电路图用Fritzing绘制。

电路 代码(.ino)
  • 面包版图

PYC icon

  • 电路图

PYC icon

  • 实际搭建

PYC icon

                                                                           
  
  #include <DHT.h> 

void DHTRead();

#define DHTPIN 2
#define DHTTYPE DHT22

DHT dht(DHTPIN, DHTTYPE);
float t = 0.0;
float h = 0.0;

unsigned long previousMillis = 0;
const long interval = 10000;

void setup() {
  Serial.begin(115200);
	dht.begin();
}

void loop() {
	unsigned long currentMillis = millis();
	if (currentMillis - previousMillis >= interval) {
		previousMillis = currentMillis;
		DHTRead();
	}
}

void DHTRead() {
	float newT = dht.readTemperature();
	if (isnan(newT)) {
		Serial.println("Failed to read from DHT sensor!");
	}
	else {
		t = newT;
    Serial.print("Temperature: ");
		Serial.print(t);
	}
	float newH = dht.readHumidity();
	if (isnan(newH)) {
		Serial.println("Failed to read from DHT sensor!");
	}
	else {
		h = newH;
    Serial.print(" °C, Humidity: ");
		Serial.print(h);
    Serial.println("%");
	}
}
  
  🡮 串口监视器中打印数据
Temperature: 26.10 °C, Humidity: 34.20%
Temperature: 26.10 °C, Humidity: 35.80%
Temperature: 26.20 °C, Humidity: 34.40%
Temperature: 26.10 °C, Humidity: 32.20%
Temperature: 26.10 °C, Humidity: 31.30%
  

🤖 代码解读

  1. #include <DHT.h> :包含 DHT 传感器库,用于控制 DHT11/DHT22 传感器。
  2. void DHTRead();:先声明DHTRead()函数,并稍后定义,用于读取 DHT22 传感器的温度和湿度。
  3. #define DHTPIN 2宏定义,用于定义 DHT 传感器的连接信息,将其连接到 Arduino 的数字引脚 2。#define DHTTYPE DHT22 宏定义,指定使用的传感器类型,此次试验为 DHT22。(如果是 DHT11,则修改为 DHT11)。
  4. DHT dht(DHTPIN, DHTTYPE);:创建 DHT 传感器对象,用于后续读取温湿度数据。
  5. float t = 0.0;用于存储温度数据,初始化为0.0float h = 0.0;用于存储湿度数据,初始化为0.0
  6. unsigned long previousMillis = 0;,用于记录上一次读取数据的时间戳(单位:毫秒);const long interval = 10000;,设定读取间隔时间,此处为 10000 毫秒(10 秒)。
  7. setup() 函数(初始化):Serial.begin(115200);初始化串口通信,波特率设为115200,用于在串口监视器中打印数据。dht.begin();初始化 DHT 传感器,准备开始读取湿度数据。
  8. loop()函数(主循环):unsigned long currentMillis = millis();,通过millis()获取当前运行时间(自 Arduino 开机以来的毫秒数)。if (currentMillis - previousMillis >= interval),如果距离上次读取超过 10 秒,则执行DHTRead(),读取温湿度数据。previousMillis = currentMillis;为更新previousMillis,确保下次10秒后才会再次读取数据。DHTRead();调用DHTRead()读取传感器数据。
  9. DHTRead()函数(读取温湿度数据):float newT = dht.readTemperature();为从 DHT 传感器读取温度(默认单位:摄氏度)。if (isnan(newT))检查温度数据是否为 NaN(无效数据),如果 DHT 传感器读取失败,则执行Serial.println("Failed to read from DHT sensor!");,在串口打印读取失败信息;如果读取成功,则将温度值存入t,并打印当前读取的温度值。float newH = dht.readHumidity();读取 DHT 湿度数据类似读取温度数据,如果成功读取,则将湿度值存入h

12.2 IoT

12.2.1 IoT 和 Arduino

物联网(Internet of Things,IoT)是指通过互联网将各种物理设备(如传感器、家电、车辆、机器等)与网络连接起来,使之能够交互数据和互相通信。这些设备通过嵌入式传感器、处理器、软件及其它技术,能够收集、传输和处理数据,从而实现自动化和智能化操作。【设备/物理对象】IoT基本组成中的设备/物理对象是 IoT 系统中的核心部分,包括各种可以感知外部环境并与其它设备进行交互的硬件设备,如温湿度传感器、摄像头、智能手表、智能家居等。【传感器和执行器】传感器用于收集环境数据(如温度、湿度、位置等),而执行器则用于根据接收到的数据做出动作(如控制机器人、调节温湿度、开关灯具和阀门等)。【连接性】物理设备通过不同的通信技术(如 Wi-Fi、蓝牙、Zigbee、LoRa、5G等)将数据传输到云平台、边缘设备或其它设备进行处理。通信方式的选择取决于应用的需求,如范围、速度和功耗等。【数据处理和分析】数据在云端或本地服务器上进行处理和分析,通常利用(大)数据分析、人工智能(AI)等技术来挖掘数据的价值。分析结果可以帮助做出决策,如自动调整设备设置、发出警报等。【用户界面/应用】用户通过应用程序(如手机/电脑应用、网页界面等)与 IoT 设备进行交互、控制设备、查看数据和设置通知等。

IoT涉及的领域非常广泛,如智能家居、健康医疗、智慧城市、工业物联网、农业物联网等。通过网络将物理设备连接起来,,实现数据交换和智能化管理,不仅改变了家庭生活、工业生产等各个领域的运作模式,也带来了新的商业机会和技术挑战。

因为 Arduino 是一个开源硬件平台, 提供了易于使用的开发环境和大量的扩展模块,可以快速的构建 IoT 设备,因此在 IoT 中的应用非常广泛。Arduino 作为硬件控制平台,与 IoT 技术的结合,主要体现在将设备连接到网络、收集和传输数据,及通过互联网进行远程控制和监测。

12.2.2 ESP32 [NodeMCU]

ESP32 是由 Espressif Systems开发的一款支持 Wi-Fi 和蓝牙的双模芯片,广泛应用于 IoT 和嵌入式项目中。ESP32 芯片处理器为双核 Xtensa® 32-bit LX6 CPU,最大工作频率 240 MHz;内存为520 KB 内部 RAM 和 4 MB Flash 存储(可扩展);无线通信包括 Wi-Fi, 支持 IEEE 802.11b/g/n 协议,802.11n (2.4 GHz) 速度达 150 Mbps。蓝牙 v4.2 完整标准,包含传统蓝牙 (BR/EDR) 和低功耗蓝牙 (Bluetooth LE);I/O 引脚最大支持 34 个 GPIO(通用输入输出引脚),支持 PWM、ADC(模数转换)、DAC(数模转换)、I2C、SPI、UART 等通信接口;具有多种低功耗模式(深度睡眠模式等),适合电池供电的 IoT 设备;具有多种低功耗模式(深度睡眠模式等),适合电池供电的 IoT 设备。

NodeMCU-32S 开发板(如图),是基于 ESP32 芯片的开发板,适合 Arduino IDE 开发,且有丰富的开发支持,尤其适合 IoT 项目。

PYC icon

图 12-3 ESP32 NodeMCU-32S,图片来源于 Waveshare,https://www.waveshare.com/nodemcu-32s.htm

ESP32 支持通过 Arduino IDE 开发,但需要安装 ESP32 支持包。打开 Arduino IDE,在File->Preferences..->Settings->Additional boards manager URLs下输入https://espressif.github.io/arduino-esp32/package_esp32_index.json链接地址;然后进入左侧工具栏下的开发板管理器(BOARD MANAGER),搜索 ESP32,安装esp32 [by Espressif Systems]。安装成功后,就可以在Tools->Board:...->esp32下选择Node32s,并选择连接开发板后对应的端口(Port:...)。将下述代码编译上传至 ESP32 芯片后,可以看到板载的 LED 灯开始闪烁,从而验证上述安装过程是否正确。

  void setup() {
  pinMode(LED_BUILTIN, OUTPUT); // 设置 LED 引脚为输出
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH); // LED 灯亮
  delay(1000);                     // 等待 1 秒
  digitalWrite(LED_BUILTIN, LOW);  // LED 灯灭
  delay(1000);                     // 等待 1 秒
}
  

也可以从File-Examples->ESP32等下选择安装支持包时预加载的示例代码,查看或学习 ESP32 的不同共用。

12.2.3 IoT 试验

对 Arduino 有了一定开发经验,并具备了一定的硬件知识,如微控制器(MCU);外围设备和接口,如 GPIO(General Purpose Input/Output)通用输入输出,串口通信(UART、USART等),用于与传感器、外设等通信的 SPI/I2C 等常见的通信协议,ADC/DAC 模拟数字转换和数字模拟转换,用于控制电机、LED 等设备的 PWM,及中断处理、定时器/计数器、电源管理等,从而有助于开发更复杂的项目。下面就 IoT 领域,示例一个相对较为简单的案例(如24-4),基于 ESP32 (Node MCU-32S)开发 IoT Web 服务器,通过 Wi-Fi 与 Web 服务器连接,控制 GPIO 引脚和获取 DHT22 传感器的温湿度数据。

Web 服务器的搭建是通过 ESPAsyncWebServe库完成 ,能够显示温湿度信息数据,并且可以通过页面上的按钮控制三个 LED 灯的开关。同时,可以通过物理开关点亮和熄灭 GPIO 26 引脚的 LED 灯,并将开关的状态同步到网页上,显示State OnState Off信息,且网页虚拟开关按钮也会同步发生改变。

PYC icon

图 12-4 ESP32 IoT

因为用到ESPAsyncWebServe库,需要下载该安装包,为一个.zip文件夹,解压缩后,将ESPAsyncWebServer-master文件夹名重命名为ESPAsyncWebServer,并复制到 Arduino IDE 安装库文件夹下,其路径通常为C:\Users\[user]\AppData\Local\Arduino15\libraries。同时,ESPAsyncWebServe库需要AsyncTCP库才能工作,因此下载.zip文件夹,解压后,重名名为AsyncTCP,同样复制到 Arduino IDE 安装库文件夹下。

电路 代码(.ino)
  • 面包版图

PYC icon

  • 电路图

PYC icon

  • 实际搭建

PYC icon

                                                                           
  
  #include "WiFi.h"
#include <ESPAsyncWebServer.h>
#include <Adafruit_Sensor.h>
#include <DHT.h> 

void DHTRead();
void DHTRequest();
void LEDRequest();
void helloRequest();
String processor(const String& var);
String outputState(int output);

const char* ssid = "SSID";
const char* password = "PASSWORD";

#define DHTPIN 27 
#define DHTTYPE DHT22

#define HELLOBUTTONPIN 14
#define HELLOLEDPIN 26

DHT dht(DHTPIN, DHTTYPE);
float t = 0.0;
float h = 0.0;

const char* PARAM_INPUT_1 = "output";
const char* PARAM_INPUT_2 = "state";

int helloLedState=LOW;
int buttonState;
int lastButtonState=LOW;

unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 50; 

unsigned long previousMillis = 0;
const long interval = 10000;

AsyncWebServer server(80);

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    html {
      font-family: Arial;
      display: inline-block;
      margin: 0px auto;
      text-align: center;
    }
    h2 { font-size: 3.0rem; }
    p { font-size: 3.0rem; }
    .units { font-size: 1.2rem; }
    .dht-labels{
      font-size: 1.5rem;
      vertical-align:middle;
      padding-bottom: 15px;
    }
    body {max-width: 600px; margin:0px auto; padding-bottom: 25px;}
      .switch {position: relative; display: inline-block; width: 120px; height: 68px} 
      .switch input {display: none}
      .slider {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; border-radius: 34px}
      .slider:before {position: absolute; content: ""; height: 52px; width: 52px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 68px}
      input:checked+.slider {background-color: #2196F3}
      input:checked+.slider:before {-webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px)} 
  </style>
</head>

<body>
  <h2>ESP32[NodeMCU] IoT Server</h2>
  %BUTTONPLACEHOLDER%
  %HELLOWBUTTONPLACEHOLDER%
  <p>
    <i class="fas fa-thermometer-half" style="color:#059e8a;"></i> 
    <span class="dht-labels">Temperature</span> 
    <span id="temperature">%TEMPERATURE%</span>
    <sup class="units">&deg;C</sup>
  </p>
  <p>
    <i class="fas fa-tint" style="color:#00add6;"></i> 
    <span class="dht-labels">Humidity</span>
    <span id="humidity">%HUMIDITY%</span>
    <sup class="units">%</sup>
  </p>
</body>

<script>
setInterval(function () {
  fetch("/temperature")
    .then(response => response.text())  // 获取响应文本
    .then(data => {
      document.getElementById("temperature").innerHTML = data;  // 更新温度
    })
    .catch(error => console.error('Error fetching temperature:', error));
}, 10000);

setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      document.getElementById("humidity").innerHTML = this.responseText;
    }
  };
  xhttp.open("GET", "/humidity", true);
  xhttp.send();
}, 10000 ) ;

setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      var inputChecked;
      var outputStateM;
      if( this.responseText == 1){ 
        inputChecked = true;
        outputStateM = "On";
      }
      else { 
        inputChecked = false;
        outputStateM = "Off";
      }
      document.getElementById("output").checked = inputChecked;
      document.getElementById("outputState").innerHTML = outputStateM;
    }
  };
  xhttp.open("GET", "/helloState", true);
  xhttp.send();
}, 1000 ) ;

function toggleCheckbox(element) {
    var xhttp = new XMLHttpRequest();
    if(element.checked){ xhttp.open("GET", "/update?output="+element.id+"&state=1", true); }
    else { xhttp.open("GET", "/update?output="+element.id+"&state=0", true); }
    xhttp.send();
  }

function helloToggleCheckbox(element) {
  var xhr = new XMLHttpRequest();
  if(element.checked){ xhr.open("GET", "/helloUpdate?state=1", true); }
  else { xhr.open("GET", "/helloUpdate?state=0", true); }
  xhr.send();
}

</script>
<script src="https://kit.fontawesome.com/476e436bc5.js" crossorigin="anonymous"></script>
</html>)rawliteral";

void setup() {
	Serial.begin(115200);
	dht.begin();

	pinMode(12, OUTPUT);
	digitalWrite(12, LOW);
	pinMode(13, OUTPUT);
	digitalWrite(13, LOW);

  pinMode(HELLOBUTTONPIN, INPUT);
  pinMode(HELLOLEDPIN, OUTPUT);
  digitalWrite(HELLOLEDPIN,LOW);


	WiFi.begin(ssid, password);
	Serial.println("Connecting to WiFi");
	while (WiFi.status() != WL_CONNECTED) {
		delay(1000);
		Serial.println(".");
	}

	Serial.println("");
	Serial.println("WiFi connected.");
	Serial.println("Web server running. Waiting for the ESP IP...");
	delay(2000);
	Serial.println("IP address: ");
	Serial.println(WiFi.localIP());

	server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
		request->send_P(200, "text/html", index_html, processor);
		});
	DHTRequest();
	LEDRequest();
  helloRequest();

	server.begin();
}

void loop() {
	unsigned long currentMillis = millis();
	if (currentMillis - previousMillis >= interval) {
		previousMillis = currentMillis;
		DHTRead();
	}

  int reading = digitalRead(HELLOBUTTONPIN);
  if (reading != lastButtonState) {
    lastDebounceTime = millis();
  }  

  if ((millis() - lastDebounceTime) > debounceDelay) {
    if (reading != buttonState) {
      buttonState = reading;
      if (buttonState == HIGH) {
        helloLedState = !helloLedState;
      }
    }
  }
  digitalWrite(HELLOLEDPIN, helloLedState);
  lastButtonState = reading;
}

String processor(const String& var) {
	if (var == "TEMPERATURE") {
		return String(t);
	}
	else if (var == "HUMIDITY") {
		return String(h);
	}
	else if (var == "BUTTONPLACEHOLDER") {
		String buttons = "";
		buttons += "<h4>Output - GPIO 12</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"12\" " + outputState(12) + "><span class=\"slider\"></span></label>";
		buttons += "<h4>Output - GPIO 13</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"13\" " + outputState(13) + "><span class=\"slider\"></span></label>";
		return buttons;
	}
  else if(var == "HELLOWBUTTONPLACEHOLDER"){
    String buttons ="";
    String outputStateValue = outputState(HELLOLEDPIN);
    buttons+= "<h4>Output - GPIO 26 [Physical Button Simultaneously] - State <span id=\"outputState\"></span></h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"helloToggleCheckbox(this)\" id=\"output\" " + outputStateValue + "><span class=\"slider\"></span></label>";
    return buttons;
  }

	return String();
}

String outputState(int output) {
	if (digitalRead(output)) {
		return "checked";
	}
	else {
		return "";
	}
  return "";
}

void LEDRequest() {
	server.on("/update", HTTP_GET, [](AsyncWebServerRequest* request) {
		String inputMessage1;
		String inputMessage2;
		if (request->hasParam(PARAM_INPUT_1) && request->hasParam(PARAM_INPUT_2)) {
			inputMessage1 = request->getParam(PARAM_INPUT_1)->value();
			inputMessage2 = request->getParam(PARAM_INPUT_2)->value();
			digitalWrite(inputMessage1.toInt(), inputMessage2.toInt());
		}
		else {
			inputMessage1 = "No message sent";
			inputMessage2 = "No message sent";
		}
		Serial.print("GPIO: ");
		Serial.print(inputMessage1);
		Serial.print(" - Set to: ");
		Serial.println(inputMessage2);
		request->send(200, "text/plain", "OK");
		});
}

void DHTRequest() {
	server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest* request) {
		request->send_P(200, "text/plain", String(t).c_str());
		});
	server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest* request) {
		request->send_P(200, "text/plain", String(h).c_str());
		});
}

void helloRequest(){
  server.on("/helloUpdate", HTTP_GET, [] (AsyncWebServerRequest *request) {
    String inputMessage;
    String inputParam;
    if (request->hasParam(PARAM_INPUT_2)) {
      inputMessage = request->getParam(PARAM_INPUT_2)->value();
      inputParam = PARAM_INPUT_2;
      digitalWrite(HELLOLEDPIN, inputMessage.toInt());
      helloLedState = !helloLedState;
    }
    else {
      inputMessage = "No message sent";
      inputParam = "none";
    }
    Serial.println(inputMessage);
    request->send(200, "text/plain", "OK");
  });

  server.on("/helloState", HTTP_GET, [] (AsyncWebServerRequest *request) {
    request->send(200, "text/plain", String(digitalRead(HELLOLEDPIN)).c_str());
  });  
}

void DHTRead() {
	float newT = dht.readTemperature();
	if (isnan(newT)) {
		Serial.println("Failed to read from DHT sensor!");
	}
	else {
		t = newT;
		Serial.println(t);
	}
	float newH = dht.readHumidity();
	if (isnan(newH)) {
		Serial.println("Failed to read from DHT sensor!");
	}
	else {
		h = newH;
		Serial.println(h);
	}
}
  

代码参考 Random Nerd Tutorials,https://randomnerdtutorials.com/

🤖 代码解读

  • 引入库和依赖
  1. WiFi.h:用于 ESP32 的 Wi-Fi 功能。
  2. ESPAsyncWebServer.h:用于搭建异步 Web 服务器,使得设备能够提供 HTTP 服务,响应请求。
  3. Adafruit_Sensor.hDHT.h:用于读取 DHT22 温湿度传感器数据。
  • 函数声明
  1. 这些前置声明,为代码中定义的函数,其中DHTRead()用于读取 DHT22 传感器的温湿度值;DHTRead()处理 DHT 传感器的 Web 请求;DHTRead()处理 LED 控制的 Web 请求;helloRequest()处理按键按下的 Web 请求;processor(const String& var)动态处理 HTML 页面中的占位符,用于服务器响应 Web 页面;outputState(int output)返回某个 GPIO 端口的状态(HIGH/LOW)。
  • Wi-Fi 连接信息
  1. ssid为 Wi-Fi网络名称;password为对应的密码。ESP32 将通过这个 SSID 和密码连接到 Wi-Fi 网络。
  • 定义常量和全局变量,与对象定义
  1. DHT22 传感器的定义包括#define DHTPIN 27 用于将 DHT22 传感器连接到 ESP32 的 GPIO27 引脚;#define DHTTYPE DHT22指定传感器的类型为 DHT22(温湿度传感器);
  2. 按钮和 LED 的定义包括#define HELLOBUTTONPIN 14将按钮连接到 ESP32 的 GPIO14引脚;#define HELLOBUTTONPIN 14将 (Hello)LED 连接到 ESP32 的 GPIO26引脚。
  3. DHT dht(DHTPIN, DHTTYPE);:创建 DHT 传感器对象,并绑定到 GPIO27引脚。
  4. float t = 0.0, h = 0.0为存储温湿度数据的变量。
  5. (Hello)LED 及按钮状态定义有helloLedState,存储 LED 的当前状态(HIGH/LOW);buttonState存储按钮当前状态;lastButtonState存储按钮上一次的状态,用于去抖动处理(防止按键误触)。
  6. 定义按钮去抖动处理变量有lastDebounceTime,记录上次检测到按钮变化的时间;debounceDelay = 50为去抖动延迟(50 毫秒),防止误触。
  7. 定义传感器读取间隔的变量有previousMillis,存储上次读取传感器数据额时间戳;interval = 10000设置数据读取间隔为 10 秒(10000 毫秒)。
  8. HTTP 请求参数,即 Web 服务器处理参数定义有PARAM_INPUT_1,可用于识别设备(如 LED);PARAM_INPUT_2用于存储设备的开关状态。
  • 异步 Web 服务器
  1. AsyncWebServer server(80);:创建异步 Web 服务器,监听端口 80(HTTP 默认端口)。服务器用于处理 ESP32 作为 Web 服务器的请求,如访问/主页,控制 LED(网页开关或物理按钮),读取传感器数据。
  • ESP32 Web 服务器前端 HTML 页面

const char index_html[] PROGMEM = R"rawliteral(<!DOCTYPE HTML><html>...</html>)rawliteral";部分内容为 ESP32 Web 服务器前端,显示温湿度,控制 LED 和 Hello 按钮。每 10 秒获取温湿度,每秒获取 Hello 按钮状态。点击按钮发送 AJAX(Asynchronous JavaScript and XML,即用 JavaScript 执行异步网络请求) 请求,ESP32 处理/update/helloUpdate

  1. HTML 结构:PROGMEM指示 ESP32 将 HTML 存储在 Flash 存储器(减少 RAM 占用);R"rawliteral(...)"是使用原始字符串,可以在 C++ 代码中直接写 HTML。
  2. <head>...</head> HTML 头部,<meta name="viewport" content="width=device-width, initial-scale=1">meta viewport让网页适应手机和 PC 屏幕(响应式布局)。在<style>...</style>之间定义了htmlh2p页面样式,和开关按钮样式。font-family: Arial;指定字体,fdisplay: inline-block;让内容居中对齐,h2p设定大号字体,提供可读性,.units设定温度和湿度的单位大小(℃ 和 %),.dht-labels设定温湿度文字标签格式。开关按钮样式定义了一个滑动开关,未选中状态为灰色背景,白色按钮;选中状态为蓝色背景,按钮向右滑动。
  3. <body>...</body>部分为 Web 服务器显示内容:<h2>ESP32[NodeMCU] IoT Server</h2>设定了网页标题;%BUTTONPLACEHOLDER%%HELLOWBUTTONPLACEHOLDER%是占位符,ESP32 代码会用真实 HTML 按钮替换占位符。<p>...</p>部分为温湿度显示。fa-thermometer-halffa-tint分别为温度和湿度图标,使用了Fonticons提供的图标,注册后,通过生成的连接<script src="https://kit.fontawesome.com/476e436bc5.js" crossorigin="anonymous"></script>包含可用的免费图标。温湿度显示中%TEMPERATURE%%HUMIDITY%是温湿度值的占位符,ESP32 会填充实际测量的温湿度值。
  4. <script>...</script>部分为 JavaScript 代码。
  setInterval(function () {
  fetch("/temperature")
    .then(response => response.text())  // 获取响应文本
    .then(data => {
      document.getElementById("temperature").innerHTML = data;  // 更新温度
    })
    .catch(error => console.error('Error fetching temperature:', error));
}, 10000);
  

这段代码的功能是每隔 10 秒向服务器发起一个 GET 请求,获取/temperature端点返回的温度数据,并将该数据更新到页面上idtemperature的元素中。如果请求过程中出现任何错误(如网络问题),错误信息会被输到控制台。

  1. setInterval(function () { ... }, 10000);函数设置了一个定时器,会每隔 10 秒(10000 毫秒)执行一次传入的回调函数。setInterval函数的第一个参数是一个回调函数,第二个参数是时间间隔(以毫秒为单位)。
  2. fetch("/temperature")fetch,是一个现代的 JavaScript API,用于发送 HTTP 请求,向服务器发起了一个 GET 请求。请求的路径是/temperature,是请求的 URL, 指向服务器上一个返回温度数据的端点。请求类型为 GET,意味着是向服务器请求数据。fetch()返回一个 Promise 对象,表示异步请求的结果。
  3. .then(response => response.text())then方法用于处理fetch返回的 Promise 对象结果。这里为响应对象response,包含了 HTTP 响应的所有信息(如状态码、响应头、响应体等)。response.text()是一个方法,解析响应体的内容为纯文本,同样返回一个 Promise,因此可以继续使用.then链式调用。
  4. .then(data => { ... })data是上一个then的返回结果,即通过response.text()获得的纯文本数据。
  5. document.getElementById("temperature").innerHTML = data;通过 DOM 操作更新页面中某个元素的内容。document.getElementById("temperature")通过getElementById方法获取网页中idtemperature的 HTML 元素。innerHTML属性允许读取或设置该元素的 HTML 内容,即将data(获取的温度数据)赋值给该元素,从而更新页面内容。每次获取到新温度值时,该元素的内容会被替换为新的温度数据。
  6. .catch(error => console.error('Error fetching temperature:', error));catch方法用于捕获任何在fetch请求或.then处理过程中出现的错误,并输出错误信息。catch捕获并处理 Promise 链中的任何错误。error是捕获的错误对象,如网络错误、响应错误或其它执行错误。console.error()会将错误信息输出到控制台。输出的错误信息包含Error fetching temperature:的提示信息。
  setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      document.getElementById("humidity").innerHTML = this.responseText;
    }
  };
  xhttp.open("GET", "/humidity", true);
  xhttp.send();
}, 10000 ) ;
  

与前一个fetch虽然实现了同样一个功能,但这里给出的示例方法是实现了一个传统的 AJAX 请求。每 10 秒通过XMLHttpRequest对象向服务器发送 GET 请求,获取服务器返回的湿度数据,并将数据显示在网页上id="humidity"的元素中。

  1. var xhttp = new XMLHttpRequest();用于创建一个新的XMLHttpRequest对象(xhttp)。XMLHttpRequest是一个内置的 JavaScript 对象,用来向服务器发送 HTTP 请求并接收响应,通常用于 AJAX 请求,是前端与服务器异步通信的基础。
  2. xhttp.onreadystatechange = function() { ... };设置onreadystatechange事件处理程序,监听请求的状态变化。onreadystatechangeXMLHttpRequest对象的一个事件,在每次请求的状态变化时都会触发。因此需要在这个事件中检查请求的状态,以便在请求完成时做出相应的处理,如if (this.readyState == 4 && this.status == 200)来判断是否是请求完成且成功的状态。readyState表示请求的状态,值从0到4,分别对应不同的状态:0,请求未初始化;1,服务器连接已建立;2,请求已接收;3,请求处理中;4,请求已完成(响应已准备好)。this.status是 HTTP 响应代码,200表示请求成功并得到了正常响应。
  3. xhttp.open("GET", "/humidity", true);初始化请求。参数包括请求的类型("GET"),目标 URL(服务器上有一个"/humidity"端点会返回湿度数据)和是否异步,true表示异步,意味着请求不会阻塞页面的其它操作。
  4. xhttp.send();发送请求,并等待服务器的响应。
  setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      var inputChecked;
      var outputStateM;
      if( this.responseText == 1){ 
        inputChecked = true;
        outputStateM = "On";
      }
      else { 
        inputChecked = false;
        outputStateM = "Off";
      }
      document.getElementById("output").checked = inputChecked;
      document.getElementById("outputState").innerHTML = outputStateM;
    }
  };
  xhttp.open("GET", "/helloState", true);
  xhttp.send();
}, 1000 ) ;
  

该部分代码是每隔 1 秒(1000 毫秒)通过XMLHttpRequest发送一个 GET 请求到服务器/helloState端点,获取返回的数据后更新页面的内容。

  1. var inputChecked; var outputStateM;声明了两个变量值,inputChecked用于控制复选框状态(truefalse),outputStateM用于文本方式显示设备状态("On""Off")。
  2. if (this.responseText == 1) { ... } else { ... }检查服务器返回的响应文本,根据其值是否为1,设置复选框和设备状态文本。
  3. document.getElementById("output").checked = inputChecked;通过 DOM 操作更新页面上id="output"的复选框的选中状态。.checked = inputChecked;根据inputChecked的值来设置复选框是否被选中;document.getElementById("outputState").innerHTML = outputStateM;更新页面上id="outputState"的元素内容。.innerHTML = outputStateM;将设备状态(outputStateM)的值设置为该元素的内容。
  function toggleCheckbox(element) {
    var xhttp = new XMLHttpRequest();
    if(element.checked){ xhttp.open("GET", "/update?output="+element.id+"&state=1", true); }
    else { xhttp.open("GET", "/update?output="+element.id+"&state=0", true); }
    xhttp.send();
  }
  

这段 JavaScript 代码定义了一个toggleCheckbox函数,用于处理复选框的切换事件,并向服务器发送一个请求来更新复选框的状态。发送 GET 请求到服务器的 URL 中包含复选框的id和当前的状态(state=1表示选中,state=0表示未选中)。服务器可以根据这个请求更新状态或执行相关操作。

  1. 函数参数element表示触发事件的 HTML 元素(为一个复选框<input type="checkbox">)。
  2. if(element.checked)检查复选框是否被选中。element.checked是一个布尔值,表示复选框的状态。如果选中,则打开一个 GET 请求,向服务器发送请求,URL 中包含复选框的idstate=1表示选中的状态;否则发送未选中的状态state=0
  function helloToggleCheckbox(element) {
  var xhr = new XMLHttpRequest();
  if(element.checked){ xhr.open("GET", "/helloUpdate?state=1", true); }
  else { xhr.open("GET", "/helloUpdate?state=0", true); }
  xhr.send();
}
  

helloToggleCheckbox函数与toggleCheckbox类似,但是向一个不同的 URL(/helloUpdate)发送请求。

  • setup()函数
  1. Serial.begin(115200);:初始化串口通信,波特率设置为 115200,用于通过串口监视器输出调试信息。
  2. dht.begin();:初始化 DHT 传感器,传感器开始工作。
  3. pinMode(12, OUTPUT);pinMode(13, OUTPUT);pinMode(HELLOBUTTONPIN, INPUT);pinMode(HELLOLEDPIN, OUTPUT);分别设置各数字引脚的模式,输出模式(OUTPUT)和输入模式(INPUT)。输出模式的引脚用于控制 LED 灯的开关,输入模式用于监测按钮的开关状态。
  4. digitalWrite(12, LOW);digitalWrite(13, LOW);digitalWrite(HELLOLEDPIN,LOW);设置输出模式的数字引脚的电平高低(HIGHLOW),均为LOW,即 LED 灯初始时为熄灭状态。
  5. WiFi.begin(ssid, password);:使用提供的ssid(Wi-Fi名)和对应的password(Wi-Fi 密码)尝试连接到 Wi-Fi 网络。
  6. while (WiFi.status() != WL_CONNECTED) :当 Wi-Fi 连接状态不是WL_CONNECTED时,每隔 1 秒输出一个点(.),直到成功连接为止。一旦连接成功,输出 Wi-Fi 已连接的消息,并获取 ESP 的 IP 地址。通过该 IP 地址访问 Web 服务器。
  	server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
		request->send_P(200, "text/html", index_html, processor);
		});
  

该段代码配置了一个 Web 服务器路由。当用户访问设备的根路径(/)时,服务器会返回一个 HTML 页面(index_html),并且在页面加载时,通过processor函数动态处理其中的占位符或变量,生成最终的 HTML 内容。返回的状态是 200,表示请求成功。

  1. server.on("/"):表示配置 Web 服务器,当接收到一个 HTTP GET 请求并访问根路径(/)时,会执行后面的回调函数。

  2. HTTP_GET:表示这是一个处理 HTTP GET 请求的方法,是用户通过浏览器输入设备的 IP 地址后,发送的请求类型。

  3. [](AsyncWebServerRequest* request):是一个匿名 Lambda 函数,用于处理请求。request是一个指向AsyncWebServerRequest对象的指针,包含了请求的相关信息(如请求头、参数等)。这个函数会在收到根路径的 GET 请求时被调用。

  4. request->send_P():这个方法向客户端发送响应。send_P是一个特定的方法,表示从程序存储的闪存中发送数据。

  5. 200是 HTTP 状态码,表示请求成功。

  6. "text/html"是返回数据的 MIME 类型,告知浏览器返回的是 HTML(超文本标记语言文本) 内容。常见的 MIME 类型有text/html(.htm),超文本标记语言文本;text/plain(.txt),普通文本;application/rtf(.rtf),RTF 文本;image/gif(.gif),GIF 图形;image/jpeg (.jpeg、.jpg),JPEG 图像;audio/basic(.au),au 声音文件;audio/midi、audio/x-midi(mid、.midi),MIDI 音乐文件;audio/x-pn-realaudio(.ra、.ram),RealAudio 音乐文件;video/mpeg(.mpg、.mpeg),MPEG 文件;video/x-msvideo(.avi),AVI 文件;application/x-gzip(.gz),GZIP 文件;application/x-tar(.tar),TAR 文件等。

  7. index_html:是存储在程序中的 HTML 内容(通常是一个字符串或常量),将在响应中发送给客户端,为嵌入在代码中的 HTML 页面。

  8. processor:是一个处理函数,通常用于返回 HTML 时对页面中的占位符进行替换,动态生成内容,即将处理index_html中的某些变量或模板标记。

  • DHTRequest();"/temperature""/humidity"),LEDRequest();"/update")和helloRequest();"/helloUpdate""/helloState") HTTP 请求处理
  void DHTRequest() {
	server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest* request) {
		request->send_P(200, "text/plain", String(t).c_str());
		});
	server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest* request) {
		request->send_P(200, "text/plain", String(h).c_str());
		});
}
  
  1. DHTRequest();函数,用于在 Web 服务器上创建两个路由,分别返回温度和湿度的值。
  void LEDRequest() {
	server.on("/update", HTTP_GET, [](AsyncWebServerRequest* request) {
		String inputMessage1;
		String inputMessage2;
		if (request->hasParam(PARAM_INPUT_1) && request->hasParam(PARAM_INPUT_2)) {
			inputMessage1 = request->getParam(PARAM_INPUT_1)->value();
			inputMessage2 = request->getParam(PARAM_INPUT_2)->value();
			digitalWrite(inputMessage1.toInt(), inputMessage2.toInt());
		}
		else {
			inputMessage1 = "No message sent";
			inputMessage2 = "No message sent";
		}
		Serial.print("GPIO: ");
		Serial.print(inputMessage1);
		Serial.print(" - Set to: ");
		Serial.println(inputMessage2);
		request->send(200, "text/plain", "OK");
		});
}
  

LEDRequest(); 函数设置了 Web 服务器路由,当用户访问/update路径时,服务器会检查请求中是否包含PARAM_INPUT_1(GPIO 引脚编号)和PARAM_INPUT_2(电平状态)。如果参数存在,服务器会根据这些参数控制相应 GPIO 引脚的电平(如开或关)。同时,服务器会返回一个简单的"OK"响应。否则,若请求中没有提供参数,则返回相应的信息"No message sent"

  1. if (request->hasParam(PARAM_INPUT_1) && request->hasParam(PARAM_INPUT_2))检查请求中是否包含名为PARAM_INPUT_1PARAM_INPUT_2的参数。如果两个参数都存在,则进入if语句块。
  2. inputMessage1 = request->getParam(PARAM_INPUT_1)->value();inputMessage2 = request->getParam(PARAM_INPUT_2)->value();是分别获取请求中PARAM_INPUT_1PARAM_INPUT_2的参数值,并存储到inputMessage1inputMessage2中。
  3. digitalWrite(inputMessage1.toInt(), inputMessage2.toInt());inputMessage1(一个 GPIO 引脚编号的字符串)和inputMessage2(一个表示电平的字符串,如"0""1")转换为整数,分别作为digitalWrite(pin, state)的参数传入,设置 GPIO 引脚的电平。
  void helloRequest(){
  server.on("/helloUpdate", HTTP_GET, [] (AsyncWebServerRequest *request) {
    String inputMessage;
    String inputParam;
    if (request->hasParam(PARAM_INPUT_2)) {
      inputMessage = request->getParam(PARAM_INPUT_2)->value();
      inputParam = PARAM_INPUT_2;
      digitalWrite(HELLOLEDPIN, inputMessage.toInt());
      helloLedState = !helloLedState;
    }
    else {
      inputMessage = "No message sent";
      inputParam = "none";
    }
    Serial.println(inputMessage);
    request->send(200, "text/plain", "OK");
  });

  server.on("/helloState", HTTP_GET, [] (AsyncWebServerRequest *request) {
    request->send(200, "text/plain", String(digitalRead(HELLOLEDPIN)).c_str());
  });  
}
  

helloRequest();函数在 Web 服务器上设置了两个路径处理,/helloUpdate通过传递PARAM_INPUT_2参数来控制 (Hello)LED 的开关状态。发送0会关闭 LED;发送1会开启 LED。每次请求还会切换 LED 的状态。 /helloState查询 LED 当前的状态(开或关),并返回该状态。

  1. digitalWrite(HELLOLEDPIN, inputMessage.toInt())根据inputMessage = request->getParam(PARAM_INPUT_2)->value();的值,控制 LED 的开关,即设置HELLOLEDPIN引脚的电平(0为低电平,1为高电平)。
  2. request->send(200, "text/plain", String(digitalRead(HELLOLEDPIN)).c_str());String(digitalRead(HELLOLEDPIN)).c_str()是读取 LED 引脚HELLOLEDPIN的当前状态(使用digitalRead函数),并将该状态转换为字符串。然后,返回该状态作为响应。
  • processor() Web 页面占位符处理
  String processor(const String& var) {
	if (var == "TEMPERATURE") {
		return String(t);
	}
	else if (var == "HUMIDITY") {
		return String(h);
	}
	else if (var == "BUTTONPLACEHOLDER") {
		String buttons = "";
		buttons += "<h4>Output - GPIO 12</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"12\" " + outputState(12) + "><span class=\"slider\"></span></label>";
		buttons += "<h4>Output - GPIO 13</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"13\" " + outputState(13) + "><span class=\"slider\"></span></label>";
		return buttons;
	}
  else if(var == "HELLOWBUTTONPLACEHOLDER"){
    String buttons ="";
    String outputStateValue = outputState(HELLOLEDPIN);
    buttons+= "<h4>Output - GPIO 26 [Physical Button Simultaneously] - State <span id=\"outputState\"></span></h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"helloToggleCheckbox(this)\" id=\"output\" " + outputStateValue + "><span class=\"slider\"></span></label>";
    return buttons;
  }

	return String();
}
  

processor() 函数通过接收占位符名称("TEMPERATURE""HUMIDITY""BUTTONPLACEHOLDER""HELLOWBUTTONPLACEHOLDER"),并根据占位符生成相应的 HTML 内容,最终替换掉 Web 页面中对应的位置。

  1. "BUTTONPLACEHOLDER"占位符代码块中buttons += "...;"是通过拼接字符串的方式,动态生成一个 HTML 元素,表示一个控制 GPIO 12/13 引脚的开关按钮。用户在网页上操作该按钮时,复选框状态会改变,并触发 JavaScript 函数toggleCheckbox,后者会向服务器发送请求更新引脚的状态。
  2. 类似的,"HELLOWBUTTONPLACEHOLDER"占位符代码块中buttons += "...;",触发了helloToggleCheckbox函数,向服务器发送请求更新引脚的状态。
  • loop()函数
  1. loop()函数的主要任务是定期读取温湿度传感器的值(每 10 秒一次),及处理按钮输入的去抖动逻辑。当按钮按下时,切换一个 LED 的状态。通过去抖机制,避免按钮状态的误读。

参考文献(Reference):

[1] Santos, R., & Santos, S. (n.d.). Learn ESP32 with Arduino IDE. https://randomnerdtutorials.com/.

[2] 250+ ESP32 Projects, Tutorials and Guides with Arduino IDE,https://randomnerdtutorials.com/projects-esp32/.

[3] Espressif, ESP32 系列芯片技术规格书 版本4.8, www.espressif.com.

[4] Arduino, https://www.arduino.cc/.