LoRa-basiertes Smart-City-Luftqualitäts-Monitoring-System
Komponenten & Material
| Bauteil | Menge |
|---|---|
| Arduino UNO | 1 |
| Seeed Base Shield V2 | 1 |
| NodeMCU ESP8266 Breakout Board | 1 |
| Seeed Loudness Sensor (Grove) | 1 |
| Seeed Grove – Air Quality Sensor v1.3 | 1 |
| Seeed Grove – Temperatur- & Feuchtigkeitssensor (DHT11) | 1 |
| Reyax LoRa Modul (RYLR998) | 1 |
| Seeed Small Solar Panel 80×100 mm, 1 W | 1 |
| Seeed LiPo Rider Pro | 1 |
| Li-Ion-Akku 3,7 V (wiederaufladbar) | 1 |
Benötigte Werkzeuge & Ausrüstung
- 3D-Drucker (für das Gehäuse)
Software & Online-Dienste
- Arduino IDE
- Grandeur IoT Platform
- Grandeur Canvas (Dashboard-Builder)
Projektüberblick
Smart City & Luftqualität
Laut Weltbank nutzt eine Smart City Technologie, um hocheffiziente öffentliche Dienstleistungen und eine nachhaltige städtische Umwelt bereitzustellen. Zentral für diese Vision ist die Echtzeit-Umweltüberwachung, die Verschmutzungsquellen identifiziert und datenbasierte politische Entscheidungen ermöglicht – sei es die Optimierung von Verkehrsflüssen oder die Empfehlung sicherster Gehwege für Bewohner.
Problem & Lösung
Städtische Luftverschmutzung birgt erhebliche Gesundheitsrisiken; zeitnahe Daten sind für Gegenmaßnahmen unerlässlich. IoT-Netzwerke aus kostengünstigen Sensorknoten können kontinuierlich Luftqualitäts- und Meteorologieparameter erfassen. In Kombination mit einer zuverlässigen, energiesparenden Kommunikationsschicht erlauben diese Netze Stadtbehörden, Verschmutzungs-Hotspots zu erkennen und proaktiv zu reagieren.
Projektplan
Unsere Lösung überwacht zentrale Umweltmetriken – Lärm, Temperatur, Luftfeuchtigkeit und Luftqualität – und übermittelt die Daten drahtlos an eine Cloud-Plattform. Der gesamte Knoten wird von einem 1-W-Solarmodul mit Li-Ion-Batteriepuffer gespeist, was einen autonomen Betrieb garantiert. Ein Cloud-Dashboard visualisiert die Daten in Echtzeit und ermöglicht sofortige Einblicke sowie Langzeit-Trendanalysen.
Anstatt jeden Sensor direkt mit der Cloud zu verbinden – ein Ansatz, der Stromverbrauch und Kosten in die Höhe treibt – aggregieren wir Daten mehrerer benachbarter Knoten und übertragen sie über ein einziges LoRa-Gateway. LoRas große Reichweite und geringer Energiebedarf machen es ideal für dichte urbane Einsätze.
Als Backend-Plattform wählten wir Grandeur wegen seiner benutzerfreundlichen SDKs für Arduino, NodeMCU und Raspberry Pi sowie dem Drag-and-Drop-Dashboard-Builder (Canvas).
Solarstrom liefert eine nachhaltige, wartungsfreie Energiequelle für entfernte Knoten, und der Li-Ion-Akku sichert den Betrieb bei geringer Sonneneinstrahlung.
Aufbau des Sensorknotens
Der Knoten integriert einen Grove-Luftqualitätssensor, einen Grove-Lautstärkesensor und einen DHT11-Temperatur/Feuchtigkeitssensor – alle stecken direkt im Seeed Base Shield, ganz ohne Löten, was Montage und Wartung vereinfacht.
Die Stromversorgung erfolgt über ein 1-W-Solarmodul und den Seeed LiPo Rider Pro Laderegler, der einen 5-V-Boost-Converter enthält, um den Arduino im Betriebsspannungsbereich zu halten.
Das Reyax RYLR998 LoRa-Transceiver-Modul übernimmt die drahtlose Übertragung. Da das Modul 3,6-V-Logik erwartet, schützt ein einfacher Spannungsteiler den 5-V-TX-Pin des Arduino.
Unten sehen Sie das Verdrahtungsdiagramm, das das LoRa-Modul über einen benutzerdefinierten Adapter mit dem Base Shield verbindet.
Wichtig: Beim Anschluss des LoRa-Moduls muss der Stromschalter des Base Shield auf 3,3 V stehen.
Nach der Montage prüfen Sie die Kommunikation, indem Sie einen AT-Befehl vom Arduino an das LoRa-Modul senden.
Hochladen & Testen
Laden Sie den bereitgestellten Arduino-Sketch hoch und öffnen Sie den Serial Monitor, um zu bestätigen, dass Sensordaten korrekt übertragen werden.
Gehäuse-Montage
Drucken Sie die im Anhang enthaltenen STL-Dateien. Nach Passformprüfung bauen Sie die Komponenten mit doppelseitigem Klebeband oder Heißkleber in das Gehäuse ein. Montieren Sie das Solarmodul oben und führen Sie die Stromkabel durch die vorgesehene Öffnung.
Nach der Sicherung aller Komponenten ist der Knoten bereit für den Feldeinsatz.
Grandeur-Konfiguration
Grandeur ist ein IoT-Backend, das Geräte mit Benutzern verknüpft und ein Echtzeit-Dashboard bereitstellt. Folgen Sie diesen Schritten:
- Erstellen Sie ein neues Projekt auf grandeur.dev.
- Registrieren Sie einen Benutzeraccount; dieser wird später zum Koppeln der Geräte verwendet.
- Definieren Sie ein Gerätemodell mit dem JSON-Schema:
{"temp":0, "humid":0, "air":0, "noise":0} - Instanziieren Sie ein Gerät, notieren Sie sich die eindeutige Geräte-ID und den Access-Token für die Arduino-Firmware.
- Koppeln Sie das Gerät mit dem Benutzer über das Dashboard oder die API (z. B.
pairDevice()). - Erstellen Sie ein Canvas-Dashboard: Fügen Sie vier Diagramm-Widgets hinzu – je eines für Temperatur, Luftfeuchtigkeit, Lärmpegel und Luftqualität – und binden Sie sie an die entsprechenden Variablen.
Nach der Canvas-Autorisierung können Benutzer sich einloggen und Live-Daten einsehen. API-Key und Geräte-Token werden in der Gateway-Firmware benötigt.
IoT LoRa-Gateway einrichten
Der NodeMCU fungiert als LoRa-Gateway: Er empfängt UART-Daten vom Sensorknoten und leitet sie per Wi-Fi an Grandeur weiter. Ein Raspberry Pi könnte diese Rolle ebenfalls übernehmen, doch der NodeMCU bietet eine kompakte, kostengünstige Lösung, die Bandbreiten- und Latenzanforderungen des Projekts erfüllt.
Gateway-Firmware
Die Firmware parst das kommagetrennte Payload vom LoRa-Modul, wandelt jeden Wert in float um und sendet ihn via Grandeur SDK. Hier die Kernlogik für Datenextraktion und -übertragung:
void process_received_data(){
start_pos = inputString.indexOf(start_val);
end_pos = inputString.indexOf(end_val);
String data_string = inputString.substring(start_pos+1, end_pos-1);
int firstCommaIndex = data_string.indexOf(',');
int secondCommaIndex = data_string.indexOf(',', firstCommaIndex+1);
int thirdCommaIndex = data_string.indexOf(',', secondCommaIndex+1);
String temperature = data_string.substring(0, firstCommaIndex);
String humidity = data_string.substring(firstCommaIndex+1, secondCommaIndex);
String noise_level = data_string.substring(secondCommaIndex+1, thirdCommaIndex);
String air_quality = data_string.substring(thirdCommaIndex+1);
temp = temperature.toFloat();
humid = humidity.toFloat();
noise = noise_level.toFloat();
air = air_quality.toFloat();
inputString = "";
stringComplete = false;
}
Dashboard-Ansicht
Firmware-Code
Sensorknoten (Arduino)
#include "AirQuality.h" // Grove Luftqualitäts-Bibliothek
#include "Arduino.h"
#include "DHT.h"
#define DHTPIN 4 // Digitaler Pin für DHT11
#define DHTTYPE DHT11 // Sensormodell DHT11
DHT dht(DHTPIN, DHTTYPE);
AirQuality airqualitysensor;
int current_quality = -1;
const int sampleWindow = 50; // 20 Hz Abtastrate
unsigned int sample;
float noise;
float temp, humid;
int airQuality;
void setup() {
Serial.begin(115200); // UART für LoRa
airqualitysensor.init(14);
dht.begin();
}
void loop() {
noise = calculate_sound_in_db();
airQuality = calculate_air_quality();
calculate_temp_humid();
String values = String(temp) + "," + String(humid) + "," + String(noise) + "," + String(airQuality);
String cmd = "AT+SEND=0," + String(values.length()) + "," + values;
Serial.println(cmd);
delay(15000);
}
float calculate_sound_in_db() {
unsigned long startMillis = millis();
float peakToPeak = 0;
unsigned int signalMax = 0;
unsigned int signalMin = 1024;
while (millis() - startMillis < sampleWindow) {
sample = analogRead(A1);
if (sample < 1024) {
if (sample > signalMax) signalMax = sample;
else if (sample < signalMin) signalMin = sample;
}
}
peakToPeak = signalMax - signalMin;
float db = map(peakToPeak, 0, 1000, 48, 120);
return db;
}
int calculate_air_quality() {
current_quality = airqualitysensor.slope();
return current_quality;
}
void calculate_temp_humid() {
float h = dht.readHumidity();
float t = dht.readTemperature();
if (isnan(h) || isnan(t)) return;
temp = t;
humid = h;
}
ISR(TIMER1_OVF_vect) {
if (airqualitysensor.counter == 61) {
airqualitysensor.last_vol = airqualitysensor.first_vol;
airqualitysensor.first_vol = analogRead(A0);
airqualitysensor.counter = 0;
airqualitysensor.timer_index = 1;
PORTB = PORTB ^ 0x20;
} else {
airqualitysensor.counter++;
}
}
Gateway (NodeMCU / ESP8266)
/* SDK & WiFi Bibliothek einbinden */
#include
#include
#include
SoftwareSerial LORA(D7, D8); // RX, TX für LoRa-Modul
/* Konfiguration */
String deviceID = "your_device_id";
String apiKey = "your_api_key";
String token = "your_device_token";
/* WiFi-Zugangsdaten */
String ssid = "your_wifi_ssid";
String password = "your_wifi_password";
/* Projekt- & Geräteobjekte */
Grandeur::Project project;
Grandeur::Project::Device device;
/* Verbindungsstatus & Zeitstempel */
int connected = 0;
uint32_t lastUpdate = 0;
String inputString = "";
boolean stringComplete = false;
// ':' und '%' wurden vom Senderknoten als Daten-Trennzeichen festgelegt
char start_val = ':';
char end_val = '%';
int start_pos = 0;
int end_pos = 0;
float temp = 0;
float humid = 0;
float air = 0;
float noise = 0;
/* Verbindungsstatus-Callback */
void onConnection(bool status) {
switch (status) {
case CONNECTED:
Serial.println("Gerät mit Cloud verbunden.");
connected = 1;
lastUpdate = millis();
return;
case DISCONNECTED:
Serial.println("Gerät von Cloud getrennt.");
connected = 0;
return;
}
}
/* WiFi-Verbindung herstellen */
void connectWiFi() {
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi verbunden");
Serial.println(WiFi.localIP());
}
/* Datenaktualisierungs-Callback */
void dataCallback(const char* code, Var res) {
if (code == "DEVICE-DATA-UPDATED") {
double temp_data = (double)res["update"]["temp"];
double humid_data = (double)res["update"]["humid"];
double noise_data = (double)res["update"]["noise"];
double air_data = (double)res["update"]["air"];
Serial.printf("Temperatur: %f Luftfeuchtigkeit: %f Lärm: %f Luftqualität: %f\n",
temp_data, humid_data, noise_data, air_data);
return;
}
Serial.println("Datenaktualisierung fehlgeschlagen");
}
/* Aktuelle Messwerte an Cloud senden */
void sendUpdate() {
Var data;
data["temp"] = temp;
data["humid"] = humid;
data["noise"] = noise;
data["air"] = air;
device.data().set("temp", temp, dataCallback);
device.data().set("humid", humid, dataCallback);
device.data().set("noise", noise, dataCallback);
device.data().set("air", air, dataCallback);
}
void setup() {
Serial.begin(9600);
LORA.begin(115200);
inputString.reserve(200);
connectWiFi();
project = grandeur.init(apiKey, token);
device = project.device(deviceID);
project.onConnection(onConnection);
}
void loop() {
serialEvent();
project.loop(WiFi.status() == WL_CONNECTED);
if (stringComplete) {
process_received_data();
if (connected) sendUpdate();
}
}
void serialEvent() {
while (LORA.available()) {
char inChar = (char)LORA.read();
if (inChar == '\n') {
stringComplete = true;
} else {
inputString += inChar;
}
}
}
Herstellungsprozess
- Winzige PM2.5 & VOC Sensoren ermöglichen Echtzeit-Personal-Luftqualitätsmonitoring
- Bosch Sensortec stellt KI-fähigen BME688 Luftqualitätssensor vor, um Virusübertragungsrisiko zu senken
- Renesas erweitert Indoor Air Quality Sensor Plattform mit eingebetteter KI für verbesserte Geruchserkennung
- Luftqualität messen mit OpenSensors: Arduino & Python Anleitung
- DIY Luftqualitätsmonitor: Kosteneffektiver EPA-konformer Detektor
- Helium-basierter solarbetriebener Luftqualitätssensor: Design- und Deployments-Guide
- IoT-Sensoren revolutionieren Luftverschmutzungsüberwachung und öffentliche Gesundheit
- IoT-gestützte Umgebungsluft-Qualitätsüberwachung: Sicherheit, Produktivität & Nachhaltigkeit
- Industrielles IoT-Luftqualitätsmonitoring: Produktivität steigern & Kosten senken
- Fortschrittlicher kohlenstoffbasierter NO₂-Sensor verbessert Innenraumluftüberwachung