├── README.md ├── SerialCommand.cpp ├── SerialCommand.h ├── battery.cpp ├── battery.h ├── gps.cpp ├── gps.h ├── hardware.h ├── icons.h ├── images ├── battery.png ├── connecting.png ├── gps_connected.bmp ├── gps_disconnected.bmp ├── menu.png ├── network_connected.bmp ├── network_disconnected.bmp ├── survey.png ├── sweep results.png ├── sweep.png ├── transmit.bmp └── usb-connector.png ├── joystick.cpp ├── joystick.h ├── lorawan.cpp ├── lorawan.h ├── lorawan_network_tester.ino ├── menu.cpp ├── menu.h ├── screen.cpp ├── screen.h ├── settings.cpp ├── settings.h ├── state.cpp ├── state.h ├── survey.cpp ├── survey.h ├── sweep.cpp ├── sweep.h ├── usb.cpp └── usb.h /README.md: -------------------------------------------------------------------------------- 1 | # LoRaWAN Tester 2 | 3 | A network tester for LoRaWAN networks. 4 | 5 | ## Setup 6 | This is designed to work with the following hardware: 7 | - Adafruit Feather M0 (ATSAMD21) 8 | - Microchip RN2483 9 | - GPS (nema encoding e.g. Ubox or Quectel L80) 10 | - Analog joystick. 11 | - 1.8TFT ST7735 Display 12 | 13 | #### Required Libraries! 14 | - [NeoGPS] 15 | - [RN2483] (Forked from arduino-device-lib by TTN) 16 | - [FlashStorage] 17 | - [Arduino Serial Command] (Already included as we overwritten bits) 18 | - [Adafruit GFX] 19 | - [Adafruit ST7735] 20 | - [Axis Joystick] 21 | 22 | #### Installation and Configuration 23 | Hardware specific changes can be made to the "hardware.h" file. Upload this sketch to your Feather M0 using the Arduino IDE. Your IDE must have the [Adafruit SAMD] boards support added for this to work. 24 | 25 | ## Instructions for Use 26 | 27 | #### Menu 28 | 29 | On boot you will be presented with the "Menu" screen containing the following functions: 30 | - [Survey](#survey) 31 | - [Sweep](#sweep) 32 | - [Settings](#settings) 33 | - [USB](#usb) 34 | 35 | You can move between menu items by pushing up/down on the joystick. Click the joystick to activate the currently selected item. "Survey" and "Sweep" will not be available (marked with an 'X', rather than '>') if the required LoRaWAN network keys for the currently selected join mode are not present; see [USB](#usb) and [Settings - join](#join) 36 | 37 | ![Menu Image](https://raw.githubusercontent.com/danielknox/lorawan_network_tester/master/images/menu.png "Fig 1. Menu") 38 | 39 | #### Survey 40 | "Survey" is a mode that utlises the current device [settings](#settings) to perform periodic or manual transmissions. If the device has been configured to join using "Over The Air Activation", and is currently not connected to a network, entering this mode the device will first attempt to join a network using the internal device keys. "Survey" mode is great for performing "war driving" tests. 41 | 42 | ![Survey Image](https://raw.githubusercontent.com/danielknox/lorawan_network_tester/master/images/survey.png "Fig 2. Survey") 43 | 44 | 1. GPS Status (Disconnected) 45 | 2. Spread factor of current transmission 46 | 3. Battery Status (Half-full) 47 | 4. The current operation frequency of the device 48 | 5. The current transmission power of the device 49 | 6. "Press to Scan" when in manual mode / RX Qual results when TX successful and acknowledged ([Sweep](#sweep), See 3 and 8) 50 | 51 | Pushing the joystick to the left when in survey mode will return the device to the [menu](#menu) 52 | 53 | #### Sweep 54 | "Sweep" is a mode that causes the device to send a transmission at each spreadfactor (SF7 - SF12, i.e. DR5 - D0); this collection of transmissions is referred to as a 'test'. This mode is useful for testing connectivity at a specific location (e.g. before deploying a sensor). If the device has been configured to join using "Over The Air Activation", and is currently not connected to a network, entering this mode the device will first attempt to join a network using the internal device keys. Because of duty cycle limitations, the device may have to wait for a free-channel before it can attempt a transmission at a specific spreadfactor. During this time the device will state "No Free Chan. Cancel Test?", clicking the joystick will cancel the current and subsequent transmissions left in the test. 55 | 56 | ![Sweep Image](https://raw.githubusercontent.com/danielknox/lorawan_network_tester/master/images/sweep.png "Fig 3. Sweep") 57 | 58 | 1. GPS Status (Connected) 59 | 2. Spread factor of current transmission 60 | 3. The current link margin, calculated from the signal recieve strength 61 | 4. The device is actively transmitting 62 | 5. Battery Status (Half-full) 63 | 6. The current operation frequency of the device 64 | 7. The current transmission power of the device 65 | 8. The number of gateways that sucessfully recieved the message 66 | 67 | At the end of the test the results will be displayed on the screen. Transmissions that were successful and therefore recieved an acknowledgement from a network server will be presented as black text on a white background. Transmissions that were not successful, because they did not recieve an acknowledgement, will be presented as white text on a black background. 68 | 69 | From this menu you can either push left on the joystick to intiate a new test or push right to return to the main menu. 70 | 71 | ![Sweep Results](https://raw.githubusercontent.com/danielknox/lorawan_network_tester/master/images/sweep%20results.png "Fig 4. Sweep Results") 72 | 73 | 1. GPS Status (Connected) 74 | 2. Spread factors that were **not** acknowledged 75 | 3. Start a new test 76 | 4. Battery Status (Half-full) 77 | 5. Spread factors that were acknowledged 78 | 6. Return to [menu](#menu) 79 | 80 | #### Settings 81 | "Settings" lets you configure the runtime settings of the device. Upon entering this menu option you will be presented with the following configurable settings: 82 | - [Join](#join) 83 | - [SF](#sf) 84 | - [TX IVL](#TX%-IVL) 85 | - [Exit](#Exit) 86 | 87 | Pushing right or left on the joystick of a currently selected setting will alter that setting. Pushing up or down on the joystick allows you to move between different settings. 88 | 89 | ##### Join 90 | This allows you to change the current join mode of the device. This can either be OTAA (Over the Air Activation) or ABP (Activation by Personalisation). OTAA means that the device will perform a join-procedure with the network during which security keys and a dynamic DevAddr is assigned. Because of this, OTAA requires the presence of a network. ABP hardcodes the DevAddr and security keys in the device. This allows the device to "skip" the join procedure. 91 | 92 | Choosing between OTAA is your choice and if you have provisioned your device with both sets of keys then you can move between them at will. If you are starting in a place with known network connectivity, OTAA may be an easier, preferred option. If you are starting from a place without network connectivity, but may move into range during a survey or sweep, then you may prefer to use ABP. If you do use ABP, you will likely need to inform your network server to have "relaxed frame counts", as power cycles will likely loose this information and therefore prevent further sucessful transmissions. 93 | 94 | **The "join-procedure" is only initiated when you enter "Survey" or "Sweep", so you can configure this setting at anytime.** 95 | 96 | ##### SF 97 | "SF" (Spread Factor) allows you to adjust the spread factor used for joins and survey transmissions. Selectable spreadfactors range from 7 to 12 (data rates 5 to 0). This [spreadfactor video] explains spreadfactor and its influence. 98 | 99 | ##### TX IVL 100 | "TX IVL" (TX Interval) allows you to adjust the frequency of periodic transmissions whilst in "Survey" mode. The available options are: Man, 1 second, 2 seconds, 5 seconds, 10 seconds, 30 seconds, 1 minute, 2 minutes, 5 minutes, 10 minutes. When set to "Man" (Manual), the device will not perform periodic transmission; instead, it will require manual activation by clicking the joystick. 101 | 102 | ##### Exit 103 | Exits the "Settings" menu if the joystick is clicked when this option is currently selected. Upon leaving the "Settings" menu you will be asked if you wish to "Save as Default?" the current settings. Selecting "Yes" will store the settings on the internal flash, allowing them to persist between device restarts. The flash is limited to around 10K writes. Selecting "No" means the current settings will only persist until the device restarts (at which point the default settings will be restored). 104 | 105 | #### USB 106 | "USB" mode allows you to configure the device using the serial interface (e.g. the one provided by the Arduino IDE, CoolTERM, etc.). Upon entering this mode, you will be able to send a commands to the device. This is how you **provision** the device with the required network keys. 107 | 108 | The commands available in this mode are: 109 | 110 | - [!AT+CFGOTAA](#!AT+CFGOTAA-[appeui]-[appkey]) 111 | - [!AT+CFGABP](#!AT+CFGOTAA-[devAddr]-[nwkskey]-[appskey]) 112 | - [!AT+HWEUI?](#!at+hweui?) 113 | - [!AT+EXIT](#!at+exit) 114 | 115 | When a command expects one or more parameters, the start of each parameter is delimited with a space. A new line and carriage return is required to send the command to the device. 116 | 117 | ##### !AT+CFGOTAA [appEui] [appKey] 118 | Configures the keys required for OTAA (Over the Air Activation). This command expects 16 characters for the appEui and 32 characters for the appKey. These should be available from your network server. In some cases appEui is not provided, in these cases try using "0000000000000000". Spaces are used to mark the start of each key, you do not need the quotes or brackets! 119 | 120 | This command should return "OK" if the device accepted and saved the keys to the RN2483 (this will persist between restarts). It will return invalid if there is a problem with your keys (e.g. you didn't specify one / wrong length) or the RN2483 rejected them. 121 | 122 | ##### !AT+CFGABP [devAddr] [NwksKey] [AppsKey] 123 | Configures the keys required for ABP (Activation by Personalisation). This command expects 8 characters for the devAddr, 32 characters for the NwksKey and 32 characters for the AppsKey. These should be available from your network server. Spaces are used to mark the start of each key, you do not need the quotes or brackets! 124 | 125 | This command should return "OK" if the device accepted and saved the keys to the RN2483 (this will persist between restarts). It will return invalid if there is a problem with your keys (e.g. you didn't specify one / wrong length) or the RN2483 rejected them. 126 | 127 | ##### !AT+HWEUI? 128 | This commands returns the hardware EUI of the RN2483. This is required when registering a device with a network server. We use the HWEUI and this is unique to the RN2483, rather than depending on the user to try and come up with their own. 129 | 130 | ##### !AT+EXIT 131 | This commands exits "USB" mode and returns you to the main menu. 132 | 133 | ## License 134 | 135 | GNU Ver 3. 136 | 137 | ## Credits 138 | - Icons used: 139 | - USB - Icon made by Smashicons from www.flaticon.com 140 | - Battery - Icon made by Smashicons from www.flaticon.com 141 | - Satellite - Icon made by FreePic from www.flaticon.com 142 | - Transmitting - Icon made by photo3idea_studio from www.flaticon.com 143 | 144 | [NeoGPS]: 145 | [RN2483]: https://github.com/danielknox/arduino-device-lib 146 | [FlashStorage]: https://github.com/cmaglie/FlashStorage 147 | [Arduino Serial Command]: https://github.com/kroimon/Arduino-SerialCommand 148 | [Adafruit GFX]: https://github.com/adafruit/Adafruit-GFX-Library 149 | [Adafruit ST7735]: https://github.com/adafruit/Adafruit-ST7735-Library 150 | [Adafruit SAMD]: https://learn.adafruit.com/adafruit-feather-m0-basic-proto/using-with-arduino-ide 151 | [Spreadfactor video]: https://www.youtube.com/watch?v=B580NvdXtjs 152 | [Axis Joystick]: https://github.com/YuriiSalimov/AxisJoystick 153 | 154 | -------------------------------------------------------------------------------- /SerialCommand.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * SerialCommand - A Wiring/Arduino library to tokenize and parse commands 3 | * received over a serial port. 4 | * 5 | * Copyright (C) 2012 Stefan Rado 6 | * Copyright (C) 2011 Steven Cogswell 7 | * http://husks.wordpress.com 8 | * 9 | * Version 20120522 10 | * 11 | * This library is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU Lesser General Public License as published by 13 | * the Free Software Foundation, either version 3 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This library is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU Lesser General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this library. If not, see . 23 | */ 24 | #include "SerialCommand.h" 25 | 26 | /** 27 | * Constructor makes sure some things are set. 28 | */ 29 | SerialCommand::SerialCommand() 30 | : commandList(NULL), 31 | commandCount(0), 32 | defaultHandler(NULL), 33 | term('\n'), // default terminator for commands, newline character 34 | last(NULL) 35 | { 36 | strcpy(delim, " "); // strtok_r needs a null-terminated string 37 | clearBuffer(); 38 | } 39 | 40 | /** 41 | * Adds a "command" and a handler function to the list of available commands. 42 | * This is used for matching a found token in the buffer, and gives the pointer 43 | * to the handler function to deal with it. 44 | */ 45 | void SerialCommand::addCommand(const char *command, void (*function)()) { 46 | #ifdef SERIALCOMMAND_DEBUG 47 | Serial.print("Adding command ("); 48 | Serial.print(commandCount); 49 | Serial.print("): "); 50 | Serial.println(command); 51 | #endif 52 | 53 | commandList = (SerialCommandCallback *) realloc(commandList, (commandCount + 1) * sizeof(SerialCommandCallback)); 54 | strncpy(commandList[commandCount].command, command, SERIALCOMMAND_MAXCOMMANDLENGTH); 55 | commandList[commandCount].function = function; 56 | commandCount++; 57 | } 58 | 59 | /** 60 | * This sets up a handler to be called in the event that the receveived command string 61 | * isn't in the list of commands. 62 | */ 63 | void SerialCommand::setDefaultHandler(void (*function)(const char *)) { 64 | defaultHandler = function; 65 | } 66 | 67 | 68 | /** 69 | * This checks the Serial stream for characters, and assembles them into a buffer. 70 | * When the terminator character (default '\n') is seen, it starts parsing the 71 | * buffer for a prefix command, and calls handlers setup by addCommand() member 72 | */ 73 | void SerialCommand::readSerial() { 74 | while (Serial.available() > 0) { 75 | char inChar = Serial.read(); // Read single available character, there may be more waiting 76 | #ifdef SERIALCOMMAND_DEBUG 77 | Serial.print(inChar); // Echo back to serial stream 78 | #endif 79 | 80 | if (inChar == term) { // Check for the terminator (default '\r') meaning end of command 81 | #ifdef SERIALCOMMAND_DEBUG 82 | Serial.print("Received: "); 83 | Serial.println(buffer); 84 | #endif 85 | 86 | char *command = strtok_r(buffer, delim, &last); // Search for command at start of buffer 87 | if (command != NULL) { 88 | boolean matched = false; 89 | for (int i = 0; i < commandCount; i++) { 90 | #ifdef SERIALCOMMAND_DEBUG 91 | Serial.print("Comparing ["); 92 | Serial.print(command); 93 | Serial.print("] to ["); 94 | Serial.print(commandList[i].command); 95 | Serial.println("]"); 96 | #endif 97 | 98 | // Compare the found command against the list of known commands for a match 99 | if (strncmp(command, commandList[i].command, SERIALCOMMAND_MAXCOMMANDLENGTH) == 0) { 100 | #ifdef SERIALCOMMAND_DEBUG 101 | Serial.print("Matched Command: "); 102 | Serial.println(command); 103 | #endif 104 | 105 | // Execute the stored handler function for the command 106 | (*commandList[i].function)(); 107 | matched = true; 108 | break; 109 | } 110 | } 111 | if (!matched && (defaultHandler != NULL)) { 112 | (*defaultHandler)(command); 113 | } 114 | } 115 | clearBuffer(); 116 | } 117 | else if (isprint(inChar)) { // Only printable characters into the buffer 118 | if (bufPos < SERIALCOMMAND_BUFFER) { 119 | buffer[bufPos++] = inChar; // Put character into buffer 120 | buffer[bufPos] = '\0'; // Null terminate 121 | } else { 122 | #ifdef SERIALCOMMAND_DEBUG 123 | Serial.println("Line buffer is full - increase SERIALCOMMAND_BUFFER"); 124 | #endif 125 | } 126 | } 127 | } 128 | } 129 | 130 | /* 131 | * Clear the input buffer. 132 | */ 133 | void SerialCommand::clearBuffer() { 134 | buffer[0] = '\0'; 135 | bufPos = 0; 136 | } 137 | 138 | /** 139 | * Retrieve the next token ("word" or "argument") from the command buffer. 140 | * Returns NULL if no more tokens exist. 141 | */ 142 | char *SerialCommand::next() { 143 | return strtok_r(NULL, delim, &last); 144 | } 145 | -------------------------------------------------------------------------------- /SerialCommand.h: -------------------------------------------------------------------------------- 1 | /** 2 | * SerialCommand - A Wiring/Arduino library to tokenize and parse commands 3 | * received over a serial port. 4 | * 5 | * Copyright (C) 2012 Stefan Rado 6 | * Copyright (C) 2011 Steven Cogswell 7 | * http://husks.wordpress.com 8 | * 9 | * Version 20120522 10 | * 11 | * This library is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU Lesser General Public License as published by 13 | * the Free Software Foundation, either version 3 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This library is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU Lesser General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this library. If not, see . 23 | */ 24 | #ifndef SerialCommand_h 25 | #define SerialCommand_h 26 | 27 | #if defined(WIRING) && WIRING >= 100 28 | #include 29 | #elif defined(ARDUINO) && ARDUINO >= 100 30 | #include 31 | #else 32 | #include 33 | #endif 34 | #include 35 | 36 | // Size of the input buffer in bytes (maximum length of one command plus arguments) 37 | #define SERIALCOMMAND_BUFFER 128 38 | // Maximum length of a command excluding the terminating null 39 | #define SERIALCOMMAND_MAXCOMMANDLENGTH 12 40 | 41 | // Uncomment the next line to run the library in debug mode (verbose messages) 42 | //#define SERIALCOMMAND_DEBUG 43 | 44 | 45 | class SerialCommand { 46 | public: 47 | SerialCommand(); // Constructor 48 | void addCommand(const char *command, void(*function)()); // Add a command to the processing dictionary. 49 | void setDefaultHandler(void (*function)(const char *)); // A handler to call when no valid command received. 50 | 51 | void readSerial(); // Main entry point. 52 | void clearBuffer(); // Clears the input buffer. 53 | char *next(); // Returns pointer to next token found in command buffer (for getting arguments to commands). 54 | 55 | private: 56 | // Command/handler dictionary 57 | struct SerialCommandCallback { 58 | char command[SERIALCOMMAND_MAXCOMMANDLENGTH + 1]; 59 | void (*function)(); 60 | }; // Data structure to hold Command/Handler function key-value pairs 61 | SerialCommandCallback *commandList; // Actual definition for command/handler array 62 | byte commandCount; 63 | 64 | // Pointer to the default handler function 65 | void (*defaultHandler)(const char *); 66 | 67 | char delim[2]; // null-terminated list of character to be used as delimeters for tokenizing (default " ") 68 | char term; // Character that signals end of command (default '\n') 69 | 70 | char buffer[SERIALCOMMAND_BUFFER + 1]; // Buffer of stored characters while waiting for terminator character 71 | byte bufPos; // Current position in the buffer 72 | char *last; // State variable used by strtok_r during processing 73 | }; 74 | 75 | #endif //SerialCommand_h 76 | -------------------------------------------------------------------------------- /battery.cpp: -------------------------------------------------------------------------------- 1 | #include "hardware.h" 2 | 3 | /**************************************************************************/ 4 | /*! 5 | @brief Get raw battery voltage 6 | @return Current battery voltage 7 | */ 8 | /**************************************************************************/ 9 | int batteryVoltage() { 10 | int adcReading; 11 | int voltage; 12 | adcReading = analogRead(V_BAT_PIN); 13 | // Discard inaccurate 1st reading 14 | adcReading = 0; 15 | // Perform averaging 16 | for (unsigned char counter = 10; counter > 0; counter--) 17 | { 18 | adcReading += analogRead(V_BAT_PIN); 19 | } 20 | adcReading = adcReading / 10; 21 | adcReading *= 2; // we divided by 2 (voltage divider), so multiply back 22 | return (adcReading * (3.3 / 1023.0)*1000); // ref voltage * adc resolution 23 | } 24 | 25 | /**************************************************************************/ 26 | /*! 27 | @brief Gets the state the battery level. 28 | @return 0 - 15. 15 is full (or powered), 0 is empty. 29 | */ 30 | /**************************************************************************/ 31 | uint8_t batteryState(){ 32 | float batterystate = batteryVoltage(); 33 | if(batterystate >= 4200){ 34 | return 15; 35 | } else if (batterystate < 3200){ 36 | return 0; 37 | } 38 | return map(batterystate, 3200, 4199, 0, 15); 39 | } 40 | 41 | -------------------------------------------------------------------------------- /battery.h: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | 3 | float batteryVoltage(); 4 | uint8_t batteryState(); 5 | -------------------------------------------------------------------------------- /gps.cpp: -------------------------------------------------------------------------------- 1 | #include "gps.h" 2 | 3 | //NeoGPS Configurations. Must be before NMEAGPS.h include! 4 | #define gpsPort Serial2 5 | #define GPS_PORT_NAME "gpsSerial" 6 | #define DEBUG_PORT Serial 7 | #define NMEAGPS_INTERRUPT_PROCESSING 8 | #define GPS_FIX_ALTITUDE 9 | #define GPS_FIX_HDOP 10 | 11 | #include 12 | #include 13 | #include "wiring_private.h" // pinPeripheral() function 14 | #include "hardware.h" 15 | 16 | // Define additional hardware serial port on M0 17 | Uart Serial2 (&sercom1, GPS_SERIAL_RX, GPS_SERIAL_TX, SERCOM_RX_PAD_0, UART_TX_PAD_2); 18 | 19 | // Check neogps configuration 20 | 21 | #ifndef NMEAGPS_INTERRUPT_PROCESSING 22 | #error You must define NMEAGPS_INTERRUPT_PROCESSING in NMEAGPS_cfg.h! 23 | #endif 24 | 25 | #ifndef GPS_FIX_ALTITUDE 26 | #error You must define GPS_FIX_ALTITUDE in GPSfix_cfg.h! 27 | #endif 28 | 29 | #ifndef GPS_FIX_HDOP 30 | #error You must define GPS_FIX_HDOP in GPSfix_cfg.h! 31 | #endif 32 | 33 | static NMEAGPS gps; 34 | static gps_fix fix; 35 | 36 | /**************************************************************************/ 37 | /*! 38 | @brief Sercom 1 interupt handler to feed NEOGPS with characters 39 | */ 40 | /**************************************************************************/ 41 | void SERCOM1_Handler() 42 | { 43 | Serial2.IrqHandler(); 44 | gps.handle( Serial2.read() ); 45 | } 46 | 47 | /**************************************************************************/ 48 | /*! 49 | @brief Starts the GPS 50 | */ 51 | /**************************************************************************/ 52 | void initGPS(){ 53 | gpsPort.begin( 9600 ); 54 | pinPeripheral(GPS_SERIAL_RX, PIO_SERCOM); 55 | pinPeripheral(GPS_SERIAL_TX, PIO_SERCOM); 56 | } 57 | 58 | /**************************************************************************/ 59 | /*! 60 | @brief Checks to see if GPS has a valid lock (location, alt and hdop) 61 | @return True if gps fix is valid, false if it is not. 62 | */ 63 | /**************************************************************************/ 64 | bool hasGPSLock() { 65 | while (gps.available()) { 66 | fix = gps.read(); 67 | } 68 | if(fix.valid.location && fix.valid.altitude && fix.valid.hdop){ 69 | return true; 70 | } 71 | return false; 72 | } 73 | 74 | /**************************************************************************/ 75 | /*! 76 | @brief If GPS has a valid lock it will populate the passed structure with location data. 77 | @param Reference of a location struct to be populated 78 | @return True if gps data was available, otherwise false. 79 | */ 80 | /**************************************************************************/ 81 | bool retieveGPS(loc* location) { 82 | if(!hasGPSLock()) return false; 83 | location->latitude = fix.latitudeL(); 84 | location->longitude = fix.longitudeL(); 85 | location->altitude = fix.altitude_cm(); 86 | location->hdop = fix.hdop; 87 | return true; 88 | } 89 | -------------------------------------------------------------------------------- /gps.h: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | #ifndef PROBE_GPS__H_ 3 | #define PROBE_GPS__H_ 4 | 5 | typedef struct { 6 | int32_t latitude; // Integer degrees, scaled by 10,000,000 7 | int32_t longitude; // Integer degrees, scaled by 10,000,000 8 | int32_t altitude; // Integer cms 9 | uint16_t hdop; // Horizontal Dilution of Precision x 1000 10 | } loc; 11 | 12 | void initGPS(); 13 | bool hasGPSLock(); 14 | bool retieveGPS(loc* location); 15 | 16 | #endif //PROBE_GPS__H_ 17 | -------------------------------------------------------------------------------- /hardware.h: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | 3 | // Joystick 4 | #define JOYSTICK_FIRE_PIN 5 5 | #define JOYSTICK_X_PIN A1 6 | #define JOYSTICK_Y_PIN A0 7 | 8 | // LoRaWAN (RN2483) 9 | #define FREQ_PLAN TTN_FP_EU868 10 | #define RN2483_RESET_PIN 12 11 | #define RN2483_SERIAL Serial1 12 | #define DEBUG_SERIAL Serial 13 | 14 | // TFT Screen (ST7735) 15 | #define TFT_CS A5 16 | #define TFT_RST A4 17 | #define TFT_DC A3 18 | 19 | // GPS Serial (NEMA device) 20 | #define GPS_SERIAL_RX 11 // must define a pin that can be mapped to sercom1 and capable of RX 21 | #define GPS_SERIAL_TX 10 // must define a pin that can be mapped to sercom1 22 | 23 | // Battery 24 | #define V_BAT_PIN A7 // Voltage divider required if not using feather. 25 | -------------------------------------------------------------------------------- /icons.h: -------------------------------------------------------------------------------- 1 | #ifndef PROBE_ICONS__H_ 2 | #define PROBE_ICONS__H_ 3 | 4 | // 'usb-connector', 128x128px 5 | const unsigned char usbConnector [] PROGMEM = { 6 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfc, 0x00, 0x00, 0x00, 7 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xfe, 0x00, 0x00, 0x00, 8 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0x80, 0x00, 0x00, 9 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x00, 0x00, 10 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xe0, 0x00, 0x00, 11 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf0, 0x00, 0x00, 12 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xf8, 0x00, 0x00, 13 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xfc, 0x00, 0x00, 14 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xfe, 0x00, 0x00, 15 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0x0f, 0xff, 0x00, 0x00, 16 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xfe, 0x07, 0xff, 0x80, 0x00, 17 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x03, 0xff, 0xc0, 0x00, 18 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xf8, 0x01, 0xff, 0xe0, 0x00, 19 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xf0, 0x00, 0xff, 0xf0, 0x00, 20 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xe0, 0x00, 0x7f, 0xf8, 0x00, 21 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xc0, 0x00, 0x3f, 0xfc, 0x00, 22 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0x80, 0x00, 0x1f, 0xfe, 0x00, 23 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0x00, 0x00, 0x0f, 0xff, 0x00, 24 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xfe, 0x00, 0x00, 0x07, 0xff, 0x80, 25 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3f, 0xfc, 0x00, 0x00, 0x03, 0xff, 0xc0, 26 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf8, 0x3f, 0x00, 0x01, 0xff, 0xe0, 27 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xf0, 0x3f, 0x80, 0x00, 0xff, 0xf0, 28 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xe0, 0x7f, 0xc0, 0x00, 0x7f, 0xf8, 29 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, 0x7f, 0xc0, 0x00, 0x3f, 0xfc, 30 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x7f, 0xc0, 0x00, 0x1f, 0xfc, 31 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0x00, 0x7f, 0xc0, 0x00, 0x0f, 0xfe, 32 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xfe, 0x00, 0x3f, 0x80, 0x00, 0x07, 0xff, 33 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xfe, 0x00, 0x3f, 0x00, 0x00, 0x03, 0xff, 34 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 35 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0x80, 0x00, 0x03, 0x00, 0x01, 0xff, 36 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x0f, 0xc0, 0x01, 0xff, 37 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x1f, 0xe0, 0x01, 0xff, 38 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x1f, 0xf0, 0x03, 0xff, 39 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x1f, 0xf0, 0x07, 0xff, 40 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x1f, 0xf0, 0x0f, 0xfe, 41 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x1f, 0xe0, 0x1f, 0xfc, 42 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xc0, 0x3f, 0xfc, 43 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x07, 0xc0, 0x7f, 0xf8, 44 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0xff, 0xf0, 45 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x01, 0xff, 0xe0, 46 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x03, 0xff, 0xc0, 47 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x07, 0xff, 0x80, 48 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x0f, 0xff, 0x00, 49 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x1f, 0xfe, 0x00, 50 | 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f, 0xfc, 0x00, 51 | 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x7f, 0xf8, 0x00, 52 | 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xff, 0xf0, 0x00, 53 | 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1, 0xff, 0xe0, 0x00, 54 | 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xc0, 0x00, 55 | 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 56 | 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 57 | 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 58 | 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 59 | 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 60 | 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 61 | 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 62 | 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 63 | 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 64 | 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 65 | 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 66 | 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 67 | 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 68 | 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 69 | 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 70 | 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 71 | 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 72 | 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 73 | 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 74 | 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 75 | 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 76 | 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 77 | 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 78 | 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 79 | 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 80 | 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 81 | 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 82 | 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 83 | 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 84 | 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 85 | 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 86 | 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 87 | 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 88 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 89 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 90 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 91 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 92 | 0x00, 0x00, 0x00, 0x03, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 93 | 0x00, 0x00, 0x00, 0x0f, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 94 | 0x00, 0x00, 0x00, 0x1f, 0xc0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 95 | 0x00, 0x00, 0x00, 0x3f, 0xc0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 96 | 0x00, 0x00, 0x00, 0x7f, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 97 | 0x00, 0x00, 0x00, 0xff, 0xf0, 0x00, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 98 | 0x00, 0x00, 0x01, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 99 | 0x00, 0x00, 0x03, 0xff, 0xfc, 0x00, 0x3f, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 100 | 0x00, 0x00, 0x07, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 101 | 0x00, 0x00, 0x0f, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 102 | 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 103 | 0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 104 | 0x00, 0x00, 0x7f, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 105 | 0x00, 0x00, 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 106 | 0x00, 0x01, 0xff, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 107 | 0x00, 0x03, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 108 | 0x00, 0x07, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x0f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 109 | 0x00, 0x0f, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x07, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 110 | 0x00, 0x1f, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 111 | 0x00, 0x3f, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 112 | 0x00, 0x7f, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 113 | 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 114 | 0x01, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 115 | 0x03, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 116 | 0x07, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 117 | 0x0f, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 118 | 0x1f, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 119 | 0x1f, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 120 | 0x3f, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 121 | 0x7f, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 122 | 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 123 | 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 124 | 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 125 | 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 126 | 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 127 | 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 128 | 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 129 | 0x7f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 130 | 0x3f, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 131 | 0x1f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 132 | 0x0f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 133 | 0x07, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 134 | }; 135 | 136 | // 'connecting', 128x128px 137 | const unsigned char joiningNetwork [] PROGMEM = { 138 | 0x3f, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 139 | 0x7f, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 140 | 0xe0, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 141 | 0xc0, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 142 | 0xc7, 0xff, 0xff, 0xff, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 143 | 0xc7, 0xff, 0xff, 0xff, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 144 | 0xc6, 0x00, 0x00, 0x01, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 145 | 0xc6, 0x00, 0x00, 0x01, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 146 | 0xc6, 0x00, 0x00, 0x01, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 147 | 0xc6, 0x00, 0x00, 0x01, 0x8c, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 148 | 0xc6, 0x00, 0x00, 0x01, 0x8c, 0x00, 0x03, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 149 | 0xc6, 0x00, 0x00, 0x01, 0x8c, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 150 | 0xc6, 0x00, 0x00, 0x01, 0x8c, 0x00, 0x0f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 151 | 0xc6, 0x06, 0x00, 0x01, 0x8c, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 152 | 0xc6, 0x07, 0x00, 0x01, 0x8c, 0x00, 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 153 | 0xc6, 0x03, 0x80, 0x01, 0x8c, 0x00, 0x7f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 154 | 0xc6, 0x01, 0xc0, 0x01, 0x8c, 0x00, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 155 | 0xc6, 0x00, 0xc0, 0x01, 0x8c, 0x01, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 156 | 0xc6, 0x00, 0x60, 0x01, 0x8c, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 157 | 0xc6, 0x00, 0x70, 0x01, 0x8c, 0x07, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 158 | 0xc6, 0x00, 0x30, 0x01, 0x8c, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 159 | 0xc6, 0x00, 0x00, 0x01, 0x8c, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 160 | 0xc7, 0xff, 0xff, 0xff, 0x8e, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 161 | 0xc3, 0xff, 0xff, 0xff, 0x8e, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 162 | 0xc0, 0x00, 0x00, 0x00, 0x0e, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 163 | 0xc0, 0x00, 0x00, 0x00, 0x0e, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 164 | 0xc0, 0x00, 0x00, 0x00, 0x0e, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 165 | 0xc0, 0x00, 0x38, 0x00, 0x0e, 0x07, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0x80, 0x00, 0x00, 166 | 0xc0, 0x01, 0xff, 0x00, 0x0e, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0x80, 0x00, 0x00, 167 | 0xc0, 0x07, 0xc7, 0xc0, 0x0e, 0x01, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, 0x00, 0x00, 168 | 0xc0, 0x0e, 0x00, 0xe0, 0x0e, 0x00, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xe0, 0x00, 0x00, 169 | 0xc0, 0x1c, 0x00, 0x70, 0x0e, 0x00, 0x7f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf0, 0x00, 0x00, 170 | 0xc0, 0x38, 0x00, 0x38, 0x0e, 0x00, 0x3f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x00, 171 | 0xc0, 0x7c, 0x00, 0x18, 0x0e, 0x00, 0x1f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xf8, 0x00, 0x00, 172 | 0xc0, 0x7f, 0x00, 0x0c, 0x0e, 0x00, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xf8, 0x00, 0x00, 173 | 0xc0, 0xe7, 0xc0, 0x0c, 0x0e, 0x00, 0x07, 0x80, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfc, 0x00, 0x00, 174 | 0xc0, 0xc1, 0xe0, 0x0e, 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfc, 0x00, 0x00, 175 | 0xc0, 0xc0, 0x78, 0x06, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xfc, 0x00, 0x00, 176 | 0xc0, 0xc0, 0x1e, 0x06, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xfc, 0x00, 0x00, 177 | 0xc0, 0xc0, 0x07, 0x86, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xfe, 0x00, 0x00, 178 | 0xc0, 0xc0, 0x03, 0xe6, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 179 | 0xc0, 0xc0, 0x00, 0xfc, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 180 | 0xc0, 0x60, 0x00, 0x3c, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 181 | 0xc0, 0x60, 0x00, 0x1c, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 182 | 0xc0, 0x30, 0x00, 0x18, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 183 | 0xc0, 0x38, 0x00, 0x38, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 184 | 0xc0, 0x1c, 0x00, 0x70, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 185 | 0xc0, 0x0f, 0x01, 0xe0, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 186 | 0xc0, 0x07, 0xff, 0x80, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 187 | 0xc0, 0x00, 0xfe, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 188 | 0xc0, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 189 | 0xc0, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 190 | 0xc0, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 191 | 0xe0, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 192 | 0x7f, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 193 | 0x3f, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 194 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 195 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 196 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 197 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x01, 0xc0, 198 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x01, 0xe0, 199 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x01, 0xe0, 200 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0xf0, 201 | 0x00, 0x01, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0xf8, 202 | 0x00, 0x01, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x00, 0x78, 203 | 0x00, 0x01, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xe0, 0xe0, 0x00, 0x70, 0x3c, 204 | 0x00, 0x01, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0xf0, 0x00, 0x78, 0x3c, 205 | 0x00, 0x01, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc1, 0xe0, 0x00, 0x7c, 0x1e, 206 | 0x00, 0x01, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x83, 0xe0, 0x00, 0x3c, 0x1e, 207 | 0x00, 0x01, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x83, 0xc1, 0xfc, 0x1e, 0x0e, 208 | 0x00, 0x01, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x87, 0x83, 0xfe, 0x1e, 0x0f, 209 | 0x00, 0x01, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x87, 0xff, 0x0e, 0x0f, 210 | 0x00, 0x01, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x8f, 0x8f, 0x0f, 0x0f, 211 | 0x00, 0x01, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x07, 0x0f, 0x07, 0x0f, 0x0f, 212 | 0x00, 0x01, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x07, 0x0f, 0x07, 0x8f, 0x0f, 213 | 0x00, 0x01, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x07, 0x0f, 0x07, 0x8f, 0x0f, 214 | 0x00, 0x01, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x07, 0x0f, 0x0f, 0x0f, 0x0f, 215 | 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x8f, 0x9f, 0x0f, 0x0f, 216 | 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x07, 0x87, 0xfe, 0x0e, 0x0f, 217 | 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x83, 0x83, 0xfe, 0x1e, 0x0f, 218 | 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x83, 0xc3, 0xfc, 0x3e, 0x0e, 219 | 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x07, 0x83, 0xe3, 0xde, 0x3c, 0x1e, 220 | 0x00, 0x00, 0x7f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x03, 0xc1, 0xf7, 0x9e, 0x78, 0x1e, 221 | 0x00, 0x00, 0x7f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xc0, 0x03, 0xc0, 0xf7, 0x8e, 0x78, 0x3c, 222 | 0x00, 0x00, 0x3f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xe0, 0x01, 0xe0, 0x67, 0x8f, 0x70, 0x3c, 223 | 0x00, 0x00, 0x3f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf0, 0x01, 0xe0, 0x0f, 0x0f, 0x00, 0x78, 224 | 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xf8, 0x00, 0xf0, 0x0f, 0x07, 0x00, 0xf8, 225 | 0x00, 0x00, 0x1f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xfc, 0x00, 0xf8, 0x0f, 0x07, 0x81, 0xf0, 226 | 0x00, 0x00, 0x0f, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x07, 0xfe, 0x00, 0x7c, 0x1e, 0x07, 0x81, 0xe0, 227 | 0x00, 0x00, 0x07, 0xff, 0x80, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x3c, 0x1e, 0x03, 0xc1, 0xc0, 228 | 0x00, 0x00, 0x03, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x01, 0xff, 0x80, 0x18, 0x1e, 0x03, 0xc1, 0x80, 229 | 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x3e, 0x07, 0xc0, 0x00, 230 | 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x3f, 0x0f, 0xe0, 0x00, 231 | 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x3f, 0x9f, 0xe0, 0x00, 232 | 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x7f, 0xff, 0xe0, 0x00, 233 | 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x7b, 0xfc, 0xf0, 0x00, 234 | 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0xf1, 0xf8, 0xf0, 0x00, 235 | 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0xf1, 0xf8, 0x70, 0x00, 236 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, 0x00, 0xf3, 0xfc, 0x78, 0x00, 237 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0x80, 0x01, 0xe7, 0xfe, 0x78, 0x00, 238 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xfe, 0x00, 0x01, 0xef, 0x9f, 0x38, 0x00, 239 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xfe, 0x00, 0x01, 0xff, 0x0f, 0xbc, 0x00, 240 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xfc, 0x00, 0x03, 0xfe, 0x07, 0xfc, 0x00, 241 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf8, 0x00, 0x03, 0xfc, 0x03, 0xfe, 0x00, 242 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x03, 0xf8, 0x01, 0xfe, 0x00, 243 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xe0, 0x00, 0x07, 0xf0, 0x00, 0xfe, 0x00, 244 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x07, 0xe0, 0x00, 0x7f, 0x00, 245 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x80, 0x00, 0x0f, 0xe0, 0x00, 0x3f, 0x00, 246 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0f, 0xc0, 0x00, 0x1f, 0x00, 247 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x00, 0x0f, 0x80, 248 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x07, 0x80, 249 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x03, 0x80, 250 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x03, 0xc0, 251 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x03, 0xc0, 252 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x01, 0xc0, 253 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x01, 0x80, 254 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 255 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 256 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 257 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 258 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 259 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 260 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 261 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 262 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 263 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 264 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 265 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 266 | }; 267 | 268 | // 'gps_connected', 24x24px 269 | const unsigned char gpsConnected [] PROGMEM = { 270 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x3f, 271 | 0x80, 0x00, 0x1f, 0x80, 0x00, 0x0f, 0xc0, 0x00, 0x07, 0x9e, 0x00, 0x01, 0x3f, 0x00, 0x00, 0x7f, 272 | 0x00, 0x00, 0xff, 0x00, 0x01, 0xff, 0x00, 0x01, 0xfe, 0x00, 0x00, 0xfc, 0x80, 0x00, 0x79, 0xe0, 273 | 0x36, 0x33, 0xf0, 0x37, 0x01, 0xf8, 0x1b, 0x01, 0xf8, 0x1c, 0x00, 0xfc, 0x0f, 0x00, 0x78, 0x03, 274 | 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 275 | }; 276 | 277 | // 'gps_disconnected', 24x24px 278 | const unsigned char gpsDisconnected [] PROGMEM = { 279 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x3f, 280 | 0x80, 0x00, 0x1f, 0x80, 0x00, 0x0f, 0xc0, 0x00, 0x07, 0x9e, 0x00, 0x01, 0x3f, 0x00, 0x00, 0x7f, 281 | 0x00, 0x00, 0xff, 0x00, 0x01, 0xff, 0x00, 0x01, 0xfe, 0x00, 0x00, 0xfc, 0x80, 0x00, 0x79, 0xe0, 282 | 0x00, 0x33, 0xf0, 0x00, 0x01, 0xf8, 0x00, 0x01, 0xf8, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x78, 0x00, 283 | 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 284 | }; 285 | 286 | // 'network_disconnected', 24x24px 287 | const unsigned char networkDisconnected [] PROGMEM = { 288 | 0x00, 0x00, 0xc2, 0x00, 0x01, 0xe7, 0x00, 0x03, 0xfe, 0x00, 0x07, 0xfc, 0x00, 0x3f, 0xfc, 0x00, 289 | 0x3f, 0xfe, 0x00, 0x1f, 0xff, 0x00, 0x1f, 0xff, 0x00, 0x3f, 0xfe, 0x00, 0x73, 0xfe, 0x08, 0x21, 290 | 0xfc, 0x0c, 0x01, 0xf8, 0x0e, 0x03, 0xf0, 0x1f, 0x07, 0x30, 0x3f, 0x82, 0x00, 0x7f, 0xc0, 0x00, 291 | 0xff, 0xe0, 0x00, 0xff, 0xf0, 0x00, 0x7f, 0xf8, 0x00, 0x3f, 0xfc, 0x00, 0x3f, 0xf0, 0x00, 0x7f, 292 | 0xe0, 0x00, 0xe7, 0xc0, 0x00, 0x43, 0x00, 0x00 293 | }; 294 | 295 | // 'network_connected', 24x24px 296 | const unsigned char networkConnected [] PROGMEM = { 297 | 0x00, 0x00, 0x02, 0x00, 0x00, 0x07, 0x00, 0x03, 0x0e, 0x00, 0x07, 0x9c, 0x00, 0x0f, 0xf8, 0x00, 298 | 0x1f, 0xf0, 0x00, 0xff, 0xf0, 0x00, 0xff, 0xf8, 0x02, 0x7f, 0xfc, 0x03, 0x3f, 0xfc, 0x03, 0x9f, 299 | 0xf8, 0x07, 0xcf, 0xf8, 0x0f, 0xe7, 0xf0, 0x1f, 0xf3, 0xe0, 0x3f, 0xf9, 0xc0, 0x3f, 0xfc, 0xc0, 300 | 0x1f, 0xfe, 0x00, 0x0f, 0xff, 0x00, 0x0f, 0xfc, 0x00, 0x1f, 0xf8, 0x00, 0x39, 0xf0, 0x00, 0x70, 301 | 0xc0, 0x00, 0xe0, 0x00, 0x00, 0x40, 0x00, 0x00 302 | }; 303 | 304 | // 'transmit', 24x24px 305 | const unsigned char transmitting [] PROGMEM = { 306 | 0x0c, 0x00, 0x08, 0x0c, 0x00, 0x0c, 0x18, 0x80, 0x4e, 0x19, 0x80, 0xe6, 0x33, 0x9c, 0x66, 0x33, 307 | 0x3e, 0x33, 0x33, 0x3f, 0x33, 0x33, 0x3f, 0x33, 0x33, 0x3e, 0x66, 0x33, 0x9c, 0x66, 0x19, 0x9e, 308 | 0xe6, 0x18, 0x9e, 0x4c, 0x0c, 0x36, 0x0c, 0x00, 0x33, 0x00, 0x00, 0x63, 0x00, 0x00, 0x7f, 0x80, 309 | 0x00, 0xf9, 0x80, 0x00, 0xf8, 0xc0, 0x00, 0xdf, 0xc0, 0x01, 0x83, 0xe0, 0x01, 0x87, 0xe0, 0x03, 310 | 0xff, 0x60, 0x03, 0xf0, 0x30, 0x07, 0x00, 0x30 311 | }; 312 | 313 | // 'battery', 24x24px 314 | const unsigned char battery [] PROGMEM = { 315 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xf8, 0x80, 316 | 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x0f, 0x80, 0x00, 0x01, 0x80, 0x00, 317 | 0x01, 0x80, 0x00, 0x01, 0x80, 0x00, 0x01, 0x80, 0x00, 0x01, 0x80, 0x00, 0x01, 0x80, 0x00, 0x0f, 318 | 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0x00, 0x08, 0x7f, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 319 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 320 | }; 321 | 322 | #endif //PROBE_ICONS__H_ 323 | -------------------------------------------------------------------------------- /images/battery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielknox/lorawan_network_tester/400c1b388d35f0b85fbce7c6febc604d50bb1010/images/battery.png -------------------------------------------------------------------------------- /images/connecting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielknox/lorawan_network_tester/400c1b388d35f0b85fbce7c6febc604d50bb1010/images/connecting.png -------------------------------------------------------------------------------- /images/gps_connected.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielknox/lorawan_network_tester/400c1b388d35f0b85fbce7c6febc604d50bb1010/images/gps_connected.bmp -------------------------------------------------------------------------------- /images/gps_disconnected.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielknox/lorawan_network_tester/400c1b388d35f0b85fbce7c6febc604d50bb1010/images/gps_disconnected.bmp -------------------------------------------------------------------------------- /images/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielknox/lorawan_network_tester/400c1b388d35f0b85fbce7c6febc604d50bb1010/images/menu.png -------------------------------------------------------------------------------- /images/network_connected.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielknox/lorawan_network_tester/400c1b388d35f0b85fbce7c6febc604d50bb1010/images/network_connected.bmp -------------------------------------------------------------------------------- /images/network_disconnected.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielknox/lorawan_network_tester/400c1b388d35f0b85fbce7c6febc604d50bb1010/images/network_disconnected.bmp -------------------------------------------------------------------------------- /images/survey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielknox/lorawan_network_tester/400c1b388d35f0b85fbce7c6febc604d50bb1010/images/survey.png -------------------------------------------------------------------------------- /images/sweep results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielknox/lorawan_network_tester/400c1b388d35f0b85fbce7c6febc604d50bb1010/images/sweep results.png -------------------------------------------------------------------------------- /images/sweep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielknox/lorawan_network_tester/400c1b388d35f0b85fbce7c6febc604d50bb1010/images/sweep.png -------------------------------------------------------------------------------- /images/transmit.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielknox/lorawan_network_tester/400c1b388d35f0b85fbce7c6febc604d50bb1010/images/transmit.bmp -------------------------------------------------------------------------------- /images/usb-connector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielknox/lorawan_network_tester/400c1b388d35f0b85fbce7c6febc604d50bb1010/images/usb-connector.png -------------------------------------------------------------------------------- /joystick.cpp: -------------------------------------------------------------------------------- 1 | #include "joystick.h" 2 | 3 | #include 4 | #include "hardware.h" 5 | 6 | AxisJoystick joystick(JOYSTICK_FIRE_PIN, JOYSTICK_X_PIN, JOYSTICK_Y_PIN); 7 | 8 | /**************************************************************************/ 9 | /*! 10 | @brief Setting up for joystick controls 11 | */ 12 | /**************************************************************************/ 13 | void initJoystick() { 14 | pinMode(JOYSTICK_FIRE_PIN, INPUT_PULLUP); 15 | } 16 | 17 | /**************************************************************************/ 18 | /*! 19 | @brief Read the current state from the joystick 20 | */ 21 | /**************************************************************************/ 22 | joyState readJoystick() { 23 | return joystick.singleRead(); 24 | } 25 | 26 | -------------------------------------------------------------------------------- /joystick.h: -------------------------------------------------------------------------------- 1 | #ifndef PROBE_JOYSTICK__H_ 2 | #define PROBE_JOYSTICK__H_ 3 | 4 | #include 5 | 6 | #define JOY_UP (AxisJoystick::Move::UP) 7 | #define JOY_DOWN (AxisJoystick::Move::DOWN) 8 | #define JOY_LEFT (AxisJoystick::Move::RIGHT) 9 | #define JOY_RIGHT (AxisJoystick::Move::LEFT) 10 | #define JOY_PRESSED (AxisJoystick::Move::PRESS) 11 | 12 | typedef AxisJoystick::Move joyState; 13 | 14 | void initJoystick(); 15 | 16 | joyState readJoystick(); 17 | 18 | #endif //PROBE_JOYSTICK__H_ 19 | -------------------------------------------------------------------------------- /lorawan.cpp: -------------------------------------------------------------------------------- 1 | #include "lorawan.h" 2 | #include 3 | #include "icons.h" 4 | #include "screen.h" 5 | #include "settings.h" 6 | #include "hardware.h" 7 | #include "battery.h" 8 | #include "gps.h" 9 | #include 10 | 11 | typedef union { 12 | struct { 13 | byte triggered : 1; // Triggered by button press 14 | byte locInfo : 1; // Contains location data 15 | byte spare1 : 1; // Spare 16 | byte spare2 : 1; // Spare 17 | byte spare3 : 1; // Spare 18 | byte spare4 : 1; // Spare 19 | byte spare5 : 1; // Spare 20 | byte spare6 : 1; // Spare 21 | }; 22 | uint8_t packed; 23 | } PayloadContents; 24 | 25 | bool joined = false; 26 | 27 | TheThingsNetwork lorawan(RN2483_SERIAL, DEBUG_SERIAL, FREQ_PLAN); 28 | 29 | /**************************************************************************/ 30 | /*! 31 | @brief Check if the device has the required keys. 32 | @return If device is currently provisioned for the currently selected join mode (OTAA or ABP) 33 | */ 34 | /**************************************************************************/ 35 | bool isDeviceProvisioned(){ 36 | if(getJoinType() == OTAA) { 37 | char currentAppEUI[8]; 38 | if(lorawan.getAppEui(currentAppEUI,8)==8) return true; // We can only check if there is an appEUI, the RN2483 does not let us read keys. 39 | } else { 40 | if(lorawan.checkPersonalize()) return true; 41 | } 42 | return false; 43 | } 44 | 45 | /**************************************************************************/ 46 | /*! 47 | @brief Provisions the RN2483 with ABP keys. 48 | @param The registered appEui (do this on your LoRaWAN network server) - likely to become deprecated for joinEUI 49 | @param The registered appKey (do this on your LoRaWAN network server) - 50 | @return Keys were valid and sucessfully saved. False if not valid or not sucessfully saved. 51 | */ 52 | /**************************************************************************/ 53 | bool provisionOTAA(const char *appEui, const char *appKey){ 54 | return lorawan.provision(appEui,appKey); 55 | } 56 | 57 | /**************************************************************************/ 58 | /*! 59 | @brief Provisions the RN2483 with ABP keys. 60 | @param The registered devAddr (do this on your LoRaWAN network server) - 4 bytes 61 | @param The registered nwkSKey (do this on your LoRaWAN network server) - 16 bytes 62 | @param The registered appSKey (do this on your LoRaWAN network server) - 16 bytes 63 | @return True if keys were valid and sucessfully saved. False if not valid or not sucessfully saved. 64 | */ 65 | /**************************************************************************/ 66 | bool provisionABP(const char *devAddr, const char *nwkSKey, const char *appSKey){ 67 | return lorawan.personalize(devAddr, nwkSKey, appSKey); 68 | } 69 | 70 | /**************************************************************************/ 71 | /*! 72 | @brief Returns the RN2483's current pwr level in DBm 73 | */ 74 | /**************************************************************************/ 75 | int getTransmitPower() { 76 | return lorawan.getPWR(); 77 | } 78 | 79 | /**************************************************************************/ 80 | /*! 81 | @brief Read back the SNR of the last recieved packet 82 | */ 83 | /**************************************************************************/ 84 | int getSNR() { 85 | return lorawan.getSNR(); 86 | } 87 | 88 | 89 | /**************************************************************************/ 90 | /*! 91 | @brief Converts a spread factor to a numerical representation 92 | */ 93 | /**************************************************************************/ 94 | int sfToNum(spread_factor sf) { 95 | return (int)sf; 96 | // switch(sf) { 97 | // case SF_7: return 7; 98 | // case SF_8: return 8; 99 | // case SF_9: return 9; 100 | // case SF_10: return 10; 101 | // case SF_11: return 11; 102 | // case SF_12: return 12; 103 | // default: return 7; 104 | // } 105 | } 106 | 107 | /**************************************************************************/ 108 | /*! 109 | @brief Ease of use to convert the spread factors numeric value back to its enum form. 110 | */ 111 | /**************************************************************************/ 112 | spread_factor numToSF(int num) { 113 | return num>=7 && num<=12 ? (spread_factor)num : SF_7; 114 | } 115 | 116 | /**************************************************************************/ 117 | /*! 118 | @brief Converts a spread factor to a text representation 119 | */ 120 | /**************************************************************************/ 121 | char* sfToText(spread_factor sf) { 122 | switch(sf) { 123 | case SF_7: return "SF 7"; 124 | case SF_8: return "SF 8"; 125 | case SF_9: return "SF 9"; 126 | case SF_10: return "SF 10"; 127 | case SF_11: return "SF 11"; 128 | case SF_12: return "SF 12"; 129 | default: return "Unknown"; 130 | } 131 | } 132 | 133 | /**************************************************************************/ 134 | /*! 135 | @brief Resets RN2483 by toggling reset pin. 136 | */ 137 | /**************************************************************************/ 138 | void hardwareReset(){ 139 | // Toggle RN2483 reset 140 | pinMode(RN2483_RESET_PIN, OUTPUT); 141 | digitalWrite(RN2483_RESET_PIN, LOW); 142 | delay(1000); 143 | digitalWrite(RN2483_RESET_PIN, HIGH); 144 | delay(1000); 145 | } 146 | 147 | /**************************************************************************/ 148 | /*! 149 | @brief Stub externally called. Begins serial device and resets hardware 150 | */ 151 | /**************************************************************************/ 152 | void initLorawan() { 153 | RN2483_SERIAL.begin(57600); 154 | hardwareReset(); 155 | } 156 | 157 | 158 | /**************************************************************************/ 159 | /*! 160 | @brief Get HWEUI 161 | */ 162 | /**************************************************************************/ 163 | size_t getHweui(char *buffer, size_t size) { 164 | return lorawan.getHardwareEui(buffer,size); 165 | } 166 | /**************************************************************************/ 167 | /*! 168 | @brief If join is required, lookup required type and attempt to join. 169 | @return Returns true if sucessfully joined (either valid ABP or successful OTAA). Returns false if failed. 170 | */ 171 | /**************************************************************************/ 172 | bool loraJoinIfNeeded() { 173 | if(joined) return true; 174 | drawFullScreenIcon(joiningNetwork); 175 | lorawan.linkCheck(1); // We want a link check with every message sent. 176 | if(getJoinType() == OTAA) { 177 | lorawan.setSF(sfToNum(getSpreadFactor())); 178 | if(!lorawan.join(1)) return false; // Attempt a single join, return false if fails. 179 | } else { 180 | if(!lorawan.personalize()) return false; // Tells device to use ABP, if keys are not valid return false 181 | } 182 | joined = true; 183 | return true; 184 | } 185 | 186 | /**************************************************************************/ 187 | /*! 188 | @brief Check if the network connection is available 189 | @return Returns true if a linkcheck suggests that the network is currently connected 190 | */ 191 | /**************************************************************************/ 192 | bool loraNetworkConnection() { 193 | if(lorawan.getLinkCheckGateways()>0) return true; 194 | return false; 195 | } 196 | 197 | /**************************************************************************/ 198 | /*! 199 | @brief Transmit the data payload. results frequency is populated if the payload was successfully transmitted. The noise and gateway count values are populated if the message is confirmed by the gateway. 200 | @return Returns TEST_SUCCESS if transmission was successfult and confirmed; TEST_FAIL on a successful transmit, but no confirmation or TEST_ERROR ion hardware issue and duty cycle restrictions 201 | */ 202 | /**************************************************************************/ 203 | transmit_responce loraTransmit(bool manual, spread_factor sf, transmit_result& result) { 204 | byte payload[12]; 205 | memset(payload, 0, sizeof(payload)); // Clear buffer 206 | PayloadContents frameFormat; 207 | 208 | frameFormat.triggered = manual ? 1 : 0; 209 | 210 | // Location Info 211 | loc locationData; 212 | if(retieveGPS(&locationData)){ 213 | frameFormat.locInfo = 1; 214 | int32_t LatitudeBinary = (locationData.latitude + 900000000 )/ 200; 215 | int32_t LongitudeBinary = (locationData.longitude + 1800000000 )/ 400; 216 | 217 | payload[1] = (LatitudeBinary >> 16) & 0xFF; 218 | payload[2] = (LatitudeBinary >> 8) & 0xFF; 219 | payload[3] = LatitudeBinary & 0xFF; 220 | 221 | payload[4] = (LongitudeBinary >> 16) & 0xFF; 222 | payload[5] = (LongitudeBinary >> 8) & 0xFF; 223 | payload[6] = LongitudeBinary & 0xFF; 224 | 225 | int32_t altitudeGps = locationData.altitude / 100; 226 | payload[7] = (altitudeGps >> 8) & 0xFF; 227 | payload[8] = altitudeGps & 0xFF; 228 | 229 | payload[9] = (locationData.hdop / 100); 230 | frameFormat.locInfo = 1; 231 | } else { 232 | frameFormat.locInfo = 0; 233 | } 234 | 235 | // Battery Info - Pack float into int 236 | int voltage = batteryVoltage(); 237 | payload[10] = voltage >> 8; 238 | payload[11] = voltage; 239 | 240 | // fill blanks 241 | frameFormat.spare1 = 0; 242 | frameFormat.spare2 = 0; 243 | frameFormat.spare3 = 0; 244 | frameFormat.spare4 = 0; 245 | frameFormat.spare5 = 0; 246 | frameFormat.spare6 = 0; 247 | 248 | payload[0] = frameFormat.packed; 249 | 250 | ttn_response_t response = lorawan.sendBytes(payload, sizeof(payload), 1, true, sfToNum(sf)); 251 | switch(response) { 252 | case TTN_ERROR_SEND_COMMAND_FAILED: 253 | return TEST_ERROR; 254 | 255 | case TTN_ERROR_UNEXPECTED_RESPONSE: 256 | result.freq = lorawan.getFreq()/1000000.0f; 257 | return TEST_FAIL; 258 | 259 | case TTN_SUCCESSFUL_RECEIVE: 260 | case TTN_SUCCESSFUL_TRANSMISSION: 261 | result.freq = lorawan.getFreq()/1000000.0f; 262 | result.noise = lorawan.getLinkCheckMargin(); 263 | result.gateways = lorawan.getLinkCheckGateways(); 264 | return TEST_SUCCESS; 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /lorawan.h: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | #ifndef PROBE_LORAWAN__H_ 3 | #define PROBE_LORAWAN__H_ 4 | 5 | enum spread_factor { 6 | SF_7 = 7, SF_8, SF_9, SF_10, SF_11, SF_12 7 | }; 8 | 9 | enum join_type { 10 | OTAA, ABP 11 | }; 12 | 13 | enum transmit_responce { 14 | TEST_SUCCESS, TEST_FAIL, TEST_ERROR 15 | }; 16 | 17 | typedef struct transmit_result { 18 | int noise; 19 | float freq; 20 | int gateways; 21 | }; 22 | 23 | bool isDeviceProvisioned(); 24 | bool provisionOTAA(const char *appEui, const char *appKey); 25 | bool provisionABP(const char *devAddr, const char *nwkSKey, const char *appSKey); 26 | 27 | int getTransmitPower(); 28 | int getSNR(); 29 | 30 | int sfToNum(); 31 | char* sfToText(spread_factor sf); 32 | 33 | void hardwareReset(); 34 | void initLorawan(); 35 | size_t getHweui(char *buffer, size_t size); 36 | bool loraJoinIfNeeded(); 37 | 38 | bool loraIsJoined(); 39 | 40 | transmit_responce loraTransmit(bool manual, spread_factor sf, transmit_result& result); 41 | 42 | #endif //PROBE_LORAWAN__H_ 43 | -------------------------------------------------------------------------------- /lorawan_network_tester.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Keith Greenhow and Daniel Knox 3 | * 4 | * Decoder function for TTN: 5 | function Decoder(bytes, port) { 6 | // Decode an uplink message from a buffer 7 | // (array) of bytes to an object of fields. 8 | var decoded = {}; 9 | 10 | if (port === 1){ 11 | decoded = { 12 | trigger_button: !!(bytes[0] & 1) , 13 | gps: !!(bytes[0] & 2) 14 | } 15 | if(decoded.gps){ 16 | decoded.latitude = ((bytes[1]<<16)) + ((bytes[2]<<8)) + bytes[3]; 17 | decoded.latitude = ((decoded.latitude * 200) - 900000000) / 10000000; 18 | decoded.longitude = ((bytes[4]<<16)) + ((bytes[5]<<8)) + bytes[6]; 19 | decoded.longitude =((decoded.longitude * 400 ) - 1800000000) / 10000000; 20 | decoded.altitude = ((bytes[7]<<8)) + bytes[8]; 21 | decoded.hdop = bytes[9]/10; 22 | } 23 | return decoded; 24 | } 25 | } 26 | */ 27 | 28 | #include "screen.h" 29 | #include "joystick.h" 30 | #include "menu.h" 31 | #include "state.h" 32 | #include "gps.h" 33 | #include "lorawan.h" 34 | #include "settings.h" 35 | 36 | void setup() { 37 | Serial.begin(115200); 38 | initGPS(); 39 | initLorawan(); 40 | initScreen(); 41 | initJoystick(); 42 | loadSettings(); 43 | setState(&menuState); 44 | } 45 | 46 | void loop() { 47 | spinCurrentState(); 48 | } 49 | -------------------------------------------------------------------------------- /menu.cpp: -------------------------------------------------------------------------------- 1 | #include "menu.h" 2 | 3 | #include 4 | #include "icons.h" 5 | #include "screen.h" 6 | #include "joystick.h" 7 | #include "usb.h" 8 | #include "sweep.h" 9 | #include "survey.h" 10 | #include "settings.h" 11 | 12 | #define POINTER_OFFSET (4) 13 | 14 | int cursor; 15 | 16 | /**************************************************************************/ 17 | /*! 18 | @brief Ease of use function for moving the curso on screen 19 | */ 20 | /**************************************************************************/ 21 | void moveCursor(int newLine) { 22 | clearSpace(0,cursor,20); 23 | cursor = newLine; 24 | if((cursor == 1 || cursor == 2) && !isDeviceProvisioned()) { 25 | drawText(POINTER_OFFSET,cursor,"X"); 26 | } else { 27 | drawText(POINTER_OFFSET,cursor,">"); 28 | } 29 | } 30 | 31 | /**************************************************************************/ 32 | /*! 33 | @brief Sets up the screen for the main menu 34 | */ 35 | /**************************************************************************/ 36 | void initMenuState() { 37 | cursor = 1; 38 | setLineInverted(0, false); 39 | setLineInverted(1, true); 40 | setLineInverted(2, true); 41 | setLineInverted(3, true); 42 | setLineInverted(4, true); 43 | clearScreen(); 44 | drawText(55,0,"Menu"); 45 | drawText(20,1,"Survey"); 46 | drawText(20,2,"Sweep"); 47 | drawText(20,3,"Settings"); 48 | drawText(20,4,"USB"); 49 | 50 | cursor = 0; 51 | moveCursor(1); 52 | } 53 | 54 | /**************************************************************************/ 55 | /*! 56 | @brief Checks the yostick for inputs and updates the screen or changes state accordingly 57 | */ 58 | /**************************************************************************/ 59 | void menuStateLoop() { 60 | drawGPSIcon(); 61 | drawBatteryIcon(); 62 | joyState joy = readJoystick(); 63 | switch(joy) { 64 | case JOY_PRESSED: 65 | switch(cursor) { 66 | case 1: 67 | if(isDeviceProvisioned()) { 68 | setState(&surveyState); 69 | return; 70 | } 71 | break; 72 | case 2: 73 | if(isDeviceProvisioned()) { 74 | setState(&sweepState); 75 | return; 76 | } 77 | break; 78 | case 3: 79 | setState(&settingsState); 80 | return; 81 | case 4: 82 | setState(&usbState); 83 | return; 84 | } 85 | break; 86 | case JOY_UP: 87 | if(cursor>1) 88 | moveCursor(cursor-1); 89 | break; 90 | case JOY_DOWN: 91 | if(cursor<4) 92 | moveCursor(cursor+1); 93 | break; 94 | } 95 | } 96 | 97 | state menuState = { 98 | &initMenuState, 99 | &menuStateLoop, 100 | NULL 101 | }; 102 | -------------------------------------------------------------------------------- /menu.h: -------------------------------------------------------------------------------- 1 | #ifndef PROBE_MENU__H_ 2 | #define PROBE_MENU__H_ 3 | 4 | #include "state.h" 5 | 6 | extern state menuState; 7 | 8 | #endif //PROBE_MENU__H_ 9 | -------------------------------------------------------------------------------- /screen.cpp: -------------------------------------------------------------------------------- 1 | #include "screen.h" 2 | 3 | #include // Core graphics library 4 | #include // Hardware-specific library 5 | #include 6 | #include "hardware.h" 7 | #include "icons.h" 8 | #include "gps.h" 9 | #include "battery.h" 10 | 11 | Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST); 12 | 13 | bool lineInverted[5] = {false, false, false, false, false}; 14 | 15 | //gps icon state 16 | bool wasLock = false; 17 | bool wasNoLock = false; 18 | 19 | int batteryLevel = -1; 20 | 21 | /**************************************************************************/ 22 | /*! 23 | @brief Initial seup of screen hardware 24 | */ 25 | /**************************************************************************/ 26 | void initScreen() { 27 | tft.initR(INITR_BLACKTAB); // initialize a ST7735S chip, black tab 28 | tft.setRotation(3); 29 | tft.setTextSize(2); 30 | tft.fillScreen(ST7735_WHITE); 31 | clearScreen(); 32 | } 33 | 34 | /**************************************************************************/ 35 | /*! 36 | @brief Marks whether or not the requested line has inverted colours. Changes only effect subsequent calls to draw or clear functions. 37 | @param line The line to update 38 | @param inverted Whether or not the line is inverted 39 | */ 40 | /**************************************************************************/ 41 | void setLineInverted(int line, bool inverted) { 42 | lineInverted[line] = inverted; 43 | } 44 | 45 | /**************************************************************************/ 46 | /*! 47 | @brief Returns the background colour for a given line 48 | @param line The line to get the background colour for 49 | @return White for inverted line, black otherwise 50 | */ 51 | /**************************************************************************/ 52 | int getBackground(int line) { 53 | return lineInverted[line] ? 0xFFFFFF : 0x000000; 54 | } 55 | 56 | /**************************************************************************/ 57 | /*! 58 | @brief Returns the foreground colour for a given line 59 | @param line The line to get the foreground colour for 60 | @return Black for inverted line, white otherwise 61 | */ 62 | /**************************************************************************/ 63 | int getForeground(int line) { 64 | return lineInverted[line] ? 0x000000 : 0xFFFFFF; 65 | } 66 | 67 | 68 | /**************************************************************************/ 69 | /*! 70 | @brief Clears the screen. Uses the background colour for each line. 71 | */ 72 | /**************************************************************************/ 73 | void clearScreen() { 74 | //tft.fillScreen(ST7735_WHITE); 75 | for(int i=0; i<5; i++) { 76 | clearLine(i); 77 | } 78 | wasLock = false; 79 | wasNoLock = false; 80 | batteryLevel = -1; 81 | } 82 | 83 | /**************************************************************************/ 84 | /*! 85 | @brief Clears a single line by filling the space with the background colour. 86 | @param line The line to clear 87 | */ 88 | /**************************************************************************/ 89 | void clearLine(int line) { 90 | clearSpace(0,line,160); 91 | } 92 | 93 | /**************************************************************************/ 94 | /*! 95 | @brief Clears a retangular space, vertically bounded by a single line, by filling the space with the background colour. 96 | @param x The left most edge of the rectangle 97 | @param line The line to bound the top and bottom edges of the clearing rectangle 98 | @param width The width of the clearing rectangle 99 | */ 100 | /**************************************************************************/ 101 | void clearSpace(int x, int line, int width) { 102 | tft.fillRect(x, line*25, width, 25, getBackground(line)); 103 | } 104 | 105 | /**************************************************************************/ 106 | /*! 107 | @brief Draws a small icon to the screen. The icon is assumed to fit within a 25x25 space. If it is larger, then overlap will occur. 108 | @param offset The horizontal distance from the left edge of the screen 109 | @param line The vertical line number to place the icon 110 | @param width The width of the provided icon 111 | @param height The height of the provided icon 112 | @param icon The graphical data for the icon 113 | */ 114 | /**************************************************************************/ 115 | void drawSmallIcon(int offset, int line, int width, int height, const unsigned char* icon) { 116 | tft.fillRect(offset, line*25, 25, 25, getBackground(line)); 117 | tft.drawBitmap(offset, (line*25), icon, width, height, getForeground(line)); 118 | } 119 | 120 | /**************************************************************************/ 121 | /*! 122 | @brief Draws text to the screen 123 | @param offset The horizontal distance from the left edge of the screen 124 | @param line The vertical line number to place the icon 125 | @param text The string to display (null terminated) 126 | */ 127 | /**************************************************************************/ 128 | void drawText(int offset, int line, char* text) { 129 | tft.setCursor(offset, line*25+5); 130 | tft.setTextColor(getForeground(line)); 131 | tft.print(text); 132 | } 133 | 134 | /**************************************************************************/ 135 | /*! 136 | @brief Draws a 128x128 icon, centered on the screen. Excess screen space is set to white. 137 | @param icon The graphical data for the icon 138 | */ 139 | /**************************************************************************/ 140 | void drawFullScreenIcon(const unsigned char* icon) { 141 | tft.fillScreen(ST7735_WHITE); 142 | tft.drawBitmap(15, 5, icon, 128, 128, 0x000000); 143 | } 144 | 145 | /**************************************************************************/ 146 | /*! 147 | @brief Specialist code for drawing battery icon to the top right corner. Repects normal/inverted colours. Can be called frequently 148 | */ 149 | /**************************************************************************/ 150 | void drawBatteryIcon() { 151 | int calcBattLevel = batteryState(); 152 | if(batteryLevel==calcBattLevel) return; 153 | 154 | drawSmallIcon(130, 0, 24, 24, battery); 155 | tft.fillRect(133, 7, calcBattLevel, 10, getForeground(0)); 156 | batteryLevel = calcBattLevel; 157 | } 158 | 159 | /**************************************************************************/ 160 | /*! 161 | @brief Specialist code for drawing gps icon to the top right corner. Repects normal/inverted colours. Can be called frequently 162 | */ 163 | /**************************************************************************/ 164 | void drawGPSIcon() { 165 | if(hasGPSLock()) { 166 | if(!wasLock) { 167 | drawSmallIcon(0, 0, 24, 24, gpsConnected); 168 | wasNoLock = false; 169 | wasLock = true; 170 | } 171 | } else if(!wasNoLock) { 172 | drawSmallIcon(0, 0, 24, 24, gpsDisconnected); 173 | wasNoLock = true; 174 | wasLock = false; 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /screen.h: -------------------------------------------------------------------------------- 1 | #ifndef PROBE_SCREEN__H_ 2 | #define PROBE_SCREEN__H_ 3 | 4 | 5 | void initScreen(); 6 | 7 | void setLineInverted(int line, bool inverted); 8 | 9 | void clearScreen(); 10 | 11 | void clearLine(int line); 12 | 13 | void clearSpace(int x, int line, int width); 14 | 15 | void drawSmallIcon(int offset, int line, int width, int height, const unsigned char* icon); 16 | 17 | void drawText(int offset, int line, char* text); 18 | 19 | void drawFullScreenIcon(const unsigned char* icon); 20 | 21 | void drawBatteryIcon(); 22 | 23 | void drawGPSIcon(); 24 | 25 | #endif //PROBE_SCREEN__H_ 26 | -------------------------------------------------------------------------------- /settings.cpp: -------------------------------------------------------------------------------- 1 | #include "settings.h" 2 | 3 | #include 4 | #include "icons.h" 5 | #include "screen.h" 6 | #include "menu.h" 7 | #include 8 | #include "joystick.h" 9 | 10 | typedef struct { 11 | boolean valid; 12 | unsigned int transmitInterval; 13 | join_type joinType; 14 | spread_factor sf; 15 | } Settings; 16 | 17 | FlashStorage(flash_store, Settings); 18 | Settings deviceSettings; 19 | 20 | /**************************************************************************/ 21 | /*! 22 | @brief Gets the current join type 23 | @return The current join type 24 | */ 25 | /**************************************************************************/ 26 | join_type getJoinType() { 27 | return deviceSettings.joinType; 28 | } 29 | 30 | /**************************************************************************/ 31 | /*! 32 | @brief Gets the current spread factor 33 | @return The current spread factor 34 | */ 35 | /**************************************************************************/ 36 | spread_factor getSpreadFactor() { 37 | return deviceSettings.sf; 38 | } 39 | 40 | /**************************************************************************/ 41 | /*! 42 | @brief Gets the current transmit interval 43 | @return The current transmit interval 44 | */ 45 | /**************************************************************************/ 46 | unsigned int getTransmitInterval() { 47 | return deviceSettings.transmitInterval; 48 | } 49 | 50 | /**************************************************************************/ 51 | /*! 52 | @brief Sets the internal settings structure join type 53 | @param The chosen join type 54 | */ 55 | /**************************************************************************/ 56 | void setJoinType(join_type chosenJoinType) { 57 | deviceSettings.joinType = chosenJoinType; 58 | } 59 | 60 | /**************************************************************************/ 61 | /*! 62 | @brief Sets the internal settings structure transmit interval 63 | @param The chosen transmit interval 64 | */ 65 | /**************************************************************************/ 66 | void setSpreadFactor(spread_factor chosenSpreadFactor) { 67 | deviceSettings.sf = chosenSpreadFactor; 68 | } 69 | 70 | /**************************************************************************/ 71 | /*! 72 | @brief Sets the internal settings structure transmit interval 73 | @param The chosen transmit interval 74 | */ 75 | /**************************************************************************/ 76 | void setTransmitInterval(unsigned int chosenTransmitInterval) { 77 | deviceSettings.transmitInterval = chosenTransmitInterval; 78 | } 79 | 80 | /**************************************************************************/ 81 | /*! 82 | @brief Loads the settings from internal flash. If not set, default populate 83 | */ 84 | /**************************************************************************/ 85 | void loadSettings(){ 86 | deviceSettings = flash_store.read(); 87 | // Not populated yet, so load default values 88 | if(!deviceSettings.valid){ 89 | setJoinType(OTAA); 90 | setSpreadFactor(SF_7); 91 | setTransmitInterval(MANUAL_TRANSMIT); 92 | } 93 | } 94 | 95 | /**************************************************************************/ 96 | /*! 97 | @brief Saves the settings to internal flash 98 | */ 99 | /**************************************************************************/ 100 | void saveSettings(){ 101 | deviceSettings.valid = true; 102 | flash_store.write(deviceSettings); 103 | } 104 | 105 | //used by both settings menu and exit/save 106 | int settingsCursor = 1; 107 | 108 | 109 | /**************************************************************************/ 110 | /*! 111 | @brief Sets the screen up for settings exit. 112 | */ 113 | /**************************************************************************/ 114 | void initExitState() { 115 | setLineInverted(0, false); 116 | setLineInverted(1, true); 117 | setLineInverted(2, true); 118 | setLineInverted(3, true); 119 | setLineInverted(4, true); 120 | clearScreen(); 121 | 122 | drawText(38, 2, "Save As"); 123 | drawText(32, 3, "Default?"); 124 | drawText(28+80, 4, "No"); 125 | setLineInverted(4, false); 126 | clearSpace(0,4, 80); 127 | drawText(22, 4, "Yes"); 128 | settingsCursor = 0; 129 | } 130 | 131 | 132 | /**************************************************************************/ 133 | /*! 134 | @brief Checks for joystick commands and updates screen accordingly 135 | */ 136 | /**************************************************************************/ 137 | void loopExitState() { 138 | drawGPSIcon(); 139 | drawBatteryIcon(); 140 | switch(readJoystick()) { 141 | case JOY_PRESSED: 142 | if(!settingsCursor) //if YES 143 | saveSettings(); 144 | setState(&menuState); 145 | break; 146 | case JOY_LEFT: 147 | if(settingsCursor) { //if NO 148 | setLineInverted(4, true); 149 | clearSpace(80, 4, 160); 150 | drawText(28+80, 4, "No"); 151 | setLineInverted(4, false); 152 | clearSpace(0, 4, 80); 153 | drawText(22, 4, "Yes"); 154 | settingsCursor = 0; 155 | } 156 | break; 157 | case JOY_RIGHT: 158 | if(!settingsCursor) { //if YES 159 | setLineInverted(4, false); 160 | clearSpace(80, 4, 160); 161 | drawText(28+80, 4, "No"); 162 | setLineInverted(4, true); 163 | clearSpace(0, 4, 80); 164 | drawText(22, 4, "Yes"); 165 | settingsCursor = 1; 166 | } 167 | break; 168 | } 169 | } 170 | 171 | state settingsExitState = { 172 | &initExitState, 173 | &loopExitState, 174 | NULL 175 | }; 176 | 177 | #define POINTER_OFFSET (4) 178 | 179 | 180 | /**************************************************************************/ 181 | /*! 182 | @brief Draws the join type value to the screen plus joystick widgets if necassary 183 | */ 184 | /**************************************************************************/ 185 | void showJoin() { 186 | clearSpace(68,1,160); 187 | switch(getJoinType()) { 188 | case OTAA: 189 | drawText(68+5+12+5,1,"OTAA"); 190 | break; 191 | case ABP: 192 | drawText(68+5+12+5+6,1,"ABP"); 193 | break; 194 | } 195 | if(settingsCursor == 1) { 196 | drawText(68+5, 1, "<"); 197 | drawText(68+5+12+5+48+5, 1, ">"); 198 | } 199 | } 200 | 201 | /**************************************************************************/ 202 | /*! 203 | @brief Draws the spread factor value to the screen plus joystick widgets if necassary 204 | */ 205 | /**************************************************************************/ 206 | void showSF() { 207 | clearSpace(44,2,160); 208 | drawText(44+8+12+8, 2, sfToText(getSpreadFactor())); 209 | if(settingsCursor == 2) { 210 | if(getSpreadFactor() > SF_7) 211 | drawText(44+8, 2, "<"); 212 | if(getSpreadFactor() < SF_12) 213 | drawText(44+8+12+8+60+8, 2, ">"); 214 | } 215 | } 216 | 217 | #define SECOND (1000) 218 | #define MINUTE (60*SECOND) 219 | 220 | const unsigned int intervals[10] = {MANUAL_TRANSMIT, SECOND, 2*SECOND, 5*SECOND, 10*SECOND, 30*SECOND, MINUTE, 2*MINUTE, 5*MINUTE, 10*MINUTE}; 221 | 222 | /**************************************************************************/ 223 | /*! 224 | @brief For a given time interval, returns the next interval strictly greater. If the provided interval is equal to or larger 225 | than the largest permitted, the maximum interval is returned. 226 | @param The current time interval 227 | @return The next larger interval or the maximum allowed interval 228 | */ 229 | /**************************************************************************/ 230 | unsigned int nextInterval(unsigned int curr) { 231 | for(int i=0; i<9; i++) 232 | if(intervals[i]>curr) 233 | return intervals[i]; 234 | return intervals[9]; 235 | } 236 | 237 | 238 | /**************************************************************************/ 239 | /*! 240 | @brief For a given time interval, returns the next interval strictly smaller. If the provided interval is equal to or less than 241 | than the smallest permitted, the minimum interval is returned. 242 | @param The current time interval 243 | @return The next smaller interval or the minimum allowed interval 244 | */ 245 | /**************************************************************************/ 246 | unsigned int prevInterval(unsigned int curr) { 247 | for(int i=9; i>0; i--) 248 | if(intervals[i]0; i--) 262 | if(intervals[i]<=curr) 263 | return intervals[i]; 264 | return intervals[0]; 265 | } 266 | 267 | /**************************************************************************/ 268 | /*! 269 | @brief Ease of use function to convert valid time intervals to text. Returns NULL on invalid intervals 270 | @param A valid time interval 271 | @return A text representation or NULL 272 | */ 273 | /**************************************************************************/ 274 | char* intervalToText(unsigned int curr) { 275 | switch(curr) { 276 | case MANUAL_TRANSMIT: return "Man"; 277 | case SECOND: return "1s"; 278 | case 2*SECOND: return "2s"; 279 | case 5*SECOND: return "5s"; 280 | case 10*SECOND: return "10s"; 281 | case 30*SECOND: return "30s"; 282 | case MINUTE: return "1m"; 283 | case 2*MINUTE: return "2m"; 284 | case 5*MINUTE: return "5m"; 285 | case 10*MINUTE: return "10m"; 286 | default: return NULL; 287 | } 288 | } 289 | 290 | /**************************************************************************/ 291 | /*! 292 | @brief Draws the transmission interval value to the screen plus joystick widgets if necassary 293 | */ 294 | /**************************************************************************/ 295 | void showTxIvl() { 296 | clearSpace(92,3,160); 297 | unsigned int interval = nearestInterval(getTransmitInterval()); 298 | drawText(92+2+12+2, 3,intervalToText(interval)); 299 | if(settingsCursor == 3) { 300 | if(interval>MANUAL_TRANSMIT) 301 | drawText(92+2, 3, "<"); 302 | if(interval<10*MINUTE) 303 | drawText(92+2+12+2+36+2, 3, ">"); 304 | } 305 | } 306 | 307 | /**************************************************************************/ 308 | /*! 309 | @brief Sets up the screen for the Settings UI 310 | */ 311 | /**************************************************************************/ 312 | void initSettingsState() { 313 | settingsCursor = 1; 314 | setLineInverted(0, false); 315 | setLineInverted(1, true); 316 | setLineInverted(2, true); 317 | setLineInverted(3, true); 318 | setLineInverted(4, true); 319 | clearScreen(); 320 | drawText(30, 0, "Settings"); 321 | drawText(20,1,"Join"); 322 | drawText(20,2,"SF"); 323 | drawText(20,3,"Tx IVL"); 324 | drawText(20,4,"Exit"); 325 | drawText(POINTER_OFFSET,settingsCursor,">"); 326 | 327 | showJoin(); 328 | showSF(); 329 | showTxIvl(); 330 | } 331 | 332 | 333 | /**************************************************************************/ 334 | /*! 335 | @brief Ease of use function to redraw the value portion of a particular line. 336 | */ 337 | /**************************************************************************/ 338 | void redrawSettings(int line) { 339 | switch(line) { 340 | case 1: 341 | showJoin(); 342 | break; 343 | case 2: 344 | showSF(); 345 | break; 346 | case 3: 347 | showTxIvl(); 348 | break; 349 | } 350 | } 351 | 352 | /**************************************************************************/ 353 | /*! 354 | @brief Moves the curor to a new location 355 | */ 356 | /**************************************************************************/ 357 | void moveCursorSettings(int newLine) { 358 | clearSpace(0,settingsCursor,20); 359 | int oldLine = settingsCursor; 360 | settingsCursor = newLine; 361 | drawText(POINTER_OFFSET,settingsCursor,">"); 362 | redrawSettings(oldLine); 363 | redrawSettings(newLine); 364 | } 365 | 366 | /**************************************************************************/ 367 | /*! 368 | @brief Checks the joystick for actions and updates the screen and settings accordingly 369 | */ 370 | /**************************************************************************/ 371 | void settingsStateLoop() { 372 | drawGPSIcon(); 373 | drawBatteryIcon(); 374 | joyState joy = readJoystick(); 375 | switch(joy) { 376 | case JOY_PRESSED: 377 | if(settingsCursor == 4) { 378 | setState(&settingsExitState); 379 | return; 380 | } 381 | break; 382 | case JOY_UP: 383 | if(settingsCursor>1) 384 | moveCursorSettings(settingsCursor-1); 385 | break; 386 | case JOY_DOWN: 387 | if(settingsCursor<4) 388 | moveCursorSettings(settingsCursor+1); 389 | break; 390 | case JOY_LEFT: 391 | switch(settingsCursor) { 392 | case 1: 393 | deviceSettings.joinType = 394 | (deviceSettings.joinType == OTAA) 395 | ? ABP 396 | : OTAA; 397 | break; 398 | case 2: 399 | if(deviceSettings.sf>SF_7) 400 | (*(int*)(&deviceSettings.sf))--; 401 | break; 402 | case 3: 403 | deviceSettings.transmitInterval = prevInterval(deviceSettings.transmitInterval); 404 | break; 405 | } 406 | redrawSettings(settingsCursor); 407 | break; 408 | case JOY_RIGHT: 409 | switch(settingsCursor) { 410 | case 1: 411 | deviceSettings.joinType = 412 | (deviceSettings.joinType == OTAA) 413 | ? ABP 414 | : OTAA; 415 | break; 416 | case 2: 417 | if(deviceSettings.sf 4 | 5 | state* currentState = NULL; 6 | 7 | /**************************************************************************/ 8 | /*! 9 | @brief Transition from the current state to a new state. It is not safe to call this method during a states end method. Doing so will result in undefined behaviour 10 | */ 11 | /**************************************************************************/ 12 | void setState(state *newState) { 13 | if(newState == currentState) return; 14 | 15 | if(currentState!=NULL && currentState->end!=NULL) 16 | currentState->end(); 17 | 18 | currentState = newState; 19 | 20 | if(currentState!=NULL && currentState->init!=NULL) 21 | currentState->init(); 22 | } 23 | 24 | /**************************************************************************/ 25 | /*! 26 | @brief Call the current states spin method (if it has one) 27 | */ 28 | /**************************************************************************/ 29 | void spinCurrentState() { 30 | if(currentState!=NULL && currentState->spin!=NULL) 31 | currentState->spin(); 32 | } 33 | -------------------------------------------------------------------------------- /state.h: -------------------------------------------------------------------------------- 1 | #ifndef PROBE_STATE__H_ 2 | #define PROBE_STATE__H_ 3 | 4 | typedef struct state { 5 | void (*init)(); 6 | void (*spin)(); 7 | void (*end)(); 8 | } state; 9 | 10 | void setState(state *newState); 11 | 12 | void spinCurrentState(); 13 | 14 | #endif //PROBE_STATE__H_ 15 | -------------------------------------------------------------------------------- /survey.cpp: -------------------------------------------------------------------------------- 1 | #include "survey.h" 2 | 3 | #include 4 | #include "icons.h" 5 | #include "screen.h" 6 | #include "settings.h" 7 | #include "joystick.h" 8 | #include "menu.h" 9 | #define TX_INFO_LOOP_SECONDS (4) 10 | 11 | long nextPing; 12 | 13 | /**************************************************************************/ 14 | /*! 15 | @brief Sets up the screen for survey mode 16 | */ 17 | /**************************************************************************/ 18 | void initSurvey() { 19 | if(!loraJoinIfNeeded()) { 20 | setState(&menuState); 21 | return; 22 | } 23 | 24 | setLineInverted(0, true); 25 | setLineInverted(1, false); 26 | setLineInverted(2, true); 27 | setLineInverted(3, false); 28 | setLineInverted(4, true); 29 | 30 | clearScreen(); 31 | 32 | char buffer[20]; 33 | drawText(5, 1, "TX"); 34 | 35 | drawText(5, 3, "RX Qual."); 36 | 37 | if(getTransmitInterval()) 38 | nextPing = millis() + 1000; 39 | else 40 | drawText(2, 4, "Press to Scan"); 41 | } 42 | 43 | /**************************************************************************/ 44 | /*! 45 | @brief Sends a single lorawan transmission. Updates the screen with the results. 46 | @param Whether the ping request was made by the user or by the automatic interval 47 | */ 48 | /**************************************************************************/ 49 | void doPing(boolean manual) { 50 | drawSmallIcon(25, 0, 24, 24, transmitting); 51 | transmit_result res; 52 | clearLine(4); 53 | char buffer[20]; 54 | boolean gwINFO = false; 55 | unsigned long starttime; 56 | unsigned long endtime; 57 | switch(loraTransmit(manual, getSpreadFactor(), res)) { 58 | case TEST_SUCCESS: 59 | clearSpace(25, 0, 25); 60 | clearSpace(50, 1, 160); 61 | snprintf(buffer, 20, "%3.1fMHz", res.freq); 62 | drawText(64, 1, buffer); 63 | clearLine(4); 64 | snprintf(buffer, 20, "%ddBM", getSNR()); 65 | drawText(5, 4, buffer); 66 | // Alternates drawing of tx linkmargin, no gateways and tx sf, dbm 67 | for(int i=0; i=0) { 125 | doPing(false); 126 | nextPing = millis() + getTransmitInterval(); 127 | } 128 | if(readJoystick() == JOY_LEFT) 129 | setState(&menuState); 130 | } else { //if manual mode 131 | switch(readJoystick()) { 132 | case JOY_PRESSED: 133 | doPing(true); 134 | break; 135 | case JOY_LEFT: 136 | setState(&menuState); 137 | break; 138 | } 139 | 140 | } 141 | } 142 | 143 | state surveyState = { 144 | &initSurvey, 145 | &spinSurvey, 146 | NULL 147 | }; 148 | -------------------------------------------------------------------------------- /survey.h: -------------------------------------------------------------------------------- 1 | #ifndef PROBE_SURVEY__H_ 2 | #define PROBE_SURVEY__H_ 3 | 4 | #include "state.h" 5 | 6 | extern state surveyState; 7 | 8 | #endif //PROBE_SURVEY__H_ 9 | -------------------------------------------------------------------------------- /sweep.cpp: -------------------------------------------------------------------------------- 1 | #include "sweep.h" 2 | 3 | #include 4 | #include "icons.h" 5 | #include "screen.h" 6 | #include "settings.h" 7 | #include "joystick.h" 8 | #include "menu.h" 9 | 10 | #define DELAY_BETWEEN_TESTS_MS (4000) 11 | #define NO_CHANNEL_DELAY_SECONDS (8) 12 | 13 | bool sfSuccess[6]; 14 | 15 | void initSweepResults(); 16 | void initSweepError(); 17 | void spinBackMenu(); 18 | 19 | state sweepResultsState = { 20 | &initSweepResults, 21 | &spinBackMenu, 22 | NULL 23 | }; 24 | 25 | state sweepErrorState = { 26 | &initSweepError, 27 | &spinBackMenu, 28 | NULL 29 | }; 30 | 31 | /**************************************************************************/ 32 | /*! 33 | @brief Sets up the screen for sweep mode 34 | */ 35 | /**************************************************************************/ 36 | void initSweep() { 37 | if(!loraJoinIfNeeded()) { 38 | setState(&menuState); 39 | return; 40 | } 41 | 42 | setLineInverted(0, true); 43 | setLineInverted(1, false); 44 | setLineInverted(2, true); 45 | setLineInverted(3, false); 46 | setLineInverted(4, true); 47 | 48 | clearScreen(); 49 | 50 | drawText(32, 1, "Press To"); 51 | drawText(50, 2, "Sweep"); 52 | } 53 | 54 | /**************************************************************************/ 55 | /*! 56 | @brief Sends a single lorawan transmission. Updates the screen with the results. 57 | @param sf The spread factor to test at. 58 | @result The overal result of the transmission. 59 | */ 60 | /**************************************************************************/ 61 | transmit_responce testSF(spread_factor sf) { 62 | int errorAttempt = 0; 63 | transmit_result res; 64 | char buffer[20]; 65 | drawSmallIcon(25, 0, 24, 24, transmitting); 66 | clearLine(4); 67 | while(1) { 68 | switch(loraTransmit(false, sf, res)) { 69 | case TEST_SUCCESS: 70 | clearLine(2); 71 | drawText(5, 2, sfToText(sf)); 72 | snprintf(buffer, 20, "%ddBM", getTransmitPower()); 73 | drawText(97, 2, buffer); 74 | clearSpace(25, 0, 25); 75 | clearSpace(50, 1, 160); 76 | snprintf(buffer, 20, "%3.1fMHz", res.freq); 77 | drawText(64, 1, buffer); 78 | snprintf(buffer, 20, "%ddBM", res.noise); 79 | drawText(5, 4, buffer); 80 | snprintf(buffer, 20, "GW %d", res.gateways); 81 | drawText(95, 4, buffer); 82 | return TEST_SUCCESS; 83 | 84 | case TEST_FAIL: 85 | clearLine(2); 86 | drawText(5, 2, sfToText(sf)); 87 | snprintf(buffer, 20, "%ddBM", getTransmitPower()); 88 | drawText(97, 2, buffer); 89 | clearSpace(25, 0, 25); 90 | drawText(5, 4, "Fail"); 91 | return TEST_FAIL; 92 | 93 | case TEST_ERROR: 94 | clearLine(2); 95 | drawText(5, 2, sfToText(sf)); 96 | snprintf(buffer, 20, "%ddBM", getTransmitPower()); 97 | drawText(97, 2, buffer); 98 | clearSpace(25, 0, 25); 99 | for(int i=0; i"); 227 | } 228 | 229 | /**************************************************************************/ 230 | /*! 231 | @brief Sets up the screen to display an error if the sweep test errors. 232 | */ 233 | /**************************************************************************/ 234 | void initSweepError() { 235 | setLineInverted(0, true); 236 | setLineInverted(1, false); 237 | setLineInverted(2, false); 238 | setLineInverted(3, false); 239 | setLineInverted(4, false); 240 | clearScreen(); 241 | 242 | drawText(50, 0, "Error"); 243 | //TODO: use lines 1-3 to display some sort of error message. Was it a hardware fault or ran out of duty cycle, etc. 244 | drawText(2, 4, ""); 246 | } 247 | 248 | /**************************************************************************/ 249 | /*! 250 | @brief A simple spin that monitors for joystick input. Transitions to initial sweep state, or back to main menu. 251 | */ 252 | /**************************************************************************/ 253 | void spinBackMenu() { 254 | drawGPSIcon(); 255 | drawBatteryIcon(); 256 | switch(readJoystick()) { 257 | case JOY_LEFT: 258 | setState(&sweepState); 259 | return; 260 | case JOY_RIGHT: 261 | setState(&menuState); 262 | return; 263 | } 264 | } 265 | 266 | state sweepState = { 267 | &initSweep, 268 | &spinSweep, 269 | NULL 270 | }; 271 | -------------------------------------------------------------------------------- /sweep.h: -------------------------------------------------------------------------------- 1 | #ifndef PROBE_SWEEP__H_ 2 | #define PROBE_SWEEP__H_ 3 | 4 | #include "state.h" 5 | 6 | extern state sweepState; 7 | 8 | #endif //PROBE_SWEEP__H_ 9 | -------------------------------------------------------------------------------- /usb.cpp: -------------------------------------------------------------------------------- 1 | #include "usb.h" 2 | 3 | #include 4 | #include "icons.h" 5 | #include "screen.h" 6 | #include "lorawan.h" 7 | #include "SerialCommand.h" 8 | #include "menu.h" 9 | 10 | SerialCommand sCmd; 11 | 12 | /**************************************************************************/ 13 | /*! 14 | @brief Called by SerialCommand to set the OTAA keys. 15 | */ 16 | /**************************************************************************/ 17 | void setOtaaKeys(){ 18 | char *arg1; 19 | char *arg2; 20 | arg1 = sCmd.next(); 21 | if (arg1 == NULL || !(strlen(arg1) == 16)) { 22 | Serial.println("INVALID APPEUI"); 23 | return; 24 | } 25 | arg2 = sCmd.next(); 26 | if (arg2 == NULL || !(strlen(arg2) == 32)) { 27 | Serial.println("INVALID APPKEY"); 28 | return; 29 | } 30 | 31 | if(provisionOTAA(arg1,arg2)){ 32 | Serial.println("OK"); 33 | return; 34 | } else { 35 | Serial.println("RN2483 FAILURE"); 36 | } 37 | return; 38 | } 39 | 40 | /**************************************************************************/ 41 | /*! 42 | @brief Called by SerialCommand to set the ABP keys. 43 | */ 44 | /**************************************************************************/ 45 | void setAbpKeys(){ 46 | char *arg1; 47 | char *arg2; 48 | char *arg3; 49 | arg1 = sCmd.next(); 50 | if (arg1 == NULL || !(strlen(arg1) == 8)) { 51 | Serial.println("INVALID DEVADDR"); 52 | return; 53 | } 54 | arg2 = sCmd.next(); 55 | if (arg2 == NULL || !(strlen(arg2) == 32)) { 56 | Serial.println("INVALID NWKSKEY"); 57 | return; 58 | } 59 | arg3 = sCmd.next(); 60 | if (arg3 == NULL || !(strlen(arg3) == 32)) { 61 | Serial.println("INVALID APPSKEY"); 62 | return; 63 | } 64 | 65 | if(provisionABP(arg1,arg2,arg3)){ 66 | Serial.println("OK"); 67 | return; 68 | } else { 69 | Serial.println("RN2483 FAILURE"); 70 | } 71 | 72 | return; 73 | } 74 | 75 | /**************************************************************************/ 76 | /*! 77 | @brief Called by SerialCommand upon a request for the hardware EUI. 78 | */ 79 | /**************************************************************************/ 80 | void getHweui(){ 81 | char buffer[16]; 82 | if(getHweui(buffer, 16)>0){ 83 | Serial.println("OK"); 84 | Serial.print("Dev EUI: "); 85 | Serial.println(buffer); 86 | return; 87 | } 88 | Serial.println("RN2483 FAILURE"); 89 | return; 90 | } 91 | 92 | /**************************************************************************/ 93 | /*! 94 | @brief called by SerialCommand to exit AT/USB/Serial mode 95 | */ 96 | /**************************************************************************/ 97 | void goToMenu() { 98 | Serial.println("Exiting"); 99 | setState(&menuState); 100 | } 101 | 102 | /**************************************************************************/ 103 | /*! 104 | @brief Sets up for AT/USB/Serial mode 105 | */ 106 | /**************************************************************************/ 107 | void enterUSBMode() { 108 | drawFullScreenIcon(usbConnector); 109 | 110 | sCmd.addCommand("!AT+CFGOTAA", setOtaaKeys); 111 | sCmd.addCommand("!AT+CFGABP", setAbpKeys); 112 | sCmd.addCommand("!AT+HWEUI?", getHweui); 113 | sCmd.addCommand("!AT+EXIT", goToMenu); 114 | 115 | Serial.println("USB MODE"); 116 | } 117 | 118 | /**************************************************************************/ 119 | /*! 120 | @brief Checks for commands on the serial line 121 | */ 122 | /**************************************************************************/ 123 | void usbSpin() { 124 | sCmd.readSerial(); 125 | } 126 | 127 | state usbState = { 128 | &enterUSBMode, 129 | &usbSpin, 130 | NULL 131 | }; 132 | -------------------------------------------------------------------------------- /usb.h: -------------------------------------------------------------------------------- 1 | #ifndef PROBE_USB__H_ 2 | #define PROBE_USB__H_ 3 | 4 | #include "state.h" 5 | 6 | extern state usbState; 7 | 8 | #endif //PROBE_USB__H_ 9 | --------------------------------------------------------------------------------