matthijskooijman / arduino-lmic

:warning: This library is deprecated, see the README for alternatives.
705 stars 651 forks source link

LMIC with Low Power #225

Open CongducPham opened 5 years ago

CongducPham commented 5 years ago

Hi, I tried to make LMIC working with low power mode when timer0 is not updated. After several attempts I ended up to modify hal_ticks() in order to take into account a sleep value that is updated from the main device sketch with a new function (https://github.com/CongducPham/LowCostLoRaGw/blob/528f97b3a4fcc61912d06e6ed3f37073208eab52/Arduino/libraries/lmic/src/hal/hal.cpp#L222). The code is here:

https://github.com/CongducPham/LowCostLoRaGw/blob/master/Arduino/libraries/lmic/src/hal/hal.cpp

It seems to work fine so far, but I'm still worried about possible rollover and I would appreciate your comment, especially on line https://github.com/CongducPham/LowCostLoRaGw/blob/528f97b3a4fcc61912d06e6ed3f37073208eab52/Arduino/libraries/lmic/src/hal/hal.cpp#L149

best regards,

Emielcp commented 4 years ago

Hii,

do you have an example of your main sketch using this method? Thanks!

CongducPham commented 4 years ago

You can find these info here, as well as the last version of the LMIC modifications.

https://github.com/CongducPham/LMIC_low_power

best regards,

matthijskooijman commented 4 years ago

In one project I updated the millis() counter rather than the LMIC ticks, which is more general (but also needs to access internal Arduino variables). See https://github.com/meetjestad/mjs_firmware/blob/master/mjs_firmware.ino#L232-L242

Emielcp commented 4 years ago

Thank you Pham,

I need a solution for the teensy 3.2 (ARM Chip), I will try this

CongducPham commented 4 years ago

@matthijskooijman Thanks, is the hack on millis() working on all platforms?

Emielcp commented 4 years ago

@CongducPham I find the example file (temp) a bit confusing. I'm using a standard LMIC code (like the examples) with the snooze library for my teensy. You use them in the example file as well. Could you have me with my code? Would be awesome! I just need the timer to work again after going in hibernate mode. For code see:

Teensy forum: https://forum.pjrc.com/threads/56999-Teensy-3-2-Snooze-LoRaWAN-LMIC-stack-problem

or TTN forum: https://www.thethingsnetwork.org/forum/t/teensy-3-2-snooze-lorawan-lmic-stack-problem/28411

My mail is: emielcpnl@gmail.com

Thanks in advance!

EDIT: looked at wrong example code, I will try to make it work :)

CongducPham commented 4 years ago

@Emielcp as indicated in the README, you need the modified LMIC lib. Then, in your code, after wake-up, just insert "hal_sleep_lowpower(8);" where "8" here is the hibernate duration that you were using. Can be something else depending on your code.

On the Teensy, I actually don't remember if the deep sleep mode preserves the internal clock or not. If millis() counter is preserved then you may not need the LMIC hack.

Emielcp commented 4 years ago

Okay i tried, still same result. It goes into hibernate and return every 20sec but my message arrives only every 3min and 20sec. Could you please check? Code:

`#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
#include <Snooze.h>
#include <i2c_t3.h>

// LoRaWAN NwkSKey, network session key
// This is the default Semtech key, which is used by the early prototype TTN
// network.
static const PROGMEM u1_t NWKSKEY[16] = { 0x09, 0x47, 0x9E, 0x7C, 0xA2, 0xEF, 0x71, 0xFB, 0x11, 0x03, 0x85, 0x53, 0x52, 0xF0, 0xEB, 0x0A };

// LoRaWAN AppSKey, application session key
// This is the default Semtech key, which is used by the early prototype TTN
// network.
static const u1_t PROGMEM APPSKEY[16] = { 0xC0, 0x5B, 0x13, 0x22, 0x55, 0xC2, 0xC4, 0xF0, 0x30, 0x34, 0xE7, 0x72, 0x6A, 0x81, 0xF4, 0xC5 };

// LoRaWAN end-device address (DevAddr)
static const u4_t DEVADDR = 0x2601190B ; // <-- Change this address for every node!

// These callbacks are only used in over-the-air activation, so they are
// left empty here (we cannot leave them out completely unless
// DISABLE_JOIN is set in config.h, otherwise the linker will complain).
void os_getArtEui (u1_t* buf) { }
void os_getDevEui (u1_t* buf) { }
void os_getDevKey (u1_t* buf) { }

static osjob_t sendjob;

// Schedule TX every this many seconds (might become longer due to duty
// cycle limitations).
// const unsigned TX_INTERVAL = 60;

// Pin mapping
const lmic_pinmap lmic_pins = {
  .nss = 10,
  .rxtx = LMIC_UNUSED_PIN,
  .rst = 4,
  .dio = {2, 3, LMIC_UNUSED_PIN},
};

#define resetPin 8

int who;
int AantalSecondeWinterslaap = 20; 

SnoozeAlarm alarm;
SnoozeBlock config_teensy32(alarm);

void onEvent (ev_t ev) {
  Serial.print(os_getTime());
  Serial.print(": ");
  switch (ev) {
    case EV_SCAN_TIMEOUT:
      Serial.println(F("EV_SCAN_TIMEOUT"));
      break;
    case EV_BEACON_FOUND:
      Serial.println(F("EV_BEACON_FOUND"));
      break;
    case EV_BEACON_MISSED:
      Serial.println(F("EV_BEACON_MISSED"));
      break;
    case EV_BEACON_TRACKED:
      Serial.println(F("EV_BEACON_TRACKED"));
      break;
    case EV_JOINING:
      Serial.println(F("EV_JOINING"));
      break;
    case EV_JOINED:
      Serial.println(F("EV_JOINED"));
      break;
    case EV_RFU1:
      Serial.println(F("EV_RFU1"));
      break;
    case EV_JOIN_FAILED:
      Serial.println(F("EV_JOIN_FAILED"));
      break;
    case EV_REJOIN_FAILED:
      Serial.println(F("EV_REJOIN_FAILED"));
      break;
    case EV_TXCOMPLETE:
      Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
      if (LMIC.txrxFlags & TXRX_ACK)
        Serial.println(F("Received ack"));
      if (LMIC.dataLen) {
        Serial.println(F("Received "));
        Serial.println(LMIC.dataLen);
        Serial.println(F(" bytes of payload"));
        WinterSlaap();
      }
      else {
        WinterSlaap();
      }
      break;
    case EV_LOST_TSYNC:
      Serial.println(F("EV_LOST_TSYNC"));
      break;
    case EV_RESET:
      Serial.println(F("EV_RESET"));
      break;
    case EV_RXCOMPLETE:
      // data received in ping slot
      Serial.println(F("EV_RXCOMPLETE"));
      break;
    case EV_LINK_DEAD:
      Serial.println(F("EV_LINK_DEAD"));
      break;
    case EV_LINK_ALIVE:
      Serial.println(F("EV_LINK_ALIVE"));
      break;
    default:
      Serial.println(F("Unknown event"));
      break;
  }
}

void do_send(osjob_t* j) {
  // Check if there is not a current TX/RX job running
  if (LMIC.opmode & OP_TXRXPEND) {
    Serial.println(F("OP_TXRXPEND, not sending"));
  } else {
    byte payload [1];
    payload [0] = 2;
    LMIC_setTxData2(1, payload, sizeof(payload), 0);
    Serial.println(F("Packet queued"));
  }
  // Next TX is scheduled after TX_COMPLETE event.
}

void setup() {
  Serial.begin(115200);
  Serial.println(F("Starting"));

  pinMode (21, OUTPUT);
  digitalWrite (21, HIGH);
   pinMode (resetPin, OUTPUT);
  digitalWrite (resetPin, LOW);

  alarm.setRtcTimer(0, 0, AantalSecondeWinterslaap);// Uren, minuten, secondes

  // LMIC init
  os_init();
  // Reset the MAC state. Session and pending data transfers will be discarded.
  LMIC_reset();

  os_setTimedCallback(&sendjob, os_getTime() + ms2osticks(10), do_send);
  // Set static session parameters. Instead of dynamically establishing a session
  // by joining the network, precomputed session parameters are be provided.
#ifdef PROGMEM
  // On AVR, these values are stored in flash and only copied to RAM
  // once. Copy them to a temporary buffer here, LMIC_setSession will
  // copy them into a buffer of its own again.
  uint8_t appskey[sizeof(APPSKEY)];
  uint8_t nwkskey[sizeof(NWKSKEY)];
  memcpy_P(appskey, APPSKEY, sizeof(APPSKEY));
  memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY));
  LMIC_setSession (0x1, DEVADDR, nwkskey, appskey);
#else
  // If not running an AVR with PROGMEM, just use the arrays directly
  LMIC_setSession (0x1, DEVADDR, NWKSKEY, APPSKEY);
#endif

#if defined(CFG_eu868)
  // Set up the channels used by the Things Network, which corresponds
  // to the defaults of most gateways. Without this, only three base
  // channels from the LoRaWAN specification are used, which certainly
  // works, so it is good for debugging, but can overload those
  // frequencies, so be sure to configure the full frequency range of
  // your network here (unless your network autoconfigures them).
  // Setting up channels should happen after LMIC_setSession, as that
  // configures the minimal channel set.
  // NA-US channels 0-71 are configured automatically
  LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
  LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI);      // g-band
  LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
  LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
  LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
  LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
  LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
  LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
  LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK,  DR_FSK),  BAND_MILLI);      // g2-band
  // TTN defines an additional channel at 869.525Mhz using SF9 for class B
  // devices' ping slots. LMIC does not have an easy way to define set this
  // frequency and support for class B is spotty and untested, so this
  // frequency is not configured here.
#elif defined(CFG_us915)
  // NA-US channels 0-71 are configured automatically
  // but only one group of 8 should (a subband) should be active
  // TTN recommends the second sub band, 1 in a zero based count.
  // https://github.com/TheThingsNetwork/gateway-conf/blob/master/US-global_conf.json
  LMIC_selectSubBand(1);
#endif

  // Disable link check validation
  LMIC_setLinkCheckMode(0);

  // TTN uses SF9 for its RX2 window.
  LMIC.dn2Dr = DR_SF9;

  LMIC_setClockError(MAX_CLOCK_ERROR * 10 / 100);

  // Set data rate and transmit power for uplink (note: txpow seems to be ignored by the library)
  LMIC_setDrTxpow(DR_SF7, 14);

  // Start job
  do_send(&sendjob);
}

void loop() {
  os_runloop_once();
  who = Snooze.hibernate( config_teensy32 );
  digitalWrite (21, HIGH);

  if (who == 35) {

    digitalWrite (21, HIGH);
    Serial.begin(115200);
    Serial.println ("Uit slaap!");
    delay(5000);

    hal_sleep_lowpower(AantalSecondeWinterslaap);

    os_getTime();   //VERY IMPORTANT after sleep to update os_time and not cause txbeg and os_time out of sync which causes send delays with the RFM95 on and eating power
    //Do here whatever needs to be done after each of the sleepcycle (e.g. check for a condition to break for send or take measurements for mean values etc.)

    os_setTimedCallback(&sendjob, os_getTime() + ms2osticks(10), do_send);  //do a send

    Serial.println(F("go to sleep ... "));
    Serial.println ("delay van 5sec");
    delay (5000);
  }
}

void WinterSlaap (){
  digitalWrite (21, LOW);
  Serial.flush();
  Snooze.hibernate( alarm ); 
matthijskooijman commented 4 years ago

@matthijskooijman Thanks, is the hack on millis() working on all platforms?

No, just AVR (others might work, but I expect their implementation to be different).

CongducPham commented 4 years ago

Well, in your program, I believe you should have the call to hal_sleep_lowpower() in the onEvent() function.

Emielcp commented 4 years ago

@CongducPham same result... :( I tried so many things... damnit

CongducPham commented 4 years ago

Well, I tested Snooze in standalone with a Teensy32 and it appears that the millis()/micros() counter is preserved. So normally there is no need to change anything to LMIC (not need to insert call to hal_sleep_lowpower()). However, I tested the LMIC example with Snooze on a Teensy and it is not working (the second TX is not scheduled correctly after the sleep). Currently I have no clue on why it is not working. My knowledge of LMIC may be quite limited, maybe Thomas has some idea.

regards,