ESP32 + RFM95W + 9DOF + Deep Sleep

Hello everyone,

I have developed a LoRaWAN sensor that transmits its data to TTN . The hardware is based on a ESP board. The software for the sensor is written using the Arduino IDE and is based on the LMIC library. When powered on, the sensor operates correctly, transmitting data to TTN as expected. I’m sending one packet everytime the 9DOF Sensor moves this is like every 10 minutes or something.

I tried implementing deep sleep mode to save power. While this partially improved the battery life, I ran into a different problem: the data transmission became unreliable. The node transmits the data as expected, and the packets are received by the gateway. However, the data does not consistently appear on TTN. Sometimes no data arrives for a long time, and sometimes multiple packets are received in a row. I also have implemented the LMIC_shutdown(); function but it really did not help.

Has anyone experienced similar issues when using deep sleep with LMIC and LoRaWAN? If so, were you able to find a solution? Any advice or insights would be greatly appreciated.
I also uploaded my Code, i hope someone can help me.

#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_LSM303_U.h>
#include <esp_sleep.h>
#include <Adafruit_9DOF.h>
#include <LSM303.h>
#include <WiFi.h>
#include <Preferences.h>
#include <lmic.h>
#include <hal/hal.h>
#include <lmic.h>
#include <Preferences.h>

// Sensor-Instanzen erstellen
Adafruit_LSM303_Accel_Unified accel = Adafruit_LSM303_Accel_Unified(54321);
Adafruit_LSM303_Mag_Unified mag = Adafruit_LSM303_Mag_Unified(12345);
LSM303 lsm;

// Variablen für Kalibrierung
float closedAccelX, closedAccelY, closedAccelZ;
float closedMagX, closedMagY, closedMagZ;
float openAccelX, openAccelY, openAccelZ;
float openMagX, openMagY, openMagZ;
float tiltedAccelX, tiltedAccelY, tiltedAccelZ;   // Werte für den gekippten Zustand
float tiltedMagX, tiltedMagY, tiltedMagZ;
float halfOpenAccelX, halfOpenAccelY, halfOpenAccelZ; // Werte für den halb offenen Zustand
float halfOpenMagX, halfOpenMagY, halfOpenMagZ;

// Schwellenwerte für Statuserkennung
float accelThreshold = 0.5;  // Toleranzwert für Beschleunigung
float magThreshold = 5.0;    // Toleranzwert für Magnetometer
float halfOpenThreshold = 5.0; // Schwellenwert für "halb offen"
float tiltThreshold = 1.5;   // Schwellenwert für Kipp-Winkel (z.B. y-Achse)
unsigned long tiltStartTime = 0;
const unsigned long tiltDelayTime = 2000; // 2 Sekunden Verzögerung für "gekippt"

// Taster-Pin und Status
const int buttonPin = 13;    // Pin für den Taster
bool lastButtonState = HIGH; // Letzter Tasterstatus
unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 50; // Entprellzeit in Millisekunden
Preferences preferences;

// Funktionsprototypen
void calibrateClosedState();
void calibrateOpenState();
void calibrateTiltedState();     // Funktion für gekippten Zustand
void calibrateHalfOpenState();   // Funktion für halb offenen Zustand
void saveCalibrationData();
void loadCalibrationData();
void printCalibrationData();
bool isButtonPressed();
void checkWindowState();
String getWindowState(sensors_event_t *accelEvent, sensors_event_t *magEvent);
float calculateWindowOpenPercentage(sensors_event_t *accelEvent, sensors_event_t *magEvent);
float calculateTiltPercentage(sensors_event_t *accelEvent);


// Variablen für Deep Sleep und Interrupt
unsigned long noMagneticChangeDuration = 0; // Zeit ohne Magnetfeldänderung
const unsigned long noMagneticChangeThreshold = 10000; // 10 Sekunden ohne Änderung, um in Sleep zu gehen
const unsigned long stayAwakeDurationAfterWakeup = 5000; // 5 Sekunden wach bleiben nach dem Aufwachen
const unsigned long outputInterval = 1000; // Zeitintervall zwischen Ausgaben in Millisekunden
unsigned long lastOutputTime = 0;    // Zeitpunkt der letzten Ausgabe
unsigned long wakeupStartTime = 0;   // Zeitpunkt, an dem der ESP aufgewacht ist
const int sda = 21;
const int scl = 22;
const int intPin = 15; // Interrupt-Pin für LSM303

// Magnetfeldänderungsschwellenwert
float magneticThreshold = 5.0;
// Flag, um festzustellen, ob der ESP nach dem Wakeup wach bleiben soll
bool stayAwakeAfterWakeup = false;

unsigned long noMovementDuration = 0;  // Dauer ohne Bewegung
const unsigned long noMovementThreshold = 5000; // Zeit ohne Bewegung (5 Sekunden)
bool movementDetected = false; // Flag für die Bewegungserkennung

// TTN Keys (Little Endian Format)
static const u1_t PROGMEM DEVEUI[8] = { };  // DevEUI
static const u1_t PROGMEM APPEUI[8] = { };  // AppEUI
static const u1_t PROGMEM APPKEY[16] = {}; // AppKey
char mydata[32];  // Speicher für die Nachricht
static osjob_t sendjob;



unsigned long halfOpenStartTime = 0;  // Startzeit des halb offenen Zustands
const unsigned long halfOpenTimeout = 10000;  // Timeout für 10 Sekunden
float previousAccelMovement = 0;
float previousMagMovement = 0;
unsigned long lastMovementCheckTime = 0;
float fluctuationThreshold = 0.05; // 5% fluctuation threshold
float lastAccelY = 0;   // Variable für die vorherige Beschleunigung auf der Y-Achse
bool isTilted = false;  // Zustand "Gekippt" merken

// Pin mapping for the ESP32 (adjust for your setup)
const lmic_pinmap lmic_pins = {
    .nss = 5,       // LoRa Chip Select (NSS)
    .rxtx = LMIC_UNUSED_PIN,
    .rst = 14,      // Reset Pin
    .dio = {26, 33, 32}  // DIO0, DIO1, DIO2
};
// Provide keys to the LMIC library
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); }
bool sendComplete = false; // Flag für abgeschlossene Übertragung
unsigned long lastMovementTime = 0;  // Zeit der letzten Bewegung

// Handle LoRaWAN events
void onEvent(ev_t ev) {
    switch(ev) {
        case EV_TXCOMPLETE:
            Serial.println("LoRa-Übertragung abgeschlossen.");
            if (LMIC.txrxFlags & TXRX_ACK) {
                Serial.println("ACK erhalten.");
            }
            sendComplete = true;  // Übertragung ist abgeschlossen
            break;
        case EV_JOINING:
            Serial.println("Starte LoRaWAN-Join...");
            break;
        case EV_JOINED:
            Serial.println("Erfolgreich bei TTN verbunden!");
            break;
        default:
            Serial.print("Unbekanntes Ereignis: ");
            Serial.println((unsigned)ev);
            break;
    }
}
void do_send(osjob_t *j) {
    if (LMIC.opmode & OP_TXRXPEND) {
        Serial.println(F("OP_TXRXPEND, not sending"));
    } else {
        sensors_event_t accelEvent, magEvent;
        mag.getEvent(&magEvent);
        accel.getEvent(&accelEvent);

        // Bestimme den aktuellen Fensterstatus
        String windowState = getWindowState(&accelEvent, &magEvent);
        uint8_t status = 0;      // 0 = Unbekannt, 1 = Gekippt, 2 = Offen
        uint8_t percentage = 0;  // Prozentwert (0–100)

        if (windowState == "Gekippt") {
            status = 1; // Gekippt
            percentage = static_cast<uint8_t>(calculateTiltPercentage(&accelEvent));
        } else if (windowState == "Offen") {
            status = 2; // Offen
            percentage = static_cast<uint8_t>(calculateWindowOpenPercentage(&accelEvent, &magEvent));
        }

        // Nachricht zusammenstellen (2 Byte)
        uint8_t message[2] = {status, percentage};

        // Nachricht an TTN senden
        LMIC_setTxData2(1, message, sizeof(message), 0);

        // Debug-Ausgabe
        Serial.print(F("Packet queued: Status = "));
        Serial.print(status);
        Serial.print(", Percentage = ");
        Serial.println(percentage);
    }
}


void setup(void) {
    Serial.begin(115200);
    Wire.begin(sda, scl); // I2C Initialisierung
    pinMode(buttonPin, INPUT_PULLUP);
    pinMode(intPin, INPUT);  // Setzt den Interrupt-Pin für den LSM303

     Serial.println(F("Starting LoRaWAN..."));
    os_init();
    LMIC_reset();
    LMIC_startJoining();
    do_send(&sendjob);



    Serial.println("Fensterstatus-Erkennung gestartet.");
    // Sensoren initialisieren
    if (!accel.begin()) {
        Serial.println("Sensorinitialisierung fehlgeschlagen!");
        while (1);
    }
    if (!mag.begin()) {
        Serial.println("Magnetometerinitialisierung fehlgeschlagen!");
        while (1);
    }
    // Kalibrierungswerte laden oder initialisieren
    preferences.begin("calibration", false); // Namespace öffnen
    if (preferences.getBool("calibrated", false)) {
        loadCalibrationData();
        printCalibrationData();
    } else {
        Serial.println("Bitte das Fenster in den *geschlossenen Zustand* bringen.");
        delay(5000);
        calibrateClosedState();
        Serial.println("Bitte das Fenster in den *offenen Zustand* bringen.");
        delay(10000);
        calibrateOpenState();
        Serial.println("Bitte das Fenster in den *gekippten Zustand* bringen.");
        delay(5000);
        calibrateTiltedState();
        Serial.println("Bitte das Fenster in den *halb offenen Zustand* bringen.");
        delay(10000);
        calibrateHalfOpenState();
        saveCalibrationData();
        printCalibrationData();
    }
    // Magnetometer-Register konfigurieren
    lsm.writeMagReg(LSM303::CRA_REG_M, 0x10);        // 15 Hz Output-Datenrate für das Magnetometer
    lsm.writeMagReg(LSM303::MR_REG_M, 0x00);         // Continuous-Conversion-Mode
    lsm.writeMagReg(LSM303::INT_CTRL_M, 0xF8);       // Interrupt auf X, Y und Z aktivieren
    lsm.writeMagReg(LSM303::INT_THS_L_M, 0x20);      // Schwellenwert (Low-Byte, z. B. 0x20 = ~0.25 Gauss)
    lsm.writeMagReg(LSM303::INT_THS_H_M, 0x00);      // Schwellenwert (High-Byte)

    // ESP32 auf externen Interrupt über GPIO12 vorbereiten
    esp_sleep_enable_ext0_wakeup(GPIO_NUM_15, 1); // Interrupt bei steigender Flanke auf GPIO12 (INT LSM303)


    WiFi.mode(WIFI_OFF);    // WiFi deaktivieren, um Strom zu sparen
    btStop();               // Bluetooth deaktivieren
    setCpuFrequencyMhz(80); // CPU-Frequenz reduzieren für geringeren Stromverbrauch

    // Lese aktuelle Magnetometer- und Accelerometerdaten
    sensors_event_t magEvent, accelEvent;
    mag.getEvent(&magEvent);
    accel.getEvent(&accelEvent);

    // Nach dem Wakeup wach bleiben
    stayAwakeAfterWakeup = true;
    wakeupStartTime = millis(); // Speichere die Zeit des Wakeups
}



void loop(void) {

    static String lastWindowState = ""; // Zustand speichern
    static unsigned long lastStatusTime = 0;
    const unsigned long statusRepeatInterval = 2000; // Status alle 2 Sekunden wiederholen
    static unsigned long noMovementDuration = millis();

    os_runloop_once();  // Process LoRaWAN events
    // Button für Kalibrierung überprüfen
    if (isButtonPressed()) {
        Serial.println("Kalibrierung wird zurückgesetzt...");
        delay(5000);
        calibrateClosedState();
        delay(5000);
        calibrateOpenState();
        delay(10000);
        calibrateTiltedState();
        delay(5000);
        calibrateHalfOpenState();
        saveCalibrationData();
        printCalibrationData();
        delay(2000);
    }

    // Nach Wakeup kurze Verzögerung
    if (millis() - wakeupStartTime < 2000) {
        delay(2000);
    }

    // Magnetometer- und Accelerometer-Daten abrufen
    sensors_event_t magEvent;
    mag.getEvent(&magEvent);
    sensors_event_t accelEvent;
    accel.getEvent(&accelEvent);


   // Fensterstatus bestimmen
    String currentWindowState = getWindowState(&accelEvent, &magEvent);
    if (millis() - lastStatusTime > statusRepeatInterval) {
        lastStatusTime = millis();
        if (isTilted) {
            float tiltPercentage = calculateTiltPercentage(&accelEvent);
            Serial.print("Fenster gekippt, Kippwinkel: ");
            Serial.print(tiltPercentage, 1);
            Serial.println("%");
        } else if (currentWindowState == "Offen") {
            float openPercentage = calculateWindowOpenPercentage(&accelEvent, &magEvent);
            Serial.print("Fenster offen: ");
            Serial.print(openPercentage, 1);
            Serial.println("%");
        }
    }

    float accelMovementClosed = sqrt(pow(accelEvent.acceleration.x - closedAccelX, 2) +
                                     pow(accelEvent.acceleration.y - closedAccelY, 2) +
                                     pow(accelEvent.acceleration.z - closedAccelZ, 2));
    float accelMovementOpen = sqrt(pow(accelEvent.acceleration.x - openAccelX, 2) +
                                   pow(accelEvent.acceleration.y - openAccelY, 2) +
                                   pow(accelEvent.acceleration.z - openAccelZ, 2));
    float accelMovementTilted = sqrt(pow(accelEvent.acceleration.x - tiltedAccelX, 2) +
                                     pow(accelEvent.acceleration.y - tiltedAccelY, 2) +
                                     pow(accelEvent.acceleration.z - tiltedAccelZ, 2));
    float accelMovementHalfOpen = sqrt(pow(accelEvent.acceleration.x - halfOpenAccelX, 2) +
                                       pow(accelEvent.acceleration.y - halfOpenAccelY, 2) +
                                       pow(accelEvent.acceleration.z - halfOpenAccelZ, 2));
    // Minimalen Bewegungswert aus allen Zuständen berechnen
    float accelMovement = min(min(accelMovementClosed, accelMovementOpen),
                              min(accelMovementTilted, accelMovementHalfOpen));
    // Analog für Magnetometerdaten
    float magMovementClosed = sqrt(pow(magEvent.magnetic.x - closedMagX, 2) +
                                   pow(magEvent.magnetic.y - closedMagY, 2) +
                                   pow(magEvent.magnetic.z - closedMagZ, 2));
    float magMovementOpen = sqrt(pow(magEvent.magnetic.x - openMagX, 2) +
                                 pow(magEvent.magnetic.y - openMagY, 2) +
                                 pow(magEvent.magnetic.z - openMagZ, 2));
    float magMovementTilted = sqrt(pow(magEvent.magnetic.x - tiltedMagX, 2) +
                                   pow(magEvent.magnetic.y - tiltedMagY, 2) +
                                   pow(magEvent.magnetic.z - tiltedMagZ, 2));
    float magMovementHalfOpen = sqrt(pow(magEvent.magnetic.x - halfOpenMagX, 2) +
                                     pow(magEvent.magnetic.y - halfOpenMagY, 2) +
                                     pow(magEvent.magnetic.z - halfOpenMagZ, 2));
    // Minimalen Bewegungswert aus allen Zuständen berechnen
    float magMovement = min(min(magMovementClosed, magMovementOpen),
                            min(magMovementTilted, magMovementHalfOpen));


        float accelFluctuation = abs(accelMovement - previousAccelMovement) / previousAccelMovement;
        float magFluctuation = abs(magMovement - previousMagMovement) / previousMagMovement;

        // Überprüfe, ob die Fluktuationen größer als der Schwellenwert (5%) sind
        if (sendComplete) {
    // Fluktuationen prüfen, um zu entscheiden, ob Deep Sleep aktiviert wird
    if (accelFluctuation <= fluctuationThreshold && magFluctuation <= fluctuationThreshold) {
        Serial.println("Fensterzustand stabil, Deep Sleep aktivieren.");
        LMIC_shutdown();  // Shutdown LMIC to prevent reinitialization issues
         Serial.println("LMIC SHUTDOWN");
        delay(1000);
        esp_deep_sleep_start(); // Deep Sleep aktivieren
    } else {
        // Wenn Bewegung oder Änderung erkannt wird, speichere die neuen Werte
        previousAccelMovement = accelMovement;
        previousMagMovement = magMovement;
        lastMovementCheckTime = millis();
    }

    // Überprüfen, ob keine Bewegung erkannt wurde und in den Deep Sleep gehen
    if (accelMovement > accelThreshold) {
        noMovementDuration = millis();  // Timer zurücksetzen bei Bewegungserkennung
        movementDetected = true;
    } else {
        movementDetected = false;
    }

    // Keine Bewegung für länger als den Schwellwert -> Deep Sleep
    if (millis() - noMovementDuration > noMovementThreshold) {
        Serial.println("Keine Bewegung erkannt, prüfe Übertragungsstatus...");
        if (sendComplete) {  // Nur in den Deep Sleep gehen, wenn die Übertragung abgeschlossen ist
            Serial.println("Übertragung abgeschlossen, ESP geht in Deep Sleep...");
            LMIC_shutdown();  // Shutdown LMIC to prevent reinitialization issues
            Serial.println("LMIC SHUTDOWN");
            delay(1000);
            esp_deep_sleep_start(); // Deep-Sleep aktivieren
        } else {
            Serial.println("Warte auf abgeschlossene Übertragung...");
        }
    }


        // LoRaWAN Status überprüfen und Daten nur senden, wenn keine Übertragung anhängig ist

        if (!(LMIC.opmode & OP_TXRXPEND) && sendComplete) {
            Serial.println("Sende Daten über LoRa...");
            sendComplete = false;  // Übertragung gestartet
            do_send(&sendjob);     // Send-Job anlegen
        }
    }

}
float calculateWindowOpenPercentage(sensors_event_t *accelEvent, sensors_event_t *magEvent) {
    // Berechne die Differenz im Magnetfeld (Magnetometer)
    float magDiffFromClosed = sqrt(
        pow(magEvent->magnetic.x - closedMagX, 2) +
        pow(magEvent->magnetic.y - closedMagY, 2) +
        pow(magEvent->magnetic.z - closedMagZ, 2)
    );
    float magDiffFromOpen = sqrt(
        pow(magEvent->magnetic.x - openMagX, 2) +
        pow(magEvent->magnetic.y - openMagY, 2) +
        pow(magEvent->magnetic.z - openMagZ, 2)
    );
    // Berechne den Prozentsatz der Fensteröffnung anhand der Magnetometerdaten
    float magPercentage = (magDiffFromClosed / (magDiffFromClosed + magDiffFromOpen)) * 100.0;
    // Begrenze den Prozentsatz zwischen 0 und 100
    magPercentage = constrain(magPercentage, 0, 100);
    return magPercentage; // Prozentsatz der Fensteröffnung
}
float calculateTiltPercentage(sensors_event_t *accelEvent) {
    float currentTiltY = abs(accelEvent->acceleration.y);
    float tiltDiffY = abs(currentTiltY - closedAccelY);
    // Prozentuale Berechnung basierend auf gekipptem Zustand
    float tiltPercentage = (tiltDiffY / abs(tiltedAccelY - closedAccelY)) * 100.0;
    tiltPercentage = constrain(tiltPercentage, 0, 100); // Begrenzen auf 0-100%
    return tiltPercentage;
}

String getWindowState(sensors_event_t *accelEvent, sensors_event_t *magEvent) {
    // Berechne die Änderung auf der Y-Achse (Beschleunigung)
    float accelChangeY = abs(accelEvent->acceleration.y - lastAccelY);
    lastAccelY = accelEvent->acceleration.y;
    // Wenn der Y-Wert sich signifikant ändert, Fenster als "Gekippt" erkennen
    if (accelChangeY > tiltThreshold) {  // Jede Änderung größer als tiltThreshold (z.B. 1.5)
        isTilted = true;  // Zustand "Gekippt" merken
    } else {
        // Wenn der Kippwinkel zurück zu 0% geht (also der Zustand neutral ist), zurücksetzen
        if (isTilted) {
            float tiltPercentage = calculateTiltPercentage(accelEvent);
            if (tiltPercentage < 10) { // Wenn der Kippwinkel fast 0% erreicht, setze den Zustand zurück
                isTilted = false;  // Kippzustand zurücksetzen
            }
        }
    }
    // Wenn das Fenster "Gekippt" ist, geben wir diesen Zustand zurück
    if (isTilted) {
        return "Gekippt";
    } else {
        // Berechne den Öffnungsgrad des Fensters basierend auf dem Magnetometer
        float openPercentage = calculateWindowOpenPercentage(accelEvent, magEvent);
        if (openPercentage > 0) {
            return "Offen";
        }
    }
    return "Unbekannt";
}

void calibrateClosedState() {
    sensors_event_t accelEvent;
    sensors_event_t magEvent;
    accel.getEvent(&accelEvent);
    mag.getEvent(&magEvent);
    closedAccelX = accelEvent.acceleration.x;
    closedAccelY = accelEvent.acceleration.y;
    closedAccelZ = accelEvent.acceleration.z;
    closedMagX = magEvent.magnetic.x;
    closedMagY = magEvent.magnetic.y;
    closedMagZ = magEvent.magnetic.z;
    // Ausgabe der kalibrierten Daten
    Serial.println("Geschlossener Zustand kalibriert:");
    Serial.print("Accel: ");
    Serial.print(closedAccelX); Serial.print(", ");
    Serial.print(closedAccelY); Serial.print(", ");
    Serial.println(closedAccelZ);
    Serial.print("Mag: ");
    Serial.print(closedMagX); Serial.print(", ");
    Serial.print(closedMagY); Serial.print(", ");
    Serial.println(closedMagZ);
}

void calibrateOpenState() {
    sensors_event_t accelEvent;
    sensors_event_t magEvent;
    accel.getEvent(&accelEvent);
    mag.getEvent(&magEvent);
    openAccelX = accelEvent.acceleration.x;
    openAccelY = accelEvent.acceleration.y;
    openAccelZ = accelEvent.acceleration.z;
    openMagX = magEvent.magnetic.x;
    openMagY = magEvent.magnetic.y;
    openMagZ = magEvent.magnetic.z;
    // Ausgabe der kalibrierten Daten
    Serial.println("Offener Zustand kalibriert:");
    Serial.print("Accel: ");
    Serial.print(openAccelX); Serial.print(", ");
    Serial.print(openAccelY); Serial.print(", ");
    Serial.println(openAccelZ);
    Serial.print("Mag: ");
    Serial.print(openMagX); Serial.print(", ");
    Serial.print(openMagY); Serial.print(", ");
    Serial.println(openMagZ);
}

void calibrateTiltedState() {
    sensors_event_t accelEvent;
    sensors_event_t magEvent;
    accel.getEvent(&accelEvent);
    mag.getEvent(&magEvent);
    tiltedAccelX = accelEvent.acceleration.x;
    tiltedAccelY = accelEvent.acceleration.y;
    tiltedAccelZ = accelEvent.acceleration.z;
    tiltedMagX = magEvent.magnetic.x;
    tiltedMagY = magEvent.magnetic.y;
    tiltedMagZ = magEvent.magnetic.z;
    // Ausgabe der kalibrierten Daten
    Serial.println("Gekippter Zustand kalibriert:");
    Serial.print("Accel: ");
    Serial.print(tiltedAccelX); Serial.print(", ");
    Serial.print(tiltedAccelY); Serial.print(", ");
    Serial.println(tiltedAccelZ);
    Serial.print("Mag: ");
    Serial.print(tiltedMagX); Serial.print(", ");
    Serial.print(tiltedMagY); Serial.print(", ");
    Serial.println(tiltedMagZ);
}

void calibrateHalfOpenState() {
    sensors_event_t accelEvent;
    sensors_event_t magEvent;
    accel.getEvent(&accelEvent);
    mag.getEvent(&magEvent);
    halfOpenAccelX = accelEvent.acceleration.x;
    halfOpenAccelY = accelEvent.acceleration.y;
    halfOpenAccelZ = accelEvent.acceleration.z;
    halfOpenMagX = magEvent.magnetic.x;
    halfOpenMagY = magEvent.magnetic.y;
    halfOpenMagZ = magEvent.magnetic.z;
    // Ausgabe der kalibrierten Daten
    Serial.println("Halb-offener Zustand kalibriert:");
    Serial.print("Accel: ");
    Serial.print(halfOpenAccelX); Serial.print(", ");
    Serial.print(halfOpenAccelY); Serial.print(", ");
    Serial.println(halfOpenAccelZ);
    Serial.print("Mag: ");
    Serial.print(halfOpenMagX); Serial.print(", ");
    Serial.print(halfOpenMagY); Serial.print(", ");
    Serial.println(halfOpenMagZ);
}

void saveCalibrationData() {
    preferences.putFloat("closedAccelX", closedAccelX);
    preferences.putFloat("closedAccelY", closedAccelY);
    preferences.putFloat("closedAccelZ", closedAccelZ);
    preferences.putFloat("closedMagX", closedMagX);
    preferences.putFloat("closedMagY", closedMagY);
    preferences.putFloat("closedMagZ", closedMagZ);
    preferences.putFloat("openAccelX", openAccelX);
    preferences.putFloat("openAccelY", openAccelY);
    preferences.putFloat("openAccelZ", openAccelZ);
    preferences.putFloat("openMagX", openMagX);
    preferences.putFloat("openMagY", openMagY);
    preferences.putFloat("openMagZ", openMagZ);
    preferences.putFloat("tiltedAccelX", tiltedAccelX);
    preferences.putFloat("tiltedAccelY", tiltedAccelY);
    preferences.putFloat("tiltedAccelZ", tiltedAccelZ);
    preferences.putFloat("tiltedMagX", tiltedMagX);
    preferences.putFloat("tiltedMagY", tiltedMagY);
    preferences.putFloat("tiltedMagZ", tiltedMagZ);
    preferences.putFloat("halfOpenAccelX", halfOpenAccelX);
    preferences.putFloat("halfOpenAccelY", halfOpenAccelY);
    preferences.putFloat("halfOpenAccelZ", halfOpenAccelZ);
    preferences.putFloat("halfOpenMagX", halfOpenMagX);
    preferences.putFloat("halfOpenMagY", halfOpenMagY);
    preferences.putFloat("halfOpenMagZ", halfOpenMagZ);
    preferences.putBool("calibrated", true);
    preferences.end();
}
void loadCalibrationData() {
    closedAccelX = preferences.getFloat("closedAccelX", 0);
    closedAccelY = preferences.getFloat("closedAccelY", 0);
    closedAccelZ = preferences.getFloat("closedAccelZ", 0);
    closedMagX = preferences.getFloat("closedMagX", 0);
    closedMagY = preferences.getFloat("closedMagY", 0);
    closedMagZ = preferences.getFloat("closedMagZ", 0);
    openAccelX = preferences.getFloat("openAccelX", 0);
    openAccelY = preferences.getFloat("openAccelY", 0);
    openAccelZ = preferences.getFloat("openAccelZ", 0);
    openMagX = preferences.getFloat("openMagX", 0);
    openMagY = preferences.getFloat("openMagY", 0);
    openMagZ = preferences.getFloat("openMagZ", 0);
    tiltedAccelX = preferences.getFloat("tiltedAccelX", 0);
    tiltedAccelY = preferences.getFloat("tiltedAccelY", 0);
    tiltedAccelZ = preferences.getFloat("tiltedAccelZ", 0);
    tiltedMagX = preferences.getFloat("tiltedMagX", 0);
    tiltedMagY = preferences.getFloat("tiltedMagY", 0);
    tiltedMagZ = preferences.getFloat("tiltedMagZ", 0);
    halfOpenAccelX = preferences.getFloat("halfOpenAccelX", 0);
    halfOpenAccelY = preferences.getFloat("halfOpenAccelY", 0);
    halfOpenAccelZ = preferences.getFloat("halfOpenAccelZ", 0);
    halfOpenMagX = preferences.getFloat("halfOpenMagX", 0);
    halfOpenMagY = preferences.getFloat("halfOpenMagY", 0);
    halfOpenMagZ = preferences.getFloat("halfOpenMagZ", 0);
}
void printCalibrationData() {
    Serial.println("Kalibrierungsdaten:");
    Serial.print("Geschlossen: Accel("); Serial.print(closedAccelX); Serial.print(", "); Serial.print(closedAccelY); Serial.print(", "); Serial.print(closedAccelZ); Serial.println(") Mag("); Serial.print(closedMagX); Serial.print(", "); Serial.print(closedMagY); Serial.print(", "); Serial.print(closedMagZ); Serial.println(")");
    Serial.print("Offen: Accel("); Serial.print(openAccelX); Serial.print(", "); Serial.print(openAccelY); Serial.print(", "); Serial.print(openAccelZ); Serial.println(") Mag("); Serial.print(openMagX); Serial.print(", "); Serial.print(openMagY); Serial.print(", "); Serial.print(openMagZ); Serial.println(")");
    Serial.print("Gekippt: Accel("); Serial.print(tiltedAccelX); Serial.print(", "); Serial.print(tiltedAccelY); Serial.print(", "); Serial.print(tiltedAccelZ); Serial.println(") Mag("); Serial.print(tiltedMagX); Serial.print(", "); Serial.print(tiltedMagY); Serial.print(", "); Serial.print(tiltedMagZ); Serial.println(")");
    Serial.print("Halb offen: Accel("); Serial.print(halfOpenAccelX); Serial.print(", "); Serial.print(halfOpenAccelY); Serial.print(", "); Serial.print(halfOpenAccelZ); Serial.println(") Mag("); Serial.print(halfOpenMagX); Serial.print(", "); Serial.print(halfOpenMagY); Serial.print(", "); Serial.print(halfOpenMagZ); Serial.println(")");
}
bool isButtonPressed() {
    bool buttonState = digitalRead(buttonPin) == LOW; // LOW = gedrückt
    if (buttonState && (millis() - lastDebounceTime > debounceDelay)) {
        lastDebounceTime = millis();
        return true;
    }
    return false;
}```
Best regards,
Leon
1 Like

Are you aware of the consequences of this? The ESP32 starts like it was powered on, all standard memory is reset when you do this so you will lose your join session so have to rejoin which will limit your uplinks to the Fair Use Policy of 10 a day.

This constant joining requires lots of random DevNonce’s to be generated which without a hardware random number generator or reseeding the software one, will end up with lots of duplications which will be rejected by the join server.

When awake the code appears to have thresholds that mean it will try to send uplinks rather quickly whilst it is being opened - particularly if someone is fiddling with the position of the window - so you might want to change the code so it only sends when it’s stopped moving to its new position.

1 Like

Dont use deep sleep mode with an ESP32 LoRaWAN node ?

As has been pointed out the ESP32 is clearing all the node session parameters when you go into deep sleep, so on coming out of deep sleep the node appears as if new. Which is not good, dont use it

You could save all the session parameters in the RTC memory of the ESP32 and recover them on restart, but that not at all easy.

I believe I have seen some ESP32 deep sleep code published for the RadioLib LoRaWAN implementation, but have not tried it.

2 Likes

Radiolib 7.1.0 + Persistence for ESP32 , seems OK after some short tests w SX1276+LOLIN32 Lite

https://github.com/radiolib-org/radiolib-persistence

K

3 Likes

:+1:

Been running it for +6 months now. The whole stack passes all the LoRa compliance tests.

1 Like

You could save all the session parameters in the RTC memory of the ESP32 and recover them on restart, but that not at all easy.

Radiolib 7.1.0 + Persistence for ESP32 , seems OK after some short tests w SX1276+LOLIN32 Lite

The LMIC library (in conjunction with the arduino-lorawan library) can store state externally. MCCI has been doing this using FRAM since 2018 or so. However, it’s not all that easy. If the radiolib library has something easier to use, then you might want to try that. Or post a question on GitHub - mcci-catena/arduino-lmic: LoraWAN-MAC-in-C library, adapted to run under the Arduino environment – there may be people with working solutions.

For the ESP32 it’s gift wrapped as the somewhat obsessed ESP-orientated developer is a Flat Earther who only believes in the one true ESP MCU range, all others are heresy.

For other less extreme developers that aren’t ESP obsessed, LMIC with AVR or SAMD is a joy of not losing RAM when you sleep.

AVR or SAMD is a joy of not losing RAM when you sleep.

STM32 also works well in this way, as long as you’re using internal RAM. External DRAM on the bigger chips will get lost during sleep. STM32 also has a mode to lose your internal RAM during sleep, but that saves so little power that it has never seemed worth the trouble.

Apart from lacking a RAM-retaining low-power sleep, the ESP32 has lots of nice features. LMIC v5.0.1 recently fixed some ESP32-specific issues. There are a fair number of people using it with ESP32, for whatever reason.

Oh bugger off you, who ported it to STM32? :kissing_heart:

Uh, that would be me, in 2018 or so.

@terrillmoore sorry didn’t mean to offend you, I replied to @descartes which I sincerely meant to offend :wink:

@stevencellist Whatever!

Ha, yeah, my bad, Steven is referring to the RadioLib port he did to STM32, it really is me that he’s poking back at.

STM32 has a special place in my heart, one where everything I try usually results in some sort of manic episode until I figure out the special sauce. So I try not to think about it much.

1 Like

LMIC+ESP32+DeepSleep

Didn’t Jack Gruber made a working solution ?

I’ll kook for some notes taken 2 years ago after tests

1 Like

Yes, I did a program to do just that with MCCI on the SEEED XIAO SAMD21. It worked, but not straightforward to implement.

The code also included the ability to automatically keep to the TTN fair useage policy, so I guess it would not appeal to the masses.

1 Like

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.