492 lines
18 KiB
C++
492 lines
18 KiB
C++
/*
|
|
RadioLib LoRaWAN ABP Example
|
|
|
|
This example joins a LoRaWAN network and will send
|
|
uplink packets. Before you start, you will have to
|
|
register your device at https://www.thethingsnetwork.org/
|
|
After your device is registered, you can run this example.
|
|
The device will join the network and start uploading data.
|
|
|
|
Running this examples REQUIRES you to check "Resets DevNonces"
|
|
on your LoRaWAN dashboard. Refer to the network's
|
|
documentation on how to do this.
|
|
|
|
For default module settings, see the wiki page
|
|
https://github.com/jgromes/RadioLib/wiki/Default-configuration
|
|
|
|
For full API reference, see the GitHub Pages
|
|
https://jgromes.github.io/RadioLib/
|
|
|
|
For LoRaWAN details, see the wiki page
|
|
https://github.com/jgromes/RadioLib/wiki/LoRaWAN
|
|
|
|
*/
|
|
|
|
#include <RadioLib.h>
|
|
#include "LoRaBoards.h"
|
|
|
|
#include <Preferences.h>
|
|
Preferences store;
|
|
|
|
#if defined(USING_SX1276)
|
|
SX1276 radio = new Module(RADIO_CS_PIN, RADIO_DIO0_PIN, RADIO_RST_PIN, RADIO_DIO1_PIN);
|
|
#elif defined(USING_SX1262)
|
|
SX1262 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
|
|
#elif defined(USING_SX1278)
|
|
SX1278 radio = new Module(RADIO_CS_PIN, RADIO_DIO0_PIN, RADIO_RST_PIN, RADIO_DIO1_PIN);
|
|
#elif defined(USING_LR1121)
|
|
LR1121 radio = new Module(RADIO_CS_PIN, RADIO_DIO9_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
|
|
#endif
|
|
|
|
// joinEUI - previous versions of LoRaWAN called this AppEUI
|
|
// for development purposes you can use all zeros - see wiki for details
|
|
#define RADIOLIB_LORAWAN_JOIN_EUI 0x0000000000000000
|
|
|
|
|
|
// the Device EUI & two keys can be generated on the TTN console
|
|
#ifndef RADIOLIB_LORAWAN_DEV_EUI // Replace with your Device EUI
|
|
#define RADIOLIB_LORAWAN_DEV_EUI 0x---------------
|
|
#endif
|
|
#ifndef RADIOLIB_LORAWAN_APP_KEY // Replace with your App Key
|
|
#define RADIOLIB_LORAWAN_APP_KEY 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--
|
|
#endif
|
|
#ifndef RADIOLIB_LORAWAN_NWK_KEY // Put your Nwk Key here
|
|
#define RADIOLIB_LORAWAN_NWK_KEY 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--
|
|
#endif
|
|
|
|
// how often to send an uplink - consider legal & FUP constraints - see notes
|
|
const uint32_t uplinkIntervalSeconds = 5UL * 60UL; // minutes x seconds
|
|
|
|
|
|
// for the curious, the #ifndef blocks allow for automated testing &/or you can
|
|
// put your EUI & keys in to your platformio.ini - see wiki for more tips
|
|
|
|
// regional choices: EU868, US915, AU915, AS923, IN865, KR920, CN780, CN500
|
|
const LoRaWANBand_t Region = EU868;
|
|
const uint8_t subBand = 0; // For US915, change this to 2, otherwise leave on 0
|
|
|
|
// ============================================================================
|
|
|
|
|
|
// copy over the EUI's & keys in to the something that will not compile if incorrectly formatted
|
|
uint64_t joinEUI = RADIOLIB_LORAWAN_JOIN_EUI;
|
|
uint64_t devEUI = RADIOLIB_LORAWAN_DEV_EUI;
|
|
uint8_t appKey[] = { RADIOLIB_LORAWAN_APP_KEY };
|
|
uint8_t nwkKey[] = { RADIOLIB_LORAWAN_NWK_KEY };
|
|
|
|
// create the LoRaWAN node
|
|
LoRaWANNode node(&radio, &Region, subBand);
|
|
|
|
RTC_DATA_ATTR uint8_t LWsession[RADIOLIB_LORAWAN_SESSION_BUF_SIZE];
|
|
|
|
|
|
|
|
// result code to text - these are error codes that can be raised when using LoRaWAN
|
|
// however, RadioLib has many more - see https://jgromes.github.io/RadioLib/group__status__codes.html for a complete list
|
|
String stateDecode(const int16_t result)
|
|
{
|
|
switch (result) {
|
|
case RADIOLIB_ERR_NONE:
|
|
return "ERR_NONE";
|
|
case RADIOLIB_ERR_CHIP_NOT_FOUND:
|
|
return "ERR_CHIP_NOT_FOUND";
|
|
case RADIOLIB_ERR_PACKET_TOO_LONG:
|
|
return "ERR_PACKET_TOO_LONG";
|
|
case RADIOLIB_ERR_RX_TIMEOUT:
|
|
return "ERR_RX_TIMEOUT";
|
|
case RADIOLIB_ERR_CRC_MISMATCH:
|
|
return "ERR_CRC_MISMATCH";
|
|
case RADIOLIB_ERR_INVALID_BANDWIDTH:
|
|
return "ERR_INVALID_BANDWIDTH";
|
|
case RADIOLIB_ERR_INVALID_SPREADING_FACTOR:
|
|
return "ERR_INVALID_SPREADING_FACTOR";
|
|
case RADIOLIB_ERR_INVALID_CODING_RATE:
|
|
return "ERR_INVALID_CODING_RATE";
|
|
case RADIOLIB_ERR_INVALID_FREQUENCY:
|
|
return "ERR_INVALID_FREQUENCY";
|
|
case RADIOLIB_ERR_INVALID_OUTPUT_POWER:
|
|
return "ERR_INVALID_OUTPUT_POWER";
|
|
case RADIOLIB_ERR_NETWORK_NOT_JOINED:
|
|
return "RADIOLIB_ERR_NETWORK_NOT_JOINED";
|
|
case RADIOLIB_ERR_DOWNLINK_MALFORMED:
|
|
return "RADIOLIB_ERR_DOWNLINK_MALFORMED";
|
|
case RADIOLIB_ERR_INVALID_REVISION:
|
|
return "RADIOLIB_ERR_INVALID_REVISION";
|
|
case RADIOLIB_ERR_INVALID_PORT:
|
|
return "RADIOLIB_ERR_INVALID_PORT";
|
|
case RADIOLIB_ERR_NO_RX_WINDOW:
|
|
return "RADIOLIB_ERR_NO_RX_WINDOW";
|
|
case RADIOLIB_ERR_INVALID_CID:
|
|
return "RADIOLIB_ERR_INVALID_CID";
|
|
case RADIOLIB_ERR_UPLINK_UNAVAILABLE:
|
|
return "RADIOLIB_ERR_UPLINK_UNAVAILABLE";
|
|
case RADIOLIB_ERR_COMMAND_QUEUE_FULL:
|
|
return "RADIOLIB_ERR_COMMAND_QUEUE_FULL";
|
|
case RADIOLIB_ERR_COMMAND_QUEUE_ITEM_NOT_FOUND:
|
|
return "RADIOLIB_ERR_COMMAND_QUEUE_ITEM_NOT_FOUND";
|
|
case RADIOLIB_ERR_JOIN_NONCE_INVALID:
|
|
return "RADIOLIB_ERR_JOIN_NONCE_INVALID";
|
|
case RADIOLIB_ERR_N_FCNT_DOWN_INVALID:
|
|
return "RADIOLIB_ERR_N_FCNT_DOWN_INVALID";
|
|
case RADIOLIB_ERR_A_FCNT_DOWN_INVALID:
|
|
return "RADIOLIB_ERR_A_FCNT_DOWN_INVALID";
|
|
case RADIOLIB_ERR_DWELL_TIME_EXCEEDED:
|
|
return "RADIOLIB_ERR_DWELL_TIME_EXCEEDED";
|
|
case RADIOLIB_ERR_CHECKSUM_MISMATCH:
|
|
return "RADIOLIB_ERR_CHECKSUM_MISMATCH";
|
|
case RADIOLIB_ERR_NO_JOIN_ACCEPT:
|
|
return "RADIOLIB_ERR_NO_JOIN_ACCEPT";
|
|
case RADIOLIB_LORAWAN_SESSION_RESTORED:
|
|
return "RADIOLIB_LORAWAN_SESSION_RESTORED";
|
|
case RADIOLIB_LORAWAN_NEW_SESSION:
|
|
return "RADIOLIB_LORAWAN_NEW_SESSION";
|
|
case RADIOLIB_ERR_NONCES_DISCARDED:
|
|
return "RADIOLIB_ERR_NONCES_DISCARDED";
|
|
case RADIOLIB_ERR_SESSION_DISCARDED:
|
|
return "RADIOLIB_ERR_SESSION_DISCARDED";
|
|
}
|
|
return "See https://jgromes.github.io/RadioLib/group__status__codes.html";
|
|
}
|
|
|
|
// helper function to display any issues
|
|
void debug(bool failed, const __FlashStringHelper *message, int state, bool halt)
|
|
{
|
|
if (failed) {
|
|
Serial.print(message);
|
|
Serial.print(" - ");
|
|
Serial.print(stateDecode(state));
|
|
Serial.print(" (");
|
|
Serial.print(state);
|
|
Serial.println(")");
|
|
while (halt) {
|
|
delay(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// helper function to display a byte array
|
|
void arrayDump(uint8_t *buffer, uint16_t len)
|
|
{
|
|
for (uint16_t c = 0; c < len; c++) {
|
|
char b = buffer[c];
|
|
if (b < 0x10) {
|
|
Serial.print('0');
|
|
}
|
|
Serial.print(b, HEX);
|
|
}
|
|
Serial.println();
|
|
}
|
|
|
|
|
|
void setup()
|
|
{
|
|
Serial.begin(115200);
|
|
|
|
while (!Serial);
|
|
|
|
setupBoards();
|
|
|
|
#ifdef RADIO_TCXO_ENABLE
|
|
pinMode(RADIO_TCXO_ENABLE, OUTPUT);
|
|
digitalWrite(RADIO_TCXO_ENABLE, HIGH);
|
|
#endif
|
|
|
|
delay(5000); // Give time to switch to the serial monitor
|
|
|
|
Serial.println(F("\nSetup ... "));
|
|
|
|
Serial.println(F("Initialise the radio"));
|
|
|
|
int16_t state = 0; // return value for calls to RadioLib
|
|
Serial.println(F("Initialise the radio"));
|
|
state = radio.begin();
|
|
debug(state != RADIOLIB_ERR_NONE, F("Initialise radio failed"), state, true);
|
|
|
|
#ifdef USING_DIO2_AS_RF_SWITCH
|
|
#ifdef USING_SX1262
|
|
// Some SX126x modules use DIO2 as RF switch. To enable
|
|
// this feature, the following method can be used.
|
|
// NOTE: As long as DIO2 is configured to control RF switch,
|
|
// it can't be used as interrupt pin!
|
|
if (radio.setDio2AsRfSwitch() != RADIOLIB_ERR_NONE) {
|
|
Serial.println(F("Failed to set DIO2 as RF switch!"));
|
|
while (true);
|
|
}
|
|
#endif //USING_SX1262
|
|
#endif //USING_DIO2_AS_RF_SWITCH
|
|
|
|
#ifdef RADIO_SWITCH_PIN
|
|
// T-MOTION
|
|
const uint32_t pins[] = {
|
|
RADIO_SWITCH_PIN, RADIO_SWITCH_PIN, RADIOLIB_NC,
|
|
};
|
|
static const Module::RfSwitchMode_t table[] = {
|
|
{Module::MODE_IDLE, {0, 0} },
|
|
{Module::MODE_RX, {1, 0} },
|
|
{Module::MODE_TX, {0, 1} },
|
|
END_OF_MODE_TABLE,
|
|
};
|
|
radio.setRfSwitchTable(pins, table);
|
|
#endif
|
|
|
|
#if defined(USING_LR1121)
|
|
// LR1121
|
|
// set RF switch configuration for Wio WM1110
|
|
// Wio WM1110 uses DIO5 and DIO6 for RF switching
|
|
static const uint32_t rfswitch_dio_pins[] = {
|
|
RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6,
|
|
RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC
|
|
};
|
|
|
|
static const Module::RfSwitchMode_t rfswitch_table[] = {
|
|
// mode DIO5 DIO6
|
|
{ LR11x0::MODE_STBY, { LOW, LOW } },
|
|
{ LR11x0::MODE_RX, { HIGH, LOW } },
|
|
{ LR11x0::MODE_TX, { LOW, HIGH } },
|
|
{ LR11x0::MODE_TX_HP, { LOW, HIGH } },
|
|
{ LR11x0::MODE_TX_HF, { LOW, LOW } },
|
|
{ LR11x0::MODE_GNSS, { LOW, LOW } },
|
|
{ LR11x0::MODE_WIFI, { LOW, LOW } },
|
|
END_OF_MODE_TABLE,
|
|
};
|
|
radio.setRfSwitchTable(rfswitch_dio_pins, rfswitch_table);
|
|
|
|
// LR1121 TCXO Voltage 2.85~3.15V
|
|
radio.setTCXO(3.0);
|
|
#endif
|
|
|
|
// Override the default join rate
|
|
uint8_t joinDR = 4;
|
|
|
|
// Setup the OTAA session information
|
|
node.beginOTAA(joinEUI, devEUI, nwkKey, appKey);
|
|
|
|
// ##### setup the flash storage
|
|
store.begin("radiolib");
|
|
// ##### if we have previously saved nonces, restore them and try to restore session as well
|
|
if (store.isKey("nonces")) {
|
|
uint8_t buffer[RADIOLIB_LORAWAN_NONCES_BUF_SIZE]; // create somewhere to store nonces
|
|
store.getBytes("nonces", buffer, RADIOLIB_LORAWAN_NONCES_BUF_SIZE); // get them from the store
|
|
state = node.setBufferNonces(buffer); // send them to LoRaWAN
|
|
debug(state != RADIOLIB_ERR_NONE, F("Restoring nonces buffer failed"), state, false);
|
|
|
|
// recall session from RTC deep-sleep preserved variable
|
|
state = node.setBufferSession(LWsession); // send them to LoRaWAN stack
|
|
|
|
// if we have booted more than once we should have a session to restore, so report any failure
|
|
// otherwise no point saying there's been a failure when it was bound to fail with an empty LWsession var.
|
|
debug((state != RADIOLIB_ERR_NONE), F("Restoring session buffer failed"), state, false);
|
|
|
|
// if Nonces and Session restored successfully, activation is just a formality
|
|
// moreover, Nonces didn't change so no need to re-save them
|
|
if (state == RADIOLIB_ERR_NONE) {
|
|
Serial.println(F("Successfully restored session - now activating"));
|
|
state = node.activateOTAA();
|
|
debug((state != RADIOLIB_LORAWAN_SESSION_RESTORED), F("Failed to activate restored session"), state, true);
|
|
|
|
// ##### close the store before returning
|
|
store.end();
|
|
}
|
|
} else { // store has no key "nonces"
|
|
Serial.println(F("No Nonces saved - starting fresh."));
|
|
}
|
|
|
|
|
|
// if we got here, there was no session to restore, so start trying to join
|
|
uint32_t sleepForSeconds = 60*1000;
|
|
state = RADIOLIB_ERR_NETWORK_NOT_JOINED;
|
|
|
|
while (state != RADIOLIB_LORAWAN_NEW_SESSION) {
|
|
|
|
Serial.println(F("Join ('login') to the LoRaWAN Network"));
|
|
|
|
state = node.activateOTAA();
|
|
|
|
// ##### save the join counters (nonces) to permanent store
|
|
Serial.println(F("Saving nonces to flash"));
|
|
uint8_t buffer[RADIOLIB_LORAWAN_NONCES_BUF_SIZE]; // create somewhere to store nonces
|
|
uint8_t *persist = node.getBufferNonces(); // get pointer to nonces
|
|
memcpy(buffer, persist, RADIOLIB_LORAWAN_NONCES_BUF_SIZE); // copy in to buffer
|
|
store.putBytes("nonces", buffer, RADIOLIB_LORAWAN_NONCES_BUF_SIZE); // send them to the store
|
|
|
|
// we'll save the session after an uplink
|
|
|
|
if (state != RADIOLIB_LORAWAN_NEW_SESSION) {
|
|
Serial.print(F("Join failed: "));
|
|
Serial.println(state);
|
|
|
|
// how long to wait before join attempts. This is an interim solution pending
|
|
// implementation of TS001 LoRaWAN Specification section #7 - this doc applies to v1.0.4 & v1.1
|
|
// it sleeps for longer & longer durations to give time for any gateway issues to resolve
|
|
// or whatever is interfering with the device <-> gateway airwaves.
|
|
// uint32_t sleepForSeconds = min((bootCountSinceUnsuccessfulJoin++ + 1UL) * 60UL, 3UL * 60UL);
|
|
Serial.print(F("Boots since unsuccessful join: "));
|
|
// Serial.println(bootCountSinceUnsuccessfulJoin);
|
|
Serial.print(F("Retrying join in "));
|
|
Serial.print(sleepForSeconds / 1000);
|
|
Serial.println(F(" seconds"));
|
|
|
|
delay(sleepForSeconds);
|
|
|
|
// gotoSleep(sleepForSeconds);
|
|
}
|
|
} // if activateOTAA state
|
|
|
|
// Print the DevAddr
|
|
Serial.print("[LoRaWAN] DevAddr: ");
|
|
Serial.println((unsigned long)node.getDevAddr(), HEX);
|
|
|
|
// Enable the ADR algorithm (on by default which is preferable)
|
|
node.setADR(true);
|
|
|
|
// Set a datarate to start off with
|
|
node.setDatarate(5);
|
|
|
|
// Manages uplink intervals to the TTN Fair Use Policy
|
|
node.setDutyCycle(true, 1250);
|
|
|
|
// Enable the dwell time limits - 400ms is the limit for the US
|
|
node.setDwellTime(true, 400);
|
|
|
|
Serial.println(F("Ready!\n"));
|
|
|
|
if (disp) {
|
|
disp->clearBuffer();
|
|
disp->setFont(u8g2_font_NokiaLargeBold_tf );
|
|
uint16_t str_w = disp->getStrWidth(BOARD_VARIANT_NAME);
|
|
disp->drawStr((disp->getWidth() - str_w) / 2, 16, BOARD_VARIANT_NAME);
|
|
disp->drawHLine(5, 21, disp->getWidth() - 5);
|
|
|
|
disp->setCursor(0, 38);
|
|
disp->print("Join LoRaWAN Ready!");
|
|
disp->sendBuffer();
|
|
}
|
|
}
|
|
|
|
|
|
void loop()
|
|
{
|
|
int16_t state = RADIOLIB_ERR_NONE;
|
|
|
|
// set battery fill level - the LoRaWAN network server
|
|
// may periodically request this information
|
|
// 0 = external power source
|
|
// 1 = lowest (empty battery)
|
|
// 254 = highest (full battery)
|
|
// 255 = unable to measure
|
|
uint8_t battLevel = 146;
|
|
node.setDeviceStatus(battLevel);
|
|
|
|
// This is the place to gather the sensor inputs
|
|
// Instead of reading any real sensor, we just generate some random numbers as example
|
|
uint8_t value1 = radio.random(100);
|
|
uint16_t value2 = radio.random(2000);
|
|
|
|
// Build payload byte array
|
|
uint8_t uplinkPayload[3];
|
|
uplinkPayload[0] = value1;
|
|
uplinkPayload[1] = highByte(value2); // See notes for high/lowByte functions
|
|
uplinkPayload[2] = lowByte(value2);
|
|
|
|
uint8_t downlinkPayload[10]; // Make sure this fits your plans!
|
|
size_t downlinkSize; // To hold the actual payload size received
|
|
|
|
// you can also retrieve additional information about an uplink or
|
|
// downlink by passing a reference to LoRaWANEvent_t structure
|
|
LoRaWANEvent_t uplinkDetails;
|
|
LoRaWANEvent_t downlinkDetails;
|
|
|
|
uint8_t fPort = 10;
|
|
|
|
// Retrieve the last uplink frame counter
|
|
uint32_t fCntUp = node.getFCntUp();
|
|
|
|
// Send a confirmed uplink on the second uplink
|
|
// and also request the LinkCheck and DeviceTime MAC commands
|
|
Serial.println(F("Sending uplink"));
|
|
if (fCntUp == 1) {
|
|
Serial.println(F("and requesting LinkCheck and DeviceTime"));
|
|
node.sendMacCommandReq(RADIOLIB_LORAWAN_MAC_LINK_CHECK);
|
|
node.sendMacCommandReq(RADIOLIB_LORAWAN_MAC_DEVICE_TIME);
|
|
state = node.sendReceive(uplinkPayload, sizeof(uplinkPayload), fPort, downlinkPayload, &downlinkSize, true, &uplinkDetails, &downlinkDetails);
|
|
} else {
|
|
state = node.sendReceive(uplinkPayload, sizeof(uplinkPayload), fPort, downlinkPayload, &downlinkSize, false, &uplinkDetails, &downlinkDetails);
|
|
}
|
|
debug(state < RADIOLIB_ERR_NONE, F("Error in sendReceive"), state, false);
|
|
|
|
// Check if a downlink was received
|
|
// (state 0 = no downlink, state 1/2 = downlink in window Rx1/Rx2)
|
|
if (state > 0) {
|
|
Serial.println(F("Received a downlink"));
|
|
// Did we get a downlink with data for us
|
|
if (downlinkSize > 0) {
|
|
Serial.println(F("Downlink data: "));
|
|
arrayDump(downlinkPayload, downlinkSize);
|
|
} else {
|
|
Serial.println(F("<MAC commands only>"));
|
|
}
|
|
|
|
// print RSSI (Received Signal Strength Indicator)
|
|
Serial.print(F("[LoRaWAN] RSSI:\t\t"));
|
|
Serial.print(radio.getRSSI());
|
|
Serial.println(F(" dBm"));
|
|
|
|
// print SNR (Signal-to-Noise Ratio)
|
|
Serial.print(F("[LoRaWAN] SNR:\t\t"));
|
|
Serial.print(radio.getSNR());
|
|
Serial.println(F(" dB"));
|
|
|
|
// print extra information about the event
|
|
Serial.println(F("[LoRaWAN] Event information:"));
|
|
Serial.print(F("[LoRaWAN] Confirmed:\t"));
|
|
Serial.println(downlinkDetails.confirmed);
|
|
Serial.print(F("[LoRaWAN] Confirming:\t"));
|
|
Serial.println(downlinkDetails.confirming);
|
|
Serial.print(F("[LoRaWAN] Datarate:\t"));
|
|
Serial.println(downlinkDetails.datarate);
|
|
Serial.print(F("[LoRaWAN] Frequency:\t"));
|
|
Serial.print(downlinkDetails.freq, 3);
|
|
Serial.println(F(" MHz"));
|
|
Serial.print(F("[LoRaWAN] Frame count:\t"));
|
|
Serial.println(downlinkDetails.fCnt);
|
|
Serial.print(F("[LoRaWAN] Port:\t\t"));
|
|
Serial.println(downlinkDetails.fPort);
|
|
Serial.print(F("[LoRaWAN] Time-on-air: \t"));
|
|
Serial.print(node.getLastToA());
|
|
Serial.println(F(" ms"));
|
|
Serial.print(F("[LoRaWAN] Rx window: \t"));
|
|
Serial.println(state);
|
|
|
|
uint8_t margin = 0;
|
|
uint8_t gwCnt = 0;
|
|
if (node.getMacLinkCheckAns(&margin, &gwCnt) == RADIOLIB_ERR_NONE) {
|
|
Serial.print(F("[LoRaWAN] LinkCheck margin:\t"));
|
|
Serial.println(margin);
|
|
Serial.print(F("[LoRaWAN] LinkCheck count:\t"));
|
|
Serial.println(gwCnt);
|
|
}
|
|
|
|
uint32_t networkTime = 0;
|
|
uint8_t fracSecond = 0;
|
|
if (node.getMacDeviceTimeAns(&networkTime, &fracSecond, true) == RADIOLIB_ERR_NONE) {
|
|
Serial.print(F("[LoRaWAN] DeviceTime Unix:\t"));
|
|
Serial.println(networkTime);
|
|
Serial.print(F("[LoRaWAN] DeviceTime second:\t1/"));
|
|
Serial.println(fracSecond);
|
|
}
|
|
|
|
} else {
|
|
Serial.println(F("[LoRaWAN] No downlink received"));
|
|
}
|
|
|
|
// wait before sending another packet
|
|
uint32_t minimumDelay = uplinkIntervalSeconds * 1000UL;
|
|
uint32_t interval = node.timeUntilUplink(); // calculate minimum duty cycle delay (per FUP & law!)
|
|
uint32_t delayMs = max(interval, minimumDelay); // cannot send faster than duty cycle allows
|
|
|
|
Serial.print(F("[LoRaWAN] Next uplink in "));
|
|
Serial.print(delayMs / 1000);
|
|
Serial.println(F(" seconds\n"));
|
|
|
|
delay(delayMs);
|
|
}
|