校园智能体温监测系统 - 精准、实时、安全的学生健康守护方案
组件与物料清单
| 组件 | 数量 |
|---|---|
| Pycom SiPy 模块 | 1 |
| Raspberry Pi 3 Model B | 1 |
| Arduino Nano R3 | 1 |
| 双向逻辑电平转换模块 | 1 |
| NFC RFID-RC522 模块 | 1 |
| Sharp GP2Y0A41SK0F 模拟测距传感器 (4-30cm) | 1 |
| MLX90614-DCI I2C 红外测温传感器 | 1 |
| DFRobot I2C 16x2 LCD 显示模块 | 1 |
| LED | 1 |
| 330Ω 电阻 | 2 |
| 蜂鸣器 | 1 |
软件与云服务
- Arduino IDE
- Visual Studio Code + Pymakr 扩展
- ThingSpeak API
- IFTTT
- PuTTY / IDLE IDE
问题背景
亚洲炎热气候及 2003 年 SARS 经验,要求中小学生定期测温。传统方式学生自带体温计,易遗失、损坏、忘带,卫生禁止共用,购买负担重,导致测温练习成功率低。教师事后手工录入 40 人数据极其繁琐。
解决方案架构
核心理念:自动测量合法用户体温,数据云端存储,异常自动报警。对应联合国可持续发展目标 (SDG) "良好健康与福祉"。
核心流程:Raspberry Pi + Arduino 协同
- Raspberry Pi 运行 Python 程序管理 MySQL 数据库,自动建表
- 串口等待 Arduino 数据
- 用户刷 RFID 卡 → Arduino 发送 UID → Pi 校验合法性
- 合法用户:LCD 显示欢迎,提示测温
- 测距传感器确认用户就位 → 红外测温 → LCD 显示结果
- 有效温度 (28-42℃) → 经串口发送 Pi (入库 MySQL) 与 SiPy (上传 Sigfox/ThingSpeak)
握手同步机制:双方通过 "READY"/"RECEIVED" 信号同步,避免时序竞争。
Sigfox 云端上传 (SiPy)
SiPy 解析温度整数/小数部分,以字节数组上传 Sigfox 后台,再经回调转发 ThingSpeak。自定义载荷格式确保字段对应正确。
云端数据处理 (ThingSpeak + MATLAB + IFTTT)
- 私有通道:接收原始分离数据 (整数、小数、用户ID)
- MATLAB Analysis:触发合并温度值,写入公共通道
- 公共通道:整合后的温度+用户ID,自动更新表格
- React 触发:新数据到达时运行 MATLAB 脚本
- IFTTT Webhooks:温度 ≥ 38℃ 触发邮件报警管理员
硬件接线指南 (Arduino Nano)
| 模块 | 连接引脚 |
|---|---|
| RFID-RC522 | SDA-D10, SCK-D13, MOSI-D11, MISO-D12, RST-D9, 3.3V, GND |
| I2C LCD | SDA-A4, SCL-A5, 5V, GND |
| 测距传感器 | VCC-5V, GND, OUT-A0 |
| 蜂鸣器 | GND, OUT-D6 |
| 红外测温 | SDA-A4, SCL-A5, 5V, GND |
| LED | 阴极-GND, 阳极-D7 |
Raspberry Pi 环境搭建
- 刷写 Raspbian 系统 (推荐 balenaEtcher)
- 启用 SSH/VNC,可选配置静态 IP
- 安装 Apache2 + PHP + MariaDB + phpMyAdmin
- 配置 MySQL root 用户权限
SiPy 扩展板 2.0 设置
- 安装 SiPy 模块 (复位键朝 USB 口)
- 固件升级 (选对 RCZ 区域,避免 ID/PAC 全 F)
- 安装 VS Code + Pymakr + Node.js
- 通过 FTP (FileZilla) 管理闪存文件
Sigfox 后台注册
运行代码获取 Device ID 与 PAC 号,在 Sigfox 后台注册设备,选择正确国家区域。
云端完整配置流程
- IFTTT 创建 Webhooks → Email 报警链
- ThingSpeak 建立私有/公共双通道
- 配置 React 触发 MATLAB 分析脚本
- MATLAB 脚本合并温度值、写入公共通道、判断发热触发 IFTTT
核心代码
Arduino Nano (C/C++)
/* 90% 原创 by Aden (https://www.astero.me) */
/* RFID 库参考 ryand1011 / miguelbalboa */
/* MLX90614 库参考 Adafruit */
#include <SPI.h>
#include <MFRC522.h>
#include <LiquidCrystal_I2C.h>
#include <Wire.h>
#include <SoftwareSerial.h>
#include <Adafruit_MLX90614.h>
#define RST_PIN 9
#define SS_PIN 10
#define BUZZER_PIN 7
#define LED_PIN 6
#define DIST_SENSE_PIN A0
MFRC522 mfrc522(SS_PIN, RST_PIN);
LiquidCrystal_I2C lcd(0x27, 16, 2);
Adafruit_MLX90614 mlx = Adafruit_MLX90614();
SoftwareSerial unoSerial(2, 3);
boolean tapped = true;
boolean confirmation = false;
String GetUID() {
String uid = "";
for (byte i = 0; i < mfrc522.uid.size; i++) {
uid += String(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " ");
uid += String(mfrc522.uid.uidByte[i], HEX);
}
String noSpace = "";
noSpace += uid[1]; noSpace += uid[2];
noSpace += uid[4]; noSpace += uid[5];
noSpace += uid[7]; noSpace += uid[8];
noSpace += uid[10]; noSpace += uid[11];
return noSpace;
}
void setup() {
Serial.begin(9600);
while (!confirmation) {
Serial.println("ARDUINO");
confirmation = Serial.readString() == "RAPI";
delay(1000);
}
SPI.begin();
mfrc522.PCD_Init();
mlx.begin();
unoSerial.begin(9600);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(LED_PIN, OUTPUT);
lcd.init(); lcd.backlight();
}
void loop() {
boolean readySignal = Serial.readString() == "READY";
if (readySignal) { tapped = true; Serial.println("RECEIVED"); }
if (tapped) {
digitalWrite(BUZZER_PIN, 0); digitalWrite(LED_PIN, 0);
lcd.clear();
lcd.setCursor(0,0); lcd.print(" Please tap");
lcd.setCursor(0,1); lcd.print(" your card.");
delay(500);
tapped = false;
} else {
if (!mfrc522.PICC_IsNewCardPresent()) return;
if (!mfrc522.PICC_ReadCardSerial()) return;
String card_UID = GetUID(); card_UID.toUpperCase();
Serial.print(card_UID); Serial.print("\n");
String rawData = Serial.readString();
int commaPos = rawData.indexOf(',');
String userInitial = rawData.substring(0, commaPos);
int userID = rawData.substring(commaPos + 1).toInt();
lcd.clear();
if (userID != 0 && userInitial != "0") {
lcd.setCursor(0,0); lcd.print("Welcome, ");
lcd.setCursor(0,1); lcd.print(userInitial);
int dist = analogRead(DIST_SENSE_PIN);
byte temp_H = 0, temp_L = 0;
float meas_temp = 0;
delay(2000);
while (dist < 170 || dist > 200) {
dist = analogRead(DIST_SENSE_PIN);
lcd.setCursor(0,0); lcd.print("Please take");
lcd.setCursor(0,1); lcd.print("your temperature.");
}
digitalWrite(BUZZER_PIN, 1); digitalWrite(LED_PIN, 1); lcd.clear();
meas_temp = mlx.readObjectTempC();
temp_H = floor(meas_temp);
temp_L = (meas_temp - temp_H) * 100;
String sMeasTemp = String(userID) + "," + temp_H + "," + temp_L;
Serial.println("TEMP"); Serial.println(meas_temp);
if (meas_temp >= 28 && meas_temp <= 42) {
unoSerial.print(sMeasTemp);
lcd.setCursor(0,0); lcd.print("Temperature: ");
lcd.setCursor(0,1); lcd.print(" " + String(meas_temp));
}
rawData = Serial.readString();
commaPos = rawData.indexOf(',');
userInitial = rawData.substring(0, commaPos);
if (userInitial == "0") {
lcd.clear();
lcd.setCursor(0,0); lcd.print("Error!");
lcd.setCursor(0,1); lcd.print("Invalid Temperature.");
}
} else {
lcd.setCursor(0,0); lcd.print("Error!");
lcd.setCursor(0,1); lcd.print("Invalid User.");
}
Serial.println("STANDBY");
delay(1000);
mfrc522.PICC_HaltA();
mfrc522.PCD_StopCrypto1();
}
}
Raspberry Pi (Python)
''' Coded by Aden for Hackster.io Sigfox Competition '''
import mysql.connector, serial, os, time
host = "localhost"; user = "root"; password = "password"; db = "smartTemp"
comPort = 'ttyUSB0'; baudRate = 9600
ser = serial.Serial('/dev/' + comPort, baudRate, timeout=0)
print("Confirming USB Serial Port...")
# 确认串口
connected = False
for x in range(2):
time.sleep(2)
ser.write("RAPI")
if not connected:
if ser.readline().strip() == "ARDUINO":
connected = True
comPort = 'ttyUSB0'
else:
comPort = 'ttyUSB1'
connected = True
print(comPort)
ser = serial.Serial('/dev/' + comPort, baudRate)
print("COM PORT confirmed: " + comPort)
countFile = open("count.txt", "a++")
cursor = mysql.connector.connect(host=host, user=user, passwd=password).cursor(buffered=True)
try:
cursor.execute("CREATE DATABASE " + db)
print("Database created.")
except:
print(db + " exists.")
cursor.execute("CREATE TABLE IF NOT EXISTS `userInfo` (id int, cardID VARCHAR(9), userID VARCHAR(4), userInitial VARCHAR(8))")
cursor.execute("CREATE TABLE IF NOT EXISTS `tempData` (id int, userID VARCHAR(4), dateMeas date, timeMeas time, tempMeasure decimal(5,2))")
while True:
cursor = mysql.connector.connect(host=host, user=user, passwd=password).cursor(buffered=True)
cursor.execute("USE " + db)
cardID = str(ser.readline().strip())
cursor.execute("SELECT * FROM userInfo WHERE cardID = '" + cardID + "'")
items = cursor.fetchone()
if items:
userID = str(items[2])
userDetails = str(items[3] + ", " + items[2])
currentDate = time.strftime("%Y-%m-%d")
currentTime = time.strftime("%H:%M:%S")
ser.write(userDetails.encode())
tempSignal = False
while not tempSignal:
tempSignal = ser.readline().strip() == "TEMP"
tempData = float(ser.readline().strip())
print(tempData)
if 28 <= tempData <= 42:
cursor.execute("INSERT INTO `tempData` VALUES (%s, %s, %s, %s, %s)",
(count, userID, currentDate, currentTime, tempData))
conn.commit()
count += 1
countFile = open("count.txt", "w")
countFile.write(str(count))
else:
ser.write(b"0, 0")
else:
ser.write(b", 0")
print("Nothing found")
readySignal = ser.readline().strip() == "STANDBY"
while readySignal:
ser.write(b"READY")
if ser.readline().strip() == "RECEIVED":
readySignal = False
cursor.close()
SiPy MicroPython
''' 100% coded by Reginald '''
from machine import UART
from network import Sigfox
import binascii, socket, time, pycom, sys
pycom.heartbeat(False)
sigfox = Sigfox(mode=Sigfox.SIGFOX, rcz=Sigfox.RCZ4) # 亚洲区
uart1 = UART(1, baudrate=9600)
uart1.init(9600, bits=8, parity=None, stop=1, pins=('P3','P4'), timeout_chars=20)
while True:
try:
recv = uart1.readline()
if recv:
print(binascii.hexlify(sigfox.id()))
print(binascii.hexlify(sigfox.pac()))
sk1 = socket.socket(socket.AF_SIGFOX, socket.SOCK_RAW)
sk1.setblocking(False)
sk1.setsockopt(socket.SOL_SIGFOX, socket.SO_RX, False)
dataList = recv.decode("utf-8")
split_val = dataList.split(",")
userID = int(split_val[0])
temp_H = int(split_val[1])
temp_L = int(split_val[2])
bData = [userID, temp_H, temp_L]
meas_temp = temp_H + temp_L * 0.01
print("Temp: %.2f" % meas_temp)
sk1.send(bytes(bData))
sk1.close()
time.sleep(5)
except KeyboardInterrupt:
sys.exit()
ThingSpeak MATLAB 脚本
%% by Aden for Hackster.io Sigfox Competition
readChannelID = 870479;
webhookTrigger = 'https://maker.ifttt.com/trigger/student_fever/with/key/...';
readAPIKey = 'QGHINBPNJQKULBH2';
writeChannelID = 870482;
writeAPIKey = 'R6NJIM8NT5A42R9N';
wholeNumber = thingSpeakRead(readChannelID, 'ReadKey', readAPIKey, 'Fields', 1);
decimalNumber = thingSpeakRead(readChannelID, 'ReadKey', readAPIKey, 'Fields', 2) / 100;
userID = thingSpeakRead(readChannelID, 'ReadKey', readAPIKey, 'Fields', 3);
analyzedData = wholeNumber + decimalNumber;
thingSpeakWrite(writeChannelID, 'Fields', [1,2], 'Values', {analyzedData, userID}, 'WriteKey', writeAPIKey);
if analyzedData >= 38:
webwrite(webhookTrigger, 'value1', analyzedData, 'value2', userID)
end
团队分工
- Aden:Raspberry Pi + Arduino + 模块 + ThingSpeak (MATLAB/配置)
- Reginald:Sigfox + ThingSpeak 数据采集 + 原理图
- Bo Sheng:IFTTT 配置
制造工艺
- 树莓派专业温度监测:基于 DS18B20 传感器的完整指南
- 树莓派精准监测机柜温度:DS18B20 多点部署实战
- DIY 家酿温控系统:Arduino 监测与调节发酵温度
- K30 CO₂ 传感器:室内空气质量实时监测
- 智能窗帘:基于Arduino与太阳能的自动光控系统
- 非接触体温筛查闸机:Arduino MKR1000 + 红外测温 + 舵机控制
- Arduino UNO + WiFi 盾板:串口实时推送温度到网页绘图
- Arduino 8 位并行 IO 端口库:一行代码实现 8 位数字读写
- 智能手语手套:柔性传感器 + 加速度计 + 蓝牙将手势转文字/语音
- Arduino 64 键原型键盘矩阵:用移位寄存器打造全自定义键盘
- Health Band 智能健康手环:基于 Infineon DPS310 的老人跌倒/步数/体温监护系统
- 智慧农业:物联网全维监测开启未来农业新纪元