├── .gitignore ├── LICENSE ├── README.md ├── esp32-ttnmapper-gps ├── esp32-ttnmapper-gps.ino ├── gps.ino ├── lmic_Payload.ino ├── oled.ino └── pbutton.ino └── img ├── hardware-gps.jpg ├── hardware-ttgo.jpg ├── hardware-ttn-box.jpg ├── hardware-ttn.jpg ├── ttn-integration.png ├── ttn-node-abp.png ├── ttn-node.png ├── ttn-payload.png └── ttn.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Luiz Henrique Cassettari 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # esp32-ttnmapper-gps 2 | 3 | Program to make a LoRaWAN node based on the TTGO LoRa 915MHz plus GPS (ATGM336H). The code is configured to connect to [The Things Network](https://www.thethingsnetwork.org/) using US frequency and send the gps values to the [ttnmapper](http://ttnmapper.org/) 4 | 5 | [![travis](https://travis-ci.org/ricaun/esp32-ttnmapper-gps.svg)](https://travis-ci.org/ricaun/esp32-ttnmapper-gps) 6 | [![license](https://img.shields.io/github/license/ricaun/esp32-ttnmapper-gps.svg)](LICENSE) 7 | 8 | ttn 9 | 10 | ## Hardware 11 | 12 | * TTGO LORA32 915Mhz (Heltec Wifi LoRa 32) 13 | * GPS ATGM336H 14 | 15 | ### Images 16 | 17 | ttgogps 18 | ttnttn 19 | 20 | ### Schematic 21 | 22 | | ESP32 | GPS | 23 | | :----: | :-----: | 24 | | 5V | VCC | 25 | | GND | GND | 26 | | 34 | TX | 27 | | 35 | RX | 28 | 29 | ## Librarys 30 | 31 | * [LMIC](https://github.com/mcci-catena/arduino-lmic) 32 | * [ThingPulse OLED SSD1306](https://github.com/ThingPulse/esp8266-oled-ssd1306) 33 | * [TinyGPSPlus](https://github.com/mikalhart/TinyGPSPlus) 34 | 35 | ## The Things Network 36 | 37 | ### Application Configuration 38 | 39 | First it's a good idea to create a new application, them go on Payload Formats and put the code below. 40 | 41 | payload 42 | 43 | ```java 44 | function Decoder(bytes, port) { 45 | // Decode an uplink message from a buffer 46 | // (array) of bytes to an object of fields. 47 | var decoded = {}; 48 | // if (port === 1) decoded.led = bytes[0]; 49 | decoded.lat = ((bytes[0]<<16)>>>0) + ((bytes[1]<<8)>>>0) + bytes[2]; 50 | decoded.lat = (decoded.lat / 16777215.0 * 180) - 90; 51 | decoded.lon = ((bytes[3]<<16)>>>0) + ((bytes[4]<<8)>>>0) + bytes[5]; 52 | decoded.lon = (decoded.lon / 16777215.0 * 360) - 180; 53 | var altValue = ((bytes[6]<<8)>>>0) + bytes[7]; 54 | var sign = bytes[6] & (1 << 7); 55 | if(sign) 56 | { 57 | decoded.alt = 0xFFFF0000 | altValue; 58 | } 59 | else 60 | { 61 | decoded.alt = altValue; 62 | } 63 | decoded.hdop = bytes[8] / 10.0; 64 | return decoded; 65 | } 66 | ``` 67 | 68 | Next go on integration and add a integration TNN Mapper. (It's a good idea to add a experiment name) 69 | 70 | integration 71 | 72 | With the experiment name is possible to check only the experiment [criciuma](https://ttnmapper.org/experiments/map.php?name=criciuma). 73 | More information on the [TTN Mapper Documentation](https://www.thethingsnetwork.org/docs/applications/ttnmapper/). 74 | 75 | ### Node 76 | 77 | Create a new device and goes to setting, change the Activation Method to ABP and disable the Frame Counter Check, them save. 78 | 79 | abp 80 | 81 | Next goes to Overview on the bottow Example Code. 82 | 83 | node 84 | 85 | Copy the code and replace on the sample code (esp32-ttnmapper-gps.ino). 86 | 87 | If your board is diferent from the `Heltec Wifi LoRa 32` you should check the pins connection on `lmic_pins`. 88 | 89 | ---- 90 | 91 | Do you like this? Please [star this project on GitHub](https://github.com/ricaun/esp32-ttnmapper-gps/stargazers)! 92 | 93 | -------------------------------------------------------------------------------- /esp32-ttnmapper-gps/esp32-ttnmapper-gps.ino: -------------------------------------------------------------------------------- 1 | //----------------------------------------// 2 | // esp32-ttnmapper-gps.ino 3 | // 4 | // created 01/06/2019 5 | // by Luiz H. Cassettari 6 | //----------------------------------------// 7 | // Designed to work with esp32 lora 915MHz 8 | // Create a device with ABP on ttn 9 | // Create a integration with ttnmapper 10 | //----------------------------------------// 11 | 12 | const char *devAddr = "00000000"; 13 | const char *nwkSKey = "00000000000000000000000000000000"; 14 | const char *appSKey = "00000000000000000000000000000000"; 15 | 16 | //----------------------------------------// 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #define SEND_TIMER 10 23 | 24 | #define LORA_HTOI(c) ((c<='9')?(c-'0'):((c<='F')?(c-'A'+10):((c<='f')?(c-'a'+10):(0)))) 25 | #define LORA_TWO_HTOI(h, l) ((LORA_HTOI(h) << 4) + LORA_HTOI(l)) 26 | #define LORA_HEX_TO_BYTE(a, h, n) { for (int i = 0; i < n; i++) (a)[i] = LORA_TWO_HTOI(h[2*i], h[2*i + 1]); } 27 | #define LORA_DEVADDR(a) (uint32_t) ((uint32_t) (a)[3] | (uint32_t) (a)[2] << 8 | (uint32_t) (a)[1] << 16 | (uint32_t) (a)[0] << 24) 28 | 29 | static uint8_t DEVADDR[4]; 30 | static uint8_t NWKSKEY[16]; 31 | static uint8_t APPSKEY[16]; 32 | 33 | // Pin mapping 34 | const lmic_pinmap lmic_pins = { 35 | .nss = 18, 36 | .rxtx = LMIC_UNUSED_PIN, 37 | .rst = 14, 38 | .dio = {/*dio0*/ 26, /*dio1*/ 33, /*dio2*/ 32} 39 | }; 40 | 41 | void os_getArtEui (u1_t* buf) { } 42 | void os_getDevEui (u1_t* buf) { } 43 | void os_getDevKey (u1_t* buf) { } 44 | 45 | static osjob_t sendjob; 46 | 47 | void onEvent (ev_t ev) { 48 | Serial.print(os_getTime()); 49 | Serial.print(": "); 50 | switch (ev) { 51 | case EV_TXCOMPLETE: 52 | oled_status(" --- TXCOMPLETE --- "); 53 | Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)")); 54 | if (LMIC.txrxFlags & TXRX_ACK) 55 | { 56 | Serial.println(F("Received ack")); 57 | } 58 | if (LMIC.dataLen != 0 || LMIC.dataBeg != 0) { 59 | uint8_t port = 0; 60 | if (LMIC.txrxFlags & TXRX_PORT) 61 | { 62 | port = LMIC.frame[LMIC.dataBeg - 1]; 63 | } 64 | message(LMIC.frame + LMIC.dataBeg, LMIC.dataLen , port); 65 | } 66 | os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(SEND_TIMER), do_send); 67 | break; 68 | case EV_TXSTART: 69 | oled_status(" --- TXSTART --- "); 70 | Serial.println(F("EV_TXSTART")); 71 | break; 72 | } 73 | } 74 | 75 | 76 | 77 | void do_send(osjob_t* j) { 78 | // Check if there is not a current TX/RX job running 79 | if (LMIC.opmode & OP_TXRXPEND) { 80 | Serial.println(F("OP_TXRXPEND, not sending")); 81 | } else { 82 | PayloadNow(); 83 | } 84 | } 85 | 86 | void setup() { 87 | Serial.begin(115200); 88 | Serial.println(F("Starting")); 89 | 90 | oled_setup(); 91 | gps_setup(); 92 | button_setup(); 93 | 94 | LORA_HEX_TO_BYTE(DEVADDR, devAddr, 4); 95 | LORA_HEX_TO_BYTE(NWKSKEY, nwkSKey, 16); 96 | LORA_HEX_TO_BYTE(APPSKEY, appSKey, 16); 97 | 98 | if (LORA_DEVADDR(DEVADDR) == 0) while(true); 99 | 100 | os_init(); 101 | 102 | LMIC_reset(); 103 | LMIC_setSession (0x13, LORA_DEVADDR(DEVADDR), NWKSKEY, APPSKEY); 104 | LMIC_setAdrMode(0); 105 | LMIC_setClockError(MAX_CLOCK_ERROR * 10 / 100); 106 | LMIC_selectSubBand(1); 107 | LMIC_setLinkCheckMode(0); 108 | 109 | do_send(&sendjob); 110 | } 111 | 112 | void loop() { 113 | os_runloop_once(); 114 | gps_loop(); 115 | oled_loop(); 116 | if (button_loop()) 117 | { 118 | oled_mode(button_mode()); 119 | 120 | if (button_count() == 0) 121 | do_send(&sendjob); 122 | else if (button_count() == 2) 123 | { 124 | os_clearCallback(&sendjob); 125 | os_radio(RADIO_RST); 126 | } 127 | } 128 | } 129 | 130 | void message(const uint8_t *payload, size_t size, uint8_t port) 131 | { 132 | Serial.println("-- MESSAGE"); 133 | Serial.println("Received " + String(size) + " bytes on port " + String(port) + ":"); 134 | if (port == 0) 135 | { 136 | oled_status(" --- TX_CONFIRMED --- "); 137 | return; 138 | } 139 | if (size == 0) return; 140 | switch (port) { 141 | case 1: 142 | break; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /esp32-ttnmapper-gps/gps.ino: -------------------------------------------------------------------------------- 1 | //----------------------------------------// 2 | // gps.ino 3 | // 4 | // created 06/09/2018 5 | // update to esp32 - 05/06/2019 6 | // by Luiz H. Cassettari 7 | //----------------------------------------// 8 | 9 | 10 | #include 11 | #include 12 | 13 | #define RXPin 34 14 | #define TXPin 35 15 | #define GPSBaud 9600 16 | 17 | TinyGPSPlus gps; 18 | HardwareSerial ss(1); 19 | 20 | void gps_setup(){ 21 | ss.begin(GPSBaud, SERIAL_8N1, RXPin, TXPin); 22 | Serial.print(F("TinyGPS++ library v. ")); Serial.println(TinyGPSPlus::libraryVersion()); 23 | Serial.println(); 24 | } 25 | 26 | void gps_loop(){ 27 | while (ss.available() > 0) { 28 | gps.encode(ss.read()); 29 | } 30 | if (runEvery_gps(5000)) 31 | { 32 | Serial.println(gps_time() + " " + gps_date()); 33 | } 34 | } 35 | 36 | boolean gps_read(){ 37 | return (gps.location.isValid()); 38 | } 39 | 40 | float gps_latitude(){ 41 | return gps.location.lat(); 42 | } 43 | float gps_longitude(){ 44 | return gps.location.lng(); 45 | } 46 | 47 | float gps_meters() { 48 | return gps.altitude.meters(); 49 | } 50 | 51 | float gps_HDOP(){ 52 | if (gps.hdop.isValid()) 53 | return gps.hdop.hdop(); 54 | else 55 | return 40.0; 56 | } 57 | 58 | String gps_location() 59 | { 60 | if (gps.location.isValid()) 61 | { 62 | String str = ""; 63 | str += gps_latitude(); 64 | str += " "; 65 | str += gps_longitude(); 66 | return str; 67 | } 68 | else 69 | { 70 | return "#### ####"; 71 | } 72 | } 73 | 74 | String gps_date() 75 | { 76 | if (gps.date.isValid()) 77 | { 78 | int d = gps.date.day(); 79 | int m = gps.date.month(); 80 | int y = gps.date.year(); 81 | String str = ""; 82 | if (d < 10) str += "0"; 83 | str += d; 84 | str += "/"; 85 | if (m < 10) str += "0"; 86 | str += m; 87 | str += "/"; 88 | str += y; 89 | return str; 90 | } 91 | else 92 | { 93 | return "xx/xx/xxxx"; 94 | } 95 | } 96 | 97 | String gps_time() 98 | { 99 | if (gps.time.isValid()) 100 | { 101 | int s = gps.time.second(); 102 | int m = gps.time.minute(); 103 | int h = gps.time.hour(); 104 | String str = ""; 105 | if (h < 10) str += "0"; 106 | str += h; 107 | str += ":"; 108 | if (m < 10) str += "0"; 109 | str += m; 110 | str += ":"; 111 | if (s < 10) str += "0"; 112 | str += s; 113 | return str; 114 | } 115 | else 116 | { 117 | return "xx:xx:xx"; 118 | } 119 | } 120 | 121 | boolean runEvery_gps(unsigned long interval) 122 | { 123 | static unsigned long previousMillis = 0; 124 | unsigned long currentMillis = millis(); 125 | if (currentMillis - previousMillis >= interval) 126 | { 127 | previousMillis = currentMillis; 128 | return true; 129 | } 130 | return false; 131 | } 132 | 133 | 134 | void displayInfo() 135 | { 136 | Serial.print(F("Location: ")); 137 | if (gps.location.isValid()) 138 | { 139 | Serial.print(gps.location.lat(), 6); 140 | Serial.print(F(",")); 141 | Serial.print(gps.location.lng(), 6); 142 | } 143 | else 144 | { 145 | Serial.print(F("INVALID")); 146 | } 147 | 148 | Serial.print(F(" Date/Time: ")); 149 | if (gps.date.isValid()) 150 | { 151 | Serial.print(gps.date.month()); 152 | Serial.print(F("/")); 153 | Serial.print(gps.date.day()); 154 | Serial.print(F("/")); 155 | Serial.print(gps.date.year()); 156 | } 157 | else 158 | { 159 | Serial.print(F("INVALID")); 160 | } 161 | 162 | Serial.print(F(" ")); 163 | if (gps.time.isValid()) 164 | { 165 | if (gps.time.hour() < 10) Serial.print(F("0")); 166 | Serial.print(gps.time.hour()); 167 | Serial.print(F(":")); 168 | if (gps.time.minute() < 10) Serial.print(F("0")); 169 | Serial.print(gps.time.minute()); 170 | Serial.print(F(":")); 171 | if (gps.time.second() < 10) Serial.print(F("0")); 172 | Serial.print(gps.time.second()); 173 | Serial.print(F(".")); 174 | if (gps.time.centisecond() < 10) Serial.print(F("0")); 175 | Serial.print(gps.time.centisecond()); 176 | } 177 | else 178 | { 179 | Serial.print(F("INVALID")); 180 | } 181 | 182 | 183 | Serial.println(); 184 | } 185 | -------------------------------------------------------------------------------- /esp32-ttnmapper-gps/lmic_Payload.ino: -------------------------------------------------------------------------------- 1 | //----------------------------------------// 2 | // lmic_payload.ino 3 | // 4 | // created 03/06/2019 5 | // by Luiz Henrique Cassettari 6 | //----------------------------------------// 7 | 8 | uint8_t txBuffer[9]; 9 | uint32_t LatitudeBinary, LongitudeBinary; 10 | uint16_t altitudeGps; 11 | uint8_t hdopGps; 12 | 13 | void PayloadNow() 14 | { 15 | boolean confirmed = false; 16 | 17 | if (button_count() == 1) confirmed = true; 18 | 19 | if (gps_read()) { 20 | 21 | LatitudeBinary = ((gps_latitude() + 90) / 180) * 16777215; 22 | LongitudeBinary = ((gps_longitude() + 180) / 360) * 16777215; 23 | 24 | txBuffer[0] = ( LatitudeBinary >> 16 ) & 0xFF; 25 | txBuffer[1] = ( LatitudeBinary >> 8 ) & 0xFF; 26 | txBuffer[2] = LatitudeBinary & 0xFF; 27 | 28 | txBuffer[3] = ( LongitudeBinary >> 16 ) & 0xFF; 29 | txBuffer[4] = ( LongitudeBinary >> 8 ) & 0xFF; 30 | txBuffer[5] = LongitudeBinary & 0xFF; 31 | 32 | altitudeGps = gps_meters(); 33 | txBuffer[6] = ( altitudeGps >> 8 ) & 0xFF; 34 | txBuffer[7] = altitudeGps & 0xFF; 35 | 36 | hdopGps = gps_HDOP() * 10; 37 | txBuffer[8] = hdopGps & 0xFF; 38 | 39 | LMIC_setTxData2(1, txBuffer, sizeof(txBuffer), confirmed); 40 | } 41 | else 42 | { 43 | LMIC_setTxData2(1, txBuffer, 0, confirmed); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /esp32-ttnmapper-gps/oled.ino: -------------------------------------------------------------------------------- 1 | //----------------------------------------// 2 | // oled.ino 3 | // 4 | // created 23/06/2018 5 | // by Luiz Henrique Cassettari 6 | //----------------------------------------// 7 | 8 | #include 9 | #include "SSD1306.h" 10 | 11 | #define OLED_RUNEVERY 500 12 | #define OLED_TIMEOUT 4 13 | 14 | #define OLED_SDA 4 15 | #define OLED_SCL 15 16 | #define OLED_ADDR 0x3C 17 | #define OLED_RST 16 18 | 19 | SSD1306 display(OLED_ADDR, OLED_SDA, OLED_SCL);// i2c ADDR & SDA, SCL on wemos 20 | 21 | 22 | void oled_setup() { 23 | pinMode(OLED_RST, OUTPUT); 24 | digitalWrite(OLED_RST, HIGH); 25 | 26 | display.init(); 27 | display.resetDisplay(); 28 | display.displayOn(); 29 | display.flipScreenVertically(); 30 | display.setFont(ArialMT_Plain_10); 31 | 32 | display.setContrast(255); 33 | 34 | oled_display("..."); 35 | } 36 | 37 | void oled_display(String s) { 38 | display.clear(); 39 | display.setTextAlignment(TEXT_ALIGN_CENTER); 40 | display.drawString(64, 0, s); 41 | display.display(); 42 | } 43 | 44 | void oled_turnoff() { 45 | display.clear(); 46 | display.displayOff(); 47 | display.end(); 48 | //digitalWrite(OLED_RST, LOW); 49 | } 50 | 51 | static int oled_i; 52 | static String oled_mode_string; 53 | static int oled_mode_timeout; 54 | static String oled_status_string; 55 | static int oled_status_timeout; 56 | 57 | boolean oled_loop() { 58 | if (oled_runEvery(OLED_RUNEVERY)) { 59 | display.clear(); 60 | display.setTextAlignment(TEXT_ALIGN_CENTER); 61 | String str = ""; 62 | if (oled_mode_string == "") 63 | { 64 | str += timeOn(0); 65 | } 66 | else 67 | { 68 | str += oled_mode_string; 69 | } 70 | str += "\n"; 71 | str += gps_date(); 72 | str += "\n"; 73 | str += gps_time(); 74 | str += "\n"; 75 | str += gps_location(); 76 | str += "\n"; 77 | str += oled_status_string; 78 | display.drawString(64,0,str); 79 | display.display(); 80 | 81 | if (--oled_status_timeout == 0) 82 | { 83 | oled_status_timeout = 0; 84 | oled_status_string = ""; 85 | } 86 | 87 | if (--oled_mode_timeout == 0) 88 | { 89 | oled_mode_timeout = 0; 90 | oled_mode_string = ""; 91 | } 92 | 93 | return true; 94 | } 95 | return false; 96 | } 97 | 98 | void oled_status(String status) { 99 | oled_status_string = status; 100 | oled_status_timeout = OLED_TIMEOUT; 101 | } 102 | 103 | void oled_mode(String status) { 104 | oled_mode_string = status; 105 | oled_mode_timeout = OLED_TIMEOUT; 106 | } 107 | 108 | boolean oled_runEvery(unsigned long interval) 109 | { 110 | static unsigned long previousMillis = 0; 111 | unsigned long currentMillis = millis(); 112 | if (currentMillis - previousMillis >= interval) 113 | { 114 | previousMillis = currentMillis; 115 | return true; 116 | } 117 | return false; 118 | } 119 | 120 | String timeOn(unsigned long diff) 121 | { 122 | String str = ""; 123 | unsigned long t = millis() / 1000; 124 | int s = t % 60; 125 | int m = (t / 60) % 60; 126 | int h = (t / 3600); 127 | str += h; 128 | str += ":"; 129 | if (m < 10) 130 | str += "0"; 131 | str += m; 132 | str += ":"; 133 | if (s < 10) 134 | str += "0"; 135 | str += s; 136 | return str; 137 | } 138 | -------------------------------------------------------------------------------- /esp32-ttnmapper-gps/pbutton.ino: -------------------------------------------------------------------------------- 1 | //----------------------------------------// 2 | // pbutton.ino 3 | // 4 | // created 03/06/2019 5 | // by Luiz Henrique Cassettari 6 | //----------------------------------------// 7 | 8 | #define BUTTON 0 9 | #define BUTTON_MODE_MAX 3 10 | 11 | static int button_i; 12 | 13 | void button_setup() 14 | { 15 | pinMode(BUTTON, INPUT_PULLUP); 16 | } 17 | 18 | boolean button_press() 19 | { 20 | static boolean last; 21 | boolean now = digitalRead(BUTTON); 22 | boolean ret = (now == false & last == true); 23 | last = now; 24 | return ret; 25 | } 26 | 27 | boolean button_loop() 28 | { 29 | if (button_press()) 30 | { 31 | button_i++; 32 | return true; 33 | } 34 | return false; 35 | } 36 | 37 | int button_count() 38 | { 39 | return button_i; 40 | } 41 | 42 | String button_mode() 43 | { 44 | button_i = button_i % BUTTON_MODE_MAX; 45 | switch (button_i) { 46 | case 0: 47 | return "MODE UNCONFIRMED"; 48 | case 1: 49 | return "MODE CONFIRMED"; 50 | case 2: 51 | return "MODE SEND OFF"; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /img/hardware-gps.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricaun/esp32-ttnmapper-gps/8d37aa60e96707303ae07ca30366d2982e15b286/img/hardware-gps.jpg -------------------------------------------------------------------------------- /img/hardware-ttgo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricaun/esp32-ttnmapper-gps/8d37aa60e96707303ae07ca30366d2982e15b286/img/hardware-ttgo.jpg -------------------------------------------------------------------------------- /img/hardware-ttn-box.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricaun/esp32-ttnmapper-gps/8d37aa60e96707303ae07ca30366d2982e15b286/img/hardware-ttn-box.jpg -------------------------------------------------------------------------------- /img/hardware-ttn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricaun/esp32-ttnmapper-gps/8d37aa60e96707303ae07ca30366d2982e15b286/img/hardware-ttn.jpg -------------------------------------------------------------------------------- /img/ttn-integration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricaun/esp32-ttnmapper-gps/8d37aa60e96707303ae07ca30366d2982e15b286/img/ttn-integration.png -------------------------------------------------------------------------------- /img/ttn-node-abp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricaun/esp32-ttnmapper-gps/8d37aa60e96707303ae07ca30366d2982e15b286/img/ttn-node-abp.png -------------------------------------------------------------------------------- /img/ttn-node.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricaun/esp32-ttnmapper-gps/8d37aa60e96707303ae07ca30366d2982e15b286/img/ttn-node.png -------------------------------------------------------------------------------- /img/ttn-payload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricaun/esp32-ttnmapper-gps/8d37aa60e96707303ae07ca30366d2982e15b286/img/ttn-payload.png -------------------------------------------------------------------------------- /img/ttn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ricaun/esp32-ttnmapper-gps/8d37aa60e96707303ae07ca30366d2982e15b286/img/ttn.jpg --------------------------------------------------------------------------------