From baf7df4a545848b1d00aaf03cdc43d752b5e8af9 Mon Sep 17 00:00:00 2001 From: Wolfgang Klenk Date: Wed, 12 Apr 2017 21:50:17 +0200 Subject: [PATCH] Got examples/join working --- README.md | 65 +- examples/join/main.c | 6 +- lmic/lmic.c | 17 + lmic/lmic.c.original | 2223 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 2305 insertions(+), 6 deletions(-) create mode 100644 lmic/lmic.c.original diff --git a/README.md b/README.md index c15d54e..ea9ea0f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # lmic-rpi-lora-gps-hat - -# 2017-04-12 WORK UNDER CONSTRUCTION - DO NOT CLONE OR DOWNLOAD +# 2017-04-12 WORK UNDER CONSTRUCTION Hardware Abstraction Layer (HAL) for IBM's LMIC 1.6 communication stack targeted to RPi and Dragino LoRA/GPS HAT. @@ -34,7 +33,7 @@ the BSD License. ## Example "hello" Directory: /examples/hello -Modifications neccessary: None +Modifications necessary: None This example does not use radio, it just periodically logs a counter value. Can be used to checked if the timer implementation on RPi works as expected. @@ -66,3 +65,63 @@ Possible output: 000003034 Debug: Label 'cnt = ' value 0x3 +## Example "join" +Directory: /examples/join + +Modifications necessary: + +File /examples/join/main.c: + +* Adapt "application router ID (LSBF)" according for your network infrastructure. + In case of The Things Network, this is the "Application EUI" of the application + created in the TTN console. Double check that you use the LSB (least significant + byte first) notation of the Application EUI. + +* Adapt "unique device ID (LSBF)" according for your network infrastructure. + In case of The Things Network, you need to register a new device using TTN console. + Copy this "Device EUI" from the console and make sure you use the LSB notation. + +* Adapt "device-specific AES key (derived from device EUI)". + This is the secret shared between your device and The Things Network. In TTN + terms this is known as "(LoRa) App Key". + Copy this 16 bytes and stick to MSB notation (Most significant byte first) + +File /lmic/lmic.c: + +The LMIC 1.6 stack randomly chooses one of six frequencies to send the "join" message +to the network. Tesing with a Kerlink IoT Station, only the following frequencies +worked: 868.1 868.3 868.5 MHz +The following default frequency did not work: 864.1 864.3 864.5 +For this reason, I modified the code to only randomly choose between the three +working join frequencies. + + +This example verifies that the radio is working and that the node settings are +correct and match your network infrastructure. It uses OTAA (Over the Air Activiation) +to register the node. Note that this example _won't_ +work with a Single Channel Gateway. + + cd examples/join + make clean + make + sudo ./build/join.out + +Possible outpout: + + 000000000 HAL: Initializing ... + 000000001 HAL: Set radio RST pin to 0x00 + 000000002 HAL: Wait until 000000002 ms + 000000003 HAL: Set radio RST pin to 0x02 + 000000003 HAL: Wait until 000000008 ms + 000000008 HAL: Receiving ... + 000000020 Debug: Initializing + 000000020 Debug: JOINING + 000002625 Debug: EV_TXSTART + 000002626 HAL: Sending ... + 000007689 HAL: Receiving ... + 000007689 HAL: Wait until 000007690 ms + 000007762 Debug: JOINED + +What can be seen is that after sending the "join" message, the LMIC stack waits +5 seconds for the receive window and receives the acknowledgement from the LoRa gateway. + diff --git a/examples/join/main.c b/examples/join/main.c index 8e7a355..e5f233a 100644 --- a/examples/join/main.c +++ b/examples/join/main.c @@ -33,13 +33,13 @@ ////////////////////////////////////////////////// // application router ID (LSBF) -static const u1_t APPEUI[8] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0xEE, 0xFF, 0xC0 }; +static const u1_t APPEUI[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // unique device ID (LSBF) -static const u1_t DEVEUI[8] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF }; +static const u1_t DEVEUI[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // device-specific AES key (derived from device EUI) -static const u1_t DEVKEY[16] = { 0xAB, 0x89, 0xEF, 0xCD, 0x23, 0x01, 0x67, 0x45, 0x54, 0x76, 0x10, 0x32, 0xDC, 0xFE, 0x98, 0xBA }; +static const u1_t DEVKEY[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; ////////////////////////////////////////////////// diff --git a/lmic/lmic.c b/lmic/lmic.c index ad17a93..966f5ee 100644 --- a/lmic/lmic.c +++ b/lmic/lmic.c @@ -697,6 +697,23 @@ static void initJoinLoop (void) { LMIC.txChnl = 0; #else LMIC.txChnl = os_getRndU1() % 6; + + // 2017-04-12 Wolfgang Klenk + // Instead to choose a random frequence for joining the network, + // you can use a fixed frequency. + // LMIC.txChnl = 0; // EU868: 868.1 MHz + // LMIC.txChnl = 2; // EU868: 868.3 MHz + // LMIC.txChnl = 4; // EU868: 868.5 MHz + // LMIC.txChnl = 1; // EU868: 864.1 MHz -- Seems not to work with Kerlink IoT Station + // LMIC.txChnl = 3; // EU868: 864.3 MHz -- Seems not to work with Kerlink IoT Station + // LMIC.txChnl = 5; // EU868: 864.5 MHz -- Seems not to work with Kerlink IoT Station + + // 2017-04-12 Wolfgang Klenk + // Workaround for Kerlink IoT Station Join Frequencies + if (LMIC.txChnl == 1) LMIC.txChnl = 0; + if (LMIC.txChnl == 3) LMIC.txChnl = 2; + if (LMIC.txChnl == 5) LMIC.txChnl = 4; + #endif LMIC.adrTxPow = 14; setDrJoin(DRCHG_SET, DR_SF7); diff --git a/lmic/lmic.c.original b/lmic/lmic.c.original new file mode 100644 index 0000000..ad17a93 --- /dev/null +++ b/lmic/lmic.c.original @@ -0,0 +1,2223 @@ +/* + * Copyright (c) 2014-2016 IBM Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +//! \file +#include "lmic.h" + +#if !defined(MINRX_SYMS) +#define MINRX_SYMS 5 +#endif // !defined(MINRX_SYMS) +#define PAMBL_SYMS 8 +#define PAMBL_FSK 5 +#define PRERX_FSK 1 +#define RXLEN_FSK (1+5+2) + +#define BCN_INTV_osticks sec2osticks(BCN_INTV_sec) +#define TXRX_GUARD_osticks ms2osticks(TXRX_GUARD_ms) +#define JOIN_GUARD_osticks ms2osticks(JOIN_GUARD_ms) +#define DELAY_DNW1_osticks sec2osticks(DELAY_DNW1) +#define DELAY_DNW2_osticks sec2osticks(DELAY_DNW2) +#define DELAY_JACC1_osticks sec2osticks(DELAY_JACC1) +#define DELAY_JACC2_osticks sec2osticks(DELAY_JACC2) +#define DELAY_EXTDNW2_osticks sec2osticks(DELAY_EXTDNW2) +#define BCN_RESERVE_osticks ms2osticks(BCN_RESERVE_ms) +#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; + + +// Fwd decls. +static void engineUpdate(void); +static void startScan (void); + + +// ================================================================================ +// BEG OS - default implementations for certain OS suport functions + +#if !defined(HAS_os_calls) + +#if !defined(os_rlsbf2) +u2_t os_rlsbf2 (xref2cu1_t buf) { + return (u2_t)(buf[0] | (buf[1]<<8)); +} +#endif + +#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 + + +#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 + + +#if !defined(os_wlsbf2) +void os_wlsbf2 (xref2u1_t buf, u2_t v) { + buf[0] = v; + buf[1] = v>>8; +} +#endif + +#if !defined(os_wlsbf4) +void os_wlsbf4 (xref2u1_t buf, u4_t v) { + buf[0] = v; + buf[1] = v>>8; + buf[2] = v>>16; + buf[3] = v>>24; +} +#endif + +#if !defined(os_wmsbf4) +void os_wmsbf4 (xref2u1_t buf, u4_t v) { + buf[3] = v; + buf[2] = v>>8; + buf[1] = v>>16; + buf[0] = v>>24; +} +#endif + +#if !defined(os_getBattLevel) +u1_t os_getBattLevel (void) { + return MCMD_DEVS_BATT_NOINFO; +} +#endif + +#if !defined(os_crc16) +// New CRC-16 CCITT(XMODEM) checksum for beacons: +u2_t os_crc16 (xref2u1_t data, uint len) { + 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 remainder; +} +#endif + +#endif // !HAS_os_calls + +// END OS - default implementations for certain OS suport functions +// ================================================================================ + +// ================================================================================ +// BEG AES + +static void micB0 (u4_t devaddr, u4_t seqno, int dndir, int len) { + os_clearMem(AESaux,16); + AESaux[0] = 0x49; + AESaux[5] = dndir?1:0; + AESaux[15] = len; + os_wlsbf4(AESaux+ 6,devaddr); + os_wlsbf4(AESaux+10,seqno); +} + + +static int aes_verifyMic (xref2cu1_t key, u4_t devaddr, u4_t seqno, int dndir, xref2u1_t pdu, int len) { + micB0(devaddr, seqno, dndir, len); + os_copyMem(AESkey,key,16); + return os_aes(AES_MIC, pdu, len) == os_rmsbf4(pdu+len); +} + + +static void aes_appendMic (xref2cu1_t key, u4_t devaddr, u4_t seqno, int dndir, xref2u1_t pdu, int len) { + micB0(devaddr, seqno, dndir, len); + os_copyMem(AESkey,key,16); + // MSB because of internal structure of AES + os_wmsbf4(pdu+len, os_aes(AES_MIC, pdu, len)); +} + + +static void aes_appendMic0 (xref2u1_t pdu, int len) { + os_getDevKey(AESkey); + os_wmsbf4(pdu+len, os_aes(AES_MIC|AES_MICNOAUX, pdu, len)); // MSB because of internal structure of AES +} + + +static int aes_verifyMic0 (xref2u1_t pdu, int len) { + os_getDevKey(AESkey); + return os_aes(AES_MIC|AES_MICNOAUX, pdu, len) == os_rmsbf4(pdu+len); +} + + +static void aes_encrypt (xref2u1_t pdu, int len) { + os_getDevKey(AESkey); + os_aes(AES_ENC, pdu, len); +} + + +static void aes_cipher (xref2cu1_t key, u4_t devaddr, u4_t seqno, int dndir, xref2u1_t payload, int len) { + if( len <= 0 ) + return; + os_clearMem(AESaux, 16); + AESaux[0] = AESaux[15] = 1; // mode=cipher / dir=down / block counter=1 + AESaux[5] = dndir?1:0; + os_wlsbf4(AESaux+ 6,devaddr); + os_wlsbf4(AESaux+10,seqno); + os_copyMem(AESkey,key,16); + os_aes(AES_CTR, payload, len); +} + + +static void aes_sessKeys (u2_t devnonce, xref2cu1_t artnonce, xref2u1_t nwkkey, xref2u1_t artkey) { + os_clearMem(nwkkey, 16); + nwkkey[0] = 0x01; + os_copyMem(nwkkey+1, artnonce, LEN_ARTNONCE+LEN_NETID); + os_wlsbf2(nwkkey+1+LEN_ARTNONCE+LEN_NETID, devnonce); + os_copyMem(artkey, nwkkey, 16); + artkey[0] = 0x02; + + os_getDevKey(AESkey); + os_aes(AES_ENC, nwkkey, 16); + os_getDevKey(AESkey); + os_aes(AES_ENC, artkey, 16); +} + +// END AES +// ================================================================================ + + +// ================================================================================ +// BEG LORA + +#if defined(CFG_eu868) // ======================================== + +#define maxFrameLen(dr) ((dr)<=DR_SF9 ? maxFrameLens[(dr)] : 0xFF) +const u1_t maxFrameLens [] = { 64,64,64,123 }; + +const u1_t _DR2RPS_CRC[] = { + ILLEGAL_RPS, + (u1_t)MAKERPS(SF12, BW125, CR_4_5, 0, 0), + (u1_t)MAKERPS(SF11, BW125, CR_4_5, 0, 0), + (u1_t)MAKERPS(SF10, BW125, CR_4_5, 0, 0), + (u1_t)MAKERPS(SF9, BW125, CR_4_5, 0, 0), + (u1_t)MAKERPS(SF8, BW125, CR_4_5, 0, 0), + (u1_t)MAKERPS(SF7, BW125, CR_4_5, 0, 0), + (u1_t)MAKERPS(SF7, BW250, CR_4_5, 0, 0), + (u1_t)MAKERPS(FSK, BW125, CR_4_5, 0, 0), + ILLEGAL_RPS +}; + +static const s1_t TXPOWLEVELS[] = { + 20, 14, 11, 8, 5, 2, 0,0, 0,0,0,0, 0,0,0,0 +}; +#define pow2dBm(mcmd_ladr_p1) (TXPOWLEVELS[(mcmd_ladr_p1&MCMD_LADR_POW_MASK)>>MCMD_LADR_POW_SHIFT]) + +#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 }; + +const u1_t _DR2RPS_CRC[] = { + ILLEGAL_RPS, + MAKERPS(SF10, BW125, CR_4_5, 0, 0), + MAKERPS(SF9 , BW125, CR_4_5, 0, 0), + MAKERPS(SF8 , BW125, CR_4_5, 0, 0), + MAKERPS(SF7 , BW125, CR_4_5, 0, 0), + MAKERPS(SF8 , BW500, CR_4_5, 0, 0), + ILLEGAL_RPS , + ILLEGAL_RPS , + ILLEGAL_RPS , + MAKERPS(SF12, BW500, CR_4_5, 0, 0), + MAKERPS(SF11, BW500, CR_4_5, 0, 0), + MAKERPS(SF10, BW500, CR_4_5, 0, 0), + MAKERPS(SF9 , BW500, CR_4_5, 0, 0), + MAKERPS(SF8 , BW500, CR_4_5, 0, 0), + MAKERPS(SF7 , BW500, CR_4_5, 0, 0), + ILLEGAL_RPS +}; + +#define pow2dBm(mcmd_ladr_p1) ((s1_t)(30 - (((mcmd_ladr_p1)&MCMD_LADR_POW_MASK)<<1))) + +#endif // ================================================ + +static const u1_t SENSITIVITY[7][3] = { + // ------------bw---------- + // 125kHz 250kHz 500kHz + { 141-109, 141-109, 141-109 }, // FSK + { 141-127, 141-124, 141-121 }, // SF7 + { 141-129, 141-126, 141-123 }, // SF8 + { 141-132, 141-129, 141-126 }, // SF9 + { 141-135, 141-132, 141-129 }, // SF10 + { 141-138, 141-135, 141-132 }, // SF11 + { 141-141, 141-138, 141-135 } // SF12 +}; + +int getSensitivity (rps_t rps) { + return -141 + SENSITIVITY[getSf(rps)][getBw(rps)]; +} + +ostime_t calcAirTime (rps_t rps, u1_t plen) { + 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 ) { + 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; + } else { + tmp = 8; + } + tmp = (tmp<<2) + /*preamble*/49 /* 4 * (8 + 4.25) */; + // bw = 125000 = 15625 * 2^3 + // 250000 = 15625 * 2^4 + // 500000 = 15625 * 2^5 + // sf = 7..12 + // + // osticks = tmp * OSTICKS_PER_SEC * 1< counter reduced divisor 125000/8 => 15625 + // 2 => counter 2 shift on tmp + 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; + } + // Need 32bit arithmetic for this last step + return (((ostime_t)tmp << sfx) * OSTICKS_PER_SEC + div/2) / div; +} + +extern inline rps_t updr2rps (dr_t dr); +extern inline rps_t dndr2rps (dr_t dr); +extern inline int isFasterDR (dr_t dr1, dr_t dr2); +extern inline int isSlowerDR (dr_t dr1, dr_t dr2); +extern inline dr_t incDR (dr_t dr); +extern inline dr_t decDR (dr_t dr); +extern inline dr_t assertDR (dr_t dr); +extern inline dr_t validDR (dr_t dr); +extern inline dr_t lowerDR (dr_t dr, u1_t n); + +extern inline sf_t getSf (rps_t params); +extern inline rps_t setSf (rps_t params, sf_t sf); +extern inline bw_t getBw (rps_t params); +extern inline rps_t setBw (rps_t params, bw_t cr); +extern inline cr_t getCr (rps_t params); +extern inline rps_t setCr (rps_t params, cr_t cr); +extern inline int getNocrc (rps_t params); +extern inline rps_t setNocrc (rps_t params, int nocrc); +extern inline int getIh (rps_t params); +extern inline rps_t setIh (rps_t params, int ih); +extern inline rps_t makeRps (sf_t sf, bw_t bw, cr_t cr, int ih, int nocrc); +extern inline int sameSfBw (rps_t r1, rps_t r2); + +// END LORA +// ================================================================================ + + +// Adjust DR for TX retries +// - indexed by retry count +// - return steps to lower DR +static const u1_t DRADJUST[2+TXCONF_ATTEMPTS] = { + // normal frames - 1st try / no retry + 0, + // confirmed frames + 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 +// +// Times for half symbol per DR +// Per DR table to minimize rounding errors +static const ostime_t DR2HSYM_osticks[] = { +#if defined(CFG_eu868) +#define dr2hsym(dr) (DR2HSYM_osticks[(dr)]) + us2osticksRound(128<<7), // DR_SF12 + us2osticksRound(128<<6), // DR_SF11 + us2osticksRound(128<<5), // DR_SF10 + us2osticksRound(128<<4), // DR_SF9 + us2osticksRound(128<<3), // DR_SF8 + us2osticksRound(128<<2), // DR_SF7 + us2osticksRound(128<<1), // DR_SF7B + us2osticksRound(80) // FSK -- not used (time for 1/2 byte) +#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 + us2osticksRound(128<<3), // DR_SF8 DR_SF10CR + us2osticksRound(128<<2), // DR_SF7 DR_SF9CR + us2osticksRound(128<<1), // DR_SF8C DR_SF8CR + us2osticksRound(128<<0) // ------ DR_SF7CR +#endif +}; + + +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; + } 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; + } + u1_t rxsyms = MINRX_SYMS; + err += (ostime_t)LMIC.maxDriftDiff * LMIC.missedBcns; + LMIC.rxsyms = MINRX_SYMS + (err / dr2hsym(dr)); + + return (rxsyms-PAMBL_SYMS) * dr2hsym(dr) + rxoff; +} + + +// 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; + } + ostime_t hsym = dr2hsym(DR_BCN); + LMIC.bcnRxsyms = MINRX_SYMS + ms2osticksCeil(ms) / hsym; + LMIC.bcnRxtime = LMIC.bcninfo.txtime + BCN_INTV_osticks - (LMIC.bcnRxsyms-PAMBL_SYMS) * hsym; +} + + +// Setup scheduled RX window (ping/multicast slot) +static void rxschedInit (xref2rxsched_t rxsched) { + os_clearMem(AESkey,16); + os_clearMem(LMIC.frame+8,8); + os_wlsbf4(LMIC.frame, LMIC.bcninfo.time); + os_wlsbf4(LMIC.frame+4, LMIC.devaddr); + os_aes(AES_ENC,LMIC.frame,16); + 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 + rxsched->slot = 0; + rxsched->rxtime = rxsched->rxbase - calcRxWindow(/*secs BCN_RESERVE*/2+(1<dr); + rxsched->rxsyms = LMIC.rxsyms; +} + + +static bit_t rxschedNext (xref2rxsched_t rxsched, ostime_t cando) { + again: + if( rxsched->rxtime - cando >= 0 ) + return 1; + u1_t slot; + if( (slot=rxsched->slot) >= 128 ) + return 0; + u1_t intv = 1<intvExp; + if( (rxsched->slot = (slot += (intv))) >= 128 ) + return 0; + rxsched->rxtime = rxsched->rxbase + + ((BCN_WINDOW_osticks * (ostime_t)slot) >> BCN_INTV_exp) + - calcRxWindow(/*secs BCN_RESERVE*/2+slot+intv,rxsched->dr); + rxsched->rxsyms = LMIC.rxsyms; + goto again; +} + + +static ostime_t rndDelay (u1_t secSpan) { + u2_t r = os_getRndU2(); + ostime_t delay = r; + if( delay > OSTICKS_PER_SEC ) + delay = r % (u2_t)OSTICKS_PER_SEC; + if( secSpan > 0 ) + delay += ((u1_t)r % secSpan) * OSTICKS_PER_SEC; + return delay; +} + + +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; + } +} + + +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)); + LMIC.datarate = 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)); + + if( pow != KEEP_TXPOW ) + LMIC.adrTxPow = pow; + if( LMIC.datarate != dr ) { + LMIC.datarate = dr; + DO_DEVDB(LMIC.datarate,datarate); + LMIC.opmode |= OP_NEXTCHNL; + } +} + + +void LMIC_stopPingable (void) { + LMIC.opmode &= ~(OP_PINGABLE|OP_PINGINI); +} + + +void LMIC_setPingable (u1_t intvExp) { + // Change setting + LMIC.ping.intvExp = (intvExp & 0x7); + LMIC.opmode |= OP_PINGABLE; + // 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); +} + + +#if defined(CFG_eu868) +// ================================================================================ +// +// BEG: EU868 related stuff +// +enum { NUM_DEFAULT_CHANNELS=6 }; +static const u4_t iniChannelFreq[12] = { + // Join frequencies and duty cycle limit (0.1%) + 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) { + os_clearMem(&LMIC.channelFreq, sizeof(LMIC.channelFreq)); + os_clearMem(&LMIC.channelDrMap, sizeof(LMIC.channelDrMap)); + os_clearMem(&LMIC.bands, sizeof(LMIC.bands)); + + LMIC.channelMap = 0x3F; + u1_t su = join ? 0 : 6; + for( u1_t fu=0; fu<6; fu++,su++ ) { + LMIC.channelFreq[fu] = iniChannelFreq[su]; + LMIC.channelDrMap[fu] = DR_RANGE_MAP(DR_SF12,DR_SF7); + } + 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_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]; + xref2band_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<> 8) * 100; + if( freq < EU868_FREQ_MIN || freq > EU868_FREQ_MAX ) + freq = 0; + return freq; +} + +static u1_t mapChannels (u1_t chpage, u2_t chmap) { + // 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; chnltxpow; + band->avail = txbeg + airtime * band->txcap; + if( LMIC.globalDutyRate != 0 ) + LMIC.globalDutyAvail = txbeg + (airtime< 0 ) + mintime = LMIC.bands[band = bi].avail; + } + // Find next channel in given band + u1_t chnl = LMIC.bands[band].lastchnl; + for( u1_t ci=0; ci= MAX_CHANNELS ) + chnl -= MAX_CHANNELS; + if( (LMIC.channelMap & (1<>LMIC.datarate)); + // 1 - triggers EV_JOIN_FAILED event + return failed; +} + +// +// END: EU868 related stuff +// +// ================================================================================ +#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[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; + return freq; +} + +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 + chidx -= 72; + LMIC.xchFreq[chidx] = freq; + 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; + } else { + if( chpage >= (72+MAX_XCHANNELS+15)/16 ) + return 0; + LMIC.channelMap[chpage] = chmap; + } + return 1; +} + +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.txpow = 26; + if( chnl < 64+8 ) { + LMIC.freq = US915_500kHz_UPFBASE + (chnl-64)*US915_500kHz_UPFSTEP; + } else { + 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<= 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; + } + } + } + // 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.rps = setIh(setNocrc(dndr2rps((dr_t)DR_BCN),1),LEN_BCN); +} + +#define setRx1Params() { \ + LMIC.freq = US915_500kHz_DNFBASE + (LMIC.txChnl & 0x7) * US915_500kHz_DNFSTEP; \ + 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.chRnd = 0; + LMIC.txChnl = 0; + LMIC.adrTxPow = 20; + ASSERT((LMIC.opmode & OP_NEXTCHNL)==0); + LMIC.txend = os_getTime(); + setDrJoin(DRCHG_SET, DR_SF7); +} + +static ostime_t nextJoinState (void) { + // Try the following: + // SF7/8/9/10 on a random channel 0..63 + // SF8C on a random channel 64..71 + // + u1_t failed = 0; + if( LMIC.datarate != DR_SF8C ) { + LMIC.txChnl = 64+(LMIC.txChnl&7); + setDrJoin(DRCHG_SET, DR_SF8C); + } else { + 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); + } + 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; +} + +// +// END: US915 related stuff +// +// ================================================================================ +#else +#error Unsupported frequency band! +#endif + + +static void runEngineUpdate (xref2osjob_t osjob) { + engineUpdate(); +} + + +static void reportEvent (ev_t ev) { + EV(devCond, INFO, (e_.reason = EV::devCond_t::LMIC_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; + LMIC.dnConf = LMIC.adrChanged = LMIC.ladrAns = LMIC.devsAns = 0; + LMIC.moreData = LMIC.dn2Ans = LMIC.snchAns = LMIC.dutyCapAns = 0; + LMIC.pingSetAns = 0; + LMIC.upRepeat = 0; + LMIC.adrAckReq = LINK_CHECK_INIT; + LMIC.dn2Dr = DR_DNW2; + LMIC.dn2Freq = FREQ_DNW2; + LMIC.bcnChnl = CHNL_BCN; + LMIC.ping.freq = FREQ_PING; + LMIC.ping.dr = DR_PING; +} + + +// ================================================================================ +// Decoding frames + + +// Decode beacon - do not overwrite bcninfo unless we have a match! +static int decodeBeacon (void) { + ASSERT(LMIC.dataLen == LEN_BCN); // implicit header RX guarantees this + xref2u1_t d = LMIC.frame; + 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 + + LMIC.bcninfo.flags &= ~(BCN_PARTIAL|BCN_FULL); + // Match - update bcninfo structure + 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; + // 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 + LMIC.bcninfo.info = d[OFF_BCN_INFO]; + LMIC.bcninfo.flags |= BCN_FULL; + return 2; +} + + +static bit_t decodeFrame (void) { + xref2u1_t d = LMIC.frame; + u1_t hdr = d[0]; + 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))); + norx: + LMIC.dataLen = 0; + return 0; + } + // Validate exact frame length + // Note: device address was already read+evaluated in order to arrive here. + int fct = d[OFF_DAT_FCT]; + u4_t addr = os_rlsbf4(&d[OFF_DAT_ADDR]); + u4_t seqno = os_rlsbf2(&d[OFF_DAT_SEQNO]); + int olen = fct & FCT_OPTLEN; + int ackup = (fct & FCT_ACK) != 0 ? 1 : 0; // ACK last up frame + int poff = OFF_DAT_OPTS+olen; + 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; + } + 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; + } + + int port = -1; + int replayConf = 0; + + if( pend > 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(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 ) { + 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(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; + + // We heard from network + LMIC.adrChanged = LMIC.rejoinCnt = 0; + if( LMIC.adrAckReq != LINK_CHECK_OFF ) + LMIC.adrAckReq = LINK_CHECK_INIT; + + // Process OPTS + 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 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; + 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; + } + 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; + } + 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; + } + 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))); + } + 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; + } + if( oidx != olen ) { + 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); + + 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)); + } + + 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)); + } + + if( LMIC.txCnt != 0 ) // we requested an ACK + LMIC.txrxFlags |= ackup ? TXRX_ACK : TXRX_NACK; + + if( port < 0 ) { + LMIC.txrxFlags |= TXRX_NOPORT; + LMIC.dataBeg = poff; + LMIC.dataLen = 0; + } else { + LMIC.txrxFlags |= TXRX_PORT; + LMIC.dataBeg = poff; + LMIC.dataLen = pend-poff; + } + return 1; +} + + +// ================================================================================ +// TX/RX transaction support + + +static void setupRx2 (void) { + LMIC.txrxFlags = TXRX_DNW2; + LMIC.rps = dndr2rps(LMIC.dn2Dr); + LMIC.freq = LMIC.dn2Freq; + LMIC.dataLen = 0; + os_radio(RADIO_RX); +} + + +static void schedRx2 (ostime_t delay, osjobcb_t func) { + // Add 1.5 symbols we need 5 out of 8. Try to sync 1.5 symbols into the preamble. + LMIC.rxtime = LMIC.txend + delay + (PAMBL_SYMS-MINRX_SYMS)*dr2hsym(LMIC.dn2Dr); + os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func); +} + +static void setupRx1 (osjobcb_t func) { + LMIC.txrxFlags = TXRX_DNW1; + // Turn LMIC.rps from TX over to RX + LMIC.rps = setNocrc(LMIC.rps,1); + LMIC.dataLen = 0; + LMIC.osjob.func = func; + os_radio(RADIO_RX); +} + + +// 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) { + 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; + } + // 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 defined(CFG_eu868) + if( /* TX datarate */LMIC.rxsyms == DR_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(LMIC.dndr); + LMIC.rxsyms = MINRX_SYMS; + } + os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func); +} + + +// ======================================== Join frames + + +static void onJoinFailed (xref2osjob_t osjob) { + // Notify app - must call LMIC_reset() to stop joining + // otherwise join procedure continues. + reportEvent(EV_JOIN_FAILED); +} + + +static bit_t processJoinAccept (void) { + ASSERT(LMIC.txrxFlags != TXRX_DNW1 || LMIC.dataLen != 0); + ASSERT((LMIC.opmode & OP_TXRXPEND)!=0); + + if( LMIC.dataLen == 0 ) { + nojoinframe: + 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; + } + 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))); + badframe: + 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; + } + + u4_t addr = os_rlsbf4(LMIC.frame+OFF_JA_DEVADDR); + LMIC.devaddr = addr; + LMIC.netid = os_rlsbf4(&LMIC.frame[OFF_JA_NETID]) & 0xFFFFFF; + +#if defined(CFG_eu868) + initDefaultChannels(0); +#endif + if( dlen > LEN_JA ) { +#if defined(CFG_us915) + goto badframe; +#endif + dlen = OFF_CFLIST; + for( u1_t chidx=3; chidx<8; chidx++, dlen+=3 ) { + u4_t freq = convFreq(&LMIC.frame[dlen]); + if( freq ) + LMIC_setupChannel(chidx, freq, 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(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_.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 ) { + // 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(); + reportEvent(EV_JOINED); + return 1; +} + + +static void processRx2Jacc (xref2osjob_t osjob) { + if( LMIC.dataLen == 0 ) + 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)); +} + + +static void jreqDone (xref2osjob_t osjob) { + txDone(DELAY_JACC1_osticks, FUNC_ADDR(setupRx1Jacc)); +} + +// ======================================== Data frames + +// 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 + // 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)), + FUNC_ADDR(processRx2DnDataDelay)); + return; + } + processDnData(); +} + + +static void setupRx2DnData (xref2osjob_t osjob) { + LMIC.osjob.func = FUNC_ADDR(processRx2DnData); + setupRx2(); +} + + +static void processRx1DnData (xref2osjob_t osjob) { + if( LMIC.dataLen == 0 || !processDnData() ) + schedRx2(DELAY_DNW2_osticks, FUNC_ADDR(setupRx2DnData)); +} + + +static void setupRx1DnData (xref2osjob_t osjob) { + setupRx1(FUNC_ADDR(processRx1DnData)); +} + + +static void updataDone (xref2osjob_t osjob) { + txDone(DELAY_DNW1_osticks, FUNC_ADDR(setupRx1DnData)); +} + +// ======================================== + + +static void buildDataFrame (void) { + bit_t txdata = ((LMIC.opmode & (OP_TXDATA|OP_POLL)) != OP_POLL); + u1_t dlen = txdata ? LMIC.pendTxLen : 0; + + // Piggyback MAC options + // 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; + } + if( LMIC.dutyCapAns ) { + 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; + } + 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; + } + 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; + } + if( LMIC.bcninfoTries > 0 ) { + LMIC.frame[end] = MCMD_BCNI_REQ; + end += 1; + } + if( LMIC.adrChanged ) { + 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; + } + if( LMIC.snchAns ) { + 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; + } + 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)); + os_wlsbf4(LMIC.frame+OFF_DAT_ADDR, LMIC.devaddr); + + if( LMIC.txCnt == 0 ) { + 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)))); + } + os_wlsbf2(LMIC.frame+OFF_DAT_SEQNO, LMIC.seqnoUp-1); + + // Clear pending DN confirmation + 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; + 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_.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; +} + + +// Callback from HAL during scan mode or when job timer expires. +static void onBcnRx (xref2osjob_t job) { + // If we arrive via job timer make sure to put radio to rest. + 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; + } + 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; + } + // Found our 1st beacon + // 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,1); + LMIC.opmode &= ~OP_SCAN; // turn SCAN off + LMIC.opmode |= OP_TRACK; // auto enable tracking + reportEvent(EV_BEACON_FOUND); // can be disabled in callback +} + + +// Enable receiver to listen to incoming beacons +// netid defines when scan stops (any or specific beacon) +// This mode ends with events: EV_SCAN_TIMEOUT/EV_SCAN_BEACON +// Implicitely cancels any pending TX/RX transaction. +// Also cancels an onpoing joining procedure. +static void startScan (void) { + ASSERT(LMIC.devaddr!=0 && (LMIC.opmode & OP_JOINING)==0); + if( (LMIC.opmode & OP_SHUTDOWN) != 0 ) + return; + // Cancel onging TX/RX transaction + LMIC.txCnt = LMIC.dnConf = LMIC.bcninfo.flags = 0; + LMIC.opmode = (LMIC.opmode | OP_SCAN) & ~(OP_TXRXPEND); + setBcnRxParams(); + LMIC.rxtime = LMIC.bcninfo.txtime = os_getTime() + sec2osticks(BCN_INTV_sec+1); + os_setTimedCallback(&LMIC.osjob, LMIC.rxtime, FUNC_ADDR(onBcnRx)); + os_radio(RADIO_RXON); +} + + +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 + // 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(); + return 1; // enabled +} + + +void LMIC_disableTracking (void) { + LMIC.opmode &= ~(OP_SCAN|OP_TRACK); + LMIC.bcninfoTries = 0; + engineUpdate(); +} + + + + + + + + + + + + + + + + + + + + + + + + + + + +// ================================================================================ +// +// Join stuff +// +// ================================================================================ + +static void buildJoinRequest (u1_t ftype) { + // Do not use pendTxData since we might have a pending + // user level frame in there. Use RX holding area instead. + xref2u1_t d = LMIC.frame; + d[OFF_JR_HDR] = ftype; + os_getArtEui(d + OFF_JR_ARTEUI); + os_getDevEui(d + OFF_JR_DEVEUI); + os_wlsbf2(d + OFF_JR_DEVNONCE, LMIC.devNonce); + 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_.reason = ((LMIC.opmode & OP_REJOIN) != 0 + ? EV::joininfo_t::REJOIN_REQUEST + : EV::joininfo_t::REQUEST))); + LMIC.dataLen = LEN_JR; + LMIC.devNonce++; + DO_DEVDB(LMIC.devNonce,devNonce); +} + +static void startJoining (xref2osjob_t osjob) { + reportEvent(EV_JOINING); +} + +// 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); + // 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; // already joined +} + + +// ================================================================================ +// +// +// +// ================================================================================ + +static void processPingRx (xref2osjob_t osjob) { + if( LMIC.dataLen != 0 ) { + LMIC.txrxFlags = TXRX_PING; + if( decodeFrame() ) { + reportEvent(EV_RXCOMPLETE); + return; + } + } + // Pick next ping slot + engineUpdate(); +} + + +static bit_t processDnData (void) { + ASSERT((LMIC.opmode & OP_TXRXPEND)!=0); + + if( LMIC.dataLen == 0 ) { + norx: + 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; + } + 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; + LMIC.opmode |= OP_REJOIN|OP_LINKDEAD; + 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; + } + else if( --LMIC.bcninfoTries == 0 ) { + startScan(); // NWK did not answer - try scan + } + } + return 1; + } + if( !decodeFrame() ) { + if( (LMIC.txrxFlags & TXRX_DNW1) != 0 ) + return 0; + goto norx; + } + goto txcomplete; +} + + +static void processBeacon (xref2osjob_t osjob) { + ostime_t lasttx = LMIC.bcninfo.txtime; // save here - decodeBeacon might overwrite + u1_t flags = LMIC.bcninfo.flags; + 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_.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.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! + reportEvent(ev); +} + + +static void startRxBcn (xref2osjob_t osjob) { + LMIC.osjob.func = FUNC_ADDR(processBeacon); + os_radio(RADIO_RX); +} + + +static void startRxPing (xref2osjob_t osjob) { + LMIC.osjob.func = FUNC_ADDR(processPingRx); + os_radio(RADIO_RX); +} + + +// Decide what to do next for the MAC layer of a device +static void engineUpdate (void) { + // Check for ongoing state: scan or TX/RX transaction + if( (LMIC.opmode & (OP_SCAN|OP_TXRXPEND|OP_SHUTDOWN)) != 0 ) + return; + + if( LMIC.devaddr == 0 && (LMIC.opmode & OP_JOINING) == 0 ) { + LMIC_startJoining(); + return; + } + + ostime_t now = os_getTime(); + ostime_t rxtime = 0; + 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; + } + + 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! + txbeg = 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); + reportEvent(EV_TXSTART); + 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) + } else { + // 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 + } + + if( txbeg != 0 && (txbeg - rxtime) < 0 ) + 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; + } + 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)); + os_setTimedCallback(&LMIC.osjob, txbeg-TX_RAMPUP, FUNC_ADDR(runEngineUpdate)); +} + + +void LMIC_setAdrMode (bit_t enabled) { + LMIC.adrEnabled = enabled ? FCT_ADREN : 0; +} + + +// Should we have/need an ext. API like this? +void LMIC_setDrTxpow (dr_t dr, s1_t txpow) { + setDrTxpow(DRCHG_SET, dr, txpow); +} + + +void LMIC_shutdown (void) { + os_clearCallback(&LMIC.osjob); + os_radio(RADIO_RST); + LMIC.opmode |= OP_SHUTDOWN; +} + + +void LMIC_reset (void) { + EV(devCond, INFO, (e_.reason = EV::devCond_t::LMIC_EV, + 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 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); +} + + +void LMIC_init (void) { + LMIC.opmode = OP_SHUTDOWN; +} + + +void LMIC_clrTxData (void) { + 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; + os_clearCallback(&LMIC.osjob); + os_radio(RADIO_RST); + engineUpdate(); +} + + +void LMIC_setTxData (void) { + LMIC.opmode |= OP_TXDATA; + if( (LMIC.opmode & OP_JOINING) == 0 ) + LMIC.txCnt = 0; // cancel any ongoing TX/RX retries + engineUpdate(); +} + + +// +int LMIC_setTxData2 (u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed) { + if( dlen > SIZEOFEXPR(LMIC.pendTxData) ) + return -2; + if( data != (xref2u1_t)0 ) + os_copyMem(LMIC.pendTxData, data, dlen); + LMIC.pendTxConf = confirmed; + LMIC.pendTxPort = port; + LMIC.pendTxLen = dlen; + LMIC_setTxData(); + return 0; +} + + +// Send a payload-less message to signal device is alive +void LMIC_sendAlive (void) { + LMIC.opmode |= OP_POLL; + engineUpdate(); +} + + +// Check if other networks are around. +void LMIC_tryRejoin (void) { + LMIC.opmode |= OP_REJOIN; + engineUpdate(); +} + +//! \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; + if( nwkKey != (xref2u1_t)0 ) + os_copyMem(LMIC.nwkKey, nwkKey, 16); + if( artKey != (xref2u1_t)0 ) + os_copyMem(LMIC.artKey, artKey, 16); + +#if defined(CFG_eu868) + initDefaultChannels(0); +#endif + + LMIC.opmode &= ~(OP_JOINING|OP_TRACK|OP_REJOIN|OP_TXRXPEND|OP_PINGINI); + LMIC.opmode |= OP_NEXTCHNL; + stateJustJoined(); + DO_DEVDB(LMIC.netid, netid); + DO_DEVDB(LMIC.devaddr, devaddr); + DO_DEVDB(LMIC.nwkKey, nwkkey); + DO_DEVDB(LMIC.artKey, artkey); + DO_DEVDB(LMIC.seqnoUp, seqnoUp); + DO_DEVDB(LMIC.seqnoDn, seqnoDn); +} + +// 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; +} + +