This commit is contained in:
Michi 2025-04-16 11:15:54 +02:00
commit 733fe0716b
2 changed files with 191 additions and 74 deletions

View file

@ -1 +1,8 @@
# bbzw-horizon # bbzw-horizon
## Adruino Firmware
| Fehlercode | LED-Blinkmuster | Bedeutung |
|------------|-------------------|-----------------------------------------------|
| 1 | 1× kurz, Pause | ❌ WLAN nicht verbunden |
| 2 | 2× kurz, Pause | ❌ BME680-Sensor nicht gefunden |
| 3 | 3× kurz, Pause | ❌ Sensor-Initialisierung fehlgeschlagen |
| 4 | 4× kurz, Pause | ❌ API `/health` nicht erreichbar / fehlerhaft |

View file

@ -1,97 +1,207 @@
#include <WiFiNINA.h> #include <WiFiNINA.h>
#include <Wire.h> #include <Wire.h>
#include <Adafruit_Sensor.h> #include "bsec.h"
#include <Adafruit_BME680.h> #include <ArduinoHttpClient.h>
#include <WiFiUdp.h>
#include <NTPClient.h>
// WLAN-Zugangsdaten
#define SSID "" #define SSID ""
#define PASSWORT "" #define PASSWORT ""
#define API_HOST "" // Deine API-Domain
#define API_ENDPOINT "/sensor-data/" // API-Endpunkt
#define API_PORT 8080 // Falls HTTPS, dann 443
#define CLIENT_ID "1.54" // Eindeutige ID für den Arduino
#define API_TOKEN "test2" // Setze hier dein API-Token
Adafruit_BME680 bme; // API-Konfiguration
WiFiClient client; #define API_HOST ""
#define API_PORT 8080
#define API_ENDPOINT "/sensors/push-data"
#define CLIENT_ID ""
#define API_TOKEN ""
// Sensor & Netzwerk
Bsec iaqSensor;
WiFiClient wifi;
HttpClient client = HttpClient(wifi, API_HOST, API_PORT);
// NTP-Client
WiFiUDP ntpUDP;
const long utcOffsetInSeconds = 0;
NTPClient timeClient(ntpUDP, "pool.ntp.org", utcOffsetInSeconds);
// Sendeintervall
unsigned long sendInterval = 30000;
// Fehlercode per LED ausgeben (Morse-artig)
void errorBlink(int code) {
while (true) {
for (int i = 0; i < code; i++) {
digitalWrite(LED_BUILTIN, HIGH);
delay(150);
digitalWrite(LED_BUILTIN, LOW);
delay(150);
}
delay(1000); // Pause zwischen Zyklen
}
}
// Werte begrenzen
float clampValue(float val) {
if (val >= 1000.0) return 999.999;
if (val <= -1000.0) return -999.999;
return val;
}
// ISO-Zeitstempel generieren
String getTimestamp() {
timeClient.update();
unsigned long epochTime = timeClient.getEpochTime();
int year = 1970;
unsigned long seconds = epochTime;
while (true) {
bool leap = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
int daysInYear = leap ? 366 : 365;
if (seconds >= daysInYear * 86400UL) {
seconds -= daysInYear * 86400UL;
year++;
} else {
break;
}
}
int month = 1;
const int daysInMonth[] = {31,28,31,30,31,30,31,31,30,31,30,31};
while (month <= 12) {
int dim = daysInMonth[month - 1];
if (month == 2 && (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))) {
dim = 29;
}
if (seconds >= dim * 86400UL) {
seconds -= dim * 86400UL;
month++;
} else {
break;
}
}
int day = seconds / 86400UL + 1;
seconds = seconds % 86400UL;
int hour = seconds / 3600UL;
seconds = seconds % 3600UL;
int minute = seconds / 60UL;
int second = seconds % 60UL;
char buf[30];
sprintf(buf, "%04d-%02d-%02dT%02d:%02d:%02dZ", year, month, day, hour, minute, second);
return String(buf);
}
// API-Health-Check
bool checkApiHealth() {
HttpClient healthClient = HttpClient(wifi, API_HOST, API_PORT);
healthClient.get("/health");
int statusCode = healthClient.responseStatusCode();
healthClient.stop();
return statusCode == 200;
}
void setup() { void setup() {
Serial.begin(115200); pinMode(LED_BUILTIN, OUTPUT);
while (!Serial); digitalWrite(LED_BUILTIN, LOW);
if (WiFi.status() == WL_NO_MODULE) { // WLAN verbinden mit Timeout
Serial.println("WiFi-Modul nicht gefunden!"); WiFi.begin(SSID, PASSWORT);
while (1); unsigned long startAttemptTime = millis();
while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 20000) {
delay(500);
}
if (WiFi.status() != WL_CONNECTED) {
errorBlink(1); // Fehlercode 1: WLAN-Fehler
}
// API-Healthcheck
if (!checkApiHealth()) {
errorBlink(4); // Fehlercode 4: API nicht erreichbar
}
Wire.begin();
// Automatische Sensor-Erkennung
byte sensorAddress = 0;
byte possibleAddresses[] = {0x76, 0x77};
bool sensorFound = false;
for (int i = 0; i < 2; i++) {
byte addr = possibleAddresses[i];
Wire.beginTransmission(addr);
if (Wire.endTransmission() == 0) {
sensorAddress = addr;
sensorFound = true;
break;
} }
}
WiFi.begin(SSID, PASSWORT); if (!sensorFound) {
Serial.print("Verbinde mit WLAN..."); errorBlink(2); // Fehlercode 2: Sensor nicht gefunden
while (WiFi.status() != WL_CONNECTED) { }
delay(500);
Serial.print(".");
}
Serial.println("\nWLAN verbunden!");
if (!bme.begin()) { iaqSensor.begin(sensorAddress, Wire);
Serial.println("BME680 nicht gefunden!"); if (iaqSensor.bsecStatus != BSEC_OK) {
while (1); errorBlink(3); // Fehlercode 3: Sensor-Init fehlgeschlagen
} }
bme.setTemperatureOversampling(BME680_OS_8X); bsec_virtual_sensor_t sensorList[] = {
bme.setHumidityOversampling(BME680_OS_2X); BSEC_OUTPUT_IAQ,
bme.setPressureOversampling(BME680_OS_4X); BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE,
bme.setIIRFilterSize(BME680_FILTER_SIZE_3); BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY,
bme.setGasHeater(320, 150); BSEC_OUTPUT_RAW_PRESSURE,
BSEC_OUTPUT_RAW_GAS
};
iaqSensor.updateSubscription(sensorList, 5, BSEC_SAMPLE_RATE_LP);
timeClient.begin();
while (!timeClient.update()) {
timeClient.forceUpdate();
}
digitalWrite(LED_BUILTIN, HIGH); // Alles bereit LED dauerhaft an
} }
void loop() { void loop() {
if (!bme.performReading()) { if (iaqSensor.run()) {
Serial.println("Fehler beim Auslesen des BME680!"); // Kurzes LED-Blink zur Messanzeige
return; digitalWrite(LED_BUILTIN, LOW);
} delay(100);
digitalWrite(LED_BUILTIN, HIGH);
if (WiFi.status() == WL_CONNECTED) { float temperature = clampValue(iaqSensor.temperature);
Serial.println("Sende Daten an API..."); float humidity = clampValue(iaqSensor.humidity);
float voc = clampValue(iaqSensor.iaq);
float gas = clampValue(iaqSensor.gasResistance / 1000.0);
float pressure = iaqSensor.pressure / 100.0;
String jsonPayload = "{"; String timestamp = getTimestamp();
jsonPayload += "\"token\": \"" + String(API_TOKEN) + "\",";
jsonPayload += "\"clientid\": \"" + String(CLIENT_ID) + "\",";
jsonPayload += "\"temperature\": " + String(bme.temperature) + ",";
jsonPayload += "\"humidity\": " + String(bme.humidity) + ",";
jsonPayload += "\"pressure\": " + String(bme.pressure / 100.0) + ",";
jsonPayload += "\"voc\": " + String(0.0) + ","; // Falls VOC nicht gemessen wird
jsonPayload += "\"gas\": " + String(bme.gas_resistance / 1000.0);
jsonPayload += "}";
if (client.connect(API_HOST, API_PORT)) { // Falls HTTPS genutzt wird, dann WiFiSSLClient String payload = "{";
client.println("POST " + String(API_ENDPOINT) + " HTTP/1.1"); payload += "\"timestamp\": \"" + timestamp + "\",";
client.println("Host: " + String(API_HOST)); payload += "\"temperature\": " + String(temperature, 3) + ",";
client.println("Content-Type: application/json"); payload += "\"humidity\": " + String(humidity, 3) + ",";
client.print("Content-Length: "); payload += "\"pressure\": " + String(pressure, 3) + ",";
client.println(jsonPayload.length()); payload += "\"voc\": " + String(voc, 3) + ",";
client.println(); payload += "\"gas\": " + String(gas, 3);
client.println(jsonPayload); payload += "}";
unsigned long timeout = millis() + 5000; // Wartezeit für die Antwort String fullPath = String(API_ENDPOINT) + "?client=" + CLIENT_ID;
while (client.available() == 0) {
if (millis() > timeout) {
Serial.println("Timeout bei API-Antwort!");
client.stop();
return;
}
}
while (client.available()) { client.beginRequest();
String response = client.readString(); client.post(fullPath);
Serial.println("API Antwort: " + response); client.sendHeader("Content-Type", "application/json");
} client.sendHeader("token", API_TOKEN);
} else { client.sendHeader("Content-Length", payload.length());
Serial.println("Fehler beim Verbinden mit API!"); client.beginBody();
} client.print(payload);
client.endRequest();
client.stop(); client.responseStatusCode();
} else { client.responseBody();
Serial.println("WLAN nicht verbunden!"); }
}
delay(5000); delay(sendInterval);
} }