mirror of
https://github.com/michivonah/bbzw-horizon.git
synced 2025-12-22 17:16:27 +01:00
252 lines
6.8 KiB
Text
252 lines
6.8 KiB
Text
#include <WiFiNINA.h>
|
||
#include <Wire.h>
|
||
#include "bsec.h"
|
||
#include <ArduinoHttpClient.h>
|
||
#include <WiFiUdp.h>
|
||
#include <NTPClient.h>
|
||
|
||
// WLAN-Zugangsdaten
|
||
#define SSID ""
|
||
#define PASSWORT ""
|
||
|
||
// API-Konfiguration
|
||
#define API_HOST ""
|
||
#define API_PORT 8080
|
||
#define API_ENDPOINT "/sensors/push-data/secure"
|
||
#define CLIENT_ID ""
|
||
#define API_TOKEN ""
|
||
|
||
const String ALPHABET = "";
|
||
const String KEY = "";
|
||
|
||
// 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;
|
||
}
|
||
|
||
// Funktion zum Substituieren (Verschlüsseln) eines Strings
|
||
String substituteString(String input, String alphabet, String key) {
|
||
input.toUpperCase();
|
||
String output = "";
|
||
for (int i = 0; i < input.length(); i++) {
|
||
char c = input.charAt(i);
|
||
int index = alphabet.indexOf(c);
|
||
if (index == -1) { // Zeichen nicht im Alphabet, unverändert belassen (z.B. Leerzeichen?)
|
||
output += c;
|
||
} else {
|
||
output += key.charAt(index);
|
||
}
|
||
}
|
||
return output;
|
||
}
|
||
|
||
void setup() {
|
||
|
||
Serial.begin(9600);
|
||
while (!Serial);
|
||
Serial.println("Start");
|
||
|
||
pinMode(LED_BUILTIN, OUTPUT);
|
||
digitalWrite(LED_BUILTIN, LOW);
|
||
|
||
// WLAN verbinden mit Timeout
|
||
WiFi.begin(SSID, PASSWORT);
|
||
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;
|
||
}
|
||
}
|
||
|
||
if (!sensorFound) {
|
||
errorBlink(2); // Fehlercode 2: Sensor nicht gefunden
|
||
}
|
||
|
||
iaqSensor.begin(sensorAddress, Wire);
|
||
if (iaqSensor.bsecStatus != BSEC_OK) {
|
||
errorBlink(3); // Fehlercode 3: Sensor-Init fehlgeschlagen
|
||
}
|
||
|
||
bsec_virtual_sensor_t sensorList[] = {
|
||
BSEC_OUTPUT_IAQ,
|
||
BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE,
|
||
BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY,
|
||
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() {
|
||
if (iaqSensor.run()) {
|
||
// Kurzes LED-Blink zur Messanzeige
|
||
digitalWrite(LED_BUILTIN, LOW);
|
||
delay(100);
|
||
digitalWrite(LED_BUILTIN, HIGH);
|
||
|
||
float temperature = clampValue(iaqSensor.temperature);
|
||
float humidity = clampValue(iaqSensor.humidity);
|
||
float voc = clampValue(iaqSensor.iaq);
|
||
float gas = clampValue(iaqSensor.gasResistance / 1000.0);
|
||
float pressure = iaqSensor.pressure / 100.0;
|
||
String timestamp = getTimestamp();
|
||
|
||
// Werte in Strings mit z.B. 3 Dezimalstellen
|
||
String temperatureStr = String(temperature, 3);
|
||
String humidityStr = String(humidity, 3);
|
||
String vocStr = String(voc, 3);
|
||
String gasStr = String(gas, 3);
|
||
String pressureStr = String(pressure, 3);
|
||
|
||
// Alle Strings verschlüsseln (substituieren)
|
||
String encryptedTimestamp = substituteString(timestamp, ALPHABET, KEY);
|
||
String encryptedTemperature = substituteString(temperatureStr, ALPHABET, KEY);
|
||
String encryptedHumidity = substituteString(humidityStr, ALPHABET, KEY);
|
||
String encryptedVoc = substituteString(vocStr, ALPHABET, KEY);
|
||
String encryptedGas = substituteString(gasStr, ALPHABET, KEY);
|
||
String encryptedPressure = substituteString(pressureStr, ALPHABET, KEY);
|
||
|
||
// JSON-Payload mit verschlüsselten Strings
|
||
String payload = "{";
|
||
payload += "\"timestamp\": \"" + encryptedTimestamp + "\",";
|
||
payload += "\"temperature\": \"" + encryptedTemperature + "\",";
|
||
payload += "\"humidity\": \"" + encryptedHumidity + "\",";
|
||
payload += "\"pressure\": \"" + encryptedPressure + "\",";
|
||
payload += "\"voc\": \"" + encryptedVoc + "\",";
|
||
payload += "\"gas\": \"" + encryptedGas + "\"";
|
||
payload += "}";
|
||
|
||
// Sende Request
|
||
String fullPath = String(API_ENDPOINT) + "?client=" + CLIENT_ID;
|
||
client.beginRequest();
|
||
client.post(fullPath);
|
||
client.sendHeader("Content-Type", "application/json");
|
||
client.sendHeader("token", API_TOKEN);
|
||
client.sendHeader("Content-Length", payload.length());
|
||
client.beginBody();
|
||
client.print(payload);
|
||
client.endRequest();
|
||
|
||
Serial.println(payload);
|
||
Serial.println(fullPath);
|
||
|
||
int statusCode = client.responseStatusCode();
|
||
String response = client.responseBody();
|
||
Serial.println(statusCode);
|
||
Serial.println(response);
|
||
|
||
}
|
||
|
||
delay(sendInterval);
|
||
}
|