mcci-catena / arduino-lmic

LoraWAN-MAC-in-C library, adapted to run under the Arduino environment
https://forum.mcci.io/c/device-software/arduino-lmic/
MIT License
641 stars 208 forks source link

Question: Sleep / Low Power on Lmic #535

Closed Dardrai closed 4 years ago

Dardrai commented 4 years ago

Hi Lmic team I have a question about the Lmic Lib in connection with the NRF52840 express.

I use atm the: mcci-catena/arduino-lmic version 3.0.99 I couldn't test version 3.1 successfully yet, had some issue with connection to the Lora Backend, but that could be an other problem with TTN or the Gateway

In Short I have a NRF52840 express (Code Runner) FeatherWing RFM95W (LoraWan Module) 2000mAh Battery

One Device should run for around 180 Days with only one charge, but at the moment, the Device is using to much power. How could I Improve the Power consumption on my sketch/template? Full Application here: template_n-NRF52.zip

"Main Class":

include "configuration.h"

include

include

CayenneLPP lpp(51); static osjob_t sendjob; uint32_t count = 0;

// ----------------------------------------------------------------------------- // Application // // Create Send Job // ----------------------------------------------------------------------------- void sendMessage(osjob_t* j) { getSensorDataToLPP();

if LORAWAN_CONFIRMED_EVERY > 0

bool confirmed = (count % LORAWAN_CONFIRMED_EVERY == 0);

else

bool confirmed = false;

endif

ttn_cnt(count); ttn_send(lpp.getBuffer(), lpp.getSize(), LORAWAN_PORT, confirmed);

count++; }

// ----------------------------------------------------------------------------- // Add Sesnor Data to LPP Payload // ----------------------------------------------------------------------------- void getSensorDataToLPP() { float vbat_mv = readVBAT(); lpp.reset(); lpp.addVoltage(0, convertToVoltage(vbat_mv)); lpp.addPercentage(0, mvToPercent(vbat_mv)); lpp.addTemperature(1, getTempData()); lpp.addRelativeHumidity(2, getHumidityData());

ifdef DEBUG_PORT

DEBUG_PORT.print("Volt: "); DEBUG_PORT.println(convertToVoltage(vbat_mv)); DEBUG_PORT.print("Percent: "); DEBUG_PORT.println(mvToPercent(vbat_mv)); DEBUG_PORT.print("Temperature: "); DEBUG_PORT.println(getTempData()); DEBUG_PORT.print("Humidity: "); DEBUG_PORT.println(getHumidityData());

endif

}

// ----------------------------------------------------------------------------- // Shedule Next Send // ----------------------------------------------------------------------------- void scheduleNextSend() { os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(TX_INTERVAL), sendMessage); }

// ----------------------------------------------------------------------------- // Callback // ----------------------------------------------------------------------------- void callback(uint8_t message) {

if (EV_TXCOMPLETE == message) {

ifdef DEBUG_PORT

DEBUG_PORT.println(F("EV_TXCOMPLETE"));

endif

**//should I add here a Delay?**
scheduleNextSend();
**//or should I add here a Delay?

//like delay(sec2osticks(TX_INTERVAL));** }

if (EV_RESPONSE == message) {

ifdef DEBUG_PORT

DEBUG_PORT.println(F("EV_RESPONSE"));

endif

size_t len = ttn_response_len();
uint8_t data[len];
ttn_response(data, len);

}

ifdef DEBUG_PORT

if (message != NULL ) { DEBUG_PORT.print("Message Code: "); DEBUG_PORT.println(message); }

endif

}

// ----------------------------------------------------------------------------- // Setup // ----------------------------------------------------------------------------- void setup() { delay(100); initSensor();

ifdef DEBUG_PORT

DEBUG_PORT.begin(SERIAL_BAUD);

endif

// TTN setup if (!ttn_setup()) { delay(MESSAGE_TO_SLEEP_DELAY); }

ttn_register(callback); ttn_join(); ttn_sf(LORAWAN_SF); ttn_adr(LORAWAN_ADR);

sendMessage(&sendjob); }

// ----------------------------------------------------------------------------- // Loop // ----------------------------------------------------------------------------- void loop() { ttn_loop(); }

Important note the NRF52840 express has a FreeRTOS Kernal implemented so if I use a delay() the delay is equal to sleep! Another way to get the NRF52840 in a much more low power state would be if I could change this "main class" to a loopless class like this example (register Callbacks etc): https://github.com/MacGyverNL/Adafruit_nRF52_Arduino/blob/LowPower_examples/libraries/Bluefruit52Lib/examples/LowPower/Blink_loopless/Blink_loopless.ino

But if I try this I'm not shure how i should start the lmic process then and register the callback so the lmic will send again and again.

"Main Class Loopless"

include "configuration.h"

include

include

CayenneLPP lpp(51); static osjob_t sendjob; uint32_t count = 0;

SoftwareTimer lorawan_timer;

// ----------------------------------------------------------------------------- // Application // // Create Send Job // ----------------------------------------------------------------------------- void sendMessage(osjob_t* j) { getSensorDataToLPP();

if LORAWAN_CONFIRMED_EVERY > 0

bool confirmed = (count % LORAWAN_CONFIRMED_EVERY == 0);

else

bool confirmed = false;

endif

ttn_cnt(count); ttn_send(lpp.getBuffer(), lpp.getSize(), LORAWAN_PORT, confirmed);

count++; }

// ----------------------------------------------------------------------------- // Add Sesnor Data to LPP Payload // ----------------------------------------------------------------------------- void getSensorDataToLPP() { float vbat_mv = readVBAT(); lpp.reset(); lpp.addVoltage(0, convertToVoltage(vbat_mv)); lpp.addPercentage(0, mvToPercent(vbat_mv)); lpp.addTemperature(1, getTempData()); lpp.addRelativeHumidity(2, getHumidityData());

ifdef DEBUG_PORT

DEBUG_PORT.print("Volt: "); DEBUG_PORT.println(convertToVoltage(vbat_mv)); DEBUG_PORT.print("Percent: "); DEBUG_PORT.println(mvToPercent(vbat_mv)); DEBUG_PORT.print("Temperature: "); DEBUG_PORT.println(getTempData()); DEBUG_PORT.print("Humidity: "); DEBUG_PORT.println(getHumidityData());

endif

}

// ----------------------------------------------------------------------------- // Shedule Next Send // ----------------------------------------------------------------------------- void scheduleNextSend() { //os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(TX_INTERVAL), sendMessage); }

// ----------------------------------------------------------------------------- // Callback // ----------------------------------------------------------------------------- void callback(uint8_t message) {

if (EV_TXCOMPLETE == message) {

ifdef DEBUG_PORT

DEBUG_PORT.println(F("EV_TXCOMPLETE"));

endif

scheduleNextSend();

}

if (EV_RESPONSE == message) {

ifdef DEBUG_PORT

DEBUG_PORT.println(F("EV_RESPONSE"));

endif

size_t len = ttn_response_len();
uint8_t data[len];
ttn_response(data, len);

}

ifdef DEBUG_PORT

if (message != NULL ) { DEBUG_PORT.print("Message Code: "); DEBUG_PORT.println(message); }

endif

}

// ----------------------------------------------------------------------------- // Setup // ----------------------------------------------------------------------------- void setup() { delay(100); initSensor();

ifdef DEBUG_PORT

DEBUG_PORT.begin(SERIAL_BAUD);

endif

// TTN setup if (!ttn_setup()) { delay(MESSAGE_TO_SLEEP_DELAY); }

ttn_register(callback); ttn_join(); ttn_sf(LORAWAN_SF); ttn_adr(LORAWAN_ADR);

//sendMessage(&sendjob);

lorawan_timer.begin(1500, timer_callback); lorawan_timer.start();

sendMessage(&sendjob); suspendLoop();

}

// ----------------------------------------------------------------------------- // Loop // ----------------------------------------------------------------------------- void timer_callback(TimerHandle_t _handle) { ttn_loop(); }

void loop (){} If I use the code above (loopless one) I only receive auth request on the TTN Portal, but the NRF don't send any data to the backend.

Thank you for your help and keep up the great work!

Dardrai commented 4 years ago

The best "low Power" sketch that I had (used around 14% Battery in 6 Day's and sends every 5 min)

if (EV_TXCOMPLETE == message) {

ifdef DEBUG_PORT

DEBUG_PORT.println(F("EV_TXCOMPLETE"));

endif

delay(SLEEP_DELAY); // SLEEP HERE!

}

void loop() {

// Send every TX_INTERVAL millis
static uint32_t last = 0;
if (0 == last || millis() - last > SLEEP_DELAY) {
    last = millis();
    send();
}

ttn_loop();

}

define SLEEP_DELAY 300000 // 5min delay

Is this also suitable for the lmic Library or what whould be the best way to cut the power consumption in half?

Update: Tried the new Version 3.1.0 again - but the NRF doesn't work well with new Version?

  1. The Sketch above ("Main Class") does make an auth request, but never sends (this sketch works fine on 3.0.99)
  2. After programing the NRF with 3.1.0 the Serial Port (over USB) doesn't work as expected
    • ArduinoIDE can't open the Serial Monitor if the NRF is running
    • If the NRF receive a reset (via button) the Serial Monitor opens but it will be empty
    • Update the running sketch nearly impossible with the lib 3.1.0 (*I believe it has something to do with the overall Serial USB Connection) | to update the NRF, the NRF has the be put in DFU Mode

After reverting back to Version 3.0.99 and downgrading the running sketch everything works as expected - NRF goes Online and send his data, Device can be programed and watch via the serial Monitor etc.

Again thank you for your help

Kind regards

David

terrillmoore commented 4 years ago

There were no changes that should affect this. My only suggestion is to use git bisect to find the commit that broke things.

terrillmoore commented 4 years ago

By the way (everyone) - please do not combine issues in one ticket. @dardrai, please file a new ticket for the 3.1.0 regression, and I'll comment further there. Meanwhile...

For low power, you must use the low power sleep feature of your CPU. Generally, one must avoid using delay(), as this is a high-power sleep.

mikethebee commented 4 years ago

Hopefully it is ok to ask about deepsleep in this thread. I am adapting https://github.com/cyberman54/ESP32-Paxcounter to do deepsleep on a TTGOv2, and am successful with sleeping and waking with a new join after sleep (not good practice), but when I try accessing the up and down counters to prep a save and restore for rejoining I get a panic. Reading the forums I see there are some hooks for working with the lorawan parameters. Can you link to a good example I could reference to do such a rejoin? Many thanks for your great code and work. - Mike

terrillmoore commented 4 years ago

It's fine but... generally it's easier to manage (because I know when to close the issue), if we please open new issues for new questions.

If you check https://github.com/mcci-catena/arduino-lorawan, you will see the code that MCCI uses to save and restore the session. It's still somewhat imperfect, but I would rather we all focus on improving that base than re-implementing every time. (If you dig through https://github.com/mcci-catena/Catena-Arduino-Platform, you'll see how we store the session data to FRAM.)

It is a very bad idea to access the LMIC contents directly. When we add class C support, SX1262 support, multicast, etc., the LMIC contents and what you have to save will need to change. Please use a library. I'd merge the arduino-lorawan library with this, but... for now it's separate.

Dardrai commented 4 years ago

@terrillmoore where would you add the designated code to go to a sleep state? Would you add it somewhere in the switch state (e.g. when the EV_TXCOMPLETE is catched)? Or would you let it run through os_runloop_once(); - let lmic finish everything something like this?

void timer_callback(TimerHandle_t _handle) { send(); for(int i = 0; i < 4000; i++){ os_runloop_once(); } Sleep(5min); }

Dardrai commented 4 years ago

For low power, you must use the low power sleep feature of your CPU. Generally, one must avoid using delay(), as this is a high-power sleep.

In generall yes - for nrf52 with freeRTOS not necessary.

But where have you @terrillmoore added the sleeping codes/function in your sketches that should save as much power as possible?