ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

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

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. 硬件采集端ESP32AHT20的搭建与避坑2.1 传感器选型对比为什么是AHT20我最早用的是DHT22折腾了两天差点劝退。DHT22是单总线协议对时序要求极严MicroPython这种解释型环境很容易出现读取失败、返回NaN的情况而且它和DHT11一样两次读取间隔至少要2秒想提高采样率很尴尬。后来换成AHT20整个世界清净了。它是I2C接口标准库直接支持读取稳定而且可以做到100ms采一次也不怕。下面这个表是我实测的选型感受供大家参考传感器接口精度温度/湿度采样间隔限制实测稳定度DHT11单总线±2℃ / ±5%RH≥1s较差容易丢失DHT22单总线±0.5℃ / ±2%RH≥2s一般偶发NaNAHT20I2C±0.3℃ / ±2%RH无严格限制稳定SHT30I2C±0.3℃ / ±2%RH无严格限制稳定但贵一点如果你手上已经有DHT22也可以继续用但建议把读取间隔放宽到5秒以上同时在代码里做好读取失败重试。2.2 ESP32固件端的采集和发送我用的MicroPython环境。ESP32接AHT20接线很简单AHT20的VCC接3.3VGND接GNDSDA接GPIO21SCL接GPIO22。有的AHT20模块板上自带上拉电阻不用额外接如果是裸芯片SDA和SCL需要各接一个4.7kΩ上拉电阻到3.3V否则I2C通不通全靠运气。固件代码不长核心逻辑是初始化I2C然后死循环采集并输出from machine import Pin, I2C import aht20 import time # ESP32 默认I2C引脚SDAGPIO21, SCLGPIO22 i2c I2C(0, sclPin(22), sdaPin(21), freq100000) 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( portCOM3, # Windows下是COM口Linux/macOS下一般是/dev/ttyUSB0 baudrate115200, timeout1 ) while True: line ser.readline().decode(utf-8, errorsignore).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(timespecseconds) print(now, temp, humi)这里有三处细节值得注意。第一timeout1必须设置不然串口没有数据时readline会一直阻塞程序看起来就像卡死了。第二errorsignore用来忽略解码错误串口偶尔出现半个字节的数据很常见直接报错会让程序崩溃。第三上位机一定要自己打时间戳不要信硬件端的时间因为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], errorscoerce) df[humi] pd.to_numeric(df[humi], errorscoerce) # 物理范围校验 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, indexFalse)为什么突变检测用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, inplaceTrue) # 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_modeWAL;) 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_modeWAL是我后来才加的关键优化。默认的SQLite日志模式在写入时会把整个数据库锁住读取就会被阻塞WAL模式允许读和写并发数据采集程序边写、查询脚本边读就不会互相卡住了。4.4 查询和数据导出数据存进去是为了用。我写了一个查询函数按时间范围取数并导出CSVimport 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], colortab:red, labelTemperature) ax1.set_ylabel(Temperature (℃), colortab:red) ax2 ax1.twinx() ax2.plot(df[sample_time], df[humi], colortab:blue, labelHumidity) ax2.set_ylabel(Humidity (%RH), colortab:blue) plt.title(Room Environment Monitor) plt.xticks(rotation45) plt.tight_layout() plt.savefig(env_curve.png, dpi120)如果数据点是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传感器、加个告警推送都是水到渠成的事。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进