【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低电平时序;
工作流程
主机将 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-C5 | DHT11 | Note |
---|---|---|
Pin 2 | Data | Signal |
3.3V | VCC | Power |
GND | GND | Ground |
实物连接

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

代码
打开 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 秒;

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

代码
打开 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()
保存代码并运行;
效果
终端输出网页客户端地址;
在局域网浏览器中输入该地址,即可获取实时温湿度信息

动态曲线
进一步在网页端添加温湿度动态演化和历史曲线;
修改 handle_index 定义代码如下
def handle_index(client):
html = b"""HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
智能温湿度监控
"""
client.send(html)
修改完成后,保存代码并运行;
效果
终端输出网页客户端地址 192.168.31.101 ;
在局域网浏览器中输入该地址,即可获取实时温湿度信息及其动态演化曲线

界面美化
为了进一步美化网页显示界面,提升观感,这里对网页配置进行参数优化,并增加 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
🌡️ 温湿度监控
● 实时更新 | 每2秒刷新
"""
client.send(html)
保存代码并运行;
效果
终端输出网页客户端地址;
在局域网浏览器中输入该地址,即可获取优化之后的网页页面,以及实时温湿度信息

曲线显示
运行 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
🌡️ 智能温湿度监控
数据每2秒自动更新 | 显示最近5分钟数据
"""
client.send(html)
保存代码并运行;
效果
终端输出网页客户端地址;
在局域网浏览器中输入该地址,即可获得美化后的实时温湿度信息及演化曲线

总结
本文介绍了 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()
评论