GPSNet
gpsnet.ino

Arduino sketch to provide a node on a GPSnet broadcast network to communicate GPS positions of all nodes throughout the network. Uses RF22 radios to preiodically broadcast GPS data to nearby nodes, who in turn broadcast to nodes near them etc.

Includes serial GPS interface, RF22 radio interface and USB-serial interface for configuration and emitting position reports. Saves configuration to EEPROM You can use this with the PC based viewer program GPSNetViewer discussed in the main documentation.

#include <GPSNet.h>
// gpsnet
//
// Arduino sketch to provide a node on a GPSnet broadcast network to communicate
// GPS positions of all nodes throughout the network.
// Uses RF22 radios to preiodically broadcast GPS data
// to nearby nodes, who in turn broadcast to nodes near them etc.
//
// Includes serial GPS interface, RF22 radio interface and USB-serial
// interface for configuration and emitting position reports.
// Saves configuration to EEPROM
//
// Tested with Arduino Mega, EM-406A GPS, Sparkfun RF22 shield
// and arduino-1.0.2
//
// Will compile and run on Uno, but reception of GPS data on SoftwareSerial interferes with
// configuration commands received on USB-serial, so Uno is suitable for passive (non-GPS equipped)
// nodes only
//
// Requires TinyGPS, RF22 and GPSNet (privately from Mike McCauley)
// Copyright 2013 Mike McCauley
// define this if you have an RF22 radio attached
#define HAVE_RF22
// Define this if you have a GPS attached
#define HAVE_GPS
// Define if this will NOT be running on a Mega
// and therefore need to use SoftwareSerial for interface with GPS
// You may want to change the pins that softwareserial() uses too.
// CAUTION: receiving data from GPS on SoftwareSerial can interfere with receiving
// high speed data on the Serial configuration port, so this is not recommended.
//#define SOFTWARE_SERIAL
#include <GPSNet.h>
#include <EEPROM.h>
// RF22 is connected by SPI. See the RF22 documentation
#ifdef HAVE_RF22
#include <RF22.h>
#include <SPI.h>
#endif
// GPS can be connected on hardware serial like Serial1, Serial2 etc on a Mega or similar
// Or it can be connected by SoftwareSerial when there are insuufficiuent hardware serial ports.
#ifdef HAVE_GPS
#include <TinyGPS.h>
TinyGPS gps; // GPS NMEA sentence interpreter
#ifdef SOFTWARE_SERIAL
#include <SoftwareSerial.h>
SoftwareSerial softwareserial(8, 9); // Change the pins if you need
#define GPS_SERIAL softwareserial
#else // SOFTWARE_SERIAL
#define GPS_SERIAL Serial1 // Change the hardware port if you need
#endif // SOFTWARE_SERIAL
#endif // HAVE_GPS
// Print interesting messages, toggle with 'd' command
boolean debugFlag = 0;
// Manages comms between nodes
GPSNet net;
#ifdef HAVE_RF22
// Manages RF22 radio
RF22 rf22;
#endif // HAVE_RF22
// Current node configuration
uint8_t radioChannel = 0;
uint8_t id[GPSNET_REPORT_ID_LEN] = "?????";
uint8_t updateInterval = 0;
// Called by GPSnet when a GPS update is due to be broadcast to all other nodes
// within hearing
// Provides the transmit glue between GPSNet and RF22
void transmitFunction(GPSNet* net, GPSReport* report)
{
if (debugFlag)
{
Serial.println("Transmit:");
printReport(report);
}
#ifdef HAVE_RF22
// We send using RF22 unaddressed, unreliable messages
// The report is sent on the wire exatly as it sits in memory
rf22.send((uint8_t*)report, sizeof(*report));
rf22.waitPacketSent();
#endif // HAVE_RF22
}
// Called to set a new transmitter channel
void setRadioChannel(uint8_t channel)
{
#ifdef HAVE_RF22
rf22.setFrequency(434.0 + 0.1 * radioChannel, 0.05);
#endif // HAVE_RF22
}
// EEPROM format is:
// GPSnet1IIIIIIIc
// Where
// 1 is the version number of the saved data format
// IIIIIIII is the node ID in ASCII
// c is the channeln number as a binary uint8_t
boolean loadConfigFromEeprom()
{
uint8_t eepromIndex = 0;
if ( EEPROM.read(eepromIndex++) == 'G'
&& EEPROM.read(eepromIndex++) == 'P'
&& EEPROM.read(eepromIndex++) == 'S'
&& EEPROM.read(eepromIndex++) == 'n'
&& EEPROM.read(eepromIndex++) == 'e'
&& EEPROM.read(eepromIndex++) == 't'
&& EEPROM.read(eepromIndex++) == '1' // Version
)
{
// Correct signature
// REVISIT: ned chacksum?
uint8_t i;
for (i = 0; i < GPSNET_REPORT_ID_LEN; i++)
id[i] = EEPROM.read(eepromIndex++);
radioChannel = EEPROM.read(eepromIndex++);
return true;
}
else
return false;
}
boolean saveConfigToEeprom()
{
// Write signature
uint8_t eepromIndex = 0;
EEPROM.write(eepromIndex++, 'G');
EEPROM.write(eepromIndex++, 'P');
EEPROM.write(eepromIndex++, 'S');
EEPROM.write(eepromIndex++, 'n');
EEPROM.write(eepromIndex++, 'e');
EEPROM.write(eepromIndex++, 't');
EEPROM.write(eepromIndex++, '1');
uint8_t i;
for (i = 0; i < GPSNET_REPORT_ID_LEN; i++)
EEPROM.write(eepromIndex++, id[i]);
EEPROM.write(eepromIndex++, radioChannel);
return true;
}
void setup()
{
// Monitoring, configuration and debug port
Serial.begin(115200);
// REVISIT: get data from EEPROM
if (!loadConfigFromEeprom())
Serial.println("Could not load valid config from EEPROM");
#ifdef HAVE_GPS
GPS_SERIAL.begin(4800);
#endif // HAVE_GPS
#ifdef HAVE_RF22
if (!rf22.init())
Serial.println("RF22 init failed");
// Defaults after init are 434.0MHz, 0.05MHz AFC pull-in, modulation FSK_Rb2_4Fd36
#endif // HAVE_RF22
setRadioChannel(radioChannel);
net.setTransmitFunction(transmitFunction);
#if 0
// Fake data for testing
GPSReport report;
strcpy((char*)report.id, " test2");
net.newReport(&report);
strcpy((char*)report.id, " test3");
net.newReport(&report);
strcpy((char*)report.id, " test4");
net.newReport(&report);
strcpy((char*)report.id, " test5");
net.newReport(&report);
// while (1);
#endif
Serial.println("\nGPSNet");
}
// Convert date ddmmyy to yymmdd
unsigned long ddmmyy_to_yymmdd(unsigned long ddmmyy)
{
return (ddmmyy / 10000) + (((ddmmyy / 100) % 100) * 100) + ((ddmmyy % 100) * 10000);
}
unsigned long hhmmsscc_to_hhmmss(unsigned long hhmmsscc)
{
return hhmmsscc / 100;
}
void checkForGPSInput()
{
#ifdef HAVE_GPS
while (GPS_SERIAL.available())
{
char c = GPS_SERIAL.read();
// Serial.print(c);
if (gps.encode(c))
{
uint32_t date; // ddmmyy
uint32_t time; // hhmmsscc
uint32_t age; // millisecs
gps.get_datetime(&date, &time, &age);
if (date != TinyGPS::GPS_INVALID_DATE)
{
// Should have valid data from GPS
GPSReport report;
static GPSReport lastReport;
memcpy(report.id, id, GPSNET_REPORT_ID_LEN);
report.date = ddmmyy_to_yymmdd(date);
report.time = hhmmsscc_to_hhmmss(time);
gps.get_position(&report.latitude, &report.longitude, &age);
report.altitude = gps.altitude() / 100; // m
report.speed = gps.speed() / 100; // knot
report.course = gps.course() / 100; // Hundredths of a degree
report.status = 0;
report.hops = 0; // Originated here therefore no hops
// REVISIT: all data valid?
if ( report.latitude != TinyGPS::GPS_INVALID_ANGLE
&& report.altitude != TinyGPS::GPS_INVALID_ALTITUDE)
{
// Completely valid report
if ( !report.isCloseTo(&lastReport)
|| report.status != lastReport.status)
{
if (debugFlag)
{
Serial.println("LocalGPS:");
printReport(&report);
}
net.newReport(&report); // Tell GPSNet we have moved or changed in a signficant way
lastReport = report;
}
}
}
}
}
#endif // HAVE_GPS
}
// Print in an easily parseable format
void printReport(GPSReport* report)
{
Serial.print("Report:");
Serial.print((char*)report->id);
Serial.print(':');
Serial.print(report->date);
Serial.print(':');
Serial.print(report->time);
Serial.print(':');
Serial.print(report->latitude);
Serial.print(':');
Serial.print(report->longitude);
Serial.print(':');
Serial.print(report->altitude);
Serial.print(':');
Serial.print(report->speed);
Serial.print(':');
Serial.print(report->course);
Serial.print(':');
Serial.print(report->flags, HEX);
Serial.print(':');
Serial.print(report->status, HEX);
Serial.print(':');
Serial.println(report->hops);
}
// Emit all reports to the monitor
void printReports()
{
uint8_t numReports = net.numReports();
Serial.print("Reports:");
Serial.println(numReports);
uint8_t i;
for (i = 0; i < numReports; i++)
{
GPSReport* report = net.reportWithIndex(i);
printReport(net.reportWithIndex(i));
}
}
// See if its time to send a database dump to the local monitoring thing
void checkForLocalOutput()
{
static unsigned long lastOutputTime = 0;
if (updateInterval == 0)
return;
if (millis() > lastOutputTime + (updateInterval * 1000))
{
printReports();
lastOutputTime = millis();
}
}
void checkForReceivedMessages()
{
#ifdef HAVE_RF22
if (rf22.available())
{
GPSReport report;
uint8_t len = sizeof(report);
if (rf22.recv((uint8_t*)&report, &len))
{
if ( len == sizeof(report)
&& report.magic == GPSNET_MAGIC_VERSION_1)
{
// REVISIT: if len is short, or bad version??? reject and update bad message stats?
if (debugFlag)
{
Serial.println("Received:");
printReport(&report);
}
net.receivedReport(&report);
}
}
}
#endif // HAVE_RF22
}
void decodeConfigurationCommand(char* buf)
{
if (buf[0] == 'd')
{
// Toggle debug flag
debugFlag = ~debugFlag;
Serial.print("Debug:");
Serial.println(debugFlag ? "on" : "off");
}
else if (buf[0] == 'c')
{
// Set Radio channel
radioChannel = atoi(&buf[1]);
setRadioChannel(radioChannel);
Serial.print("Channel:");
Serial.println(radioChannel);
}
else if (buf[0] == 'i')
{
// Set node ID
strncpy((char*)id, &buf[1], GPSNET_REPORT_ID_LEN);
Serial.print("NodeID:");
Serial.println((char*)id);
}
else if (buf[0] == 's')
{
// Save config to EEPROM
saveConfigToEeprom();
Serial.println("Saved to EEPROM");
}
else if (buf[0] == 'p')
{
// Print config
Serial.print("NodeID:");
Serial.println((char*)id);
Serial.print("Channel:");
Serial.println(radioChannel);
}
else if (buf[0] == 'u')
{
// Update interval
updateInterval = atoi(&buf[1]);
Serial.print("UpdateInterval:");
Serial.println(updateInterval);
}
else if (buf[0] == 'l')
{
// Immediate print of all reports
printReports();
}
}
void checkForConfigurationCommands()
{
static char buf[10] = { '\0' };
static uint8_t buflen = 0;
while (Serial.available())
{
char c = Serial.read();
if (c == '\n')
{
// End of command, try to interpret it
decodeConfigurationCommand(buf);
buflen = 0; // Clear the command
buf[0] = '\0';
}
else if (c == '\r')
{
// Ignore
}
else
{
if (buflen < sizeof(buf) - 1)
{
buf[buflen++] = c;
buf[buflen] = '\0';
}
}
}
}
void loop()
{
checkForGPSInput();
checkForLocalOutput();
checkForReceivedMessages();
checkForConfigurationCommands();
net.poll(); // Do periodic tasks
}