Умная система мониторинга температуры для школ: точно, в реальном времени, безопасно
Компоненты и материалы
| Pycom SiPy | ×1 |
| Raspberry Pi 3 Model B | ×1 |
| Arduino Nano R3 | ×1 |
| Logic Level Converter (бидирекциональный) | ×1 |
| NFC RFID-RC522 | ×1 |
| Датчик расстояния Sharp GP2Y0A41SK0F (4–30 см) | ×1 |
| ИК-термометр MLX90614-DCI (I2C) | ×1 |
| I2C LCD 16×2 (DFRobot) | ×1 |
| Светодиод | ×1 |
| Резисторы 330 Ом (×2) | ×2 |
| Пьезо-зумер | ×1 |
Программное обеспечение и сервисы
- Arduino IDE
- Visual Studio Code + Pymakr
- ThingSpeak API
- IFTTT
- PuTTY, IDLE
О проекте

Проблема
В Азии из-за жары и SARS (2003) школьники ежеквартально измеряют температуру. Приносят личные термометры, но некоторые забывают, ломают, не могут купить новые. Общий термометр гигиенично недопустим. Учителя вручную вносят данные 40 учеников — трудоёмко.
Решение


Тема: Social Impact (SqwidNet), SDG: Good Health and Well-being. Автоматическое измерение температуры авторизованных пользователей, алерт при превышении порога.
Как работает: Raspberry Pi + Arduino
- Raspberry Pi: Python + MySQL. При старте создаёт БД/таблицы автоматически.
- Ожидает данные от Arduino по Serial.
- Приложение RFID-карты → Arduino отправляет ID → RPi проверяет в БД → если валидно, отправляет подтверждение → Arduino пишет «Welcome, Name» на LCD.
- Датчик расстояния проверяет наличие человека → измеряет температуру MLX90614 → выводит на LCD.
- Если температура 28–42 °C — данные уходят в RPi (MySQL) и в SiPy (Sigfox → ThingSpeak).
- Синхронизация (Handshaking): обе стороны обмениваются сигналами READY/RECEIVED перед передачей.


Как работает: Sigfox (SiPy)
SiPy получает от Arduino userID и температуру, разделяет на целую и дробную части, отправляет байтами в Sigfox Backend (для совместимости с Ubidots и ThingSpeak).
В Sigfox Backend (CALLBACKS) настраивается форвардинг в ThingSpeak: Custom Payload должен точно соответствовать порядку полей (userID, temp_H, temp_L).
Облако (ThingSpeak + MATLAB + IFTTT)
- Два канала: Private (сырые байты) и Public (обработанные данные).
- MATLAB Analysis на Private канале объединяет целую и дробную части температуры, переносит userID в Public канал.
- React триггерит MATLAB при появлении данных (Field 3 ≠ 0).
- Если температура ≥ 38 °C — MATLAB вызывает Webhook IFTTT → email уведомление персоналу.
- Public канал имеет автообновляемую таблицу для админов.


1. Подключение Arduino Nano
Пины (Nano и Uno одинаковые):
RFID RC522: SDA-D10, SCK-D13, MOSI-D11, MISO-D12, IRQ-NC, GND-GND, RST-D9, 3.3V-3.3V
LCD I2C: SDA-A4, SCL-A5, GND-GND, 5V-5V
Distance: 5V-5V, GND-GND, Out-A0
Buzzer: GND-GND, Out-D6
MLX90614: SDA-A4, SCL-A5, GND-GND, 5V-5V
LED: Cathode-GND, Anode-D7
2. Настройка Raspberry Pi
- Установка Raspbian (balenaEtcher → образ → SD).
- Включение SSH/VNC в raspi-config. Опционально: статический IP в
/etc/dhcpcd.conf.
3. MySQL Database
sudo apt update && sudo apt upgrade
sudo apt install apache2 php php-mbstring mariadb-server php-mysql
sudo mysql --user=root
DROP USER 'root'@'localhost';
CREATE USER 'root'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION;
FLUSH PRIVILEGES; exit;
mysql --user=root --password=yourpassword
sudo apt install phpmyadmin
sudo phpenmod mysqli
sudo /etc/init.d/apache2 restart
Доступ: https://
4. SiPy Expansion Board 2.0
- Вставьте SiPy с кнопкой ресета к USB.
- Обновите прошивку (Pycom Firmware Updater) — выберите правильный RCZ Region (RCZ4 для Азии).
- Установите VS Code + Pymakr + Node.js.
- Создайте папку, файл
main.py. - Бесконечные циклы загружаются в флеш — для удаления используйте FileZilla (FTP: plain, passive, 1 соединение). Хост/юзер/пасс из Pymakr Global Settings. SSID SiPy — уникальный, пароль:
www.pycom.io.
5. Регистрация в Sigfox Backend
Получите Device ID и PAC через код в терминале или файле. Зарегистрируйте на backend.sigfox.com (выберите страну). Тест отправки байтов.
6. Настройка облака (IFTTT + ThingSpeak)
- IFTTT: Webhooks (Receive web request, event name «fever») → Email (Send me an email, Body с {{Value1}}, {{Value2}}).
- ThingSpeak: создайте каналы Private и Public. Настройте React на Private: условие Field 3 ≠ 0 → MATLAB Analysis (код объединения температуры и отправки в Public + IFTTT webhook).
Участники
- Aden — Raspberry Pi + Arduino + ThingSpeak (MATLAB/Setup)
- Reginald — Sigfox + ThingSpeak Data Collection + Схемы
- Bo Sheng — IFTTT Setup
Исходный код
[C] ARDUINO NANO
/* 90% coded by Aden (https://www.astero.me), 10% open libraries (RFID, MLX90614) */
#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_sensePin 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 card_UID = "", card_UID_no_space = "";
for (byte i = 0; i < mfrc522.uid.size; i++) {
card_UID.concat(String(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " "));
card_UID.concat(String(mfrc522.uid.uidByte[i], HEX));
}
card_UID_no_space.concat(card_UID.charAt(1));
card_UID_no_space.concat(card_UID.charAt(2));
card_UID_no_space.concat(card_UID.charAt(4));
card_UID_no_space.concat(card_UID.charAt(5));
card_UID_no_space.concat(card_UID.charAt(7));
card_UID_no_space.concat(card_UID.charAt(8));
card_UID_no_space.concat(card_UID.charAt(10));
card_UID_no_space.concat(card_UID.charAt(11));
return card_UID_no_space;
}
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 {
String card_UID = "";
if (!mfrc522.PICC_IsNewCardPresent()) return;
if (!mfrc522.PICC_ReadCardSerial()) return;
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_senseValue = analogRead(dist_sensePin);
byte temp_Hbyte = 0, temp_Lbyte = 0;
float meas_temp = 0.0;
delay(2000);
while ((dist_senseValue < 170) || (dist_senseValue > 200)) {
dist_senseValue = analogRead(dist_sensePin);
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 = (float)mlx.readObjectTempC();
temp_Hbyte = floor(meas_temp);
temp_Lbyte = (meas_temp - temp_Hbyte) * 100;
String sMeasTemp = ""; sMeasTemp += userID; sMeasTemp += ","; sMeasTemp += temp_Hbyte; sMeasTemp += ","; sMeasTemp += temp_Lbyte;
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));
} else {
lcd.setCursor(0,0); lcd.print("Error!"); lcd.setCursor(0,1); lcd.print("Invalid User.");
}
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."); }
Serial.println("STANDBY"); delay(1000);
mfrc522.PICC_HaltA(); mfrc522.PCD_StopCrypto1();
}
}
[PYTHON] Raspberry Pi
''' Coded by Aden for Hackster.io Sigfox competition. https://www.astero.me '''
import mysql.connector, serial, os, time
def queryConnection():
cursor = conn.cursor(buffered=True)
cursor.execute("USE `" + databaseName + "`")
return cursor
host = "localhost"; user = "root"; password = "password"; databaseName = "smartTemp"
conn = mysql.connector.connect(host=host, user=user, passwd=password)
comPort = 'ttyUSB0'; baudRate = 9600
ser = serial.Serial('/dev/' + comPort, baudRate, timeout=0)
print("Confirming USB Serial Port now, please wait")
connected = False
for x in range(2):
time.sleep(2); ser.write("RAPI")
if not connected:
serialConfirmation = ser.readline().strip() == "ARDUINO"
print(serialConfirmation)
if not serialConfirmation: comPort = 'ttyUSB1'; connected = True
else: comPort = 'ttyUSB0'; connected = True
print(comPort)
ser = serial.Serial('/dev/' + comPort, baudRate)
print("COM PORT confirmed, using " + comPort)
countFile = open("count.txt", "a++")
tapRFID = False
cursor = queryConnection()
if os.stat("count.txt").st_size == 0:
print("Count has not been set, setting to 1 now."); count = 1
else:
for countNumber in countFile: count = int(countNumber)
print("Count restored: " + str(count))
try:
print("Creating " + databaseName + " database now..")
cursor.execute("CREATE DATABASE " + databaseName)
print("Database created.")
except mysql.connector.errors.DatabaseError:
print(databaseName + " database already 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 = queryConnection()
cardID = str(ser.readline().strip())
cursor.execute("SELECT * FROM userInfo WHERE cardID = '" + cardID + "'")
items = cursor.fetchone()
if items:
print(cardID)
userID = str(items[2])
userDetails = str(items[3] + ", " + items[2])
currentDate = time.strftime("%Y-%m-%d", time.gmtime())
currentTime = time.strftime("%H:%M:%S", time.gmtime())
print(items)
ser.write(b'' + userDetails)
countFile.close()
countFile = open("count.txt", 'w')
tempSignal = False
while not tempSignal:
print("Getting Temperature")
tempSignal = ser.readline().strip() == "TEMP"
tempData = float(ser.readline().strip())
print(tempData)
if 28 <= tempData <= 42:
cursor.execute("INSERT INTO `tempData`(`id`,`userID`,`dateMeas`,`timeMeas`,`tempMeasure`) VALUES (" + str(count) + ",'" + userID + "','" + currentDate + "','" + currentTime + "','" + str(tempData) + "')")
conn.commit()
count += 1; 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"); print("sending")
receivedSignal = ser.readline().strip() == "RECEIVED"
if receivedSignal: readySignal = False; print("received")
cursor.close(); conn.close()
[MicroPython] Sigfox (Pycom)
''' 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")
print("dataList : %s" % dataList)
split_val = dataList.split(",")
userID = int(split_val[0])
temp_H = int(split_val[1])
temp_L = int(split_val[2])
print("userID : %d temp_H : %d temp_L : %d" % (userID, temp_H, temp_L))
bData = [userID, temp_H, temp_L]
meas_temp = temp_H + (temp_L * 0.01)
print("measure temperature : %.2f" % meas_temp)
sk1.send(bytes(bData))
sk1.close()
time.sleep(5)
except KeyboardInterrupt:
sys.exit()
[ThingSpeak] MATLAB SCRIPT
%% Made by Aden for Hackster.io Sigfox Competition. https://www.astero.me
readChannelID = 870479;
webhookTrigger = 'https://maker.ifttt.com/trigger/student_fever/with/key/h10MdSGwjSPtZQ43wH-AgoiKI0pwaljBNnGUEu4Yecn';
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);
display(wholeNumber, 'BEFORE')
analyzedData = wholeNumber + decimalNumber;
display(analyzedData, 'AFTER')
display(userID, 'USERID')
thingSpeakWrite(writeChannelID, 'Fields', [1,2], 'Values', {analyzedData, userID}, 'WriteKey', writeAPIKey)
if analyzedData >= 38
webwrite(webhookTrigger, 'value1', analyzedData, 'value2', userID);
end
Схемы подключения







Производственный процесс
- Профессиональный мониторинг температуры на Raspberry Pi с датчиком DS18B20
- Точный мониторинг температуры в серверной на Raspberry Pi
- Самодельная система термостатирования для домашнего пивоварения: мониторинг и регулирование на Arduino
- Датчик CO2 K30: мониторинг качества воздуха в реальном времени на Arduino
- Умные шторы: автоматическое управление светом на Arduino с солнечной энергией
- Бесконтактный термометрический шлюз для быстрого и точного скрининга
- Arduino UNO + WiFi Shield: реальный график температуры в вебе через Serial
- Библиотека 8-битного порта ввода-вывода для Arduino — расширение цифрового управления
- Health Band: умный браслет для мониторинга пожилых людей на Arduino Nano и DPS310
- Умное фермерство: комплексный IoT-мониторинг для будущего аграриа