diff --git a/aes/ideetron/AES-128_V10.cpp b/aes/ideetron/AES-128_V10.cpp new file mode 100644 index 0000000..1d790f9 --- /dev/null +++ b/aes/ideetron/AES-128_V10.cpp @@ -0,0 +1,342 @@ +/****************************************************************************************** +#if defined(USE_IDEETRON_AES) +* Copyright 2015, 2016 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/****************************************************************************************** +* +* File: AES-128_V10.cpp +* Author: Gerben den Hartog +* Compagny: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +******************************************************************************************/ +/**************************************************************************************** +* +* Created on: 20-10-2015 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +* +* Firmware Version 1.0 +* First version +****************************************************************************************/ + +// This file was taken from +// https://github.com/Ideetron/RFM95W_Nexus/tree/master/LoRaWAN_V31 for +// use with LMIC. It was only cosmetically modified: +// - AES_Encrypt was renamed to lmic_aes_encrypt. +// - All other functions and variables were made static +// - Tabs were converted to 2 spaces +// - An #include and #if guard was added +// - S_Table is now stored in PROGMEM + +#include "../../lmic/oslmic.h" + +#if defined(USE_IDEETRON_AES) + +/* +******************************************************************************************** +* Global Variables +******************************************************************************************** +*/ + +static unsigned char State[4][4]; + +static CONST_TABLE(unsigned char, S_Table)[16][16] = { + {0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5,0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76}, + {0xCA,0x82,0xC9,0x7D,0xFA,0x59,0x47,0xF0,0xAD,0xD4,0xA2,0xAF,0x9C,0xA4,0x72,0xC0}, + {0xB7,0xFD,0x93,0x26,0x36,0x3F,0xF7,0xCC,0x34,0xA5,0xE5,0xF1,0x71,0xD8,0x31,0x15}, + {0x04,0xC7,0x23,0xC3,0x18,0x96,0x05,0x9A,0x07,0x12,0x80,0xE2,0xEB,0x27,0xB2,0x75}, + {0x09,0x83,0x2C,0x1A,0x1B,0x6E,0x5A,0xA0,0x52,0x3B,0xD6,0xB3,0x29,0xE3,0x2F,0x84}, + {0x53,0xD1,0x00,0xED,0x20,0xFC,0xB1,0x5B,0x6A,0xCB,0xBE,0x39,0x4A,0x4C,0x58,0xCF}, + {0xD0,0xEF,0xAA,0xFB,0x43,0x4D,0x33,0x85,0x45,0xF9,0x02,0x7F,0x50,0x3C,0x9F,0xA8}, + {0x51,0xA3,0x40,0x8F,0x92,0x9D,0x38,0xF5,0xBC,0xB6,0xDA,0x21,0x10,0xFF,0xF3,0xD2}, + {0xCD,0x0C,0x13,0xEC,0x5F,0x97,0x44,0x17,0xC4,0xA7,0x7E,0x3D,0x64,0x5D,0x19,0x73}, + {0x60,0x81,0x4F,0xDC,0x22,0x2A,0x90,0x88,0x46,0xEE,0xB8,0x14,0xDE,0x5E,0x0B,0xDB}, + {0xE0,0x32,0x3A,0x0A,0x49,0x06,0x24,0x5C,0xC2,0xD3,0xAC,0x62,0x91,0x95,0xE4,0x79}, + {0xE7,0xC8,0x37,0x6D,0x8D,0xD5,0x4E,0xA9,0x6C,0x56,0xF4,0xEA,0x65,0x7A,0xAE,0x08}, + {0xBA,0x78,0x25,0x2E,0x1C,0xA6,0xB4,0xC6,0xE8,0xDD,0x74,0x1F,0x4B,0xBD,0x8B,0x8A}, + {0x70,0x3E,0xB5,0x66,0x48,0x03,0xF6,0x0E,0x61,0x35,0x57,0xB9,0x86,0xC1,0x1D,0x9E}, + {0xE1,0xF8,0x98,0x11,0x69,0xD9,0x8E,0x94,0x9B,0x1E,0x87,0xE9,0xCE,0x55,0x28,0xDF}, + {0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68,0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16} +}; + +extern "C" void lmic_aes_encrypt(unsigned char *Data, unsigned char *Key); +static void AES_Add_Round_Key(unsigned char *Round_Key); +static unsigned char AES_Sub_Byte(unsigned char Byte); +static void AES_Shift_Rows(); +static void AES_Mix_Collums(); +static void AES_Calculate_Round_Key(unsigned char Round, unsigned char *Round_Key); +static void Send_State(); + +/* +***************************************************************************************** +* Description : Function for encrypting data using AES-128 +* +* Arguments : *Data Data to encrypt is a 16 byte long arry +* *Key Key to encrypt data with is a 16 byte long arry +***************************************************************************************** +*/ +void lmic_aes_encrypt(unsigned char *Data, unsigned char *Key) +{ + unsigned char i; + unsigned char Row,Collum; + unsigned char Round = 0x00; + unsigned char Round_Key[16]; + + //Copy input to State arry + for(Collum = 0; Collum < 4; Collum++) + { + for(Row = 0; Row < 4; Row++) + { + State[Row][Collum] = Data[Row + (4*Collum)]; + } + } + + //Copy key to round key + for(i = 0; i < 16; i++) + { + Round_Key[i] = Key[i]; + } + + //Add round key + AES_Add_Round_Key(Round_Key); + + //Preform 9 full rounds + for(Round = 1; Round < 10; Round++) + { + //Preform Byte substitution with S table + for(Collum = 0; Collum < 4; Collum++) + { + for(Row = 0; Row < 4; Row++) + { + State[Row][Collum] = AES_Sub_Byte(State[Row][Collum]); + } + } + + //Preform Row Shift + AES_Shift_Rows(); + + //Mix Collums + AES_Mix_Collums(); + + //Calculate new round key + AES_Calculate_Round_Key(Round,Round_Key); + + //Add round key + AES_Add_Round_Key(Round_Key); + } + + //Last round whitout mix collums + //Preform Byte substitution with S table + for(Collum = 0; Collum < 4; Collum++) + { + for(Row = 0; Row < 4; Row++) + { + State[Row][Collum] = AES_Sub_Byte(State[Row][Collum]); + } + } + + //Shift rows + AES_Shift_Rows(); + + //Calculate new round key + AES_Calculate_Round_Key(Round,Round_Key); + + //Add round Key + AES_Add_Round_Key(Round_Key); + + //Copy the State into the data array + for(Collum = 0; Collum < 4; Collum++) + { + for(Row = 0; Row < 4; Row++) + { + Data[Row + (4*Collum)] = State[Row][Collum]; + } + } + +} + +/* +***************************************************************************************** +* Description : Function that add's the round key for the current round +* +* Arguments : *Round_Key 16 byte long array holding the Round Key +***************************************************************************************** +*/ +static void AES_Add_Round_Key(unsigned char *Round_Key) +{ + unsigned char Row,Collum; + + for(Collum = 0; Collum < 4; Collum++) + { + for(Row = 0; Row < 4; Row++) + { + State[Row][Collum] = State[Row][Collum] ^ Round_Key[Row + (4*Collum)]; + } + } +} + +/* +***************************************************************************************** +* Description : Function that substitutes a byte with a byte from the S_Table +* +* Arguments : Byte The byte that will be substituted +* +* Return : The return is the found byte in the S_Table +***************************************************************************************** +*/ +static unsigned char AES_Sub_Byte(unsigned char Byte) +{ + unsigned char S_Row,S_Collum; + unsigned char S_Byte; + + //Split byte up in Row and Collum + S_Row = ((Byte >> 4) & 0x0F); + S_Collum = (Byte & 0x0F); + + //Find the correct byte in the S_Table + S_Byte = TABLE_GET_U1_TWODIM(S_Table, S_Row, S_Collum); + + return S_Byte; +} + +/* +***************************************************************************************** +* Description : Function that preforms the shift row operation described in the AES standard +***************************************************************************************** +*/ +static void AES_Shift_Rows() +{ + unsigned char Buffer; + + //Row 0 doesn't change + + //Shift Row 1 one left + //Store firt byte in buffer + Buffer = State[1][0]; + //Shift all bytes + State[1][0] = State[1][1]; + State[1][1] = State[1][2]; + State[1][2] = State[1][3]; + State[1][3] = Buffer; + + //Shift row 2 two left + Buffer = State[2][0]; + State[2][0] = State[2][2]; + State[2][2] = Buffer; + Buffer = State[2][1]; + State[2][1] = State[2][3]; + State[2][3] = Buffer; + + //Shift row 3 three left + Buffer = State[3][3]; + State[3][3] = State[3][2]; + State[3][2] = State[3][1]; + State[3][1] = State[3][0]; + State[3][0] = Buffer; +} + +/* +***************************************************************************************** +* Description : Function that preforms the Mix Collums operation described in the AES standard +***************************************************************************************** +*/ +static void AES_Mix_Collums() +{ + unsigned char Row,Collum; + unsigned char a[4], b[4]; + for(Collum = 0; Collum < 4; Collum++) + { + for(Row = 0; Row < 4; Row++) + { + a[Row] = State[Row][Collum]; + b[Row] = (State[Row][Collum] << 1); + + if((State[Row][Collum] & 0x80) == 0x80) + { + b[Row] = b[Row] ^ 0x1B; + } + } + State[0][Collum] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3]; + State[1][Collum] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3]; + State[2][Collum] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3]; + State[3][Collum] = a[0] ^ b[0] ^ a[1] ^ a[2] ^ b[3]; + } +} + +/* +***************************************************************************************** +* Description : Function that calculaties the round key for the current round +* +* Arguments : Round Number of current Round +* *Round_Key 16 byte long array holding the Round Key +***************************************************************************************** +*/ +static void AES_Calculate_Round_Key(unsigned char Round, unsigned char *Round_Key) +{ + unsigned char i,j; + unsigned char b; + unsigned char Temp[4]; + unsigned char Buffer; + unsigned char Rcon; + + //Calculate first Temp + //Copy laste byte from previous key + for(i = 0; i < 4; i++) + { + Temp[i] = Round_Key[i+12]; + } + + //Rotate Temp + Buffer = Temp[0]; + Temp[0] = Temp[1]; + Temp[1] = Temp[2]; + Temp[2] = Temp[3]; + Temp[3] = Buffer; + + //Substitute Temp + for(i = 0; i < 4; i++) + { + Temp[i] = AES_Sub_Byte(Temp[i]); + } + + //Calculate Rcon + Rcon = 0x01; + while(Round != 1) + { + b = Rcon & 0x80; + Rcon = Rcon << 1; + if(b == 0x80) + { + Rcon = Rcon ^ 0x1b; + } + Round--; + } + + //XOR Rcon + Temp[0] = Temp[0] ^ Rcon; + + //Calculate new key + for(i = 0; i < 4; i++) + { + for(j = 0; j < 4; j++) + { + Round_Key[j + (4*i)] = Round_Key[j + (4*i)] ^ Temp[j]; + Temp[j] = Round_Key[j + (4*i)]; + } + } +} + +#endif // defined(USE_IDEETRON_AES) diff --git a/lmic/aes.c b/aes/lmic.c similarity index 99% rename from lmic/aes.c rename to aes/lmic.c index d2009e9..2a5bca3 100644 --- a/lmic/aes.c +++ b/aes/lmic.c @@ -25,7 +25,9 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "oslmic.h" +#include "../lmic/oslmic.h" + +#if defined(USE_ORIGINAL_AES) #define AES_MICSUB 0x30 // internal use only @@ -381,3 +383,4 @@ u4_t os_aes (u1_t mode, xref2u1_t buf, u2_t len) { return AESAUX[0]; } +#endif diff --git a/aes/other.c b/aes/other.c new file mode 100644 index 0000000..52febdb --- /dev/null +++ b/aes/other.c @@ -0,0 +1,145 @@ +/******************************************************************************* + * Copyright (c) 2016 Matthijs Kooijman + * + * LICENSE + * + * Permission is hereby granted, free of charge, to anyone + * obtaining a copy of this document and accompanying files, + * to do whatever they want with them without any restriction, + * including, but not limited to, copying, modification and + * redistribution. + * + * NO WARRANTY OF ANY KIND IS PROVIDED. + *******************************************************************************/ + +/* + * The original LMIC AES implementation integrates raw AES encryption + * with CMAC and AES-CTR in a single piece of code. Most other AES + * implementations (only) offer raw single block AES encryption, so this + * file contains an implementation of CMAC and AES-CTR, and offers the + * same API through the os_aes() function as the original AES + * implementation. This file assumes that there is an encryption + * function available with this signature: + * + * extern "C" void lmic_aes_encrypt(u1_t *data, u1_t *key); + * + * That takes a single 16-byte buffer and encrypts it wit the given + * 16-byte key. + */ + +#include "../lmic/oslmic.h" + +#if !defined(USE_ORIGINAL_AES) + +// This should be defined elsewhere +void lmic_aes_encrypt(u1_t *data, u1_t *key); + +// global area for passing parameters (aux, key) and for storing round keys +u4_t AESAUX[16/sizeof(u4_t)]; +u4_t AESKEY[11*16/sizeof(u4_t)]; + +// Shift the given buffer left one bit +static void shift_left(xref2u1_t buf, u1_t len) { + while (len--) { + u1_t next = len ? buf[1] : 0; + + u1_t val = (*buf << 1); + if (next & 0x80) + val |= 1; + *buf++ = val; + } +} + +// Apply RFC4493 CMAC, using AESKEY as the key. If prepend_aux is true, +// AESAUX is prepended to the message. AESAUX is used as working memory +// in any case. The CMAC result is returned in AESAUX as well. +static void os_aes_cmac(xref2u1_t buf, u2_t len, u1_t prepend_aux) { + if (prepend_aux) + lmic_aes_encrypt(AESaux, AESkey); + else + memset (AESaux, 0, 16); + + while (len > 0) { + u1_t need_padding = 0; + for (u1_t i = 0; i < 16; ++i, ++buf, --len) { + if (len == 0) { + // The message is padded with 0x80 and then zeroes. + // Since zeroes are no-op for xor, we can just skip them + // and leave AESAUX unchanged for them. + AESaux[i] ^= 0x80; + need_padding = 1; + break; + } + AESaux[i] ^= *buf; + } + + if (len == 0) { + // Final block, xor with K1 or K2. K1 and K2 are calculated + // by encrypting the all-zeroes block and then applying some + // shifts and xor on that. + u1_t final_key[16]; + memset(final_key, 0, sizeof(final_key)); + lmic_aes_encrypt(final_key, AESkey); + + // Calculate K1 + u1_t msb = final_key[0] & 0x80; + shift_left(final_key, sizeof(final_key)); + if (msb) + final_key[sizeof(final_key)-1] ^= 0x87; + + // If the final block was not complete, calculate K2 from K1 + if (need_padding) { + msb = final_key[0] & 0x80; + shift_left(final_key, sizeof(final_key)); + if (msb) + final_key[sizeof(final_key)-1] ^= 0x87; + } + + // Xor with K1 or K2 + for (u1_t i = 0; i < sizeof(final_key); ++i) + AESaux[i] ^= final_key[i]; + } + + lmic_aes_encrypt(AESaux, AESkey); + } +} + +// Run AES-CTR using the key in AESKEY and using AESAUX as the +// counter block. The last byte of the counter block will be incremented +// for every block. The given buffer will be encrypted in place. +static void os_aes_ctr (xref2u1_t buf, u2_t len) { + u1_t ctr[16]; + while (len) { + // Encrypt the counter block with the selected key + memcpy(ctr, AESaux, sizeof(ctr)); + lmic_aes_encrypt(ctr, AESkey); + + // Xor the payload with the resulting ciphertext + for (u1_t i = 0; i < 16 && len > 0; i++, len--, buf++) + *buf ^= ctr[i]; + + // Increment the block index byte + AESaux[15]++; + } +} + +u4_t os_aes (u1_t mode, xref2u1_t buf, u2_t len) { + switch (mode & ~AES_MICNOAUX) { + case AES_MIC: + os_aes_cmac(buf, len, /* prepend_aux */ !(mode & AES_MICNOAUX)); + return os_rmsbf4(AESaux); + + case AES_ENC: + // TODO: Check / handle when len is not a multiple of 16 + for (u1_t i = 0; i < len; i += 16) + lmic_aes_encrypt(buf+i, AESkey); + break; + + case AES_CTR: + os_aes_ctr(buf, len); + break; + } + return 0; +} + +#endif // !defined(USE_ORIGINAL_AES) diff --git a/lmic/lmic.c b/lmic/lmic.c index 9867cce..752cf36 100644 --- a/lmic/lmic.c +++ b/lmic/lmic.c @@ -28,6 +28,10 @@ //! \file #include "lmic.h" +#if defined(DISABLE_BEACONS) && !defined(DISABLE_PING) +#error Ping needs beacon tracking +#endif + #if !defined(MINRX_SYMS) #define MINRX_SYMS 5 #endif // !defined(MINRX_SYMS) @@ -39,8 +43,6 @@ #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) @@ -73,20 +75,20 @@ static void startScan (void); #if !defined(os_rlsbf2) u2_t os_rlsbf2 (xref2cu1_t buf) { - return (u2_t)(buf[0] | (buf[1]<<8)); + return (u2_t)((u2_t)buf[0] | ((u2_t)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)); + return (u4_t)((u4_t)buf[0] | ((u4_t)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)); + return (u4_t)((u4_t)buf[3] | ((u4_t)buf[2]<<8) | ((u4_t)buf[1]<<16) | ((u4_t)buf[0]<<24)); } #endif @@ -402,6 +404,7 @@ static CONST_TABLE(ostime_t, DR2HSYM_osticks)[] = { }; +#if !defined(DISABLE_BEACONS) static ostime_t calcRxWindow (u1_t secs, dr_t dr) { ostime_t rxoff, err; if( secs==0 ) { @@ -433,8 +436,10 @@ static void calcBcnRxWindowFromMillis (u1_t ms, bit_t ini) { LMIC.bcnRxsyms = MINRX_SYMS + ms2osticksCeil(ms) / hsym; LMIC.bcnRxtime = LMIC.bcninfo.txtime + BCN_INTV_osticks - (LMIC.bcnRxsyms-PAMBL_SYMS) * hsym; } +#endif // !DISABLE_BEACONS +#if !defined(DISABLE_PING) // Setup scheduled RX window (ping/multicast slot) static void rxschedInit (xref2rxsched_t rxsched) { os_clearMem(AESkey,16); @@ -469,6 +474,7 @@ static bit_t rxschedNext (xref2rxsched_t rxsched, ostime_t cando) { rxsched->rxsyms = LMIC.rxsyms; goto again; } +#endif // !DISABLE_PING) static ostime_t rndDelay (u1_t secSpan) { @@ -521,6 +527,7 @@ static void setDrTxpow (u1_t reason, u1_t dr, s1_t pow) { } +#if !defined(DISABLE_PING) void LMIC_stopPingable (void) { LMIC.opmode &= ~(OP_PINGABLE|OP_PINGINI); } @@ -536,21 +543,19 @@ void LMIC_setPingable (u1_t intvExp) { LMIC_enableTracking(0); } +#endif // !DISABLE_PING #if defined(CFG_eu868) // ================================================================================ // // BEG: EU868 related stuff // -enum { NUM_DEFAULT_CHANNELS=6 }; -static CONST_TABLE(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, +enum { NUM_DEFAULT_CHANNELS=3 }; +static CONST_TABLE(u4_t, iniChannelFreq)[6] = { + // Join frequencies and duty cycle limit (1%) + EU868_F1|BAND_CENTI, EU868_F2|BAND_CENTI, EU868_F3|BAND_CENTI, // 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) { @@ -558,16 +563,12 @@ static void initDefaultChannels (bit_t join) { 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.channelMap = 0x07; + u1_t su = join ? 0 : 3; + for( u1_t fu=0; fu<3; fu++,su++ ) { LMIC.channelFreq[fu] = TABLE_GET_U4(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; @@ -600,7 +601,7 @@ bit_t LMIC_setupChannel (u1_t chidx, u4_t freq, u2_t drmap, s1_t band) { if( band == -1 ) { if( freq >= 869400000 && freq <= 869650000 ) freq |= BAND_DECI; // 10% 27dBm - else if( (freq >= 868000000 && freq <= 868600000) || + else if( (freq >= 865000000 && freq <= 868600000) || (freq >= 869700000 && freq <= 870000000) ) freq |= BAND_CENTI; // 1% 14dBm else @@ -683,19 +684,22 @@ static ostime_t nextTx (ostime_t now) { } +#if !defined(DISABLE_BEACONS) 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); } +#endif // !DISABLE_BEACONS #define setRx1Params() /*LMIC.freq/rps remain unchanged*/ +#if !defined(DISABLE_JOIN) static void initJoinLoop (void) { #if CFG_TxContinuousMode LMIC.txChnl = 0; #else - LMIC.txChnl = os_getRndU1() % 6; + LMIC.txChnl = os_getRndU1() % 3; #endif LMIC.adrTxPow = 14; setDrJoin(DRCHG_SET, DR_SF7); @@ -710,7 +714,7 @@ static ostime_t nextJoinState (void) { // Try 869.x and then 864.x with same DR // If both fail try next lower datarate - if( ++LMIC.txChnl == 6 ) + if( ++LMIC.txChnl == 3 ) LMIC.txChnl = 0; if( (++LMIC.txCnt & 1) == 0 ) { // Lower DR every 2nd try (having tried 868.x and 864.x with the same DR) @@ -736,6 +740,7 @@ static ostime_t nextJoinState (void) { // 1 - triggers EV_JOIN_FAILED event return failed; } +#endif // !DISABLE_JOIN // // END: EU868 related stuff @@ -773,7 +778,36 @@ bit_t LMIC_setupChannel (u1_t chidx, u4_t freq, u2_t drmap, s1_t band) { void LMIC_disableChannel (u1_t channel) { if( channel < 72+MAX_XCHANNELS ) - LMIC.channelMap[channel/4] &= ~(1<<(channel&0xF)); + LMIC.channelMap[channel>>4] &= ~(1<<(channel&0xF)); +} + +void LMIC_enableChannel (u1_t channel) { + if( channel < 72+MAX_XCHANNELS ) + LMIC.channelMap[channel>>4] |= (1<<(channel&0xF)); +} + +void LMIC_enableSubBand (u1_t band) { + ASSERT(band < 8); + u1_t start = band * 8; + u1_t end = start + 8; + for (int channel=start; channel < end; ++channel ) + LMIC_enableChannel(channel); +} +void LMIC_disableSubBand (u1_t band) { + ASSERT(band < 8); + u1_t start = band * 8; + u1_t end = start + 8; + for (int channel=start; channel < end; ++channel ) + LMIC_disableChannel(channel); +} +void LMIC_selectSubBand (u1_t band) { + ASSERT(band < 8); + for (int b=0; b<8; ++b) { + if (band==b) + LMIC_enableSubBand(b); + else + LMIC_disableSubBand(b); + } } static u1_t mapChannels (u1_t chpage, u2_t chmap) { @@ -837,11 +871,13 @@ static void _nextTx (void) { // No feasible channel found! Keep old one. } +#if !defined(DISABLE_BEACONS) 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); } +#endif // !DISABLE_BEACONS #define setRx1Params() { \ LMIC.freq = US915_500kHz_DNFBASE + (LMIC.txChnl & 0x7) * US915_500kHz_DNFSTEP; \ @@ -852,6 +888,7 @@ static void setBcnRxParams (void) { LMIC.rps = dndr2rps(LMIC.dndr); \ } +#if !defined(DISABLE_JOIN) static void initJoinLoop (void) { LMIC.chRnd = 0; LMIC.txChnl = 0; @@ -890,6 +927,7 @@ static ostime_t nextJoinState (void) { // 1 - triggers EV_JOIN_FAILED event return failed; } +#endif // !DISABLE_JOIN // // END: US915 related stuff @@ -917,7 +955,9 @@ static void reportEvent (ev_t ev) { static void runReset (xref2osjob_t osjob) { // Disable session LMIC_reset(); +#if !defined(DISABLE_JOIN) LMIC_startJoining(); +#endif // !DISABLE_JOIN reportEvent(EV_RESET); } @@ -925,15 +965,30 @@ 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; +#if !defined(DISABLE_MCMD_SNCH_REQ) + LMIC.snchAns = 0; +#endif +#if !defined(DISABLE_MCMD_DN2P_SET) + LMIC.dn2Ans = 0; +#endif + LMIC.moreData = 0; +#if !defined(DISABLE_MCMD_DCAP_REQ) + LMIC.dutyCapAns = 0; +#endif +#if !defined(DISABLE_MCMD_PING_SET) && !defined(DISABLE_PING) LMIC.pingSetAns = 0; +#endif LMIC.upRepeat = 0; LMIC.adrAckReq = LINK_CHECK_INIT; LMIC.dn2Dr = DR_DNW2; LMIC.dn2Freq = FREQ_DNW2; +#if !defined(DISABLE_BEACONS) LMIC.bcnChnl = CHNL_BCN; +#endif +#if !defined(DISABLE_PING) LMIC.ping.freq = FREQ_PING; LMIC.ping.dr = DR_PING; +#endif } @@ -941,6 +996,7 @@ static void stateJustJoined (void) { // Decoding frames +#if !defined(DISABLE_BEACONS) // 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 @@ -976,6 +1032,7 @@ static int decodeBeacon (void) { LMIC.bcninfo.flags |= BCN_FULL; return 2; } +#endif // !DISABLE_BEACONS static bit_t decodeFrame (void) { @@ -983,6 +1040,7 @@ static bit_t decodeFrame (void) { u1_t hdr = d[0]; u1_t ftype = hdr & HDR_FTYPE; int dlen = LMIC.dataLen; + const char *window = (LMIC.txrxFlags & TXRX_DNW1) ? "RX1" : ((LMIC.txrxFlags & TXRX_DNW2) ? "RX2" : "Other"); if( dlen < OFF_DAT_OPTS+4 || (hdr & HDR_MAJOR) != HDR_MAJOR_V1 || (ftype != HDR_FTYPE_DADN && ftype != HDR_FTYPE_DCDN) ) { @@ -992,6 +1050,9 @@ static bit_t decodeFrame (void) { e_.info = dlen < 4 ? 0 : os_rlsbf4(&d[dlen-4]), e_.info2 = hdr + (dlen<<8))); norx: +#if LMIC_DEBUG_LEVEL > 0 + printf("%lu: Invalid downlink, window=%s\n", os_getTime(), window); +#endif LMIC.dataLen = 0; return 0; } @@ -1086,6 +1147,10 @@ static bit_t decodeFrame (void) { case MCMD_LCHK_ANS: { //int gwmargin = opts[oidx+1]; //int ngws = opts[oidx+2]; + LMIC.LinkCheckMargin = opts[oidx+1]; + LMIC.LinkCheckGwCnt = opts[oidx+2]; + LMIC.LinkCheckReq = 0; + LMIC.LinkCheckAns = 1; oidx += 3; continue; } @@ -1122,9 +1187,9 @@ static bit_t decodeFrame (void) { continue; } case MCMD_DN2P_SET: { +#if !defined(DISABLE_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; @@ -1136,11 +1201,13 @@ static bit_t decodeFrame (void) { DO_DEVDB(LMIC.dn2Dr,dn2Dr); DO_DEVDB(LMIC.dn2Freq,dn2Freq); } +#endif // !DISABLE_MCMD_DN2P_SET + oidx += 5; continue; } case MCMD_DCAP_REQ: { +#if !defined(DISABLE_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 @@ -1148,21 +1215,25 @@ static bit_t decodeFrame (void) { LMIC.globalDutyAvail = os_getTime(); DO_DEVDB(cap,dutyCap); LMIC.dutyCapAns = 1; +#endif // !DISABLE_MCMD_DCAP_REQ + oidx += 2; continue; } case MCMD_SNCH_REQ: { +#if !defined(DISABLE_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; +#endif // !DISABLE_MCMD_SNCH_REQ oidx += 6; continue; } case MCMD_PING_SET: { +#if !defined(DISABLE_MCMD_PING_SET) && !defined(DISABLE_PING) u4_t freq = convFreq(&opts[oidx+1]); - oidx += 4; u1_t flags = 0x80; if( freq != 0 ) { flags |= MCMD_PING_ANS_FQACK; @@ -1172,9 +1243,12 @@ static bit_t decodeFrame (void) { DO_DEVDB(LMIC.ping.dr, pingDr); } LMIC.pingSetAns = flags; +#endif // !DISABLE_MCMD_PING_SET && !DISABLE_PING + oidx += 4; continue; } case MCMD_BCNI_ANS: { +#if !defined(DISABLE_MCMD_BCNI_ANS) && !defined(DISABLE_BEACONS) // Ignore if tracking already enabled if( (LMIC.opmode & OP_TRACK) == 0 ) { LMIC.bcnChnl = opts[oidx+3]; @@ -1198,6 +1272,7 @@ static bit_t decodeFrame (void) { - LMIC.bcnRxtime) << 8)), e_.time = MAIN::CDEV->ostime2ustime(LMIC.bcninfo.txtime + BCN_INTV_osticks))); } +#endif // !DISABLE_MCMD_BCNI_ANS && !DISABLE_BEACONS oidx += 4; continue; } @@ -1262,6 +1337,9 @@ static bit_t decodeFrame (void) { LMIC.dataBeg = poff; LMIC.dataLen = pend-poff; } +#if LMIC_DEBUG_LEVEL > 0 + printf("%lu: Received downlink, window=%s, port=%d, ack=%d\n", os_getTime(), window, port, ackup); +#endif return 1; } @@ -1279,9 +1357,36 @@ static void setupRx2 (void) { } -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); +static void schedRx12 (ostime_t delay, osjobcb_t func, u1_t dr) { + ostime_t hsym = dr2hsym(dr); + + LMIC.rxsyms = MINRX_SYMS; + + // If a clock error is specified, compensate for it by extending the + // receive window + if (LMIC.clockError != 0) { + // Calculate how much the clock will drift maximally after delay has + // passed. This indicates the amount of time we can be early + // _or_ late. + ostime_t drift = (int64_t)delay * LMIC.clockError / MAX_CLOCK_ERROR; + + // Increase the receive window by twice the maximum drift (to + // compensate for a slow or a fast clock). + // decrease the rxtime to compensate for. Note that hsym is a + // *half* symbol time, so the factor 2 is hidden. First check if + // this would overflow (which can happen if the drift is very + // high, or the symbol time is low at high datarates). + if ((255 - LMIC.rxsyms) * hsym < drift) + LMIC.rxsyms = 255; + else + LMIC.rxsyms += drift / hsym; + + } + + // Center the receive window on the center of the expected preamble + // (again note that hsym is half a sumbol time, so no /2 needed) + LMIC.rxtime = LMIC.txend + delay + PAMBL_SYMS * hsym - LMIC.rxsyms * hsym; + os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func); } @@ -1297,10 +1402,13 @@ static void setupRx1 (osjobcb_t func) { // 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 !defined(DISABLE_PING) 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; } +#endif // !DISABLE_PING + // Change RX frequency / rps (US only) before we increment txChnl setRx1Params(); // LMIC.rxsyms carries the TX datarate (can be != LMIC.datarate [confirm retries etc.]) @@ -1310,20 +1418,20 @@ static void txDone (ostime_t delay, osjobcb_t func) { if( /* TX datarate */LMIC.rxsyms == DR_FSK ) { LMIC.rxtime = LMIC.txend + delay - PRERX_FSK*us2osticksRound(160); LMIC.rxsyms = RXLEN_FSK; + os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func); } else #endif { - LMIC.rxtime = LMIC.txend + delay + (PAMBL_SYMS-MINRX_SYMS)*dr2hsym(LMIC.dndr); - LMIC.rxsyms = MINRX_SYMS; + schedRx12(delay, func, LMIC.dndr); } - os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func); } // ======================================== Join frames +#if !defined(DISABLE_JOIN) static void onJoinFailed (xref2osjob_t osjob) { // Notify app - must call LMIC_reset() to stop joining // otherwise join procedure continues. @@ -1396,8 +1504,12 @@ static bit_t processJoinAccept (void) { dlen = OFF_CFLIST; for( u1_t chidx=3; chidx<8; chidx++, dlen+=3 ) { u4_t freq = convFreq(&LMIC.frame[dlen]); - if( freq ) + if( freq ) { LMIC_setupChannel(chidx, freq, 0, -1); +#if LMIC_DEBUG_LEVEL > 1 + printf("%lu: Setup channel, idx=%d, freq=%lu\n", os_getTime(), chidx, (unsigned long)freq); +#endif + } } } @@ -1424,7 +1536,11 @@ static bit_t processJoinAccept (void) { LMIC.datarate = lowerDR(LMIC.datarate, LMIC.rejoinCnt); } LMIC.opmode &= ~(OP_JOINING|OP_TRACK|OP_REJOIN|OP_TXRXPEND|OP_PINGINI) | OP_NEXTCHNL; + LMIC.txCnt = 0; stateJustJoined(); + LMIC.dn2Dr = LMIC.frame[OFF_JA_DLSET] & 0x0F; + LMIC.rxDelay = LMIC.frame[OFF_JA_RXDLY]; + if (LMIC.rxDelay == 0) LMIC.rxDelay = 1; reportEvent(EV_JOINED); return 1; } @@ -1445,7 +1561,7 @@ static void setupRx2Jacc (xref2osjob_t osjob) { static void processRx1Jacc (xref2osjob_t osjob) { if( LMIC.dataLen == 0 || !processJoinAccept() ) - schedRx2(DELAY_JACC2_osticks, FUNC_ADDR(setupRx2Jacc)); + schedRx12(DELAY_JACC2_osticks, FUNC_ADDR(setupRx2Jacc), LMIC.dn2Dr); } @@ -1458,6 +1574,8 @@ static void jreqDone (xref2osjob_t osjob) { txDone(DELAY_JACC1_osticks, FUNC_ADDR(setupRx1Jacc)); } +#endif // !DISABLE_JOIN + // ======================================== Data frames // Fwd decl. @@ -1489,7 +1607,7 @@ static void setupRx2DnData (xref2osjob_t osjob) { static void processRx1DnData (xref2osjob_t osjob) { if( LMIC.dataLen == 0 || !processDnData() ) - schedRx2(DELAY_DNW2_osticks, FUNC_ADDR(setupRx2DnData)); + schedRx12(sec2osticks(LMIC.rxDelay +(int)DELAY_EXTDNW2), FUNC_ADDR(setupRx2DnData), LMIC.dn2Dr); } @@ -1499,7 +1617,7 @@ static void setupRx1DnData (xref2osjob_t osjob) { static void updataDone (xref2osjob_t osjob) { - txDone(DELAY_DNW1_osticks, FUNC_ADDR(setupRx1DnData)); + txDone(sec2osticks(LMIC.rxDelay), FUNC_ADDR(setupRx1DnData)); } // ======================================== @@ -1512,27 +1630,33 @@ static void buildDataFrame (void) { // Piggyback MAC options // Prioritize by importance int end = OFF_DAT_OPTS; +#if !defined(DISABLE_PING) 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; } +#endif // !DISABLE_PING +#if !defined(DISABLE_MCMD_DCAP_REQ) if( LMIC.dutyCapAns ) { LMIC.frame[end] = MCMD_DCAP_ANS; end += 1; LMIC.dutyCapAns = 0; } +#endif // !DISABLE_MCMD_DCAP_REQ +#if !defined(DISABLE_MCMD_DN2P_SET) 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; } +#endif // !DISABLE_MCMD_DN2P_SET 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(); + LMIC.frame[end+1] = os_getBattLevel(); + LMIC.frame[end+2] = LMIC.margin; end += 3; LMIC.devsAns = 0; } @@ -1542,27 +1666,38 @@ static void buildDataFrame (void) { end += 2; LMIC.ladrAns = 0; } + if( LMIC.LinkCheckReq ) { //Link Check Request + LMIC.frame[end+0] = MCMD_LCHK_REQ; + end += 1; + LMIC.LinkCheckAns = 0; + } +#if !defined(DISABLE_BEACONS) if( LMIC.bcninfoTries > 0 ) { LMIC.frame[end] = MCMD_BCNI_REQ; end += 1; } +#endif // !DISABLE_BEACONS if( LMIC.adrChanged ) { if( LMIC.adrAckReq < 0 ) LMIC.adrAckReq = 0; LMIC.adrChanged = 0; } +#if !defined(DISABLE_MCMD_PING_SET) && !defined(DISABLE_PING) 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; } +#endif // !DISABLE_MCMD_PING_SET && !DISABLE_PING +#if !defined(DISABLE_MCMD_SNCH_REQ) 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; } +#endif // !DISABLE_MCMD_SNCH_REQ ASSERT(end <= OFF_DAT_OPTS+16); u1_t flen = end + (txdata ? 5+dlen : 4); @@ -1622,6 +1757,7 @@ static void buildDataFrame (void) { } +#if !defined(DISABLE_BEACONS) // 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. @@ -1685,6 +1821,7 @@ void LMIC_disableTracking (void) { LMIC.bcninfoTries = 0; engineUpdate(); } +#endif // !DISABLE_BEACONS @@ -1718,6 +1855,7 @@ void LMIC_disableTracking (void) { // // ================================================================================ +#if !defined(DISABLE_JOIN) 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. @@ -1755,7 +1893,7 @@ bit_t LMIC_startJoining (void) { // Cancel scanning LMIC.opmode &= ~(OP_SCAN|OP_REJOIN|OP_LINKDEAD|OP_NEXTCHNL); // Setup state - LMIC.rejoinCnt = LMIC.txCnt = LMIC.pendTxConf = 0; + LMIC.rejoinCnt = LMIC.txCnt = 0; initJoinLoop(); LMIC.opmode |= OP_JOINING; // reportEvent will call engineUpdate which then starts sending JOIN REQUESTS @@ -1764,6 +1902,7 @@ bit_t LMIC_startJoining (void) { } return 0; // already joined } +#endif // !DISABLE_JOIN // ================================================================================ @@ -1772,6 +1911,7 @@ bit_t LMIC_startJoining (void) { // // ================================================================================ +#if !defined(DISABLE_PING) static void processPingRx (xref2osjob_t osjob) { if( LMIC.dataLen != 0 ) { LMIC.txrxFlags = TXRX_PING; @@ -1783,6 +1923,7 @@ static void processPingRx (xref2osjob_t osjob) { // Pick next ping slot engineUpdate(); } +#endif // !DISABLE_PING static bit_t processDnData (void) { @@ -1828,6 +1969,7 @@ static bit_t processDnData (void) { LMIC.opmode |= OP_REJOIN|OP_LINKDEAD; reportEvent(EV_LINK_DEAD); } +#if !defined(DISABLE_BEACONS) // 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 ) { @@ -1838,6 +1980,7 @@ static bit_t processDnData (void) { startScan(); // NWK did not answer - try scan } } +#endif // !DISABLE_BEACONS return 1; } if( !decodeFrame() ) { @@ -1849,6 +1992,7 @@ static bit_t processDnData (void) { } +#if !defined(DISABLE_BEACONS) static void processBeacon (xref2osjob_t osjob) { ostime_t lasttx = LMIC.bcninfo.txtime; // save here - decodeBeacon might overwrite u1_t flags = LMIC.bcninfo.flags; @@ -1904,8 +2048,10 @@ static void processBeacon (xref2osjob_t osjob) { #if CFG_us915 LMIC.bcnChnl = (LMIC.bcnChnl+1) & 7; #endif +#if !defined(DISABLE_PING) if( (LMIC.opmode & OP_PINGINI) != 0 ) rxschedInit(&LMIC.ping); // note: reuses LMIC.frame buffer! +#endif // !DISABLE_PING reportEvent(ev); } @@ -1914,34 +2060,44 @@ static void startRxBcn (xref2osjob_t osjob) { LMIC.osjob.func = FUNC_ADDR(processBeacon); os_radio(RADIO_RX); } +#endif // !DISABLE_BEACONS +#if !defined(DISABLE_PING) static void startRxPing (xref2osjob_t osjob) { LMIC.osjob.func = FUNC_ADDR(processPingRx); os_radio(RADIO_RX); } +#endif // !DISABLE_PING // Decide what to do next for the MAC layer of a device static void engineUpdate (void) { +#if LMIC_DEBUG_LEVEL > 0 + printf("%lu: engineUpdate, opmode=0x%x\n", os_getTime(), LMIC.opmode); +#endif // Check for ongoing state: scan or TX/RX transaction if( (LMIC.opmode & (OP_SCAN|OP_TXRXPEND|OP_SHUTDOWN)) != 0 ) return; +#if !defined(DISABLE_JOIN) if( LMIC.devaddr == 0 && (LMIC.opmode & OP_JOINING) == 0 ) { LMIC_startJoining(); return; } +#endif // !DISABLE_JOIN ostime_t now = os_getTime(); ostime_t rxtime = 0; ostime_t txbeg = 0; +#if !defined(DISABLE_BEACONS) if( (LMIC.opmode & OP_TRACK) != 0 ) { // We are tracking a beacon ASSERT( now + RX_RAMPUP - LMIC.bcnRxtime <= 0 ); rxtime = LMIC.bcnRxtime - RX_RAMPUP; } +#endif // !DISABLE_BEACONS if( (LMIC.opmode & (OP_JOINING|OP_REJOIN|OP_TXDATA|OP_POLL)) != 0 ) { // Need to TX some data... @@ -1957,6 +2113,7 @@ static void engineUpdate (void) { // Delayed TX or waiting for duty cycle? if( (LMIC.globalDutyRate != 0 || (LMIC.opmode & OP_RNDTX) != 0) && (txbeg - LMIC.globalDutyAvail) < 0 ) txbeg = LMIC.globalDutyAvail; +#if !defined(DISABLE_BEACONS) // If we're tracking a beacon... // then make sure TX-RX transaction is complete before beacon if( (LMIC.opmode & OP_TRACK) != 0 && @@ -1967,11 +2124,13 @@ static void engineUpdate (void) { txbeg = 0; goto checkrx; } +#endif // !DISABLE_BEACONS // 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 !defined(DISABLE_JOIN) if( jacc ) { u1_t ftype; if( (LMIC.opmode & OP_REJOIN) != 0 ) { @@ -1982,7 +2141,9 @@ static void engineUpdate (void) { } buildJoinRequest(ftype); LMIC.osjob.func = FUNC_ADDR(jreqDone); - } else { + } else +#endif // !DISABLE_JOIN + { if( LMIC.seqnoDn >= 0xFFFFFF80 ) { // Imminent roll over - proactively reset MAC EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_ROLL_OVER, @@ -2027,8 +2188,10 @@ static void engineUpdate (void) { return; } +#if !defined(DISABLE_BEACONS) // Are we pingable? checkrx: +#if !defined(DISABLE_PING) if( (LMIC.opmode & OP_PINGINI) != 0 ) { // One more RX slot in this beacon period? if( rxschedNext(&LMIC.ping, now+RX_RAMPUP) ) { @@ -2045,6 +2208,7 @@ static void engineUpdate (void) { } // no - just wait for the beacon } +#endif // !DISABLE_PING if( txbeg != 0 && (txbeg - rxtime) < 0 ) goto txdelay; @@ -2059,6 +2223,7 @@ static void engineUpdate (void) { } os_setTimedCallback(&LMIC.osjob, rxtime, FUNC_ADDR(startRxBcn)); return; +#endif // !DISABLE_BEACONS txdelay: EV(devCond, INFO, (e_.reason = EV::devCond_t::TX_DELAY, @@ -2102,9 +2267,12 @@ void LMIC_reset (void) { LMIC.adrEnabled = FCT_ADREN; LMIC.dn2Dr = DR_DNW2; // we need this for 2nd DN window of join accept LMIC.dn2Freq = FREQ_DNW2; // ditto + LMIC.rxDelay = DELAY_DNW1; +#if !defined(DISABLE_PING) LMIC.ping.freq = FREQ_PING; // defaults for ping LMIC.ping.dr = DR_PING; // ditto LMIC.ping.intvExp = 0xFF; +#endif // !DISABLE_PING #if defined(CFG_us915) initDefaultChannels(); #endif @@ -2112,9 +2280,11 @@ void LMIC_reset (void) { DO_DEVDB(LMIC.devNonce, devNonce); DO_DEVDB(LMIC.dn2Dr, dn2Dr); DO_DEVDB(LMIC.dn2Freq, dn2Freq); +#if !defined(DISABLE_PING) DO_DEVDB(LMIC.ping.freq, pingFreq); DO_DEVDB(LMIC.ping.dr, pingDr); DO_DEVDB(LMIC.ping.intvExp, pingIntvExp); +#endif // !DISABLE_PING } @@ -2219,4 +2389,9 @@ void LMIC_setLinkCheckMode (bit_t enabled) { LMIC.adrAckReq = enabled ? LINK_CHECK_INIT : LINK_CHECK_OFF; } - +// Sets the max clock error to compensate for (defaults to 0, which +// allows for +/- 640 at SF7BW250). MAX_CLOCK_ERROR represents +/-100%, +// so e.g. for a +/-1% error you would pass MAX_CLOCK_ERROR * 1 / 100. +void LMIC_setClockError(u2_t error) { + LMIC.clockError = error; +} diff --git a/lmic/lmic.h b/lmic/lmic.h index 75757ad..87d7a44 100644 --- a/lmic/lmic.h +++ b/lmic/lmic.h @@ -89,6 +89,7 @@ enum { DRCHG_SET, DRCHG_NOJACC, DRCHG_NOACK, DRCHG_NOADRACK, DRCHG_NWKCMD }; enum { KEEP_TXPOW = -128 }; +#if !defined(DISABLE_PING) //! \internal struct rxsched_t { u1_t dr; @@ -100,8 +101,10 @@ struct rxsched_t { u4_t freq; }; TYPEDEF_xref2rxsched_t; //!< \internal +#endif // !DISABLE_PING +#if !defined(DISABLE_BEACONS) //! Parsing and tracking states of beacons. enum { BCN_NONE = 0x00, //!< No beacon received BCN_PARTIAL = 0x01, //!< Only first (common) part could be decoded (info,lat,lon invalid/previous) @@ -120,6 +123,7 @@ struct bcninfo_t { s4_t lat; //!< Lat field of last beacon (valid only if BCN_FULL set) s4_t lon; //!< Lon field of last beacon (valid only if BCN_FULL set) }; +#endif // !DISABLE_BEACONS // purpose of receive window - lmic_t.rxState enum { RADIO_RST=0, RADIO_TX=1, RADIO_RX=2, RADIO_RXON=3 }; @@ -159,6 +163,10 @@ enum _ev_t { EV_SCAN_TIMEOUT=1, EV_BEACON_FOUND, EV_TXSTART }; typedef enum _ev_t ev_t; +enum { + // This value represents 100% error in LMIC.clockError + MAX_CLOCK_ERROR = 65536, +}; struct lmic_t { // Radio settings TX/RX (also accessed by HAL) @@ -197,9 +205,14 @@ struct lmic_t { u1_t datarate; // current data rate u1_t errcr; // error coding rate (used for TX only) u1_t rejoinCnt; // adjustment for rejoin datarate +#if !defined(DISABLE_BEACONS) s2_t drift; // last measured drift s2_t lastDriftDiff; s2_t maxDriftDiff; +#endif + + u2_t clockError; // Inaccuracy in the clock. CLOCK_ERROR_MAX + // represents +/-100% error u1_t pendTxPort; u1_t pendTxConf; // confirmed data @@ -217,23 +230,41 @@ struct lmic_t { s1_t adrAckReq; // counter until we reset data rate (0=off) u1_t adrChanged; + u1_t rxDelay; // Rx delay after TX + u1_t margin; bit_t ladrAns; // link adr adapt answer pending bit_t devsAns; // device status answer pending + u1_t LinkCheckReq; // send LinkCheckReq command + u1_t LinkCheckAns; // LinkCheckAns received + u1_t LinkCheckMargin; // link margin in dB of the last successfully received LinkCheckReq + u1_t LinkCheckGwCnt; // number of gateways that successfully received the last LinkCheckReq u1_t adrEnabled; u1_t moreData; // NWK has more data pending +#if !defined(DISABLE_MCMD_DCAP_REQ) bit_t dutyCapAns; // have to ACK duty cycle settings +#endif +#if !defined(DISABLE_MCMD_SNCH_REQ) u1_t snchAns; // answer set new channel +#endif // 2nd RX window (after up stream) u1_t dn2Dr; u4_t dn2Freq; +#if !defined(DISABLE_MCMD_DN2P_SET) u1_t dn2Ans; // 0=no answer pend, 0x80+ACKs +#endif // Class B state +#if !defined(DISABLE_BEACONS) u1_t missedBcns; // unable to track last N beacons u1_t bcninfoTries; // how often to try (scan mode only) +#endif +#if !defined(DISABLE_MCMD_PING_SET) && !defined(DISABLE_PING) u1_t pingSetAns; // answer set cmd and ACK bits +#endif +#if !defined(DISABLE_PING) rxsched_t ping; // pingable setup +#endif // Public part of MAC state u1_t txCnt; @@ -242,10 +273,12 @@ struct lmic_t { u1_t dataLen; // 0 no data or zero length data, >0 byte count of data u1_t frame[MAX_LEN_FRAME]; +#if !defined(DISABLE_BEACONS) u1_t bcnChnl; u1_t bcnRxsyms; // ostime_t bcnRxtime; bcninfo_t bcninfo; // Last received beacon info +#endif u1_t noRXIQinversion; }; @@ -261,10 +294,18 @@ bit_t LMIC_setupBand (u1_t bandidx, s1_t txpow, u2_t txcap); #endif bit_t LMIC_setupChannel (u1_t channel, u4_t freq, u2_t drmap, s1_t band); void LMIC_disableChannel (u1_t channel); +#if defined(CFG_us915) +void LMIC_enableChannel (u1_t channel); +void LMIC_enableSubBand (u1_t band); +void LMIC_disableSubBand (u1_t band); +void LMIC_selectSubBand (u1_t band); +#endif void LMIC_setDrTxpow (dr_t dr, s1_t txpow); // set default/start DR/txpow void LMIC_setAdrMode (bit_t enabled); // set ADR mode (if mobile turn off) +#if !defined(DISABLE_JOIN) bit_t LMIC_startJoining (void); +#endif void LMIC_shutdown (void); void LMIC_init (void); @@ -274,15 +315,22 @@ void LMIC_setTxData (void); int LMIC_setTxData2 (u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed); void LMIC_sendAlive (void); +#if !defined(DISABLE_BEACONS) bit_t LMIC_enableTracking (u1_t tryBcnInfo); void LMIC_disableTracking (void); +#endif +#if !defined(DISABLE_PING) void LMIC_stopPingable (void); void LMIC_setPingable (u1_t intvExp); +#endif +#if !defined(DISABLE_JOIN) void LMIC_tryRejoin (void); +#endif void LMIC_setSession (u4_t netid, devaddr_t devaddr, xref2u1_t nwkKey, xref2u1_t artKey); void LMIC_setLinkCheckMode (bit_t enabled); +void LMIC_setClockError(u2_t error); // Declare onEvent() function, to make sure any definition will have the // C conventions, even when in a C++ file. diff --git a/lmic/oslmic.h b/lmic/oslmic.h index 1cd9e57..f4c5b80 100644 --- a/lmic/oslmic.h +++ b/lmic/oslmic.h @@ -34,7 +34,14 @@ // You should not, however, change the lmic.[hc] #include +#ifdef ARDUINO +// When using the makefile, the target directory is put into the include +// path, so we can include target-config.h directly. When using Arduino, +// this is not the case, so we have to specify the path explicitly. +#include "../hal/target-config.h" +#else #include +#endif #ifdef __cplusplus extern "C"{ @@ -50,8 +57,6 @@ typedef uint16_t u2_t; typedef int16_t s2_t; typedef uint32_t u4_t; typedef int32_t s4_t; -typedef uint64_t u8_t; -typedef int64_t s8_t; typedef unsigned int uint; typedef const char* str_t; @@ -121,16 +126,16 @@ void os_runloop_once (void); typedef s4_t ostime_t; #if !HAS_ostick_conv -#define us2osticks(us) ((ostime_t)( ((s8_t)(us) * OSTICKS_PER_SEC) / 1000000)) -#define ms2osticks(ms) ((ostime_t)( ((s8_t)(ms) * OSTICKS_PER_SEC) / 1000)) -#define sec2osticks(sec) ((ostime_t)( (s8_t)(sec) * OSTICKS_PER_SEC)) -#define osticks2ms(os) ((s4_t)(((os)*(s8_t)1000 ) / OSTICKS_PER_SEC)) -#define osticks2us(os) ((s4_t)(((os)*(s8_t)1000000 ) / OSTICKS_PER_SEC)) +#define us2osticks(us) ((ostime_t)( ((int64_t)(us) * OSTICKS_PER_SEC) / 1000000)) +#define ms2osticks(ms) ((ostime_t)( ((int64_t)(ms) * OSTICKS_PER_SEC) / 1000)) +#define sec2osticks(sec) ((ostime_t)( (int64_t)(sec) * OSTICKS_PER_SEC)) +#define osticks2ms(os) ((s4_t)(((os)*(int64_t)1000 ) / OSTICKS_PER_SEC)) +#define osticks2us(os) ((s4_t)(((os)*(int64_t)1000000 ) / OSTICKS_PER_SEC)) // Special versions -#define us2osticksCeil(us) ((ostime_t)( ((s8_t)(us) * OSTICKS_PER_SEC + 999999) / 1000000)) -#define us2osticksRound(us) ((ostime_t)( ((s8_t)(us) * OSTICKS_PER_SEC + 500000) / 1000000)) -#define ms2osticksCeil(ms) ((ostime_t)( ((s8_t)(ms) * OSTICKS_PER_SEC + 999) / 1000)) -#define ms2osticksRound(ms) ((ostime_t)( ((s8_t)(ms) * OSTICKS_PER_SEC + 500) / 1000)) +#define us2osticksCeil(us) ((ostime_t)( ((int64_t)(us) * OSTICKS_PER_SEC + 999999) / 1000000)) +#define us2osticksRound(us) ((ostime_t)( ((int64_t)(us) * OSTICKS_PER_SEC + 500000) / 1000000)) +#define ms2osticksCeil(ms) ((ostime_t)( ((int64_t)(ms) * OSTICKS_PER_SEC + 999) / 1000)) +#define ms2osticksRound(ms) ((ostime_t)( ((int64_t)(ms) * OSTICKS_PER_SEC + 500) / 1000)) #endif diff --git a/lmic/radio.c b/lmic/radio.c index 8ef7d37..7ac25e6 100644 --- a/lmic/radio.c +++ b/lmic/radio.c @@ -411,7 +411,7 @@ static void configLoraModem () { static void configChannel () { // set frequency: FQ = (FRF * 32 Mhz) / (2 ^ 19) - u8_t frf = ((u8_t)LMIC.freq << 19) / 32000000; + uint64_t frf = ((uint64_t)LMIC.freq << 19) / 32000000; writeReg(RegFrfMsb, (u1_t)(frf>>16)); writeReg(RegFrfMid, (u1_t)(frf>> 8)); writeReg(RegFrfLsb, (u1_t)(frf>> 0)); @@ -527,6 +527,18 @@ static void txlora () { // now we actually start the transmission opmode(OPMODE_TX); + +#if LMIC_DEBUG_LEVEL > 0 + u1_t sf = getSf(LMIC.rps) + 6; // 1 == SF7 + u1_t bw = getBw(LMIC.rps); + u1_t cr = getCr(LMIC.rps); + printf("%lu: TXMODE, freq=%lu, len=%d, SF=%d, BW=%d, CR=4/%d, IH=%d\n", + os_getTime(), LMIC.freq, LMIC.dataLen, sf, + bw == BW125 ? 125 : (bw == BW250 ? 250 : 500), + cr == CR_4_5 ? 5 : (cr == CR_4_6 ? 6 : (cr == CR_4_7 ? 7 : 8)), + getIh(LMIC.rps) + ); +#endif } // start transmitter (buf=LMIC.frame, len=LMIC.dataLen) @@ -601,6 +613,24 @@ static void rxlora (u1_t rxmode) { } else { // continous rx (scan or rssi) opmode(OPMODE_RX); } + +#if LMIC_DEBUG_LEVEL > 0 + if (rxmode == RXMODE_RSSI) { + printf("RXMODE_RSSI\n"); + } else { + u1_t sf = getSf(LMIC.rps) + 6; // 1 == SF7 + u1_t bw = getBw(LMIC.rps); + u1_t cr = getCr(LMIC.rps); + printf("%lu: %s, freq=%lu, SF=%d, BW=%d, CR=4/%d, IH=%d\n", + os_getTime(), + rxmode == RXMODE_SINGLE ? "RXMODE_SINGLE" : (rxmode == RXMODE_SCAN ? "RXMODE_SCAN" : "UNKNOWN_RX"), + LMIC.freq, sf, + bw == BW125 ? 125 : (bw == BW250 ? 250 : 500), + cr == CR_4_5 ? 5 : (cr == CR_4_6 ? 6 : (cr == CR_4_7 ? 7 : 8)), + getIh(LMIC.rps) + ); + } +#endif } static void rxfsk (u1_t rxmode) { diff --git a/target/arduino/examples/raw/raw.ino b/target/arduino/examples/raw/raw.ino new file mode 100644 index 0000000..1e0382f --- /dev/null +++ b/target/arduino/examples/raw/raw.ino @@ -0,0 +1,162 @@ +/******************************************************************************* + * Copyright (c) 2015 Matthijs Kooijman + * + * Permission is hereby granted, free of charge, to anyone + * obtaining a copy of this document and accompanying files, + * to do whatever they want with them without any restriction, + * including, but not limited to, copying, modification and redistribution. + * NO WARRANTY OF ANY KIND IS PROVIDED. + * + * This example transmits data on hardcoded channel and receives data + * when not transmitting. Running this sketch on two nodes should allow + * them to communicate. + *******************************************************************************/ + +#include +#include +#include + +#if !defined(DISABLE_INVERT_IQ_ON_RX) +#error This example requires DISABLE_INVERT_IQ_ON_RX to be set. Update \ + config.h in the lmic library to set it. +#endif + +// How often to send a packet. Note that this sketch bypasses the normal +// LMIC duty cycle limiting, so when you change anything in this sketch +// (payload length, frequency, spreading factor), be sure to check if +// this interval should not also be increased. +// See this spreadsheet for an easy airtime and duty cycle calculator: +// https://docs.google.com/spreadsheets/d/1voGAtQAjC1qBmaVuP1ApNKs1ekgUjavHuVQIXyYSvNc +#define TX_INTERVAL 2000 + +// Pin mapping +const lmic_pinmap lmic_pins = { + .nss = 6, + .rxtx = LMIC_UNUSED_PIN, + .rst = 5, + .dio = {2, 3, 4}, +}; + + +// These callbacks are only used in over-the-air activation, so they are +// left empty here (we cannot leave them out completely unless +// DISABLE_JOIN is set in config.h, otherwise the linker will complain). +void os_getArtEui (u1_t* buf) { } +void os_getDevEui (u1_t* buf) { } +void os_getDevKey (u1_t* buf) { } + +void onEvent (ev_t ev) { +} + +osjob_t txjob; +osjob_t timeoutjob; +static void tx_func (osjob_t* job); + +// Transmit the given string and call the given function afterwards +void tx(const char *str, osjobcb_t func) { + os_radio(RADIO_RST); // Stop RX first + delay(1); // Wait a bit, without this os_radio below asserts, apparently because the state hasn't changed yet + LMIC.dataLen = 0; + while (*str) + LMIC.frame[LMIC.dataLen++] = *str++; + LMIC.osjob.func = func; + os_radio(RADIO_TX); + Serial.println("TX"); +} + +// Enable rx mode and call func when a packet is received +void rx(osjobcb_t func) { + LMIC.osjob.func = func; + LMIC.rxtime = os_getTime(); // RX _now_ + // Enable "continuous" RX (e.g. without a timeout, still stops after + // receiving a packet) + os_radio(RADIO_RXON); + Serial.println("RX"); +} + +static void rxtimeout_func(osjob_t *job) { + digitalWrite(LED_BUILTIN, LOW); // off +} + +static void rx_func (osjob_t* job) { + // Blink once to confirm reception and then keep the led on + digitalWrite(LED_BUILTIN, LOW); // off + delay(10); + digitalWrite(LED_BUILTIN, HIGH); // on + + // Timeout RX (i.e. update led status) after 3 periods without RX + os_setTimedCallback(&timeoutjob, os_getTime() + ms2osticks(3*TX_INTERVAL), rxtimeout_func); + + // Reschedule TX so that it should not collide with the other side's + // next TX + os_setTimedCallback(&txjob, os_getTime() + ms2osticks(TX_INTERVAL/2), tx_func); + + Serial.print("Got "); + Serial.print(LMIC.dataLen); + Serial.println(" bytes"); + Serial.write(LMIC.frame, LMIC.dataLen); + Serial.println(); + + // Restart RX + rx(rx_func); +} + +static void txdone_func (osjob_t* job) { + rx(rx_func); +} + +// log text to USART and toggle LED +static void tx_func (osjob_t* job) { + // say hello + tx("Hello, world!", txdone_func); + // reschedule job every TX_INTERVAL (plus a bit of random to prevent + // systematic collisions), unless packets are received, then rx_func + // will reschedule at half this time. + os_setTimedCallback(job, os_getTime() + ms2osticks(TX_INTERVAL + random(500)), tx_func); +} + +// application entry point +void setup() { + Serial.begin(115200); + Serial.println("Starting"); + #ifdef VCC_ENABLE + // For Pinoccio Scout boards + pinMode(VCC_ENABLE, OUTPUT); + digitalWrite(VCC_ENABLE, HIGH); + delay(1000); + #endif + + pinMode(LED_BUILTIN, OUTPUT); + + // initialize runtime env + os_init(); + + // Set up these settings once, and use them for both TX and RX + +#if defined(CFG_eu868) + // Use a frequency in the g3 which allows 10% duty cycling. + LMIC.freq = 869525000; +#elif defined(CFG_us915) + LMIC.freq = 902300000; +#endif + + // Maximum TX power + LMIC.txpow = 27; + // Use a medium spread factor. This can be increased up to SF12 for + // better range, but then the interval should be (significantly) + // lowered to comply with duty cycle limits as well. + LMIC.datarate = DR_SF9; + // This sets CR 4/5, BW125 (except for DR_SF7B, which uses BW250) + LMIC.rps = updr2rps(LMIC.datarate); + + Serial.println("Started"); + Serial.flush(); + + // setup initial job + os_setCallback(&txjob, tx_func); +} + +void loop() { + // execute scheduled jobs and events + os_runloop_once(); +} diff --git a/target/arduino/examples/ttn-abp/ttn-abp.ino b/target/arduino/examples/ttn-abp/ttn-abp.ino new file mode 100644 index 0000000..a8b4a18 --- /dev/null +++ b/target/arduino/examples/ttn-abp/ttn-abp.ino @@ -0,0 +1,226 @@ +/******************************************************************************* + * Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman + * + * Permission is hereby granted, free of charge, to anyone + * obtaining a copy of this document and accompanying files, + * to do whatever they want with them without any restriction, + * including, but not limited to, copying, modification and redistribution. + * NO WARRANTY OF ANY KIND IS PROVIDED. + * + * This example sends a valid LoRaWAN packet with payload "Hello, + * world!", using frequency and encryption settings matching those of + * the The Things Network. + * + * This uses ABP (Activation-by-personalisation), where a DevAddr and + * Session keys are preconfigured (unlike OTAA, where a DevEUI and + * application key is configured, while the DevAddr and session keys are + * assigned/generated in the over-the-air-activation procedure). + * + * Note: LoRaWAN per sub-band duty-cycle limitation is enforced (1% in + * g1, 0.1% in g2), but not the TTN fair usage policy (which is probably + * violated by this sketch when left running for longer)! + * + * To use this sketch, first register your application and device with + * the things network, to set or generate a DevAddr, NwkSKey and + * AppSKey. Each device should have their own unique values for these + * fields. + * + * Do not forget to define the radio type correctly in config.h. + * + *******************************************************************************/ + +#include +#include +#include + +// LoRaWAN NwkSKey, network session key +// This is the default Semtech key, which is used by the early prototype TTN +// network. +static const PROGMEM u1_t NWKSKEY[16] = { 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C }; + +// LoRaWAN AppSKey, application session key +// This is the default Semtech key, which is used by the early prototype TTN +// network. +static const u1_t PROGMEM APPSKEY[16] = { 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C }; + +// LoRaWAN end-device address (DevAddr) +static const u4_t DEVADDR = 0x03FF0001 ; // <-- Change this address for every node! + +// These callbacks are only used in over-the-air activation, so they are +// left empty here (we cannot leave them out completely unless +// DISABLE_JOIN is set in config.h, otherwise the linker will complain). +void os_getArtEui (u1_t* buf) { } +void os_getDevEui (u1_t* buf) { } +void os_getDevKey (u1_t* buf) { } + +static uint8_t mydata[] = "Hello, world!"; +static osjob_t sendjob; + +// Schedule TX every this many seconds (might become longer due to duty +// cycle limitations). +const unsigned TX_INTERVAL = 60; + +// Pin mapping +const lmic_pinmap lmic_pins = { + .nss = 6, + .rxtx = LMIC_UNUSED_PIN, + .rst = 5, + .dio = {2, 3, 4}, +}; + +void onEvent (ev_t ev) { + Serial.print(os_getTime()); + Serial.print(": "); + switch(ev) { + case EV_SCAN_TIMEOUT: + Serial.println(F("EV_SCAN_TIMEOUT")); + break; + case EV_BEACON_FOUND: + Serial.println(F("EV_BEACON_FOUND")); + break; + case EV_BEACON_MISSED: + Serial.println(F("EV_BEACON_MISSED")); + break; + case EV_BEACON_TRACKED: + Serial.println(F("EV_BEACON_TRACKED")); + break; + case EV_JOINING: + Serial.println(F("EV_JOINING")); + break; + case EV_JOINED: + Serial.println(F("EV_JOINED")); + break; + case EV_RFU1: + Serial.println(F("EV_RFU1")); + break; + case EV_JOIN_FAILED: + Serial.println(F("EV_JOIN_FAILED")); + break; + case EV_REJOIN_FAILED: + Serial.println(F("EV_REJOIN_FAILED")); + break; + case EV_TXCOMPLETE: + Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)")); + if (LMIC.txrxFlags & TXRX_ACK) + Serial.println(F("Received ack")); + if (LMIC.dataLen) { + Serial.println(F("Received ")); + Serial.println(LMIC.dataLen); + Serial.println(F(" bytes of payload")); + } + // Schedule next transmission + os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send); + break; + case EV_LOST_TSYNC: + Serial.println(F("EV_LOST_TSYNC")); + break; + case EV_RESET: + Serial.println(F("EV_RESET")); + break; + case EV_RXCOMPLETE: + // data received in ping slot + Serial.println(F("EV_RXCOMPLETE")); + break; + case EV_LINK_DEAD: + Serial.println(F("EV_LINK_DEAD")); + break; + case EV_LINK_ALIVE: + Serial.println(F("EV_LINK_ALIVE")); + break; + default: + Serial.println(F("Unknown event")); + break; + } +} + +void do_send(osjob_t* j){ + // Check if there is not a current TX/RX job running + if (LMIC.opmode & OP_TXRXPEND) { + Serial.println(F("OP_TXRXPEND, not sending")); + } else { + // Prepare upstream data transmission at the next possible time. + LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0); + Serial.println(F("Packet queued")); + } + // Next TX is scheduled after TX_COMPLETE event. +} + +void setup() { + Serial.begin(115200); + Serial.println(F("Starting")); + + #ifdef VCC_ENABLE + // For Pinoccio Scout boards + pinMode(VCC_ENABLE, OUTPUT); + digitalWrite(VCC_ENABLE, HIGH); + delay(1000); + #endif + + // LMIC init + os_init(); + // Reset the MAC state. Session and pending data transfers will be discarded. + LMIC_reset(); + + // Set static session parameters. Instead of dynamically establishing a session + // by joining the network, precomputed session parameters are be provided. + #ifdef PROGMEM + // On AVR, these values are stored in flash and only copied to RAM + // once. Copy them to a temporary buffer here, LMIC_setSession will + // copy them into a buffer of its own again. + uint8_t appskey[sizeof(APPSKEY)]; + uint8_t nwkskey[sizeof(NWKSKEY)]; + memcpy_P(appskey, APPSKEY, sizeof(APPSKEY)); + memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY)); + LMIC_setSession (0x1, DEVADDR, nwkskey, appskey); + #else + // If not running an AVR with PROGMEM, just use the arrays directly + LMIC_setSession (0x1, DEVADDR, NWKSKEY, APPSKEY); + #endif + + #if defined(CFG_eu868) + // Set up the channels used by the Things Network, which corresponds + // to the defaults of most gateways. Without this, only three base + // channels from the LoRaWAN specification are used, which certainly + // works, so it is good for debugging, but can overload those + // frequencies, so be sure to configure the full frequency range of + // your network here (unless your network autoconfigures them). + // Setting up channels should happen after LMIC_setSession, as that + // configures the minimal channel set. + // NA-US channels 0-71 are configured automatically + LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band + LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI); // g-band + LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band + LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band + LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band + LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band + LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band + LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band + LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK, DR_FSK), BAND_MILLI); // g2-band + // TTN defines an additional channel at 869.525Mhz using SF9 for class B + // devices' ping slots. LMIC does not have an easy way to define set this + // frequency and support for class B is spotty and untested, so this + // frequency is not configured here. + #elif defined(CFG_us915) + // NA-US channels 0-71 are configured automatically + // but only one group of 8 should (a subband) should be active + // TTN recommends the second sub band, 1 in a zero based count. + // https://github.com/TheThingsNetwork/gateway-conf/blob/master/US-global_conf.json + LMIC_selectSubBand(1); + #endif + + // Disable link check validation + LMIC_setLinkCheckMode(0); + + // TTN uses SF9 for its RX2 window. + LMIC.dn2Dr = DR_SF9; + + // Set data rate and transmit power for uplink (note: txpow seems to be ignored by the library) + LMIC_setDrTxpow(DR_SF7,14); + + // Start job + do_send(&sendjob); +} + +void loop() { + os_runloop_once(); +} diff --git a/target/arduino/examples/ttn-otaa/ttn-otaa.ino b/target/arduino/examples/ttn-otaa/ttn-otaa.ino new file mode 100644 index 0000000..d667aa2 --- /dev/null +++ b/target/arduino/examples/ttn-otaa/ttn-otaa.ino @@ -0,0 +1,173 @@ +/******************************************************************************* + * Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman + * + * Permission is hereby granted, free of charge, to anyone + * obtaining a copy of this document and accompanying files, + * to do whatever they want with them without any restriction, + * including, but not limited to, copying, modification and redistribution. + * NO WARRANTY OF ANY KIND IS PROVIDED. + * + * This example sends a valid LoRaWAN packet with payload "Hello, + * world!", using frequency and encryption settings matching those of + * the The Things Network. + * + * This uses OTAA (Over-the-air activation), where where a DevEUI and + * application key is configured, which are used in an over-the-air + * activation procedure where a DevAddr and session keys are + * assigned/generated for use with all further communication. + * + * Note: LoRaWAN per sub-band duty-cycle limitation is enforced (1% in + * g1, 0.1% in g2), but not the TTN fair usage policy (which is probably + * violated by this sketch when left running for longer)! + + * To use this sketch, first register your application and device with + * the things network, to set or generate an AppEUI, DevEUI and AppKey. + * Multiple devices can use the same AppEUI, but each device has its own + * DevEUI and AppKey. + * + * Do not forget to define the radio type correctly in config.h. + * + *******************************************************************************/ + +#include +#include +#include + +// This EUI must be in little-endian format, so least-significant-byte +// first. When copying an EUI from ttnctl output, this means to reverse +// the bytes. For TTN issued EUIs the last bytes should be 0xD5, 0xB3, +// 0x70. +static const u1_t PROGMEM APPEUI[8]={ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +void os_getArtEui (u1_t* buf) { memcpy_P(buf, APPEUI, 8);} + +// This should also be in little endian format, see above. +static const u1_t PROGMEM DEVEUI[8]={ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +void os_getDevEui (u1_t* buf) { memcpy_P(buf, DEVEUI, 8);} + +// This key should be in big endian format (or, since it is not really a +// number but a block of memory, endianness does not really apply). In +// practice, a key taken from ttnctl can be copied as-is. +// The key shown here is the semtech default key. +static const u1_t PROGMEM APPKEY[16] = { 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C }; +void os_getDevKey (u1_t* buf) { memcpy_P(buf, APPKEY, 16);} + +static uint8_t mydata[] = "Hello, world!"; +static osjob_t sendjob; + +// Schedule TX every this many seconds (might become longer due to duty +// cycle limitations). +const unsigned TX_INTERVAL = 60; + +// Pin mapping +const lmic_pinmap lmic_pins = { + .nss = 6, + .rxtx = LMIC_UNUSED_PIN, + .rst = 5, + .dio = {2, 3, 4}, +}; + +void onEvent (ev_t ev) { + Serial.print(os_getTime()); + Serial.print(": "); + switch(ev) { + case EV_SCAN_TIMEOUT: + Serial.println(F("EV_SCAN_TIMEOUT")); + break; + case EV_BEACON_FOUND: + Serial.println(F("EV_BEACON_FOUND")); + break; + case EV_BEACON_MISSED: + Serial.println(F("EV_BEACON_MISSED")); + break; + case EV_BEACON_TRACKED: + Serial.println(F("EV_BEACON_TRACKED")); + break; + case EV_JOINING: + Serial.println(F("EV_JOINING")); + break; + case EV_JOINED: + Serial.println(F("EV_JOINED")); + + // Disable link check validation (automatically enabled + // during join, but not supported by TTN at this time). + LMIC_setLinkCheckMode(0); + break; + case EV_RFU1: + Serial.println(F("EV_RFU1")); + break; + case EV_JOIN_FAILED: + Serial.println(F("EV_JOIN_FAILED")); + break; + case EV_REJOIN_FAILED: + Serial.println(F("EV_REJOIN_FAILED")); + break; + break; + case EV_TXCOMPLETE: + Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)")); + if (LMIC.txrxFlags & TXRX_ACK) + Serial.println(F("Received ack")); + if (LMIC.dataLen) { + Serial.println(F("Received ")); + Serial.println(LMIC.dataLen); + Serial.println(F(" bytes of payload")); + } + // Schedule next transmission + os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send); + break; + case EV_LOST_TSYNC: + Serial.println(F("EV_LOST_TSYNC")); + break; + case EV_RESET: + Serial.println(F("EV_RESET")); + break; + case EV_RXCOMPLETE: + // data received in ping slot + Serial.println(F("EV_RXCOMPLETE")); + break; + case EV_LINK_DEAD: + Serial.println(F("EV_LINK_DEAD")); + break; + case EV_LINK_ALIVE: + Serial.println(F("EV_LINK_ALIVE")); + break; + default: + Serial.println(F("Unknown event")); + break; + } +} + +void do_send(osjob_t* j){ + // Check if there is not a current TX/RX job running + if (LMIC.opmode & OP_TXRXPEND) { + Serial.println(F("OP_TXRXPEND, not sending")); + } else { + // Prepare upstream data transmission at the next possible time. + LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0); + Serial.println(F("Packet queued")); + } + // Next TX is scheduled after TX_COMPLETE event. +} + +void setup() { + Serial.begin(9600); + Serial.println(F("Starting")); + + #ifdef VCC_ENABLE + // For Pinoccio Scout boards + pinMode(VCC_ENABLE, OUTPUT); + digitalWrite(VCC_ENABLE, HIGH); + delay(1000); + #endif + + // LMIC init + os_init(); + // Reset the MAC state. Session and pending data transfers will be discarded. + LMIC_reset(); + + // Start job (sending automatically starts OTAA too) + do_send(&sendjob); +} + +void loop() { + os_runloop_once(); +} diff --git a/target/arduino/export.sh b/target/arduino/export.sh new file mode 100755 index 0000000..d05fcc3 --- /dev/null +++ b/target/arduino/export.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +SRC=$(cd "$(dirname "$0")"; pwd) + +usage() { + echo "$0 [--link] TARGET_DIR" + echo "Create an Arduino-compatible library in the given directory, overwriting existing files. If --link is given, creates symbolic links for easy testing." +} + + +CPOPTS= +case "$1" in + --help) + usage + exit 0 + ;; + --link) + CPOPTS=--symbolic-link + shift; + ;; + --*) + echo "Unknown option: $1" 1>&2 + exit 1 + ;; +esac + +TARGET=$1 + +if [ -z "$TARGET" ]; then + usage + exit 1 +fi + +if ! [ -d "$(dirname "$TARGET")" ]; then + echo "Parent of $TARGET should exist" 1>&2 + exit 1 +fi + +mkdir -p "$TARGET"/src +cp $CPOPTS -f -v "$SRC"/library.properties "$TARGET" +cp $CPOPTS -f -v "$SRC"/lmic.h "$TARGET"/src +cp $CPOPTS -r -f -v "$SRC"/../../lmic "$TARGET"/src +cp $CPOPTS -r -f -v "$SRC"/../../aes "$TARGET"/src +cp $CPOPTS -r -f -v "$SRC"/hal "$TARGET"/src +cp $CPOPTS -r -f -v "$SRC"/examples "$TARGET" diff --git a/target/arduino/hal/hal.cpp b/target/arduino/hal/hal.cpp new file mode 100644 index 0000000..53bf359 --- /dev/null +++ b/target/arduino/hal/hal.cpp @@ -0,0 +1,253 @@ +/******************************************************************************* + * Copyright (c) 2015 Matthijs Kooijman + * 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 + * + * This the HAL to run LMIC on top of the Arduino environment. + *******************************************************************************/ + +#include +#include +#include "../lmic.h" +#include "hal.h" +#include + +// ----------------------------------------------------------------------------- +// I/O + +static void hal_io_init () { + // NSS and DIO0 are required, DIO1 is required for LoRa, DIO2 for FSK + ASSERT(lmic_pins.nss != LMIC_UNUSED_PIN); + ASSERT(lmic_pins.dio[0] != LMIC_UNUSED_PIN); + ASSERT(lmic_pins.dio[1] != LMIC_UNUSED_PIN || lmic_pins.dio[2] != LMIC_UNUSED_PIN); + + pinMode(lmic_pins.nss, OUTPUT); + if (lmic_pins.rxtx != LMIC_UNUSED_PIN) + pinMode(lmic_pins.rxtx, OUTPUT); + if (lmic_pins.rst != LMIC_UNUSED_PIN) + pinMode(lmic_pins.rst, OUTPUT); + + pinMode(lmic_pins.dio[0], INPUT); + if (lmic_pins.dio[1] != LMIC_UNUSED_PIN) + pinMode(lmic_pins.dio[1], INPUT); + if (lmic_pins.dio[2] != LMIC_UNUSED_PIN) + pinMode(lmic_pins.dio[2], INPUT); +} + +// val == 1 => tx 1 +void hal_pin_rxtx (u1_t val) { + if (lmic_pins.rxtx != LMIC_UNUSED_PIN) + digitalWrite(lmic_pins.rxtx, val); +} + +// set radio RST pin to given value (or keep floating!) +void hal_pin_rst (u1_t val) { + if (lmic_pins.rst == LMIC_UNUSED_PIN) + return; + + if(val == 0 || val == 1) { // drive pin + pinMode(lmic_pins.rst, OUTPUT); + digitalWrite(lmic_pins.rst, val); + } else { // keep pin floating + pinMode(lmic_pins.rst, INPUT); + } +} + +static bool dio_states[NUM_DIO] = {0}; + +static void hal_io_check() { + uint8_t i; + for (i = 0; i < NUM_DIO; ++i) { + if (lmic_pins.dio[i] == LMIC_UNUSED_PIN) + continue; + + if (dio_states[i] != digitalRead(lmic_pins.dio[i])) { + dio_states[i] = !dio_states[i]; + if (dio_states[i]) + radio_irq_handler(i); + } + } +} + +// ----------------------------------------------------------------------------- +// SPI + +static const SPISettings settings(10E6, MSBFIRST, SPI_MODE0); + +static void hal_spi_init () { + SPI.begin(); +} + +void hal_pin_nss (u1_t val) { + if (!val) + SPI.beginTransaction(settings); + else + SPI.endTransaction(); + + //Serial.println(val?">>":"<<"); + digitalWrite(lmic_pins.nss, val); +} + +// perform SPI transaction with radio +u1_t hal_spi (u1_t out) { + u1_t res = SPI.transfer(out); +/* + Serial.print(">"); + Serial.print(out, HEX); + Serial.print("<"); + Serial.println(res, HEX); + */ + return res; +} + +// ----------------------------------------------------------------------------- +// TIME + +static void hal_time_init () { + // Nothing to do +} + +u4_t hal_ticks () { + // Because micros() is scaled down in this function, micros() will + // overflow before the tick timer should, causing the tick timer to + // miss a significant part of its values if not corrected. To fix + // this, the "overflow" serves as an overflow area for the micros() + // counter. It consists of three parts: + // - The US_PER_OSTICK upper bits are effectively an extension for + // the micros() counter and are added to the result of this + // function. + // - The next bit overlaps with the most significant bit of + // micros(). This is used to detect micros() overflows. + // - The remaining bits are always zero. + // + // By comparing the overlapping bit with the corresponding bit in + // the micros() return value, overflows can be detected and the + // upper bits are incremented. This is done using some clever + // bitwise operations, to remove the need for comparisons and a + // jumps, which should result in efficient code. By avoiding shifts + // other than by multiples of 8 as much as possible, this is also + // efficient on AVR (which only has 1-bit shifts). + static uint8_t overflow = 0; + + // Scaled down timestamp. The top US_PER_OSTICK_EXPONENT bits are 0, + // the others will be the lower bits of our return value. + uint32_t scaled = micros() >> US_PER_OSTICK_EXPONENT; + // Most significant byte of scaled + uint8_t msb = scaled >> 24; + // Mask pointing to the overlapping bit in msb and overflow. + const uint8_t mask = (1 << (7 - US_PER_OSTICK_EXPONENT)); + // Update overflow. If the overlapping bit is different + // between overflow and msb, it is added to the stored value, + // so the overlapping bit becomes equal again and, if it changed + // from 1 to 0, the upper bits are incremented. + overflow += (msb ^ overflow) & mask; + + // Return the scaled value with the upper bits of stored added. The + // overlapping bit will be equal and the lower bits will be 0, so + // bitwise or is a no-op for them. + return scaled | ((uint32_t)overflow << 24); + + // 0 leads to correct, but overly complex code (it could just return + // micros() unmodified), 8 leaves no room for the overlapping bit. + static_assert(US_PER_OSTICK_EXPONENT > 0 && US_PER_OSTICK_EXPONENT < 8, "Invalid US_PER_OSTICK_EXPONENT value"); +} + +// Returns the number of ticks until time. Negative values indicate that +// time has already passed. +static s4_t delta_time(u4_t time) { + return (s4_t)(time - hal_ticks()); +} + +void hal_waitUntil (u4_t time) { + s4_t delta = delta_time(time); + // From delayMicroseconds docs: Currently, the largest value that + // will produce an accurate delay is 16383. + while (delta > (16000 / US_PER_OSTICK)) { + delay(16); + delta -= (16000 / US_PER_OSTICK); + } + if (delta > 0) + delayMicroseconds(delta * US_PER_OSTICK); +} + +// check and rewind for target time +u1_t hal_checkTimer (u4_t time) { + // No need to schedule wakeup, since we're not sleeping + return delta_time(time) <= 0; +} + +static uint8_t irqlevel = 0; + +void hal_disableIRQs () { + noInterrupts(); + irqlevel++; +} + +void hal_enableIRQs () { + if(--irqlevel == 0) { + interrupts(); + + // Instead of using proper interrupts (which are a bit tricky + // and/or not available on all pins on AVR), just poll the pin + // values. Since os_runloop disables and re-enables interrupts, + // putting this here makes sure we check at least once every + // loop. + // + // As an additional bonus, this prevents the can of worms that + // we would otherwise get for running SPI transfers inside ISRs + hal_io_check(); + } +} + +void hal_sleep () { + // Not implemented +} + +// ----------------------------------------------------------------------------- + +#if defined(LMIC_PRINTF_TO) +static int uart_putchar (char c, FILE *) +{ + LMIC_PRINTF_TO.write(c) ; + return 0 ; +} + +void hal_printf_init() { + // create a FILE structure to reference our UART output function + static FILE uartout; + memset(&uartout, 0, sizeof(uartout)); + + // fill in the UART file descriptor with pointer to writer. + fdev_setup_stream (&uartout, uart_putchar, NULL, _FDEV_SETUP_WRITE); + + // The uart is the standard output device STDOUT. + stdout = &uartout ; +} +#endif // defined(LMIC_PRINTF_TO) + +void hal_init () { + // configure radio I/O and interrupt handler + hal_io_init(); + // configure radio SPI + hal_spi_init(); + // configure timer and interrupt handler + hal_time_init(); +#if defined(LMIC_PRINTF_TO) + // printf support + hal_printf_init(); +#endif +} + +void hal_failed (const char *file, u2_t line) { +#if defined(LMIC_FAILURE_TO) + LMIC_FAILURE_TO.println("FAILURE "); + LMIC_FAILURE_TO.print(file); + LMIC_FAILURE_TO.print(':'); + LMIC_FAILURE_TO.println(line); + LMIC_FAILURE_TO.flush(); +#endif + hal_disableIRQs(); + while(1); +} diff --git a/target/arduino/hal/hal.h b/target/arduino/hal/hal.h new file mode 100644 index 0000000..e096569 --- /dev/null +++ b/target/arduino/hal/hal.h @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2015 Matthijs Kooijman + * 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 + * + * This the HAL to run LMIC on top of the Arduino environment. + *******************************************************************************/ +#ifndef _hal_hal_h_ +#define _hal_hal_h_ + +static const int NUM_DIO = 3; + +struct lmic_pinmap { + u1_t nss; + u1_t rxtx; + u1_t rst; + u1_t dio[NUM_DIO]; +}; + +// Use this for any unused pins. +const u1_t LMIC_UNUSED_PIN = 0xff; + +// Declared here, to be defined an initialized by the application +extern const lmic_pinmap lmic_pins; + +#endif // _hal_hal_h_ diff --git a/target/arduino/hal/target-config.h b/target/arduino/hal/target-config.h new file mode 100644 index 0000000..026a18c --- /dev/null +++ b/target/arduino/hal/target-config.h @@ -0,0 +1,80 @@ +#ifndef _lmic_arduino_hal_config_h_ +#define _lmic_arduino_hal_config_h_ + +#define CFG_eu868 1 +//#define CFG_us915 1 + +// This is the SX1272/SX1273 radio, which is also used on the HopeRF +// RFM92 boards. +//#define CFG_sx1272_radio 1 +// This is the SX1276/SX1277/SX1278/SX1279 radio, which is also used on +// the HopeRF RFM95 boards. +#define CFG_sx1276_radio 1 + +// 16 μs per tick +// LMIC requires ticks to be 15.5μs - 100 μs long +#define US_PER_OSTICK_EXPONENT 4 +#define US_PER_OSTICK (1 << US_PER_OSTICK_EXPONENT) +#define OSTICKS_PER_SEC (1000000 / US_PER_OSTICK) + +// Enable this to allow using printf() to print to the given serial port +// (or any other Print object). This can be easy for debugging. The +// current implementation only works on AVR, though. +//#define LMIC_PRINTF_TO Serial + +// Any runtime assertion failures are printed to this serial port (or +// any other Print object). If this is unset, any failures just silently +// halt execution. +#define LMIC_FAILURE_TO Serial + +// Set this to 1 to enable some basic debug output (using printf) about +// RF settings used during transmission and reception. Set to 2 to +// enable more verbose output. Make sure that printf is actually +// configured (e.g. on AVR it is not by default), otherwise using it can +// cause crashing. +#define LMIC_DEBUG_LEVEL 0 + +// Uncomment this to disable all code related to joining +//#define DISABLE_JOIN +// Uncomment this to disable all code related to ping +#define DISABLE_PING +// Uncomment this to disable all code related to beacon tracking. +// Requires ping to be disabled too +#define DISABLE_BEACONS + +// Uncomment these to disable the corresponding MAC commands. +// Class A +//#define DISABLE_MCMD_DCAP_REQ // duty cycle cap +//#define DISABLE_MCMD_DN2P_SET // 2nd DN window param +//#define DISABLE_MCMD_SNCH_REQ // set new channel +// Class B +//#define DISABLE_MCMD_PING_SET // set ping freq, automatically disabled by DISABLE_PING +//#define DISABLE_MCMD_BCNI_ANS // next beacon start, automatical disabled by DISABLE_BEACON + +// In LoRaWAN, a gateway applies I/Q inversion on TX, and nodes do the +// same on RX. This ensures that gateways can talk to nodes and vice +// versa, but gateways will not hear other gateways and nodes will not +// hear other nodes. By uncommenting this macro, this inversion is +// disabled and this node can hear other nodes. If two nodes both have +// this macro set, they can talk to each other (but they can no longer +// hear gateways). This should probably only be used when debugging +// and/or when talking to the radio directly (e.g. like in the "raw" +// example). +//#define DISABLE_INVERT_IQ_ON_RX + +// This allows choosing between multiple included AES implementations. +// Make sure exactly one of these is uncommented. +// +// This selects the original AES implementation included LMIC. This +// implementation is optimized for speed on 32-bit processors using +// fairly big lookup tables, but it takes up big amounts of flash on the +// AVR architecture. +// #define USE_ORIGINAL_AES +// +// This selects the AES implementation written by Ideetroon for their +// own LoRaWAN library. It also uses lookup tables, but smaller +// byte-oriented ones, making it use a lot less flash space (but it is +// also about twice as slow as the original). +#define USE_IDEETRON_AES + +#endif // _lmic_arduino_hal_config_h_ diff --git a/target/arduino/library.properties b/target/arduino/library.properties new file mode 100644 index 0000000..0dbf7d5 --- /dev/null +++ b/target/arduino/library.properties @@ -0,0 +1,9 @@ +name=IBM LMIC framework +version=1.5.0+arduino-1 +author=IBM +maintainer=Matthijs Kooijman +sentence=Arduino port of the LMIC (LoraWAN-in-C, formerly LoraMAC-in-C) framework provided by IBM. +paragraph=Supports SX1272/SX1276 and HopeRF RFM92/RFM95 tranceivers +category=Communication +url=http://www.research.ibm.com/labs/zurich/ics/lrsc/lmic.html +architectures=* diff --git a/target/arduino/lmic.h b/target/arduino/lmic.h new file mode 100644 index 0000000..f2c7935 --- /dev/null +++ b/target/arduino/lmic.h @@ -0,0 +1,9 @@ +#ifdef __cplusplus +extern "C"{ +#endif + +#include "lmic/lmic.h" + +#ifdef __cplusplus +} +#endif