Ok, after a week of testing 24/7 we can pbly claim there is a workaround for this.
I’m putting here my notes just in case someone needs some hint, pbly there is a more clean way of integrating these features in mainline code.
- in LoRaWan_APP.cpp I use an external function to report back to my app when an ACK is received (this could be a weak link if we have a forma api for a call like this…)
extern void myLoRaWanFCNCheck(uint32_t currFCN, bool ackReceived, uint8_t NbRetries);
- in LoRaWan_APP.cpp I added the call into the McpsConfirm() function like this:
static void McpsConfirm( McpsConfirm_t *mcpsConfirm )
{
if( mcpsConfirm->Status == LORAMAC_EVENT_INFO_STATUS_OK )
{
switch( mcpsConfirm->McpsRequest )
{
case MCPS_UNCONFIRMED:
{
// Check Datarate
// Check TxPower
break;
}
case MCPS_CONFIRMED:
{
efestoLoRaWanFCNCheck( mcpsConfirm->UpLinkCounter, mcpsConfirm->AckReceived, mcpsConfirm->NbRetries);
// Check Datarate
// Check TxPower
// Check AckReceived
// Check NbTrials
break;
}
case MCPS_PROPRIETARY:
{
break;
}
default:
break;
}
}
nextTx = true;
}
As I mentioned before for my specific application I do not need to send messages at every wakeup of the device. I send messages only in specific situations and so I needed a way to terminate the code loop that we have in the default lorawan example (see example of sending messages with interrupt).
I’m using confirmed messages and so we need a way to either wait or terminate the loop based on the ACK/NO_ACK feedback.
If we missed one ack … we can wait until all the retry are done. If there are no more retry … and we did not receive any ack … we trigger a re-join, which will take care of setting the FCN to zero.
This also should take care of the use case when the gateway router goes down and eventually comes back and we need to re-authenticate to it.
here is my version of the code in my file (called myLoRaWAN.ino)
void lorawanLoop()
{
volatile bool loopDone = false;
unsigned long loraWanTimeout = 0;
unsigned long loraWanTimeoutMax = (4*ONESEC_MSECS);
unsigned long tmpTime = 0;
bool send_f = false;
/*
* inner LoRaWAN library loop
*/
ackReceived = true; /* this will be set before each send call */
ackWait = confirmedNbTrials; /* by default we wait until ack is received or quit */
while(loopDone == false)
{
switch( deviceState )
{
case DEVICE_STATE_INIT:
{
loraWanTimeout = 0;
//printDevParam();
LoRaWAN.init(loraWanClass,loraWanRegion);
deviceState = DEVICE_STATE_JOIN;
break;
}
case DEVICE_STATE_JOIN:
{
loraWanTimeout = 0;
LOG_MSGLN("[LoRaWAN: Join] ");
LoRaWAN.join();
break;
}
case DEVICE_STATE_SEND:
{
loraWanTimeout = 0;
LOG_MSGLN("[LoRaWAN: SEND] ");
if(timeReq_f)
{
appPort = DEVPORT;
TimerSysTime_t sysTimeCurrent = TimerGetSysTime( );
LOG_PRINTF("[TIME] Current Unix time:%u.%d\r\n",(unsigned int)sysTimeCurrent.Seconds, sysTimeCurrent.SubSeconds);
timeReq_f = false;
MlmeReq_t mlmeReq;
mlmeReq.Type = MLME_DEVICE_TIME;
LoRaMacMlmeRequest( &mlmeReq );
}
appPort = DEVPORT;
prepareTxFrame( appPort );
ackReceived = false;
LoRaWAN.send();
loraWanTimeout = 0;
deviceState = DEVICE_STATE_CYCLE;
break;
}
case DEVICE_STATE_CYCLE:
{
loraWanTimeout = 0;
// Schedule next packet transmission
txDutyCycleTime = appTxDutyCycle + randr( 0, APP_TX_DUTYCYCLE_RND );
LoRaWAN.cycle(txDutyCycleTime);
deviceState = DEVICE_STATE_SLEEP;
break;
}
case DEVICE_STATE_SLEEP:
{
/*******/
if (send_f) {
if (IsLoRaMacNetworkJoined) {
appPort = APPPORT;
if(prepareEfestoFrame(appPort)) {
ackReceived = false;
LoRaWAN.send();
loraWanTimeout = 0;
}
}
send_f = false;
}
/*****/
if(rtcTimeIsSync())
{
tmpTime = millis();
if(loraWanTimeout == 0)
{
loraWanTimeout = tmpTime;
}
if(ackReceived)
{
//Serial.println("ACK RECEIVED or NO_ACK_REQ, quit the loop.");
loopDone = true;
}
else if( (tmpTime - loraWanTimeout) >= LORAWAN_TIMEOUT )
{
if(ackWait > 0)
{
//Serial.println("ACK MISSED, timeout extend.");
ackWait--;
loopDone = false;
loraWanTimeout = 0;
}
else
{
//Serial.println("ACK TOTALLY MISSED, re-join ");
/* this will trigger a re-join when we miss ack & send again */
deviceState = DEVICE_STATE_INIT;
send_f = true;
loopDone = false;
}
}
}
LoRaWAN.sleep();
break;
}
default:
{
deviceState = DEVICE_STATE_INIT;
break;
}
}
}
}
and of course at the beginning of the file the flags are initialized as follow:
/* flag to requequest time */
static bool timeReq_f = 1;
static bool firstRun_f = 1;
/* handles missing hack */
static int8_t ackWait = 0;
static bool ackReceived = false;
static uint32_t ackCurrFCN = 0;