news 2026/9/2 1:53:16

Python+ESP32温湿度监测实战:从AHT20采集到SQLite存储

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python+ESP32温湿度监测实战:从AHT20采集到SQLite存储

简介:该压缩包是一套Python温湿度数据测量、处理与数据库存储的实战资源,适合学习物联网数据链路的开发者。项目覆盖硬件通信、数据解析、清洗校验到数据库写入的流程,主控、库连接、CRC校验、数据上报等模块齐全。压缩包共十四个文件,含八个Python源码、五个编译中间文件及一个持久化数据文件,大小约十一KB,结构轻量。

项目中通过数据处理库处理缺失值与异常值,用数据库驱动参数化插入并兼顾事务一致性,还给出温湿度曲线展示思路,并涉及数据库表结构与事务设计,能直观理解完整工程链路。模块职责清晰,便于修改连接参数或扩展传感器,可作为课程设计起点。已有八百二十五人学习下载,适合想结合代码学习Python物联网应用的中级学习者。 前阵子帮朋友打理一个小温室,顺便把自己工位阳台的环境也纳入监控,干脆用Python做了一套温湿度数据测量与处理及数据库存储的小系统。硬件端用ESP32接AHT20传感器,5秒采集一次温湿度,通过串口发给电脑;电脑端Python脚本负责接收、清洗、入库,最后还能画趋势图、按时间段查询历史记录。这套东西做下来,最大的体会是:读传感器很简单,真正的坑都在数据处理、存储和长期稳定运行上。

如果你是刚开始接触Python数据采集、想给家里或工位搭一套环境监控,或者正在做相关课程设计、毕设,这篇内容基本可以照着复现。我会把硬件选型、固件采集、Python接收清洗、数据库建表写入、可视化和常见问题都过一遍,重点讲为什么这么做,以及哪些地方容易翻车。

1. 项目拆解:先摸清整条链路再动手

1.1 这系统到底要解决什么问题

很多刚接触温湿度测量的朋友,第一反应是拿个传感器接上Arduino或者ESP32,串口监视器里看到温度25.3℃、湿度56.8%就完事了。但实测数据一旦离开设备,价值就少了一大半——没有时间戳、没有历史存档,你想回看"昨天凌晨阳台湿度是不是超标了"根本无从下手。

所以这套系统要解决的不只是"测量",而是三个层次的问题:

  • 测量:用可靠的传感器按固定频率读取温湿度。
  • 处理:把原始数据里的噪声、异常值、乱码清洗掉,统一成规范格式。
  • 存储与查询:把清洗后的数据落库,支持按时间范围、设备维度查询,为后续分析或可视化提供数据源。

只有把这三层打通,才算一个真正可用的数据采集项目。这也是"Python温湿度数据测量与处理及数据库存储"这个项目名的完整含义。

1.2 方案选型:ESP32 + AHT20 + Python + SQLite

硬件端我选了ESP32开发板,原因很直接:便宜、支持WiFi和蓝牙、引脚多、MicroPython和Arduino都能跑。而且它自带串口转USB芯片,插上电脑就能被识别成串口设备,不需要额外买下载器。

传感器选了AHT20,而不是更常见的DHT22。一是AHT20走I2C接口,读数稳定、不需要严格时序;二是它的温度精度±0.3℃、湿度精度±2%RH,日常环境监控完全够用;三是价格也就几块钱,损坏了换新成本低。

上位机用Python,这个没太多悬念。pyserial负责读串口,pandas负责清洗,sqlite3或SQLAlchemy负责入库,matplotlib负责绘图,生态太成熟了。数据库起步用SQLite,零配置、单文件、够用;等以后要多人、多设备并发访问,再平滑迁到MySQL或PostgreSQL。

1.3 数据从采集到入库的完整流向

整个系统的数据流向,用一句话说就是:传感器 → 单片机 → 串口 → Python脚本 → 清洗 → 数据库 → 查询/可视化。

ESP32上电后循环采集AHT20数据,通过串口按固定格式发送字符串;电脑端Python脚本用pyserial监听串口,读到一行数据就解析成温度、湿度两个浮点数,加上当前系统时间;接着做范围校验和异常过滤,通过的就批量写入SQLite;最后,查询脚本从数据库取数画图或导出CSV。链条不复杂,但每一环都有需要注意的细节。

2. 硬件采集端:ESP32+AHT20的搭建与避坑

2.1 传感器选型对比:为什么是AHT20

我最早用的是DHT22,折腾了两天差点劝退。DHT22是单总线协议,对时序要求极严,MicroPython这种解释型环境很容易出现读取失败、返回NaN的情况,而且它和DHT11一样,两次读取间隔至少要2秒,想提高采样率很尴尬。

后来换成AHT20,整个世界清净了。它是I2C接口,标准库直接支持,读取稳定,而且可以做到100ms采一次也不怕。下面这个表是我实测的选型感受,供大家参考:

传感器接口精度(温度/湿度)采样间隔限制实测稳定度
DHT11单总线±2℃ / ±5%RH≥1s较差,容易丢失
DHT22单总线±0.5℃ / ±2%RH≥2s一般,偶发NaN
AHT20I2C±0.3℃ / ±2%RH无严格限制稳定
SHT30I2C±0.3℃ / ±2%RH无严格限制稳定,但贵一点

如果你手上已经有DHT22,也可以继续用,但建议把读取间隔放宽到5秒以上,同时在代码里做好读取失败重试。

2.2 ESP32固件端的采集和发送

我用的MicroPython环境。ESP32接AHT20,接线很简单:AHT20的VCC接3.3V,GND接GND,SDA接GPIO21,SCL接GPIO22。有的AHT20模块板上自带上拉电阻,不用额外接;如果是裸芯片,SDA和SCL需要各接一个4.7kΩ上拉电阻到3.3V,否则I2C通不通全靠运气。

固件代码不长,核心逻辑是初始化I2C,然后死循环采集并输出:

from machine import Pin, I2C import aht20 import time # ESP32 默认I2C引脚:SDA=GPIO21, SCL=GPIO22 i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=100000) sensor = aht20.AHT20(i2c) while True: temp = sensor.temperature humi = sensor.relative_humidity # 输出固定格式:温度,湿度 print("{:.2f},{:.2f}".format(temp, humi)) time.sleep(5)

这里有几个关键点。第一,波特率建议设成115200,只要USB线质量没问题,传输稳定且速度快。第二,输出格式越简单越好,一行一个数据,逗号分隔,这样上位机解析最容易。第三,采样间隔5秒是我实际使用的值,既能捕捉环境变化趋势,又不会让数据库膨胀太快——一天86400秒,5秒一条就是17280条,SQLite完全扛得住,但要是1秒一条,一年下来600多万条,查询就会变慢。

2.3 硬件端最容易翻车的几个地方

第一个坑是供电。ESP32的3.3V稳压能力有限,如果传感器模块上还带OLED屏或其他外设,电流一大就会导致AHT20读数漂移甚至I2C通信失败。我遇到过温度突然跳到80℃的情况,排查半天发现是USB口供电不足,换了个带屏蔽的USB线就好了。

第二个坑是I2C地址冲突。AHT20默认地址是0x38,如果同一个I2C总线上挂了多个相同地址的传感器,需要用TCA9548A这类I2C多路复用器,或者换不同地址的传感器型号。别以为地址冲突是小概率事件,我后来加第二个AHT20时就撞上了。

第三个坑是串口输出不能被其他打印干扰。MicroPython的print语句默认都会走同一个串口,你如果在采集循环里加了很多调试print,上位机解析就会乱。建议正式运行前把调试输出全部删掉,只保留数据行。

3. Python端数据接收与清洗

3.1 用pyserial监听串口并解析数据

硬件端数据发出来了,电脑端要接住。我用的是pyserial,读取逻辑很直接:

import serial import time from datetime import datetime ser = serial.Serial( port='COM3', # Windows下是COM口,Linux/macOS下一般是/dev/ttyUSB0 baudrate=115200, timeout=1 ) while True: line = ser.readline().decode('utf-8', errors='ignore').strip() if not line: continue try: temp_str, humi_str = line.split(',') temp = float(temp_str) humi = float(humi_str) except ValueError: continue # 解析失败就丢掉这一行 now = datetime.now().isoformat(timespec='seconds') print(now, temp, humi)

这里有三处细节值得注意。第一,timeout=1必须设置,不然串口没有数据时readline会一直阻塞,程序看起来就像卡死了。第二,errors='ignore'用来忽略解码错误,串口偶尔出现半个字节的数据很常见,直接报错会让程序崩溃。第三,上位机一定要自己打时间戳,不要信硬件端的时间,因为ESP32一般没接RTC模块,掉电后时间就归零了。

3.2 异常值清洗:别让脏数据进数据库

串口数据进到Python后,第一件事不是急着入库,而是清洗。我总结了四个必须处理的脏数据源:

  • 解析失败的行:传感器上电瞬间输出不完整,或USB串口线松动导致半截数据。
  • 超出物理范围的值:温度-40℃到85℃、湿度0%到100%,超出这个范围的直接判为无效。
  • 突变异常值:环境温度不可能在5秒内跳10℃,这类突变一般是传感器受干扰或瞬间供电不稳。
  • 重复值/空值:传感器卡死时可能连续输出同一组数据,适当去重能减少脏数据。

清洗我直接用pandas处理,逻辑写起来清晰。先把串口收到的原始数据追加到一个CSV文件,然后定期用pandas做批处理:

import pandas as pd df = pd.read_csv('raw_env.csv', names=['timestamp', 'temp', 'humi']) # 转数值,无法转换的置为NaN df['temp'] = pd.to_numeric(df['temp'], errors='coerce') df['humi'] = pd.to_numeric(df['humi'], errors='coerce') # 物理范围校验 df = df[(df['temp'] > -10) & (df['temp'] < 60)] df = df[(df['humi'] >= 5) & (df['humi'] <= 99)] # 突变检测:和上一条记录相比,温度变化超过±5℃就剔除 df['temp_diff'] = df['temp'].diff().abs() df = df[df['temp_diff'] < 5] df = df.dropna(subset=['temp', 'humi']) df.to_csv('clean_env.csv', index=False)

为什么突变检测用diff()?因为正常环境变化是渐变的,如果传感器在5秒内从25℃跳到35℃,不是传感器坏了就是环境出了极端情况,但无论如何这种数据对分析没有意义,剔除最安全。

3.3 时间戳、去重和重采样

清洗时还有一个容易被忽略的问题:同一秒内可能收到多条数据。ESP32大约5秒发一条,但由于串口缓冲和电脑端处理延迟,偶尔会出现几行数据时间戳相同的情况。解决办法很简单,入库前按timestamp去重,保留第一条即可。

重采样是我后期才加的功能。原始数据是5秒一条,画24小时曲线时点太多,图看起来很密;分析时也更关心分钟级、小时级趋势。所以我用pandas做了一步入库前或出图前的重采样,把5秒数据聚合为1分钟均值:

df = pd.read_csv('clean_env.csv', parse_dates=['timestamp']) df.set_index('timestamp', inplace=True) # 1分钟重采样,取均值 minute_df = df.resample('1min').mean().dropna() minute_df.to_csv('minute_env.csv')

重采样的意义不只是让图好看,更重要的是能平滑传感器白噪声。AHT20虽然稳定,但单次读数也有±0.3℃的误差,聚合后得到的是更接近真实环境的平均值。

4. 数据库存储:表结构设计与写入优化

4.1 选SQLite还是MySQL

先说结论:单机、单用户、数据量在百万条以内,无脑用SQLite;以后要多台电脑或多人同时查询、写入,再考虑MySQL。我这套系统一开始就用的SQLite,跑了三个月,文件涨到大概200MB,查询仍然很快。

两者的核心差异在于并发和部署复杂度。SQLite是文件型数据库,写入时整个数据库文件会被锁定,并发写多会报database is locked;但它零配置、单文件、备份方便,作为个人监控系统的存储层非常合适。MySQL需要装服务、建用户、配权限,前期成本高,但能扛住真正的多客户端并发。

对比项SQLiteMySQL
部署复杂度零配置,嵌入式需要服务和权限管理
并发写入弱,单写者强,支持并发
单文件备份直接复制文件即可需要mysqldump导出
适合场景个人监控、单机应用团队共享、Web服务

4.2 建表SQL与索引设计

数据库表设计遵循一个原则:能新增字段就不要改字段类型。我的核心表结构如下:

CREATE TABLE IF NOT EXISTS env_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, device_id TEXT NOT NULL, temp REAL NOT NULL, humi REAL NOT NULL, sample_time TEXT NOT NULL ); CREATE INDEX IF NOT EXISTS idx_env_time ON env_log(sample_time);

这里有几个设计细节。第一,device_id是必须的,哪怕现在只有一个设备,以后万一加第二个传感器、第二个房间,没有这个字段就得改表,很痛苦。第二,sample_time用TEXT存ISO格式时间,是因为在SQLite里它排序和比较都友好,没必要转成Unix时间戳。第三,索引建在sample_time上,因为最常见的查询是"某段时间内的温湿度曲线",这个索引能把查询速度从全表扫描变成索引查找。

4.3 批量写入与连接管理

写入方式是最容易优化也最容易被忽视的部分。新手最常见的写法是每来一条数据就connect一次、commit一次,这样有两个问题:串口数据每秒都好几条,频繁commit会拖慢脚本;更严重的是SQLite在频繁打开关闭文件时容易累积锁等待。

我实际用的是攒一批、写一批的策略:

import sqlite3 DB_PATH = 'env.db' def batch_insert(rows): conn = sqlite3.connect(DB_PATH) try: conn.execute("PRAGMA journal_mode=WAL;") cur = conn.cursor() cur.executemany( "INSERT INTO env_log(device_id, temp, humi, sample_time) VALUES(?,?,?,?)", rows ) conn.commit() finally: conn.close() # 攒够50条再入库 buffer = [] if len(buffer) >= 50: batch_insert(buffer) buffer.clear()

开启PRAGMA journal_mode=WAL是我后来才加的关键优化。默认的SQLite日志模式在写入时会把整个数据库锁住,读取就会被阻塞;WAL模式允许读和写并发,数据采集程序边写、查询脚本边读就不会互相卡住了。

4.4 查询和数据导出

数据存进去是为了用。我写了一个查询函数,按时间范围取数并导出CSV:

import sqlite3 import pandas as pd def query_env(start, end): conn = sqlite3.connect('env.db') sql = """ SELECT sample_time, temp, humi FROM env_log WHERE sample_time BETWEEN ? AND ? ORDER BY sample_time """ df = pd.read_sql_query(sql, conn, params=(start, end)) conn.close() return df

查询出的DataFrame可以直接给matplotlib画图,也可以导成Excel。实测百万条数据量下,这个查询一般都在几百毫秒内返回,完全够用。

5. 可视化与工程化部署

5.1 matplotlib快速画温湿度曲线

数据入库后,最直观的是画图。我用matplotlib画双Y轴曲线,左边温度、右边湿度,这样两条曲线不会因为量纲不同而叠在一起:

import matplotlib.pyplot as plt df = query_env('2025-06-01 00:00:00', '2025-06-02 00:00:00') df['sample_time'] = pd.to_datetime(df['sample_time']) fig, ax1 = plt.subplots(figsize=(12, 5)) ax1.plot(df['sample_time'], df['temp'], color='tab:red', label='Temperature') ax1.set_ylabel('Temperature (℃)', color='tab:red') ax2 = ax1.twinx() ax2.plot(df['sample_time'], df['humi'], color='tab:blue', label='Humidity') ax2.set_ylabel('Humidity (%RH)', color='tab:blue') plt.title('Room Environment Monitor') plt.xticks(rotation=45) plt.tight_layout() plt.savefig('env_curve.png', dpi=120)

如果数据点是5秒一条、画24小时图,曲线会非常密,我建议先做分钟级重采样再画图。另外,savefig保存图片比用plt.show()更适合无人值守的运行环境,后者在没有显示器的Linux服务器上会直接报错。

5.2 定时调度、断线重连与守护运行

数据采集脚本不能只在前台跑,它需要7×24小时稳定运行。我采用的是最简单的方案:在采集脚本里加一个断线重连机制,遇到串口被拔掉或传感器无响应时自动重试,而不是直接退出。

while True: try: # 连接串口并进入数据读取循环 pass except serial.SerialException as e: print("串口异常,5秒后重连", e) time.sleep(5)

如果设备断电、USB口松动,串口连接会断开,程序会捕捉到SerialException,等待5秒后重新连接,serial.Serial重新初始化,比直接崩溃退出强得多。如果连串口断开都解决不了,还可以写一个systemd服务或Windows计划任务,开机自启、异常退出自动拉起。

5.3 Python打包成exe与Web查询

如果你不想让目标电脑装Python环境,可以用PyInstaller把脚本打包成exe。我打包过包含pyserial、pandas、sqlite3的采集程序,命令很简单:

pyinstaller -F -w collector.py

-F表示打包成单文件,-w表示不显示控制台窗口。需要注意的是,pandas打包后体积很大,大概60MB起步,因为要带上底层的numpy等依赖,但换来的是目标机器不用装任何运行环境,适合部署到没有Python的Windows小主机上。

如果想在手机上随时查看数据,我在数据库基础上加了一个极简Flask接口,返回JSON格式的最新温湿度,手机浏览器直接访问。这个方法对个人项目足够了,没必要上来就上物联网平台、消息队列那套重型架构。

6. 常见问题排查与经验速记

6.1 我踩过的坑和对应解法

我把实际运行中遇到最多的几个问题整理成了表格,方便排查:

现象可能原因解决方法
串口打不开,报PermissionError串口被其他程序占用,或驱动没装关掉串口监视器/其他串口工具,重插USB线,检查设备管理器
串口能打开但读不到数据USB线是纯充电线,没有数据线换一根带数据传输的USB线
串口数据乱码波特率不匹配确认ESP32和Python脚本波特率一致
AHT20读数一直为0或NaNI2C接线错误,或缺少上拉电阻检查SDA/SCL接线和4.7kΩ上拉电阻,用I2C扫描脚本确认设备地址
湿度读出来是负数或大于100传感器受潮或损坏放在干燥环境静置24小时,仍异常就更换传感器
SQLite报database is locked写入过于频繁,或读写在抢占锁开启WAL模式,改为批量写入,减少commit频率
数据在时间轴上出现断档电脑休眠或采集脚本异常退出关闭自动休眠,给脚本加断线重连和开机自启

6.2 几点实际操作心得

最后聊几点没有写在任何文档里的经验。

第一,不要一开始就追求高采样频率。5秒采样和1秒采样在趋势分析上几乎没有差别,但数据量差了5倍,数据库膨胀和查询变慢是必然的。先跑起来,确认链路稳定,再根据需求调整采样率。

第二,时间戳一定要用上位机的时间,不要信设备端。不光是ESP32,很多单片机没有RTC,掉电重启后时间全是错的。数据一旦带上错误的时间戳,后面所有分析都会跟着错。

第三,数据库备份比代码重要。代码丢了可以重写,三个月的环境数据丢了,那就是真的没了。我用的SQLite是单文件,直接定时复制一份到网盘或另一台机器,成本极低但安全感极高。

第四,如果条件允许,给传感器做个最简单的百叶箱或遮阳罩。阳光直射会造成温湿度读数偏高几度,这个偏差不是代码能修掉的,只能从物理层面解决。

这套系统从打样到现在跑了几个月,稳定度已经达到预期。技术上没有特别高深的东西,难点全在于把采集、清洗、存储、查询这条链路想清楚,把每一环的异常情况处理好。照着这个思路扩展,后续接个PM2.5传感器、加个告警推送,都是水到渠成的事。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/2 1:51:56

MFC集成SVG显示:lunasvg + GDI+ 实战指南

简介&#xff1a;面向需要在MFC&#xff08;Microsoft Foundation Classes&#xff09;框架下解析SVG矢量图并显示到视图窗口的C开发者&#xff0c;这套完整示例工程基于VS2012实现SVG文件的XML解析、GDI绘图与MFC视图框架的整合&#xff0c;涉及XML库用法、重写CView的OnDraw函…

作者头像 李华
网站建设 2026/9/2 1:51:37

TSW-F4读写器随机软件安装指南:从RAR解压到驱动调试全流程

简介&#xff1a;这是一份德生TSW-F4 U系列社保卡读写器的随机软件资源包&#xff0c;面向医疗机构、社保服务机构和企事业单位信息化人员&#xff0c;重点解决社保卡读取、验证及信息处理中的驱动安装、联调配置和二次开发问题。压缩包共33个文件、2.27MB&#xff0c;内含动态…

作者头像 李华
网站建设 2026/9/2 1:51:24

基于ICA与DVA特征的锂电池SOH与RUL预测实战

简介&#xff1a;面向锂离子电池健康管理研究人员与电池管理系统开发者&#xff0c;这套基于增量容量分析&#xff08;ICA&#xff09;与差分电压分析&#xff08;DVA&#xff09;的完整方法资料&#xff0c;围绕电池SOH与RUL预测全流程展开。内容涵盖原始充放电数据的预处理与…

作者头像 李华
网站建设 2026/9/2 1:51:16

NocoBase零代码平台实战:从Docker部署到博客后台搭建

各位开发者朋友&#xff0c;大家好。此前我们在系列教程中初步认识了 NocoBase 这款开源零代码平台&#xff0c;很多朋友留言问到底怎么把它跑起来、怎么用它搭一个真实可用的业务系统。本期教程就围绕“安装”和“使用”两个核心环节&#xff0c;以搭建一个简单博客后台为案例…

作者头像 李华
网站建设 2026/9/2 1:50:56

浏览器音乐应用核心:Web Audio精准节拍循环与lookahead音频调度

如果你看过 Incredibox 的二创社区&#xff0c;大概率见过类似标题&#xff1a;[Incredibox] Simon Treatment、[Incredibox] XXX Treatment。表面上看&#xff0c;这不过是一个音乐小游戏的同人混音视频&#xff0c;几个小人站在舞台上&#xff0c;作者拖拖拽拽&#xff0c;一…

作者头像 李华
网站建设 2026/9/2 1:47:50

国产M4 MCU N32G455xx上手:资源包、外设与调试全攻略

简介&#xff1a;面向嵌入式开发者的国民技术N32G455xx系列微控制器资源包&#xff0c;版本为V3.0.0&#xff0c;主要服务工业自动化、消费电子、汽车电子、智能家居与智能电表等场景。压缩包整体约110.43MB&#xff0c;内容围绕V3.0.0版本展开&#xff0c;从资源说明看&#x…

作者头像 李华