1. 项目缘起:一个被“遗忘”的测温需求
去年冬天,我负责的一个小型线下活动场地,因为防疫要求,需要在入口处安排专人进行体温检测。这活儿听起来简单,但实际操作起来问题一大堆:手持测温枪需要人一直举着,数据全靠手抄,遇到体温异常者,还得临时找人、打电话层层上报,流程繁琐,效率低下,而且记录容易丢失。更头疼的是,这个岗位需要人一直盯着,枯燥且容易出错。
当时我就想,能不能做个“无人值守”的智能测温点?核心需求很明确:非接触、自动测量、异常自动告警、数据可追溯。市面上成熟的商业解决方案要么太贵,要么功能臃肿,不适合我们这种小场景。于是,我把目光投向了手边的树莓派(Raspberry Pi)和一些传感器模块。最终,我用树莓派、MLX90614红外测温传感器和Python,搭建了一个成本不到300元、能自动发送邮件通知的防疫测温装置。它安静地挂在入口处,有人经过时自动测温,体温正常绿灯亮,体温异常则红灯闪烁并立即向指定邮箱发送告警邮件,所有数据连同时间戳都记录在本地CSV文件里。
这个项目并不复杂,但完美解决了“最后一米”的自动化监控问题。今天,我就把这个从硬件选型、电路连接、代码编写到实际部署的完整过程拆解出来。无论你是想复现一个类似装置,还是学习如何将树莓派、Python和硬件传感器结合解决实际问题,这篇内容都会给你提供清晰的路径和避坑指南。
2. 核心硬件选型与电路连接:为什么是它们?
一套稳定可靠的硬件是项目基石。这个项目的硬件清单非常精简,但每一件都经过深思熟虑。
2.1 主控板:Raspberry Pi 4B/3B+ 的权衡
主控板首选树莓派,这是创客领域的“瑞士军刀”。我手头有Pi 4B和更老的Pi 3B+。对于这个项目,两者皆可,但有些细微差别。
- 树莓派4B:性能更强,有USB 3.0接口,如果未来想扩展摄像头进行人脸检测或接入更复杂的应用,它有充足的余量。但功耗和发热也略高。
- 树莓派3B+:性能完全足够驱动一个传感器和运行Python脚本,功耗更低,更稳定。对于单纯的测温+通知任务,它是性价比更高的选择。
注意:如果你用的是树莓派Zero系列,虽然更小巧便宜,但需要额外购买USB Hub或焊接排针来连接传感器,对新手不友好,且GPIO(通用输入输出)引脚电流驱动能力较弱,直接驱动LED需谨慎。因此,建议初学者从3B+或4B开始。
最终我选择了树莓派3B+,理由很简单:项目功能单一,稳定性和低功耗优先,且3B+的GPIO驱动能力足够。
2.2 测温传感器:MLX90614 的非接触原理
这是项目的核心“眼睛”。为什么选MLX90614,而不是更便宜的DS18B20(接触式)或其他红外模块?
- 非接触安全:防疫测温必须非接触,避免交叉感染。MLX90614通过检测人体辐射的红外能量来测温,距离通常在2-5厘米内效果最佳。
- 精度尚可:在室温环境下,针对人体体温范围(35-42°C),其典型精度可达±0.5°C,完全满足防疫初筛要求。它出厂前已校准,无需用户再次校准。
- 数字接口:它通过I2C总线与树莓派通信,只需要两根数据线(SDA, SCL),编程简单,抗干扰能力比模拟传感器强。
MLX90614有两种测量值:物体温度(TObject)和环境温度(TAmbient)。我们关心的是物体温度,即人的体表温度。需要注意的是,体表温度通常比腋下或口腔等核心体温低1-2°C,所以我们的告警阈值要相应调整(例如,设置体表温度≥37.3°C为异常)。
2.3 辅助元件:LED与电阻的“配角”戏份
- LED指示灯:我用了一个绿色LED和一个红色LED。绿色代表体温正常,红色代表体温异常。这是给人最直观的反馈。如果没有LED,当邮件发送延迟或失败时,现场人员无法立即知晓结果。
- 220Ω 限流电阻:这是保护LED和树莓派GPIO口的关键!树莓派GPIO引脚最大输出电流约16mA,而LED的工作电流一般在20mA左右。直接连接会烧毁LED或损坏GPIO引脚。串联一个220Ω的电阻,可以将电流限制在安全范围内(约 (3.3V - LED压降) / 220Ω ≈ 10mA)。
- 杜邦线:用于连接,建议使用母对母的杜邦线。
- 面包板:用于临时搭建和测试电路,非常方便。
2.4 电路连接实战:GPIO引脚图就是地图
连接电路前,务必找到你的树莓派型号的GPIO引脚图。树莓派3B+/4B的40针GPIO引脚排列是标准的。以下是具体的连接方案:
| 元件 | 连接至树莓派GPIO引脚 | 说明 |
|---|---|---|
| MLX90614 VCC | Pin 1 (3.3V) | 绝对不要接5V!会烧坏传感器。 |
| MLX90614 GND | Pin 6 (GND) | 接地。 |
| MLX90614 SDA | Pin 3 (GPIO2, SDA) | I2C数据线。 |
| MLX90614 SCL | Pin 5 (GPIO3, SCL) | I2C时钟线。 |
| 绿色LED 长脚(阳极) | 通过220Ω电阻接 Pin 11 (GPIO17) | 需串联电阻。 |
| 绿色LED 短脚(阴极) | Pin 9 (GND) | 直接接地。 |
| 红色LED 长脚(阳极) | 通过220Ω电阻接 Pin 13 (GPIO27) | 需串联电阻。 |
| 红色LED 短脚(阴极) | Pin 14 (GND) | 直接接地。 |
连接关键点:
- 电源确认:MLX90614务必接3.3V(Pin 1)。接5V(Pin 2或4)瞬间报废。
- 电阻位置:电阻串联在LED阳极和GPIO引脚之间,或串联在LED阴极和GND之间均可。我习惯放在阳极侧。
- LED极性:长脚是阳极(+),接GPIO;短脚是阴极(-),接GND。接反了不亮。
- 共地:确保传感器和LED的GND都连接到树莓派的GND引脚上,形成共同的参考零电位。
连接好后,硬件部分就准备好了。接下来是让树莓派“认识”这些硬件。
3. 软件环境搭建与I2C通信调试
树莓派默认系统是Raspberry Pi OS(以前叫Raspbian)。我们需要开启I2C接口,并安装必要的Python库。
3.1 系统配置:开启硬件接口的“开关”
在终端中执行以下命令:
sudo raspi-config这是一个图形化配置工具。依次选择:
Interface Options->I2C->Yes启用I2C驱动。- 同样在
Interface Options里,可以确认SSH已启用,方便远程操作。
退出raspi-config后,建议更新一下软件包列表并升级现有软件:
sudo apt update sudo apt upgrade -y然后安装Python包管理工具pip和I2C工具:
sudo apt install python3-pip i2c-tools -y3.2 检测传感器:与MLX90614“握手”
安装i2c-tools后,我们可以用它来探测连接到I2C总线上的设备。首先,你需要知道你的树莓派使用的是哪个I2C总线。树莓派3B+/4B通常有两个I2C总线:i2c-0和i2c-1。GPIO引脚上的SDA/SCL对应的是i2c-1。
运行探测命令:
sudo i2cdetect -y 1如果看到类似下面的输出,就说明连接成功:
0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- 5A --这里的0x5A就是MLX90614默认的I2C设备地址。如果你看到的是--,请检查:1) 接线是否正确(特别是VCC和GND);2) I2C是否已启用;3) 传感器是否完好。
3.3 Python库安装:装备我们的“武器库”
我们需要两个核心Python库:
smbus2或smbus:用于通过Python代码进行I2C通信。smbus2是更新、更活跃的版本。MLX90614库:有现成的库封装了与MLX90614通信的底层协议,让我们可以用一两行代码就读出温度值。当然,你也可以直接用smbus2根据数据手册读写寄存器,但用库更快捷。
安装命令:
pip3 install smbus2 pip3 install PyMLX90614这里注意,库名可能是PyMLX90614或mlx90614,具体取决于作者。安装前可以pip3 search mlx90614搜索一下。我使用的是PyMLX90614。
至此,软硬件环境就绪。我们可以写一个简单的测试脚本,验证传感器和LED是否工作正常。
4. 核心代码实现:从测温到邮件通知的完整链路
整个项目的逻辑流非常清晰:初始化 -> 循环测温 -> 判断 -> 本地记录 -> 控制LED -> 条件触发邮件告警。我们将分模块构建这个系统。
4.1 基础测温与LED控制
首先,我们创建一个名为temperature_monitor.py的Python脚本,实现最基础的功能。
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import time import board import busio import adafruit_mlx90614 import RPi.GPIO as GPIO from datetime import datetime # 1. GPIO引脚设置 LED_GREEN = 17 LED_RED = 27 GPIO.setmode(GPIO.BCM) # 使用BCM编号模式 GPIO.setup(LED_GREEN, GPIO.OUT) GPIO.setup(LED_RED, GPIO.OUT) # 初始化时关闭所有LED GPIO.output(LED_GREEN, GPIO.LOW) GPIO.output(LED_RED, GPIO.LOW) # 2. 初始化MLX90614传感器 # 创建I2C总线对象,SDA=GPIO2, SCL=GPIO3 i2c = busio.I2C(board.SCL, board.SDA) mlx = adafruit_mlx90614.MLX90614(i2c) # 3. 体温异常阈值(体表温度,单位:摄氏度) FEVER_THRESHOLD = 37.3 # 4. 数据记录函数 def log_temperature(temp_obj, status): timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") log_entry = f"{timestamp}, {temp_obj:.2f}, {status}\n" with open("/home/pi/temperature_log.csv", "a") as f: f.write(log_entry) print(log_entry.strip()) # 同时在终端打印 # 5. LED控制函数 def set_leds(normal=False, warning=False): GPIO.output(LED_GREEN, GPIO.HIGH if normal else GPIO.LOW) GPIO.output(LED_RED, GPIO.HIGH if warning else GPIO.LOW) try: print("防疫测温系统启动...按Ctrl+C终止。") while True: # 读取物体温度(目标温度) temp_object = mlx.object_temperature # 也可以读取环境温度:mlx.ambient_temperature status = "NORMAL" if temp_object >= FEVER_THRESHOLD: status = "FEVER" set_leds(normal=False, warning=True) # 这里后续会加入邮件发送 print(f"警告!检测到异常体温:{temp_object:.2f}°C") else: set_leds(normal=True, warning=False) # 记录数据 log_temperature(temp_object, status) # 每秒测量一次 time.sleep(1) except KeyboardInterrupt: print("\n程序被用户中断。") finally: # 清理GPIO资源,非常重要! GPIO.cleanup() print("GPIO已清理,程序退出。")代码要点解析:
GPIO.setmode(GPIO.BCM):设置GPIO引脚编号模式为BCM。这是树莓派上最常用的模式,引脚号对应的是芯片的GPIO编号,而不是物理引脚顺序。adafruit_mlx90614:这是一个由Adafruit维护的库,用法非常简洁。你需要先pip3 install adafruit-circuitpython-mlx90614。如果安装失败,可能需要先安装一些系统依赖:sudo apt install python3-pip libatlas-base-dev。finally块中的GPIO.cleanup():这是良好编程习惯。确保程序无论正常结束还是异常退出,都会释放GPIO引脚,将其设置为安全状态(输入模式),避免下次运行时引脚状态冲突。
运行这个脚本(python3 temperature_monitor.py),你应该能看到终端打印温度,并且当用手靠近传感器使温度超过37.3°C时,红色LED亮起,绿色LED熄灭。
4.2 集成邮件通知功能:SMTP协议实战
当检测到异常体温时,我们需要自动发送邮件。Python内置了smtplib库来处理邮件发送。这里以QQ邮箱的SMTP服务为例,其他邮箱(163、Gmail等)原理类似,只是服务器地址和端口不同。
首先,你需要开启邮箱的SMTP服务并获取授权码。以QQ邮箱为例:登录网页版QQ邮箱 -> 设置 -> 账户 -> 开启“POP3/SMTP服务” -> 生成授权码(这是一串字母数字组合,代替你的邮箱密码用于第三方登录)。
然后,我们在代码中添加邮件发送模块:
import smtplib from email.mime.text import MIMEText from email.header import Header # 邮件配置(替换为你的实际信息) SMTP_SERVER = "smtp.qq.com" SMTP_PORT = 465 # QQ邮箱SSL端口 SENDER_EMAIL = "your_email@qq.com" SENDER_AUTH_CODE = "your_authorization_code" # 注意:是授权码,不是邮箱密码! RECEIVER_EMAIL = "receiver@example.com" # 可以是一个列表,发送给多人 def send_alert_email(temp_value): """发送体温异常告警邮件""" subject = f"[防疫告警] 检测到异常体温:{temp_value:.2f}°C" # 邮件正文内容 body = f""" 时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} 地点:入口测温点 检测结果:体温异常 测量值:{temp_value:.2f}°C 阈值:{FEVER_THRESHOLD}°C 请相关工作人员及时处理。 """ # 构建MIME邮件对象 message = MIMEText(body, 'plain', 'utf-8') message['From'] = Header(f"防疫测温系统 <{SENDER_EMAIL}>", 'utf-8') message['To'] = Header(RECEIVER_EMAIL, 'utf-8') message['Subject'] = Header(subject, 'utf-8') try: # 建立SSL安全连接 with smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT) as server: server.login(SENDER_EMAIL, SENDER_AUTH_CODE) server.sendmail(SENDER_EMAIL, [RECEIVER_EMAIL], message.as_string()) print(f"告警邮件已发送至 {RECEIVER_EMAIL}") return True except Exception as e: print(f"邮件发送失败: {e}") # 这里可以加入失败重试逻辑,或者记录到日志文件 return False接下来,修改主循环中的告警部分,调用邮件发送函数:
if temp_object >= FEVER_THRESHOLD: status = "FEVER" set_leds(normal=False, warning=True) # 发送告警邮件 send_alert_email(temp_object) print(f"警告!检测到异常体温:{temp_object:.2f}°C")重要安全提示:千万不要将包含真实授权码的代码上传到公开的代码仓库(如GitHub)。最佳实践是将敏感配置(邮箱、授权码、接收人)写入一个单独的配置文件(如
config.py或settings.json),并在.gitignore中忽略这个文件。或者使用环境变量来存储。
4.3 防误报与状态保持优化
在实际部署中,我们遇到了一个问题:人只是快速挥手经过,传感器可能捕捉到一个瞬间的高值(比如碰到了热源反射),导致误报警。我们需要加入简单的防抖逻辑。
另外,当检测到异常后,红色LED常亮可能不够醒目。我们可以让它闪烁。同时,为了避免在短时间内因同一人连续触发多次邮件轰炸,需要加入状态保持和延时。
改进后的主循环逻辑如下:
# 在循环外定义状态变量 fever_detected = False last_alert_time = 0 ALERT_COOLDOWN = 300 # 同一事件告警冷却时间,单位秒(5分钟) try: print("防疫测温系统启动...按Ctrl+C终止。") while True: temp_object = mlx.object_temperature current_time = time.time() is_fever_now = temp_object >= FEVER_THRESHOLD # 状态判断与LED控制 if is_fever_now: status = "FEVER" if not fever_detected: # 首次进入发烧状态 fever_detected = True fever_start_time = current_time print(f"进入异常体温状态:{temp_object:.2f}°C") # 控制红灯闪烁 if int(current_time) % 2 == 0: # 简单闪烁,1秒周期 GPIO.output(LED_RED, GPIO.HIGH) else: GPIO.output(LED_RED, GPIO.LOW) GPIO.output(LED_GREEN, GPIO.LOW) # 发送告警邮件(带冷却时间判断) if current_time - last_alert_time > ALERT_COOLDOWN: if send_alert_email(temp_object): last_alert_time = current_time else: # 邮件发送失败,可以尝试记录或增加其他通知方式 pass else: # 体温正常 status = "NORMAL" if fever_detected: # 从发烧状态恢复 fever_detected = False print(f"体温恢复正常。") # 绿灯常亮 GPIO.output(LED_GREEN, GPIO.HIGH) GPIO.output(LED_RED, GPIO.LOW) # 记录数据(每次循环都记录) log_temperature(temp_object, status) time.sleep(0.5) # 提高检测频率到0.5秒一次 except KeyboardInterrupt: print("\n程序被用户中断。") finally: GPIO.cleanup()这段代码实现了:
- 状态机:用
fever_detected变量记录是否处于持续异常状态。 - LED闪烁:在异常状态下,红色LED以1秒为周期闪烁,更醒目。
- 告警冷却:用
last_alert_time和ALERT_COOLDOWN确保不会在短时间内重复发送邮件。冷却时间可以根据需要调整。
5. 系统部署、优化与问题排查
代码跑通只是第一步,要让它在树莓派上稳定、可靠地作为后台服务运行,还需要做一些部署和优化工作。
5.1 开机自启动:让服务“永生”
我们不想每次树莓派重启后都手动登录去运行Python脚本。有几种方法实现开机自启,这里推荐使用systemd服务,这是最标准、最易于管理的方式。
创建一个服务单元文件:
sudo nano /etc/systemd/system/covid-thermometer.service写入以下内容(根据你的实际路径修改):
[Unit] Description=Anti-COVID Thermometer Service After=network.target multi-user.target [Service] Type=simple User=pi WorkingDirectory=/home/pi ExecStart=/usr/bin/python3 /home/pi/temperature_monitor.py Restart=on-failure RestartSec=10 [Install] WantedBy=multi-user.target保存退出(Ctrl+X, 然后Y, 然后回车)。
重新加载systemd配置,启用并启动服务:
sudo systemctl daemon-reload sudo systemctl enable covid-thermometer.service sudo systemctl start covid-thermometer.service检查服务状态:
sudo systemctl status covid-thermometer.service如果看到
active (running),说明服务已成功在后台运行。你可以用sudo journalctl -u covid-thermometer.service -f来实时查看日志。
5.2 性能与稳定性优化
- 降低CPU占用:我们的主循环里有一个
time.sleep(0.5)。对于测温应用,0.5秒甚至1秒的间隔完全足够,没必要太快。过短的间隔会导致Python进程空转,增加不必要的CPU负载。可以调整为time.sleep(1)。 - 异常处理增强:在
try块内部,可以捕获更具体的异常,比如I2C读取错误,并进行相应处理(例如记录日志、尝试重新初始化传感器),而不是让整个程序崩溃。 - 日志轮转:
temperature_log.csv文件会不断增长。可以修改log_temperature函数,或者使用Linux的logrotate工具,按日期或大小分割日志文件,避免单个文件过大。
5.3 常见问题与排查指南
在实际部署中,你可能会遇到以下问题:
问题1:运行脚本时报错ModuleNotFoundError: No module named 'board'或'adafruit_mlx90614'
- 原因:Python库没有安装,或者安装在了错误的Python环境(比如系统Python vs 用户Python)。
- 解决:
- 确认安装命令使用了
pip3:pip3 install adafruit-circuitpython-mlx90614。 - 如果还不行,尝试用绝对路径:
/usr/bin/python3 -m pip install adafruit-circuitpython-mlx90614。 - 对于
board模块,它通常是adafruit-blinka库的一部分,这是一个用于在树莓派上模拟CircuitPython的库。安装:pip3 install adafruit-blinka。
- 确认安装命令使用了
问题2:传感器读数不稳定,偶尔跳变很大
- 原因:
- 电气干扰:杜邦线过长或接触不良。尽量使用短线,并确保插接牢固。
- 电源噪声:树莓派的3.3V电源带载能力有限,如果连接了其他大电流设备可能造成波动。尝试单独给传感器供电(仍需共地)。
- 环境干扰:传感器视场内有其他热源(如阳光直射、暖气、灯光)。
- 测量距离/角度不当:MLX90614需要正对目标,且距离在建议范围内(2-5cm)。
- 解决:
- 在代码中加入软件滤波。例如,连续读取5次,去掉最高最低值后取平均。
- 检查硬件连接,使用带屏蔽的线或缩短连接线。
- 为传感器制作一个简单的遮光筒,限制其视场,只接收正前方的红外辐射。
问题3:邮件发送失败,报错ssl.SSLError或smtplib.SMTPAuthenticationError
- 原因:
ssl.SSLError:通常是网络问题或服务器证书验证失败。可以尝试将SMTP_SSL换成SMTP并使用starttls(),但安全性稍差。SMTPAuthenticationError:99%是账号或授权码错误。请仔细检查:- 发送邮箱地址是否正确。
- 是否使用了授权码(16位字符串)而不是邮箱登录密码。
- SMTP服务器地址和端口是否正确(QQ邮箱是
smtp.qq.com:465/587)。
- 解决:
- 先在电脑上用Python脚本或邮件客户端测试邮箱配置是否正确。
- 检查树莓派系统时间是否正确。错误的系统时间可能导致SSL证书验证失败。运行
date命令查看,如果不准,可以设置NTP同步:sudo timedatectl set-ntp true。
问题4:GPIO控制LED时,灯不亮或非常暗
- 原因:
- LED极性接反:长脚(阳极)必须接GPIO(通过电阻),短脚(阴极)接GND。
- 电阻值过大:如果用成了1kΩ或更大的电阻,电流会太小,LED发光微弱。220Ω-330Ω是常用值。
- GPIO引脚模式错误:确保使用了
GPIO.setup(pin, GPIO.OUT)设置为输出模式。 - 代码逻辑错误:检查
GPIO.output(pin, GPIO.HIGH)是否确实被执行了。
- 解决:用万用表测量GPIO引脚在输出HIGH时的电压,应为3.3V左右。如果电压正常但LED不亮,肯定是LED或电阻坏了,或者电路不通。
这个项目从想法到落地,最难的部分往往不是代码本身,而是硬件环境的稳定性和实际部署中的细节打磨。比如,给树莓派和传感器做一个合适的外壳,既能保护电路,又能固定传感器的测量距离和角度;再比如,将系统配置成真正的“无头模式”(不接显示器键盘),只通过SSH管理,并做好断电恢复。这些工程化的思考,才是将一个原型变成可靠产品的关键。