GPS tracker

In order to verify the accuracy of the developed LoRaLOC method, a LoRaWAN transceiver with a GPS module was developed. This contains:

  • Adafruit single-chip board with Atmega 32u4 processor and RFM95 LoRaWAN transceiver
  • GPS module uBlox NEO 6M (using serial1 at Adafruit to connect the uBlox)
  • rechargeable battery Li-Pol 2700 mAh
  • output to external antenna (LoRa 868 MHz)

Sketch sample (in payload the LAT, LON, ALT and HDOP are sent) :

#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>

#include <TinyGPS++.h>

TinyGPSPlus gps;

static const PROGMEM u1_t NWKSKEY[16] = {    };
static const u1_t PROGMEM APPSKEY[16] = { } ;
static const u4_t DEVADDR = 0x;

void os_getArtEui (u1_t* buf) { }
void os_getDevEui (u1_t* buf) { }
void os_getDevKey (u1_t* buf) { }

static osjob_t sendjob, blinkjob;

const unsigned TX_INTERVAL = 30;
static unsigned blink_interval = 1;

static uint8_t mydata[] = "!";

uint32_t LatitudeBinary, LongitudeBinary;
uint16_t altitudeGps;
uint8_t hdopGps;
uint8_t txBuffer[10];


const lmic_pinmap lmic_pins = { 
   .nss = 8, 
   .rxtx = LMIC_UNUSED_PIN, 
   .rst = 4, 
   .dio = {7, 6, LMIC_UNUSED_PIN}, 
}; 


void get_coords () {
  bool newData = false;
  unsigned long age;
  
  for (unsigned long start = millis(); millis() - start < 1000;) {
    while (Serial1.available()) {
      char c = Serial1.read();
      Serial.write(c); // uncomment this line if you want to see the GPS data flowing
      if (gps.encode(c)) { // Did a new valid sentence come in?
        newData = true;
      }
    }
  }

  for(int i=0;i<9;i++)
    txBuffer[i]=0;
   
  
  if ( newData && (gps.location.age()<1000)) { build_packet(); } 
else Serial.print("neni GPS!!! "); } 
void onEvent (ev_t ev) { Serial.print(os_getTime()); 
Serial.print(": "); 
switch(ev)
 { case EV_SCAN_TIMEOUT: Serial.println(F("EV_SCAN_TIMEOUT"));
 break; 
case EV_BEACON_FOUND: Serial.println(F("EV_BEACON_FOUND"));
 break; 
case EV_BEACON_MISSED: Serial.println(F("EV_BEACON_MISSED"));
 break;
case EV_BEACON_TRACKED: Serial.println(F("EV_BEACON_TRACKED"));
 break; 
case EV_JOINING: Serial.println(F("EV_JOINING"));
 break;
 case EV_JOINED: Serial.println(F("EV_JOINED")); 
break; 
case EV_RFU1: Serial.println(F("EV_RFU1")); 
break; 
case EV_JOIN_FAILED: Serial.println(F("EV_JOIN_FAILED")); 
break; 
case EV_REJOIN_FAILED: Serial.println(F("EV_REJOIN_FAILED"));
 break; 
case EV_TXCOMPLETE: Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
 if (LMIC.txrxFlags & TXRX_ACK) Serial.println(F("Received ack"));
if (LMIC.dataLen) { Serial.println(F("Received ")); 
Serial.println(LMIC.dataLen); 
Serial.println(F(" bytes of payload")); }
 os_setCallback(&sendjob, do_send); 
break; 
case EV_LOST_TSYNC: Serial.println(F("EV_LOST_TSYNC"));
 break; 
case EV_RESET: Serial.println(F("EV_RESET"));
 break; 
case EV_RXCOMPLETE: // data received in ping slot 
Serial.println(F("EV_RXCOMPLETE")); break; case EV_LINK_DEAD: Serial.println(F("EV_LINK_DEAD"));
 break; case EV_LINK_ALIVE: Serial.println(F("EV_LINK_ALIVE"));
 break; default: Serial.println(F("Unknown event")); 
break; } } 

void build_packet() 
{ 
uint32_t latitude = (gps.location.lat()) * 10000;
 uint32_t longitude = (gps.location.lng())* 10000;
 uint16_t altitude = (gps.altitude.meters()) * 10;
 uint8_t hdop = (gps.hdop.value()) * 10;

 txBuffer[0] = latitude >> 16;
txBuffer[1] = latitude >> 8;
txBuffer[2] = latitude;

txBuffer[3] = longitude >> 16;
txBuffer[4] = longitude >> 8;
txBuffer[5] = longitude;

txBuffer[6] = altitude >> 8;
txBuffer[7] = altitude;

txBuffer[8] = hdop;

}

void do_send(osjob_t* j){
    // Check if there is not a current TX/RX job running
    if (LMIC.opmode & OP_TXRXPEND) {
        Serial.println(F("OP_TXRXPEND, not sending"));
    } else {
     get_coords();
   
        LMIC_setTxData2(1,txBuffer, sizeof(txBuffer), 0);  
        Serial.println(F("Paket zarazen k odeslani."));
    }
   
}

void setup() {
    Serial.begin(9600);
    Serial.println(F("Starting"));
    Serial1.begin(9600);

    os_init();
    
    LMIC_reset();

   
    #ifdef PROGMEM
      uint8_t appskey[sizeof(APPSKEY)];
    uint8_t nwkskey[sizeof(NWKSKEY)];
    memcpy_P(appskey, APPSKEY, sizeof(APPSKEY));
    memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY));
    LMIC_setSession (0x1, DEVADDR, nwkskey, appskey);
    #else
  
    LMIC_setSession (0x1, DEVADDR, NWKSKEY, APPSKEY);
    #endif

    #if defined(CFG_eu868)
    
    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
    
    #elif defined(CFG_us915)
    
    LMIC_selectSubBand(1);
    #endif

    // Disable link check validation
    LMIC_setLinkCheckMode(0);

    // TTN uses SF9 for its RX2 window.
    LMIC.dn2Dr = DR_SF9;

    // Set data rate and transmit power for uplink (note: txpow seems to be ignored by the library)
    LMIC_setDrTxpow(DR_SF7,14);

    // Start job
    do_send(&sendjob);
}

void loop() {
    os_runloop_once();
}

JSON output from The Things Stack using MQTT subscribe (mosquitto_sub) command:

{
   "end_device_ids":{
      "device_id":"eui-",
      "application_ids":{
         "application_id":""
      },
      "dev_eui":"",
      "dev_addr":""
   },
   "correlation_ids":[
      "as:up:",
      "gs:conn:",
      "gs:up:host:",
      "gs:uplink:",
      "ns:uplink:",
      "rpc:/ttn.lorawan.v3.GsNs/HandleUplink:",
      "rpc:/ttn.lorawan.v3.NsAs/HandleUplink:"
   ],
   "received_at":"2021-08-23T09:58:19.054652020Z",
   "uplink_message":{
      "f_port":1,
      "f_cnt":838,
      "frm_payload":"AAAAAAAAAAAAAA==",
      "decoded_payload":{
         "alt":0,
         "lat":0,
         "lon":0
      },
      "rx_metadata":[
         {
            "gateway_ids":{
               "gateway_id":"tts-vsb-gw001",
               "eui":"B827EBFFFE998292"
            },
            "time":"2021-08-23T09:58:16.789019Z",
            "timestamp":403319100,
            "rssi":-103,
            "channel_rssi":-103,
            "snr":10.8,
            "location":{
               "latitude":49.8314036,
               "longitude":18.1588219,
               "altitude":254,
               "source":"SOURCE_REGISTRY"
            },
            "uplink_token":"ChsKGQoNdHRzLXZzYi1ndzAwMRIIuCfr//==",
            "channel_index":7
         },
         {
            "gateway_ids":{
               "gateway_id":"tts-vsb-gw004",
               "eui":"B827EBFFFE6ED1BB"
            },
            "time":"2021-08-23T09:58:16.289050Z",
            "timestamp":216279740,
            "rssi":-119,
            "channel_rssi":-119,
            "snr":-8.2,
            "location":{
               "latitude":49.77526867,
               "longitude":18.2502655,
               "altitude":344,
               "source":"SOURCE_REGISTRY"
            },
            "uplink_token":"ChsKGQoNdHRzLXZzYi1ndzAwNBIIuCfr//=",
            "channel_index":7
         }
      ],
      "settings":{
         "data_rate":{
            "lora":{
               "bandwidth":125000,
               "spreading_factor":7
            }
         },
         "data_rate_index":5,
         "coding_rate":"4/5",
         "frequency":"867900000",
         "timestamp":403319100,
         "time":"2021-08-23T09:58:16.789019Z"
      },
      "received_at":"2021-08-23T09:58:18.815967602Z",
      "consumed_airtime":"0.061696s",
      "locations":{
         "frm-payload":{
            "latitude":49.8313,
            "longitude":18.1602,
            "altitude":268,
            "source":"SOURCE_GPS"
         }
      },
      "network_ids":{
         "net_id":"000013",
         "tenant_id":"ttn",
         "cluster_id":"ttn-eu1"
      }
   }
}

Using simple BASH code we can read i:

#!/bin/bash

dir=$(pwd)
OUT=""
cd $dir

USER=""
PASSW=""
TOPIC=""
URL="eu1.cloud.thethings.network"
#TLS="file.pem"

mosquitto_sub -h $URL -t $TOPIC -u $USER -P $PASSW | while read -r  LINE ;do
echo "$LINE" > "$dir/zaznam"

arrayeui=()
arraycas=()
arraylon=()
arraylat=()

jq '.uplink_message.decoded_payload.lat' zaznam > lat
lat=$(awk '{gsub(/\"/,"");print $0}' lat)
jq '.uplink_message.decoded_payload.lon' zaznam > lon
lon=$(awk '{gsub(/\"/,"");print $0}' lon)


for (( i=0; i < $(jq '.uplink_message.rx_metadata | length' zaznam); i++ )) 
do
    jq --arg i "$i" '.uplink_message.rx_metadata['$i'].gateway_ids.eui' zaznam > eui
    eui=$(awk '{gsub(/\"/,"");print $0}' eui)
    jq --arg i "$i" '.uplink_message.rx_metadata['$i'].time' zaznam > cas
    cas=$(awk '{gsub(/\"/,"");print $0}' cas)
    arrayeui[i]=$eui
    arraycas[i]=$cas
    out+=${arrayeui[$i]}";"${arraycas[$i]}"&"
    unset eui
    unset cas
done

echo $lat";"$lon";"$out

unset out
unset lat
unset lon

The output is as follows:

lat;lon;eui1;cas1&eui2;cas2&eui3;cas3& ...

where:

  • lat, lon is the current GPS position of the tracker that is sent directly in the placket payload
  • eui, cas is the appropriate time when the packet “arrives” at the given gateway with the given eui