Version of personalized IMPACT LoRa

Fork of LMiC-10secs by Alcatel-Lucent IoT Development

Revision:
1:d3b7bde3995c
Parent:
0:62d1edcc13d1
Child:
2:bebd6b2e3d18
--- a/lmic.cpp	Thu Jan 22 12:50:49 2015 +0000
+++ b/lmic.cpp	Tue Mar 31 13:36:56 2015 +0000
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2014 IBM Corporation.
+ * Copyright (c) 2014-2015 IBM Corporation.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -9,10 +9,13 @@
  *    IBM Zurich Research Lab - initial API, implementation and documentation
  *******************************************************************************/
 
+//! \file
 #include "lmic.h"
 
+#if !defined(MINRX_SYMS)
+#define MINRX_SYMS 5
+#endif // !defined(MINRX_SYMS)
 #define PAMBL_SYMS 8
-#define MINRX_SYMS 5
 #define PAMBL_FSK  5
 #define PRERX_FSK  1
 #define RXLEN_FSK  (1+5+2)
@@ -29,6 +32,15 @@
 #define BCN_GUARD_osticks      ms2osticks(BCN_GUARD_ms)
 #define BCN_WINDOW_osticks     ms2osticks(BCN_WINDOW_ms)
 #define AIRTIME_BCN_osticks    us2osticks(AIRTIME_BCN)
+#if defined(CFG_eu868)
+#define DNW2_SAFETY_ZONE       ms2osticks(3000)
+#endif
+#if defined(CFG_us915)
+#define DNW2_SAFETY_ZONE       ms2osticks(750)
+#endif
+
+// Special APIs - for development or testing
+#define isTESTMODE() 0
 
 DEFINE_LMIC;
 DECL_ON_LMIC_EVENT;
@@ -42,36 +54,36 @@
 // ================================================================================
 // BEG OS - default implementations for certain OS suport functions
 
-#if !HAS_os_calls
+#if !defined(HAS_os_calls)
 
-#ifndef os_rlsbf2
+#if !defined(os_rlsbf2)
 u2_t os_rlsbf2 (xref2cu1_t buf) {
     return (u2_t)(buf[0] | (buf[1]<<8));
 }
 #endif
 
-#ifndef os_rlsbf4
+#if !defined(os_rlsbf4)
 u4_t os_rlsbf4 (xref2cu1_t buf) {
     return (u4_t)(buf[0] | (buf[1]<<8) | ((u4_t)buf[2]<<16) | ((u4_t)buf[3]<<24));
 }
 #endif
 
 
-#ifndef os_rmsbf4
+#if !defined(os_rmsbf4)
 u4_t os_rmsbf4 (xref2cu1_t buf) {
     return (u4_t)(buf[3] | (buf[2]<<8) | ((u4_t)buf[1]<<16) | ((u4_t)buf[0]<<24));
 }
 #endif
 
 
-#ifndef os_wlsbf2
+#if !defined(os_wlsbf2)
 void os_wlsbf2 (xref2u1_t buf, u2_t v) {
     buf[0] = v;
     buf[1] = v>>8;
 }
 #endif
 
-#ifndef os_wlsbf4
+#if !defined(os_wlsbf4)
 void os_wlsbf4 (xref2u1_t buf, u4_t v) {
     buf[0] = v;
     buf[1] = v>>8;
@@ -80,7 +92,7 @@
 }
 #endif
 
-#ifndef os_wmsbf4
+#if !defined(os_wmsbf4)
 void os_wmsbf4 (xref2u1_t buf, u4_t v) {
     buf[3] = v;
     buf[2] = v>>8;
@@ -89,54 +101,27 @@
 }
 #endif
 
-#ifndef os_getBattLevel
+#if !defined(os_getBattLevel)
 u1_t os_getBattLevel (void) {
     return MCMD_DEVS_BATT_NOINFO;
 }
 #endif
 
-#ifndef os_crc16
-static const u2_t CRC_TABLE[] = {
-    0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
-    0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
-    0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
-    0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
-    0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
-    0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
-    0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
-    0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
-    0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
-    0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
-    0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
-    0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
-    0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
-    0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
-    0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
-    0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
-    0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
-    0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
-    0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
-    0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
-    0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
-    0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
-    0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
-    0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
-    0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
-    0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
-    0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
-    0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
-    0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
-    0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
-    0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
-    0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
-};
+#if !defined(os_crc16)
+// New CRC-16 CCITT(XMODEM) checksum for beacons:
 u2_t os_crc16 (xref2u1_t data, uint len) {
-    u2_t fcs = 0;
-    for( uint u=0; u<len; u++ ) {
-    u1_t b = data[u];
-    fcs = (CRC_TABLE[(fcs ^ b) & 0xFF] ^ ((fcs >> 8) & 0xFF));
+    u2_t remainder = 0;
+    u2_t polynomial = 0x1021;
+    for( uint i = 0; i < len; i++ ) {
+        remainder ^= data[i] << 8;
+        for( u1_t bit = 8; bit > 0; bit--) {
+            if( (remainder & 0x8000) )
+                remainder = (remainder << 1) ^ polynomial;
+            else 
+                remainder <<= 1;
+        }
     }
-    return fcs;
+    return remainder;
 }
 #endif
 
@@ -193,7 +178,7 @@
 
 static void aes_cipher (xref2cu1_t key, u4_t devaddr, u4_t seqno, int dndir, xref2u1_t payload, int len) {
     if( len <= 0 )
-    return;
+        return;
     os_clearMem(AESaux, 16);
     AESaux[0] = AESaux[15] = 1; // mode=cipher / dir=down / block counter=1
     AESaux[5] = dndir?1:0;
@@ -225,7 +210,7 @@
 // ================================================================================
 // BEG LORA
 
-#if CFG_eu868 // ========================================
+#if defined(CFG_eu868) // ========================================
 
 #define maxFrameLen(dr) ((dr)<=DR_SF9 ? maxFrameLens[(dr)] : 0xFF)
 const u1_t maxFrameLens [] = { 64,64,64,123 };
@@ -248,7 +233,7 @@
 };
 #define pow2dBm(mcmd_ladr_p1) (TXPOWLEVELS[(mcmd_ladr_p1&MCMD_LADR_POW_MASK)>>MCMD_LADR_POW_SHIFT])
 
-#elif CFG_us915 // ========================================
+#elif defined(CFG_us915) // ========================================
 
 #define maxFrameLen(dr) ((dr)<=DR_SF11CR ? maxFrameLens[(dr)] : 0xFF)
 const u1_t maxFrameLens [] = { 24,66,142,255,255,255,255,255,  66,142 };
@@ -296,21 +281,18 @@
     u1_t bw = getBw(rps);  // 0,1,2 = 125,250,500kHz
     u1_t sf = getSf(rps);  // 0=FSK, 1..6 = SF7..12
     if( sf == FSK ) {
-    // MLu - 12012015
-    return (plen+/*preamble*/5+/*syncword*/3+/*len*/1+/*crc*/2) * /*bits/byte*/8
-        * (s4_t)OSTICKS_PER_SEC / /*kbit/s*/50000;
-    //return (plen+/*preamble*/5+/*syncword*/2+/*len*/1+/*crc*/2) * /*bits/byte*/8
-    //    * (s4_t)OSTICKS_PER_SEC / /*kbit/s*/50000;
+        return (plen+/*preamble*/5+/*syncword*/3+/*len*/1+/*crc*/2) * /*bits/byte*/8
+            * (s4_t)OSTICKS_PER_SEC / /*kbit/s*/50000;
     }
     u1_t sfx = 4*(sf+(7-SF7));
     u1_t q = sfx - (sf >= SF11 ? 8 : 0);
     int tmp = 8*plen - sfx + 28 + (getNocrc(rps)?0:16) - (getIh(rps)?20:0);
     if( tmp > 0 ) {
-    tmp = (tmp + q - 1) / q;
-    tmp *= getCr(rps)+5;
-    tmp += 8;
+        tmp = (tmp + q - 1) / q;
+        tmp *= getCr(rps)+5;
+        tmp += 8;
     } else {
-    tmp = 8;
+        tmp = 8;
     }
     tmp = (tmp<<2) + /*preamble*/49 /* 4 * (8 + 4.25) */;
     // bw = 125000 = 15625 * 2^3
@@ -325,9 +307,9 @@
     sfx = sf+(7-SF7) - (3+2) - bw;
     int div = 15625;
     if( sfx > 4 ) {
-    // prevent 32bit signed int overflow in last step
-    div >>= sfx-4;
-    sfx = 4;
+        // prevent 32bit signed int overflow in last step
+        div >>= sfx-4;
+        sfx = 4;
     }
     // Need 32bit arithmetic for this last step
     return (((ostime_t)tmp << sfx) * OSTICKS_PER_SEC + div/2) / div;
@@ -337,8 +319,6 @@
 extern inline int   s12rssi (s1_t v);
 extern inline float  s12snr (s1_t v);
 extern inline s1_t   snr2s1 (double v);
-extern inline int   getRssi (rxqu_t*rxq);
-extern inline void  setRssi (rxqu_t*rxq, int v);
 
 extern inline rps_t updr2rps (dr_t dr);
 extern inline rps_t dndr2rps (dr_t dr);
@@ -374,23 +354,23 @@
     // normal frames - 1st try / no retry
     0,
     // confirmed frames
-    0,0,0,0,0,1,1,1,2
+    0,0,1,0,1,0,1,0,0
 };
 
 
 // Table below defines the size of one symbol as
 //   symtime = 256us * 2^T(sf,bw)
 // 256us is called one symunit. 
-//                 SF:                      
-//      BW:     |__7___8___9__10__11__12
-//      125kHz     |  2   3   4   5   6   7
-//      250kHz     |  1   2   3   4   5   6
-//      500kHz     |  0   1   2   3   4   5
+//                 SF:                                  
+//      BW:      |__7___8___9__10__11__12
+//      125kHz   |  2   3   4   5   6   7
+//      250kHz   |  1   2   3   4   5   6
+//      500kHz   |  0   1   2   3   4   5
 //  
 // Times for half symbol per DR
 // Per DR table to minimize rounding errors
 static const ostime_t DR2HSYM_osticks[] = {
-#if CFG_eu868
+#if defined(CFG_eu868)
 #define dr2hsym(dr) (DR2HSYM_osticks[(dr)])
     us2osticksRound(128<<7),  // DR_SF12
     us2osticksRound(128<<6),  // DR_SF11
@@ -400,7 +380,7 @@
     us2osticksRound(128<<2),  // DR_SF7
     us2osticksRound(128<<1),  // DR_SF7B
     us2osticksRound(80)       // FSK -- not used (time for 1/2 byte)
-#elif CFG_us915
+#elif defined(CFG_us915)
 #define dr2hsym(dr) (DR2HSYM_osticks[(dr)&7])  // map DR_SFnCR -> 0-6
     us2osticksRound(128<<5),  // DR_SF10   DR_SF12CR
     us2osticksRound(128<<4),  // DR_SF9    DR_SF11CR
@@ -415,13 +395,13 @@
 static ostime_t calcRxWindow (u1_t secs, dr_t dr) {
     ostime_t rxoff, err;
     if( secs==0 ) {
-    // aka 128 secs (next becaon)
-    rxoff = LMIC.drift;
-    err = LMIC.lastDriftDiff;
+        // aka 128 secs (next becaon)
+        rxoff = LMIC.drift;
+        err = LMIC.lastDriftDiff;
     } else {
-    // scheduled RX window within secs into current beacon period
-    rxoff = (LMIC.drift * (ostime_t)secs) >> BCN_INTV_exp;
-    err = (LMIC.lastDriftDiff * (ostime_t)secs) >> BCN_INTV_exp;
+        // scheduled RX window within secs into current beacon period
+        rxoff = (LMIC.drift * (ostime_t)secs) >> BCN_INTV_exp;
+        err = (LMIC.lastDriftDiff * (ostime_t)secs) >> BCN_INTV_exp;
     }
     u1_t rxsyms = MINRX_SYMS;
     err += (ostime_t)LMIC.maxDriftDiff * LMIC.missedBcns;
@@ -434,10 +414,10 @@
 // Setup beacon RX parameters assuming we have an error of ms (aka +/-(ms/2))
 static void calcBcnRxWindowFromMillis (u1_t ms, bit_t ini) {
     if( ini ) {
-    LMIC.drift = 0;
-    LMIC.maxDriftDiff = 0;
-    LMIC.missedBcns = 0;
-    LMIC.bcninfo.flags |= BCN_NODRIFT|BCN_NODDIFF;
+        LMIC.drift = 0;
+        LMIC.maxDriftDiff = 0;
+        LMIC.missedBcns = 0;
+        LMIC.bcninfo.flags |= BCN_NODRIFT|BCN_NODDIFF;
     }
     ostime_t hsym = dr2hsym(DR_BCN);
     LMIC.bcnRxsyms = MINRX_SYMS + ms2osticksCeil(ms) / hsym;
@@ -455,8 +435,8 @@
     u1_t intvExp = rxsched->intvExp;
     ostime_t off = os_rlsbf2(LMIC.frame) & (0x0FFF >> (7 - intvExp)); // random offset (slot units)
     rxsched->rxbase = (LMIC.bcninfo.txtime +
-               BCN_RESERVE_osticks +
-               ms2osticks(BCN_SLOT_SPAN_ms * off)); // random offset osticks
+                       BCN_RESERVE_osticks +
+                       ms2osticks(BCN_SLOT_SPAN_ms * off)); // random offset osticks
     rxsched->slot   = 0;
     rxsched->rxtime = rxsched->rxbase - calcRxWindow(/*secs BCN_RESERVE*/2+(1<<intvExp),rxsched->dr);
     rxsched->rxsyms = LMIC.rxsyms;
@@ -466,16 +446,16 @@
 static bit_t rxschedNext (xref2rxsched_t rxsched, ostime_t cando) {
   again:
     if( rxsched->rxtime - cando >= 0 )
-    return 1;
+        return 1;
     u1_t slot;
     if( (slot=rxsched->slot) >= 128 )
-    return 0;
+        return 0;
     u1_t intv = 1<<rxsched->intvExp;
     if( (rxsched->slot = (slot += (intv))) >= 128 )
-    return 0;
+        return 0;
     rxsched->rxtime = rxsched->rxbase
-    + ((BCN_WINDOW_osticks * (ostime_t)slot) >> BCN_INTV_exp)
-    - calcRxWindow(/*secs BCN_RESERVE*/2+slot+intv,rxsched->dr);
+        + ((BCN_WINDOW_osticks * (ostime_t)slot) >> BCN_INTV_exp)
+        - calcRxWindow(/*secs BCN_RESERVE*/2+slot+intv,rxsched->dr);
     rxsched->rxsyms = LMIC.rxsyms;
     goto again;
 }
@@ -485,9 +465,9 @@
     u2_t r = os_getRndU2();
     ostime_t delay = r;
     if( delay > OSTICKS_PER_SEC )
-    delay = r % (u2_t)OSTICKS_PER_SEC;
+        delay = r % (u2_t)OSTICKS_PER_SEC;
     if( secSpan > 0 )
-    delay += ((u1_t)r % secSpan) * OSTICKS_PER_SEC;
+        delay += ((u1_t)r % secSpan) * OSTICKS_PER_SEC;
     return delay;
 }
 
@@ -495,36 +475,39 @@
 static void txDelay (ostime_t reftime, u1_t secSpan) {
     reftime += rndDelay(secSpan);
     if( LMIC.globalDutyRate == 0  ||  (reftime - LMIC.globalDutyAvail) > 0 ) {
-    LMIC.globalDutyAvail = reftime;
-    LMIC.opmode |= OP_RNDTX;
+        LMIC.globalDutyAvail = reftime;
+        LMIC.opmode |= OP_RNDTX;
     }
 }
 
 
 static void setDrJoin (u1_t reason, u1_t dr) {
     EV(drChange, INFO, (e_.reason    = reason,
-            e_.deveui    = MAIN::CDEV->getEui(),
-            e_.dr        = dr|DR_PAGE,
-            e_.txpow     = LMIC.adrTxPow,
-            e_.prevdr    = LMIC.datarate|DR_PAGE,
-            e_.prevtxpow = LMIC.adrTxPow));
+                        e_.deveui    = MAIN::CDEV->getEui(),
+                        e_.dr        = dr|DR_PAGE,
+                        e_.txpow     = LMIC.adrTxPow,
+                        e_.prevdr    = LMIC.datarate|DR_PAGE,
+                        e_.prevtxpow = LMIC.adrTxPow));
     LMIC.datarate = dr;
-    DO_DEVDB(updateDatarate, dr);
+    DO_DEVDB(LMIC.datarate,datarate);
 }
 
 
 static void setDrTxpow (u1_t reason, u1_t dr, s1_t pow) {
     EV(drChange, INFO, (e_.reason    = reason,
-            e_.deveui    = MAIN::CDEV->getEui(),
-            e_.dr        = dr|DR_PAGE,
-            e_.txpow     = pow,
-            e_.prevdr    = LMIC.datarate|DR_PAGE,
-            e_.prevtxpow = LMIC.adrTxPow));
+                        e_.deveui    = MAIN::CDEV->getEui(),
+                        e_.dr        = dr|DR_PAGE,
+                        e_.txpow     = pow,
+                        e_.prevdr    = LMIC.datarate|DR_PAGE,
+                        e_.prevtxpow = LMIC.adrTxPow));
+    
     if( pow != KEEP_TXPOW )
-    LMIC.adrTxPow = pow;
-    LMIC.datarate = dr;
-    DO_DEVDB(updateDatarate, dr);
-    LMIC.opmode |= OP_NEXTCHNL;
+        LMIC.adrTxPow = pow;
+    if( LMIC.datarate != dr ) {
+        LMIC.datarate = dr;
+        DO_DEVDB(LMIC.datarate,datarate);
+        LMIC.opmode |= OP_NEXTCHNL;
+    }
 }
 
 
@@ -540,74 +523,107 @@
     // App may call LMIC_enableTracking() explicitely before
     // Otherwise tracking is implicitly enabled here
     if( (LMIC.opmode & (OP_TRACK|OP_SCAN)) == 0  &&  LMIC.bcninfoTries == 0 )
-    LMIC_enableTracking(0);
+        LMIC_enableTracking(0);
 }
 
 
-#if CFG_eu868
+#if defined(CFG_eu868)
 // ================================================================================
 //
 // BEG: EU868 related stuff
 //
-enum { BAND_MILLI=0, BAND_CENTI=1, BAND_DECI=2 };
+enum { NUM_DEFAULT_CHANNELS=6 };
 static const u4_t iniChannelFreq[12] = {
     // Join frequencies and duty cycle limit (0.1%)
-    EU868_F1|BAND_MILLI, EU868_F2|BAND_MILLI, EU868_F3|BAND_MILLI,
-    EU868_J4|BAND_MILLI, EU868_J5|BAND_MILLI, EU868_J6|BAND_MILLI,
+    EU868_F1|BAND_MILLI, EU868_J4|BAND_MILLI,
+    EU868_F2|BAND_MILLI, EU868_J5|BAND_MILLI,
+    EU868_F3|BAND_MILLI, EU868_J6|BAND_MILLI,
     // Default operational frequencies
     EU868_F1|BAND_CENTI, EU868_F2|BAND_CENTI, EU868_F3|BAND_CENTI,
     EU868_F4|BAND_MILLI, EU868_F5|BAND_MILLI, EU868_F6|BAND_DECI
 };
 
 static void initDefaultChannels (bit_t join) {
-    LMIC.channelMap = 0x3F;
+    os_clearMem(&LMIC.channelFreq, sizeof(LMIC.channelFreq));
+    os_clearMem(&LMIC.channelDrMap, sizeof(LMIC.channelDrMap));
+    os_clearMem(&LMIC.bands, sizeof(LMIC.bands));
+
+    LMIC.channelMap = 0x1F;
     u1_t su = join ? 0 : 6;
     for( u1_t fu=0; fu<6; fu++,su++ ) {
-    LMIC.channelFreq[fu] = iniChannelFreq[su];
-    LMIC.channelDrs[fu]  = DR_SF12 | (DR_SF7<<4);
+        LMIC.channelFreq[fu]  = iniChannelFreq[su];
+        LMIC.channelDrMap[fu] = DR_RANGE_MAP(DR_SF12,DR_SF7);
     }
-    if( !join )
-    LMIC.channelDrs[1] = DR_SF12 | (DR_FSK<<4);
+    if( !join ) {
+        LMIC.channelDrMap[5] = DR_RANGE_MAP(DR_SF12,DR_SF7);
+        LMIC.channelDrMap[1] = DR_RANGE_MAP(DR_SF12,DR_FSK);
+    }
 
-    LMIC.bands[BAND_MILLI].txcap = 1000;  // 0.1%
-    LMIC.bands[BAND_MILLI].txpow = 14;
-    LMIC.bands[BAND_CENTI].txcap = 100;   // 1%
-    LMIC.bands[BAND_CENTI].txpow = 14;
-    LMIC.bands[BAND_DECI ].txcap = 10;    // 10%
-    LMIC.bands[BAND_DECI ].txpow = 27;
+    LMIC.bands[BAND_MILLI].txcap    = 1000;  // 0.1%
+    LMIC.bands[BAND_MILLI].txpow    = 14;
+    LMIC.bands[BAND_MILLI].lastchnl = os_getRndU1() % MAX_CHANNELS;
+    LMIC.bands[BAND_CENTI].txcap    = 100;   // 1%
+    LMIC.bands[BAND_CENTI].txpow    = 14;
+    LMIC.bands[BAND_CENTI].lastchnl = os_getRndU1() % MAX_CHANNELS;
+    LMIC.bands[BAND_DECI ].txcap    = 10;    // 10%
+    LMIC.bands[BAND_DECI ].txpow    = 27;
+    LMIC.bands[BAND_CENTI].lastchnl = os_getRndU1() % MAX_CHANNELS;
     LMIC.bands[BAND_MILLI].avail = 
     LMIC.bands[BAND_CENTI].avail =
     LMIC.bands[BAND_DECI ].avail = os_getTime();
 }
 
+bit_t LMIC_setupBand (u1_t bandidx, s1_t txpow, u2_t txcap) {
+    if( bandidx > BAND_AUX ) return 0;
+    band_t* b = &LMIC.bands[bandidx];
+    b->txpow = txpow;
+    b->txcap = txcap;
+    b->avail = os_getTime();
+    b->lastchnl = os_getRndU1() % MAX_CHANNELS;
+    return 1;
+}
+
+bit_t LMIC_setupChannel (u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
+    if( chidx >= MAX_CHANNELS )
+        return 0;
+    if( band == -1 ) {
+        if( freq >= 869400000 && freq <= 869650000 )
+            freq |= BAND_DECI;   // 10% 27dBm
+        else if( (freq >= 868000000 && freq <= 868600000) ||
+                 (freq >= 869700000 && freq <= 870000000)  )
+            freq |= BAND_CENTI;  // 1% 14dBm 
+        else 
+            freq |= BAND_MILLI;  // 0.1% 14dBm
+    } else {
+        if( band > BAND_AUX ) return 0;
+        freq = (freq&~3) | band;
+    }
+    LMIC.channelFreq [chidx] = freq;
+    LMIC.channelDrMap[chidx] = drmap==0 ? DR_RANGE_MAP(DR_SF12,DR_SF7) : drmap;
+    LMIC.channelMap |= 1<<chidx;  // enabled right away
+    return 1;
+}
+
+void LMIC_disableChannel (u1_t channel) {
+    LMIC.channelFreq[channel] = 0;
+    LMIC.channelDrMap[channel] = 0;
+    LMIC.channelMap &= ~(1<<channel);
+}
+
 static u4_t convFreq (xref2u1_t ptr) {
     u4_t freq = (os_rlsbf4(ptr-1) >> 8) * 100;
-    if( freq >= EU868_FREQ_MIN && freq <= EU868_FREQ_MAX )
-    freq = 0;
+    if( freq < EU868_FREQ_MIN || freq > EU868_FREQ_MAX )
+        freq = 0;
     return freq;
 }
 
-static bit_t setupChannel (u1_t chidx, u4_t freq, int drs) {
-    if( chidx >= MAX_CHANNELS )
-    return 0;
-    if( freq >= 869400000 && freq <= 869650000 )
-    freq |= BAND_DECI;  // 10% 27dBm
-    else if( (freq >= 868000000 && freq <= 868600000) ||
-         (freq >= 869700000 && freq <= 870000000)  )
-    freq |= BAND_CENTI;  // 1% 14dBm
-    //else 
-    //  freq |= BAND_MILLI;  // 0.1% 14dBm
-    LMIC.channelFreq[chidx] = freq;
-    LMIC.channelDrs [chidx] = drs < 0 ? (DR_SF12|(DR_SF7<<4)) : drs;
-    return 1;
-}
-
 static u1_t mapChannels (u1_t chpage, u2_t chmap) {
-    if( chpage != 0 || (chmap & ~((u2_t)(1<<MAX_CHANNELS)-1)) != 0 )
-    return 0;  // illegal input
+    // Bad page, disable all channel, enable non-existent
+    if( chpage != 0 || chmap==0 || (chmap & ~LMIC.channelMap) != 0 )
+        return 0;  // illegal input
     for( u1_t chnl=0; chnl<MAX_CHANNELS; chnl++ ) {
-    if( (chmap & (1<<chnl)) != 0 && LMIC.channelFreq[chnl] == 0 )
-        chmap &= ~(1<<chnl); // ignore - channel is not defined
+        if( (chmap & (1<<chnl)) != 0 && LMIC.channelFreq[chnl] == 0 )
+            chmap &= ~(1<<chnl); // ignore - channel is not defined
     }
     LMIC.channelMap = chmap;
     return 1;
@@ -623,68 +639,39 @@
     LMIC.freq  = freq & ~(u4_t)3;
     LMIC.txpow = band->txpow;
     band->avail = txbeg + airtime * band->txcap;
-    // Update obsolete avail's to prevent rollover
-    // (needed for devices that send rarely - e.g. once/twice a day)
-    for( u1_t b=0; b<MAX_BANDS; b++ ) {
-    if( LMIC.bands[b].avail - txbeg < 0 )
-        LMIC.bands[b].avail = txbeg;
-    }
     if( LMIC.globalDutyRate != 0 )
-    LMIC.globalDutyAvail = txbeg + (airtime<<LMIC.globalDutyRate);
+        LMIC.globalDutyAvail = txbeg + (airtime<<LMIC.globalDutyRate);
 }
 
 static ostime_t nextTx (ostime_t now) {
-    // If we do not find a suitable channel stop sending (misconfigured device)
-    ostime_t mintime = now + /*10h*/36000*OSTICKS_PER_SEC;
-    s1_t     bmask   = 0;
-    for( u1_t b=0; b<MAX_BANDS; b++ ) {
-    xref2band_t band = &LMIC.bands[b];
-    if( band->txcap == 0 ) // band not setup
-        continue;
-    if( now - band->avail >= 0 ) {
-        bmask = (bmask < 0 ? 0 : bmask) | (1<<b);
-    }
-    else if( mintime - band->avail > 0 ) {
-        mintime = band->avail;
-    }
-    }
-    if( bmask == 0 )
-    return mintime;
-    u1_t chnl, ccnt;
-    u2_t cset, mask;
-    chnl = cset = ccnt = 0;
-    mask = 1;
-    while( mask && mask <= LMIC.channelMap ) {
-    // Channel is enabled AND and in a ready band and can handle the datarate
-    if( (mask & LMIC.channelMap) != 0  &&  (bmask & (1 << (LMIC.channelFreq[chnl] & 0x3))) != 0) {
-        u1_t drs = LMIC.channelDrs[chnl];
-        if( !(isSlowerDR((dr_t)LMIC.datarate, (dr_t)(drs & 0xF)) ||   // lowest datarate
-          isFasterDR((dr_t)LMIC.datarate, (dr_t)(drs >>  4))) ) { // fastest datarate
-        cset |= mask;
-        ccnt++;
+    u1_t bmap=0xF;
+    do {
+        ostime_t mintime = now + /*10h*/36000*OSTICKS_PER_SEC;
+        u1_t band=0;
+        for( u1_t bi=0; bi<4; bi++ ) {
+            if( (bmap & (1<<bi)) && mintime - LMIC.bands[bi].avail > 0 )
+                mintime = LMIC.bands[band = bi].avail;
         }
-    }
-    mask <<= 1;
-    chnl++;
-    }
-    if( ccnt == 0 ) // No eligible channel - misconfigured device?
-    return mintime;
-    ccnt = os_getRndU2() % ccnt;
-    mask = 1;
-    chnl = 0;
-    do {
-    if( (cset & mask) != 0 ) {
-        if( ccnt==0 ) {
-        LMIC.txChnl = chnl;
-        return now;
+        // Find next channel in given band
+        u1_t chnl = LMIC.bands[band].lastchnl;
+        for( u1_t ci=0; ci<MAX_CHANNELS; ci++ ) {
+            if( (chnl = (chnl+1)) >= MAX_CHANNELS )
+                chnl -=  MAX_CHANNELS;
+            if( (LMIC.channelMap & (1<<chnl)) != 0  &&  // channel enabled
+                (LMIC.channelDrMap[chnl] & (1<<(LMIC.datarate&0xF))) != 0  &&
+                band == (LMIC.channelFreq[chnl] & 0x3) ) { // in selected band
+                LMIC.txChnl = LMIC.bands[band].lastchnl = chnl;
+                return mintime;
+            }
         }
-        ccnt--;
-    }
-    mask <<= 1;
-    chnl++;
+        if( (bmap &= ~(1<<band)) == 0 ) {
+            // No feasible channel  found!
+            return mintime;
+        }
     } while(1);
 }
 
+
 static void setBcnRxParams (void) {
     LMIC.dataLen = 0;
     LMIC.freq = LMIC.channelFreq[LMIC.bcnChnl] & ~(u4_t)3;
@@ -694,84 +681,96 @@
 #define setRx1Params() /*LMIC.freq/rps remain unchanged*/
 
 static void initJoinLoop (void) {
-    LMIC.txChnl = os_getRndU1() % 3;
+    LMIC.txChnl = os_getRndU1() % 6;
     LMIC.adrTxPow = 14;
     setDrJoin(DRCHG_SET, DR_SF7);
     initDefaultChannels(1);
     ASSERT((LMIC.opmode & OP_NEXTCHNL)==0);
-    LMIC.txend = LMIC.bands[BAND_MILLI].avail;
+    LMIC.txend = LMIC.bands[BAND_MILLI].avail + rndDelay(8);
 }
 
 
 static ostime_t nextJoinState (void) {
+    u1_t failed = 0;
+
     // Try 869.x and then 864.x with same DR
     // If both fail try next lower datarate
+    if( ++LMIC.txChnl == 6 )
+        LMIC.txChnl = 0;
+    if( (++LMIC.txCnt & 1) == 0 ) {
+        // Lower DR every 2nd try (having tried 868.x and 864.x with the same DR)
+        if( LMIC.datarate == DR_SF12 )
+            failed = 1; // we have tried all DR - signal EV_JOIN_FAILED
+        else
+            setDrJoin(DRCHG_NOJACC, decDR((dr_t)LMIC.datarate));
+    }
+    // Clear NEXTCHNL because join state engine controls channel hopping
     LMIC.opmode &= ~OP_NEXTCHNL;
-    LMIC.txend = LMIC.bands[BAND_MILLI].avail;
-    if( LMIC.txChnl < 3 ) {
-    LMIC.txChnl += 3;
-    } else {
-    if( LMIC.datarate == DR_SF12 ) {
-        setDrJoin(DRCHG_NOJACC, DR_SF7);
-        // We tried one round of FSK..SF12
-        // Now pick new random channel and insert a random delay and try another round.
-        LMIC.txChnl = os_getRndU1() % 3;
-        if( LMIC.txCnt < 5 )
-        LMIC.txCnt += 1;
-        // Delay by 7,15,31,63,127,255 secs
-        return rndDelay((4<<LMIC.txCnt)-1) | 1;   // |1 - trigger EV_JOIN_FAILED event
-    }
-    LMIC.txChnl = LMIC.txChnl==5 ? 0 : LMIC.txChnl-2;
-    setDrJoin(DRCHG_NOJACC, decDR((dr_t)LMIC.datarate));
-    }
-    // Avoid collision with JOIN ACCEPT being sent by GW (but we missed it)
-    return 3*OSTICKS_PER_SEC;
+    // Move txend to randomize synchronized concurrent joins.
+    // Duty cycle is based on txend.
+    ostime_t time = os_getTime();
+    if( time - LMIC.bands[BAND_MILLI].avail < 0 )
+        time = LMIC.bands[BAND_MILLI].avail;
+    LMIC.txend = time +
+        (isTESTMODE()
+         // Avoid collision with JOIN ACCEPT @ SF12 being sent by GW (but we missed it)
+         ? DNW2_SAFETY_ZONE
+         // Otherwise: randomize join (street lamp case):
+         // SF12:255, SF11:127, .., SF7:8secs
+         : DNW2_SAFETY_ZONE+rndDelay(255>>LMIC.datarate));
+    // 1 - triggers EV_JOIN_FAILED event
+    return failed;
 }
 
 //
 // END: EU868 related stuff
 //
 // ================================================================================
-#elif CFG_us915
+#elif defined(CFG_us915)
 // ================================================================================
 //
 // BEG: US915 related stuff
 //
 
+
 static void initDefaultChannels (void) {
     for( u1_t i=0; i<4; i++ )
-    LMIC.channelMap[i] = 0xFFFF;
+        LMIC.channelMap[i] = 0xFFFF;
     LMIC.channelMap[4] = 0x00FF;
 }
 
 static u4_t convFreq (xref2u1_t ptr) {
     u4_t freq = (os_rlsbf4(ptr-1) >> 8) * 100;
     if( freq >= US915_FREQ_MIN && freq <= US915_FREQ_MAX )
-    freq = 0;
+        freq = 0;
     return freq;
 }
 
-static bit_t setupChannel (u1_t chidx, u4_t freq, int drs) {
+bit_t LMIC_setupChannel (u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
     if( chidx < 72 || chidx >= 72+MAX_XCHANNELS )
-    return 0; // channels 0..71 are hardwired
+        return 0; // channels 0..71 are hardwired
     chidx -= 72;
     LMIC.xchFreq[chidx] = freq;
-    LMIC.xchDrs[chidx] = drs < 0 ? (DR_SF10|(DR_SF8C<<4)) : drs;
+    LMIC.xchDrMap[chidx] = drmap==0 ? DR_RANGE_MAP(DR_SF10,DR_SF8C) : drmap;
     LMIC.channelMap[chidx>>4] |= (1<<(chidx&0xF));
     return 1;
 }
 
+void LMIC_disableChannel (u1_t channel) {
+    if( channel < 72+MAX_XCHANNELS )
+        LMIC.channelMap[channel/4] &= ~(1<<(channel&0xF));
+}
 
 static u1_t mapChannels (u1_t chpage, u2_t chmap) {
     if( chpage == MCMD_LADR_CHP_125ON || chpage == MCMD_LADR_CHP_125OFF ) {
-    u2_t en125 = chpage == MCMD_LADR_CHP_125ON ? 0xFFFF : 0x0000;
-    for( u1_t u=0; u<4; u++ )
-        LMIC.channelMap[u] = en125;
-    LMIC.channelMap[64/16] = chmap;
+        u2_t en125 = chpage == MCMD_LADR_CHP_125ON ? 0xFFFF : 0x0000;
+        for( u1_t u=0; u<4; u++ )
+            LMIC.channelMap[u] = en125;
+        LMIC.channelMap[64/16] = chmap;
     } else {
-    if( chpage >= (72+MAX_XCHANNELS+15)/16 )
-        return 0;
-    LMIC.channelMap[chpage] = chmap;
+        if( chpage >= (72+MAX_XCHANNELS+15)/16 )
+            return 0;
+        LMIC.channelMap[chpage] = chmap;
     }
     return 1;
 }
@@ -779,77 +778,68 @@
 static void updateTx (ostime_t txbeg) {
     u1_t chnl = LMIC.txChnl;
     if( chnl < 64 ) {
-    LMIC.freq = US915_125kHz_UPFBASE + chnl*US915_125kHz_UPFSTEP;
-    LMIC.txpow = 30;
-    return;
+        LMIC.freq = US915_125kHz_UPFBASE + chnl*US915_125kHz_UPFSTEP;
+        LMIC.txpow = 30;
+        return;
     }
     LMIC.txpow = 26;
     if( chnl < 64+8 ) {
-    LMIC.freq = US915_500kHz_UPFBASE + (chnl-64)*US915_500kHz_UPFSTEP;
+        LMIC.freq = US915_500kHz_UPFBASE + (chnl-64)*US915_500kHz_UPFSTEP;
     } else {
-    ASSERT(chnl < 64+8+MAX_XCHANNELS);
-    LMIC.freq = LMIC.xchFreq[chnl-72];
+        ASSERT(chnl < 64+8+MAX_XCHANNELS);
+        LMIC.freq = LMIC.xchFreq[chnl-72];
     }
 
     // Update global duty cycle stats
     if( LMIC.globalDutyRate != 0 ) {
-    ostime_t airtime = calcAirTime(LMIC.rps, LMIC.dataLen);
-    LMIC.globalDutyAvail = txbeg + (airtime<<LMIC.globalDutyRate);
+        ostime_t airtime = calcAirTime(LMIC.rps, LMIC.dataLen);
+        LMIC.globalDutyAvail = txbeg + (airtime<<LMIC.globalDutyRate);
     }
 }
 
 // US does not have duty cycling - return now as earliest TX time
 #define nextTx(now) (_nextTx(),(now))
 static void _nextTx (void) {
-    u1_t  chnl  = LMIC.txChnl;
-    u1_t  cnt   = 0;
-    bit_t bw500 = (LMIC.datarate >= DR_SF8C);
-
-    if( (LMIC.chRnd & 0xF) == 0 ) {
-    chnl += LMIC.chRnd>>4;
-    LMIC.chRnd = os_getRndU1();
+    if( LMIC.chRnd==0 )
+        LMIC.chRnd = os_getRndU1() & 0x3F;
+    if( LMIC.datarate >= DR_SF8C ) { // 500kHz
+        u1_t map = LMIC.channelMap[64/16]&0xFF;
+        for( u1_t i=0; i<8; i++ ) {
+            if( (map & (1<<(++LMIC.chRnd & 7))) != 0 ) {
+                LMIC.txChnl = 64 + (LMIC.chRnd & 7);
+                return;
+            }
+        }
+    } else { // 125kHz
+        for( u1_t i=0; i<64; i++ ) {
+            u1_t chnl = ++LMIC.chRnd & 0x3F;
+            if( (LMIC.channelMap[(chnl >> 4)] & (1<<(chnl & 0xF))) != 0 ) {
+                LMIC.txChnl = chnl;
+                return;
+            }
+        }
     }
-    LMIC.chRnd--;
-  again:
-    chnl += 1;
-    if( bw500 ) {
-    // Only channels 64..71
-    chnl = 64+(chnl&0x7);
-    // At least one 500kHz channel must be enabled
-    // - otherwise MAC should not have used such a datarate
-    ASSERT((LMIC.channelMap[64/16]&0xFF)!=0x00);
-    if( ++cnt == 8 )
-        return; // no appropriate channel enabled stay where we are
-    } else {
-    // At least one 125kHz channel must be enabled
-    // - otherwise MAC should not have used DR_SF10-7
-    ASSERT((LMIC.channelMap[0]|LMIC.channelMap[1]|LMIC.channelMap[2]|LMIC.channelMap[3])!=0x0000);
-    chnl = chnl&0x3F;
-    if( ++cnt == 64 )
-        return; // no appropriate channel enabled stay where we are
-    }
-    if( (LMIC.channelMap[(chnl >> 4)] & (1<<(chnl&0xF))) == 0 )
-    goto again;  // not enabled
-    LMIC.txChnl = chnl;
+    // No feasible channel  found! Keep old one.
 }
 
 static void setBcnRxParams (void) {
     LMIC.dataLen = 0;
-    LMIC.freq = US915_500kHz_DNFBASE + LMIC.bcnChnl*US915_500kHz_DNFSTEP;
+    LMIC.freq = US915_500kHz_DNFBASE + LMIC.bcnChnl * US915_500kHz_DNFSTEP;
     LMIC.rps  = setIh(setNocrc(dndr2rps((dr_t)DR_BCN),1),LEN_BCN);
 }
 
-#define setRx1Params(void) {                         \
+#define setRx1Params() {                                                \
     LMIC.freq = US915_500kHz_DNFBASE + (LMIC.txChnl & 0x7) * US915_500kHz_DNFSTEP; \
-    if( /* TX datarate */LMIC.rxsyms < DR_SF8C )                \
-    LMIC.rxsyms += DR_SF10CR - DR_SF10;                \
-    else if( LMIC.rxsyms == DR_SF8C )                    \
-    LMIC.rxsyms = DR_SF7CR;                        \
-    LMIC.rps = dndr2rps(LMIC.rxsyms);                    \
+    if( /* TX datarate */LMIC.dndr < DR_SF8C )                          \
+        LMIC.dndr += DR_SF10CR - DR_SF10;                               \
+    else if( LMIC.dndr == DR_SF8C )                                     \
+        LMIC.dndr = DR_SF7CR;                                           \
+    LMIC.rps = dndr2rps(LMIC.dndr);                                     \
 }
 
 static void initJoinLoop (void) {
-    LMIC.txChnl = os_getRndU1() & 0x3F;
+    LMIC.chRnd = 0;
+    LMIC.txChnl = 0;
     LMIC.adrTxPow = 20;
     ASSERT((LMIC.opmode & OP_NEXTCHNL)==0);
     LMIC.txend = os_getTime();
@@ -861,23 +851,29 @@
     //   SF7/8/9/10  on a random channel 0..63
     //   SF8C        on a random channel 64..71
     //
-    LMIC.opmode &= ~OP_NEXTCHNL;
-    LMIC.txend = os_getTime();
+    u1_t failed = 0;
     if( LMIC.datarate != DR_SF8C ) {
-    LMIC.txChnl = 64+(LMIC.txChnl&7);
-    LMIC.datarate = DR_SF8C;
+        LMIC.txChnl = 64+(LMIC.txChnl&7);
+        setDrJoin(DRCHG_SET, DR_SF8C);
     } else {
-    LMIC.txChnl = os_getRndU1() & 0x3F;
-    LMIC.datarate = DR_SF7 - (LMIC.txCnt++ & 3);
-    if( (LMIC.txCnt & 3) == 0 ) {
-        // Tried SF10/SF8C
-        // Delay by 7,15,31,63,127,255 secs
-        return rndDelay((4<<(LMIC.txCnt>>2))-1) | 1;   // |1 - trigger EV_JOIN_FAILED event
+        LMIC.txChnl = os_getRndU1() & 0x3F;
+        s1_t dr = DR_SF7 - ++LMIC.txCnt;
+        if( dr < DR_SF10 ) {
+            dr = DR_SF10;
+            failed = 1; // All DR exhausted - signal failed
+        }
+        setDrJoin(DRCHG_SET, dr);
     }
-    }
-    // Always wait 500ms in case there was a TX and we just missed it.
-    // Sending immediately would not be picked up because GW is still busy txing missed frame.
-    return (OSTICKS_PER_SEC/2) & ~1;
+    LMIC.opmode &= ~OP_NEXTCHNL;
+    LMIC.txend = os_getTime() +
+        (isTESTMODE()
+         // Avoid collision with JOIN ACCEPT being sent by GW (but we missed it - GW is still busy)
+         ? DNW2_SAFETY_ZONE
+         // Otherwise: randomize join (street lamp case):
+         // SF10:16, SF9=8,..SF8C:1secs
+         : rndDelay(16>>LMIC.datarate));
+    // 1 - triggers EV_JOIN_FAILED event
+    return failed;
 }
 
 //
@@ -896,13 +892,20 @@
 
 static void reportEvent (ev_t ev) {
     EV(devCond, INFO, (e_.reason = EV::devCond_t::LMIC_EV,
-               e_.eui    = MAIN::CDEV->getEui(),
-               e_.info   = ev));
+                       e_.eui    = MAIN::CDEV->getEui(),
+                       e_.info   = ev));
     ON_LMIC_EVENT(ev);
     engineUpdate();
 }
 
 
+static void runReset (xref2osjob_t osjob) {
+    // Disable session
+    LMIC_reset();
+    LMIC_startJoining();
+    reportEvent(EV_RESET);
+}
+
 static void stateJustJoined (void) {
     LMIC.seqnoDn     = LMIC.seqnoUp = 0;
     LMIC.rejoinCnt   = 0;
@@ -927,23 +930,30 @@
 static int decodeBeacon (void) {
     ASSERT(LMIC.dataLen == LEN_BCN); // implicit header RX guarantees this
     xref2u1_t d = LMIC.frame;
-    if( os_rlsbf2(&d[OFF_BCN_CRC1]) != os_crc16(d, OFF_BCN_CRC1) )
-    return 0;   // first (common) part fails CRC check
+    if(
+#if CFG_eu868
+        d[OFF_BCN_CRC1] != (u1_t)os_crc16(d,OFF_BCN_CRC1)
+#elif CFG_us915
+        os_rlsbf2(&d[OFF_BCN_CRC1]) != os_crc16(d,OFF_BCN_CRC1)
+#endif
+        )
+        return 0;   // first (common) part fails CRC check
     // First set of fields is ok
     u4_t bcnnetid = os_rlsbf4(&d[OFF_BCN_NETID]) & 0xFFFFFF;
     if( bcnnetid != LMIC.netid )
-    return -1;  // not the beacon we're looking for
+        return -1;  // not the beacon we're looking for
 
     LMIC.bcninfo.flags &= ~(BCN_PARTIAL|BCN_FULL);
     // Match - update bcninfo structure
-    os_copyMem((xref2u1_t)&LMIC.bcninfo.rxq, (xref2u1_t)&LMIC.rxq, SIZEOFEXPR(LMIC.rxq));
+    LMIC.bcninfo.snr    = LMIC.snr;
+    LMIC.bcninfo.rssi   = LMIC.rssi;
     LMIC.bcninfo.txtime = LMIC.rxtime - AIRTIME_BCN_osticks;
     LMIC.bcninfo.time   = os_rlsbf4(&d[OFF_BCN_TIME]);
     LMIC.bcninfo.flags |= BCN_PARTIAL;
 
     // Check 2nd set
     if( os_rlsbf2(&d[OFF_BCN_CRC2]) != os_crc16(d,OFF_BCN_CRC2) )
-    return 1;
+        return 1;
     // Second set of fields is ok
     LMIC.bcninfo.lat    = (s4_t)os_rlsbf4(&d[OFF_BCN_LAT-1]) >> 8; // read as signed 24-bit
     LMIC.bcninfo.lon    = (s4_t)os_rlsbf4(&d[OFF_BCN_LON-1]) >> 8; // ditto
@@ -959,16 +969,16 @@
     u1_t ftype  = hdr & HDR_FTYPE;
     int  dlen   = LMIC.dataLen;
     if( dlen < OFF_DAT_OPTS+4 ||
-    (hdr & HDR_MAJOR) != HDR_MAJOR_V1 ||
-    (ftype != HDR_FTYPE_DADN  &&  ftype != HDR_FTYPE_DCDN) ) {
-    // Basic sanity checks failed
-    EV(specCond, WARN, (e_.reason = EV::specCond_t::UNEXPECTED_FRAME,
-                e_.eui    = MAIN::CDEV->getEui(),
-                e_.info   = dlen < 4 ? 0 : os_rlsbf4(&d[dlen-4]),
-                e_.info2  = hdr + (dlen<<8)));
+        (hdr & HDR_MAJOR) != HDR_MAJOR_V1 ||
+        (ftype != HDR_FTYPE_DADN  &&  ftype != HDR_FTYPE_DCDN) ) {
+        // Basic sanity checks failed
+        EV(specCond, WARN, (e_.reason = EV::specCond_t::UNEXPECTED_FRAME,
+                            e_.eui    = MAIN::CDEV->getEui(),
+                            e_.info   = dlen < 4 ? 0 : os_rlsbf4(&d[dlen-4]),
+                            e_.info2  = hdr + (dlen<<8)));
       norx:
-    LMIC.dataLen = 0;
-    return 0;
+        LMIC.dataLen = 0;
+        return 0;
     }
     // Validate exact frame length
     // Note: device address was already read+evaluated in order to arrive here.
@@ -981,252 +991,261 @@
     int  pend  = dlen-4;  // MIC
 
     if( addr != LMIC.devaddr ) {
-    EV(specCond, WARN, (e_.reason = EV::specCond_t::ALIEN_ADDRESS,
-                e_.eui    = MAIN::CDEV->getEui(),
-                e_.info   = addr,
-                e_.info2  = LMIC.devaddr));
-    goto norx;
+        EV(specCond, WARN, (e_.reason = EV::specCond_t::ALIEN_ADDRESS,
+                            e_.eui    = MAIN::CDEV->getEui(),
+                            e_.info   = addr,
+                            e_.info2  = LMIC.devaddr));
+        goto norx;
     }
     if( poff > pend ) {
-    EV(specCond, ERR, (e_.reason = EV::specCond_t::CORRUPTED_FRAME,
-               e_.eui    = MAIN::CDEV->getEui(),
-               e_.info   = 0x1000000 + (poff-pend) + (fct<<8) + (dlen<<16)));
-    goto norx;
+        EV(specCond, ERR, (e_.reason = EV::specCond_t::CORRUPTED_FRAME,
+                           e_.eui    = MAIN::CDEV->getEui(),
+                           e_.info   = 0x1000000 + (poff-pend) + (fct<<8) + (dlen<<16)));
+        goto norx;
     }
 
     int port = -1;
     int replayConf = 0;
 
     if( pend > poff )
-    port = d[poff++];
+        port = d[poff++];
 
     seqno = LMIC.seqnoDn + (u2_t)(seqno - LMIC.seqnoDn);
 
     if( !aes_verifyMic(LMIC.nwkKey, LMIC.devaddr, seqno, /*dn*/1, d, pend) ) {
-    EV(specCond, ERR, (e_.reason = EV::specCond_t::CORRUPTED_MIC,
-               e_.eui    = MAIN::CDEV->getEui(),
-               e_.info   = Base::lsbf4(&d[pend]),
-               e_.info2  = seqno));
-    goto norx;
-    }
-    if( seqno < LMIC.seqnoDn ) {
-    if( (s4_t)seqno > (s4_t)LMIC.seqnoDn ) {
-        EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_ROLL_OVER,
-                e_.eui    = MAIN::CDEV->getEui(),
-                e_.info   = LMIC.seqnoDn, 
-                e_.info2  = seqno));
+        EV(spe3Cond, ERR, (e_.reason = EV::spe3Cond_t::CORRUPTED_MIC,
+                           e_.eui1   = MAIN::CDEV->getEui(),
+                           e_.info1  = Base::lsbf4(&d[pend]),
+                           e_.info2  = seqno,
+                           e_.info3  = LMIC.devaddr));
         goto norx;
     }
-    if( seqno != LMIC.seqnoDn-1 || !LMIC.dnConf || ftype != HDR_FTYPE_DCDN ) {
-        EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_OBSOLETE,
-                e_.eui    = MAIN::CDEV->getEui(),
-                e_.info   = LMIC.seqnoDn, 
-                e_.info2  = seqno));
-        goto norx;
-    }
-    // Replay of previous sequence number allowed only if
-    // previous frame and repeated both requested confirmation
-    replayConf = 1;
+    if( seqno < LMIC.seqnoDn ) {
+        if( (s4_t)seqno > (s4_t)LMIC.seqnoDn ) {
+            EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_ROLL_OVER,
+                                e_.eui    = MAIN::CDEV->getEui(),
+                                e_.info   = LMIC.seqnoDn, 
+                                e_.info2  = seqno));
+            goto norx;
+        }
+        if( seqno != LMIC.seqnoDn-1 || !LMIC.dnConf || ftype != HDR_FTYPE_DCDN ) {
+            EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_OBSOLETE,
+                                e_.eui    = MAIN::CDEV->getEui(),
+                                e_.info   = LMIC.seqnoDn, 
+                                e_.info2  = seqno));
+            goto norx;
+        }
+        // Replay of previous sequence number allowed only if
+        // previous frame and repeated both requested confirmation
+        replayConf = 1;
     }
     else {
-    if( seqno > LMIC.seqnoDn ) {
-        EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_SKIP,
-                e_.eui    = MAIN::CDEV->getEui(),
-                e_.info   = LMIC.seqnoDn, 
-                e_.info2  = seqno));
-    }
-    LMIC.seqnoDn = seqno+1;  // next number to be expected
-    DO_DEVDB(updateSeqnoDn, LMIC.seqnoDn);
-    // DN frame requested confirmation - provide ACK once with next UP frame
-    LMIC.dnConf = (ftype == HDR_FTYPE_DCDN ? FCT_ACK : 0);
+        if( seqno > LMIC.seqnoDn ) {
+            EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_SKIP,
+                                e_.eui    = MAIN::CDEV->getEui(),
+                                e_.info   = LMIC.seqnoDn, 
+                                e_.info2  = seqno));
+        }
+        LMIC.seqnoDn = seqno+1;  // next number to be expected
+        DO_DEVDB(LMIC.seqnoDn,seqnoDn);
+        // DN frame requested confirmation - provide ACK once with next UP frame
+        LMIC.dnConf = (ftype == HDR_FTYPE_DCDN ? FCT_ACK : 0);
     }
 
     if( LMIC.dnConf || (fct & FCT_MORE) )
-    LMIC.opmode |= OP_POLL;
+        LMIC.opmode |= OP_POLL;
 
     // We heard from network
-    LMIC.adrChanged = 0;
-    LMIC.adrAckReq = LINK_CHECK_INIT;
+    LMIC.adrChanged = LMIC.rejoinCnt = 0;
+    if( LMIC.adrAckReq != LINK_CHECK_OFF )
+        LMIC.adrAckReq = LINK_CHECK_INIT;
 
     // Process OPTS
-    int m = LMIC.rxq.rssi - RSSI_OFF - getSensitivity(LMIC.rps);
+    int m = LMIC.rssi - RSSI_OFF - getSensitivity(LMIC.rps);
     LMIC.margin = m < 0 ? 0 : m > 254 ? 254 : m;
 
     xref2u1_t opts = &d[OFF_DAT_OPTS];
     int oidx = 0;
     while( oidx < olen ) {
-    switch( opts[oidx] ) {
-    case MCMD_LCHK_ANS: {
-        //int gwmargin = opts[oidx+1];
-        //int ngws = opts[oidx+2];
-        oidx += 3;
-        continue;
-    }
-    case MCMD_LADR_REQ: {
-        u1_t p1     = opts[oidx+1];            // txpow + DR
-        u2_t chmap  = os_rlsbf2(&opts[oidx+2]);// list of enabled channel
-        u1_t chpage = opts[oidx+4] & MCMD_LADR_CHPAGE_MASK;     // channel page
-        u1_t uprpt  = opts[oidx+4] & MCMD_LADR_REPEAT_MASK;     // up repeat count
-        oidx += 5;
+        switch( opts[oidx] ) {
+        case MCMD_LCHK_ANS: {
+            //int gwmargin = opts[oidx+1];
+            //int ngws = opts[oidx+2];
+            oidx += 3;
+            continue;
+        }
+        case MCMD_LADR_REQ: {
+            u1_t p1     = opts[oidx+1];            // txpow + DR
+            u2_t chmap  = os_rlsbf2(&opts[oidx+2]);// list of enabled channels
+            u1_t chpage = opts[oidx+4] & MCMD_LADR_CHPAGE_MASK;     // channel page
+            u1_t uprpt  = opts[oidx+4] & MCMD_LADR_REPEAT_MASK;     // up repeat count
+            oidx += 5;
 
-        LMIC.ladrAns = 0x80 |     // Include an answer into next frame up
-        MCMD_LADR_ANS_POWACK | MCMD_LADR_ANS_CHACK | MCMD_LADR_ANS_DRACK;
-        if( !mapChannels(chpage, chmap) )
-        LMIC.ladrAns &= ~MCMD_LADR_ANS_CHACK;
-        dr_t dr = (dr_t)(p1>>MCMD_LADR_DR_SHIFT);
-        if( !validDR(dr) ) {
-        LMIC.ladrAns &= ~MCMD_LADR_ANS_DRACK;
-        dr = (dr_t)LMIC.datarate;
-        EV(specCond, ERR, (e_.reason = EV::specCond_t::BAD_MAC_CMD,
-                   e_.eui    = MAIN::CDEV->getEui(),
-                   e_.info   = Base::lsbf4(&d[pend]),
-                   e_.info2  = Base::msbf4(&opts[oidx-4])));
+            LMIC.ladrAns = 0x80 |     // Include an answer into next frame up
+                MCMD_LADR_ANS_POWACK | MCMD_LADR_ANS_CHACK | MCMD_LADR_ANS_DRACK;
+            if( !mapChannels(chpage, chmap) )
+                LMIC.ladrAns &= ~MCMD_LADR_ANS_CHACK;
+            dr_t dr = (dr_t)(p1>>MCMD_LADR_DR_SHIFT);
+            if( !validDR(dr) ) {
+                LMIC.ladrAns &= ~MCMD_LADR_ANS_DRACK;
+                EV(specCond, ERR, (e_.reason = EV::specCond_t::BAD_MAC_CMD,
+                                   e_.eui    = MAIN::CDEV->getEui(),
+                                   e_.info   = Base::lsbf4(&d[pend]),
+                                   e_.info2  = Base::msbf4(&opts[oidx-4])));
+            }
+            if( (LMIC.ladrAns & 0x7F) == (MCMD_LADR_ANS_POWACK | MCMD_LADR_ANS_CHACK | MCMD_LADR_ANS_DRACK) ) {
+                // Nothing went wrong - use settings
+                LMIC.upRepeat = uprpt;
+                setDrTxpow(DRCHG_NWKCMD, dr, pow2dBm(p1));
+            }
+            LMIC.adrChanged = 1;  // Trigger an ACK to NWK
+            continue;
         }
-        LMIC.upRepeat = uprpt;
-        setDrTxpow(DRCHG_NWKCMD, dr, pow2dBm(p1));
-        LMIC.adrChanged = 1;  // Trigger an ACK to NWK
-        continue;
-    }
-    case MCMD_DEVS_REQ: {
-        LMIC.devsAns = 1;
-        oidx += 1;
-        continue;
-    }
-    case MCMD_DN2P_SET: {
-        dr_t dr = (dr_t)(opts[oidx+1] & 0x0F);
-        u4_t freq = convFreq(&opts[oidx+2]);
-        oidx += 5;
-        LMIC.dn2Ans = 0x80;   // answer pending
-        if( validDR(dr) )
-        LMIC.dn2Ans |= MCMD_DN2P_ANS_DRACK;
-        if( freq != 0 )
-        LMIC.dn2Ans |= MCMD_DN2P_ANS_CHACK;
-        if( LMIC.dn2Ans == (0x80|MCMD_DN2P_ANS_DRACK|MCMD_DN2P_ANS_CHACK) ) {
-        LMIC.dn2Dr = dr;
-        LMIC.dn2Freq = freq;
-        DO_DEVDB(updateDn2,LMIC.dn2Dr,LMIC.dn2Freq);
+        case MCMD_DEVS_REQ: {
+            LMIC.devsAns = 1;
+            oidx += 1;
+            continue;
+        }
+        case MCMD_DN2P_SET: {
+            dr_t dr = (dr_t)(opts[oidx+1] & 0x0F);
+            u4_t freq = convFreq(&opts[oidx+2]);
+            oidx += 5;
+            LMIC.dn2Ans = 0x80;   // answer pending
+            if( validDR(dr) )
+                LMIC.dn2Ans |= MCMD_DN2P_ANS_DRACK;
+            if( freq != 0 )
+                LMIC.dn2Ans |= MCMD_DN2P_ANS_CHACK;
+            if( LMIC.dn2Ans == (0x80|MCMD_DN2P_ANS_DRACK|MCMD_DN2P_ANS_CHACK) ) {
+                LMIC.dn2Dr = dr;
+                LMIC.dn2Freq = freq;
+                DO_DEVDB(LMIC.dn2Dr,dn2Dr);
+                DO_DEVDB(LMIC.dn2Freq,dn2Freq);
+            }
+            continue;
         }
-        continue;
-    }
-    case MCMD_DCAP_REQ: {
-        u1_t cap = opts[oidx+1];
-        oidx += 2;
-        // A value cap=0xFF means device is OFF unless enabled again manually
-        // We just set duty cap to 0xF which is 0.003% -- pretty much off.
-        // We don't check 0xF0 bits if cap!=0xFF
-        LMIC.globalDutyRate  = cap & 0xF;
-        LMIC.globalDutyAvail = os_getTime();
-        DO_DEVDB(updateDutyCap,cap);
-        LMIC.dutyCapAns = 1;
-        continue;
-    }
-    case MCMD_SNCH_REQ: {
-        u1_t chidx = opts[oidx+1];  // channel
-        u4_t freq  = convFreq(&opts[oidx+2]); // freq
-        u1_t drs   = opts[oidx+5];  // datarate span
-        LMIC.snchAns = 0x80;
-        if( freq != 0 && setupChannel(chidx,freq,drs) )
-        LMIC.snchAns |= MCMD_SNCH_ANS_DRACK|MCMD_SNCH_ANS_FQACK;
-        oidx += 6;
-    }
-    case MCMD_PING_SET: {
-        u4_t freq = convFreq(&opts[oidx+1]);
-        oidx += 4;
-        u1_t flags = 0x80;
-        if( freq != 0 ) {
-        flags |= MCMD_PING_ANS_FQACK;
-        LMIC.ping.freq = freq;
-        DO_DEVDB(updateClassB,LMIC.ping.intvExp,freq,LMIC.ping.dr);
+        case MCMD_DCAP_REQ: {
+            u1_t cap = opts[oidx+1];
+            oidx += 2;
+            // A value cap=0xFF means device is OFF unless enabled again manually.
+            if( cap==0xFF )
+                LMIC.opmode |= OP_SHUTDOWN;  // stop any sending
+            LMIC.globalDutyRate  = cap & 0xF;
+            LMIC.globalDutyAvail = os_getTime();
+            DO_DEVDB(cap,dutyCap);
+            LMIC.dutyCapAns = 1;
+            continue;
+        }
+        case MCMD_SNCH_REQ: {
+            u1_t chidx = opts[oidx+1];  // channel
+            u4_t freq  = convFreq(&opts[oidx+2]); // freq
+            u1_t drs   = opts[oidx+5];  // datarate span
+            LMIC.snchAns = 0x80;
+            if( freq != 0 && LMIC_setupChannel(chidx, freq, DR_RANGE_MAP(drs&0xF,drs>>4), -1) )
+                LMIC.snchAns |= MCMD_SNCH_ANS_DRACK|MCMD_SNCH_ANS_FQACK;
+            oidx += 6;
+            continue;
         }
-        LMIC.pingSetAns = flags;
-        continue;
-    }
-    case MCMD_BCNI_ANS: {
-        // Ignore if tracking already enabled
-        if( (LMIC.opmode & OP_TRACK) == 0 ) {
-        LMIC.bcnChnl = opts[oidx+3];
-        // Enable tracking - bcninfoTries
-        LMIC.opmode |= OP_TRACK;
-        // Cleared later in txComplete handling - triggers EV_BEACON_FOUND
-        ASSERT(LMIC.bcninfoTries!=0);
-        // Setup RX parameters
-        LMIC.bcninfo.txtime =  (LMIC.rxtime
-                       - calcAirTime(LMIC.rps, LMIC.dataLen)
-                       + ms2osticks(os_rlsbf2(&opts[oidx+1]) * 10)
-                       + ms2osticksCeil(5)
-                       - BCN_INTV_osticks);
-        LMIC.bcninfo.flags = 0;  // txtime above cannot be used as reference (BCN_PARTIAL|BCN_FULL cleared)
-        calcBcnRxWindowFromMillis(10,1);  // error of +/-5 ms 
+        case MCMD_PING_SET: {
+            u4_t freq = convFreq(&opts[oidx+1]);
+            oidx += 4;
+            u1_t flags = 0x80;
+            if( freq != 0 ) {
+                flags |= MCMD_PING_ANS_FQACK;
+                LMIC.ping.freq = freq;
+                DO_DEVDB(LMIC.ping.intvExp, pingIntvExp);
+                DO_DEVDB(LMIC.ping.freq, pingFreq);
+                DO_DEVDB(LMIC.ping.dr, pingDr);
+            }
+            LMIC.pingSetAns = flags;
+            continue;
+        }
+        case MCMD_BCNI_ANS: {
+            // Ignore if tracking already enabled
+            if( (LMIC.opmode & OP_TRACK) == 0 ) {
+                LMIC.bcnChnl = opts[oidx+3];
+                // Enable tracking - bcninfoTries
+                LMIC.opmode |= OP_TRACK;
+                // Cleared later in txComplete handling - triggers EV_BEACON_FOUND
+                ASSERT(LMIC.bcninfoTries!=0);
+                // Setup RX parameters
+                LMIC.bcninfo.txtime = (LMIC.rxtime
+                                       + ms2osticks(os_rlsbf2(&opts[oidx+1]) * MCMD_BCNI_TUNIT)
+                                       + ms2osticksCeil(MCMD_BCNI_TUNIT/2)
+                                       - BCN_INTV_osticks);
+                LMIC.bcninfo.flags = 0;  // txtime above cannot be used as reference (BCN_PARTIAL|BCN_FULL cleared)
+                calcBcnRxWindowFromMillis(MCMD_BCNI_TUNIT,1);  // error of +/-N ms 
 
-        EV(lostFrame, INFO, (e_.reason  = EV::lostFrame_t::MCMD_BCNI_ANS,
-                     e_.eui     = MAIN::CDEV->getEui(),
-                     e_.lostmic = Base::lsbf4(&d[pend]),
-                     e_.info    = (LMIC.missedBcns |
-                           (osticks2us(LMIC.bcninfo.txtime + BCN_INTV_osticks
-                                   - LMIC.bcnRxtime) << 8)),
-                     e_.time    = MAIN::CDEV->ostime2ustime(LMIC.bcninfo.txtime + BCN_INTV_osticks)));
+                EV(lostFrame, INFO, (e_.reason  = EV::lostFrame_t::MCMD_BCNI_ANS,
+                                     e_.eui     = MAIN::CDEV->getEui(),
+                                     e_.lostmic = Base::lsbf4(&d[pend]),
+                                     e_.info    = (LMIC.missedBcns |
+                                                   (osticks2us(LMIC.bcninfo.txtime + BCN_INTV_osticks
+                                                               - LMIC.bcnRxtime) << 8)),
+                                     e_.time    = MAIN::CDEV->ostime2ustime(LMIC.bcninfo.txtime + BCN_INTV_osticks)));
+            }
+            oidx += 4;
+            continue;
         }
-        oidx += 4;
-        continue;
-    }
-    }
-    EV(specCond, ERR, (e_.reason = EV::specCond_t::BAD_MAC_CMD,
-               e_.eui    = MAIN::CDEV->getEui(),
-               e_.info   = Base::lsbf4(&d[pend]),
-               e_.info2  = Base::msbf4(&opts[oidx])));
-    break;
+        }
+        EV(specCond, ERR, (e_.reason = EV::specCond_t::BAD_MAC_CMD,
+                           e_.eui    = MAIN::CDEV->getEui(),
+                           e_.info   = Base::lsbf4(&d[pend]),
+                           e_.info2  = Base::msbf4(&opts[oidx])));
+        break;
     }
     if( oidx != olen ) {
-    EV(specCond, ERR, (e_.reason = EV::specCond_t::CORRUPTED_FRAME,
-               e_.eui    = MAIN::CDEV->getEui(),
-               e_.info   = 0x1000000 + (oidx) + (olen<<8)));
+        EV(specCond, ERR, (e_.reason = EV::specCond_t::CORRUPTED_FRAME,
+                           e_.eui    = MAIN::CDEV->getEui(),
+                           e_.info   = 0x1000000 + (oidx) + (olen<<8)));
     }
 
     if( !replayConf ) {
-    // Handle payload only if not a replay
-    // Decrypt payload - if any
-    if( port >= 0  &&  pend-poff > 0 )
-        aes_cipher(port <= 0 ? LMIC.nwkKey : LMIC.artKey, LMIC.devaddr, seqno, /*dn*/1, d+poff, pend-poff);
+        // Handle payload only if not a replay
+        // Decrypt payload - if any
+        if( port >= 0  &&  pend-poff > 0 )
+            aes_cipher(port <= 0 ? LMIC.nwkKey : LMIC.artKey, LMIC.devaddr, seqno, /*dn*/1, d+poff, pend-poff);
 
-    EV(dfinfo, DEBUG, (e_.deveui  = MAIN::CDEV->getEui(),
-               e_.devaddr = LMIC.devaddr,
-               e_.seqno   = seqno,
-               e_.flags   = (port < 0 ? EV::dfinfo_t::NOPORT : 0) | EV::dfinfo_t::DN,
-               e_.mic     = Base::lsbf4(&d[pend]),
-               e_.hdr     = d[LORA::OFF_DAT_HDR],
-               e_.fct     = d[LORA::OFF_DAT_FCT],
-               e_.port    = port,
-               e_.plen    = dlen,
-               e_.olen    = olen,
-               memcpy(e_.opts, opts, olen)));
+        EV(dfinfo, DEBUG, (e_.deveui  = MAIN::CDEV->getEui(),
+                           e_.devaddr = LMIC.devaddr,
+                           e_.seqno   = seqno,
+                           e_.flags   = (port < 0 ? EV::dfinfo_t::NOPORT : 0) | EV::dfinfo_t::DN,
+                           e_.mic     = Base::lsbf4(&d[pend]),
+                           e_.hdr     = d[LORA::OFF_DAT_HDR],
+                           e_.fct     = d[LORA::OFF_DAT_FCT],
+                           e_.port    = port,
+                           e_.plen    = dlen,
+                           e_.opts.length = olen,
+                           memcpy(&e_.opts[0], opts, olen)));
     } else {
-    EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_REPLAY,
-                e_.eui    = MAIN::CDEV->getEui(),
-                e_.info   = Base::lsbf4(&d[pend]),
-                e_.info2  = seqno));
+        EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_REPLAY,
+                            e_.eui    = MAIN::CDEV->getEui(),
+                            e_.info   = Base::lsbf4(&d[pend]),
+                            e_.info2  = seqno));
     }
 
     if( // NWK acks but we don't have a frame pending
-    (ackup && LMIC.txCnt == 0) ||
-    // We sent up confirmed and we got a response in DNW1/DNW2
-    // BUT it did not carry an ACK - this should never happen
-    // Do not resend and assume frame was not ACKed.
-    (!ackup && LMIC.txCnt != 0) ) {
-    EV(specCond, ERR, (e_.reason = EV::specCond_t::SPURIOUS_ACK,
-               e_.eui    = MAIN::CDEV->getEui(),
-               e_.info   = seqno,
-               e_.info2  = ackup));
+        (ackup && LMIC.txCnt == 0) ||
+        // We sent up confirmed and we got a response in DNW1/DNW2
+        // BUT it did not carry an ACK - this should never happen
+        // Do not resend and assume frame was not ACKed.
+        (!ackup && LMIC.txCnt != 0) ) {
+        EV(specCond, ERR, (e_.reason = EV::specCond_t::SPURIOUS_ACK,
+                           e_.eui    = MAIN::CDEV->getEui(),
+                           e_.info   = seqno,
+                           e_.info2  = ackup));
     }
 
     if( LMIC.txCnt != 0 ) // we requested an ACK
-    LMIC.txrxFlags |= ackup ? TXRX_ACK : TXRX_NACK;
+        LMIC.txrxFlags |= ackup ? TXRX_ACK : TXRX_NACK;
 
     if( port < 0 ) {
-    LMIC.txrxFlags |= TXRX_NOPORT;
-    LMIC.dataBeg = LMIC.dataLen = 0;
+        LMIC.txrxFlags |= TXRX_NOPORT;
+        LMIC.dataBeg = poff;
+        LMIC.dataLen = 0;
     } else {
-    LMIC.dataBeg = poff;
-    LMIC.dataLen = pend-poff;
+        LMIC.txrxFlags |= TXRX_PORT;
+        LMIC.dataBeg = poff;
+        LMIC.dataLen = pend-poff;
     }
     return 1;
 }
@@ -1263,27 +1282,25 @@
 
 // Called by HAL once TX complete and delivers exact end of TX time stamp in LMIC.rxtime
 static void txDone (ostime_t delay, osjobcb_t func) {
-    // NOTE: LMIC.rxsyms carries the modified DR for TX (LMIC.datarate is the base DR - [CONFIRM mode])
-    u1_t dr = LMIC.rxsyms;    // rxschedInit - would overwrite rxsyms
     if( (LMIC.opmode & (OP_TRACK|OP_PINGABLE|OP_PINGINI)) == (OP_TRACK|OP_PINGABLE) ) {
-    rxschedInit(&LMIC.ping);    // note: reuses LMIC.frame buffer!
-    LMIC.opmode |= OP_PINGINI;
+        rxschedInit(&LMIC.ping);    // note: reuses LMIC.frame buffer!
+        LMIC.opmode |= OP_PINGINI;
     }
     // Change RX frequency / rps (US only) before we increment txChnl
     setRx1Params();
     // LMIC.rxsyms carries the TX datarate (can be != LMIC.datarate [confirm retries etc.])
     // Setup receive - LMIC.rxtime is preloaded with 1.5 symbols offset to tune
     // into the middle of the 8 symbols preamble.
-#if CFG_eu868
+#if defined(CFG_eu868)
     if( /* TX datarate */LMIC.rxsyms == DR_FSK ) {
-    LMIC.rxtime = LMIC.txend + delay - PRERX_FSK*us2osticksRound(160);
-    LMIC.rxsyms = RXLEN_FSK;
+        LMIC.rxtime = LMIC.txend + delay - PRERX_FSK*us2osticksRound(160);
+        LMIC.rxsyms = RXLEN_FSK;
     }
     else
 #endif
     {
-    LMIC.rxtime = LMIC.txend + delay + (PAMBL_SYMS-MINRX_SYMS)*dr2hsym(dr);
-    LMIC.rxsyms = MINRX_SYMS;
+        LMIC.rxtime = LMIC.txend + delay + (PAMBL_SYMS-MINRX_SYMS)*dr2hsym(LMIC.dndr);
+        LMIC.rxsyms = MINRX_SYMS;
     }
     os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func);
 }
@@ -1305,86 +1322,90 @@
 
     if( LMIC.dataLen == 0 ) {
       nojoinframe:
-    if( (LMIC.opmode & OP_OTA) != 0 ) {
-    if( (LMIC.opmode & OP_JOINING) == 0 ) {
-        ASSERT((LMIC.opmode & OP_REJOIN) != 0);
-        // REJOIN attempt for roaming
-        LMIC.opmode &= ~(OP_REJOIN|OP_TXRXPEND);
-        if( LMIC.rejoinCnt < 10 )
-        LMIC.rejoinCnt++;
-        reportEvent(EV_REJOIN_FAILED);
+        if( (LMIC.opmode & OP_JOINING) == 0 ) {
+            ASSERT((LMIC.opmode & OP_REJOIN) != 0);
+            // REJOIN attempt for roaming
+            LMIC.opmode &= ~(OP_REJOIN|OP_TXRXPEND);
+            if( LMIC.rejoinCnt < 10 )
+                LMIC.rejoinCnt++;
+            reportEvent(EV_REJOIN_FAILED);
+            return 1;
+        }
+        LMIC.opmode &= ~OP_TXRXPEND;
+        ostime_t delay = nextJoinState();
+        EV(devCond, DEBUG, (e_.reason = EV::devCond_t::NO_JACC,
+                            e_.eui    = MAIN::CDEV->getEui(),
+                            e_.info   = LMIC.datarate|DR_PAGE,
+                            e_.info2  = osticks2ms(delay)));
+        // Build next JOIN REQUEST with next engineUpdate call
+        // Optionally, report join failed.
+        // Both after a random/chosen amount of ticks.
+        os_setTimedCallback(&LMIC.osjob, os_getTime()+delay,
+                            (delay&1) != 0
+                            ? FUNC_ADDR(onJoinFailed)      // one JOIN iteration done and failed
+                            : FUNC_ADDR(runEngineUpdate)); // next step to be delayed
         return 1;
     }
-    }
-    LMIC.opmode &= ~OP_TXRXPEND;
-    ostime_t delay = nextJoinState();
-    EV(devCond, DEBUG, (e_.reason = EV::devCond_t::NO_JACC,
-                e_.eui    = MAIN::CDEV->getEui(),
-                e_.info   = LMIC.datarate|DR_PAGE,
-                e_.info2  = osticks2ms(delay)));
-    // Build next JOIN REQUEST with next engineUpdate call
-    // Optionally, report join failed.
-    // Both after a random/chosen amount of ticks.
-    os_setTimedCallback(&LMIC.osjob, os_getTime()+delay,
-                (delay&1) != 0
-                ? FUNC_ADDR(onJoinFailed)      // one JOIN iteration done and failed
-                : FUNC_ADDR(runEngineUpdate)); // next step to be delayed
-    return 1;
-    }
     u1_t hdr  = LMIC.frame[0];
     u1_t dlen = LMIC.dataLen;
     u4_t mic  = os_rlsbf4(&LMIC.frame[dlen-4]); // safe before modified by encrypt!
     if( (dlen != LEN_JA && dlen != LEN_JAEXT)
-    || (hdr & (HDR_FTYPE|HDR_MAJOR)) != (HDR_FTYPE_JACC|HDR_MAJOR_V1) ) {
-    EV(specCond, ERR, (e_.reason = EV::specCond_t::UNEXPECTED_FRAME,
-               e_.eui    = MAIN::CDEV->getEui(),
-               e_.info   = dlen < 4 ? 0 : mic,
-               e_.info2  = hdr + (dlen<<8)));
+        || (hdr & (HDR_FTYPE|HDR_MAJOR)) != (HDR_FTYPE_JACC|HDR_MAJOR_V1) ) {
+        EV(specCond, ERR, (e_.reason = EV::specCond_t::UNEXPECTED_FRAME,
+                           e_.eui    = MAIN::CDEV->getEui(),
+                           e_.info   = dlen < 4 ? 0 : mic,
+                           e_.info2  = hdr + (dlen<<8)));
       badframe:
-    goto nojoinframe;
+        if( (LMIC.txrxFlags & TXRX_DNW1) != 0 )
+            return 0;
+        goto nojoinframe;
     }
     aes_encrypt(LMIC.frame+1, dlen-1);
     if( !aes_verifyMic0(LMIC.frame, dlen-4) ) {
-    EV(specCond, ERR, (e_.reason = EV::specCond_t::JOIN_BAD_MIC,
-               e_.info   = mic));
-    goto badframe;
+        EV(specCond, ERR, (e_.reason = EV::specCond_t::JOIN_BAD_MIC,
+                           e_.info   = mic));
+        goto badframe;
     }
 
     u4_t addr = os_rlsbf4(LMIC.frame+OFF_JA_DEVADDR);
     LMIC.devaddr = addr;
-    LMIC.netid = os_rlsbf4(&LMIC.frame[OFF_BCN_NETID-1]) >> 8;
+    LMIC.netid = os_rlsbf4(&LMIC.frame[OFF_JA_NETID]) & 0xFFFFFF;
 
-#if CFG_eu868
+#if defined(CFG_eu868)
     initDefaultChannels(0);
 #endif
     if( dlen > LEN_JA ) {
-    dlen = OFF_CFLIST;
-#if CFG_eu868
-    u1_t chidx=3;
-#elif CFG_us915
-    u1_t chidx=72;
+        dlen = OFF_CFLIST;
+#if defined(CFG_eu868)
+        u1_t chidx=3;
+#elif defined(CFG_us915)
+        u1_t chidx=72;
 #endif
-    for( ; chidx<8; chidx++, dlen+=3 )
-        setupChannel(chidx, os_rlsbf4(&LMIC.frame[dlen-1]) >> 8, -1);
+        for( ; chidx<8; chidx++, dlen+=3 )
+            LMIC_setupChannel(chidx, os_rlsbf4(&LMIC.frame[dlen-1]) >> 8, 0, -1);
     }
 
     // already incremented when JOIN REQ got sent off
     aes_sessKeys(LMIC.devNonce-1, &LMIC.frame[OFF_JA_ARTNONCE], LMIC.nwkKey, LMIC.artKey);
-    DO_DEVDB(updateJoinAcc, LMIC.devaddr, LMIC.nwkKey, LMIC.artKey);
+    DO_DEVDB(LMIC.netid,   netid);
+    DO_DEVDB(LMIC.devaddr, devaddr);
+    DO_DEVDB(LMIC.nwkKey,  nwkkey);
+    DO_DEVDB(LMIC.artKey,  artkey);
 
     EV(joininfo, INFO, (e_.arteui  = MAIN::CDEV->getArtEui(),
-            e_.deveui  = MAIN::CDEV->getEui(),
-            e_.devaddr = LMIC.devaddr,
-            e_.oldaddr = oldaddr,
-            e_.nonce   = LMIC.devNonce-1,
-            e_.mic     = mic,
-            e_.flags   = ((LMIC.opmode & OP_REJOIN) != 0
-                      ? EV::joininfo_t::REJOIN_ACCEPT
-                      : EV::joininfo_t::ACCEPT)));
+                        e_.deveui  = MAIN::CDEV->getEui(),
+                        e_.devaddr = LMIC.devaddr,
+                        e_.oldaddr = oldaddr,
+                        e_.nonce   = LMIC.devNonce-1,
+                        e_.mic     = mic,
+                        e_.reason  = ((LMIC.opmode & OP_REJOIN) != 0
+                                      ? EV::joininfo_t::REJOIN_ACCEPT
+                                      : EV::joininfo_t::ACCEPT)));
     
     ASSERT((LMIC.opmode & (OP_JOINING|OP_REJOIN))!=0);
     if( (LMIC.opmode & OP_REJOIN) != 0 ) {
-    LMIC.datarate = lowerDR(LMIC.datarate, LMIC.rejoinCnt);
+        // Lower DR every try below current UP DR
+        LMIC.datarate = lowerDR(LMIC.datarate, LMIC.rejoinCnt);
     }
     LMIC.opmode &= ~(OP_JOINING|OP_TRACK|OP_REJOIN|OP_TXRXPEND|OP_PINGINI) | OP_NEXTCHNL;
     stateJustJoined();
@@ -1392,12 +1413,26 @@
     return 1;
 }
 
-static void processRx1Jacc (xref2osjob_t osjob) {
+
+static void processRx2Jacc (xref2osjob_t osjob) {
     if( LMIC.dataLen == 0 )
-    LMIC.txrxFlags = 0;  // nothing in 1st/2nd DN slot
+        LMIC.txrxFlags = 0;  // nothing in 1st/2nd DN slot
     processJoinAccept();
 }
 
+
+static void setupRx2Jacc (xref2osjob_t osjob) {
+    LMIC.osjob.func = FUNC_ADDR(processRx2Jacc);
+    setupRx2();
+}
+
+
+static void processRx1Jacc (xref2osjob_t osjob) {
+    if( LMIC.dataLen == 0 || !processJoinAccept() )
+        schedRx2(DELAY_JACC2_osticks, FUNC_ADDR(setupRx2Jacc));
+}
+
+
 static void setupRx1Jacc (xref2osjob_t osjob) {
     setupRx1(FUNC_ADDR(processRx1Jacc));
 }
@@ -1412,9 +1447,20 @@
 // Fwd decl.
 static bit_t processDnData(void);
 
+static void processRx2DnDataDelay (xref2osjob_t osjob) {
+    processDnData();
+}
+
 static void processRx2DnData (xref2osjob_t osjob) {
-    if( LMIC.dataLen == 0 )
-    LMIC.txrxFlags = 0;  // nothing in 1st/2nd DN slot
+    if( LMIC.dataLen == 0 ) {
+        LMIC.txrxFlags = 0;  // nothing in 1st/2nd DN slot
+        // Delay callback processing to avoid up TX while gateway is txing our missed frame! 
+        // Since DNW2 uses SF12 by default we wait 3 secs.
+        os_setTimedCallback(&LMIC.osjob,
+                            (os_getTime() + DNW2_SAFETY_ZONE + rndDelay(2)),
+                            processRx2DnDataDelay);
+        return;
+    }
     processDnData();
 }
 
@@ -1427,7 +1473,7 @@
 
 static void processRx1DnData (xref2osjob_t osjob) {
     if( LMIC.dataLen == 0 || !processDnData() )
-    schedRx2(DELAY_DNW2_osticks, FUNC_ADDR(setupRx2DnData));
+        schedRx2(DELAY_DNW2_osticks, FUNC_ADDR(setupRx2DnData));
 }
 
 
@@ -1451,77 +1497,80 @@
     // Prioritize by importance
     int  end = OFF_DAT_OPTS;
     if( (LMIC.opmode & (OP_TRACK|OP_PINGABLE)) == (OP_TRACK|OP_PINGABLE) ) {
-    // Indicate pingability in every UP frame
-    LMIC.frame[end] = MCMD_PING_IND;
-    LMIC.frame[end+1] = LMIC.ping.dr | (LMIC.ping.intvExp<<4);
-    end += 2;
+        // Indicate pingability in every UP frame
+        LMIC.frame[end] = MCMD_PING_IND;
+        LMIC.frame[end+1] = LMIC.ping.dr | (LMIC.ping.intvExp<<4);
+        end += 2;
     }
     if( LMIC.dutyCapAns ) {
-    LMIC.frame[end] = MCMD_DCAP_ANS;
-    end += 1;
-    LMIC.dutyCapAns = 0;
+        LMIC.frame[end] = MCMD_DCAP_ANS;
+        end += 1;
+        LMIC.dutyCapAns = 0;
     }
     if( LMIC.dn2Ans ) {
-    LMIC.frame[end+0] = MCMD_DN2P_ANS;
-    LMIC.frame[end+1] = LMIC.dn2Ans & ~MCMD_DN2P_ANS_RFU;
-    end += 2;
-    LMIC.dn2Ans = 0;
+        LMIC.frame[end+0] = MCMD_DN2P_ANS;
+        LMIC.frame[end+1] = LMIC.dn2Ans & ~MCMD_DN2P_ANS_RFU;
+        end += 2;
+        LMIC.dn2Ans = 0;
     }
     if( LMIC.devsAns ) {  // answer to device status
-    LMIC.frame[end+0] = MCMD_DEVS_ANS;
-    LMIC.frame[end+1] = LMIC.margin;
-    LMIC.frame[end+2] = os_getBattLevel();
-    end += 3;
-    LMIC.devsAns = 0;
+        LMIC.frame[end+0] = MCMD_DEVS_ANS;
+        LMIC.frame[end+1] = LMIC.margin;
+        LMIC.frame[end+2] = os_getBattLevel();
+        end += 3;
+        LMIC.devsAns = 0;
     }
     if( LMIC.ladrAns ) {  // answer to ADR change
-    LMIC.frame[end+0] = MCMD_LADR_ANS;
-    LMIC.frame[end+1] = LMIC.ladrAns & ~MCMD_LADR_ANS_RFU;
-    end += 2;
-    LMIC.ladrAns = 0;
+        LMIC.frame[end+0] = MCMD_LADR_ANS;
+        LMIC.frame[end+1] = LMIC.ladrAns & ~MCMD_LADR_ANS_RFU;
+        end += 2;
+        LMIC.ladrAns = 0;
     }
     if( LMIC.bcninfoTries > 0 ) {
-    LMIC.frame[end] = MCMD_BCNI_REQ;
-    end += 1;
+        LMIC.frame[end] = MCMD_BCNI_REQ;
+        end += 1;
     }
     if( LMIC.adrChanged ) {
-    LMIC.adrAckReq = LMIC.adrAckReq < 0 ? 0 : LMIC.adrAckReq;
-    LMIC.adrChanged = 0;
+        if( LMIC.adrAckReq < 0 )
+            LMIC.adrAckReq = 0;
+        LMIC.adrChanged = 0;
     }
     if( LMIC.pingSetAns != 0 ) {
-    LMIC.frame[end+0] = MCMD_PING_ANS;
-    LMIC.frame[end+1] = LMIC.pingSetAns & ~MCMD_PING_ANS_RFU;
-    end += 2;
-    LMIC.pingSetAns = 0;
+        LMIC.frame[end+0] = MCMD_PING_ANS;
+        LMIC.frame[end+1] = LMIC.pingSetAns & ~MCMD_PING_ANS_RFU;
+        end += 2;
+        LMIC.pingSetAns = 0;
     }
     if( LMIC.snchAns ) {
-    LMIC.frame[end+0] = MCMD_SNCH_ANS;
-    LMIC.frame[end+1] = LMIC.snchAns;
-    end += 2;
-    LMIC.snchAns = 0;
+        LMIC.frame[end+0] = MCMD_SNCH_ANS;
+        LMIC.frame[end+1] = LMIC.snchAns & ~MCMD_SNCH_ANS_RFU;
+        end += 2;
+        LMIC.snchAns = 0;
     }
     ASSERT(end <= OFF_DAT_OPTS+16);
 
     u1_t flen = end + (txdata ? 5+dlen : 4);
     if( flen > MAX_LEN_FRAME ) {
-    // Options and payload too big - delay payload
-    txdata = 0;
-    flen = end+4;
+        // Options and payload too big - delay payload
+        txdata = 0;
+        flen = end+4;
     }
     LMIC.frame[OFF_DAT_HDR] = HDR_FTYPE_DAUP | HDR_MAJOR_V1;
     LMIC.frame[OFF_DAT_FCT] = (LMIC.dnConf | LMIC.adrEnabled
-                  | (LMIC.adrAckReq >= 0 ? FCT_ADRARQ : 0)
-                  | (end-OFF_DAT_OPTS));
+                              | (LMIC.adrAckReq >= 0 ? FCT_ADRARQ : 0)
+                              | (end-OFF_DAT_OPTS));
     os_wlsbf4(LMIC.frame+OFF_DAT_ADDR,  LMIC.devaddr);
 
     if( LMIC.txCnt == 0 ) {
-    LMIC.seqnoUp += 1;
-    DO_DEVDB(updateSeqnoUp, LMIC.seqnoUp);
+        LMIC.seqnoUp += 1;
+        DO_DEVDB(LMIC.seqnoUp,seqnoUp);
     } else {
-    EV(devCond, INFO, (e_.reason = EV::devCond_t::RE_TX,
-               e_.eui    = MAIN::CDEV->getEui(),
-               e_.info   = LMIC.seqnoUp-1,
-               e_.info2  = (LMIC.txCnt+1) | (DRADJUST[LMIC.txCnt+1] << 8) | ((LMIC.datarate|DR_PAGE)<<16)));
+        EV(devCond, INFO, (e_.reason = EV::devCond_t::RE_TX,
+                           e_.eui    = MAIN::CDEV->getEui(),
+                           e_.info   = LMIC.seqnoUp-1,
+                           e_.info2  = ((LMIC.txCnt+1) |
+                                        (DRADJUST[LMIC.txCnt+1] << 8) |
+                                        ((LMIC.datarate|DR_PAGE)<<16))));
     }
     os_wlsbf2(LMIC.frame+OFF_DAT_SEQNO, LMIC.seqnoUp-1);
 
@@ -1529,30 +1578,30 @@
     LMIC.dnConf = 0;
 
     if( txdata ) {
-    if( LMIC.pendTxConf ) {
-        // Confirmed only makes sense if we have a payload (or at least a port)
-        LMIC.frame[OFF_DAT_HDR] = HDR_FTYPE_DCUP | HDR_MAJOR_V1;
-        LMIC.txCnt += 1;
-    }
-    LMIC.frame[end] = LMIC.pendTxPort;
-    os_copyMem(LMIC.frame+end+1, LMIC.pendTxData, dlen);
-    aes_cipher(LMIC.pendTxPort==0 ? LMIC.nwkKey : LMIC.artKey,
-           LMIC.devaddr, LMIC.seqnoUp-1,
-           /*up*/0, LMIC.frame+end+1, dlen);
+        if( LMIC.pendTxConf ) {
+            // Confirmed only makes sense if we have a payload (or at least a port)
+            LMIC.frame[OFF_DAT_HDR] = HDR_FTYPE_DCUP | HDR_MAJOR_V1;
+            if( LMIC.txCnt == 0 ) LMIC.txCnt = 1;
+        }
+        LMIC.frame[end] = LMIC.pendTxPort;
+        os_copyMem(LMIC.frame+end+1, LMIC.pendTxData, dlen);
+        aes_cipher(LMIC.pendTxPort==0 ? LMIC.nwkKey : LMIC.artKey,
+                   LMIC.devaddr, LMIC.seqnoUp-1,
+                   /*up*/0, LMIC.frame+end+1, dlen);
     }
     aes_appendMic(LMIC.nwkKey, LMIC.devaddr, LMIC.seqnoUp-1, /*up*/0, LMIC.frame, flen-4);
 
     EV(dfinfo, DEBUG, (e_.deveui  = MAIN::CDEV->getEui(),
-               e_.devaddr = LMIC.devaddr,
-               e_.seqno   = LMIC.seqnoUp-1,
-               e_.flags   = (LMIC.pendTxPort < 0 ? EV::dfinfo_t::NOPORT : EV::dfinfo_t::NOP),
-               e_.mic     = Base::lsbf4(&LMIC.frame[flen-4]),
-               e_.hdr     = LMIC.frame[LORA::OFF_DAT_HDR],
-               e_.fct     = LMIC.frame[LORA::OFF_DAT_FCT],
-               e_.port    = LMIC.pendTxPort,
-               e_.plen    = txdata ? dlen : 0,
-               e_.olen    = end-LORA::OFF_DAT_OPTS,
-               memcpy(e_.opts, LMIC.frame+LORA::OFF_DAT_OPTS, end-LORA::OFF_DAT_OPTS)));
+                       e_.devaddr = LMIC.devaddr,
+                       e_.seqno   = LMIC.seqnoUp-1,
+                       e_.flags   = (LMIC.pendTxPort < 0 ? EV::dfinfo_t::NOPORT : EV::dfinfo_t::NOP),
+                       e_.mic     = Base::lsbf4(&LMIC.frame[flen-4]),
+                       e_.hdr     = LMIC.frame[LORA::OFF_DAT_HDR],
+                       e_.fct     = LMIC.frame[LORA::OFF_DAT_FCT],
+                       e_.port    = LMIC.pendTxPort,
+                       e_.plen    = txdata ? dlen : 0,
+                       e_.opts.length = end-LORA::OFF_DAT_OPTS,
+                       memcpy(&e_.opts[0], LMIC.frame+LORA::OFF_DAT_OPTS, end-LORA::OFF_DAT_OPTS)));
     LMIC.dataLen = flen;
 }
 
@@ -1563,17 +1612,17 @@
     os_radio(RADIO_RST);
     os_clearCallback(&LMIC.osjob);
     if( LMIC.dataLen == 0 ) {
-    // Nothing received - timeout
-    LMIC.opmode &= ~(OP_SCAN | OP_TRACK);
-    reportEvent(EV_SCAN_TIMEOUT);
-    return;
+        // Nothing received - timeout
+        LMIC.opmode &= ~(OP_SCAN | OP_TRACK);
+        reportEvent(EV_SCAN_TIMEOUT);
+        return;
     }
     if( decodeBeacon() <= 0 ) {
-    // Something is wrong with the beacon - continue scan
-    LMIC.dataLen = 0;
-    os_radio(RADIO_RXON);
-    os_setTimedCallback(&LMIC.osjob, LMIC.bcninfo.txtime, FUNC_ADDR(onBcnRx));
-    return;
+        // Something is wrong with the beacon - continue scan
+        LMIC.dataLen = 0;
+        os_radio(RADIO_RXON);
+        os_setTimedCallback(&LMIC.osjob, LMIC.bcninfo.txtime, FUNC_ADDR(onBcnRx));
+        return;
     }
     // Found our 1st beacon
     // We don't have a previous beacon to calc some drift - assume
@@ -1593,7 +1642,7 @@
 static void startScan (void) {
     ASSERT(LMIC.devaddr!=0 && (LMIC.opmode & OP_JOINING)==0);
     if( (LMIC.opmode & OP_SHUTDOWN) != 0 )
-    return;
+        return;
     // Cancel onging TX/RX transaction
     LMIC.txCnt = LMIC.dnConf = LMIC.bcninfo.flags = 0;
     LMIC.opmode = (LMIC.opmode | OP_SCAN) & ~(OP_TXRXPEND);
@@ -1606,11 +1655,11 @@
 
 bit_t LMIC_enableTracking (u1_t tryBcnInfo) {
     if( (LMIC.opmode & (OP_SCAN|OP_TRACK|OP_SHUTDOWN)) != 0 )
-    return 0;  // already in progress or failed to enable
+        return 0;  // already in progress or failed to enable
     // If BCN info requested from NWK then app has to take are
     // of sending data up so that MCMD_BCNI_REQ can be attached.
     if( (LMIC.bcninfoTries = tryBcnInfo) == 0 )
-    startScan();
+        startScan();
     return 1;  // enabled
 }
 
@@ -1639,16 +1688,16 @@
     aes_appendMic0(d, OFF_JR_MIC);
 
     EV(joininfo,INFO,(e_.deveui  = MAIN::CDEV->getEui(),
-              e_.arteui  = MAIN::CDEV->getArtEui(),
-              e_.nonce   = LMIC.devNonce,
-              e_.oldaddr = LMIC.devaddr,
-              e_.mic     = Base::lsbf4(&d[LORA::OFF_JR_MIC]),
-              e_.flags   = ((LMIC.opmode & OP_REJOIN) != 0
-                    ? EV::joininfo_t::REJOIN_REQUEST
-                    : EV::joininfo_t::REQUEST)));
+                      e_.arteui  = MAIN::CDEV->getArtEui(),
+                      e_.nonce   = LMIC.devNonce,
+                      e_.oldaddr = LMIC.devaddr,
+                      e_.mic     = Base::lsbf4(&d[LORA::OFF_JR_MIC]),
+                      e_.reason  = ((LMIC.opmode & OP_REJOIN) != 0
+                                    ? EV::joininfo_t::REJOIN_REQUEST
+                                    : EV::joininfo_t::REQUEST)));
     LMIC.dataLen = LEN_JR;
     LMIC.devNonce++;
-    DO_DEVDB(updateDevNonce, LMIC.devNonce);
+    DO_DEVDB(LMIC.devNonce,devNonce);
 }
 
 static void startJoining (xref2osjob_t osjob) {
@@ -1658,19 +1707,21 @@
 // Start join procedure if not already joined.
 bit_t LMIC_startJoining (void) {
     if( LMIC.devaddr == 0 ) {
-    // There should be no TX/RX going on
-    ASSERT((LMIC.opmode & (OP_POLL|OP_TXRXPEND)) == 0);
-    // Cancel scanning
-    LMIC.opmode &= ~(OP_SCAN|OP_REJOIN|OP_LINKDEAD|OP_NEXTCHNL);
-    // Setup state
-    LMIC.rejoinCnt = LMIC.txCnt = 0;
-    initJoinLoop();
-    LMIC.opmode |= OP_OTA|OP_JOINING;
-    // reportEvent will call engineUpdate which then starts sending JOIN REQUESTS
-    os_setCallback(&LMIC.osjob, FUNC_ADDR(startJoining));
-    return 1;
+        // There should be no TX/RX going on
+        ASSERT((LMIC.opmode & (OP_POLL|OP_TXRXPEND)) == 0);
+        // Lift any previous duty limitation
+        LMIC.globalDutyRate = 0;
+        // Cancel scanning
+        LMIC.opmode &= ~(OP_SCAN|OP_REJOIN|OP_LINKDEAD|OP_NEXTCHNL);
+        // Setup state
+        LMIC.rejoinCnt = LMIC.txCnt = LMIC.pendTxConf = 0;
+        initJoinLoop();
+        LMIC.opmode |= OP_JOINING;
+        // reportEvent will call engineUpdate which then starts sending JOIN REQUESTS
+        os_setCallback(&LMIC.osjob, FUNC_ADDR(startJoining));
+        return 1;
     }
-    return 0;
+    return 0; // already joined
 }
 
 
@@ -1682,11 +1733,11 @@
 
 static void processPingRx (xref2osjob_t osjob) {
     if( LMIC.dataLen != 0 ) {
-    LMIC.txrxFlags = TXRX_PING;
-    if( decodeFrame() ) {
-        reportEvent(EV_RXCOMPLETE);
-        return;
-    }
+        LMIC.txrxFlags = TXRX_PING;
+        if( decodeFrame() ) {
+            reportEvent(EV_RXCOMPLETE);
+            return;
+        }
     }
     // Pick next ping slot
     engineUpdate();
@@ -1698,66 +1749,61 @@
 
     if( LMIC.dataLen == 0 ) {
       norx:
-    if( LMIC.txCnt != 0 ) {
-        if( LMIC.txCnt <= TXCONF_ATTEMPTS ) {
-        // Schedule another retransmission
-        txDelay(LMIC.rxtime, RETRY_PERIOD_secs);
-        LMIC.opmode &= ~OP_TXRXPEND;
-        engineUpdate();
-        return 1;
+        if( LMIC.txCnt != 0 ) {
+            if( LMIC.txCnt < TXCONF_ATTEMPTS ) {
+                LMIC.txCnt += 1;
+                setDrTxpow(DRCHG_NOACK, lowerDR(LMIC.datarate, DRADJUST[LMIC.txCnt]), KEEP_TXPOW);
+                // Schedule another retransmission
+                txDelay(LMIC.rxtime, RETRY_PERIOD_secs);
+                LMIC.opmode &= ~OP_TXRXPEND;
+                engineUpdate();
+                return 1;
+            }
+            LMIC.txrxFlags = TXRX_NACK | TXRX_NOPORT;
+        } else {
+            // Nothing received - implies no port
+            LMIC.txrxFlags = TXRX_NOPORT;
         }
-        LMIC.txrxFlags = TXRX_NACK | TXRX_NOPORT;
-    } else {
-        // Nothing received - implies no port
-        LMIC.txrxFlags = TXRX_NOPORT;
-    }
-    LMIC.adrAckReq += 1;
-    LMIC.dataBeg = LMIC.dataLen = 0;
+        if( LMIC.adrAckReq != LINK_CHECK_OFF )
+            LMIC.adrAckReq += 1;
+        LMIC.dataBeg = LMIC.dataLen = 0;
       txcomplete:
-    LMIC.opmode &= ~(OP_TXDATA|OP_TXRXPEND);
-    if( (LMIC.txrxFlags & (TXRX_DNW1|TXRX_DNW2|TXRX_PING)) != 0  &&  (LMIC.opmode & OP_LINKDEAD) != 0 ) {
-        LMIC.opmode &= ~OP_LINKDEAD;
-        reportEvent(EV_LINK_ALIVE);
-    }
-    reportEvent(EV_TXCOMPLETE);
-    // If we haven't heard from NWK in a while although we asked for a sign
-    // assume link is dead - notify application and keep going
-    if( LMIC.adrAckReq > LINK_CHECK_DEAD ) {
-        // We haven't heard from NWK for some time although
-        // We asked for a response for some time - assume we're disconnected. Lower DR one notch.
-        EV(devCond, ERR, (e_.reason = EV::devCond_t::LINK_DEAD,
-                  e_.eui    = MAIN::CDEV->getEui(),
-                  e_.info   = LMIC.adrAckReq));
-        setDrTxpow(DRCHG_NOADRACK, decDR((dr_t)LMIC.datarate), KEEP_TXPOW);
-        LMIC.adrAckReq = LINK_CHECK_CONT;
-        if( (LMIC.opmode & OP_OTA) != 0 ) {
+        LMIC.opmode &= ~(OP_TXDATA|OP_TXRXPEND);
+        if( (LMIC.txrxFlags & (TXRX_DNW1|TXRX_DNW2|TXRX_PING)) != 0  &&  (LMIC.opmode & OP_LINKDEAD) != 0 ) {
+            LMIC.opmode &= ~OP_LINKDEAD;
+            reportEvent(EV_LINK_ALIVE);
+        }
+        reportEvent(EV_TXCOMPLETE);
+        // If we haven't heard from NWK in a while although we asked for a sign
+        // assume link is dead - notify application and keep going
+        if( LMIC.adrAckReq > LINK_CHECK_DEAD ) {
+            // We haven't heard from NWK for some time although we
+            // asked for a response for some time - assume we're disconnected. Lower DR one notch.
+            EV(devCond, ERR, (e_.reason = EV::devCond_t::LINK_DEAD,
+                              e_.eui    = MAIN::CDEV->getEui(),
+                              e_.info   = LMIC.adrAckReq));
+            setDrTxpow(DRCHG_NOADRACK, decDR((dr_t)LMIC.datarate), KEEP_TXPOW);
+            LMIC.adrAckReq = LINK_CHECK_CONT;
             LMIC.opmode |= OP_REJOIN|OP_LINKDEAD;
-        }
-        else {
-            LMIC.opmode |= OP_LINKDEAD;
+            reportEvent(EV_LINK_DEAD);
         }
-        reportEvent(EV_LINK_DEAD);
-    }
-    // If this falls to zero the NWK did not answer our MCMD_BCNI_REQ commands - try full scan
-    if( LMIC.bcninfoTries > 0 ) {
-        if( (LMIC.opmode & OP_TRACK) != 0 ) {
-        reportEvent(EV_BEACON_FOUND);
-        LMIC.bcninfoTries = 0;
+        // If this falls to zero the NWK did not answer our MCMD_BCNI_REQ commands - try full scan
+        if( LMIC.bcninfoTries > 0 ) {
+            if( (LMIC.opmode & OP_TRACK) != 0 ) {
+                reportEvent(EV_BEACON_FOUND);
+                LMIC.bcninfoTries = 0;
+            }
+            else if( --LMIC.bcninfoTries == 0 ) {
+                startScan();   // NWK did not answer - try scan
+            }
         }
-        else if( --LMIC.bcninfoTries == 0 ) {
-        startScan();   // NWK did not answer - try scan
-        }
-    }
-    return 1;
+        return 1;
     }
     if( !decodeFrame() ) {
-    if( (LMIC.txrxFlags & TXRX_DNW1) != 0 )
-        return 0;
-    goto norx;
+        if( (LMIC.txrxFlags & TXRX_DNW1) != 0 )
+            return 0;
+        goto norx;
     }
-    // If we received a frame reset counter
-    LMIC.adrAckReq = LINK_CHECK_INIT;
-    LMIC.rejoinCnt = 0;
     goto txcomplete;
 }
 
@@ -1768,55 +1814,57 @@
     ev_t ev;
 
     if( LMIC.dataLen != 0 && decodeBeacon() >= 1 ) {
-    ev = EV_BEACON_TRACKED;
-    if( (flags & (BCN_PARTIAL|BCN_FULL)) == 0 ) {
-        // We don't have a previous beacon to calc some drift - assume
-        // an max error of 13ms = 128sec*100ppm which is roughly +/-100ppm
-        calcBcnRxWindowFromMillis(13,0);
-        goto rev;
-    }
-    // We have a previous BEACON to calculate some drift
-    s2_t drift = BCN_INTV_osticks - (LMIC.bcninfo.txtime - lasttx);
-    if( LMIC.missedBcns > 0 ) {
-        drift = LMIC.drift + (drift - LMIC.drift) / (LMIC.missedBcns+1);
-    }
-    if( (LMIC.bcninfo.flags & BCN_NODRIFT) == 0 ) {
-        s2_t diff = LMIC.drift - drift;
-        if( diff < 0 ) diff = -diff;
-        LMIC.lastDriftDiff = diff;
-        if( LMIC.maxDriftDiff < diff )
-        LMIC.maxDriftDiff = diff;
-        LMIC.bcninfo.flags &= ~BCN_NODDIFF;
-    }
-    LMIC.drift = drift;
-    LMIC.missedBcns = LMIC.rejoinCnt = 0;
-    LMIC.bcninfo.flags &= ~BCN_NODRIFT;
-    EV(devCond,INFO,(e_.reason = EV::devCond_t::CLOCK_DRIFT,
-             e_.info   = drift,
-             e_.info2  = /*occasion BEACON*/0));
-    ASSERT((LMIC.bcninfo.flags & (BCN_PARTIAL|BCN_FULL)) != 0);
+        ev = EV_BEACON_TRACKED;
+        if( (flags & (BCN_PARTIAL|BCN_FULL)) == 0 ) {
+            // We don't have a previous beacon to calc some drift - assume
+            // an max error of 13ms = 128sec*100ppm which is roughly +/-100ppm
+            calcBcnRxWindowFromMillis(13,0);
+            goto rev;
+        }
+        // We have a previous BEACON to calculate some drift
+        s2_t drift = BCN_INTV_osticks - (LMIC.bcninfo.txtime - lasttx);
+        if( LMIC.missedBcns > 0 ) {
+            drift = LMIC.drift + (drift - LMIC.drift) / (LMIC.missedBcns+1);
+        }
+        if( (LMIC.bcninfo.flags & BCN_NODRIFT) == 0 ) {
+            s2_t diff = LMIC.drift - drift;
+            if( diff < 0 ) diff = -diff;
+            LMIC.lastDriftDiff = diff;
+            if( LMIC.maxDriftDiff < diff )
+                LMIC.maxDriftDiff = diff;
+            LMIC.bcninfo.flags &= ~BCN_NODDIFF;
+        }
+        LMIC.drift = drift;
+        LMIC.missedBcns = LMIC.rejoinCnt = 0;
+        LMIC.bcninfo.flags &= ~BCN_NODRIFT;
+        EV(devCond,INFO,(e_.reason = EV::devCond_t::CLOCK_DRIFT,
+                         e_.eui    = MAIN::CDEV->getEui(),
+                         e_.info   = drift,
+                         e_.info2  = /*occasion BEACON*/0));
+        ASSERT((LMIC.bcninfo.flags & (BCN_PARTIAL|BCN_FULL)) != 0);
     } else {
-    ev = EV_BEACON_MISSED;
-    LMIC.bcninfo.txtime += BCN_INTV_osticks - LMIC.drift;
-    LMIC.bcninfo.time   += BCN_INTV_sec;
-    LMIC.missedBcns++;
-    // Delay any possible TX after surmised beacon - it's there although we missed it
-    txDelay(LMIC.bcninfo.txtime + BCN_RESERVE_osticks, 4);
-    if( (LMIC.opmode & OP_OTA) != 0 ) {
-    if( LMIC.missedBcns > MAX_MISSED_BCNS )
-        LMIC.opmode |= OP_REJOIN;  // try if we can roam to another network
-    }
-    if( LMIC.bcnRxsyms > MAX_RXSYMS ) {
-        LMIC.opmode &= ~(OP_TRACK|OP_PINGABLE|OP_PINGINI|OP_REJOIN);
-        reportEvent(EV_LOST_TSYNC);
-        return;
-    }
+        ev = EV_BEACON_MISSED;
+        LMIC.bcninfo.txtime += BCN_INTV_osticks - LMIC.drift;
+        LMIC.bcninfo.time   += BCN_INTV_sec;
+        LMIC.missedBcns++;
+        // Delay any possible TX after surmised beacon - it's there although we missed it
+        txDelay(LMIC.bcninfo.txtime + BCN_RESERVE_osticks, 4);
+        if( LMIC.missedBcns > MAX_MISSED_BCNS )
+            LMIC.opmode |= OP_REJOIN;  // try if we can roam to another network
+        if( LMIC.bcnRxsyms > MAX_RXSYMS ) {
+            LMIC.opmode &= ~(OP_TRACK|OP_PINGABLE|OP_PINGINI|OP_REJOIN);
+            reportEvent(EV_LOST_TSYNC);
+            return;
+        }
     }
     LMIC.bcnRxtime = LMIC.bcninfo.txtime + BCN_INTV_osticks - calcRxWindow(0,DR_BCN);
     LMIC.bcnRxsyms = LMIC.rxsyms;    
   rev:
+#if CFG_us915
+    LMIC.bcnChnl = (LMIC.bcnChnl+1) & 7;
+#endif
     if( (LMIC.opmode & OP_PINGINI) != 0 )
-    rxschedInit(&LMIC.ping);  // note: reuses LMIC.frame buffer!
+        rxschedInit(&LMIC.ping);  // note: reuses LMIC.frame buffer!
     reportEvent(ev);
 }
 
@@ -1837,11 +1885,11 @@
 static void engineUpdate (void) {
     // Check for ongoing state: scan or TX/RX transaction
     if( (LMIC.opmode & (OP_SCAN|OP_TXRXPEND|OP_SHUTDOWN)) != 0 ) 
-    return;
+        return;
 
     if( LMIC.devaddr == 0 && (LMIC.opmode & OP_JOINING) == 0 ) {
-    LMIC_startJoining();
-    return;
+        LMIC_startJoining();
+        return;
     }
 
     ostime_t now    = os_getTime();
@@ -1849,123 +1897,131 @@
     ostime_t txbeg  = 0;
 
     if( (LMIC.opmode & OP_TRACK) != 0 ) {
-    // We are tracking a beacon
-    ASSERT( now + RX_RAMPUP - LMIC.bcnRxtime <= 0 );
-    rxtime = LMIC.bcnRxtime - RX_RAMPUP;
+        // We are tracking a beacon
+        ASSERT( now + RX_RAMPUP - LMIC.bcnRxtime <= 0 );
+        rxtime = LMIC.bcnRxtime - RX_RAMPUP;
     }
 
     if( (LMIC.opmode & (OP_JOINING|OP_REJOIN|OP_TXDATA|OP_POLL)) != 0 ) {
-    // Need to TX some data...
-    // Assuming txChnl points to channel which first becomes available again.
-    bit_t jacc = ((LMIC.opmode & (OP_JOINING|OP_REJOIN)) != 0 ? 1 : 0);
-    // Find next suitable channel and return availability time
-    if( (LMIC.opmode & OP_NEXTCHNL) != 0 ) {
-        txbeg = LMIC.txend = nextTx(now);
-        LMIC.opmode &= ~OP_NEXTCHNL;
-    } else {
-        txbeg = LMIC.txend;
-    }
-    // Delayed TX or waiting for duty cycle?
-    if( (LMIC.globalDutyRate != 0 || (LMIC.opmode & OP_RNDTX) != 0)  &&  (txbeg - LMIC.globalDutyAvail) < 0 )
-        txbeg = LMIC.globalDutyAvail;
-    // If we're tracking a beacon...
-    // then make sure TX-RX transaction is complete before beacon
-    if( (LMIC.opmode & OP_TRACK) != 0 &&
-        txbeg + (jacc ? JOIN_GUARD_osticks : TXRX_GUARD_osticks) - rxtime > 0 ) {
-        // Not enough time to complete TX-RX before beacon - postpone after beacon.
-        // In order to avoid clustering of postponed TX right after beacon randomize start!
-        txDelay(rxtime + BCN_RESERVE_osticks, 16);
-        txbeg = 0;
-        goto checkrx;
-    }
-    // Earliest possible time vs overhead to setup radio
-    if( txbeg - (now + TX_RAMPUP) < 0 ) {
-        // We could send right now!
-        dr_t txdr = (dr_t)LMIC.datarate;
-        if( jacc ) {
-        u1_t ftype;
-        if( (LMIC.opmode & OP_REJOIN) != 0 ) {
-            txdr = lowerDR(txdr, LMIC.rejoinCnt);
-            ftype = HDR_FTYPE_REJOIN;
+        // Need to TX some data...
+        // Assuming txChnl points to channel which first becomes available again.
+        bit_t jacc = ((LMIC.opmode & (OP_JOINING|OP_REJOIN)) != 0 ? 1 : 0);
+        // Find next suitable channel and return availability time
+        if( (LMIC.opmode & OP_NEXTCHNL) != 0 ) {
+            txbeg = LMIC.txend = nextTx(now);
+            LMIC.opmode &= ~OP_NEXTCHNL;
         } else {
-            ftype = HDR_FTYPE_JREQ;
+            txbeg = LMIC.txend;
+        }
+        // Delayed TX or waiting for duty cycle?
+        if( (LMIC.globalDutyRate != 0 || (LMIC.opmode & OP_RNDTX) != 0)  &&  (txbeg - LMIC.globalDutyAvail) < 0 )
+            txbeg = LMIC.globalDutyAvail;
+        // If we're tracking a beacon...
+        // then make sure TX-RX transaction is complete before beacon
+        if( (LMIC.opmode & OP_TRACK) != 0 &&
+            txbeg + (jacc ? JOIN_GUARD_osticks : TXRX_GUARD_osticks) - rxtime > 0 ) {
+            // Not enough time to complete TX-RX before beacon - postpone after beacon.
+            // In order to avoid clustering of postponed TX right after beacon randomize start!
+            txDelay(rxtime + BCN_RESERVE_osticks, 16);
+            txbeg = 0;
+            goto checkrx;
         }
-        buildJoinRequest(ftype);
-        LMIC.osjob.func = FUNC_ADDR(jreqDone);
-        } else {
-        if( LMIC.seqnoUp == 0xFFFFFFFF ) {
-            // Roll over of up seq counter
-            EV(specCond, ERR, (e_.reason = EV::specCond_t::UPSEQNO_ROLL_OVER,
-                       e_.eui    = MAIN::CDEV->getEui()));
-            // Rerun join procedure - start with current datarate
-            LMIC.devaddr = 0;
-            LMIC_startJoining();
-            reportEvent(EV_RESET);
+        // Earliest possible time vs overhead to setup radio
+        if( txbeg - (now + TX_RAMPUP) < 0 ) {
+            // We could send right now!
+            dr_t txdr = (dr_t)LMIC.datarate;
+            if( jacc ) {
+                u1_t ftype;
+                if( (LMIC.opmode & OP_REJOIN) != 0 ) {
+                    txdr = lowerDR(txdr, LMIC.rejoinCnt);
+                    ftype = HDR_FTYPE_REJOIN;
+                } else {
+                    ftype = HDR_FTYPE_JREQ;
+                }
+                buildJoinRequest(ftype);
+                LMIC.osjob.func = FUNC_ADDR(jreqDone);
+            } else {
+                if( LMIC.seqnoDn >= 0xFFFFFF80 ) {
+                    // Imminent roll over - proactively reset MAC
+                    EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_ROLL_OVER,
+                                        e_.eui    = MAIN::CDEV->getEui(),
+                                        e_.info   = LMIC.seqnoDn, 
+                                        e_.info2  = 0));
+                    // Device has to react! NWK will not roll over and just stop sending.
+                    // Thus, we have N frames to detect a possible lock up.
+                  reset:
+                    os_setCallback(&LMIC.osjob, FUNC_ADDR(runReset));
+                    return;
+                }
+                if( (LMIC.txCnt==0 && LMIC.seqnoUp == 0xFFFFFFFF) ) {
+                    // Roll over of up seq counter
+                    EV(specCond, ERR, (e_.reason = EV::specCond_t::UPSEQNO_ROLL_OVER,
+                                       e_.eui    = MAIN::CDEV->getEui(),
+                                       e_.info2  = LMIC.seqnoUp));
+                    // Do not run RESET event callback from here!
+                    // App code might do some stuff after send unaware of RESET.
+                    goto reset;
+                }
+                buildDataFrame();
+                LMIC.osjob.func = FUNC_ADDR(updataDone);
+            }
+            LMIC.rps    = setCr(updr2rps(txdr), (cr_t)LMIC.errcr);
+            LMIC.dndr   = txdr;  // carry TX datarate (can be != LMIC.datarate) over to txDone/setupRx1
+            LMIC.opmode = (LMIC.opmode & ~(OP_POLL|OP_RNDTX)) | OP_TXRXPEND | OP_NEXTCHNL;
+            updateTx(txbeg);
+            os_radio(RADIO_TX);
             return;
         }
-        buildDataFrame();
-        // NOTE: a channel decision for LMIC.txChnl above will never be rendered invalid
-        // by chosing a slower datarate (it could be invalided ONLY by a faster datarate).
-        txdr = lowerDR(txdr, DRADJUST[LMIC.txCnt]);
-        LMIC.osjob.func = FUNC_ADDR(updataDone);
-        }
-        LMIC.rps    = setCr(updr2rps(txdr), (cr_t)LMIC.errcr);
-        LMIC.rxsyms = txdr;  // carry TX datarate (can be != LMIC.datarate) over to txDone/setupRx1
-        LMIC.opmode = (LMIC.opmode & ~(OP_POLL|OP_RNDTX)) | OP_TXRXPEND | OP_NEXTCHNL;
-        updateTx(txbeg);
-        os_radio(RADIO_TX);
-        return;
-    }
-    // Cannot yet TX
-    if( (LMIC.opmode & OP_TRACK) == 0 )
-        goto txdelay; // We don't track the beacon - nothing else to do - so wait for the time to TX
-    // Consider RX tasks
-    if( txbeg == 0 ) // zero indicates no TX pending
-        txbeg += 1;  // TX delayed by one tick (insignificant amount of time)
+        // Cannot yet TX
+        if( (LMIC.opmode & OP_TRACK) == 0 )
+            goto txdelay; // We don't track the beacon - nothing else to do - so wait for the time to TX
+        // Consider RX tasks
+        if( txbeg == 0 ) // zero indicates no TX pending
+            txbeg += 1;  // TX delayed by one tick (insignificant amount of time)
     } else {
-    // No TX pending - no scheduled RX
-    if( (LMIC.opmode & OP_TRACK) == 0 )
-        return;
+        // No TX pending - no scheduled RX
+        if( (LMIC.opmode & OP_TRACK) == 0 )
+            return;
     }
 
     // Are we pingable?
   checkrx:
     if( (LMIC.opmode & OP_PINGINI) != 0 ) {
-    // One more RX slot in this beacon period?
-    if( rxschedNext(&LMIC.ping, now+RX_RAMPUP) ) {
-        if( txbeg != 0  &&  (txbeg - LMIC.ping.rxtime) < 0 )
-        goto txdelay;
-        LMIC.rxsyms  = LMIC.ping.rxsyms;
-        LMIC.rxtime  = LMIC.ping.rxtime;
-        LMIC.freq    = LMIC.ping.freq;
-        LMIC.rps     = dndr2rps(LMIC.ping.dr);
-        LMIC.dataLen = 0;
-        ASSERT(LMIC.rxtime - now+RX_RAMPUP >= 0 );
-        os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, FUNC_ADDR(startRxPing));
-        return;
-    }
-    // no - just wait for the beacon
+        // One more RX slot in this beacon period?
+        if( rxschedNext(&LMIC.ping, now+RX_RAMPUP) ) {
+            if( txbeg != 0  &&  (txbeg - LMIC.ping.rxtime) < 0 )
+                goto txdelay;
+            LMIC.rxsyms  = LMIC.ping.rxsyms;
+            LMIC.rxtime  = LMIC.ping.rxtime;
+            LMIC.freq    = LMIC.ping.freq;
+            LMIC.rps     = dndr2rps(LMIC.ping.dr);
+            LMIC.dataLen = 0;
+            ASSERT(LMIC.rxtime - now+RX_RAMPUP >= 0 );
+            os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, FUNC_ADDR(startRxPing));
+            return;
+        }
+        // no - just wait for the beacon
     }
 
     if( txbeg != 0  &&  (txbeg - rxtime) < 0 )
-    goto txdelay;
+        goto txdelay;
 
     setBcnRxParams();
     LMIC.rxsyms = LMIC.bcnRxsyms;
     LMIC.rxtime = LMIC.bcnRxtime;
     if( now - rxtime >= 0 ) {
-    LMIC.osjob.func = FUNC_ADDR(processBeacon);
-    os_radio(RADIO_RX);
-    return;
+        LMIC.osjob.func = FUNC_ADDR(processBeacon);
+        os_radio(RADIO_RX);
+        return;
     }
     os_setTimedCallback(&LMIC.osjob, rxtime, FUNC_ADDR(startRxBcn));
     return;
 
   txdelay:
     EV(devCond, INFO, (e_.reason = EV::devCond_t::TX_DELAY,
-               e_.eui    = MAIN::CDEV->getEui(),
-               e_.info   = osticks2ms(txbeg-now),
-               e_.info2  = LMIC.seqnoUp-1));
+                       e_.eui    = MAIN::CDEV->getEui(),
+                       e_.info   = osticks2ms(txbeg-now),
+                       e_.info2  = LMIC.seqnoUp-1));
     os_setTimedCallback(&LMIC.osjob, txbeg-TX_RAMPUP, FUNC_ADDR(runEngineUpdate));
 }
 
@@ -1990,22 +2046,32 @@
 
 void LMIC_reset (void) {
     EV(devCond, INFO, (e_.reason = EV::devCond_t::LMIC_EV,
-               e_.eui    = MAIN::CDEV->getEui(),
-               e_.info   = EV_RESET));
+                       e_.eui    = MAIN::CDEV->getEui(),
+                       e_.info   = EV_RESET));
     os_radio(RADIO_RST);
     os_clearCallback(&LMIC.osjob);
 
     os_clearMem((xref2u1_t)&LMIC,SIZEOFEXPR(LMIC));
-    LMIC.devaddr     = 0;
-    LMIC.devNonce    = os_getRndU2();
-    LMIC.opmode      = OP_NONE;
-    LMIC.errcr       = CR_4_5;
-    LMIC.adrEnabled  = FCT_ADREN;
-    LMIC.dn2Dr       = DR_DNW2;   // we need this for 2ns DN window of join accept
-    LMIC.dn2Freq     = FREQ_DNW2; // ditto
-#if CFG_us915
+    LMIC.devaddr      =  0;
+    LMIC.devNonce     =  os_getRndU2();
+    LMIC.opmode       =  OP_NONE;
+    LMIC.errcr        =  CR_4_5;
+    LMIC.adrEnabled   =  FCT_ADREN;
+    LMIC.dn2Dr        =  DR_DNW2;   // we need this for 2nd DN window of join accept
+    LMIC.dn2Freq      =  FREQ_DNW2; // ditto
+    LMIC.ping.freq    =  FREQ_PING; // defaults for ping
+    LMIC.ping.dr      =  DR_PING;   // ditto
+    LMIC.ping.intvExp =  0xFF;
+#if defined(CFG_us915)
     initDefaultChannels();
 #endif
+    DO_DEVDB(LMIC.devaddr,      devaddr);
+    DO_DEVDB(LMIC.devNonce,     devNonce);
+    DO_DEVDB(LMIC.dn2Dr,        dn2Dr);
+    DO_DEVDB(LMIC.dn2Freq,      dn2Freq);
+    DO_DEVDB(LMIC.ping.freq,    pingFreq);
+    DO_DEVDB(LMIC.ping.dr,      pingDr);
+    DO_DEVDB(LMIC.ping.intvExp, pingIntvExp);
 }
 
 
@@ -2018,7 +2084,7 @@
     LMIC.opmode &= ~(OP_TXDATA|OP_TXRXPEND|OP_POLL);
     LMIC.pendTxLen = 0;
     if( (LMIC.opmode & (OP_JOINING|OP_SCAN)) != 0 ) // do not interfere with JOINING
-    return;
+        return;
     os_clearCallback(&LMIC.osjob);
     os_radio(RADIO_RST);
     engineUpdate();
@@ -2028,7 +2094,7 @@
 void LMIC_setTxData (void) {
     LMIC.opmode |= OP_TXDATA;
     if( (LMIC.opmode & OP_JOINING) == 0 )
-    LMIC.txCnt = 0;             // cancel any ongoing TX/RX retries
+        LMIC.txCnt = 0;             // cancel any ongoing TX/RX retries
     engineUpdate();
 }
 
@@ -2036,9 +2102,9 @@
 //
 int LMIC_setTxData2 (u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed) {
     if( dlen > SIZEOFEXPR(LMIC.pendTxData) )
-    return -2;
+        return -2;
     if( data != (xref2u1_t)0 )
-    os_copyMem(LMIC.pendTxData, data, dlen);
+        os_copyMem(LMIC.pendTxData, data, dlen);
     LMIC.pendTxConf = confirmed;
     LMIC.pendTxPort = port;
     LMIC.pendTxLen  = dlen;
@@ -2060,19 +2126,48 @@
     engineUpdate();
 }
 
-void LMIC_startABP(u4_t netid, devaddr_t devaddr, u1_t* nwkKey, u1_t* artKey)
-{
+//! \brief Setup given session keys
+//! and put the MAC in a state as if 
+//! a join request/accept would have negotiated just these keys.
+//! It is crucial that the combinations `devaddr/nwkkey` and `devaddr/artkey`
+//! are unique within the network identified by `netid`.
+//! NOTE: on Harvard architectures when session keys are in flash:
+//!  Caller has to fill in LMIC.{nwk,art}Key  before and pass {nwk,art}Key are NULL
+//! \param netid a 24 bit number describing the network id this device is using
+//! \param devaddr the 32 bit session address of the device. It is strongly recommended
+//!    to ensure that different devices use different numbers with high probability.
+//! \param nwkKey  the 16 byte network session key used for message integrity.
+//!     If NULL the caller has copied the key into `LMIC.nwkKey` before.
+//! \param artKey  the 16 byte application router session key used for message confidentiality.
+//!     If NULL the caller has copied the key into `LMIC.artKey` before.
+void LMIC_setSession (u4_t netid, devaddr_t devaddr, xref2u1_t nwkKey, xref2u1_t artKey) {
     LMIC.netid = netid;
     LMIC.devaddr = devaddr;
-    memcpy(LMIC.nwkKey, nwkKey, 16);
-    memcpy(LMIC.artKey, artKey, 16);
+    if( nwkKey != (xref2u1_t)0 )
+        os_copyMem(LMIC.nwkKey, nwkKey, 16);
+    if( artKey != (xref2u1_t)0 )
+        os_copyMem(LMIC.artKey, artKey, 16);
     
-#if CFG_eu868
+#if defined(CFG_eu868)
     initDefaultChannels(0);
 #endif
-
-    LMIC.opmode &= ~(OP_JOINING|OP_TRACK|OP_REJOIN|OP_TXRXPEND|OP_PINGINI) | OP_NEXTCHNL;
+ 
+    LMIC.opmode &= ~(OP_JOINING|OP_TRACK|OP_REJOIN|OP_TXRXPEND|OP_PINGINI);
+    LMIC.opmode |= OP_NEXTCHNL;
     stateJustJoined();
-    //engineUpdate();
-    reportEvent(EV_JOINED);
 }
+
+// Enable/disable link check validation.
+// LMIC sets the ADRACKREQ bit in UP frames if there were no DN frames
+// for a while. It expects the network to provide a DN message to prove
+// connectivity with a span of UP frames. If this no such prove is coming
+// then the datarate is lowered and a LINK_DEAD event is generated.
+// This mode can be disabled and no connectivity prove (ADRACKREQ) is requested
+// nor is the datarate changed.
+// This must be called only if a session is established (e.g. after EV_JOINED)
+void LMIC_setLinkCheckMode (bit_t enabled) {
+    LMIC.adrChanged = 0;
+    LMIC.adrAckReq = enabled ? LINK_CHECK_INIT : LINK_CHECK_OFF;
+}
+
+