Has anyone tried sending GPS coords to TTN?

Hi everyone,

I was wondering if anyone has made a TTN device tracker. I have an arduino uno with a Dragino LoRa hat and a cheap GPS module. The GPS itself is pretty accurate and I was wondering if I could send the output over TTN.

Does anyone have any experience with this?

1 Like

The Uno + Dragino hat maybe a bit big, but in the end it depends on what you want to do with it. I have a gps ttn tracker to measure coverage on the ttnmapper site. Its based on the workshop node from @galagaking1 and features a adafruit gps, solar charger, a solar panel and an on/ioff switch and reset button (which includes a led for status monitoring) to be independent:



2 Likes

Wow thatā€™s really cool!

We are actually doing this to make a proof of concept for a sheep tracking device (i know right)

I ā€œjustā€ need to do the lora integration part. Iā€™m basing my script off Andreas Spiess his code that he modified from Martijn. Itā€™ll take me a while but atleast Iā€™m sure it is possible.

Do you have any information sources that I can study/use?

During a previous project Iā€™ve had to send GPS coordinates over LoRaWan, among other things. Latitude & longitude is data like everything else, and you can make them fit nicely into 2x32bits integers.
What is, exactly, your problem?

2 posts were merged into an existing topic: BIG product placement allowed topic

I just wanted to know if itā€™s possible, thatā€™s all. I do have a problem with the script, for some reason my GPS keeps timing out and it doesnā€™t want to find a location, while it did using the script that you can find on arduino guides.

Iā€™ve been messing around with this code for a while now. It gives no errors and it says that the packets are being queued. Iā€™ve yet to recieve the payload in the TTN backend. But Iā€™ve made some progress. I canā€™t seem to find any errors, maybe some of you can find it?

// MIT License
// https://github.com/gonzalocasas/arduino-uno-dragino-lorawan/blob/master/LICENSE
// Based on examples from https://github.com/matthijskooijman/arduino-lmic
// Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman

//DEBUGGING
#define DEBUG
#define DEBU//G_XL

//Includes
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
#include <SoftwareSerial.h>
#include <TinyGPS.h>

//LoRaWAN credentials
#ifdef CREDENTIALS
static const u1_t NWKSKEY[16] = NWKSKEY1;
static const u1_t APPSKEY[16] = APPSKEY1;
static const u4_t DEVADDR = DEVADDR1;
#else
static const u1_t NWKSKEY[16] = {xxx, 0x3F, 0x0A, xxxx 0xF7, 0xEE,xxxx3, 0xAC, 0xC2, 0x9E, 0x38, 0x29, 0x25, 0x29, 0x88, 0x30 };
static const u1_t APPSKEY[16] = { 0x93, xxxx, 0xC9, xxxx, 0x15, xxx, 0xF8, xxxxB, 0x54, 0x3A, 0x8E, 0x7F, 0x93, 0x61, 0xD9, 0x30 };
static const u4_t DEVADDR = 0xxxxxx89E;
#endif

uint8_t mydata[14]; //Extra bytes as buffer to play with
const unsigned message_size =11;
// These callbacks are only used in over-the-air activation, so they are
// left empty here (we cannot leave them out completely unless
// DISABLE_JOIN is set in config.h, otherwise the linker will complain).
void os_getArtEui (u1_t* buf) { }
void os_getDevEui (u1_t* buf) { }
void os_getDevKey (u1_t* buf) { }

//Sendjob
static osjob_t sendjob;

// Schedule TX every this many seconds (might become longer due to duty
// cycle limitations).
const unsigned TX_INTERVAL = 250;
dr_t LMIC_DR_sequence[] = {DR_SF7, DR_SF10, DR_SF7, DR_SF7, DR_SF7, DR_SF7, DR_SF7, DR_SF9, DR_SF7, DR_SF7, DR_SF7, DR_SF7};
int LMIC_DR_sequence_count = 12;
int LMIC_DR_sequence_index = 0;

// Pin mapping Dragino Shield
const lmic_pinmap lmic_pins = {
.nss = 10,
.rxtx = LMIC_UNUSED_PIN,
.rst = 9,
.dio = {2, 6, 7},
};

// TinyGPS object
TinyGPS gps;

//SoftwareSerial pin mapping
SoftwareSerial ss(3, 2); // RX, TX  Arduino RX, TX --> GPS TXD, RXD

void onEvent (ev_t ev) {
Serial.print(os_getTime());
Serial.print(": ");
//Error catching
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"));
    //Disable link check validarion (Auto enabled during join, but not supported by TTN);
    LMIC_setLinkCheckMode(0);
    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;
    break;
  case EV_TXCOMPLETE:
    Serial.println(F("EV_TXCOMPLETE (Includes waiting for RX windows"));
    if (LMIC.txrxFlags & TXRX_ACK) { // No { ???
      Serial.println(F("Received ack"));
    }
    if (LMIC.dataLen) {
      Serial.println(F("Recieved "));
      Serial.println(LMIC.dataLen);
      Serial.println(F(" bytes of payload"));
    }
    // Schedule next transmission
    os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), 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:
    //Recieved data in ping slot
    Serial.println(F("EV_RX_COMPLETE"));
    break;
  case EV_LINK_DEAD:
    Serial.println(F("EV_LINK_DEAD"));
    break;
  case EV_LINK_ALIVE:
    Serial.println(F("EV_LINK_ALIVE"));
    break;
  default:
    //If error != debug then display default value
    Serial.println(F("What the fuck is going on"));
    break;
    
}
}

// do_send call is scheduled in event handler
void do_send(osjob_t* j){
  // starting version == martijn's version

Serial.println("\ndo_send was called");

// Check if there is not a current TX/RX job running
if (LMIC.opmode & OP_TXRXPEND) {
    Serial.println(F("OP_TXRXPEND, not sending"));
} else {
    // Prepare upstream data transmission at the next possible time.
    // Debug for sending

    #ifdef DEBUG
    Serial.println("  expected   CA DA F? 83 5E 9? 0 ?? ?? " );  
    Serial.println("    dummy   7F FF FF 7F FF FF 0 0 0 " );    
    #endif  
    Serial.print(" mydata[] = [");
    Serial.print( mydata[0], HEX );
    Serial.print(" ");
    Serial.print( mydata[1], HEX );
    Serial.print(" ");
    Serial.print( mydata[2], HEX );
    Serial.print(" ");
    Serial.print( mydata[3], HEX );
    Serial.print(" ");
    Serial.print( mydata[4], HEX );
    Serial.print(" ");
    Serial.print( mydata[5], HEX );
    Serial.print(" ");
    Serial.print( mydata[6], HEX );
    if (message_size>7) Serial.print(" ");
    if (message_size>7) Serial.print( mydata[7], HEX );
    if (message_size>8) Serial.print(" ");
    if (message_size>8) Serial.print( mydata[8], HEX );
    if (message_size>9) Serial.print(" / ");
    if (message_size>9) Serial.print( mydata[9], HEX );
    if (message_size>10) Serial.print(" ");
    if (message_size>10) Serial.print( mydata[10], HEX );
    Serial.print("]    ");
    
    Serial.print("DR [ ");
    Serial.print( LMIC_DR_sequence_index );
    Serial.print(" ] = ");
    Serial.print( LMIC_DR_sequence[LMIC_DR_sequence_index] );
    if ( LMIC_DR_sequence[LMIC_DR_sequence_index]==DR_SF7) Serial.print(" DR_SF7 "); 
    if ( LMIC_DR_sequence[LMIC_DR_sequence_index]==DR_SF8) Serial.print(" DR_SF8 "); 
    if ( LMIC_DR_sequence[LMIC_DR_sequence_index]==DR_SF9) Serial.print(" DR_SF9 "); 
    if ( LMIC_DR_sequence[LMIC_DR_sequence_index]==DR_SF10) Serial.print(" DR_SF10 "); 
    if ( LMIC_DR_sequence[LMIC_DR_sequence_index]==DR_SF11) Serial.print(" DR_SF11 "); 
    if ( LMIC_DR_sequence[LMIC_DR_sequence_index]==DR_SF12) Serial.print(" DR_SF12 "); 

    // Set data rate and transmit power for uplink (note: txpow seems to be ignored by the library
    // for the ttn mapper always use SF7. For other use cases, SF12 can be used, however that will require 60 mins quiet time,
    // according to TTN rules.
    LMIC_setDrTxpow(LMIC_DR_sequence[LMIC_DR_sequence_index],14); // Set data rate

    LMIC_DR_sequence_index = LMIC_DR_sequence_index + 1;
    if (LMIC_DR_sequence_index >= LMIC_DR_sequence_count) {
      LMIC_DR_sequence_index=0;
    }

    // Send that shit
    LMIC_setTxData2(1, mydata, message_size, 0);

    Serial.println(" - packet queued");
    
}
// Next TX is scheduled after TX_COMPLETE event.
}

void lmic_init() {
  os_init();
  //Reset MAC state
  LMIC_reset();

  //EU config
  #if defined(CFG_eu868)
  // Set up the channels for TTN
  // Corresponds to most default EU gateways
  // Good for debugging, doesn't overload frequencies
  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)
  //USA frequency config
  LMIC_selectSubBand(1);
  #endif
  LMIC_setLinkCheckMode(0); //Enable/disable link check validation

  // TTN uses SF9 for its RX2 window
  LMIC.dn2Dr = DR_SF9;
  //Set data rate and transmit power for uplink
  LMIC_setDrTxpow(DR_SF7,14);
  
}

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

//Load the send buffer with dummy location. 0,0 is recognized as dummy by TTN and will therefor not be displayed.
put_gpsvalues_into_sendbuffer( 0,0,0,0);

Serial.println();
Serial.println();
Serial.println("Starting GPS LoRa transmission");
Serial.print("Simple TinyGPS library v. "); Serial.println(TinyGPS::library_version());
Serial.println();

//GPS Serial
ss.begin(9600); //Software serial with GPS module.

#ifdef VCC_ENABLE
  //For sleep mode
  pinMode(VCC_ENABLE, OUTPUT);
  digitalWrite(VCC_ENABLE, HIGH);
  delay(1000);
 #endif

 lmic_init();

 //Start job delayed so system can look for GPS first
 os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(5), do_send);

// Set static session parameters.
LMIC_setSession (0x1, DEVADDR, NWKSKEY, APPSKEY);
}

void loop() {

// process the serial feed from GPS module
Serial.println(" ");
//Serial.println("Read GPS... ");
char c;
unsigned long start = millis();
do 
{   
  while (ss.available())
  {
    char c = ss.read();
    #ifdef DEBUG_XL
    Serial.write(c); // uncomment this line if you want to see the GPS data flowing
    #endif
    
    if (gps.encode(c)) // Did a new valid sentence come in?
        process_gps_values();
  }
} while (millis() - start < 3000); // if too high a value then system wil delay scheduled jobs and the send sequence will take too long
 
os_runloop_once();  // system picks up scheduled jobs
}

void process_gps_values()
{ 
  // retrieve some usefull values from GPS library
  float flat, flon, alt;
  unsigned long age; 
  int hdopNumber;  
  
  gps.f_get_position(&flat, &flon, &age);  // lat -90.0 .. 90.0 as a 4 byte float, lon -180 .. 180 as a 4 byte float, age in 1/1000 seconds as a 4 byte unsigned long
  alt = gps.f_altitude();    // signed float altitude in meters
  hdopNumber = gps.hdop();   // int 100ths of a meter

  // check if possibly invalid
  bool GPS_values_are_valid = true;
  if (flat == TinyGPS::GPS_INVALID_F_ANGLE)    GPS_values_are_valid = false;
  if (flon == TinyGPS::GPS_INVALID_F_ANGLE)    GPS_values_are_valid = false;
  if (hdopNumber == TinyGPS::GPS_INVALID_HDOP) GPS_values_are_valid = false;
  if (age == TinyGPS::GPS_INVALID_AGE)         GPS_values_are_valid = false;
  
  if (alt == TinyGPS::GPS_INVALID_F_ALTITUDE)  GPS_values_are_valid = false;   // if alt, hdop remain giving errors, possibly the GPS character read misses every start few characters of every feed. Solution: make the code lighter so it returns quicker to character read. Or process a bit of buffer while doing other actions, see TinyGPS example.

  // if valid, put into buffer
  if (GPS_values_are_valid) put_gpsvalues_into_sendbuffer( flat, flon, alt, hdopNumber);
// after init, sendbuffer holds 0,0 lovation; after first fix it will retain the last valid location
  
  Serial.print(".");
  //show me something
  #ifdef DEBUG
  // keep some values out as seems to take performance and/or make for code to miss GPS sentences
  unsigned long chars = 0;
  //unsigned short sentences = 0, failed = 0;
  //uint32_t sat; 
  //gps.stats(&chars, &sentences, &failed);
  //sat = gps.satellites();
  
  Serial.println();
  Serial.print("Data: ");
  if (GPS_values_are_valid) Serial.print("(valid) ");
  if (!GPS_values_are_valid) Serial.print("(** INVALID");
  if (flat == TinyGPS::GPS_INVALID_F_ANGLE)    {Serial.print(" lat="); Serial.print(flat);}
  if (flon == TinyGPS::GPS_INVALID_F_ANGLE)    {Serial.print(" lon="); Serial.print(flon);}
  if (hdopNumber == TinyGPS::GPS_INVALID_HDOP) {Serial.print(" hdop="); Serial.print(hdopNumber);}
  if (age == TinyGPS::GPS_INVALID_AGE)         {Serial.print(" age="); Serial.print(age);}
  if (alt == TinyGPS::GPS_INVALID_F_ALTITUDE)  {Serial.print(" alt="); Serial.print(alt);}
  if (!GPS_values_are_valid) Serial.print(" **) ");
  Serial.print("  LAT, LON=");
  Serial.print( flat, 6);   
  Serial.print(", ");
  Serial.print(flon, 6); // 52.632656, 4.738389
  Serial.print(" hdop=");
  Serial.print( hdopNumber);
  Serial.print(" alt=");
  Serial.print( alt );
  Serial.print(" AGE=");
  Serial.print(age);
  //Serial.print(" SAT=");
  //Serial.print( sat);
  
  //  Serial.print(" CHARS=");
  //  Serial.print(chars);
  //Serial.print(" SENT=");
  //Serial.print(sentences);
  //Serial.print(" ERR=");
  //Serial.print(failed);
  Serial.println("");
  #endif
  
  #ifdef DEBUG_XL
  if (chars == 0)
Serial.println("** No characters from GPS: check wiring **");
  else if (age > 5000)
Serial.println("Warning: possible stale GPS data (age over 5 seconds)");
  else
Serial.println("GPS Data is fresh (age less than 5 seconds)");

  Serial.print("For TTN message LatitudeBinary, LongitudeBinary, altitudeGps, accuracy: ");
//    Serial.print( LatitudeBinary, HEX);
//    Serial.print(", ");
//    Serial.print( LongitudeBinary, HEX );
//    Serial.print(", ");
//    Serial.print( altitudeGps, HEX );
//    Serial.print(", ");
//    Serial.println( accuracy, HEX );
  Serial.println("expected   CA DA F. 83 5E 9. 0 .. .. " );     
  Serial.println("    dummy   7F FF FF 7F FF FF 0 0 0 " );    
  Serial.print(  "mydata[] = ");
  Serial.print( mydata[0], HEX );
  Serial.print(" ");
  Serial.print( mydata[1], HEX );
  Serial.print(" ");
  Serial.print( mydata[2], HEX );
  Serial.print(" ");
  Serial.print( mydata[3], HEX );
  Serial.print(" ");
  Serial.print( mydata[4], HEX );
  Serial.print(" ");
  Serial.print( mydata[5], HEX );
  Serial.print(" ");
  Serial.print( mydata[6], HEX );
  if (message_size>6) Serial.print(" ");
  if (message_size>6) Serial.print( mydata[7], HEX );
  if (message_size>7) Serial.print(" ");
  if (message_size>7) Serial.print( mydata[8], HEX );
  if (message_size>8) Serial.print(" / ");
  if (message_size>8) Serial.print( mydata[9], HEX );
  if (message_size>9) Serial.print(" ");
  if (message_size>9) Serial.print( mydata[10], HEX );
  Serial.println("]");
  #endif    
}



void put_gpsvalues_into_sendbuffer(float flat, float flon, float alt, int hdopNumber)
{  
  
  uint32_t LatitudeBinary = ((flat + 90) / 180) * 16777215;
  uint32_t LongitudeBinary = ((flon + 180) / 360) * 16777215;
  uint16_t altitudeGps = alt;         // altitudeGps in meters, alt from tinyGPS is float in meters
  if (alt<0) altitudeGps=0;   // unsigned int wil not allow negative values and warps them to huge number, needs to be zero'ed
  // uint8_t accuracy = hdopNumber*10;   // needs to be /10 instead of *10 as per example JP
  uint8_t accuracy = hdopNumber/10;   // from TinyGPS horizontal dilution of precision in 100ths, TinyGPSplus seems the same in 100ths as per MNEMA string
  
  mydata[0] = ( LatitudeBinary >> 16 ) & 0xFF;
  mydata[1] = ( LatitudeBinary >> 8 ) & 0xFF;
  mydata[2] = LatitudeBinary & 0xFF;

  mydata[3] = ( LongitudeBinary >> 16 ) & 0xFF;
  mydata[4] = ( LongitudeBinary >> 8 ) & 0xFF;
  mydata[5] = LongitudeBinary & 0xFF;

  // altitudeGps in meters into unsigned int
  mydata[6] = ( altitudeGps >> 8 ) & 0xFF;
  mydata[7] = altitudeGps & 0xFF;

  // hdop in tenths of meter
  mydata[8] = accuracy & 0xFF;
  
  mydata[9] = 0;  // fill up next bytes in buffer, just for play. As-if null terminated string.
  mydata[10] = 0xFF;  // dummy filler byte
}
1 Like

UPDATE:

I got it working! I didnā€™t declare the channels so I commented them out, just uses 868 as default right now. I get lat and lng in the backend using SF12.

Would SF7 be a better idea?

1 Like

Of course. Quit hogging our gateways god dammit! :triumph:

ADR would be best, then the network server will set the appropriate SF, but I donā€™t know if LMIC supports this.

1 Like

only static nodes should use ADR

https://www.thethingsnetwork.org/wiki/LoRaWAN/ADR

UPDATE:

Itā€™s working, but it outputs bullshit. See below

Okay so here is my script:

//DEBUGGING
#define DEBUG
#define DEBUG_XL

//Includes
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
#include <SoftwareSerial.h>
#include <TinyGPS.h>

//LoRaWAN credentials
#ifdef CREDENTIALS
static const u1_t NWKSKEY[16] = NWKSKEY1;
static const u1_t APPSKEY[16] = APPSKEY1;
static const u4_t DEVADDR = DEVADDR1;
#else
static const u1_t NWKSKEY[16] = { 0x0C, 0x3F, 0x0A, 0x47, 0xF7, 0xEE, 0x23, 0xAC, 0xC2, 0x9E, 0x38, 0x29, 0x25, 0x29, 0x88, 0x30 };
static const u1_t APPSKEY[16] = { 0x93, 0x35, 0xC9, 0x49, 0x15, 0xC1, 0xF8, 0x2B, 0x54, 0x3A, 0x8E, 0x7F, 0x93, 0x61, 0xD9, 0x30 };
static const u4_t DEVADDR = 0x2601189E;
#endif

uint8_t mydata[14]; //Extra bytes as buffer to play with
const unsigned message_size =11;
// These callbacks are only used in over-the-air activation, so they are
// left empty here (we cannot leave them out completely unless
// DISABLE_JOIN is set in config.h, otherwise the linker will complain).
void os_getArtEui (u1_t* buf) { }
void os_getDevEui (u1_t* buf) { }
void os_getDevKey (u1_t* buf) { }

//Sendjob
static osjob_t sendjob;

// Schedule TX every this many seconds (might become longer due to duty
// cycle limitations).
const unsigned TX_INTERVAL = 250;
dr_t LMIC_DR_sequence[] = {DR_SF12, DR_SF10, DR_SF7, DR_SF7, DR_SF7, DR_SF7, DR_SF7, DR_SF9, DR_SF7, DR_SF7, DR_SF7, DR_SF7};
int LMIC_DR_sequence_count = 12;
int LMIC_DR_sequence_index = 0;

// Pin mapping Dragino Shield
const lmic_pinmap lmic_pins = {
.nss = 10,
.rxtx = LMIC_UNUSED_PIN,
.rst = 9,
.dio = {2, 6, 7},
};

// TinyGPS object
TinyGPS gps;

//SoftwareSerial pin mapping
SoftwareSerial ss(3, 2); // RX, TX  Arduino RX, TX --> GPS TXD, RXD

void onEvent (ev_t ev) {
Serial.print(os_getTime());
Serial.print(": ");
//Error catching
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"));
    //Disable link check validarion (Auto enabled during join, but not supported by TTN);
    LMIC_setLinkCheckMode(0);
    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;
    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"));
        }
    // Schedule next transmission
    os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), 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:
    //Recieved data in ping slot
    Serial.println(F("EV_RX_COMPLETE"));
    break;
  case EV_LINK_DEAD:
    Serial.println(F("EV_LINK_DEAD"));
    break;
  case EV_LINK_ALIVE:
    Serial.println(F("EV_LINK_ALIVE"));
    break;
  default:
    //If error != debug then display default value
    Serial.println(F("What the fuck is going on"));
    break;
    
}
}

// do_send call is scheduled in event handler
void do_send(osjob_t* j){
  // starting version == martijn's version

Serial.println("\ndo_send was called");

// Check if there is not a current TX/RX job running
if (LMIC.opmode & OP_TXRXPEND) {
    Serial.println(F("OP_TXRXPEND, not sending"));
} else {
    // Prepare upstream data transmission at the next possible time.
    // Debug for sending

    #ifdef DEBUG
    Serial.println("  expected   CA DA F? 83 5E 9? 0 ?? ?? " );  
    Serial.println("    dummy   7F FF FF 7F FF FF 0 0 0 " );    
    #endif  
    Serial.print(" mydata[] = [");
    Serial.print( mydata[0], HEX );
    Serial.print(" ");
    Serial.print( mydata[1], HEX );
    Serial.print(" ");
    Serial.print( mydata[2], HEX );
    Serial.print(" ");
    Serial.print( mydata[3], HEX );
    Serial.print(" ");
    Serial.print( mydata[4], HEX );
    Serial.print(" ");
    Serial.print( mydata[5], HEX );
    Serial.print(" ");
    Serial.print( mydata[6], HEX );
    if (message_size>7) Serial.print(" ");
    if (message_size>7) Serial.print( mydata[7], HEX );
    if (message_size>8) Serial.print(" ");
    if (message_size>8) Serial.print( mydata[8], HEX );
    if (message_size>9) Serial.print(" / ");
    if (message_size>9) Serial.print( mydata[9], HEX );
    if (message_size>10) Serial.print(" ");
    if (message_size>10) Serial.print( mydata[10], HEX );
    Serial.print("]    ");
    
    Serial.print("DR [ ");
    Serial.print( LMIC_DR_sequence_index );
    Serial.print(" ] = ");
    Serial.print( LMIC_DR_sequence[LMIC_DR_sequence_index] );
    if ( LMIC_DR_sequence[LMIC_DR_sequence_index]==DR_SF7) Serial.print(" DR_SF7 "); 
    if ( LMIC_DR_sequence[LMIC_DR_sequence_index]==DR_SF8) Serial.print(" DR_SF8 "); 
    if ( LMIC_DR_sequence[LMIC_DR_sequence_index]==DR_SF9) Serial.print(" DR_SF9 "); 
    if ( LMIC_DR_sequence[LMIC_DR_sequence_index]==DR_SF10) Serial.print(" DR_SF10 "); 
    if ( LMIC_DR_sequence[LMIC_DR_sequence_index]==DR_SF11) Serial.print(" DR_SF11 "); 
    if ( LMIC_DR_sequence[LMIC_DR_sequence_index]==DR_SF12) Serial.print(" DR_SF12 ");
    Serial.print("Data =");
    Serial.println(mydata[0]);
    Serial.println(mydata[1]);
    Serial.println(mydata[2]);
    Serial.println(mydata[3]);
    Serial.println(mydata[4]);
    Serial.println(mydata[5]);
    Serial.println(mydata[6]);
    Serial.println(mydata[7]);
    Serial.println(mydata[8]);
    Serial.println(mydata[9]);
    Serial.println(mydata[10]);
    

    // Set data rate and transmit power for uplink (note: txpow seems to be ignored by the library
    // for the ttn mapper always use SF7. For other use cases, SF12 can be used, however that will require 60 mins quiet time,
    // according to TTN rules.
    LMIC_setDrTxpow(LMIC_DR_sequence[LMIC_DR_sequence_index],14); // Set data rate

    LMIC_DR_sequence_index = LMIC_DR_sequence_index + 1;
    if (LMIC_DR_sequence_index >= LMIC_DR_sequence_count) {
      LMIC_DR_sequence_index=0;
    }

    // Send that shit
    LMIC_setTxData2(1, mydata, message_size, 0);

    Serial.println(" - packet queued");
    
}
// Next TX is scheduled after TX_COMPLETE event.
}

void lmic_init() {
  os_init();
  //Reset MAC state
  LMIC_reset();

//  //EU config
//  #if defined(CFG_eu868)
//  // Set up the channels for TTN
//  // Corresponds to most default EU gateways
//  // Good for debugging, doesn't overload frequencies
//  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)
//  //USA frequency config
//  LMIC_selectSubBand(1);
//  #endif
  LMIC_setLinkCheckMode(0); //Enable/disable link check validation

  // TTN uses SF9 for its RX2 window
  LMIC.dn2Dr = DR_SF9;
  //Set data rate and transmit power for uplink
  LMIC_setDrTxpow(DR_SF12,14);
  
}

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

//Load the send buffer with dummy location. 0,0 is recognized as dummy by TTN and will therefor not be displayed.
put_gpsvalues_into_sendbuffer( 0,0,0,0);

Serial.println();
Serial.println();
Serial.println("Starting GPS LoRa transmission");
Serial.print("Simple TinyGPS library v. "); Serial.println(TinyGPS::library_version());
Serial.println();

//GPS Serial
ss.begin(9600); //Software serial with GPS module.

 lmic_init();

 //Start job delayed so system can look for GPS first
 os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(20), do_send);

// Set static session parameters.
LMIC_setSession (0x1, DEVADDR, NWKSKEY, APPSKEY);
}

void loop() {

// process the serial feed from GPS module
Serial.println(" ");
Serial.println("Read GPS... ");
char c;
unsigned long start = millis();
do 
{   
  while (ss.available())
  {
    char c = ss.read();
    #ifdef DEBUG_XL
    Serial.write(c); // uncomment this line if you want to see the GPS data flowing
    #endif
    
    if (gps.encode(c)) // Did a new valid sentence come in?
        process_gps_values();
  }
} while (millis() - start < 3000); // if too high a value then system wil delay scheduled jobs and the send sequence will take too long
 
os_runloop_once();  // system picks up scheduled jobs
}

void process_gps_values()
{ 
  // retrieve some usefull values from GPS library
  float flat, flon, alt;
  unsigned long age; 
  int hdopNumber;
  
  gps.f_get_position(&flat, &flon, &age);  // lat -90.0 .. 90.0 as a 4 byte float, lon -180 .. 180 as a 4 byte float, age in 1/1000 seconds as a 4 byte unsigned long
  alt = gps.f_altitude();    // signed float altitude in meters
  hdopNumber = gps.hdop();   // int 100ths of a meter

  // check if possibly invalid
  bool GPS_values_are_valid = true;
  if (flat == TinyGPS::GPS_INVALID_F_ANGLE)    GPS_values_are_valid = false;
  if (flon == TinyGPS::GPS_INVALID_F_ANGLE)    GPS_values_are_valid = false;
  if (hdopNumber == TinyGPS::GPS_INVALID_HDOP) GPS_values_are_valid = false;
  if (age == TinyGPS::GPS_INVALID_AGE)         GPS_values_are_valid = false;
  
  if (alt == TinyGPS::GPS_INVALID_F_ALTITUDE)  GPS_values_are_valid = false;   // if alt, hdop remain giving errors, possibly the GPS character read misses every start few characters of every feed. Solution: make the code lighter so it returns quicker to character read. Or process a bit of buffer while doing other actions, see TinyGPS example.

  // if valid, put into buffer
  if (GPS_values_are_valid) put_gpsvalues_into_sendbuffer( flat, flon, alt, hdopNumber);
// after init, sendbuffer holds 0,0 lovation; after first fix it will retain the last valid location
  
  Serial.print(".");
  //show me something
  #ifdef DEBUG
  // keep some values out as seems to take performance and/or make for code to miss GPS sentences
  unsigned long chars = 0;
  //unsigned short sentences = 0, failed = 0;
  //uint32_t sat; 
  //gps.stats(&chars, &sentences, &failed);
  //sat = gps.satellites();
  
  Serial.println();
  Serial.print("Data: ");
  if (GPS_values_are_valid) Serial.print("(valid) ");
  if (!GPS_values_are_valid) Serial.print("(** INVALID");
  if (flat == TinyGPS::GPS_INVALID_F_ANGLE)    {Serial.print(" lat="); Serial.print(flat);}
  if (flon == TinyGPS::GPS_INVALID_F_ANGLE)    {Serial.print(" lon="); Serial.print(flon);}
  if (hdopNumber == TinyGPS::GPS_INVALID_HDOP) {Serial.print(" hdop="); Serial.print(hdopNumber);}
  if (age == TinyGPS::GPS_INVALID_AGE)         {Serial.print(" age="); Serial.print(age);}
  if (alt == TinyGPS::GPS_INVALID_F_ALTITUDE)  {Serial.print(" alt="); Serial.print(alt);}
  if (!GPS_values_are_valid) Serial.print(" **) ");
  Serial.print("  LAT, LON=");
  Serial.print( flat, 6);   
  Serial.print(", ");
  Serial.print(flon, 6); // 52.632656, 4.738389
  Serial.print(" hdop=");
  Serial.print( hdopNumber);
  Serial.print(" alt=");
  Serial.print( alt );
  Serial.print(" AGE=");
  Serial.print(age);
  //Serial.print(" SAT=");
  //Serial.print( sat);
  
  //  Serial.print(" CHARS=");
  //  Serial.print(chars);
  //Serial.print(" SENT=");
  //Serial.print(sentences);
  //Serial.print(" ERR=");
  //Serial.print(failed);
  Serial.println("");
  #endif
  
  #ifdef DEBUG_XL
  if (chars == 0)
Serial.println("** No characters from GPS: check wiring **");
  else if (age > 5000)
Serial.println("Warning: possible stale GPS data (age over 5 seconds)");
  else
Serial.println("GPS Data is fresh (age less than 5 seconds)");

  Serial.print("For TTN message LatitudeBinary, LongitudeBinary, altitudeGps, accuracy: ");

Serial.print( LatitudeBinary, HEX);
Serial.print(", ");
Serial.print( LongitudeBinary, HEX );
Serial.print(", ");
Serial.print( altitudeGps, HEX );
Serial.print(", ");
Serial.println( accuracy, HEX );
  Serial.println("expected   CA DA F. 83 5E 9. 0 .. .. " );     
  Serial.println("    dummy   7F FF FF 7F FF FF 0 0 0 " );    
  Serial.print(  "mydata[] = ");
  Serial.print( mydata[0], HEX );
  Serial.print(" ");
  Serial.print( mydata[1], HEX );
  Serial.print(" ");
  Serial.print( mydata[2], HEX );
  Serial.print(" ");
  Serial.print( mydata[3], HEX );
  Serial.print(" ");
  Serial.print( mydata[4], HEX );
  Serial.print(" ");
  Serial.print( mydata[5], HEX );
  Serial.print(" ");
  Serial.print( mydata[6], HEX );
  if (message_size>6) Serial.print(" ");
  if (message_size>6) Serial.print( mydata[7], HEX );
  if (message_size>7) Serial.print(" ");
  if (message_size>7) Serial.print( mydata[8], HEX );
  if (message_size>8) Serial.print(" / ");
  if (message_size>8) Serial.print( mydata[9], HEX );
  if (message_size>9) Serial.print(" ");
  if (message_size>9) Serial.print( mydata[10], HEX );
  Serial.println("]");
  #endif    
}



void put_gpsvalues_into_sendbuffer(float flat, float flon, float alt, int hdopNumber)
{  
  
  uint32_t LatitudeBinary = ((flat + 90) / 180) * 16777215;
  uint32_t LongitudeBinary = ((flon + 180) / 360) * 16777215;
  uint16_t altitudeGps = alt;         // altitudeGps in meters, alt from tinyGPS is float in meters
  if (alt<0) altitudeGps=0;   // unsigned int wil not allow negative values and warps them to huge number, needs to be zero'ed
  // uint8_t accuracy = hdopNumber*10;   // needs to be /10 instead of *10 as per example JP
  uint8_t accuracy = hdopNumber/10;   // from TinyGPS horizontal dilution of precision in 100ths, TinyGPSplus seems the same in 100ths as per MNEMA string
  
  mydata[0] = ( LatitudeBinary >> 16 ) & 0xFF;
  mydata[1] = ( LatitudeBinary >> 8 ) & 0xFF;
  mydata[2] = LatitudeBinary & 0xFF;

  mydata[3] = ( LongitudeBinary >> 16 ) & 0xFF;
  mydata[4] = ( LongitudeBinary >> 8 ) & 0xFF;
  mydata[5] = LongitudeBinary & 0xFF;

  // altitudeGps in meters into unsigned int
  mydata[6] = ( altitudeGps >> 8 ) & 0xFF;
  mydata[7] = altitudeGps & 0xFF;

  // hdop in tenths of meter
  mydata[8] = accuracy & 0xFF;
  
  mydata[9] = 0;  // fill up next bytes in buffer, just for play. As-if null terminated string.
  mydata[10] = 0xFF;  // dummy filler byte
}

Whem Iā€™m outside the lat/lng output in the arduino serial monitor is correct. It also puts the output in a HEX format for ttn to support. But once I convert it back into readable text it outputs someting like this:
CAFC49849B8800000F00FF
wich translates into:
ĆŠĆ¼IĀ„Ā›ĀˆĆæ

I donā€™t get it, the console looks all good but the output makes no sense at all.

1 Like

HI Nidaros. Howā€™s this project progressing? Any updates on Solar powered GPS trackers?

1 Like

Hi there, I have a prototype working but not on solar sadly. Iā€™ts currently on usb

oh and i also got the decoding part working, if i can find it ill post below. Give me a sec

Here is what I used for my decorder. This was like 4 months ago so please donā€™t ask me questions :joy:

function Decoder (bytes) {
  var _lat = ((bytes[0] << 16) + (bytes[1] << 8) + bytes[2]) / 16777215.0 * 180.0 - 90;
  var _lng = ((bytes[3] << 16) + (bytes[4] << 8) + bytes[5]) / 16777215.0 * 360.0 - 180;
  var _alt = (bytes[6] << 8) + bytes[7];
  var _acc = bytes[8] / 10.0;
  var _VCC = bytes[9] / 50;
  var _tempCPU = bytes[10] -100;
  var _time_to_fix_bin = bytes[11];
  var _time_to_fix;
  if (_time_to_fix_bin>=218) { _time_to_fix = 60*60+(_time_to_fix_bin-218)*600 }
  else if (_time_to_fix_bin>=168) { _time_to_fix = 10*60+(_time_to_fix_bin-168)*60 }
  else if (_time_to_fix_bin>=60) {  _time_to_fix = 60+(_time_to_fix_bin-60)*5 }
  else  {_time_to_fix = _time_to_fix_bin }
      
          // 0..60 sec  at 1 sec interval <==> values 0 .. 60 
          // 1..10 min at 5 sec interval  <==> values 60 ..  168
          // 10..60 min at 1 min interval <==> values 168 .. 218
          // 1..7 hour at 10 min interval <==> values 218 ..254; 255 means "more than 7 hours"
          

      return {
        gps_lat: _lat,
        gps_lng: _lng,
        gps_alt: _alt,
        gps_prec: _acc,
        arduino_VCC: _VCC,
        arduino_temp: _tempCPU,
        time_to_fix: _time_to_fix,
      };
    }
1 Like

Thanks for the update :slight_smile:

1 Like

@Nidaros @Epyon @Gig @casper

For mapping or range test. I am building Arudino mega + lora shield + adafruit ultimate gps v3 ā€¦ Please correct me if I am wrong. Many thanks
GPS >> arduino Shield pin mappingā€¦
vin >>vin
Gnd >> gnd
Tx >> rx (0)
Rx >> tx (1)

Config with ABP :
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
#include <TinyGPS.h>

TinyGPS gps;
ā€¦

GPS got fixed in 30 second to 1 minā€¦ however, only 0000000000 delivered as a payload.

Compiling the code has no error.arduino-gps

isnā€™t the GPS 3v3 logic level ? ( and the mega uart 5v)

Thanks for prompt reply. I powered mega by USB 5v. and GPS breakout board by mega VIN. Like this UNO examplw wiring @https://learn.adafruit.com/adafruit-ultimate-gps/arduino-wiring. Please tell me how should I wire since mega use hw serial as discussed in previous posts.

Iā€™m doing it with a Seeeduino LoRaWAN w/ GPS

IMG_20171224_171046320

FB_IMG_1514918358117