#include #include #include #include #include #include #include // HC-SR04 Ultraschallsensor Pins #define TRIG_PIN 12 #define ECHO_PIN 13 // Batterie-Messung (LilyGO T3 V1.6.1) #define BATTERY_PIN 35 // ADC1_CH7 für Batterie-Messung #define BATTERY_ADC_MULTIPLIER 2.0 // Spannungsteiler 1:1 (2× Faktor) // Tank-Konfiguration (Füllstandsmessung) #define TANK_HEIGHT 160.0 // Tank-Höhe in cm (innen) #define SENSOR_MOUNT_OFFSET 14.0 // Montageabstand: Sensor über Tank-Oberkante in cm #define SENSOR_MIN_DISTANCE 2.0 // HC-SR04 Mindestabstand (technische Grenze) // Deep Sleep Konfiguration (Batteriebetrieb) #define ENABLE_DEEP_SLEEP false // true = Deep Sleep aktiv, false = Deep Sleep deaktiviert (für Tests) #define SLEEP_INTERVAL 300 // Sleep-Zeit in Sekunden (1800 = 30 Minuten) #define DISPLAY_ON_TIME 10000 // Display-Anzeigedauer in ms (30000 = 30 Sekunden) #define uS_TO_S_FACTOR 1000000 // Umrechnung Mikrosekunden zu Sekunden HCSR04 distanceSensor(TRIG_PIN, ECHO_PIN); // OLED-Display (I2C: SDA 21, SCL 22 beim LilyGO T3 V1.6.1) #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); // LoRaWAN Keys (hier deine echten Werte eintragen) static const u1_t PROGMEM APPEUI[8] = { 0x01, 0x00, 0x00, 0xD0, 0x7E, 0xD5, 0xB3, 0x70 }; static const u1_t PROGMEM DEVEUI[8] = { 0xB2, 0xA1, 0x05, 0xD0, 0x7E, 0xD5, 0xB3, 0x70 }; static const u1_t PROGMEM APPKEY[16] = { 0xA1, 0xB2, 0xC3, 0xD4, 0xE5, 0xF6, 0x07, 0x18, 0x29, 0x38, 0x47, 0x56, 0xAB, 0xCD, 0xEF, 0x12 }; void os_getArtEui(u1_t *buf) { memcpy_P(buf, APPEUI, 8); } void os_getDevEui(u1_t *buf) { memcpy_P(buf, DEVEUI, 8); } void os_getDevKey(u1_t *buf) { memcpy_P(buf, APPKEY, 16); } static osjob_t sendjob; const unsigned TX_INTERVAL = 30; // Nicht verwendet bei Deep Sleep // RTC Memory für Deep Sleep (bleibt beim Sleep erhalten) RTC_DATA_ATTR int bootCount = 0; RTC_DATA_ATTR bool joinedNetwork = false; // Globale Variablen unsigned long displayStartTime = 0; bool dataSent = false; bool userDataScheduled = false; // Flag: Nutzdaten (nicht nur MAC) wurden gesendet // LoRa Pinout für LilyGO T3 V1.6.1 const lmic_pinmap lmic_pins = { .nss = 18, .rxtx = LMIC_UNUSED_PIN, .rst = 23, .dio = {26, 33, 32} }; // Forward-Deklaration void do_send(osjob_t* j); // Deep Sleep aktivieren void goToSleep() { #if ENABLE_DEEP_SLEEP Serial.println(F("=== Deep Sleep aktivieren ===")); Serial.print(F("Schlafdauer: ")); Serial.print(SLEEP_INTERVAL); Serial.print(F(" Sekunden (")); Serial.print(SLEEP_INTERVAL / 60); Serial.println(F(" Minuten)")); // Display ausschalten display.clearDisplay(); display.display(); display.ssd1306_command(0xAE); // Display OFF // Deep Sleep konfigurieren esp_sleep_enable_timer_wakeup(SLEEP_INTERVAL * uS_TO_S_FACTOR); Serial.println(F("Gehe in Deep Sleep...")); Serial.flush(); // Deep Sleep starten esp_deep_sleep_start(); #else Serial.println(F("=== Deep Sleep DEAKTIVIERT (Test-Modus) ===")); Serial.print(F("Nächste Messung in ")); Serial.print(SLEEP_INTERVAL); Serial.println(F(" Sekunden...")); // Variablen zurücksetzen für nächste Messung dataSent = false; displayStartTime = 0; userDataScheduled = false; // Warte SLEEP_INTERVAL Sekunden, dann sende erneut os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(SLEEP_INTERVAL), do_send); #endif } void do_send(osjob_t* j) { // Batteriespannung messen uint16_t batteryRaw = analogRead(BATTERY_PIN); float batteryVoltage = (batteryRaw / 4095.0) * 3.3 * BATTERY_ADC_MULTIPLIER; // Batterie-Prozent berechnen (LiPo 3.0V = 0%, 4.2V = 100%) float batteryPercent = ((batteryVoltage - 3.0) / (4.2 - 3.0)) * 100.0; if (batteryPercent < 0) batteryPercent = 0; if (batteryPercent > 100) batteryPercent = 100; // Distanz messen (in cm) float distance = distanceSensor.dist(); // Prüfen ob Messung gültig ist if (distance < 0) { Serial.println(F("Fehler beim Messen der Distanz!")); distance = 0; // Fehlerfall: 0 senden } // Füllstand berechnen // Gesamtabstand bei leerem Tank = Montageabstand + Tank-Höhe float emptyDistance = SENSOR_MOUNT_OFFSET + TANK_HEIGHT; // Füllhöhe = Leerer Tank - gemessene Distanz float fillHeight = emptyDistance - distance; // Korrektur: Negativ = leer, über Tank-Höhe = voll if (fillHeight < 0) fillHeight = 0; if (fillHeight > TANK_HEIGHT) fillHeight = TANK_HEIGHT; // Füllstand in Prozent float fillPercent = (fillHeight / TANK_HEIGHT) * 100.0; // Warnung bei kritischem Abstand (zu nah am Sensor) bool tooClose = (distance < (SENSOR_MOUNT_OFFSET + SENSOR_MIN_DISTANCE)); // Distanz in mm umrechnen und als uint16_t speichern (0-65535 mm = 0-65.5 m) uint16_t distanceMm = (uint16_t)(distance * 10); // Füllstand in mm für LoRaWAN uint16_t fillHeightMm = (uint16_t)(fillHeight * 10); Serial.println(F("--- Messung ---")); Serial.print("Batterie: "); Serial.print(batteryVoltage, 2); Serial.print("V ("); Serial.print(batteryPercent, 0); Serial.println("%)"); Serial.print("Distanz: "); Serial.print(distance); Serial.println(" cm"); Serial.print("Füllhöhe: "); Serial.print(fillHeight); Serial.println(" cm"); Serial.print("Füllstand: "); Serial.print(fillPercent, 1); Serial.println(" %"); if (tooClose) { Serial.println(F("WARNUNG: Flüssigkeit zu nah am Sensor!")); } // Display aktualisieren display.clearDisplay(); // Header: iotwave.de display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(0, 0); display.println(F("iotwave.de")); display.drawLine(0, 10, 128, 10, SSD1306_WHITE); // Füllstand groß anzeigen display.setTextSize(3); display.setCursor(0, 16); display.print(fillPercent, 0); display.println(F("%")); // Füllhöhe klein anzeigen display.setTextSize(1); display.setCursor(0, 48); display.print(F("Fuellhoehe: ")); display.print(fillHeight, 1); display.println(F("cm")); // Distanz anzeigen display.setCursor(0, 56); display.print(F("Distanz: ")); display.print(distance, 1); display.println(F("cm")); display.display(); // Display-Timer starten (nur beim ersten Mal) if (displayStartTime == 0) { displayStartTime = millis(); Serial.println(F("Display-Timer gestartet")); } // Prüfen ob LMIC bereit ist zum Senden if (LMIC.opmode & OP_TXRXPEND) { Serial.println(F("OP_TXRXPEND, nicht gesendet")); } else { // LoRaWAN Payload vorbereiten (8 Bytes): // Byte 0-1: Distanz in mm (uint16_t) // Byte 2-3: Füllhöhe in mm (uint16_t) // Byte 4-5: Füllstand in % * 10 (z.B. 855 = 85.5%) // Byte 6-7: Batteriespannung in mV (uint16_t) uint16_t fillPercentInt = (uint16_t)(fillPercent * 10); uint16_t batteryMv = (uint16_t)(batteryVoltage * 1000); uint8_t payload[8] = { highByte(distanceMm), // Distanz High Byte lowByte(distanceMm), // Distanz Low Byte highByte(fillHeightMm), // Füllhöhe High Byte lowByte(fillHeightMm), // Füllhöhe Low Byte highByte(fillPercentInt), // Füllstand % High Byte lowByte(fillPercentInt), // Füllstand % Low Byte highByte(batteryMv), // Batterie High Byte lowByte(batteryMv) // Batterie Low Byte }; // Debug: Payload anzeigen Serial.print(F("LoRa Payload (Hex): ")); for (int i = 0; i < 8; i++) { if (payload[i] < 16) Serial.print("0"); Serial.print(payload[i], HEX); Serial.print(" "); } Serial.println(); LMIC_setTxData2(1, payload, sizeof(payload), 0); Serial.println(F("LoRa Payload gesendet")); userDataScheduled = true; // Markiere dass Nutzdaten gesendet wurden } } void onEvent(ev_t ev) { switch(ev) { case EV_JOINING: Serial.println(F("Beitritt läuft...")); break; case EV_JOINED: Serial.println(F("Netzwerkbeitritt erfolgreich")); joinedNetwork = true; // Merken für nächsten Boot // Warte 5 Sekunden, damit MAC-Commands abgeschlossen werden os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(5), do_send); break; case EV_TXCOMPLETE: Serial.println(F("Nachricht übertragen.")); // Debug: Frame Counter Serial.print(F("Frame Counter: ")); Serial.println(LMIC.seqnoUp - 1); if (LMIC.txrxFlags & TXRX_ACK) { Serial.println(F("ACK empfangen")); } if (LMIC.dataLen > 0) { Serial.print(F("Downlink empfangen: ")); Serial.print(LMIC.dataLen); Serial.println(F(" Bytes")); } // Prüfe ob echte Nutzdaten gesendet wurden if (userDataScheduled) { Serial.println(F("Nutzdaten erfolgreich gesendet")); // Markiere Daten als gesendet if (!dataSent) { dataSent = true; Serial.print(F("Übertragung abgeschlossen. Display bleibt noch ")); Serial.print((DISPLAY_ON_TIME - (millis() - displayStartTime)) / 1000); Serial.println(F(" Sekunden an...")); } } else { Serial.println(F("Nur MAC-Commands gesendet, warte auf nächstes TX...")); // Sende Nutzdaten erneut nach 3 Sekunden os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(3), do_send); } break; default: Serial.print(F("Ereignis: ")); Serial.println(ev); break; } } void setup() { Serial.begin(115200); delay(1000); // Boot-Zähler erhöhen bootCount++; Serial.println(F("\n=== ESP32 aufgewacht ===")); Serial.print(F("Boot-Nummer: ")); Serial.println(bootCount); // Wakeup-Grund anzeigen esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause(); if (wakeup_reason == ESP_SLEEP_WAKEUP_TIMER) { Serial.println(F("Aufgewacht durch Timer (5 Minuten vergangen)")); } else { Serial.println(F("Erster Start oder Reset")); bootCount = 1; joinedNetwork = false; } // OLED Display initialisieren display.begin(SSD1306_SWITCHCAPVCC, 0x3C); display.clearDisplay(); // Kurzer Startup-Screen (nur 1 Sekunde, Batterie sparen!) display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(0, 0); display.println(F("iotwave.de")); display.setCursor(0, 20); display.print(F("Boot #")); display.println(bootCount); display.setCursor(0, 35); display.println(F("Initialisiere...")); display.display(); delay(1000); // LMIC initialisieren os_init(); LMIC_reset(); LMIC_setClockError(MAX_CLOCK_ERROR * 1 / 100); // OTAA Join starten LMIC_startJoining(); Serial.println(F("Starte LoRaWAN...")); // Variablen zurücksetzen dataSent = false; displayStartTime = 0; userDataScheduled = false; } void loop() { os_runloop_once(); // Prüfe ob Display-Zeit abgelaufen ist und Daten gesendet wurden if (dataSent && displayStartTime > 0) { unsigned long currentTime = millis(); unsigned long elapsedTime = currentTime - displayStartTime; // Nach DISPLAY_ON_TIME (30 Sekunden): Deep Sleep if (elapsedTime >= DISPLAY_ON_TIME) { Serial.println(F("Display-Zeit abgelaufen, gehe in Deep Sleep...")); goToSleep(); } } }