mirror of
https://bitbucket.org/librepilot/librepilot.git
synced 2024-12-13 20:48:42 +01:00
488 lines
15 KiB
C++
488 lines
15 KiB
C++
/**
|
|
******************************************************************************
|
|
*
|
|
* @file nmeaparser.cpp
|
|
* @author Sami Korhonen Copyright (C) 2010.
|
|
* @addtogroup GCSPlugins GCS Plugins
|
|
* @{
|
|
* @addtogroup GPSGadgetPlugin GPS Gadget Plugin
|
|
* @{
|
|
* @brief A gadget that displays GPS status and enables basic configuration
|
|
*****************************************************************************/
|
|
/*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
|
|
#include "nmeaparser.h"
|
|
#include <iostream>
|
|
#include <math.h>
|
|
#include <QDebug>
|
|
#include <QStringList>
|
|
#include <QtGui/QWidget>
|
|
#include <QtGui/QVBoxLayout>
|
|
#include <QtGui/QPushButton>
|
|
|
|
// Message Codes
|
|
#define NMEA_NODATA 0 // No data. Packet not available, bad, or not decoded
|
|
#define NMEA_GPGGA 1 // Global Positioning System Fix Data
|
|
#define NMEA_GPVTG 2 // Course over ground and ground speed
|
|
#define NMEA_GPGLL 3 // Geographic position - latitude/longitude
|
|
#define NMEA_GPGSV 4 // GPS satellites in view
|
|
#define NMEA_GPGSA 5 // GPS DOP and active satellites
|
|
#define NMEA_GPRMC 6 // Recommended minimum specific GPS data
|
|
#define NMEA_GPZDA 7 // Time and Date
|
|
#define NMEA_UNKNOWN 0xFF // Packet received but not known
|
|
|
|
#define GPS_TIMEOUT_MS 500
|
|
|
|
// Debugging
|
|
|
|
// #define GPSDEBUG
|
|
// #define NMEA_DEBUG_PKT ///< define to enable debug of all NMEA messages
|
|
|
|
#ifdef GPSDEBUG
|
|
#define NMEA_DEBUG_PKT ///< define to enable debug of all NMEA messages
|
|
#define NMEA_DEBUG_GGA ///< define to enable debug of GGA messages
|
|
#define NMEA_DEBUG_VTG ///< define to enable debug of VTG messages
|
|
#define NMEA_DEBUG_RMC ///< define to enable debug of RMC messages
|
|
#define NMEA_DEBUG_GSA ///< define to enable debug of GSA messages
|
|
#define NMEA_DEBUG_ZDA ///< define to enable debug of ZDA messages
|
|
#endif
|
|
|
|
/**
|
|
* Initialize the parser
|
|
*/
|
|
NMEAParser::NMEAParser(QObject *parent) : GPSParser(parent)
|
|
{
|
|
bufferInit(&gpsRxBuffer, (unsigned char *)gpsRxData, 512);
|
|
gpsRxOverflow = 0;
|
|
}
|
|
|
|
NMEAParser::~NMEAParser()
|
|
{}
|
|
|
|
/**
|
|
* Called each time there are data in the input buffer
|
|
*/
|
|
void NMEAParser::processInputStream(char c)
|
|
{
|
|
if (!bufferAddToEnd(&gpsRxBuffer, c)) {
|
|
// no space in buffer
|
|
// count overflow
|
|
gpsRxOverflow++;
|
|
return;
|
|
}
|
|
nmeaProcess(&gpsRxBuffer);
|
|
}
|
|
|
|
|
|
/**
|
|
* Prosesses NMEA sentence checksum
|
|
* \param[in] Buffer for parsed nmea sentence
|
|
* \return 0 checksum not valid
|
|
* \return 1 checksum valid
|
|
*/
|
|
char NMEAParser::nmeaChecksum(char *gps_buffer)
|
|
{
|
|
char checksum = 0;
|
|
char checksum_received = 0;
|
|
|
|
for (int x = 0; x < NMEA_BUFFERSIZE; x++) {
|
|
if (gps_buffer[x] == '*') {
|
|
// Parsing received checksum...
|
|
checksum_received = strtol(&gps_buffer[x + 1], NULL, 16);
|
|
|
|
break;
|
|
} else {
|
|
// XOR the received data...
|
|
checksum ^= gps_buffer[x];
|
|
}
|
|
}
|
|
if (checksum == checksum_received) {
|
|
++numUpdates;
|
|
return 1;
|
|
} else {
|
|
++numErrors;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void NMEAParser::nmeaTerminateAtChecksum(char *gps_buffer)
|
|
{
|
|
for (int x = 0; x < NMEA_BUFFERSIZE; x++) {
|
|
if (gps_buffer[x] == '*') {
|
|
gps_buffer[x] = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prosesses NMEA sentences
|
|
* \param[in] cBuffer for prosessed nmea sentences
|
|
* \return Message code for found packet
|
|
* \return 0xFF NO packet found
|
|
*/
|
|
uint8_t NMEAParser::nmeaProcess(cBuffer *rxBuffer)
|
|
{
|
|
uint8_t foundpacket = NMEA_NODATA;
|
|
uint8_t startFlag = FALSE;
|
|
// u08 data;
|
|
uint16_t i, j;
|
|
|
|
// process the receive buffer
|
|
// go through buffer looking for packets
|
|
while (rxBuffer->datalength) {
|
|
// look for a start of NMEA packet
|
|
if (bufferGetAtIndex(rxBuffer, 0) == '$') {
|
|
// found start
|
|
startFlag = TRUE;
|
|
// when start is found, we leave it intact in the receive buffer
|
|
// in case the full NMEA string is not completely received. The
|
|
// start will be detected in the next nmeaProcess iteration.
|
|
|
|
// done looking for start
|
|
break;
|
|
} else {
|
|
bufferGetFromFront(rxBuffer);
|
|
}
|
|
}
|
|
|
|
// if we detected a start, look for end of packet
|
|
if (startFlag) {
|
|
for (i = 1; i < (rxBuffer->datalength) - 1; i++) {
|
|
// check for end of NMEA packet <CR><LF>
|
|
if ((bufferGetAtIndex(rxBuffer, i) == '\r') && (bufferGetAtIndex(rxBuffer, i + 1) == '\n')) {
|
|
// have a packet end
|
|
// dump initial '$'
|
|
bufferGetFromFront(rxBuffer);
|
|
// copy packet to NmeaPacket
|
|
for (j = 0; j < (i - 1); j++) {
|
|
// although NMEA strings should be 80 characters or less,
|
|
// receive buffer errors can generate erroneous packets.
|
|
// Protect against packet buffer overflow
|
|
if (j < (NMEA_BUFFERSIZE - 1)) {
|
|
NmeaPacket[j] = bufferGetFromFront(rxBuffer);
|
|
} else {
|
|
bufferGetFromFront(rxBuffer);
|
|
}
|
|
}
|
|
// null terminate it
|
|
if (j < (NMEA_BUFFERSIZE - 1)) {
|
|
NmeaPacket[j] = 0;
|
|
} else {
|
|
NmeaPacket[NMEA_BUFFERSIZE - 1] = 0;
|
|
}
|
|
// dump <CR><LF> from rxBuffer
|
|
bufferGetFromFront(rxBuffer);
|
|
bufferGetFromFront(rxBuffer);
|
|
// DEBUG
|
|
#ifdef NMEA_DEBUG_PKT
|
|
qDebug() << NmeaPacket;
|
|
#endif
|
|
emit packet(QString(NmeaPacket));
|
|
// found a packet
|
|
// done with this processing session
|
|
foundpacket = NMEA_UNKNOWN;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (foundpacket) {
|
|
// check message type and process appropriately
|
|
if (!strncmp(NmeaPacket, "GPGGA", 5)) {
|
|
// process packet of this type
|
|
nmeaProcessGPGGA(NmeaPacket);
|
|
// report packet type
|
|
foundpacket = NMEA_GPGGA;
|
|
} else if (!strncmp(NmeaPacket, "GPVTG", 5)) {
|
|
// process packet of this type
|
|
nmeaProcessGPVTG(NmeaPacket);
|
|
// report packet type
|
|
foundpacket = NMEA_GPVTG;
|
|
} else if (!strncmp(NmeaPacket, "GPGSA", 5)) {
|
|
// process packet of this type
|
|
nmeaProcessGPGSA(NmeaPacket);
|
|
// report packet type
|
|
foundpacket = NMEA_GPGSA;
|
|
} else if (!strncmp(NmeaPacket, "GPRMC", 5)) {
|
|
// process packet of this type
|
|
nmeaProcessGPRMC(NmeaPacket);
|
|
// report packet type
|
|
foundpacket = NMEA_GPRMC;
|
|
} else if (!strncmp(NmeaPacket, "GPGSV", 5)) {
|
|
// Process packet of this type
|
|
nmeaProcessGPGSV(NmeaPacket);
|
|
// rerpot packet type
|
|
foundpacket = NMEA_GPGSV;
|
|
} else if (!strncmp(NmeaPacket, "GPZDA", 5)) {
|
|
// Process packet of this type
|
|
nmeaProcessGPZDA(NmeaPacket);
|
|
// rerpot packet type
|
|
foundpacket = NMEA_GPZDA;
|
|
}
|
|
} else if (rxBuffer->datalength >= rxBuffer->size) {
|
|
// if we found no packet, and the buffer is full
|
|
// we're logjammed, flush entire buffer
|
|
bufferFlush(rxBuffer);
|
|
}
|
|
return foundpacket;
|
|
}
|
|
|
|
/**
|
|
* Processes NMEA GSV sentences (satellites in view)
|
|
* \param[in] Buffer for parsed nmea GSV sentence
|
|
*/
|
|
void NMEAParser::nmeaProcessGPGSV(char *packet)
|
|
{
|
|
// start parsing just after "GPGSV,"
|
|
// attempt to reject empty packets right away
|
|
if (packet[6] == ',' && packet[7] == ',') {
|
|
return;
|
|
}
|
|
|
|
if (!nmeaChecksum(packet)) {
|
|
// checksum not valid
|
|
return;
|
|
}
|
|
nmeaTerminateAtChecksum(packet);
|
|
|
|
QString nmeaString(packet);
|
|
QStringList tokenslist = nmeaString.split(",");
|
|
|
|
|
|
// Officially there should be a max of three sentences (12 sats), some gps receivers do more..
|
|
|
|
const int sentence_total = tokenslist.at(1).toInt(); // Number of sentences for full data
|
|
const int sentence_index = tokenslist.at(2).toInt(); // sentence x of y
|
|
|
|
int sats = (tokenslist.size() - 4) / 4;
|
|
for (int sat = 0; sat < sats; sat++) {
|
|
int base = 4 + sat * 4;
|
|
const int id = tokenslist.at(base + 0).toInt(); // Satellite PRN number
|
|
const int elv = tokenslist.at(base + 1).toInt(); // Elevation, degrees
|
|
const int azimuth = tokenslist.at(base + 2).toInt(); // Azimuth, degrees
|
|
const int sig = tokenslist.at(base + 3).toInt(); // SNR - higher is better
|
|
const int index = (sentence_index - 1) * 4 + sat;
|
|
emit satellite(index, id, elv, azimuth, sig);
|
|
}
|
|
|
|
if (sentence_index == sentence_total) {
|
|
// Last sentence
|
|
int total_sats = (sentence_index - 1) * 4 + sats;
|
|
for (int emptySatIndex = total_sats; emptySatIndex < 16; emptySatIndex++) {
|
|
// Wipe the rest.
|
|
emit satellite(emptySatIndex, 0, 0, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prosesses NMEA GPGGA sentences
|
|
* \param[in] Buffer for parsed nmea GPGGA sentence
|
|
*/
|
|
void NMEAParser::nmeaProcessGPGGA(char *packet)
|
|
{
|
|
// start parsing just after "GPGGA,"
|
|
// attempt to reject empty packets right away
|
|
if (packet[6] == ',' && packet[7] == ',') {
|
|
return;
|
|
}
|
|
|
|
if (!nmeaChecksum(packet)) {
|
|
// checksum not valid
|
|
return;
|
|
}
|
|
nmeaTerminateAtChecksum(packet);
|
|
|
|
QString nmeaString(packet);
|
|
QStringList tokenslist = nmeaString.split(",");
|
|
GpsData.GPStime = tokenslist.at(1).toDouble();
|
|
GpsData.Latitude = tokenslist.at(2).toDouble();
|
|
int deg = (int)GpsData.Latitude / 100;
|
|
double min = ((GpsData.Latitude) - (deg * 100)) / 60.0;
|
|
GpsData.Latitude = deg + min;
|
|
// next field: N/S indicator
|
|
// correct latitute for N/S
|
|
if (tokenslist.at(3).contains("S")) {
|
|
GpsData.Latitude = -GpsData.Latitude;
|
|
}
|
|
|
|
GpsData.Longitude = tokenslist.at(4).toDouble();
|
|
deg = (int)GpsData.Longitude / 100;
|
|
min = ((GpsData.Longitude) - (deg * 100)) / 60.0;
|
|
GpsData.Longitude = deg + min;
|
|
// next field: E/W indicator
|
|
// correct latitute for E/W
|
|
if (tokenslist.at(5).contains("W")) {
|
|
GpsData.Longitude = -GpsData.Longitude;
|
|
}
|
|
|
|
GpsData.SV = tokenslist.at(7).toInt();
|
|
|
|
GpsData.Altitude = tokenslist.at(9).toDouble();
|
|
GpsData.GeoidSeparation = tokenslist.at(11).toDouble();
|
|
emit position(GpsData.Latitude, GpsData.Longitude, GpsData.Altitude);
|
|
emit sv(GpsData.SV);
|
|
emit datetime(GpsData.GPSdate, GpsData.GPStime);
|
|
}
|
|
|
|
/**
|
|
* Prosesses NMEA GPRMC sentences
|
|
* \param[in] Buffer for parsed nmea GPRMC sentence
|
|
*/
|
|
void NMEAParser::nmeaProcessGPRMC(char *packet)
|
|
{
|
|
// start parsing just after "GPRMC,"
|
|
// attempt to reject empty packets right away
|
|
if (packet[6] == ',' && packet[7] == ',') {
|
|
return;
|
|
}
|
|
|
|
if (!nmeaChecksum(packet)) {
|
|
// checksum not valid
|
|
return;
|
|
}
|
|
nmeaTerminateAtChecksum(packet);
|
|
|
|
QString nmeaString(packet);
|
|
QStringList tokenslist = nmeaString.split(",");
|
|
GpsData.GPStime = tokenslist.at(1).toDouble();
|
|
GpsData.Groundspeed = tokenslist.at(7).toDouble();
|
|
GpsData.Groundspeed = GpsData.Groundspeed * 0.51444;
|
|
GpsData.Heading = tokenslist.at(8).toDouble();
|
|
GpsData.GPSdate = tokenslist.at(9).toDouble();
|
|
emit datetime(GpsData.GPSdate, GpsData.GPStime);
|
|
emit speedheading(GpsData.Groundspeed, GpsData.Heading);
|
|
}
|
|
|
|
|
|
/**
|
|
* Prosesses NMEA GPVTG sentences
|
|
* \param[in] Buffer for parsed nmea GPVTG sentence
|
|
*/
|
|
void NMEAParser::nmeaProcessGPVTG(char *packet)
|
|
{
|
|
// start parsing just after "GPVTG,"
|
|
// attempt to reject empty packets right away
|
|
if (packet[6] == ',' && packet[7] == ',') {
|
|
return;
|
|
}
|
|
|
|
if (!nmeaChecksum(packet)) {
|
|
// checksum not valid
|
|
return;
|
|
}
|
|
nmeaTerminateAtChecksum(packet);
|
|
|
|
QString nmeaString(packet);
|
|
QStringList tokenslist = nmeaString.split(",");
|
|
|
|
GpsData.Heading = tokenslist.at(1).toDouble();
|
|
GpsData.Groundspeed = tokenslist.at(7).toDouble();
|
|
GpsData.Groundspeed = GpsData.Groundspeed / 3.6;
|
|
emit speedheading(GpsData.Groundspeed, GpsData.Heading);
|
|
}
|
|
|
|
/**
|
|
* Prosesses NMEA GPGSA sentences
|
|
* \param[in] Buffer for parsed nmea GPGSA sentence
|
|
*/
|
|
void NMEAParser::nmeaProcessGPGSA(char *packet)
|
|
{
|
|
// start parsing just after "GPGSA,"
|
|
// attempt to reject empty packets right away
|
|
if (packet[6] == ',' && packet[7] == ',') {
|
|
return;
|
|
}
|
|
|
|
if (!nmeaChecksum(packet)) {
|
|
// checksum not valid
|
|
return;
|
|
}
|
|
nmeaTerminateAtChecksum(packet);
|
|
|
|
QString nmeaString(packet);
|
|
QStringList tokenslist = nmeaString.split(",");
|
|
|
|
// M=Manual, forced to operate in 2D or 3D
|
|
// A=Automatic, 3D/2D
|
|
QString fixmodeValue = tokenslist.at(1);
|
|
if (fixmodeValue == "A") {
|
|
emit fixmode(QString("Auto"));
|
|
} else if (fixmodeValue == "B") {
|
|
emit fixmode(QString("Manual"));
|
|
}
|
|
|
|
// Mode: 1=Fix not available, 2=2D, 3=3D
|
|
int fixtypeValue = tokenslist.at(2).toInt();
|
|
if (fixtypeValue == 1) {
|
|
emit fixtype(QString("NoFix"));
|
|
} else if (fixtypeValue == 2) {
|
|
emit fixtype(QString("Fix2D"));
|
|
} else if (fixtypeValue == 3) {
|
|
emit fixtype(QString("Fix3D"));
|
|
}
|
|
|
|
// 3-14 = IDs of SVs used in position fix (null for unused fields)
|
|
QList<int> svList;
|
|
for (int pos = 0; pos < 12; pos++) {
|
|
QString sv = tokenslist.at(3 + pos);
|
|
if (!sv.isEmpty()) {
|
|
svList.append(sv.toInt());
|
|
}
|
|
}
|
|
emit fixSVs(svList);
|
|
|
|
// 15 = PDOP
|
|
// 16 = HDOP
|
|
// 17 = VDOP
|
|
GpsData.PDOP = tokenslist.at(15).toDouble();
|
|
GpsData.HDOP = tokenslist.at(16).toDouble();
|
|
GpsData.VDOP = tokenslist.at(17).toDouble();
|
|
emit dop(GpsData.HDOP, GpsData.VDOP, GpsData.PDOP);
|
|
}
|
|
|
|
/**
|
|
* Prosesses NMEA GPZDA sentences
|
|
* \param[in] Buffer for parsed nmea GPZDA sentence
|
|
*/
|
|
void NMEAParser::nmeaProcessGPZDA(char *packet)
|
|
{
|
|
// start parsing just after "GPZDA,"
|
|
// attempt to reject empty packets right away
|
|
if (packet[6] == ',' && packet[7] == ',') {
|
|
return;
|
|
}
|
|
|
|
if (!nmeaChecksum(packet)) {
|
|
// checksum not valid
|
|
return;
|
|
}
|
|
nmeaTerminateAtChecksum(packet);
|
|
|
|
QString nmeaString(packet);
|
|
QStringList tokenslist = nmeaString.split(",");
|
|
|
|
GpsData.GPStime = tokenslist.at(1).toDouble();
|
|
int day = tokenslist.at(2).toInt();
|
|
int month = tokenslist.at(3).toInt();
|
|
int year = tokenslist.at(4).toInt();
|
|
GpsData.GPSdate = day * 10000 + month * 100 + (year - 2000);
|
|
emit datetime(GpsData.GPSdate, GpsData.GPStime);
|
|
}
|