User Tools

Site Tools


project:lora_nodes

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
project:lora_nodes [2018/02/25 12:46]
dp [The Things Network account]
project:lora_nodes [2018/05/11 10:54] (current)
dp
Line 1: Line 1:
 ====== LoRa TTN node setup ====== ====== LoRa TTN node setup ======
 +Osnovni setup za LoRa TTN nodes... za Heltec i TTGO module, al vjerojatno i za druge.
  
 ===== Arduino i podrška za ESP32 ===== ===== Arduino i podrška za ESP32 =====
Line 6: Line 7:
   * https://​www.arduino.cc/​en/​Main/​Software   * https://​www.arduino.cc/​en/​Main/​Software
  
-Instaliraj Arduino podršku za ESP32 mikroknotrolere+Instaliraj Arduino podršku za ESP32 mikrokontrolere
   * https://​github.com/​espressif/​arduino-esp32   * https://​github.com/​espressif/​arduino-esp32
  
Line 14: Line 15:
   * https://​console.thethingsnetwork.org/​   * https://​console.thethingsnetwork.org/​
  
-Pod TTN Applications registriraj ​novi uređaj+Pod TTN Applications registriraj ​novu aplikaciju (grupa ​uređaja)
   * https://​console.thethingsnetwork.org/​applications/​   * https://​console.thethingsnetwork.org/​applications/​
 +
 +Registriraj novi device.
 +
  
 ===== Example code ===== ===== Example code =====
  
-==== Spremi primjer ​i instaliraj librarije koji nedostaju ​====+==== Spremi primjer ====
  
-<file cpp otaa_example.ino>+<file cpp otaa_abp_example.ino>
 #include <​lmic.h>​ #include <​lmic.h>​
 #include <​hal/​hal.h>​ #include <​hal/​hal.h>​
Line 32: Line 36:
 U8X8_SSD1306_128X64_NONAME_SW_I2C u8x8(/* clock=*/ 15, /* data=*/ 4, /* reset=*/ 16); U8X8_SSD1306_128X64_NONAME_SW_I2C u8x8(/* clock=*/ 15, /* data=*/ 4, /* reset=*/ 16);
  
-// This EUI must be in little-endian format, so least-significant-byte +// Schedule TX every this many seconds (might become longer due to duty 
-// first. When copying an EUI from ttnctl output, ​this means to reverse +// cycle limitations)
-// the bytesFor TTN issued EUIs the last bytes should be 0xD5, 0xB3, +const unsigned TX_INTERVAL ​20;
-// 0x70. +
-static ​const u1_t PROGMEM APPEUI[8]={ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +
-void os_getArtEui (u1_t* buf) { +
-  memcpy_P(buf,​ APPEUI, 8); +
-}+
  
-// This should also be in little endian format, see above. +//#define USE_JOINING
-static const u1_t PROGMEM DEVEUI[8]={ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +
-void os_getDevEui (u1_t* buf) { +
-  memcpy_P(buf,​ DEVEUI, 8); +
-}+
  
-// This key should be in big endian format (or, since it is not really a +#ifdef USE_JOINING 
-// number but a block of memory, endianness does not really apply). In +  // OTAA join keys 
-// practice, a key taken from ttnctl can be copied as-is. +  // This EUI must be in little-endian format, so least-significant-byte 
-// The key shown here is the semtech default key. +  // first. When copying an EUI from ttnctl output, this means to reverse 
-static const u1_t PROGMEM APPKEY[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +  // the bytes. For TTN issued EUIs the last bytes should be 0xD5, 0xB3, 
-void os_getDevKey (u1_t* buf) { +  // 0x70. 
-  memcpy_P(buf,​ APPKEY, 16); +  static const u1_t PROGMEM APPEUI[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 
-}+  void os_getArtEui (u1_t* buf) { 
 +    memcpy_P(buf,​ APPEUI, 8); 
 +  } 
 +   
 +  // This should also be in little endian format, see above. 
 +  static const u1_t PROGMEM DEVEUI[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 
 +  void os_getDevEui (u1_t* buf) { 
 +    memcpy_P(buf,​ DEVEUI, 8); 
 +  } 
 +   
 +  ​// This key should be in big endian format (or, since it is not really a 
 +  // number but a block of memory, endianness does not really apply). In 
 +  // practice, a key taken from ttnctl can be copied as-is. 
 +  // The key shown here is the semtech default key. 
 +  static const u1_t PROGMEM APPKEY[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 
 +  void os_getDevKey (u1_t* buf) { 
 +    memcpy_P(buf,​ APPKEY, 16); 
 +  }
  
-static uint8_t mydata[] = "​Hi"​;+#else 
 +  // ABP keys 
 +   
 +  // LoRaWAN NwkSKey, network session key (msb) 
 +  static const PROGMEM u1_t NWKSKEY[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 
 +   
 +  // LoRaWAN AppSKey, application session key (msb) 
 +  static const u1_t PROGMEM APPSKEY[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 
 +   
 +  // LoRaWAN end-device address (DevAddr) 
 +  static const u4_t DEVADDR = 0xffffffff;  
 + 
 +  void os_getArtEui (u1_t* buf) { } 
 +  void os_getDevEui (u1_t* buf) { } 
 +  void os_getDevKey (u1_t* buf) { } 
 + 
 +#endif 
 + 
 + 
 +static uint8_t mydata[] = {13, 37};
 static osjob_t sendjob; static osjob_t sendjob;
  
-// Schedule TX every this many seconds (might become longer due to duty 
-// cycle limitations). 
-const unsigned TX_INTERVAL = 60; 
  
 // Pin mapping // Pin mapping
Line 77: Line 105:
   Serial.print(":​ ");   Serial.print(":​ ");
   switch (ev) {   switch (ev) {
-case EV_SCAN_TIMEOUT:​ +    ​case EV_SCAN_TIMEOUT:​ 
-  Serial.println(F("​EV_SCAN_TIMEOUT"​));​ +      Serial.println(F("​EV_SCAN_TIMEOUT"​));​ 
-  u8x8.drawString(0,​ 7, "​EV_SCAN_TIMEOUT"​);​ +      u8x8.drawString(0,​ 7, "​EV_SCAN_TIMEOUT"​);​ 
-  break; +      break; 
-case EV_BEACON_FOUND:​ +    case EV_BEACON_FOUND:​ 
-  Serial.println(F("​EV_BEACON_FOUND"​));​ +      Serial.println(F("​EV_BEACON_FOUND"​));​ 
-  u8x8.drawString(0,​ 7, "​EV_BEACON_FOUND"​);​ +      u8x8.drawString(0,​ 7, "​EV_BEACON_FOUND"​);​ 
-  break; +      break; 
-case EV_BEACON_MISSED:​ +    case EV_BEACON_MISSED:​ 
-  Serial.println(F("​EV_BEACON_MISSED"​));​ +      Serial.println(F("​EV_BEACON_MISSED"​));​ 
-  u8x8.drawString(0,​ 7, "​EV_BEACON_MISSED"​);​ +      u8x8.drawString(0,​ 7, "​EV_BEACON_MISSED"​);​ 
-  break; +      break; 
-case EV_BEACON_TRACKED:​ +    case EV_BEACON_TRACKED:​ 
-  Serial.println(F("​EV_BEACON_TRACKED"​));​ +      Serial.println(F("​EV_BEACON_TRACKED"​));​ 
-  u8x8.drawString(0,​ 7, "​EV_BEACON_TRACKED"​);​ +      u8x8.drawString(0,​ 7, "​EV_BEACON_TRACKED"​);​ 
-  break; +      break; 
-case EV_JOINING:​ +    case EV_JOINING:​ 
-  Serial.println(F("​EV_JOINING"​));​ +      Serial.println(F("​EV_JOINING"​));​ 
-  u8x8.drawString(0,​ 7, "​EV_JOINING"​);​ +      u8x8.drawString(0,​ 7, "​EV_JOINING"​);​ 
-  break; +      break; 
-case EV_JOINED:​ +    case EV_JOINED:​ 
-  Serial.println(F("​EV_JOINED"​));​ +      Serial.println(F("​EV_JOINED"​));​ 
-  u8x8.drawString(0,​ 7, "​EV_JOINED "); +      u8x8.drawString(0,​ 7, "​EV_JOINED "); 
-            LMIC_setDrTxpow(DR_SF12, 14); //added fixed SF after join for longer range messages  +      LMIC_setDrTxpow(DR_SF7, 14); //added fixed SF after join for longer range messages 
-  // Disable link check validation (automatically enabled +      // Disable link check validation (automatically enabled 
-  // during join, but not supported by TTN at this time). +      // during join, but not supported by TTN at this time). 
-  LMIC_setLinkCheckMode(0);​ +      LMIC_setLinkCheckMode(0);​ 
-  break; +      break; 
-case EV_RFU1: +    case EV_RFU1: 
-  Serial.println(F("​EV_RFU1"​));​ +      Serial.println(F("​EV_RFU1"​));​ 
-  u8x8.drawString(0,​ 7, "​EV_RFUI"​);​ +      u8x8.drawString(0,​ 7, "​EV_RFUI"​);​ 
-  break; +      break; 
-case EV_JOIN_FAILED:​ +    case EV_JOIN_FAILED:​ 
-  Serial.println(F("​EV_JOIN_FAILED"​));​ +      Serial.println(F("​EV_JOIN_FAILED"​));​ 
-  u8x8.drawString(0,​ 7, "​EV_JOIN_FAILED"​);​ +      u8x8.drawString(0,​ 7, "​EV_JOIN_FAILED"​);​ 
-  break; +      break; 
-case EV_REJOIN_FAILED:​ +    case EV_REJOIN_FAILED:​ 
-  Serial.println(F("​EV_REJOIN_FAILED"​));​ +      Serial.println(F("​EV_REJOIN_FAILED"​));​ 
-  u8x8.drawString(0,​ 7, "​EV_REJOIN_FAILED"​);​ +      u8x8.drawString(0,​ 7, "​EV_REJOIN_FAILED"​);​ 
-  //break; +      //break; 
-  break; +      break; 
-case EV_TXCOMPLETE:​ +    case EV_TXCOMPLETE:​ 
-  Serial.println(F("​EV_TXCOMPLETE (includes waiting for RX windows)"​));​ +      Serial.println(F("​EV_TXCOMPLETE (includes waiting for RX windows)"​));​ 
-  u8x8.drawString(0,​ 7, "​EV_TXCOMPLETE"​);​ +      u8x8.drawString(0,​ 7, "​EV_TXCOMPLETE"​);​ 
-  digitalWrite(BUILTIN_LED,​ LOW); +      digitalWrite(BUILTIN_LED,​ LOW); 
-  if (LMIC.txrxFlags & TXRX_ACK) { +      if (LMIC.txrxFlags & TXRX_ACK) { 
-    Serial.println(F("​Received ack"​));​ +        Serial.println(F("​Received ack"​));​ 
-    u8x8.drawString(0,​ 7, "​Received ACK"​);​ +        u8x8.drawString(0,​ 7, "​Received ACK"​);​ 
-  +      
-  if (LMIC.dataLen) { +      if (LMIC.dataLen) { 
-    Serial.println(F("​Received ")); +        Serial.println(F("​Received ")); 
-    u8x8.drawString(0,​ 6, "RX "); +        u8x8.drawString(0,​ 6, "RX "); 
-    Serial.println(LMIC.dataLen);​ +        Serial.println(LMIC.dataLen);​ 
-    u8x8.setCursor(4,​ 6); +        u8x8.setCursor(4,​ 6); 
-    u8x8.printf("​%i bytes",​ LMIC.dataLen);​ +        u8x8.printf("​%i bytes",​ LMIC.dataLen);​ 
-    Serial.println(F("​ bytes of payload"​));​ +        Serial.println(F("​ bytes of payload"​));​ 
-    u8x8.setCursor(0,​ 7); +        u8x8.setCursor(0,​ 7); 
-    u8x8.printf("​RSSI %d SNR %.1d", LMIC.rssi, LMIC.snr);​ +        u8x8.printf("​RSSI %d SNR %.1d", LMIC.rssi, LMIC.snr);​ 
-  +      
-  // Schedule next transmission +      // Schedule next transmission 
-  os_setTimedCallback(&​sendjob,​ os_getTime() + sec2osticks(TX_INTERVAL),​ do_send); +      os_setTimedCallback(&​sendjob,​ os_getTime() + sec2osticks(TX_INTERVAL),​ do_send); 
-  break; +      break; 
-case EV_LOST_TSYNC:​ +    case EV_LOST_TSYNC:​ 
-  Serial.println(F("​EV_LOST_TSYNC"​));​ +      Serial.println(F("​EV_LOST_TSYNC"​));​ 
-  u8x8.drawString(0,​ 7, "​EV_LOST_TSYNC"​);​ +      u8x8.drawString(0,​ 7, "​EV_LOST_TSYNC"​);​ 
-  break; +      break; 
-case EV_RESET: +    case EV_RESET: 
-  Serial.println(F("​EV_RESET"​));​ +      Serial.println(F("​EV_RESET"​));​ 
-  u8x8.drawString(0,​ 7, "​EV_RESET"​);​ +      u8x8.drawString(0,​ 7, "​EV_RESET"​);​ 
-  break; +      break; 
-case EV_RXCOMPLETE:​ +    case EV_RXCOMPLETE:​ 
-  // data received in ping slot +      // data received in ping slot 
-  Serial.println(F("​EV_RXCOMPLETE"​));​ +      Serial.println(F("​EV_RXCOMPLETE"​));​ 
-  u8x8.drawString(0,​ 7, "​EV_RXCOMPLETE"​);​ +      u8x8.drawString(0,​ 7, "​EV_RXCOMPLETE"​);​ 
-  break; +      break; 
-case EV_LINK_DEAD:​ +    case EV_LINK_DEAD:​ 
-  Serial.println(F("​EV_LINK_DEAD"​));​ +      Serial.println(F("​EV_LINK_DEAD"​));​ 
-  u8x8.drawString(0,​ 7, "​EV_LINK_DEAD"​);​ +      u8x8.drawString(0,​ 7, "​EV_LINK_DEAD"​);​ 
-  break; +      break; 
-case EV_LINK_ALIVE:​ +    case EV_LINK_ALIVE:​ 
-  Serial.println(F("​EV_LINK_ALIVE"​));​ +      Serial.println(F("​EV_LINK_ALIVE"​));​ 
-  u8x8.drawString(0,​ 7, "​EV_LINK_ALIVE"​);​ +      u8x8.drawString(0,​ 7, "​EV_LINK_ALIVE"​);​ 
-  break; +      break; 
-default: +    default: 
-  Serial.println(F("​Unknown event"​));​ +      Serial.println(F("​Unknown event"​));​ 
-  u8x8.setCursor(0,​ 7); +      u8x8.setCursor(0,​ 7); 
-  u8x8.printf("​UNKNOWN EVENT %d", ev); +      u8x8.printf("​UNKNOWN EVENT %d", ev); 
-  break;+      break;
   }   }
 } }
Line 171: Line 199:
   // Check if there is not a current TX/RX job running   // Check if there is not a current TX/RX job running
   if (LMIC.opmode & OP_TXRXPEND) {   if (LMIC.opmode & OP_TXRXPEND) {
-Serial.println(F("​OP_TXRXPEND,​ not sending"​));​ +    ​Serial.println(F("​OP_TXRXPEND,​ not sending"​));​ 
-u8x8.drawString(0,​ 7, "​OP_TXRXPEND,​ not sent"​);​+    u8x8.drawString(0,​ 7, "​OP_TXRXPEND,​ not sent"​);​
   } else {   } else {
-// Prepare upstream data transmission at the next possible time. +    ​// Prepare upstream data transmission at the next possible time. 
-LMIC_setTxData2(1,​ mydata, sizeof(mydata) - 1, 0); +    LMIC_setTxData2(1,​ mydata, sizeof(mydata) - 1, 0); 
-Serial.println(F("​Packet queued"​));​ +    Serial.println(F("​Packet queued"​));​ 
-u8x8.drawString(0,​ 7, "​PACKET QUEUED"​);​ +    u8x8.drawString(0,​ 7, "​PACKET QUEUED"​);​ 
-digitalWrite(BUILTIN_LED,​ HIGH);+    digitalWrite(BUILTIN_LED,​ HIGH);
   }   }
   // Next TX is scheduled after TX_COMPLETE event.   // Next TX is scheduled after TX_COMPLETE event.
 } }
 +
  
 void setup() { void setup() {
Line 189: Line 218:
   u8x8.begin();​   u8x8.begin();​
   u8x8.setFont(u8x8_font_chroma48medium8_r);​   u8x8.setFont(u8x8_font_chroma48medium8_r);​
-  u8x8.drawString(0,​ 1, "LoRaWAN LMiC");+  u8x8.drawString(0,​ 1, "radiona.org");
  
   SPI.begin(5,​ 19, 27);   SPI.begin(5,​ 19, 27);
Line 197: Line 226:
   // Reset the MAC state. Session and pending data transfers will be discarded.   // Reset the MAC state. Session and pending data transfers will be discarded.
   LMIC_reset();​   LMIC_reset();​
-  LMIC_setDrTxpow(DR_SF1214); //set join at SF12  + 
-  // Start job (sending automatically starts OTAA too+#ifndef USE_JOINING 
-  ​do_send(&​sendjob);+    #ifdef PROGMEM 
 +      // On AVR, these values are stored in flash and only copied to RAM 
 +      // once. Copy them to a temporary buffer here, LMIC_setSession will 
 +      // copy them into a buffer of its own again. 
 +      uint8_t appskey[sizeof(APPSKEY)];​ 
 +      uint8_t nwkskey[sizeof(NWKSKEY)];​ 
 +      memcpy_P(appskeyAPPSKEY, sizeof(APPSKEY)); 
 +      memcpy_P(nwkskey,​ NWKSKEY, sizeof(NWKSKEY));​ 
 +      LMIC_setSession (0x1, DEVADDR, nwkskey, appskey); 
 +    #else 
 +      // If not running an AVR with PROGMEM, just use the arrays directly 
 +      LMIC_setSession (0x1, DEVADDR, NWKSKEY, APPSKEY); 
 +    #endif 
 +#endif 
 + 
 +  // Set up the channels used by the Things Network, which corresponds 
 +  // to the defaults of most gateways. Without this, only three base 
 +  // channels from the LoRaWAN specification are used, which certainly 
 +  // works, so it is good for debugging, but can overload those 
 +  // frequencies,​ so be sure to configure the full frequency range of 
 +  // your network here (unless your network autoconfigures them). 
 +  // Setting up channels should happen after LMIC_setSession,​ as that 
 +  ​// configures the minimal channel ​set
 +  // NA-US channels 0-71 are configured automatically 
 +  LMIC_setupChannel(0,​ 868100000, DR_RANGE_MAP(DR_SF12,​ DR_SF7), ​ BAND_CENTI); ​     // g-band 
 +  LMIC_setupChannel(1,​ 868300000, DR_RANGE_MAP(DR_SF12,​ DR_SF7B), BAND_CENTI); ​     // g-band 
 +  LMIC_setupChannel(2,​ 868500000, DR_RANGE_MAP(DR_SF12,​ DR_SF7), ​ BAND_CENTI); ​     // g-band 
 +  LMIC_setupChannel(3,​ 867100000, DR_RANGE_MAP(DR_SF12,​ DR_SF7), ​ BAND_CENTI); ​     // g-band 
 +  LMIC_setupChannel(4,​ 867300000, DR_RANGE_MAP(DR_SF12,​ DR_SF7), ​ BAND_CENTI); ​     // g-band 
 +  LMIC_setupChannel(5,​ 867500000, DR_RANGE_MAP(DR_SF12,​ DR_SF7), ​ BAND_CENTI); ​     // g-band 
 +  LMIC_setupChannel(6,​ 867700000, DR_RANGE_MAP(DR_SF12,​ DR_SF7), ​ BAND_CENTI); ​     // g-band 
 +  LMIC_setupChannel(7,​ 867900000, DR_RANGE_MAP(DR_SF12,​ DR_SF7), ​ BAND_CENTI); ​     // g-band 
 +  LMIC_setupChannel(8,​ 868800000, DR_RANGE_MAP(DR_FSK, ​ DR_FSK), ​ BAND_MILLI); ​     // g2-band 
 +  // TTN defines an additional channel ​at 869.525Mhz using SF9 for class B 
 +  // devices'​ ping slots. LMIC does not have an easy way to define set this 
 +  // frequency and support for class B is spotty and untested, so this 
 +  // frequency is not configured here. 
 + 
 +  // Disable link check validation 
 +  //​LMIC_setLinkCheckMode(0)
 + 
 +  ​// TTN uses SF9 for its RX2 window. 
 +  //​LMIC.dn2Dr = DR_SF9; 
 +   
 +   
 +  LMIC_setDrTxpow(DR_SF7, 14); //set join at SF12
  
   pinMode(BUILTIN_LED,​ OUTPUT);   pinMode(BUILTIN_LED,​ OUTPUT);
   digitalWrite(BUILTIN_LED,​ LOW);   digitalWrite(BUILTIN_LED,​ LOW);
 +  ​
 +  // Start job (sending automatically starts OTAA too)
 +  do_send(&​sendjob);​
 } }
  
Line 208: Line 285:
   os_runloop_once();​   os_runloop_once();​
 } }
 +
 </​file>​ </​file>​
  
-==== Koprija ​EUI i ključeve s TTN konzole u kod ==== +==== Za ABP mode ==== 
- +Za korištenje APB u settinzima devicea na TTNu treba označiti APB te će onda biti prikazani i network session key i app session key (kopiraju se u defaultnom ''​msb''​ formatu), te device id (kopira se direktno kao broj). U TTN konzoli da ideš na ABP join i pod settings treba maknuti najdoljnju kvačicu ''​Frame Counter Checks''​. 
 + 
 +==== Za OTTA mode ==== 
 +OTAA mode (sa joinanjem) se enejbla otkomentiravanjem ''"#​define USE_JOINING"''​ 
 + 
 +Kopraj ​EUI i ključeve s TTN konzole u kod:
   * Device EUI ''​lsb''​   * Device EUI ''​lsb''​
   * Application EUI ''​lsb''​   * Application EUI ''​lsb''​
Line 233: Line 316:
 {{ :​project:​lora_modem_packet_formating.png?​nolink&​600 |}} {{ :​project:​lora_modem_packet_formating.png?​nolink&​600 |}}
  
-==== Upload-aj kod ==== +==== Instaliraj librarije koji nedostaju ​==== 
-Izaberi odgovarajućboard u Arduino-u+Sketch / Include Library / Manage Libraries ... ''​lmic'' ​''​U8x8''​
  
 +==== Upload-aj kod ====
 +U Arduino IDE-u pod ''​Tools / Boards''​ izaberi odgovarajuću pločicu (u našem slučaju ''​Heltec_WIFI_Kit_32''​) i pod ''​Port''​ izaberi port na kojem se nalazi device.
 ==== Provjeri primljene poruke u konzoli ==== ==== Provjeri primljene poruke u konzoli ====
   * https://​console.thethingsnetwork.org/​applications/​   * https://​console.thethingsnetwork.org/​applications/​
Line 246: Line 331:
  
 [[https://​play.google.com/​store/​apps/​details?​id=com.jpmeijers.ttnmapper&​hl=en|TTN mapper]] android aplikacija pomoću koje se može upariti telefon s GPS-om i LoRa node, kako bi se moglo testirati i mapirati pokrivenost mreže. [[https://​play.google.com/​store/​apps/​details?​id=com.jpmeijers.ttnmapper&​hl=en|TTN mapper]] android aplikacija pomoću koje se može upariti telefon s GPS-om i LoRa node, kako bi se moglo testirati i mapirati pokrivenost mreže.
 +
 +===== Random links =====
 +  * https://​lcd-web.nl/​ttngenerator/​
  
project/lora_nodes.1519559211.txt.gz · Last modified: 2018/02/25 12:46 by dp