Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Fork of LMiC by
lmic.cpp
- Committer:
- mluis
- Date:
- 2015-01-22
- Revision:
- 0:62d1edcc13d1
- Child:
- 1:d3b7bde3995c
File content as of revision 0:62d1edcc13d1:
/*******************************************************************************
* Copyright (c) 2014 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Zurich Research Lab - initial API, implementation and documentation
*******************************************************************************/
#include "lmic.h"
#define PAMBL_SYMS 8
#define MINRX_SYMS 5
#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)
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 !HAS_os_calls
#ifndef os_rlsbf2
u2_t os_rlsbf2 (xref2cu1_t buf) {
return (u2_t)(buf[0] | (buf[1]<<8));
}
#endif
#ifndef 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
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
void os_wlsbf2 (xref2u1_t buf, u2_t v) {
buf[0] = v;
buf[1] = v>>8;
}
#endif
#ifndef 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
#ifndef 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
#ifndef 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
};
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));
}
return fcs;
}
#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 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 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 ) {
// 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;
}
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<<sf / bw
//
// 3 => 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 s1_t rssi2s1 (int v);
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);
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,0,0,0,1,1,1,2
};
// 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 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 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<<intvExp),rxsched->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<<rxsched->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(updateDatarate, dr);
}
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;
LMIC.datarate = dr;
DO_DEVDB(updateDatarate, dr);
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 CFG_eu868
// ================================================================================
//
// BEG: EU868 related stuff
//
enum { BAND_MILLI=0, BAND_CENTI=1, BAND_DECI=2 };
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,
// 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;
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);
}
if( !join )
LMIC.channelDrs[1] = DR_SF12 | (DR_FSK<<4);
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].avail =
LMIC.bands[BAND_CENTI].avail =
LMIC.bands[BAND_DECI ].avail = os_getTime();
}
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;
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
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
}
LMIC.channelMap = chmap;
return 1;
}
static void updateTx (ostime_t txbeg) {
u4_t freq = LMIC.channelFreq[LMIC.txChnl];
// Update global/band specific duty cycle stats
ostime_t airtime = calcAirTime(LMIC.rps, LMIC.dataLen);
// Update channel/global duty cycle stats
xref2band_t band = &LMIC.bands[freq & 0x3];
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);
}
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++;
}
}
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;
}
ccnt--;
}
mask <<= 1;
chnl++;
} while(1);
}
static void setBcnRxParams (void) {
LMIC.dataLen = 0;
LMIC.freq = LMIC.channelFreq[LMIC.bcnChnl] & ~(u4_t)3;
LMIC.rps = setIh(setNocrc(dndr2rps((dr_t)DR_BCN),1),LEN_BCN);
}
#define setRx1Params() /*LMIC.freq/rps remain unchanged*/
static void initJoinLoop (void) {
LMIC.txChnl = os_getRndU1() % 3;
LMIC.adrTxPow = 14;
setDrJoin(DRCHG_SET, DR_SF7);
initDefaultChannels(1);
ASSERT((LMIC.opmode & OP_NEXTCHNL)==0);
LMIC.txend = LMIC.bands[BAND_MILLI].avail;
}
static ostime_t nextJoinState (void) {
// Try 869.x and then 864.x with same DR
// If both fail try next lower datarate
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;
}
//
// END: EU868 related stuff
//
// ================================================================================
#elif 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;
}
static bit_t setupChannel (u1_t chidx, u4_t freq, int drs) {
if( chidx < 72 || chidx >= 72+MAX_XCHANNELS )
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.channelMap[chidx>>4] |= (1<<(chidx&0xF));
return 1;
}
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<<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();
}
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;
}
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(void) { \
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); \
}
static void initJoinLoop (void) {
LMIC.txChnl = os_getRndU1() & 0x3F;
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
//
LMIC.opmode &= ~OP_NEXTCHNL;
LMIC.txend = os_getTime();
if( LMIC.datarate != DR_SF8C ) {
LMIC.txChnl = 64+(LMIC.txChnl&7);
LMIC.datarate = 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
}
}
// 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;
}
//
// 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 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( os_rlsbf2(&d[OFF_BCN_CRC1]) != os_crc16(d, OFF_BCN_CRC1) )
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
os_copyMem((xref2u1_t)&LMIC.bcninfo.rxq, (xref2u1_t)&LMIC.rxq, SIZEOFEXPR(LMIC.rxq));
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(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));
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( LMIC.dnConf || (fct & FCT_MORE) )
LMIC.opmode |= OP_POLL;
// We heard from network
LMIC.adrChanged = 0;
LMIC.adrAckReq = LINK_CHECK_INIT;
// Process OPTS
int m = LMIC.rxq.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;
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.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);
}
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);
}
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
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_.olen = olen,
memcpy(e_.opts, 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 = LMIC.dataLen = 0;
} else {
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) {
// 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;
}
// 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( /* 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(dr);
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_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);
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:
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_BCN_NETID-1]) >> 8;
#if 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;
#endif
for( ; chidx<8; chidx++, dlen+=3 )
setupChannel(chidx, os_rlsbf4(&LMIC.frame[dlen-1]) >> 8, -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);
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)));
ASSERT((LMIC.opmode & (OP_JOINING|OP_REJOIN))!=0);
if( (LMIC.opmode & OP_REJOIN) != 0 ) {
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 processRx1Jacc (xref2osjob_t osjob) {
if( LMIC.dataLen == 0 )
LMIC.txrxFlags = 0; // nothing in 1st/2nd DN slot
processJoinAccept();
}
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 processRx2DnData (xref2osjob_t osjob) {
if( LMIC.dataLen == 0 )
LMIC.txrxFlags = 0; // nothing in 1st/2nd DN slot
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 ) {
LMIC.adrAckReq = LMIC.adrAckReq < 0 ? 0 : LMIC.adrAckReq;
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;
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(updateSeqnoUp, LMIC.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;
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)));
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_.flags = ((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);
}
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);
// 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;
}
return 0;
}
// ================================================================================
//
//
//
// ================================================================================
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 ) {
// 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.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_REJOIN|OP_LINKDEAD;
}
else {
LMIC.opmode |= 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;
}
// If we received a frame reset counter
LMIC.adrAckReq = LINK_CHECK_INIT;
LMIC.rejoinCnt = 0;
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_.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;
}
}
LMIC.bcnRxtime = LMIC.bcninfo.txtime + BCN_INTV_osticks - calcRxWindow(0,DR_BCN);
LMIC.bcnRxsyms = LMIC.rxsyms;
rev:
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!
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.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);
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)
} 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 2ns DN window of join accept
LMIC.dn2Freq = FREQ_DNW2; // ditto
#if CFG_us915
initDefaultChannels();
#endif
}
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();
}
void LMIC_startABP(u4_t netid, devaddr_t devaddr, u1_t* nwkKey, u1_t* artKey)
{
LMIC.netid = netid;
LMIC.devaddr = devaddr;
memcpy(LMIC.nwkKey, nwkKey, 16);
memcpy(LMIC.artKey, artKey, 16);
#if CFG_eu868
initDefaultChannels(0);
#endif
LMIC.opmode &= ~(OP_JOINING|OP_TRACK|OP_REJOIN|OP_TXRXPEND|OP_PINGINI) | OP_NEXTCHNL;
stateJustJoined();
//engineUpdate();
reportEvent(EV_JOINED);
}
