/*
* nmea.c
*
* Created on: 6 Sep 2020
* Author: mike
*/
#include <math.h>
#include <stdint.h>
#include <stdlib.h>
#include <memory.h>
#include "libNMEA/nmea.h"
#include "libSerial/serial.h"
char linebuff[80];
unsigned linePos = 0;
typedef enum
{
SEARCH, READING
} NmeaState_t;
NmeaState_t lineState = SEARCH;
static bool
decodePacket (char *linebuff, int linePos, Location *loc);
bool
updateLocation (Location *loc, usart_ctl *uc)
{
while (1)
{
if (!SerialCharsReceived (uc))
return false; // nothing to read, return immediately
char c = GetCharSerial (uc);
switch (lineState)
{
case SEARCH:
if (c == '$')
lineState = READING;
linePos = 0;
break;
case READING:
if (c == '\r')
{
// handle the read code
bool success = decodePacket (linebuff, linePos, loc);
lineState = SEARCH;
linePos = 0;
return success;
}
if (linePos < sizeof(linebuff))
linebuff[linePos++] = c;
else
{
lineState = SEARCH;
// search for the $ in any unread string
int i;
for (i = 0; i < linePos; i++)
if (linebuff[i] == '$')
{
int n = i + 1;
memcpy (linebuff
, linebuff
+ n
, linePos
- i
);
linePos = linePos - i;
linebuff[linePos++] = c;
lineState = READING;
}
if (lineState == SEARCH)
linePos = 0;
}
break;
}
}
return false;
}
static int8_t
decodeDec (char c)
{
if (c > '9' && c < '0')
return -1;
return c - '0';
}
static uint8_t
decodeHex (char c)
{
int8_t v = decodeDec (c);
if (v >= 0)
return v;
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
return 0;
}
// lat/long decoder
static float
decodeLL (char *ptr, int msDigitWeight)
{
float digitWeight = msDigitWeight;
float result = 0;
int i = 0;
while (1)
{
char c = ptr[i++];
if (c == '.')
continue;
int8_t v = decodeDec (c);
if (v > 0)
{
result += digitWeight * v;
if (fabs (digitWeight
- 1) < 0.01)
digitWeight = 1 / 6.0;
else
digitWeight = digitWeight / 10;
continue;
}
break;
}
return result;
}
static float
decodeFP (char *ptr)
{
return strtof (ptr, NULL);
}
static int
decodeDecimal (char *ptr)
{
int i;
int res = 0;
int const width = 2;
for (i = 0; i < width; i++)
{
int8_t v = decodeDec (*ptr++);
if (v < 0)
return 0;
res *= 10;
res += v;
}
return res;
}
bool
decodePacket (char *linebuff, int linePos, Location *loc)
{
uint8_t checksum = 0;
for (int i = 0; i < linePos - 3; i++)
checksum ^= linebuff[i];
uint8_t givenSum = (decodeHex (linebuff[linePos - 2]) << 4)
+ decodeHex (linebuff[linePos - 1]);
if (givenSum != checksum)
return false;
char *fieldPos[20];
int fieldCnt = 0;
// split fields
for (int i = 5; i < linePos - 3; i++)
{
if (linebuff[i] == ',')
{
fieldPos[fieldCnt++] = linebuff + i + 1;
}
}
// decode RMC
if (linebuff[2] == 'R' && linebuff[3] == 'M' && linebuff[4] == 'C')
{
// decode the fields
loc->valid = *fieldPos[1];
if (loc->valid == 'A')
{
loc->lat = decodeLL (fieldPos[2], 10);
loc->ns = *fieldPos[3];
loc->lon = decodeLL (fieldPos[4], 100);
loc->ew = *fieldPos[5];
loc->speed = decodeFP (fieldPos[6]);
loc->heading = decodeFP (fieldPos[7]);
memcpy (loc
->date
, fieldPos
[1], 6);
loc
->tv.
tm_sec = decodeDecimal
(&loc
->time[4]);
loc
->tv.
tm_min = decodeDecimal
(&loc
->time[2]);
loc
->tv.
tm_hour = decodeDecimal
(&loc
->time[0]);
loc->tv.tm_mday = decodeDecimal (&loc->date[0]);
loc->tv.tm_mon = decodeDecimal (&loc->date[2]) - 1;
loc->tv.tm_year = decodeDecimal (&loc->date[4]) + 100; //
loc->tv.tm_isdst = 0;
loc->good = true;
}
}
return true;
}