Figured I would post my implementation and code here in case anyone could use it for reference. It is a Heltec HTCC-AB02A with 1/2AA 1425 rechargeable battery and a u-blox NEO-6 TTY serial GPS for testing the range of my gateway. It sleeps for a few seconds, reads GPS and transmits via ABP the current coordinates.
Here is the Arduino sketch that utilizes the TinyGPS+ and Cayenne LPP libraries:
#include <TinyGPS++.h>
#include <softSerial.h>
#include "LoRaWan_APP.h"
#include "loramac/system/timeServer.h"
#include "Arduino.h"
#include <CayenneLPP.h>
/* NOTE: To change Data Rate for non-ADR mode and Spreading Factor edit the following in LoRaWAN_APP.cpp
*
* #ifdef REGION_US915
* int8_t defaultDrForNoAdr = 1;
*
* 0 LoRa: SF10 / 125 kHz 980 0 30 dBm – 2*TXpower
* 1 LoRa: SF9 / 125 kHz 1760 1 28 dBm
* 2 LoRa: SF8 / 125 kHz 3125 2 26 dBm
* 3 LoRa: SF7 / 125 kHz 5470 3 : 9 ….
* 4 LoRa: SF8 / 500 kHz 12500 10 10 dBm
*
*/
/*LoraWan region, select in arduino IDE tools*/
LoRaMacRegion_t loraWanRegion = ACTIVE_REGION;
/*LoraWan Class, Class A and Class C are supported*/
DeviceClass_t loraWanClass = LORAWAN_CLASS;
/*the application data transmission duty cycle. value in [ms].*/
uint32_t appTxDutyCycle = 15000;
/*OTAA or ABP*/
bool overTheAirActivation = LORAWAN_NETMODE;
/*ADR enable*/
bool loraWanAdr = LORAWAN_ADR;
/* set LORAWAN_Net_Reserve ON, the node could save the network info to flash, when node reset not need to join again */
bool keepNet = LORAWAN_NET_RESERVE;
/* Indicates if the node is sending confirmed or unconfirmed messages */
bool isTxConfirmed = LORAWAN_UPLINKMODE;
/* Application port */
uint8_t appPort = 2;
uint8_t confirmedNbTrials = 4;
static const uint32_t GPSBaud = 9600;
// The TinyGPS++ object
TinyGPSPlus gps;
// The serial connection to the GPS device
//softSerial ss(GPIO17 /*TX pin*/, GPIO18 /*RX pin*/);
//Set these OTAA parameters to match your app/node in TTN
uint8_t devEui[] = { 0x00, 0x22, 0x0....
uint8_t appEui[] = { 0x70, 0xB3, 0xD.....
uint8_t appKey[] = { 0xC3, 0xAC, 0xE....
uint8_t nwkSKey[] = { 0x12, 0x3A, 0xA....
uint8_t appSKey[] = { 0xB5, 0x47, 0x25....
uint32_t devAddr = ( uint32_t ) 0x2602.....
uint16_t userChannelsMask[6]={ 0xFF00,0x0000,0x0000,0x0000,0x0000,0x0000 };
static uint8_t counter=0;
///////////////////////////////////////////////////
//Some utilities for going into low power mode
TimerEvent_t sleepTimer;
//Records whether our sleep/low power timer expired
bool sleepTimerExpired;
// This custom version of delay() ensures that the gps object
// is being "fed".
static void smartDelay(unsigned long ms)
{
unsigned long start = millis();
do
{
while (Serial1.available())
gps.encode(Serial1.read());
} while (millis() - start < ms);
}
static void printFloat(float val, bool valid, int len, int prec)
{
if (!valid)
{
while (len-- > 1)
Serial.print('*');
Serial.print(' ');
}
else
{
Serial.print(val, prec);
int vi = abs((int)val);
int flen = prec + (val < 0.0 ? 2 : 1); // . and -
flen += vi >= 1000 ? 4 : vi >= 100 ? 3 : vi >= 10 ? 2 : 1;
for (int i=flen; i<len; ++i)
Serial.print(' ');
}
smartDelay(0);
}
static void printInt(unsigned long val, bool valid, int len)
{
char sz[32] = "*****************";
if (valid)
sprintf(sz, "%ld", val);
sz[len] = 0;
for (int i=strlen(sz); i<len; ++i)
sz[i] = ' ';
if (len > 0)
sz[len-1] = ' ';
Serial.print(sz);
smartDelay(0);
}
static void printDateTime(TinyGPSDate &d, TinyGPSTime &t)
{
if (!d.isValid())
{
Serial.print(F("********** "));
}
else
{
char sz[32];
sprintf(sz, "%02d/%02d/%02d ", d.month(), d.day(), d.year());
Serial.print(sz);
}
if (!t.isValid())
{
Serial.print(F("******** "));
}
else
{
char sz[32];
sprintf(sz, "%02d:%02d:%02d ", t.hour(), t.minute(), t.second());
Serial.print(sz);
}
printInt(d.age(), d.isValid(), 5);
smartDelay(0);
}
static void printStr(const char *str, int len)
{
int slen = strlen(str);
for (int i=0; i<len; ++i)
Serial.print(i<slen ? str[i] : ' ');
smartDelay(0);
}
static void wakeUp()
{
sleepTimerExpired=true;
}
static void lowPowerSleep(uint32_t sleeptime)
{
sleepTimerExpired=false;
TimerInit( &sleepTimer, &wakeUp );
TimerSetValue( &sleepTimer, sleeptime );
TimerStart( &sleepTimer );
//Low power handler also gets interrupted by other timers
//So wait until our timer had expired
while (!sleepTimerExpired) lowPowerHandler();
TimerStop( &sleepTimer );
}
void setup()
{
// Setup Lorawan
boardInitMcu();
Serial.begin(115200);
#if(AT_SUPPORT)
enableAt();
#endif
deviceState = DEVICE_STATE_INIT;
LoRaWAN.ifskipjoin();
// Setup GPS
pinMode(Vext, OUTPUT);
digitalWrite(Vext, LOW);
delay(500);
Serial.begin(115200);
Serial1.begin(GPSBaud);
Serial.println(F("by Mikal Hart"));
Serial.println();
Serial.println(F("Sats HDOP Latitude Longitude Fix Date Time Date Alt Course Speed Card Distance Course Card Chars Sentences Checksum"));
Serial.println(F(" (deg) (deg) Age Age (m) --- from GPS ---- ---- to London ---- RX RX Fail"));
Serial.println(F("----------------------------------------------------------------------------------------------------------------------------------------"));
}
void loop()
{
static const double LONDON_LAT = 51.508131, LONDON_LON = -0.128002;
printInt(gps.satellites.value(), gps.satellites.isValid(), 5);
printFloat(gps.hdop.hdop(), gps.hdop.isValid(), 6, 1);
printFloat(gps.location.lat(), gps.location.isValid(), 11, 6);
printFloat(gps.location.lng(), gps.location.isValid(), 12, 6);
printInt(gps.location.age(), gps.location.isValid(), 5);
printDateTime(gps.date, gps.time);
printFloat(gps.altitude.meters(), gps.altitude.isValid(), 7, 2);
printFloat(gps.course.deg(), gps.course.isValid(), 7, 2);
printFloat(gps.speed.kmph(), gps.speed.isValid(), 6, 2);
printStr(gps.course.isValid() ? TinyGPSPlus::cardinal(gps.course.deg()) : "*** ", 6);
unsigned long distanceKmToLondon =
(unsigned long)TinyGPSPlus::distanceBetween(
gps.location.lat(),
gps.location.lng(),
LONDON_LAT,
LONDON_LON) / 1000;
printInt(distanceKmToLondon, gps.location.isValid(), 9);
double courseToLondon =
TinyGPSPlus::courseTo(
gps.location.lat(),
gps.location.lng(),
LONDON_LAT,
LONDON_LON);
printFloat(courseToLondon, gps.location.isValid(), 7, 2);
const char *cardinalToLondon = TinyGPSPlus::cardinal(courseToLondon);
printStr(gps.location.isValid() ? cardinalToLondon : "*** ", 6);
printInt(gps.charsProcessed(), true, 6);
printInt(gps.sentencesWithFix(), true, 10);
printInt(gps.failedChecksum(), true, 9);
Serial.println();
smartDelay(1000);
//Counter is just some dummy data we send for the example
counter++;
//In this demo we use a timer to go into low power mode to kill some time.
//You might be collecting data or doing something more interesting instead.
lowPowerSleep(15000);
switch( deviceState )
{
case DEVICE_STATE_INIT:
{
#if(AT_SUPPORT)
getDevParam();
#endif
printDevParam();
LoRaWAN.init(loraWanClass,loraWanRegion);
deviceState = DEVICE_STATE_JOIN;
break;
}
case DEVICE_STATE_JOIN:
{
LoRaWAN.join();
break;
}
case DEVICE_STATE_SEND:
{
CayenneLPP lpp(LORAWAN_APP_DATA_MAX_SIZE);
lpp.addGPS(1, gps.location.lat(), gps.location.lng(), gps.altitude.meters());
//lpp.addGPS(1, -12.34f, 45.56f, 9.01f);
lpp.addGyrometer(1, -12.34f, 45.56f, 89.01f);
lpp.getBuffer(),
appDataSize = lpp.getSize();
memcpy(appData,lpp.getBuffer(),appDataSize);
//prepareTxFrame( appPort );
LoRaWAN.send();
deviceState = DEVICE_STATE_CYCLE;
break;
}
case DEVICE_STATE_CYCLE:
{
// Schedule next packet transmission
txDutyCycleTime = appTxDutyCycle + randr( 0, APP_TX_DUTYCYCLE_RND );
LoRaWAN.cycle(txDutyCycleTime);
deviceState = DEVICE_STATE_SLEEP;
break;
}
case DEVICE_STATE_SLEEP:
{
LoRaWAN.sleep();
break;
}
default:
{
deviceState = DEVICE_STATE_INIT;
break;
}
}
if (millis() > 5000 && gps.charsProcessed() < 10)
Serial.println(F("No GPS data received: check wiring"));
}