Dummy's question on how to sleep a SAMD21 with LMIC

I apologize in advance if my question is not relevant or impossible to answer.
I am an old electronic engineer (with very little knowledge of C++) trying to reconnect to the latest technologies in order to build a connected bee hive. I am using an Adafruit Feather M0 LoRa (SAMD21 processor) with the MCCI LMIC library on PlatformIO. I loaded the code from Lopes Parentes which is great for masking LMIC complexity and I added some lines of code to read my sensors, prepare the payloads and send them. Everything works fine but I am left with the problem of optimizing power consumption.
Does anybody know about a step by step instructions guide (understandable by a dummy of my kind) about how to implement sleep mode and wake-up on the micro controller I am using. I have read a lot of posts on this forum but still fail to understand precisely what to do. Which library should I use, in which part of the code should I implement the sleep, how to wake-up, how to ensure LMIC doesn’t loose time reference when it wakes-up…
I have seen the code from @descartes but it’s for a different processor and I wasn’t able to precisely understand the logic of the code.
Any help of any hint would be welcome.
Thanks in advance !

There is a state machine that uses a series of flags set in different places to decide if it’s OK to sleep, should it do a data acquisition, trigger a send etc etc. The main flag of importance is set after a TX_COMPLETE event as you can be (reasonably) sure that the LoRaWAN MAC has done its thing for now and is OK to sleep. The code that puts the ATmega328 to sleep is particular to the AVR.

For SAMD21 there is the un-specifically named Low Power library. Unhelpfully on the Arduino MKR WAN board a reset after programming doesn’t initialise the MCU correctly to sleep, this may be an issue for the Adafruit Feather M0 with RFM95, I haven’t tried. The solution if it does crop up is to detect the reason for reset and then get the board to, erm, reset, but properly. I can dig out some code if required.

I’ve not tried subverting the sys-ticks on the SAMD21. On the AVR you can only sleep for 8 seconds so you usually accumulate enough ticks to allow LMIC to see that the world has moved on. The SAMD21 can sleep for up to 49 days in one go, so LMIC may be confused on waking that time doesn’t appear to have moved on much. Again, something else for the “to try” list.

Thanks much for the explanation.
I like the idea of subverting the sys-ticks on the SAMD21. Let’s say that I want to send an uplink every 30mn. Can I tell LMIC that I want to send an uplink every 1 mn and before it calculate when to schedule the next uplink, I put the processor in sleep mode for 29mn.
In the code below, the sleep would take place before calculating startAt (right after the comment line “// This job must explicitly reschedule itself for the next run.”), is it correct ?
Tell me if I am missing something or if this strategy can work.
Thanks again !

static void doWorkCallback(osjob_t* job)
{
    // Event hander for doWorkJob. Gets called by the LMIC scheduler.
    // The actual work is performed in function processWork() which is called below.

    ostime_t timestamp = os_getTime();
    #ifdef USE_SERIAL
        serial.println();
        printEvent(timestamp, "doWork job started", PrintTarget::Serial);
    #endif    

    // Do the work that needs to be performed.
    processWork(timestamp);

    // This job must explicitly reschedule itself for the next run.
    ostime_t startAt = timestamp + sec2osticks((int64_t)doWorkIntervalSeconds);
    os_setTimedCallback(&doWorkJob, startAt, doWorkCallback);    
}

You could but that looks like trouble if it doesn’t sleep.

Why not just sleep and then on wake ask LMIC to do a send.

No need to do the os_setTimedCallback - that’s just a mechanism for an idle device to know when to do something next. But @bluejedi is the expert in this area.

Thanks again. I’ll do some tests and may ask @bluejedi
Happy new year BTW !

Just for reference, in case somebody else is running into the same questions: sleeping the processor right before the callback routine reschedules itself (last two lines above) works well. In that case, it’s indeed better to launch the call back routine directly [os_setCallback()] instead of rescheduling it [os_setTimedCallback()]. However, this prevents LMIC to process the events that take place after the callback routine. In particular, the event “EV_TXCOMPLETE” is not processed, which means it’s impossible to process downlinks. In order to go around this problem, I put the processor to sleep after the event EV_TXCOMPLETE has been processed (in the onLmicEvent() routine). Not very clean but it works and short of better idea, I’ll go with that.

As per:

Nothing unclean about it - LMIC has done it’s work, you can signal that it’s time to sleep. As long as you are aware of State Machines, this is a working example:

Flag set here: Arduino-LMIC-example/TinyThing/TinyThing.ino at d1c6d6ce10e546c36bb3203602638675cabc92d9 · descartes/Arduino-LMIC-example · GitHub

and

Various other flags set & then processed in the main loop: Arduino-LMIC-example/TinyThing/TinyThing.ino at d1c6d6ce10e546c36bb3203602638675cabc92d9 · descartes/Arduino-LMIC-example · GitHub