├── README.md ├── LICENSE └── examples ├── LoRaWAN_ESP32 ├── config.h ├── LoRaWAN_ESP32.ino └── notes.md └── LoRaWAN_ESP8266 ├── config.h └── LoRaWAN_ESP8266.ino /README.md: -------------------------------------------------------------------------------- 1 | # Persistence support for RadioLib 2 | Utility functions for enabling persistence, mainly for LoRaWAN support in RadioLib. 3 | 4 | Currently, examples are available for the following protocols and platforms: 5 | * [LoRaWAN for ESP32](https://github.com/radiolib-org/radiolib-persistence/tree/main/examples/LoRaWAN_ESP32): ESP32 and all related versions, such as -S3 (last tested on Heltec Wireless Stick Lite V3 @ RadioLib 7.1.0) 6 | * [LoRaWAN for ESP8266](https://github.com/radiolib-org/radiolib-persistence/tree/main/examples/LoRaWAN_ESP8266) (last tested on Ai-Thinker ESP8266 NodeMCU V2 coupled with SX1262 @ RadioLib 6.5.0) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 radiolib-org 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/LoRaWAN_ESP32/config.h: -------------------------------------------------------------------------------- 1 | #ifndef _CONFIG_H 2 | #define _CONFIG_H 3 | 4 | #include 5 | 6 | // How often to send an uplink - consider legal & FUP constraints - see notes 7 | const uint32_t uplinkIntervalSeconds = 5UL * 60UL; // minutes x seconds 8 | 9 | // JoinEUI - previous versions of LoRaWAN called this AppEUI 10 | // for development purposes you can use all zeros - see wiki for details 11 | #define RADIOLIB_LORAWAN_JOIN_EUI 0x0000000000000000 12 | 13 | // The Device EUI & two keys can be generated on the TTN console 14 | #ifndef RADIOLIB_LORAWAN_DEV_EUI // Replace with your Device EUI 15 | #define RADIOLIB_LORAWAN_DEV_EUI 0x--------------- 16 | #endif 17 | #ifndef RADIOLIB_LORAWAN_APP_KEY // Replace with your App Key 18 | #define RADIOLIB_LORAWAN_APP_KEY 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x-- 19 | #endif 20 | #ifndef RADIOLIB_LORAWAN_NWK_KEY // Put your Nwk Key here 21 | #define RADIOLIB_LORAWAN_NWK_KEY 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x-- 22 | #endif 23 | 24 | // For the curious, the #ifndef blocks allow for automated testing &/or you can 25 | // put your EUI & keys in to your platformio.ini - see wiki for more tips 26 | 27 | 28 | 29 | // Regional choices: EU868, US915, AU915, AS923, IN865, KR920, CN780, CN500 30 | const LoRaWANBand_t Region = EU868; 31 | const uint8_t subBand = 0; // For US915, change this to 2, otherwise leave on 0 32 | 33 | 34 | // ============================================================================ 35 | // Below is to support the sketch - only make changes if the notes say so ... 36 | 37 | // Auto select MCU <-> radio connections 38 | // If you get an error message when compiling, it may be that the 39 | // pinmap could not be determined - see the notes for more info 40 | 41 | // Adafruit 42 | #if defined(ARDUINO_SAMD_FEATHER_M0) 43 | #pragma message ("Adafruit Feather M0 with RFM95") 44 | #pragma message ("Link required on board") 45 | SX1276 radio = new Module(8, 3, 4, 6); 46 | 47 | 48 | // LilyGo 49 | #elif defined(ARDUINO_TTGO_LORA32_V1) 50 | #pragma message ("TTGO LoRa32 v1 - no Display") 51 | SX1276 radio = new Module(18, 26, 14, 33); 52 | 53 | #elif defined(ARDUINO_TTGO_LORA32_V2) 54 | #pragma error ("ARDUINO_TTGO_LORA32_V2 awaiting pin map") 55 | 56 | #elif defined(ARDUINO_TTGO_LoRa32_v21new) // T3_V1.6.1 57 | #pragma message ("Using TTGO LoRa32 v2.1 marked T3_V1.6.1 + Display") 58 | SX1276 radio = new Module(18, 26, 14, 33); 59 | 60 | #elif defined(ARDUINO_TBEAM_USE_RADIO_SX1262) 61 | #pragma error ("ARDUINO_TBEAM_USE_RADIO_SX1262 awaiting pin map") 62 | 63 | #elif defined(ARDUINO_TBEAM_USE_RADIO_SX1276) 64 | #pragma message ("Using TTGO LoRa32 v2.1 marked T3_V1.6.1 + Display") 65 | SX1276 radio = new Module(18, 26, 23, 33); 66 | 67 | 68 | // Heltec 69 | #elif defined(ARDUINO_HELTEC_WIFI_LORA_32) 70 | #pragma error ("ARDUINO_HELTEC_WIFI_LORA_32 awaiting pin map") 71 | 72 | #elif defined(ARDUINO_heltec_wifi_kit_32_V2) 73 | #pragma message ("ARDUINO_heltec_wifi_kit_32_V2 awaiting pin map") 74 | SX1276 radio = new Module(18, 26, 14, 35); 75 | 76 | #elif defined(ARDUINO_heltec_wifi_kit_32_V3) 77 | #pragma message ("Using Heltec WiFi LoRa32 v3 - Display + USB-C") 78 | SX1262 radio = new Module(8, 14, 12, 13); 79 | 80 | #elif defined(ARDUINO_CUBECELL_BOARD) 81 | #pragma message ("Using TTGO LoRa32 v2.1 marked T3_V1.6.1 + Display") 82 | SX1262 radio = new Module(RADIOLIB_BUILTIN_MODULE); 83 | 84 | #elif defined(ARDUINO_CUBECELL_BOARD_V2) 85 | #pragma error ("ARDUINO_CUBECELL_BOARD_V2 awaiting pin map") 86 | 87 | 88 | #else 89 | #pragma message ("Unknown board - no automagic pinmap available") 90 | 91 | // SX1262 pin order: Module(NSS/CS, DIO1, RESET, BUSY); 92 | // SX1262 radio = new Module(8, 14, 12, 13); 93 | 94 | // SX1278 pin order: Module(NSS/CS, DIO0, RESET, DIO1); 95 | // SX1278 radio = new Module(10, 2, 9, 3); 96 | 97 | #endif 98 | 99 | 100 | // Copy over the EUI's & keys in to the something that will not compile if incorrectly formatted 101 | uint64_t joinEUI = RADIOLIB_LORAWAN_JOIN_EUI; 102 | uint64_t devEUI = RADIOLIB_LORAWAN_DEV_EUI; 103 | uint8_t appKey[] = { RADIOLIB_LORAWAN_APP_KEY }; 104 | uint8_t nwkKey[] = { RADIOLIB_LORAWAN_NWK_KEY }; 105 | 106 | // Create the LoRaWAN node 107 | LoRaWANNode node(&radio, &Region, subBand); 108 | 109 | 110 | // Helper function to display any issues 111 | void debug(bool isFail, const __FlashStringHelper* message, int state, bool Freeze) { 112 | if (isFail) { 113 | Serial.print(message); 114 | Serial.print("("); 115 | Serial.print(state); 116 | Serial.println(")"); 117 | while (Freeze); 118 | } 119 | } 120 | 121 | // Helper function to display a byte array 122 | void arrayDump(uint8_t *buffer, uint16_t len) { 123 | for (uint16_t c = 0; c < len; c++) { 124 | Serial.printf("%02X", buffer[c]); 125 | } 126 | Serial.println(); 127 | } 128 | 129 | 130 | #endif -------------------------------------------------------------------------------- /examples/LoRaWAN_ESP8266/config.h: -------------------------------------------------------------------------------- 1 | #ifndef _CONFIG_H 2 | #define _CONFIG_H 3 | 4 | #include 5 | 6 | // How often to send an uplink - consider legal & FUP constraints - see notes 7 | const uint32_t uplinkIntervalSeconds = 5UL * 60UL; // minutes x seconds 8 | 9 | // JoinEUI - previous versions of LoRaWAN called this AppEUI 10 | // for development purposes you can use all zeros - see wiki for details 11 | #define RADIOLIB_LORAWAN_JOIN_EUI 0x0000000000000000 12 | 13 | // The Device EUI & two keys can be generated on the TTN console 14 | #ifndef RADIOLIB_LORAWAN_DEV_EUI // Replace with your Device EUI 15 | #define RADIOLIB_LORAWAN_DEV_EUI 0x--------------- 16 | #endif 17 | #ifndef RADIOLIB_LORAWAN_APP_KEY // Replace with your App Key 18 | #define RADIOLIB_LORAWAN_APP_KEY 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x-- 19 | #endif 20 | #ifndef RADIOLIB_LORAWAN_NWK_KEY // Put your Nwk Key here 21 | #define RADIOLIB_LORAWAN_NWK_KEY 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x--, 0x-- 22 | #endif 23 | 24 | // For the curious, the #ifndef blocks allow for automated testing &/or you can 25 | // put your EUI & keys in to your platformio.ini - see wiki for more tips 26 | 27 | 28 | 29 | // Regional choices: EU868, US915, AU915, AS923, IN865, KR920, CN780, CN500 30 | const LoRaWANBand_t Region = EU868; 31 | const uint8_t subBand = 0; // For US915, change this to 2, otherwise leave on 0 32 | 33 | 34 | // ============================================================================ 35 | // Below is to support the sketch - only make changes if the notes say so ... 36 | 37 | // Auto select MCU <-> radio connections 38 | // If you get an error message when compiling, it may be that the 39 | // pinmap could not be determined - see the notes for more info 40 | 41 | // Adafruit 42 | #if defined(ARDUINO_SAMD_FEATHER_M0) 43 | #pragma message ("Adafruit Feather M0 with RFM95") 44 | #pragma message ("Link required on board") 45 | SX1276 radio = new Module(8, 3, 4, 6); 46 | 47 | 48 | // LilyGo 49 | #elif defined(ARDUINO_TTGO_LORA32_V1) 50 | #pragma message ("TTGO LoRa32 v1 - no Display") 51 | SX1276 radio = new Module(18, 26, 14, 33); 52 | 53 | #elif defined(ARDUINO_TTGO_LORA32_V2) 54 | #pragma error ("ARDUINO_TTGO_LORA32_V2 awaiting pin map") 55 | 56 | #elif defined(ARDUINO_TTGO_LoRa32_v21new) // T3_V1.6.1 57 | #pragma message ("Using TTGO LoRa32 v2.1 marked T3_V1.6.1 + Display") 58 | SX1276 radio = new Module(18, 26, 14, 33); 59 | 60 | #elif defined(ARDUINO_TBEAM_USE_RADIO_SX1262) 61 | #pragma error ("ARDUINO_TBEAM_USE_RADIO_SX1262 awaiting pin map") 62 | 63 | #elif defined(ARDUINO_TBEAM_USE_RADIO_SX1276) 64 | #pragma message ("Using TTGO LoRa32 v2.1 marked T3_V1.6.1 + Display") 65 | SX1276 radio = new Module(18, 26, 23, 33); 66 | 67 | 68 | // Heltec 69 | #elif defined(ARDUINO_HELTEC_WIFI_LORA_32) 70 | #pragma error ("ARDUINO_HELTEC_WIFI_LORA_32 awaiting pin map") 71 | 72 | #elif defined(ARDUINO_heltec_wifi_kit_32_V2) 73 | #pragma message ("ARDUINO_heltec_wifi_kit_32_V2 awaiting pin map") 74 | SX1276 radio = new Module(18, 26, 14, 35); 75 | 76 | #elif defined(ARDUINO_heltec_wifi_kit_32_V3) 77 | #pragma message ("Using Heltec WiFi LoRa32 v3 - Display + USB-C") 78 | SX1262 radio = new Module(8, 14, 12, 13); 79 | 80 | #elif defined(ARDUINO_CUBECELL_BOARD) 81 | #pragma message ("Using TTGO LoRa32 v2.1 marked T3_V1.6.1 + Display") 82 | SX1262 radio = new Module(RADIOLIB_BUILTIN_MODULE); 83 | 84 | #elif defined(ARDUINO_CUBECELL_BOARD_V2) 85 | #pragma error ("ARDUINO_CUBECELL_BOARD_V2 awaiting pin map") 86 | 87 | 88 | #else 89 | #pragma message ("Unknown board - no automagic pinmap available") 90 | 91 | // SX1262 pin order: Module(NSS/CS, DIO1, RESET, BUSY); 92 | // SX1262 radio = new Module(8, 14, 12, 13); 93 | 94 | // SX1278 pin order: Module(NSS/CS, DIO0, RESET, DIO1); 95 | // SX1278 radio = new Module(10, 2, 9, 3); 96 | 97 | #endif 98 | 99 | 100 | // Copy over the EUI's & keys in to the something that will not compile if incorrectly formatted 101 | uint64_t joinEUI = RADIOLIB_LORAWAN_JOIN_EUI; 102 | uint64_t devEUI = RADIOLIB_LORAWAN_DEV_EUI; 103 | uint8_t appKey[] = { RADIOLIB_LORAWAN_APP_KEY }; 104 | uint8_t nwkKey[] = { RADIOLIB_LORAWAN_NWK_KEY }; 105 | 106 | // Create the LoRaWAN node 107 | LoRaWANNode node(&radio, &Region, subBand); 108 | 109 | 110 | // Helper function to display any issues 111 | void debug(bool isFail, const __FlashStringHelper* message, int state, bool Freeze) { 112 | if (isFail) { 113 | Serial.print(message); 114 | Serial.print("("); 115 | Serial.print(state); 116 | Serial.println(")"); 117 | while (Freeze); 118 | } 119 | } 120 | 121 | // Helper function to display a byte array 122 | void arrayDump(uint8_t *buffer, uint16_t len) { 123 | for (uint16_t c = 0; c < len; c++) { 124 | Serial.printf("%02X", buffer[c]); 125 | } 126 | Serial.println(); 127 | } 128 | 129 | 130 | #endif -------------------------------------------------------------------------------- /examples/LoRaWAN_ESP32/LoRaWAN_ESP32.ino: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | This demonstrates how to save the join information in to permanent memory 5 | so that if the power fails, batteries run out or are changed, the rejoin 6 | is more efficient & happens sooner due to the way that LoRaWAN secures 7 | the join process - see the wiki for more details. 8 | 9 | This is typically useful for devices that need more power than a battery 10 | driven sensor - something like a air quality monitor or GPS based device that 11 | is likely to use up it's power source resulting in loss of the session. 12 | 13 | The relevant code is flagged with a ##### comment 14 | 15 | Saving the entire session is possible but not demonstrated here - it has 16 | implications for flash wearing and complications with which parts of the 17 | session may have changed after an uplink. So it is assumed that the device 18 | is going in to deep-sleep, as below, between normal uplinks. 19 | 20 | Once you understand what happens, feel free to delete the comments and 21 | Serial.prints - we promise the final result isn't that many lines. 22 | 23 | */ 24 | 25 | #if !defined(ESP32) 26 | #pragma error ("This is not the example your device is looking for - ESP32 only") 27 | #endif 28 | 29 | // ##### load the ESP32 preferences facilites 30 | #include 31 | Preferences store; 32 | 33 | // LoRaWAN config, credentials & pinmap 34 | #include "config.h" 35 | 36 | #include 37 | 38 | // utilities & vars to support ESP32 deep-sleep. The RTC_DATA_ATTR attribute 39 | // puts these in to the RTC memory which is preserved during deep-sleep 40 | RTC_DATA_ATTR uint16_t bootCount = 0; 41 | RTC_DATA_ATTR uint16_t bootCountSinceUnsuccessfulJoin = 0; 42 | RTC_DATA_ATTR uint8_t LWsession[RADIOLIB_LORAWAN_SESSION_BUF_SIZE]; 43 | 44 | // abbreviated version from the Arduino-ESP32 package, see 45 | // https://espressif-docs.readthedocs-hosted.com/projects/arduino-esp32/en/latest/api/deepsleep.html 46 | // for the complete set of options 47 | void print_wakeup_reason() { 48 | esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause(); 49 | if (wakeup_reason == ESP_SLEEP_WAKEUP_TIMER) { 50 | Serial.println(F("Wake from sleep")); 51 | } else { 52 | Serial.print(F("Wake not caused by deep sleep: ")); 53 | Serial.println(wakeup_reason); 54 | } 55 | 56 | Serial.print(F("Boot count: ")); 57 | Serial.println(++bootCount); // increment before printing 58 | } 59 | 60 | // put device in to lowest power deep-sleep mode 61 | void gotoSleep(uint32_t seconds) { 62 | esp_sleep_enable_timer_wakeup(seconds * 1000UL * 1000UL); // function uses uS 63 | Serial.println(F("Sleeping\n")); 64 | Serial.flush(); 65 | 66 | esp_deep_sleep_start(); 67 | 68 | // if this appears in the serial debug, we didn't go to sleep! 69 | // so take defensive action so we don't continually uplink 70 | Serial.println(F("\n\n### Sleep failed, delay of 5 minutes & then restart ###\n")); 71 | delay(5UL * 60UL * 1000UL); 72 | ESP.restart(); 73 | } 74 | 75 | int16_t lwActivate() { 76 | int16_t state = RADIOLIB_ERR_UNKNOWN; 77 | 78 | // setup the OTAA session information 79 | node.beginOTAA(joinEUI, devEUI, nwkKey, appKey); 80 | 81 | Serial.println(F("Recalling LoRaWAN nonces & session")); 82 | // ##### setup the flash storage 83 | store.begin("radiolib"); 84 | // ##### if we have previously saved nonces, restore them and try to restore session as well 85 | if (store.isKey("nonces")) { 86 | uint8_t buffer[RADIOLIB_LORAWAN_NONCES_BUF_SIZE]; // create somewhere to store nonces 87 | store.getBytes("nonces", buffer, RADIOLIB_LORAWAN_NONCES_BUF_SIZE); // get them from the store 88 | state = node.setBufferNonces(buffer); // send them to LoRaWAN 89 | debug(state != RADIOLIB_ERR_NONE, F("Restoring nonces buffer failed"), state, false); 90 | 91 | // recall session from RTC deep-sleep preserved variable 92 | state = node.setBufferSession(LWsession); // send them to LoRaWAN stack 93 | 94 | // if we have booted more than once we should have a session to restore, so report any failure 95 | // otherwise no point saying there's been a failure when it was bound to fail with an empty LWsession var. 96 | debug((state != RADIOLIB_ERR_NONE) && (bootCount > 1), F("Restoring session buffer failed"), state, false); 97 | 98 | // if Nonces and Session restored successfully, activation is just a formality 99 | // moreover, Nonces didn't change so no need to re-save them 100 | if (state == RADIOLIB_ERR_NONE) { 101 | Serial.println(F("Succesfully restored session - now activating")); 102 | state = node.activateOTAA(); 103 | debug((state != RADIOLIB_LORAWAN_SESSION_RESTORED), F("Failed to activate restored session"), state, true); 104 | 105 | // ##### close the store before returning 106 | store.end(); 107 | return(state); 108 | } 109 | 110 | } else { // store has no key "nonces" 111 | Serial.println(F("No Nonces saved - starting fresh.")); 112 | } 113 | 114 | // if we got here, there was no session to restore, so start trying to join 115 | state = RADIOLIB_ERR_NETWORK_NOT_JOINED; 116 | while (state != RADIOLIB_LORAWAN_NEW_SESSION) { 117 | Serial.println(F("Join ('login') to the LoRaWAN Network")); 118 | state = node.activateOTAA(); 119 | 120 | // ##### save the join counters (nonces) to permanent store 121 | Serial.println(F("Saving nonces to flash")); 122 | uint8_t buffer[RADIOLIB_LORAWAN_NONCES_BUF_SIZE]; // create somewhere to store nonces 123 | uint8_t *persist = node.getBufferNonces(); // get pointer to nonces 124 | memcpy(buffer, persist, RADIOLIB_LORAWAN_NONCES_BUF_SIZE); // copy in to buffer 125 | store.putBytes("nonces", buffer, RADIOLIB_LORAWAN_NONCES_BUF_SIZE); // send them to the store 126 | 127 | // we'll save the session after an uplink 128 | 129 | if (state != RADIOLIB_LORAWAN_NEW_SESSION) { 130 | Serial.print(F("Join failed: ")); 131 | Serial.println(state); 132 | 133 | // how long to wait before join attempts. This is an interim solution pending 134 | // implementation of TS001 LoRaWAN Specification section #7 - this doc applies to v1.0.4 & v1.1 135 | // it sleeps for longer & longer durations to give time for any gateway issues to resolve 136 | // or whatever is interfering with the device <-> gateway airwaves. 137 | uint32_t sleepForSeconds = min((bootCountSinceUnsuccessfulJoin++ + 1UL) * 60UL, 3UL * 60UL); 138 | Serial.print(F("Boots since unsuccessful join: ")); 139 | Serial.println(bootCountSinceUnsuccessfulJoin); 140 | Serial.print(F("Retrying join in ")); 141 | Serial.print(sleepForSeconds); 142 | Serial.println(F(" seconds")); 143 | 144 | gotoSleep(sleepForSeconds); 145 | 146 | } // if activateOTAA state 147 | } // while join 148 | 149 | Serial.println(F("Joined")); 150 | 151 | // reset the failed join count 152 | bootCountSinceUnsuccessfulJoin = 0; 153 | 154 | delay(1000); // hold off off hitting the airwaves again too soon - an issue in the US 155 | 156 | // ##### close the store 157 | store.end(); 158 | return(state); 159 | } 160 | 161 | // setup & execute all device functions ... 162 | void setup() { 163 | Serial.begin(115200); 164 | while (!Serial); // wait for serial to be initalised 165 | delay(2000); // give time to switch to the serial monitor 166 | Serial.println(F("\nSetup")); 167 | print_wakeup_reason(); 168 | 169 | int16_t state = 0; // return value for calls to RadioLib 170 | 171 | // setup the radio based on the pinmap (connections) in config.h 172 | Serial.println(F("Initalise the radio")); 173 | state = radio.begin(); 174 | debug(state != RADIOLIB_ERR_NONE, F("Initalise radio failed"), state, true); 175 | 176 | // activate node by restoring session or otherwise joining the network 177 | state = lwActivate(); 178 | // state is one of RADIOLIB_LORAWAN_NEW_SESSION or RADIOLIB_LORAWAN_SESSION_RESTORED 179 | 180 | // ----- and now for the main event ----- 181 | Serial.println(F("Sending uplink")); 182 | 183 | // this is the place to gather the sensor inputs 184 | // instead of reading any real sensor, we just generate some random numbers as example 185 | uint8_t value1 = radio.random(100); 186 | uint16_t value2 = radio.random(2000); 187 | 188 | // build payload byte array 189 | uint8_t uplinkPayload[3]; 190 | uplinkPayload[0] = value1; 191 | uplinkPayload[1] = highByte(value2); // See notes for high/lowByte functions 192 | uplinkPayload[2] = lowByte(value2); 193 | 194 | // perform an uplink 195 | state = node.sendReceive(uplinkPayload, sizeof(uplinkPayload)); 196 | debug((state < RADIOLIB_ERR_NONE) && (state != RADIOLIB_ERR_NONE), F("Error in sendReceive"), state, false); 197 | 198 | Serial.print(F("FCntUp: ")); 199 | Serial.println(node.getFCntUp()); 200 | 201 | // now save session to RTC memory 202 | uint8_t *persist = node.getBufferSession(); 203 | memcpy(LWsession, persist, RADIOLIB_LORAWAN_SESSION_BUF_SIZE); 204 | 205 | // wait until next uplink - observing legal & TTN FUP constraints 206 | gotoSleep(uplinkIntervalSeconds); 207 | 208 | } 209 | 210 | 211 | // The ESP32 wakes from deep-sleep and starts from the very beginning. 212 | // It then goes back to sleep, so loop() is never called and which is 213 | // why it is empty. 214 | 215 | void loop() {} 216 | -------------------------------------------------------------------------------- /examples/LoRaWAN_ESP8266/LoRaWAN_ESP8266.ino: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | This demonstrates how to save the join information in to permanent memory 5 | so that if the power fails, batteries run out or are changed, the rejoin 6 | is more efficient & happens sooner due to the way that LoRaWAN secures 7 | the join process - see the wiki for more details. 8 | 9 | This is typically useful for devices that need more power than a battery 10 | driven sensor - something like a air quality monitor or GPS based device that 11 | is likely to use up it's power source resulting in loss of the session. 12 | 13 | The relevant code is flagged with a ##### comment 14 | 15 | Saving the entire session is possible but not demonstrated here - it has 16 | implications for flash wearing and complications with which parts of the 17 | session may have changed after an uplink. So it is assumed that the device 18 | is going in to deep-sleep, as below, between normal uplinks. 19 | 20 | This example uses deep sleep mode, so connect D0=GPIO16 and RST 21 | pins before running it. 22 | 23 | */ 24 | 25 | #if !defined(ESP8266) 26 | #pragma error ("This is not the example your device is looking for - ESP8266 only") 27 | #endif 28 | 29 | // ##### load the ESP8266 persistent storage facilites 30 | #include 31 | 32 | // LoRaWAN config, credentials & pinmap 33 | #include "config.h" 34 | 35 | #include 36 | 37 | // utilities & vars to support ESP8266 deep-sleep. 38 | // the contents of these variables is restored from RTC RAM upon deepsleep wake 39 | uint8_t LWsession[RADIOLIB_LORAWAN_SESSION_BUF_SIZE]; 40 | uint32_t bootCount = 1; 41 | uint32_t bootCountSinceUnsuccessfulJoin = 0; 42 | 43 | // print a human-readable format of the wakeup cause: 44 | // external (reset) or Deep-sleep (timer / button) 45 | void print_wakeup_reason() { 46 | Serial.print(F("Wake caused by: ")); 47 | Serial.println(ESP.getResetReason()); 48 | if(ESP.getResetReason() != "Deep-Sleep Wake") { 49 | Serial.println("Initialising RTC RAM to 0"); 50 | uint8_t rtcMemory[512] = { 0 }; 51 | if(!ESP.rtcUserMemoryWrite(0, (uint32_t *)&rtcMemory, sizeof(rtcMemory))) { 52 | Serial.println("RTC write failed!"); 53 | } 54 | } 55 | 56 | } 57 | 58 | // put device in to lowest power deep-sleep mode 59 | void gotoSleep(uint32_t seconds) { 60 | uint32_t address = 0; 61 | Serial.println(F("Storing bootcount variables in RTC RAM")); 62 | if(!ESP.rtcUserMemoryWrite(address, &bootCount, sizeof(bootCount))) { 63 | Serial.println("RTC write failed!"); 64 | } 65 | address += sizeof(bootCount); 66 | if(!ESP.rtcUserMemoryWrite(address, &bootCountSinceUnsuccessfulJoin, sizeof(bootCountSinceUnsuccessfulJoin))) { 67 | Serial.println("RTC write failed!"); 68 | } 69 | 70 | Serial.println(F("Sleeping\n")); 71 | Serial.flush(); 72 | 73 | ESP.deepSleep(seconds * 1000UL * 1000UL); // function uses uS 74 | 75 | // if this appears in the serial debug, we didn't go to sleep! 76 | // so take defensive action so we don't continually uplink 77 | Serial.println(F("\n\n### Sleep failed, delay of 5 minutes & then restart ###\n")); 78 | delay(5UL * 60UL * 1000UL); 79 | ESP.restart(); 80 | } 81 | 82 | 83 | 84 | // setup & execute all device functions ... 85 | void setup() { 86 | Serial.begin(74880); // match the bootloader baud rate 87 | while (!Serial); // wait for serial to be initalised 88 | delay(2000); // give time to switch to the serial monitor 89 | Serial.println(F("\nSetup")); 90 | 91 | print_wakeup_reason(); 92 | 93 | // restore the bootCount variables from RTC deep-sleep preserved RAM 94 | uint32_t address = 0; 95 | Serial.println(F("Recalling boot counts")); 96 | if(!ESP.rtcUserMemoryRead(address, &bootCount, sizeof(bootCount))) { 97 | Serial.println("RTC read failed!"); 98 | } 99 | address += sizeof(bootCount); // increment address for next read 100 | if(!ESP.rtcUserMemoryRead(address, &bootCountSinceUnsuccessfulJoin, sizeof(bootCountSinceUnsuccessfulJoin))) { 101 | Serial.println("RTC read failed!"); 102 | } 103 | address += sizeof(bootCountSinceUnsuccessfulJoin); // increment address for next read 104 | 105 | Serial.print(F("Boot count: ")); 106 | Serial.println(++bootCount); 107 | 108 | int16_t state = 0; // return value for calls to RadioLib 109 | 110 | // setup the radio based on the pinmap (connections) in config.h 111 | Serial.println(F("Initalise the radio")); 112 | state = radio.begin(); 113 | debug(state != RADIOLIB_ERR_NONE, F("Initalise radio failed"), state, true); 114 | 115 | Serial.println(F("Recalling LoRaWAN nonces & session")); 116 | // ##### setup the flash storage 117 | EEPROM.begin(RADIOLIB_LORAWAN_NONCES_BUF_SIZE); 118 | uint8_t LWnonces[RADIOLIB_LORAWAN_NONCES_BUF_SIZE]; // create somewhere to store nonces 119 | for(uint8_t i = 0; i < RADIOLIB_LORAWAN_NONCES_BUF_SIZE; i++) { 120 | LWnonces[i] = EEPROM.read(i); 121 | } 122 | 123 | // if we have booted at least once we should have a session to restore, so report any failure 124 | // otherwise no point saying there's been a failure when it was bound to fail with an empty 125 | // LWnonces var. At this point, bootCount has already been incremented, hence the > 2 126 | state = node.setBufferNonces(LWnonces); // send them to LoRaWAN 127 | debug((state != RADIOLIB_ERR_NONE) && (bootCount > 2), F("Restoring nonces buffer failed"), state, false); 128 | 129 | // recall session from RTC deep-sleep preserved variable 130 | if(!ESP.rtcUserMemoryRead(address, (uint32_t *)&LWsession, RADIOLIB_LORAWAN_SESSION_BUF_SIZE)) { 131 | Serial.println("RTC read failed!"); 132 | } 133 | state = node.setBufferSession(LWsession); // send them to LoRaWAN stack 134 | // see comment above, no need to report a failure that is bound to occur on first boot 135 | debug((state != RADIOLIB_ERR_NONE) && (bootCount > 2), F("Restoring session buffer failed"), state, false); 136 | 137 | // process the restored session or failing that, create a new one & 138 | // return flag to indicate a fresh join is required 139 | Serial.println(F("Setup LoRaWAN session")); 140 | state = node.beginOTAA(joinEUI, devEUI, nwkKey, appKey, false); 141 | // see comment above, no need to report a failure that is bound to occur on first boot 142 | debug((state != RADIOLIB_ERR_NONE) && (bootCount > 2), F("Restore session failed"), state, false); 143 | 144 | // loop until successful join 145 | while (state != RADIOLIB_ERR_NONE) { 146 | Serial.println(F("Join ('login') to the LoRaWAN Network")); 147 | state = node.beginOTAA(joinEUI, devEUI, nwkKey, appKey, true); 148 | 149 | if (state < RADIOLIB_ERR_NONE) { 150 | Serial.print(F("Join failed: ")); 151 | Serial.println(state); 152 | 153 | // how long to wait before join attempts. This is an interim solution pending 154 | // implementation of TS001 LoRaWAN Specification section #7 - this doc applies to v1.0.4 & v1.1 155 | // it sleeps for longer & longer durations to give time for any gateway issues to resolve 156 | // or whatever is interfering with the device <-> gateway airwaves. 157 | uint32_t sleepForSeconds = min((bootCountSinceUnsuccessfulJoin++ + 1UL) * 60UL, 3UL * 60UL); 158 | Serial.print(F("Boots since unsuccessful join: ")); 159 | Serial.println(bootCountSinceUnsuccessfulJoin); 160 | Serial.print(F("Retrying join in ")); 161 | Serial.print(sleepForSeconds); 162 | Serial.println(F(" seconds")); 163 | 164 | gotoSleep(sleepForSeconds); 165 | 166 | } else { // join was successful 167 | Serial.println(F("Joined")); 168 | 169 | // ##### save the join counters (nonces) to permanent store 170 | Serial.println(F("Saving nonces to flash")); 171 | uint8_t *persist = node.getBufferNonces(); // get pointer to nonces 172 | memcpy(LWnonces, persist, RADIOLIB_LORAWAN_NONCES_BUF_SIZE); // copy in to buffer 173 | for(uint8_t i = 0; i < RADIOLIB_LORAWAN_NONCES_BUF_SIZE; i++) { 174 | EEPROM.write(i, LWnonces[i]); 175 | } 176 | EEPROM.commit(); 177 | 178 | // we'll save the session after the uplink 179 | 180 | // reset the failed join count 181 | bootCountSinceUnsuccessfulJoin = 0; 182 | 183 | delay(1000); // hold off off hitting the airwaves again too soon - an issue in the US 184 | 185 | } // if beginOTAA state 186 | } // while join 187 | 188 | // ##### close the EEPROM 189 | EEPROM.end(); 190 | 191 | 192 | // ----- and now for the main event ----- 193 | Serial.println(F("Sending uplink")); 194 | 195 | // read some inputs 196 | uint8_t Digital2 = digitalRead(2); 197 | uint16_t Analog1 = analogRead(3); 198 | 199 | // build payload byte array 200 | uint8_t uplinkPayload[3]; 201 | uplinkPayload[0] = Digital2; 202 | uplinkPayload[1] = highByte(Analog1); // see notes for high/lowByte functions 203 | uplinkPayload[2] = lowByte(Analog1); 204 | 205 | // perform an uplink 206 | state = node.sendReceive(uplinkPayload, sizeof(uplinkPayload)); 207 | debug((state != RADIOLIB_LORAWAN_NO_DOWNLINK) && (state != RADIOLIB_ERR_NONE), F("Error in sendReceive"), state, false); 208 | 209 | Serial.print(F("FcntUp: ")); 210 | Serial.println(node.getFcntUp()); 211 | 212 | // now save session to RTC memory 213 | uint8_t *persist = node.getBufferSession(); 214 | memcpy(LWsession, persist, RADIOLIB_LORAWAN_SESSION_BUF_SIZE); 215 | if(!ESP.rtcUserMemoryWrite(address, (uint32_t *)&LWsession, RADIOLIB_LORAWAN_SESSION_BUF_SIZE)) { 216 | Serial.println("RTC write failed!"); 217 | } 218 | 219 | // wait until next uplink - observing legal & TTN FUP constraints 220 | gotoSleep(uplinkIntervalSeconds); 221 | 222 | } 223 | 224 | 225 | // The ESP8266 wakes from deep-sleep and starts from the very beginning. 226 | // It then goes back to sleep, so loop() is never called and which is 227 | // why it is empty. 228 | 229 | void loop() {} -------------------------------------------------------------------------------- /examples/LoRaWAN_ESP32/notes.md: -------------------------------------------------------------------------------- 1 | > [!NOTE] 2 | > These are the notes as included with the original RadioLib LoRaWAN Starter example. We strongly recommend you test that out first. 3 | 4 | # RadioLib LoRaWAN on TTN starter script 5 | 6 | ## Welcome 7 | 8 | These notes are for someone who has successfully created a few sketches for their Arduino based device but is starting out with LoRaWAN. You don't have to be a C++ coding ninja but some familarity with C++ and procedural programming is assumed. The absolutely simplest way to get started is to buy some known good hardware that's all done for you so you can concentrate on the code & configuration. 9 | 10 | 11 | ## Introduction 12 | 13 | LoRaWAN is an amazing system for small battery powered sensors collecting data for years at a time. With great features comes some more complex elements which means it is not quite as simple as just providing WiFi credentials and pushing data through. It is in the range of setting up & customising the settings for a home router but with no wizards to do the heavy lifting for you. So we strongly recommend spending a couple of hours reviewing the TTN Getting Started section so you are aware of the minimum knowledge to make a successful start: https://www.thethingsnetwork.org/docs/lorawan/. Johan's video is amazing but is also drinking from the firehose. Read the text first and then watch the video on Youtube where there are bookmarks to deliver it in small digestable chunks. 14 | 15 | These notes plus a lot more are available in the wiki: https://github.com/jgromes/RadioLib/wiki/LoRaWAN 16 | 17 | For questions about using RadioLib there is the discussions section (https://github.com/jgromes/RadioLib/discussions) and if you believe you've found an issue (aka bug), the issues section (https://github.com/jgromes/RadioLib/issues). If posting an issue please ensure you tell us what hardware you are using and provide a debug log - make sure you enable `RADIOLIB_DEBUG_PROTOCOL`. If the question is more LoRaWAN or firmware related, then you can use the TTN forum: https://www.thethingsnetwork.org/forum/ 18 | 19 | 20 | ## Register & setup on TTN 21 | 22 | This sketch isn't particularly aimed at The Things Stack (TTS) but you can get a free Sandbox account and the following instructions are for that. Chirpstack works just as well, but the buttons and labels may have different names. We discourage the use of Helium as it does not support LoRaWAN v1.1 and has some other odd limitations. Other LoRaWAN Network Servers (LNS) have not been tested by the developers, so YMMV. 23 | 24 | Why no screen shots? TTS is a web based app, one that you will need to become familiar with and we will need to direct you to some of the less obvious parts. So much better that you learn the layouts in concept than slavishly follow screen shots that can & will go stale. 25 | 26 | There will be some instructions that you have to take on face value. You didn't learn to run before you walked and it's so much more encouraging to get started and build on success than get bogged down in endless details. Once you are up & running more of the details start to slot in to place. 27 | 28 | ### Register on TTN 29 | 30 | Go to https://www.thethingsnetwork.org/get-started and register - just like any other website. These instructions are for TTS Sandbox. 31 | 32 | Once you have confirmed your email address, you can login to the console here: https://console.cloud.thethings.network/. If you allow your browser to share your location the best console will be selected. For most users the best one is the obvious one, if you have any doubts you can ask on the forum here: https://www.thethingsnetwork.org/forum/ - you login with the exact same details. 33 | 34 | It is simpler to register your gateway first. If you don't have a gateway, then a The Things Indoor Gateway (TTIG) is a very affordable option. A gateway gives you a console to see if your device is being heard and is hugely useful when debugging a DIY device. If you are in range of a community gateway you may be lucky with your first device creation but you will never know if you are in range unless you have access to that gateway's console. 35 | 36 | You can read up on key concepts and troubleshooting here: https://www.thethingsindustries.com/docs/gateways/ 37 | 38 | LoRa stands for Long Range - having the gateway & device on the same desk tends to overload both receiver circuits when they hear a transmission so close to hand. The gateway should be 5 - 10m away, preferably with a solid wall in the way as well. 39 | 40 | ### Create your application 41 | 42 | An application is like a box to keep some devices in - normally doing the same thing - on larger deployments this may be 1,000's of similar devices. Starting out it is likely to be just a few so there is no need to get concerned about how to divide up your use just yet. 43 | 44 | Onced logged in to the console you can go in to Applications to create your first application. The ID must be all lower case or numbers, no spaces, dashes are OK and it has to be unique to the entire TTN community - so `first-app` will be rejected - you could use `your-username-first-app` as that's likely to be unique. The name and description are for your own use and are optional. 45 | 46 | The main menu for an application is in the left hand panel - nothing is needed there just yet. 47 | 48 | ### Create your device 49 | 50 | On the right hand side about half way down on your application's summary is a big blue button `+ Register end device`. Click this to create the settings for your first device. 51 | 52 | You are making your own device using a third party LoRaWAN stack so there will not be an entry in the device repository so choose 'Enter end device specifics manually'. 53 | 54 | Choose the Frequency plan appropriate for your region. Consider that almost all countries have laws relating to what frequencies you use so don't get creative. For Europe please use the recommended option. For other regions use the entry marked 'used by TTN'. 55 | 56 | Choose LoRaWAN 1.1.0 - the last one in the list - the latest specfication. RadioLib uses RP002 Regional Parameters 1.0.4. 57 | 58 | At this point you will be asked for your JoinEUI. As this is a DIY device and we are using RadioLib, you can use all zero's as recommended by The LoRa Alliance TR007 Technical Recommendations document. Once you've put in all zeros and clicked confirm you will be asked for a DevEUI, AppKey and NwkKey. It is preferable to have the console generate them so they are properly formatted. 59 | 60 | Your End device ID can be changed to make the device more identifiable. Something related to your hardware helps - like `devicename-01`. The you can click the blue 'Register device'. 61 | 62 | When retail sensors are being deployed, a device is registered, batteries put in, it joins and gets on with sending data for the next few years. For development purposes however we need to turn off one of the security settings so that you can join & uplink out of the normal sequence that a device in the field would do. 63 | 64 | Click on General Settings, scroll down to Join settings, click the Expand button, scroll down and click the 'Resets join nonces' option. You will see a warning about replay attacks which is entirely proper & correct. If anyone eavesdropping in your area on your LoRa transmissions could fake a join and send uplinks from their device but only if they happened to find out your AppKey & NwkKey which is kept securely on the TTN servers and is never transmitted over the air, so they'd also have to login to your account, which is protected by your password. 65 | 66 | You then need to copy over the device details in to the config file for RadioLib. There are buttons to copy items to the clipboard so you don't have to hand type them. 67 | 68 | ### Copy & Paste made easy 69 | 70 | You can copy the EUIs & keys from the device overview section. 71 | 72 | The EUIs are really straightforward - click the clipboard icon at the right hand end of the EUI display field and it will be copied in the format you need. You can then paste it in to the code - you must leave the 0x in place so the compiler knows that it's a hex value. 73 | 74 | The keys are relatively straightforward. Click the eye icon at the right hand end of the field. Then click the <> icon that will appear to the left. This will format the hex values as an array. Then you can click the clipboard icon to copy the array and then paste it between the { } brackets. 75 | 76 | ### Secrets to keep safe. 77 | 78 | The Join & Dev EUI's are transmitted in plain text when the device joins a network. The gateway ID is public. If you have an issue and are asked for details, there are only three things to keep private - your password, the keys which are used for encryption and any API keys you create which are used for accessing your data & configuration. 79 | 80 | 81 | ### Monitoring your device 82 | 83 | If you are on your application summary page you'll see uplinks in the small activity box top right with a link to the full size table. If you click the Live Data menu item on the left it will show activity for all the devices registered on the application in the full window. 84 | 85 | If you just want your devices activity, from the summary page click on the device in the list in the middle of the page. 86 | 87 | The main menu for a device is the horizontal band: Overview, Live Data, Messaging etc. You can click Live Data or the link above the small activity box. 88 | 89 | **The console shows LIVE data - not a history of everything that has ever happened. A LNS is a management & relay service, not a database. When you open the console you may see a summary of recent activity - this is a bonus. You must leave the console open, even in another tab, if you want to see live activity.** 90 | 91 | 92 | ### Explore 93 | 94 | Nothing on the console can be upset unless you confirm a warning message, so you are safe to explore the different menus to orientate yourself. This is very good idea so you have an understanding of the layout of the land and shouldn't take more than 10 or 15 minutes. The documentation & volunteers on GitHub and the TTN forum will make refer to parts of the console without giving blow by blow directions. 95 | 96 | 97 | 98 | 99 | ## The config.h 100 | 101 | ### The uplinkInterval 102 | 103 | LoRaWAN devices typically send small amounts of data at intervals between 15 minutes through to once per day. This allows a device to run on two AA batteries for 2 to 5 years. Hoping that LoRaWAN can move lots of data and your device can regularly receive commands to do something on demand is trying to bend the LoRaWAN system in ways it is not designed for and usually ends up with far too many issues to unravel. 104 | 105 | The radio frequencies that are used are usually shared with other Industrial, Scientific & Medical, known as ISM, users. The LoRa modulation is particularly resistant to interference due to other simultaneous transmissions on the same frequency but too much local activity will mean that not all uplinks get through. The Things Industries suggest designing a system to a potential packet loss rate of 10%. Typically we see 1 or 2% loss. This is entirely down to shared use of the radio waves, once an uplink is heard by a gateway the system is super reliable through The Things Stack. 106 | 107 | To ensure that the shared ISM bands are fairly used there are limits defined in law on how often you can transmit, called Duty Cycle. The details vary by region or country but typically you can only transmit for 1% of the time. Some frequencies you can only use 0.1% of the time. See https://www.thethingsnetwork.org/docs/lorawan/duty-cycle/ for more information. 108 | 109 | Additionally, as The Things Stack Sandbox aka TTN is an array of servers in three locations around the world paid for by The Things Industries, there is a Fair Use Policy so that those learning LoRaWAN, communities, hobbyists & makers are guided on how much of the resource any one device can use. In short, it's 30 seconds of airtime a day and 10 downlinks. When a gateway is transmitting a downlink it can not hear any uplinks (contributing to the potential uplink loss outlined above). The community consensus is that 1 downlink a fortnight to update or adjust settings is appropriate. See https://www.thethingsnetwork.org/docs/lorawan/duty-cycle/#fair-use-policy for more information. 110 | 111 | You can see what intervals can be used with this interactive calculator: https://avbentem.github.io/airtime-calculator/ttn/. Devices further away from gateways will have to use a higher Spread Factor to be heard - do not assume everything will happen at SF7. An uplink takes a minimum of 6 seconds from start to end, sometimes longer if the device is further away from the gateway, so you will need to be patient for just a short while whilst waiting for feedback after seeing "Sending uplink" 112 | 113 | With all these considerations, trying to use LoRaWAN for command & control isn't appropriate and realtime GPS tracking almost always breaches FUP and usually legal limits, leaving aside the challenges of coverage. 114 | 115 | See the hints & tips section on testing your device. 116 | 117 | 118 | ### EUI's & Keys 119 | 120 | In the `config.h` towards the top there are four lines thus: 121 | 122 | // replace-with-your-device-id 123 | uint64_t joinEUI = 0x0000000000000000; 124 | uint64_t devEUI = 0x0000000000000000; 125 | uint8_t appKey[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 126 | uint8_t nwkKey[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 127 | 128 | On the TTN console on the device summary page, click the clipboard icon next to the DevEUI, highlight the 16 0's in the third line after the x and paste. 129 | 130 | The devEUI must start with 0x and will end up looking something like 0x70B3D57ED006544E 131 | 132 | For the appKey we need TTN to format it correctly. Click the eye icon and an extra icon will appear <> - click this and the key will be formatted for you. Click the clipboard icon and then paste over the 32 0x00's in the config file. Then do the same for nwkKey. 133 | 134 | A key will end up something like 0x31, 0x16, 0x6A, 0x22, 0x97, 0x52, 0xB6, 0x34, 0x57, 0x45, 0x1B, 0xC3, 0xC9, 0xD8, 0x83, 0xE8 135 | 136 | 137 | ### Region 138 | 139 | The region value you use MUST match the one you selected on the console. 140 | 141 | If you are using US915 or AU915 then you should change the subBand const to 2. 142 | 143 | ### The pinmap 144 | 145 | This is the connection between your microcontroller (ESP32, ATmega, SAMD etc.) and the radio (SX1276, SX1262, LR1110 etc.). 146 | You have to select the correct module and set the correct pins. 147 | 148 | Pin maps for commonly used radio modules are kept in a separate library, called RadioBoards: https://github.com/radiolib-org/RadioBoards 149 | 150 | It can automatically detect your microcontroller platform and radio, and configure things for you. If you have a board that is not supported by RadioBoards, feel free to suggest it in the RadioBoards issues, or better yet - open a pull request there! 151 | 152 | 153 | ## Observations on the main sketch 154 | 155 | Most of the sketch has comments that tell you what the various parts are doing. This should add a little more info: 156 | 157 | ### The Join 158 | 159 | When a device is first started, it needs to register with the LoRaWAN Network Server (LNS) and setup it's session. With the settings from the console copied over and a gateway an appropriate distance away, most of the time the join will 'just work'. 160 | 161 | If it doesn't, then there is no point trying repeatedly without going through the troubleshootng sequence. So this starter sketch will try once only to save the airwaves & TTN Community servers from repeated misfires. 162 | 163 | 164 | ### The payload 165 | 166 | You may see other starter sketches sending text. Apart from being massively inefficient, the text isn't easily displayed on the TTN console which makes it rather pointless and pro embedded engineers don't send strings. So this sketch sends the data as a sequence of bytes as recommended. 167 | 168 | Further reading on this can be found here, just ignore the pink message about v2, it's all still valid: https://www.thethingsnetwork.org/docs/devices/bytes/ 169 | 170 | We've not assumed anything about any sensors you have, so we are just reading a digital & an analog pin. An analog reading is typically a two byte value - an integer - this is split using the Arduino highByte & lowByte function. You'll see how we put it back together in the TTN console below. 171 | 172 | 173 | ## TTN Console Payload Decoder 174 | 175 | Coming soon 176 | 177 | ## Hints & Tips 178 | 179 | ### Device testing 180 | 181 | The LoRaWAN code base works to a specification and once you are happy your device is able to join & send a few dozen uplinks, continuing to sit around waiting for an uplink to test your sensor code & payload format is a waste of your time. The solution is to write everything else in a different sketch, output the array to the serial console and then you can copy & paste the hex array in to the TTN console Payload Formatters section to test the decoding. 182 | --------------------------------------------------------------------------------