commit 6917b991564fe3c380fc232c0ba47782b78e3561 Author: Stefan Brand Date: Sat May 15 19:47:57 2021 +0200 Initial Prototype Firmware diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4d37315 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch +src/config.h \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..0f0d740 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..237efa6 --- /dev/null +++ b/README.md @@ -0,0 +1,79 @@ +# ATTMapper (WIP) + +A TTN Mapper based on the ATTNode v3. + +**Everything in this repository is work in progress, use at your own risk!** + +## Hardware + + * Attnode v3 with PCB-Edge SMA Antenna Mount + * Powerpack, LiPo Cell (500mA), LiPo Charger Board (You can use any 3,3V Power Source) + * NEO M8N GPS Breakout (Any 3.3V-Capable Module which is NMEA-Compatible should work) + * SSD1306 128x64 OLED-Display + * Button + +### Connections + +Connect the AttNode and Modules as follows: + +#### GPS + +| ATTNode | Module | +|---------|--------| +| Vin | VCC | +| GND | GND | +| Tx | RX | +| Rx | TX | + +#### OLED + +| ATTNode | Module | +|---------|--------| +| Vin | VCC | +| GND | GND | +| SDA | SDA | +| SCL | SCL | + +#### Button + +The Button has to be Connected between PC2 and GND on the ATTNode + +## Software + +This repository contains the PlatformIO project with the mapper firmware. Copy config.example.h to config.h and insert your keys and DeviceID from TTN. Compile and upload as normal (See AttNode v3 dokumentation for details). + +### Usage + +If a Button is connected you can use the following functions: + + * **Short press:** Schedule an imideate send, regardless of interval + * **Long Press (>1s):** Enter Setup Mode, change interval with short press, use another long press to leave setup + +## License + +Unless stated otherwise, everything in this repository is under the BSD 3-Clause License as follows: + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/include/README b/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/LoRaWAN/LoRaWAN.cpp b/lib/LoRaWAN/LoRaWAN.cpp new file mode 100644 index 0000000..4fa6cfc --- /dev/null +++ b/lib/LoRaWAN/LoRaWAN.cpp @@ -0,0 +1,671 @@ +/* + 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 \ No newline at end of file diff --git a/lib/LoRaWAN/LoRaWAN.h b/lib/LoRaWAN/LoRaWAN.h new file mode 100644 index 0000000..8f9d70b --- /dev/null +++ b/lib/LoRaWAN/LoRaWAN.h @@ -0,0 +1,66 @@ +/* + 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/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 \ No newline at end of file diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/lib/RFM95/RFM95.cpp b/lib/RFM95/RFM95.cpp new file mode 100644 index 0000000..b00a105 --- /dev/null +++ b/lib/RFM95/RFM95.cpp @@ -0,0 +1,333 @@ +/* + 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 +// 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); +} \ No newline at end of file diff --git a/lib/RFM95/RFM95.h b/lib/RFM95/RFM95.h new file mode 100644 index 0000000..4aa8c92 --- /dev/null +++ b/lib/RFM95/RFM95.h @@ -0,0 +1,37 @@ +/* + 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 \ No newline at end of file diff --git a/lib/gps/gps.cpp b/lib/gps/gps.cpp new file mode 100644 index 0000000..54eb7dc --- /dev/null +++ b/lib/gps/gps.cpp @@ -0,0 +1,78 @@ +#include + +byte CFG_RST[12] = {0xb5, 0x62, 0x06, 0x04, 0x04, 0x00, 0x00, 0x00, 0x01,0x00, 0x0F, 0x66}; + +void Gps::init() +{ + Serial.begin(9600); + Serial.setTimeout(2); + Serial.write(CFG_RST, sizeof(CFG_RST)); // Soft Reset GPS on Start + satsInView.begin(tGps, "GPGSV", 3); // NMEA Sentence GPGSV, Element 3 (Sat in View) +} + +void Gps::encode() +{ + int data; + unsigned long previousMillis = millis(); + + while((previousMillis + 100) > millis()) + { + while (Serial.available() ) + { + char data = Serial.read(); + tGps.encode(data); + } + } + //Serial.println(""); +} + +bool Gps::buildPacket(uint8_t txBuffer[9]) +{ + LatitudeBinary = ((tGps.location.lat() + 90) / 180.0) * 16777215; + LongitudeBinary = ((tGps.location.lng() + 180) / 360.0) * 16777215; + + txBuffer[0] = ( LatitudeBinary >> 16 ) & 0xFF; + txBuffer[1] = ( LatitudeBinary >> 8 ) & 0xFF; + txBuffer[2] = LatitudeBinary & 0xFF; + + txBuffer[3] = ( LongitudeBinary >> 16 ) & 0xFF; + txBuffer[4] = ( LongitudeBinary >> 8 ) & 0xFF; + txBuffer[5] = LongitudeBinary & 0xFF; + + altitudeGps = tGps.altitude.meters(); + txBuffer[6] = ( altitudeGps >> 8 ) & 0xFF; + txBuffer[7] = altitudeGps & 0xFF; + + hdopGps = tGps.hdop.value()/10; + txBuffer[8] = hdopGps & 0xFF; + + return true; +} + +void Gps::gdisplay(uint16_t dispBuffer[]) +{ + dispBuffer[0] = tGps.hdop.value()/10; + dispBuffer[1] = TinyGPSPlus::parseDecimal(satsInView.value()); + dispBuffer[2] = tGps.satellites.value(); + dispBuffer[3] = tGps.altitude.meters(); + dispBuffer[4] = tGps.speed.kmph(); +} + +bool Gps::checkGpsFix() +{ + encode(); + if (tGps.location.isValid() && + tGps.location.age() < 4000 && + tGps.hdop.isValid() && + tGps.hdop.value() <= 600 && + tGps.hdop.age() < 4000 && + tGps.altitude.isValid() && + tGps.altitude.age() < 4000 ) + { + return true; + } + else + { + return false; + } +} \ No newline at end of file diff --git a/lib/gps/gps.h b/lib/gps/gps.h new file mode 100644 index 0000000..0df3eb9 --- /dev/null +++ b/lib/gps/gps.h @@ -0,0 +1,23 @@ +#ifndef __GPS_H__ +#define __GPS_H__ + +#include + +class Gps +{ + public: + void init(); + bool checkGpsFix(); + bool buildPacket(uint8_t txBuffer[]); + void gdisplay(uint16_t dispBuffer[]); + void encode(); + + private: + uint32_t LatitudeBinary, LongitudeBinary; + uint16_t altitudeGps; + uint8_t hdopGps; + TinyGPSPlus tGps; + TinyGPSCustom satsInView; +}; + +#endif \ No newline at end of file diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..8fd62c0 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,48 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:ATtiny3216] +platform = atmelmegaavr +board = ATtiny3216 +framework = arduino + +## Board Config ## +# You might want to set f_cpu to 5MHz (5000000L) to allow operation at lower Battery Voltage - Use "Burn Fuses" after changing f_cpu +# Be aware that some Functions (like WS2812B LED Support) will not work at 5 HMz +board_build.f_cpu = 20000000L +board_hardware.oscillator = internal +board_hardware.bod = disabled + +## Debug Port Config ## +monitor_speed = 115200 +monitor_port = /dev/ttyACM1 + +## LMIC Config via Build Flags ## +build_flags = + -D CFG_eu868 + -D CFG_sx1276_radio + -D DISABLE_PING + -D DISABLE_BEACONS + -D ARDUINO_LMIC_PROJECT_CONFIG_H_SUPPRESS + -D DISABLE_JOIN + +## Programmer Config (MicroUPDI) ## +upload_port = usb +upload_protocol = xplainedmini_updi +upload_flags = + -p$BOARD_MCU + -P$UPLOAD_PORT + -c$UPLOAD_PROTOCOL + +lib_deps = + mcci-catena/MCCI LoRaWAN LMIC library @ ^3.3.0 + mikalhart/TinyGPSPlus @ ^1.0.2 + olikraus/U8g2 @ ^2.28.8 + lennarthennigs/Button2 @ ^1.6.1 \ No newline at end of file diff --git a/src/config.example.h b/src/config.example.h new file mode 100644 index 0000000..f7e8327 --- /dev/null +++ b/src/config.example.h @@ -0,0 +1,6 @@ +//******************************************************************* +// LoRa Config - Put Your Keys Here! +//******************************************************************* +unsigned char NwkSkey[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +unsigned char AppSkey[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +unsigned char DevAddr[4] = { 0x00, 0x00, 0x00, 0x00 }; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..bacf497 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,177 @@ +#include +#include +#include +#include +#include +#include + +// OLED +#include +U8X8_SSD1306_128X64_NONAME_HW_I2C oled(U8X8_PIN_NONE, PIN_PB0, PIN_PB1); + +// GPS +#include +Gps gps; +bool hasFix = false; +bool oldFix = false; + +// Button Handling +#include +Button2 btn = Button2(PIN_PC2); +bool adhocsend = false; + +// Modes for Display Menus and Navigation +enum Modes { + M_NORMAL, + M_SETUP +}; + +Modes amode = M_NORMAL; // Active Mode +Modes omode = M_NORMAL; // Previous Mode + +// LoRa +#include "config.h" // Contains LoRa ABP Keys +#define DIO0 PIN_PA4 +#define NSS PIN_PA5 +RFM95 rfm(DIO0, NSS); +LoRaWAN lora = LoRaWAN(rfm); +uint16_t Frame_Counter_Tx = 0x0000; + +// Some Status Variables +uint8_t interval = 20; // Sending Interval in Settings +uint8_t packets = 0; // Sent LoRa Packets +String statusmsg = ""; + +uint8_t loraBuffer[9]; // Lora Data Packet +uint32_t lastmillis; + +// Button Handler Function +void handler(Button2& btn) { + switch (btn.getClickType()) { + case SINGLE_CLICK: + if (amode == M_SETUP) { + interval = interval += 10; + if (interval > 40) + interval = 10; + } else if (amode == M_NORMAL) { + statusmsg = "Adhoc Request"; + adhocsend = true; + } + break; + case LONG_CLICK: + switch (amode) { + case M_NORMAL: + amode = M_SETUP; + break; + case M_SETUP: + amode = M_NORMAL; + break; + } + break; + } +} + +// Draw the Display +void updateDisplay() { + uint16_t buffer[6]; + gps.gdisplay(buffer); + + oled.home(); + oled.setFont(u8x8_font_victoriabold8_r); + + if (amode != omode) { + oled.clear(); + omode = amode; + } + + if (amode == M_SETUP) { + oled.println ("<< SETUP >>"); + oled.println (""); + oled.print("Interval: "); + oled.println(interval); + } else if (amode == M_NORMAL) { + if (hasFix) { + if (hasFix != oldFix){ + oled.clear(); + oldFix = hasFix; + } + oled.print("HDOP: "); + oled.println(buffer[0]); + oled.print("Sats: "); + oled.print(buffer[1]); + oled.print("/"); + oled.println(buffer[2]); + oled.print("Int: "); + oled.println(interval); + oled.print("Packet: "); + oled.println(Frame_Counter_Tx); + oled.print("Alt: "); + oled.println(buffer[3]); + oled.print("Speed: "); + oled.println(buffer[4]); + } else { + if (hasFix != oldFix){ + oled.clear(); + oldFix = hasFix; + } + oled.println("NO GPS FIX"); + oled.print("Sats: "); + oled.print(buffer[1]); + oled.print("/"); + oled.println(buffer[2]); + oled.print("Int: "); + oled.println(interval); + oled.print("Packet: "); + oled.println(Frame_Counter_Tx); + } + oled.print(statusmsg); + oled.println(" "); + } +} + +// Setup +void setup() { + // GPS Setup + Serial.begin(9600); + + // Button + btn.setLongClickTime(1000); + btn.setClickHandler(handler); + btn.setLongClickHandler(handler); + + // OLED Setup + Wire.begin(); + Wire.setClock(400000L); + oled.begin(); + oled.setPowerSave(0); + oled.setFlipMode(1); + oled.clear(); + + // Lora Initialization + rfm.init(); + lora.setKeys(NwkSkey, AppSkey, DevAddr); +} + +// Main Loop +void loop() { + hasFix = gps.checkGpsFix(); + uint32_t curmillis = millis(); + if (hasFix && (((uint32_t)(curmillis - lastmillis) >= interval*1000) || adhocsend)) { + if (adhocsend) { + statusmsg = "Sending (Adhoc)"; + } else { + statusmsg = "Sending"; + } + updateDisplay(); + adhocsend = false; + + gps.buildPacket(loraBuffer); + lora.Send_Data((unsigned char *)&loraBuffer, sizeof(loraBuffer), Frame_Counter_Tx, SF7BW125, 0x01); + Frame_Counter_Tx++; + lastmillis = millis(); + } else { + updateDisplay(); + } + statusmsg = ""; + btn.loop(); +} \ No newline at end of file