@ -0,0 +1 @@ | |||
config.h |
@ -1,686 +0,0 @@ | |||
/* | |||
LoRaWAN.cpp - Library for LoRaWAN protocol, uses RFM95W module | |||
Created by Leo Korbee, March 31, 2018. | |||
Released into the public domain. | |||
@license Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) | |||
Thanks to all the folks who contributed on the base of this code. | |||
(Gerben den Hartog, et al - Ideetron.nl) | |||
*/ | |||
#include "Arduino.h" | |||
#include "LoRaWAN.h" | |||
// constructor | |||
LoRaWAN::LoRaWAN(RFM95 &rfm95) | |||
{ | |||
_rfm95 = &rfm95; | |||
} | |||
void LoRaWAN::setKeys(unsigned char NwkSkey[], unsigned char AppSkey[], unsigned char DevAddr[]) | |||
{ | |||
_NwkSkey = NwkSkey; | |||
_AppSkey = AppSkey; | |||
_DevAddr = DevAddr; | |||
} | |||
/* | |||
***************************************************************************************** | |||
* Description : Function contstructs a LoRaWAN package and sends it | |||
* | |||
* Arguments : *Data pointer to the array of data that will be transmitted | |||
* Data_Length nuber of bytes to be transmitted | |||
* Frame_Counter_Up Frame counter of upstream frames | |||
* | |||
***************************************************************************************** | |||
*/ | |||
void LoRaWAN::Send_Data(unsigned char *Data, unsigned char Data_Length, unsigned int Frame_Counter_Tx, lora_dr_t datarate,unsigned char Frame_Port) | |||
{ | |||
//Define variables | |||
unsigned char i; | |||
//Direction of frame is up | |||
unsigned char Direction = 0x00; | |||
unsigned char RFM_Data[64]; | |||
unsigned char RFM_Package_Length; | |||
unsigned char MIC[4]; | |||
/* | |||
@leo: | |||
https://hackmd.io/s/S1kg6Ymo- | |||
7…5 bits 4…2 bits 1…0 bits | |||
MType RFU Major | |||
MType Description | |||
000 (0x00) Join Request | |||
001 (0x20) Join Accept | |||
010 (0x40) Unconfirmed Data Up | |||
011 (0x60) Unconfirmed Data Down | |||
100 (0x80) Confirmed Data Up | |||
101 (0xA0) Confirmed Data Down | |||
110 (0xC0) RFU | |||
111 (0xE0) Proprietary | |||
*/ | |||
// Unconfirmed data up | |||
unsigned char Mac_Header = 0x40; | |||
// Confirmed data up | |||
// unsigned char Mac_Header = 0x80; | |||
unsigned char Frame_Control = 0x00; | |||
//unsigned char Frame_Port = 0x01; | |||
//Encrypt the data | |||
Encrypt_Payload(Data, Data_Length, Frame_Counter_Tx, Direction); | |||
//Build the Radio Package | |||
RFM_Data[0] = Mac_Header; | |||
RFM_Data[1] = _DevAddr[3]; | |||
RFM_Data[2] = _DevAddr[2]; | |||
RFM_Data[3] = _DevAddr[1]; | |||
RFM_Data[4] = _DevAddr[0]; | |||
RFM_Data[5] = Frame_Control; | |||
RFM_Data[6] = (Frame_Counter_Tx & 0x00FF); | |||
RFM_Data[7] = ((Frame_Counter_Tx >> 8) & 0x00FF); | |||
RFM_Data[8] = Frame_Port; | |||
//Set Current package length | |||
RFM_Package_Length = 9; | |||
//Load Data | |||
for(i = 0; i < Data_Length; i++) | |||
{ | |||
RFM_Data[RFM_Package_Length + i] = Data[i]; | |||
} | |||
//Add data Lenth to package length | |||
RFM_Package_Length = RFM_Package_Length + Data_Length; | |||
//Calculate MIC | |||
Calculate_MIC(RFM_Data, MIC, RFM_Package_Length, Frame_Counter_Tx, Direction); | |||
//Load MIC in package | |||
for(i = 0; i < 4; i++) | |||
{ | |||
RFM_Data[i + RFM_Package_Length] = MIC[i]; | |||
} | |||
//Add MIC length to RFM package length | |||
RFM_Package_Length = RFM_Package_Length + 4; | |||
//Set Lora Datarate | |||
_rfm95->RFM_Set_Datarate(datarate); | |||
//Send Package | |||
_rfm95->RFM_Send_Package(RFM_Data, RFM_Package_Length); | |||
} | |||
/* | |||
Encryption stuff after this line | |||
*/ | |||
void LoRaWAN::Encrypt_Payload(unsigned char *Data, unsigned char Data_Length, unsigned int Frame_Counter, unsigned char Direction) | |||
{ | |||
unsigned char i = 0x00; | |||
unsigned char j; | |||
unsigned char Number_of_Blocks = 0x00; | |||
unsigned char Incomplete_Block_Size = 0x00; | |||
unsigned char Block_A[16]; | |||
//Calculate number of blocks | |||
Number_of_Blocks = Data_Length / 16; | |||
Incomplete_Block_Size = Data_Length % 16; | |||
if(Incomplete_Block_Size != 0) | |||
{ | |||
Number_of_Blocks++; | |||
} | |||
for(i = 1; i <= Number_of_Blocks; i++) | |||
{ | |||
Block_A[0] = 0x01; | |||
Block_A[1] = 0x00; | |||
Block_A[2] = 0x00; | |||
Block_A[3] = 0x00; | |||
Block_A[4] = 0x00; | |||
Block_A[5] = Direction; | |||
Block_A[6] = _DevAddr[3]; | |||
Block_A[7] = _DevAddr[2]; | |||
Block_A[8] = _DevAddr[1]; | |||
Block_A[9] = _DevAddr[0]; | |||
Block_A[10] = (Frame_Counter & 0x00FF); | |||
Block_A[11] = ((Frame_Counter >> 8) & 0x00FF); | |||
Block_A[12] = 0x00; //Frame counter upper Bytes | |||
Block_A[13] = 0x00; | |||
Block_A[14] = 0x00; | |||
Block_A[15] = i; | |||
//Calculate S | |||
AES_Encrypt(Block_A, _AppSkey); //original | |||
//Check for last block | |||
if(i != Number_of_Blocks) | |||
{ | |||
for(j = 0; j < 16; j++) | |||
{ | |||
*Data = *Data ^ Block_A[j]; | |||
Data++; | |||
} | |||
} | |||
else | |||
{ | |||
if(Incomplete_Block_Size == 0) | |||
{ | |||
Incomplete_Block_Size = 16; | |||
} | |||
for(j = 0; j < Incomplete_Block_Size; j++) | |||
{ | |||
*Data = *Data ^ Block_A[j]; | |||
Data++; | |||
} | |||
} | |||
} | |||
} | |||
void LoRaWAN::Calculate_MIC(unsigned char *Data, unsigned char *Final_MIC, unsigned char Data_Length, unsigned int Frame_Counter, unsigned char Direction) | |||
{ | |||
unsigned char i; | |||
unsigned char Block_B[16]; | |||
unsigned char Key_K1[16] = { | |||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 | |||
}; | |||
unsigned char Key_K2[16] = { | |||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 | |||
}; | |||
//unsigned char Data_Copy[16]; | |||
unsigned char Old_Data[16] = { | |||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 | |||
}; | |||
unsigned char New_Data[16] = { | |||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 | |||
}; | |||
unsigned char Number_of_Blocks = 0x00; | |||
unsigned char Incomplete_Block_Size = 0x00; | |||
unsigned char Block_Counter = 0x01; | |||
//Create Block_B | |||
Block_B[0] = 0x49; | |||
Block_B[1] = 0x00; | |||
Block_B[2] = 0x00; | |||
Block_B[3] = 0x00; | |||
Block_B[4] = 0x00; | |||
Block_B[5] = Direction; | |||
Block_B[6] = _DevAddr[3]; | |||
Block_B[7] = _DevAddr[2]; | |||
Block_B[8] = _DevAddr[1]; | |||
Block_B[9] = _DevAddr[0]; | |||
Block_B[10] = (Frame_Counter & 0x00FF); | |||
Block_B[11] = ((Frame_Counter >> 8) & 0x00FF); | |||
Block_B[12] = 0x00; //Frame counter upper bytes | |||
Block_B[13] = 0x00; | |||
Block_B[14] = 0x00; | |||
Block_B[15] = Data_Length; | |||
//Calculate number of Blocks and blocksize of last block | |||
Number_of_Blocks = Data_Length / 16; | |||
Incomplete_Block_Size = Data_Length % 16; | |||
if(Incomplete_Block_Size != 0) | |||
{ | |||
Number_of_Blocks++; | |||
} | |||
Generate_Keys(Key_K1, Key_K2); | |||
//Preform Calculation on Block B0 | |||
//Preform AES encryption | |||
AES_Encrypt(Block_B, _NwkSkey); | |||
//Copy Block_B to Old_Data | |||
for(i = 0; i < 16; i++) | |||
{ | |||
Old_Data[i] = Block_B[i]; | |||
} | |||
//Preform full calculating until n-1 messsage blocks | |||
while(Block_Counter < Number_of_Blocks) | |||
{ | |||
//Copy data into array | |||
for(i = 0; i < 16; i++) | |||
{ | |||
New_Data[i] = *Data; | |||
Data++; | |||
} | |||
//Preform XOR with old data | |||
XOR(New_Data,Old_Data); | |||
//Preform AES encryption | |||
AES_Encrypt(New_Data, _NwkSkey); | |||
//Copy New_Data to Old_Data | |||
for(i = 0; i < 16; i++) | |||
{ | |||
Old_Data[i] = New_Data[i]; | |||
} | |||
//Raise Block counter | |||
Block_Counter++; | |||
} | |||
//Perform calculation on last block | |||
//Check if Datalength is a multiple of 16 | |||
if(Incomplete_Block_Size == 0) | |||
{ | |||
//Copy last data into array | |||
for(i = 0; i < 16; i++) | |||
{ | |||
New_Data[i] = *Data; | |||
Data++; | |||
} | |||
//Preform XOR with Key 1 | |||
XOR(New_Data,Key_K1); | |||
//Preform XOR with old data | |||
XOR(New_Data,Old_Data); | |||
//Preform last AES routine | |||
// read NwkSkey from PROGMEM | |||
AES_Encrypt(New_Data, _NwkSkey); | |||
} | |||
else | |||
{ | |||
//Copy the remaining data and fill the rest | |||
for(i = 0; i < 16; i++) | |||
{ | |||
if(i < Incomplete_Block_Size) | |||
{ | |||
New_Data[i] = *Data; | |||
Data++; | |||
} | |||
if(i == Incomplete_Block_Size) | |||
{ | |||
New_Data[i] = 0x80; | |||
} | |||
if(i > Incomplete_Block_Size) | |||
{ | |||
New_Data[i] = 0x00; | |||
} | |||
} | |||
//Preform XOR with Key 2 | |||
XOR(New_Data,Key_K2); | |||
//Preform XOR with Old data | |||
XOR(New_Data,Old_Data); | |||
//Preform last AES routine | |||
AES_Encrypt(New_Data, _NwkSkey); | |||
} | |||
Final_MIC[0] = New_Data[0]; | |||
Final_MIC[1] = New_Data[1]; | |||
Final_MIC[2] = New_Data[2]; | |||
Final_MIC[3] = New_Data[3]; | |||
} | |||
void LoRaWAN::Generate_Keys(unsigned char *K1, unsigned char *K2) | |||
{ | |||
unsigned char i; | |||
unsigned char MSB_Key; | |||
//Encrypt the zeros in K1 with the NwkSkey | |||
AES_Encrypt(K1,_NwkSkey); | |||
//Create K1 | |||
//Check if MSB is 1 | |||
if((K1[0] & 0x80) == 0x80) | |||
{ | |||
MSB_Key = 1; | |||
} | |||
else | |||
{ | |||
MSB_Key = 0; | |||
} | |||
//Shift K1 one bit left | |||
Shift_Left(K1); | |||
//if MSB was 1 | |||
if(MSB_Key == 1) | |||
{ | |||
K1[15] = K1[15] ^ 0x87; | |||
} | |||
//Copy K1 to K2 | |||
for( i = 0; i < 16; i++) | |||
{ | |||
K2[i] = K1[i]; | |||
} | |||
//Check if MSB is 1 | |||
if((K2[0] & 0x80) == 0x80) | |||
{ | |||
MSB_Key = 1; | |||
} | |||
else | |||
{ | |||
MSB_Key = 0; | |||
} | |||
//Shift K2 one bit left | |||
Shift_Left(K2); | |||
//Check if MSB was 1 | |||
if(MSB_Key == 1) | |||
{ | |||
K2[15] = K2[15] ^ 0x87; | |||
} | |||
} | |||
void LoRaWAN::Shift_Left(unsigned char *Data) | |||
{ | |||
unsigned char i; | |||
unsigned char Overflow = 0; | |||
//unsigned char High_Byte, Low_Byte; | |||
for(i = 0; i < 16; i++) | |||
{ | |||
//Check for overflow on next byte except for the last byte | |||
if(i < 15) | |||
{ | |||
//Check if upper bit is one | |||
if((Data[i+1] & 0x80) == 0x80) | |||
{ | |||
Overflow = 1; | |||
} | |||
else | |||
{ | |||
Overflow = 0; | |||
} | |||
} | |||
else | |||
{ | |||
Overflow = 0; | |||
} | |||
//Shift one left | |||
Data[i] = (Data[i] << 1) + Overflow; | |||
} | |||
} | |||
void LoRaWAN::XOR(unsigned char *New_Data,unsigned char *Old_Data) | |||
{ | |||
unsigned char i; | |||
for(i = 0; i < 16; i++) | |||
{ | |||
New_Data[i] = New_Data[i] ^ Old_Data[i]; | |||
} | |||
} | |||
/* | |||
***************************************************************************************** | |||
* Title : AES_Encrypt | |||
* Description : | |||
***************************************************************************************** | |||
*/ | |||
void LoRaWAN::AES_Encrypt(unsigned char *Data, unsigned char *Key) | |||
{ | |||
unsigned char Row, Column, Round = 0; | |||
unsigned char Round_Key[16]; | |||
unsigned char State[4][4]; | |||
// Copy input to State arry | |||
for( Column = 0; Column < 4; Column++ ) | |||
{ | |||
for( Row = 0; Row < 4; Row++ ) | |||
{ | |||
State[Row][Column] = Data[Row + (Column << 2)]; | |||
} | |||
} | |||
// Copy key to round key | |||
memcpy( &Round_Key[0], &Key[0], 16 ); | |||
// Add round key | |||
AES_Add_Round_Key( Round_Key, State ); | |||
// Preform 9 full rounds with mixed collums | |||
for( Round = 1 ; Round < 10 ; Round++ ) | |||
{ | |||
// Perform Byte substitution with S table | |||
for( Column = 0 ; Column < 4 ; Column++ ) | |||
{ | |||
for( Row = 0 ; Row < 4 ; Row++ ) | |||
{ | |||
State[Row][Column] = AES_Sub_Byte( State[Row][Column] ); | |||
} | |||
} | |||
// Perform Row Shift | |||
AES_Shift_Rows(State); | |||
// Mix Collums | |||
AES_Mix_Collums(State); | |||
// Calculate new round key | |||
AES_Calculate_Round_Key(Round, Round_Key); | |||
// Add the round key to the Round_key | |||
AES_Add_Round_Key(Round_Key, State); | |||
} | |||
// Perform Byte substitution with S table whitout mix collums | |||
for( Column = 0 ; Column < 4 ; Column++ ) | |||
{ | |||
for( Row = 0; Row < 4; Row++ ) | |||
{ | |||
State[Row][Column] = AES_Sub_Byte(State[Row][Column]); | |||
} | |||
} | |||
// Shift rows | |||
AES_Shift_Rows(State); | |||
// Calculate new round key | |||
AES_Calculate_Round_Key( Round, Round_Key ); | |||
// Add round key | |||
AES_Add_Round_Key( Round_Key, State ); | |||
// Copy the State into the data array | |||
for( Column = 0; Column < 4; Column++ ) | |||
{ | |||
for( Row = 0; Row < 4; Row++ ) | |||
{ | |||
Data[Row + (Column << 2)] = State[Row][Column]; | |||
} | |||
} | |||
} // AES_Encrypt | |||
/* | |||
***************************************************************************************** | |||
* Title : AES_Add_Round_Key | |||
* Description : | |||
***************************************************************************************** | |||
*/ | |||
void LoRaWAN::AES_Add_Round_Key(unsigned char *Round_Key, unsigned char (*State)[4]) | |||
{ | |||
unsigned char Row, Collum; | |||
for(Collum = 0; Collum < 4; Collum++) | |||
{ | |||
for(Row = 0; Row < 4; Row++) | |||
{ | |||
State[Row][Collum] ^= Round_Key[Row + (Collum << 2)]; | |||
} | |||
} | |||
} // AES_Add_Round_Key | |||
/* | |||
***************************************************************************************** | |||
* Title : AES_Sub_Byte | |||
* Description : | |||
***************************************************************************************** | |||
*/ | |||
unsigned char LoRaWAN::AES_Sub_Byte(unsigned char Byte) | |||
{ | |||
// unsigned char S_Row,S_Collum; | |||
// unsigned char S_Byte; | |||
// | |||
// S_Row = ((Byte >> 4) & 0x0F); | |||
// S_Collum = ((Byte >> 0) & 0x0F); | |||
// S_Byte = S_Table [S_Row][S_Collum]; | |||
//return S_Table [ ((Byte >> 4) & 0x0F) ] [ ((Byte >> 0) & 0x0F) ]; // original | |||
return pgm_read_byte(&(S_Table [((Byte >> 4) & 0x0F)] [((Byte >> 0) & 0x0F)])); | |||
} // AES_Sub_Byte | |||
/* | |||
***************************************************************************************** | |||
* Title : AES_Shift_Rows | |||
* Description : | |||
***************************************************************************************** | |||
*/ | |||
void LoRaWAN::AES_Shift_Rows(unsigned char (*State)[4]) | |||
{ | |||
unsigned char Buffer; | |||
//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; | |||
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; | |||
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; | |||
} // AES_Shift_Rows | |||
/* | |||
***************************************************************************************** | |||
* Title : AES_Mix_Collums | |||
* Description : | |||
***************************************************************************************** | |||
*/ | |||
void LoRaWAN::AES_Mix_Collums(unsigned char (*State)[4]) | |||
{ | |||
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] ^= 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]; | |||
} | |||
} // AES_Mix_Collums | |||
/* | |||
***************************************************************************************** | |||
* Title : AES_Calculate_Round_Key | |||
* Description : | |||
***************************************************************************************** | |||
*/ | |||
void LoRaWAN::AES_Calculate_Round_Key(unsigned char Round, unsigned char *Round_Key) | |||
{ | |||
unsigned char i, j, b, Rcon; | |||
unsigned char Temp[4]; | |||
//Calculate Rcon | |||
Rcon = 0x01; | |||
while(Round != 1) | |||
{ | |||
b = Rcon & 0x80; | |||
Rcon = Rcon << 1; | |||
if(b == 0x80) | |||
{ | |||
Rcon ^= 0x1b; | |||
} | |||
Round--; | |||
} | |||
// Calculate first Temp | |||
// Copy laste byte from previous key and subsitute the byte, but shift the array contents around by 1. | |||
Temp[0] = AES_Sub_Byte( Round_Key[12 + 1] ); | |||
Temp[1] = AES_Sub_Byte( Round_Key[12 + 2] ); | |||
Temp[2] = AES_Sub_Byte( Round_Key[12 + 3] ); | |||
Temp[3] = AES_Sub_Byte( Round_Key[12 + 0] ); | |||
// XOR with Rcon | |||
Temp[0] ^= Rcon; | |||
// Calculate new key | |||
for(i = 0; i < 4; i++) | |||
{ | |||
for(j = 0; j < 4; j++) | |||
{ | |||
Round_Key[j + (i << 2)] ^= Temp[j]; | |||
Temp[j] = Round_Key[j + (i << 2)]; | |||
} | |||
} | |||
} // AES_Calculate_Round_Key |
@ -1,70 +0,0 @@ | |||
/* | |||
LoRaWAN.h - Library header file for LoRaWAN protocol, uses RFM95W module | |||
Created by Leo Korbee, March 31, 2018. | |||
Released into the public domain. | |||
@license Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) | |||
Thanks to all the folks who contributed before me on this code. | |||
*/ | |||
#include "RFM95.h" | |||
#include "Arduino.h" | |||
#ifndef LoRaWAN_h | |||
#define LoRaWAN_h | |||
// for AES encryption | |||
static const unsigned char PROGMEM 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} | |||
}; | |||
class LoRaWAN | |||
{ | |||
public: | |||
LoRaWAN(RFM95 &rfm95); | |||
void setKeys(unsigned char NwkSkey[], unsigned char AppSkey[], unsigned char DevAddr[]); | |||
void Send_Data(unsigned char *Data, unsigned char Data_Length, unsigned int Frame_Counter_Tx, lora_dr_t datarate,unsigned char Frame_Port); | |||
private: | |||
RFM95 *_rfm95; | |||
// remember arrays are pointers! | |||
unsigned char *_NwkSkey; | |||
unsigned char *_AppSkey; | |||
unsigned char *_DevAddr; | |||
void RFM_Send_Package(unsigned char *RFM_Tx_Package, unsigned char Package_Length); | |||
// security stuff: | |||
void Encrypt_Payload(unsigned char *Data, unsigned char Data_Length, unsigned int Frame_Counter, unsigned char Direction); | |||
void Calculate_MIC(unsigned char *Data, unsigned char *Final_MIC, unsigned char Data_Length, unsigned int Frame_Counter, unsigned char Direction); | |||
void Generate_Keys(unsigned char *K1, unsigned char *K2); | |||
void Shift_Left(unsigned char *Data); | |||
void XOR(unsigned char *New_Data,unsigned char *Old_Data); | |||
void AES_Encrypt(unsigned char *Data, unsigned char *Key); | |||
void AES_Add_Round_Key(unsigned char *Round_Key, unsigned char (*State)[4]); | |||
unsigned char AES_Sub_Byte(unsigned char Byte); | |||
void AES_Shift_Rows(unsigned char (*State)[4]); | |||
void AES_Mix_Collums(unsigned char (*State)[4]); | |||
void AES_Calculate_Round_Key(unsigned char Round, unsigned char *Round_Key); | |||
}; | |||
#endif |
@ -1,9 +1,23 @@ | |||
# ATTNode v3 Firmware (WiP) | |||
THIS IS STILL WORK IN PROGRESS AND VERY MUCH EXPERIMENTAL! | |||
## Disclaimer | |||
This is the Work in Progress Repository for ATTNode v3 compatible firmware. | |||
THIS IS STILL WORK IN PROGRESS! | |||
As there is no PlatformIO Support for the ATTiny3216 yet, it is (for now) developed using Arduino IDE and the [MegaTinyCore](https://github.com/SpenceKonde/megaTinyCore) | |||
## Configuration and Programming | |||
Programming is done using a [MicroUPDI Programmer](https://github.com/MCUdude/microUPDI) - for other pogramming variants see the MegaTinyCore documentation. | |||
This is the Work in Progress Repository for ATTNode v3 compatible firmware. At the moment it supports LoRa communication using OTAA and a BME280 sensor, as well as deep sleep between measurements. | |||
As there is no PlatformIO Support for the ATTiny3216 yet, it is (for now) developed using Arduino IDE and the [MegaTinyCore](https://github.com/SpenceKonde/megaTinyCore). You also need to set the correct Settings for programming the ATTiny3216 in ArduionIDE. Here is a screenshot of the settings I use: | |||
 | |||
You also need to install the MCCI Arduino LMIC Library form the IDEs Library Manager or from https://github.com/mcci-catena/arduino-lmic | |||
Before Compiling and Flashing make sure to copy config.h.example to config.h and set your LoRa OTAA Keys there. You can also set the Sending Interval and used Sensors there. | |||
Programming is done using a [MicroUPDI Programmer](https://github.com/MCUdude/microUPDI) - for other pogramming variants see the MegaTinyCore documentation. | |||
## Acknowledgements | |||
Parts of this code where kindly provided by [@shempe](https://twitter.com/shempe) |
@ -1,342 +0,0 @@ | |||
/* | |||
RFM95.cpp - Library for RFM95 LoRa module. | |||
Created by Leo Korbee, March 31, 2018. | |||
Released into the public domain. | |||
@license Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) | |||
Thanks to all the folks who contributed on the base of this code. | |||
(Gerben den Hartog, et al - Ideetron.nl) | |||
*/ | |||
#include "Arduino.h" | |||
#include "RFM95.h" | |||
#include <SPI.h> | |||
// constructor | |||
RFM95::RFM95(int DIO0, int NSS) | |||
{ | |||
_DIO0 = DIO0; | |||
_NSS = NSS; | |||
// init tinySPI | |||
SPI.setDataMode(SPI_MODE0); | |||
SPI.begin(); | |||
} | |||
/* | |||
***************************************************************************************** | |||
* Description: Function used to initialize the RFM module on startup | |||
***************************************************************************************** | |||
*/ | |||
void RFM95::init() | |||
{ | |||
// set pinmodes input/output | |||
pinMode(_NSS, OUTPUT); | |||
pinMode(_DIO0, INPUT); | |||
// Set default Datarate Config SF7BW125 | |||
_sf = 0x74; | |||
_bw = 0x72; | |||
_mc = 0x04; | |||
// NSS for starting and stopping communication with the RFM95 module | |||
digitalWrite(_NSS, HIGH); | |||
//Switch RFM to sleep | |||
RFM_Write(0x01,0x00); | |||
//Set RFM in LoRa mode | |||
RFM_Write(0x01,0x80); | |||
//Set RFM in Standby mode wait on mode ready | |||
RFM_Write(0x01,0x81); | |||
/* | |||
while (digitalRead(DIO5) == LOW) | |||
{ | |||
} | |||
*/ | |||
delay(10); | |||
//Set carrair frequency | |||
// 868.100 MHz / 61.035 Hz = 14222987 = 0xD9068B | |||
RFM_Write(0x06,0xD9); | |||
RFM_Write(0x07,0x06); | |||
RFM_Write(0x08,0x8B); | |||
// Minmal Power | |||
//RFM_Write(0x09,0xF0); | |||
//PA pin (maximal power) | |||
RFM_Write(0x09,0xFF); | |||
//BW = 125 kHz, Coding rate 4/5, Explicit header mode | |||
RFM_Write(0x1D,0x72); | |||
//Spreading factor 7, PayloadCRC On | |||
RFM_Write(0x1E,0xB4); | |||
//Rx Timeout set to 37 symbols | |||
RFM_Write(0x1F,0x25); | |||
//Preamble length set to 8 symbols | |||
//0x0008 + 4 = 12 | |||
RFM_Write(0x20,0x00); | |||
RFM_Write(0x21,0x08); | |||
//Low datarate optimization off AGC auto on | |||
RFM_Write(0x26,0x0C); | |||
//Set LoRa sync word | |||
RFM_Write(0x39,0x34); | |||
//Set IQ to normal values | |||
RFM_Write(0x33,0x27); | |||
RFM_Write(0x3B,0x1D); | |||
//Set FIFO pointers | |||
//TX base adress | |||
RFM_Write(0x0E,0x80); | |||
//Rx base adress | |||
RFM_Write(0x0F,0x00); | |||
//Switch RFM to sleep | |||
RFM_Write(0x01,0x00); | |||
} | |||
/* | |||
***************************************************************************************** | |||
* Description : Funtion that writes a register from the RFM | |||
* | |||
* Arguments : RFM_Address Address of register to be written | |||
* RFM_Data Data to be written | |||
***************************************************************************************** | |||
*/ | |||
void RFM95::RFM_Write(unsigned char RFM_Address, unsigned char RFM_Data) | |||
{ | |||
//Set NSS pin Low to start communication | |||
digitalWrite(_NSS,LOW); | |||
//Send Addres with MSB 1 to make it a write command | |||
SPI.transfer(RFM_Address | 0x80); | |||
//Send Data | |||
SPI.transfer(RFM_Data); | |||
//Set NSS pin High to end communication | |||
digitalWrite(_NSS,HIGH); | |||
} | |||
/* | |||
***************************************************************************************** | |||
* Description : Funtion that reads a register from the RFM and returns the value | |||
* | |||
* Arguments : RFM_Address Address of register to be read | |||
* | |||
* Returns : Value of the register | |||
***************************************************************************************** | |||
*/ | |||
unsigned char RFM95::RFM_Read(unsigned char RFM_Address) | |||
{ | |||
unsigned char RFM_Data; | |||
//Set NSS pin low to start SPI communication | |||
digitalWrite(_NSS,LOW); | |||
//Send Address | |||
SPI.transfer(RFM_Address); | |||
//Send 0x00 to be able to receive the answer from the RFM | |||
RFM_Data = SPI.transfer(0x00); | |||
//Set NSS high to end communication | |||
digitalWrite(_NSS,HIGH); | |||
//Return received data | |||
return RFM_Data; | |||
} | |||
/* | |||
***************************************************************************************** | |||
* Description : Set Datarate and Spreading Factor | |||
* | |||
* Arguments : datarate Lora Datarate Enum (see RFM95.h) | |||
***************************************************************************************** | |||
*/ | |||
void RFM95::RFM_Set_Datarate(lora_dr_t datarate) { | |||
switch(datarate) { | |||
case SF7BW125: | |||
_sf = 0x74; | |||
_bw = 0x72; | |||
_mc = 0x04; | |||
break; | |||
case SF8BW125: | |||
_sf = 0x84; | |||
_bw = 0x72; | |||
_mc = 0x04; | |||
break; | |||
case SF9BW125: | |||
_sf = 0x94; | |||
_bw = 0x72; | |||
_mc = 0x04; | |||
break; | |||
case SF10BW125: | |||
_sf = 0xA4; | |||
_bw = 0x72; | |||
_mc = 0x04; | |||
break; | |||
case SF11BW125: | |||
_sf = 0xB4; | |||
_bw = 0x72; | |||
_mc = 0x0C; | |||
break; | |||
case SF12BW125: | |||
_sf = 0xC4; | |||
_bw = 0x72; | |||
_mc = 0x0C; | |||
break; | |||
default: | |||
_sf = 0x74; | |||
_bw = 0x72; | |||
_mc = 0x04; | |||
break; | |||
} | |||
} | |||
/* | |||
***************************************************************************************** | |||
* Description : Function for sending a package with the RFM | |||
* | |||
* Arguments : *RFM_Tx_Package Pointer to arry with data to be send | |||
* Package_Length Length of the package to send | |||
***************************************************************************************** | |||
*/ | |||
void RFM95::RFM_Send_Package(unsigned char *RFM_Tx_Package, unsigned char Package_Length) | |||
{ | |||
unsigned char i; | |||
// unsigned char RFM_Tx_Location = 0x00; | |||
//Set RFM in Standby mode wait on mode ready | |||
RFM_Write(0x01,0x81); | |||
/* | |||
while (digitalRead(DIO5) == LOW) | |||
{ | |||
} | |||
*/ | |||
delay(10); | |||
//Switch DIO0 to TxDone | |||
//RFM_Write(0x40,0x40); | |||
//Set carrier frequency | |||
/* | |||
fixed frequency | |||
// 868.100 MHz / 61.035 Hz = 14222987 = 0xD9068B | |||
_rfm95.RFM_Write(0x06,0xD9); | |||
_rfm95.RFM_Write(0x07,0x06); | |||
_rfm95.RFM_Write(0x08,0x8B); | |||
*/ | |||
//Channel 0 868.100 MHz / 61.035 Hz = 14222987 = 0xD9068B | |||
RFM_Write(0x06,0xD9); | |||
RFM_Write(0x07,0x06); | |||
RFM_Write(0x08,0x8B); | |||
// EU863-870 specifications | |||
/* | |||
// TCNT0 is timer0 continous timer, kind of random selection of frequency | |||
switch (TCNT0 % 8) | |||
{ | |||
case 0x00: //Channel 0 868.100 MHz / 61.035 Hz = 14222987 = 0xD9068B | |||
RFM_Write(0x06,0xD9); | |||
RFM_Write(0x07,0x06); | |||
RFM_Write(0x08,0x8B); | |||
break; | |||
case 0x01: //Channel 1 868.300 MHz / 61.035 Hz = 14226264 = 0xD91358 | |||
RFM_Write(0x06,0xD9); | |||
RFM_Write(0x07,0x13); | |||
RFM_Write(0x08,0x58); | |||
break; | |||
case 0x02: //Channel 2 868.500 MHz / 61.035 Hz = 14229540 = 0xD92024 | |||
RFM_Write(0x06,0xD9); | |||
RFM_Write(0x07,0x20); | |||
RFM_Write(0x08,0x24); | |||
break; | |||
// added five more channels | |||
case 0x03: // Channel 3 867.100 MHz / 61.035 Hz = 14206603 = 0xD8C68B | |||
RFM_Write(0x06,0xD8); | |||
RFM_Write(0x07,0xC6); | |||
RFM_Write(0x08,0x8B); | |||
break; | |||
case 0x04: // Channel 4 867.300 MHz / 61.035 Hz = 14209880 = 0xD8D358 | |||
RFM_Write(0x06,0xD8); | |||
RFM_Write(0x07,0xD3); | |||
RFM_Write(0x08,0x58); | |||
break; | |||
case 0x05: // Channel 5 867.500 MHz / 61.035 Hz = 14213156 = 0xD8E024 | |||
RFM_Write(0x06,0xD8); | |||
RFM_Write(0x07,0xE0); | |||
RFM_Write(0x08,0x24); | |||
break; | |||
case 0x06: // Channel 6 867.700 MHz / 61.035 Hz = 14216433 = 0xD8ECF1 | |||
RFM_Write(0x06,0xD8); | |||
RFM_Write(0x07,0xEC); | |||
RFM_Write(0x08,0xF1); | |||
break; | |||
case 0x07: // Channel 7 867.900 MHz / 61.035 Hz = 14219710 = 0xD8F9BE | |||
RFM_Write(0x06,0xD8); | |||
RFM_Write(0x07,0xF9); | |||
RFM_Write(0x08,0xBE); | |||
break; | |||
// FSK 868.800 Mhz => not used in this config | |||
// 869.525 - SF9BW125 (RX2 downlink only) for package received | |||
} | |||
*/ | |||
//SF7 BW 125 kHz | |||
RFM_Write(0x1E,_sf); //SF7 CRC On | |||
RFM_Write(0x1D,_bw); //125 kHz 4/5 coding rate explicit header mode | |||
RFM_Write(0x26,_mc); //Low datarate optimization off AGC auto on | |||
//Set IQ to normal values | |||
RFM_Write(0x33,0x27); | |||
RFM_Write(0x3B,0x1D); | |||
//Set payload length to the right length | |||
RFM_Write(0x22,Package_Length); | |||
//Get location of Tx part of FiFo | |||
//RFM_Tx_Location = RFM_Read(0x0E); | |||
//Set SPI pointer to start of Tx part in FiFo | |||
//RFM_Write(0x0D,RFM_Tx_Location); | |||
RFM_Write(0x0D,0x80); // hardcoded fifo location according RFM95 specs | |||
//Write Payload to FiFo | |||
for (i = 0;i < Package_Length; i++) | |||
{ | |||
RFM_Write(0x00,*RFM_Tx_Package); | |||
RFM_Tx_Package++; | |||
} | |||
//Switch RFM to Tx | |||
RFM_Write(0x01,0x83); | |||
//Wait for TxDone | |||
//while( digitalRead(_DIO0) == LOW ) | |||
//{ | |||
//} | |||
while((RFM_Read(0x12) & 0x08) != 0x08) | |||
{ | |||
} | |||
//Clear interrupt | |||
RFM_Write(0x12,0x08); | |||
//Switch RFM to sleep | |||
RFM_Write(0x01,0x00); | |||
} |
@ -1,41 +0,0 @@ | |||
/* | |||
RFM95.h - Library header file for RFM95 LoRa module. | |||
Created by Leo Korbee, March 31, 2018. | |||
Released into the public domain. | |||
@license Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) | |||
Thanks to all the folks who contributed beforme me on this code. | |||
*/ | |||
#ifndef RFM95_h | |||
#define RMF95_h | |||
#include "Arduino.h" | |||
typedef enum lora_dr | |||
{ | |||
SF7BW125, | |||
SF8BW125, | |||
SF9BW125, | |||
SF10BW125, | |||
SF11BW125, | |||
SF12BW125, | |||
} lora_dr_t; | |||
class RFM95 | |||
{ | |||
public: | |||
RFM95(int DIO0, int NSS); | |||
void init(); | |||
void RFM_Write(unsigned char RFM_Address, unsigned char RFM_Data); | |||
unsigned char RFM_Read(unsigned char RFM_Address); | |||
void RFM_Send_Package(unsigned char *RFM_Tx_Package, unsigned char Package_Length); | |||
void RFM_Set_Datarate(lora_dr_t datarate); | |||
private: | |||
int _DIO0; | |||
int _NSS; | |||
unsigned char _sf, _bw, _mc; | |||
}; | |||
#endif |
@ -1,12 +0,0 @@ | |||
// Onboard LED is on PA7 | |||
#define LED_PIN PIN_PA7 | |||
// How many 32s Intervals to sleep - so 2 SLEEP_TIME 1 == 32s | |||
#define SLEEP_TIME 10 | |||
// Information from The Things Network, device configuration ACTIVATION METHOD: ABP, msb left | |||
unsigned char NwkSkey[16] = { 0x39, 0x15, 0x63, 0x65, 0x22, 0xD4, 0xD1, 0x04, 0x6E, 0x3C, 0x68, 0xB2, 0xE7, 0xE3, 0x3C, 0x1D }; | |||
unsigned char AppSkey[16] = { 0xE6, 0xC2, 0x51, 0x4A, 0xF4, 0x3A, 0x1D, 0x7C, 0x31, 0x83, 0x2C, 0xE6, 0xA6, 0x5E, 0x41, 0x1D }; | |||
unsigned char DevAddr[4] = { 0x26, 0x01, 0x38, 0xB3 }; |
@ -0,0 +1,150 @@ | |||
#include <Arduino.h> | |||
#include <stdint.h> | |||
#include <Wire.h> | |||
#include "BME280.h" | |||
BME280::BME280() {} | |||
void BME280::getCalData() { | |||
dig_T1 = read16_LE(0x88); | |||
dig_T2 = readS16_LE(0x8A); | |||
dig_T3 = readS16_LE(0x8C); | |||
dig_P1 = read16_LE(0x8E); | |||
dig_P2 = readS16_LE(0x90); | |||
dig_P3 = readS16_LE(0x92); | |||
dig_P4 = readS16_LE(0x94); | |||
dig_P5 = readS16_LE(0x96); | |||
dig_P6 = readS16_LE(0x98); | |||
dig_P7 = readS16_LE(0x9A); | |||
dig_P8 = readS16_LE(0x9C); | |||
dig_P9 = readS16_LE(0x9E); | |||
dig_H1 = read8(0xA1); | |||
dig_H2 = readS16_LE(0xE1); | |||
dig_H3 = read8(0xE3); | |||
dig_H4 = (read8(0xE4) << 4) | (read8(0xE5) & 0xF); | |||
dig_H5 = (read8(0xE6) << 4) | (read8(0xE5) >> 4); | |||
dig_H6 = (int8_t)read8(0xE7); | |||
} | |||
int32_t BME280::compensate_t(int32_t adc_T) | |||
{ | |||
int32_t var1, var2, T; | |||
var1 = ((((adc_T>>3) - ((int32_t)dig_T1<<1))) * ((int32_t)dig_T2)) >> 11; | |||
var2 = (((((adc_T>>4) - ((int32_t)dig_T1)) * ((adc_T>>4) - ((int32_t)dig_T1))) >> 12) * ((int32_t)dig_T3)) >> 14; | |||
t_fine = var1 + var2; | |||
T = (t_fine * 5 + 128) >> 8; | |||
return T; | |||
} | |||
int32_t BME280::compensate_p(int32_t adc_P) | |||
{ | |||
int32_t var1, var2; | |||
uint32_t p; | |||
var1 = (((int32_t)t_fine)>>1) -(int32_t)64000; | |||
var2 = (((var1>>2) * (var1>>2)) >> 11 ) * ((int32_t)dig_P6); | |||
var2 = var2 + ((var1*((int32_t)dig_P5))<<1);var2 = (var2>>2)+(((int32_t)dig_P4)<<16); | |||
var1 = (((dig_P3 * (((var1>>2) * (var1>>2)) >> 13 )) >> 3) + ((((int32_t)dig_P2) * var1)>>1))>>18; | |||
var1 =((((32768+var1))*((int32_t)dig_P1))>>15); | |||
if (var1 == 0) { | |||
return 0; | |||
} | |||
p = (((uint32_t)(((int32_t)1048576)-adc_P)-(var2>>12)))*3125; | |||
if (p < 0x80000000) { | |||
p = (p << 1) / ((uint32_t)var1); | |||
} else { | |||
p = (p / (uint32_t)var1) * 2; | |||
} | |||
var1 = (((int32_t)dig_P9) * ((int32_t)(((p>>3) * (p>>3))>>13)))>>12; | |||
var2 = (((int32_t)(p>>2)) * ((int32_t)dig_P8))>>13; | |||
p = (uint32_t)((int32_t)p + ((var1 + var2 + dig_P7) >> 4)); | |||
return p; | |||
} | |||
int32_t BME280::compensate_h(int32_t adc_H) | |||
{ | |||
int32_t v_x1_u32r; | |||
v_x1_u32r=(t_fine-((int32_t)76800)); | |||
v_x1_u32r=(((((adc_H<<14)-(((int32_t)dig_H4)<<20)-(((int32_t)dig_H5)*v_x1_u32r))+ | |||
((int32_t)16384))>>15)*(((((((v_x1_u32r*((int32_t)dig_H6))>>10)* | |||
(((v_x1_u32r*((int32_t)dig_H3))>>11)+((int32_t)32768)))>>10)+ | |||
((int32_t)2097152))*((int32_t)dig_H2)+8192)>>14)); | |||
v_x1_u32r=(v_x1_u32r-(((((v_x1_u32r>>15)*(v_x1_u32r>>15))>>7)*((int32_t)dig_H1))>>4)); | |||
v_x1_u32r=(v_x1_u32r < 0 ? 0 : v_x1_u32r); | |||
v_x1_u32r=(v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r); | |||
return (uint32_t)((v_x1_u32r>>12)/10); | |||
} | |||
void BME280::getData(int32_t *t, int32_t *p, int32_t *h) { | |||
int32_t UP, UT, UH; | |||
int32_t rawP, rawT; | |||
// Trigger Measurement | |||
// Set Sensor Config | |||
write8(0xF2, 0b00000001); // 1x Oversampling for Humidity | |||
write8(0xF4, 0b00100101); // 1x Oversampling for Temperature, Pressure, Forced Mode | |||
delay(10); | |||
// Read Pressure | |||
rawP = read16(0xF7); | |||
rawP <<= 8; | |||
rawP |= read8(0xF9); | |||
UP = rawP >> 4; | |||
// Read Temperature | |||
rawT = read16(0xFA); | |||
rawT <<= 8; | |||
rawT |= read8(0xFC); | |||
UT = rawT >> 4; | |||
// Read Humidity | |||
UH = read16(0xFD); | |||
// Compensate Values and Return | |||
*t = compensate_t(UT); | |||
*p = compensate_p(UP); | |||
*h = compensate_h(UH); | |||
} | |||
uint8_t BME280::read8(uint8_t addr) { | |||
Wire.beginTransmission(BME280_I2CADDR); | |||
Wire.write(addr); | |||
Wire.endTransmission(); | |||
Wire.requestFrom(BME280_I2CADDR, 1); | |||
uint8_t ret = Wire.read(); | |||
return ret; | |||
} | |||
uint16_t BME280::read16(uint8_t addr) { | |||
Wire.beginTransmission(BME280_I2CADDR); | |||
Wire.write(addr); | |||
Wire.endTransmission(); | |||
Wire.requestFrom(BME280_I2CADDR, 2); | |||
uint16_t ret = (Wire.read() << 8) | Wire.read(); | |||
return ret; | |||
} | |||
uint16_t BME280::read16_LE(uint8_t addr) { | |||
uint16_t temp = read16(addr); | |||
return (temp >> 8) | (temp << 8); | |||
} | |||
int16_t BME280::readS16(uint8_t addr) { | |||
return (int16_t)read16(addr); | |||
} | |||
int16_t BME280::readS16_LE(uint8_t addr) { | |||
return (int16_t)read16_LE(addr); | |||
} | |||
void BME280::write8(uint8_t addr, uint8_t data) { | |||
Wire.beginTransmission(BME280_I2CADDR); | |||
Wire.write(addr); | |||
Wire.write(data); | |||
Wire.endTransmission(); | |||
} |
@ -0,0 +1,42 @@ | |||
#ifndef BME280_H | |||
#define BME280_H | |||
#include <stdint.h> | |||
#define BME280_I2CADDR 0x76 | |||
class BME280 | |||
{ | |||
private: | |||
// Variables for Calibration Values | |||
uint8_t dig_H1, dig_H3; | |||
int8_t dig_H6; | |||
uint16_t dig_T1, dig_P1; | |||
int16_t dig_T2, dig_T3, dig_P2, dig_P3, dig_P4, dig_P5, dig_P6, dig_P7, dig_P8, dig_P9, dig_H2, dig_H4, dig_H5; | |||
// Helper Variable | |||
int32_t t_fine; | |||
// Functions for Calculation compensated Values | |||
int32_t compensate_t(int32_t); | |||
int32_t compensate_p(int32_t); | |||
int32_t compensate_h(int32_t); | |||
// Helper Functions for Reading / Writing I2C Data | |||
uint8_t read8(uint8_t addr); | |||
uint16_t read16(uint8_t addr); | |||
uint16_t read16_LE(uint8_t addr); | |||
int16_t readS16(uint8_t addr); | |||
int16_t readS16_LE(uint8_t addr); | |||
void write8(uint8_t addr, uint8_t data); | |||
public: | |||
BME280(void); | |||
// Get Calibration Data from Sensor | |||
void getCalData(void); | |||
// Read Pressure From Sensor | |||
void getData(int32_t *t, int32_t *p, int32_t *h); | |||
}; | |||
#endif |
@ -0,0 +1,22 @@ | |||
// ATTNode v3 Onboard LED is on PIN_PA7 | |||
#define LED_PIN PIN_PA7 | |||
// Define which Sensor is installed | |||
#define HAS_BME280 | |||
// How many Minutes to sleep between Measuring/Sending | |||
// Actual Sleep Time is SLEEP_TIME*2*32 Seconds due to the 32s sleep intervals of the ATTiny3216 | |||
#define SLEEP_TIME 10 | |||
// Specify TTN EU Bandplan and LoRa Chip for the LMIC | |||
// See LMIC documentation / project_config.h for more options | |||
#define CFG_eu868 1 | |||
#define CFG_sx1276_radio 1 | |||
// Keys for OTAA Mode | |||
// APPEUI and DEVEUI from TTN, LSB! | |||
static const u1_t PROGMEM APPEUI[8]={ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; | |||
static const u1_t PROGMEM DEVEUI[8]={ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; | |||
// APPKey from TTN, MSB! | |||
static const u1_t PROGMEM APPKEY[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; |
@ -0,0 +1,181 @@ | |||
#include <Arduino.h> | |||
#include <avr/sleep.h> | |||
#include <avr/interrupt.h> | |||
#include <SPI.h> | |||
#include <Wire.h> | |||
// Use the local config.h for LMIC Configuration | |||
#define ARDUINO_LMIC_PROJECT_CONFIG_H config.h | |||
#include <lmic.h> | |||
#include <hal/hal.h> | |||
#include "config.h" | |||
#ifdef HAS_BME280 | |||
#include "BME280.h" | |||
BME280 sensor; | |||
#endif | |||
// Define some LMIC Callbacks and Variables | |||
void os_getArtEui (u1_t* buf) { | |||
memcpy_P(buf, APPEUI, 8); | |||
} | |||
void os_getDevEui (u1_t* buf) { | |||
memcpy_P(buf, DEVEUI, 8); | |||
} | |||
void os_getDevKey (u1_t* buf) { | |||
memcpy_P(buf, APPKEY, 16); | |||
} | |||
static osjob_t sendjob; | |||
// Pin-Mapping for ATTNode v3 | |||
const lmic_pinmap lmic_pins = { | |||
.nss = PIN_PA5, | |||
.rxtx = LMIC_UNUSED_PIN, | |||
.rst = LMIC_UNUSED_PIN, | |||
.dio = {PIN_PA4, PIN_PA6, LMIC_UNUSED_PIN}, | |||
}; | |||
// List of unused Pins - will be disabled for Power Saving | |||
const int disabledPins[] = {PIN_PB5, PIN_PB4, PIN_PB3, PIN_PB2, PIN_PB1, PIN_PB0, PIN_PC3, PIN_PC2, PIN_PC1, PIN_PC0}; | |||
// ISR Routine for Sleep | |||
ISR(RTC_PIT_vect) | |||
{ | |||
/* Clear interrupt flag by writing '1' (required) */ | |||
RTC.PITINTFLAGS = RTC_PI_bm; | |||
} | |||
// Sleep Routine | |||
void sleep_32s() { | |||
cli(); | |||
while (RTC.PITSTATUS > 0) {} | |||
RTC.PITINTCTRL = RTC_PI_bm; | |||
// 32 Sekunden | |||
RTC.PITCTRLA = RTC_PERIOD_CYC32768_gc | RTC_PITEN_bm; | |||
while (RTC.PITSTATUS & RTC_CTRLBUSY_bm) {} | |||
set_sleep_mode(SLEEP_MODE_PWR_DOWN); | |||
sleep_enable(); | |||
sei(); | |||
sleep_cpu(); | |||
sleep_disable(); | |||
sei(); | |||
} | |||
// LMIC Callback Handling | |||
void onEvent(ev_t ev) { | |||
switch (ev) { | |||
case EV_JOINED: | |||
// Disable LinkCheck | |||
LMIC_setLinkCheckMode(0); | |||
break; | |||
case EV_TXCOMPLETE: | |||
// Schedule Next Transmit | |||
do_send(&sendjob); | |||
// Got to sleep for specified Time | |||
for (int i = 0; i < int(SLEEP_TIME*2); i++) | |||
sleep_32s(); | |||
break; | |||
} | |||
} | |||
// Get Battery Voltage | |||
uint16_t readSupplyVoltage() { //returns value in millivolts to avoid floating point | |||
uint16_t temp = 0; | |||
analogReference(VDD); | |||
VREF.CTRLA = VREF_ADC0REFSEL_1V5_gc; | |||
ADC0.CTRLD = ADC_INITDLY_DLY256_gc; | |||
ADC0_CTRLB = ADC_SAMPNUM_ACC64_gc; | |||
uint16_t reading = analogRead(ADC_INTREF); | |||
temp = reading / 64; | |||
uint32_t intermediate = 1534500; | |||
reading = 0; | |||
reading = intermediate / temp; | |||
return reading; | |||
} | |||
// Blink x times | |||
#ifdef LED_PIN | |||
void blink(uint8_t num) { | |||
pinMode(LED_PIN, OUTPUT); | |||
digitalWrite(LED_PIN, 0); | |||
for (uint8_t i = 0; i < num * 2; i++) { | |||
digitalWrite(LED_PIN, !digitalRead(LED_PIN)); | |||
delay(5); | |||
} | |||
digitalWrite(LED_PIN, 0); | |||
} | |||
#endif | |||
// Read Sensors and Send Data | |||
// All Sensor Code and Data Preparation goes here | |||
void do_send(osjob_t* j) { | |||
// Prepare LoRa Data Packet | |||
#ifdef HAS_BME280 | |||
struct lora_data { | |||
uint8_t bat; | |||
int32_t temperature; | |||
int32_t humidity; | |||
int32_t pressure; | |||
} __attribute__ ((packed)) data; | |||
#endif | |||
if (LMIC.opmode & OP_TXRXPEND) { | |||
delay(10); | |||
} else { | |||
// Add Battery Voltage (0.2V Accuracy stored in 1 byte) | |||
uint32_t batv = readSupplyVoltage(); | |||
data.bat = (uint8_t)(batv / 20); | |||
if (batv % 20 > 9) | |||
data.bat += 1; | |||
#ifdef HAS_BME280 | |||
sensor.getData(&data.temperature, &data.pressure, &data.humidity); | |||
#endif | |||
// Queue Paket for Sending | |||
LMIC_setTxData2(1, (unsigned char *)&data, sizeof(data), 0); | |||
} | |||
} | |||
void setup() | |||
{ | |||
// Initialize SPI and I2C | |||
Wire.begin(); | |||
SPI.begin(); | |||
// Disable unused Pins (for power saving) | |||
for (int i = 0; i < (sizeof(disabledPins) / sizeof(disabledPins[0])) - 1; i++) | |||
pinMode(disabledPins[i], INPUT_PULLUP); | |||
// Set RTC | |||
while (RTC.STATUS > 0) {} | |||
RTC.CLKSEL = RTC_CLKSEL_INT1K_gc; | |||
while (RTC.PITSTATUS > 0) {} | |||
// Initialize Sensor(s) | |||
#ifdef HAS_BME280 | |||
sensor.getCalData(); | |||
#endif | |||
// Setup LMIC | |||
os_init(); | |||
LMIC_reset(); // Reset LMIC state and cancel all queued transmissions | |||
LMIC_setClockError(MAX_CLOCK_ERROR * 1 / 100); // Compensate for Clock Skew | |||
LMIC.dn2Dr = DR_SF9; // Downlink Band | |||
LMIC_setDrTxpow(DR_SF7, 14); // Default to SF7 | |||
// Schedule First Send (Triggers OTAA Join as well) | |||
do_send(&sendjob); | |||
#ifdef LED_PIN | |||
pinMode(LED_PIN, OUTPUT); | |||
blink(1); | |||
#endif | |||
} | |||