回到首页 返回首页
回到顶部 回到顶部
返回上一页 返回上一页

【FireBeetle 2 ESP32-C5】智能温湿度计 简单

头像 无垠的广袤 2025.10.11 21 0

【FireBeetle 2 ESP32-C5】智能温湿度计

本文介绍了 FireBeetle 2 ESP32-C5 开发套件结合 WiFi 网络、 HTTP 协议和 DHT11 温湿度传感器,实现物联网温湿度计的项目设计。

项目介绍

ESP32-C5 开发板调用 micropython 固件包含的 dht 库,通过单总线协议,读取 GPIO 连接的 DHT11 传感器模块数据;开发板 WiFi 联网并通过 HTTP 协议上传采集的数据至网页客户端,实时更新温湿度数据,接入 Home Assistant 智能家具平台,进而实现智能温湿度计的项目设计。

准备工作:包括开发板固件烧录、Thonny IDE 安装、硬件连接等;

流程图:包括开发板代码和网页服务器代码对应的流程图;

工程代码:包括开发板板端执行代码、云端服务器代码等;

工程测试:包括程序运行、数据传输、温湿度采集、效果演示等;

DHT11

DHT11 内置一个电阻式感湿元件、一个 NTC 测温元件和一个与之相连的主控单片机;

DHT11 传感器在极为精确的湿度校验室中进行校准,校准系数存储于其内部的单片机中;

传感器在检测和处理环境温湿度数据时,需要调用内部的校准系数。

工作参数

湿度测量范围:20~90%RH,精度:±5%RH

温度测量范围:0~50℃,精度:±2℃

工作电压:DC 3.3V 至 5V

通信协议

DHT11 采用单总线协议,即使用一根 DATA 线进行数据收发。

DHT11 的 DATA 线一次通讯时间约为 4ms;

40 位数据包括整数部分、小数部分和校验位;

具体为: 8bit 湿度整数数据 + 8bit 湿度小数数据 + 8bit 温度整数数据 + 8bit 温度小数数据 + 8bit 校验位。

工作时序

主要包括:DHT11触发采集时序,DHT11高电平时序,DHT11低电平时序;

clock_dht11.JPG

工作流程

主机将 DHT11 数据引脚拉低,持续至少 18ms;

主机将 DHT11 数据引脚拉高,持续 20 至 40us,等待DHT11响应;

若 DHT11 存在,则会有80us的响应信号,此时数据引脚会被拉为低电平,否则数据引脚始终为高电平;

后面由 DHT11 控制和发送,DHT11 将数据引脚拉高 80us,并开始返回数据;

DHT11 数据引脚拉低 50us,表示开始传输数据位;

DHT11发送数据位,数据位的高低电平取决于数据位拉高的时间:

若数据位高电平持续时间为 26us~28us,则代表数据位为低 0;

若数据位高电平持续时间为 70us,则代表数据位为高 1。

详见:DHT11时序理论 .

准备工作

ESP32-C5 开发板烧录 MicroPython 固件;

电脑安装 Thonny IDE 软件,用以调试板端程序;

详见:【FireBeetle 2 ESP32-C5】 介绍、固件上传、工程测试 DF创客社区 .

硬件连接

开发板与 DHT11 模块的接线方式如下

ESP32-C5DHT11Note
Pin 2DataSignal
3.3VVCCPower
GNDGNDGround

实物连接

dht11_board_connect.jpg

 

温湿度获取

在硬件连接和固件烧录完成的基础上,通过 micropython 编程实现终端打印温湿度数据。

流程图

flowchart_dht11_print.png

代码

打开 Thonny IDE 软件,新建 dht11_print.py 工程文件,并添加如下代码

 

import time
import machine
import dht

dht11 = dht.DHT11(machine.Pin(2))   # DATA 接 GPIO2

try:
    while True:
        try:
            dht11.measure()             # 触发采样
            t = dht11.temperature()     # ℃ - temperature
            h = dht11.humidity()        # %RH - humidity
            print("Temperature: {:2.0f} °C, Humidity: {:2.0f} %".format(t, h))
        except OSError as e:
            print("DHT11 read error:", e)
        time.sleep(2)
except KeyboardInterrupt:          # 捕获 Ctrl+C
    print("Interrupted by user, exiting")

 

保存代码。

效果

打开 Thonny IDE 并连接开发板;

运行 dht11_print.py 程序,终端连续输出环境温湿度数据连,间隔为 2 秒;

dht11_print.gif

 

网页温湿度计

 

进一步实现物联网温湿度计,通过WiFi功能实现数据上传和网页显示。

 

流程图

flowchart_dht11_web.png

代码

打开 Thonny IDE 软件,新建 dht11_http.py 工程文件,并添加如下代码

 

import time, machine, dht, network, socket, json

WIFI_SSID     = "xxx"
WIFI_PASSWORD = "xxx"

dht11 = dht.DHT11(machine.Pin(2))

def connect_wifi():
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    if not wlan.isconnected():
        print('连接WiFi...')
        wlan.connect(WIFI_SSID, WIFI_PASSWORD)
        while not wlan.isconnected():
            time.sleep(0.5)
    ip = wlan.ifconfig()[0]
    print('IP:', ip)
    return ip

def read_sensor():
    try:
        dht11.measure()
        return dht11.temperature(), dht11.humidity()
    except:
        return 0, 0

def handle_api(client):
    t, h = read_sensor()
    client.send(b'HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n')
    client.send(json.dumps({'t': t, 'h': h}))

def handle_index(client):
    html = b"""HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8




 
  温湿度监控
 


 

温湿度监控


 

温度: -- °C


 

湿度: -- %



 


"""
    client.send(html)

def main():
    ip = connect_wifi()
    s = socket.socket()
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(('0.0.0.0', 80))
    s.listen(5)
    print('HTTP server on http://' + ip)

    while True:
        client, addr = s.accept()
        req = client.recv(1024)
        if b'GET /api' in req:
            handle_api(client)
        else:
            handle_index(client)
        client.close()

if __name__ == '__main__':
    main()

保存代码并运行;

效果

终端输出网页客户端地址;

dht11_http_print.JPG

在局域网浏览器中输入该地址,即可获取实时温湿度信息

dht11_web_text.jpg

动态曲线

进一步在网页端添加温湿度动态演化和历史曲线;

修改 handle_index 定义代码如下

 

def handle_index(client):
    html = b"""HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8




 
  智能温湿度监控
 


 

智能温湿度监控


 
温度: -- °C | 湿度: -- %

 




"""
    client.send(html)

 

修改完成后,保存代码并运行;

 

效果

终端输出网页客户端地址 192.168.31.101 ;

在局域网浏览器中输入该地址,即可获取实时温湿度信息及其动态演化曲线

dht11_web_curve.jpg

界面美化

为了进一步美化网页显示界面,提升观感,这里对网页配置进行参数优化,并增加 UI 设计。

数据显示

运行 Thonny IDE 软件,打开 dht11_http.py 工程文件,修改 handle_index 定义如下

 

def handle_index(client):
    html = b"""HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8




 
  智能温湿度监控
 


 


    

🌡️ 温湿度监控


    
    

      

        
🌡️

        
温度

        
--°C

      

      
      

        
💧

        
湿度

        
--%

      

    

    
    

       实时更新 | 每2秒刷新
    

 


 


"""
    client.send(html)

保存代码并运行;

效果

终端输出网页客户端地址;

在局域网浏览器中输入该地址,即可获取优化之后的网页页面,以及实时温湿度信息

dht11_web_text_optimized.jpg

 

曲线显示

运行 Thonny IDE 软件,打开 dht11_http_curve.py 工程文件,修改 handle_index 定义如下

 

def handle_index(client):
    html = b"""HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8




 
  智能温湿度监控
 


 


    

🌡️ 智能温湿度监控


    
实时环境数据监测系统

 


 

    

      
🌡️ 当前温度

      
--°C

    

    

      
💧 当前湿度

      
--%

    

 


 

    
 


 

    数据每2秒自动更新 | 显示最近5分钟数据
 





"""
    client.send(html)

保存代码并运行;

 

效果

终端输出网页客户端地址;

在局域网浏览器中输入该地址,即可获得美化后的实时温湿度信息及演化曲线

dht11_web_curve_optimized.jpg

总结

本文介绍了 FireBeetle 2 ESP32-C5 开发套件结合 WiFi 网络、HTTP 协议和 DHT11 温湿度传感器,实现物联网温湿度计的项目设计,为相关产品的开发设计和快速应用提供了参考。

代码
import time, machine, dht, network, socket, json, math

WIFI_SSID     = "D106-108"
WIFI_PASSWORD = "kobe2016"
dht11 = dht.DHT11(machine.Pin(2))

# ---------- 基础函数 ----------
def connect_wifi():
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    if not wlan.isconnected():
        print('连接WiFi...')
        wlan.connect(WIFI_SSID, WIFI_PASSWORD)
        while not wlan.isconnected():
            time.sleep(0.5)
    ip = wlan.ifconfig()[0]
    print('IP:', ip)
    return ip

def read_sensor():
    try:
        dht11.measure()
        return dht11.temperature(), dht11.humidity()
    except:
        return 0, 0

# ---------- 路由 ----------
def handle_api(client):
    t, h = read_sensor()
    t = round(t, 1)
    h = round(h, 1)
    client.send(b'HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n')
    client.send(json.dumps({'t': t, 'h': h}))

def handle_index(client):
    html = b"""HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8

<!doctype html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>智能温湿度监控</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    html, body { 
      height: 100%; 
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      color: white;
    }
    body { 
      display: flex; 
      flex-direction: column; 
      align-items: center; 
      padding: 20px;
      min-height: 100vh;
    }
    .header {
      text-align: center;
      margin-bottom: 20px;
    }
    h1 {
      font-size: 2.2rem;
      margin-bottom: 8px;
      text-shadow: 0 2px 4px rgba(0,0,0,0.3);
    }
    .subtitle {
      font-size: 1rem;
      opacity: 0.9;
      margin-bottom: 5px;
    }
    .data-cards {
      display: flex;
      gap: 20px;
      margin-bottom: 25px;
    }
    .card {
      background: rgba(255,255,255,0.15);
      backdrop-filter: blur(10px);
      border-radius: 16px;
      padding: 20px 30px;
      text-align: center;
      border: 1px solid rgba(255,255,255,0.2);
      box-shadow: 0 8px 32px rgba(0,0,0,0.1);
    }
    .card-title {
      font-size: 1rem;
      margin-bottom: 8px;
      opacity: 0.9;
    }
    .card-value {
      font-size: 2.2rem;
      font-weight: bold;
    }
    .chart-container {
      background: rgba(255,255,255,0.1);
      backdrop-filter: blur(10px);
      border-radius: 16px;
      padding: 20px;
      margin-bottom: 20px;
      border: 1px solid rgba(255,255,255,0.2);
      box-shadow: 0 8px 32px rgba(0,0,0,0.1);
      width: 100%;
      max-width: 800px;
    }
    canvas {
      width: 100% !important;
      height: 300px !important;
      border-radius: 8px;
    }
    .status {
      text-align: center;
      font-size: 0.9rem;
      opacity: 0.8;
      margin-top: 10px;
    }
    @media (max-width: 600px) {
      .data-cards { flex-direction: column; gap: 15px; }
      .card { padding: 15px 20px; }
      h1 { font-size: 1.8rem; }
      .card-value { font-size: 1.8rem; }
    }
  </style>
</head>
<body>
  <div class="header">
    <h1>🌡️ 智能温湿度监控</h1>
    <div class="subtitle">实时环境数据监测系统</div>
  </div>

  <div class="data-cards">
    <div class="card">
      <div class="card-title">🌡️ 当前温度</div>
      <div class="card-value"><span id="t">--</span>°C</div>
    </div>
    <div class="card">
      <div class="card-title">💧 当前湿度</div>
      <div class="card-value"><span id="h">--</span>%</div>
    </div>
  </div>

  <div class="chart-container">
    <canvas id="c"></canvas>
  </div>

  <div class="status">
    数据每2秒自动更新 | 显示最近5分钟数据
  </div>

<script>
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
const MAX = 150;
const data = {t:[], h:[]};

// 设置canvas尺寸
function resizeCanvas() {
  canvas.width = canvas.offsetWidth;
  canvas.height = canvas.offsetHeight;
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);

function addPoint(json){
  const {t,h} = json;
  document.getElementById('t').textContent = t.toFixed(1);
  document.getElementById('h').textContent = h.toFixed(1);
  data.t.push(t); data.h.push(h);
  if(data.t.length > MAX) { data.t.shift(); data.h.shift(); }
  draw();
}

function draw(){
  ctx.clearRect(0,0,canvas.width,canvas.height);
  const w = canvas.width, h = canvas.height;
  const pad = 40, usableH = h - 2*pad;
  
  if(data.t.length < 2) return;
  
  const minT = Math.min(...data.t) - 1, maxT = Math.max(...data.t) + 1;
  const minH = Math.min(...data.h) - 2, maxH = Math.max(...data.h) + 2;

  // 绘制网格
  ctx.strokeStyle = 'rgba(255,255,255,0.2)';
  ctx.lineWidth = 1;
  for(let i=0;i<=5;i++){
    const y = pad + i*usableH/5;
    ctx.beginPath();
    ctx.moveTo(pad, y);
    ctx.lineTo(w-pad, y);
    ctx.stroke();
  }

  // 绘制温度曲线
  ctx.strokeStyle = '#ffff6b';
  ctx.lineWidth = 3;
  ctx.beginPath();
  data.t.forEach((v,i)=>{
    const x = pad + (w-2*pad)*i/(data.t.length-1);
    const y = pad + usableH*(maxT - v)/(maxT - minT);
    if(i===0) ctx.moveTo(x,y); else ctx.lineTo(x,y);
  });
  ctx.stroke();

  // 绘制湿度曲线
  ctx.strokeStyle = '#4ecdc4';
  ctx.lineWidth = 3;
  ctx.beginPath();
  data.h.forEach((v,i)=>{
    const x = pad + (w-2*pad)*i/(data.h.length-1);
    const y = pad + usableH*(maxH - v)/(maxH - minH);
    if(i===0) ctx.moveTo(x,y); else ctx.lineTo(x,y);
  });
  ctx.stroke();

  // 绘制图例
  ctx.font = '14px Arial';
  ctx.fillStyle = '#ffff6b';
  ctx.fillRect(pad, 15, 20, 3);
  ctx.fillText('温度', pad + 25, 20);
  ctx.fillStyle = '#4ecdc4';
  ctx.fillRect(pad + 80, 15, 20, 3);
  ctx.fillText('湿度', pad + 105, 20);
}

// 初始数据加载
fetch('/api').then(r=>r.json()).then(addPoint);
setInterval(()=>fetch('/api').then(r=>r.json()).then(addPoint), 2000);
</script>
</body>
</html>
"""
    client.send(html)

# ---------- 主循环 ----------
def main():
    ip = connect_wifi()
    s = socket.socket()
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(('0.0.0.0', 80))
    s.listen(5)
    print('HTTP+Curve server on http://' + ip)
    while True:
        client, addr = s.accept()
        req = client.recv(1024)
        if b'GET /api' in req:
            handle_api(client)
        else:
            handle_index(client)
        client.close()

if __name__ == '__main__':
    main()

评论

user-avatar