-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathParse_NMEA.cpp
305 lines (259 loc) · 9.56 KB
/
Parse_NMEA.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
/*------------------------------------------------------------------------------
Parse_NMEA.cpp
NMEA sentence parsing support routines
The parser routines within a parser module are typically placed in
reverse order within the module. This lets the routine declaration
proceed the routine use and eliminates the need for forward declaration.
Removing the forward declaration helps reduce the exposure of the
routines to the application layer. As such only the preamble routine
should need to be listed in SparkFun_Extensible_Message_Parser.h.
License: MIT. Please see LICENSE.md for more details
------------------------------------------------------------------------------*/
#include <stdio.h>
#include "SparkFun_Extensible_Message_Parser.h"
//----------------------------------------
// Constants
//----------------------------------------
// Save room for the asterisk, checksum, carriage return, linefeed and zero termination
#define NMEA_BUFFER_OVERHEAD (1 + 2 + 2 + 1)
//----------------------------------------
// NMEA parse routines
//----------------------------------------
//
// NMEA Sentence
//
// +----------+---------+--------+---------+----------+----------+
// | Preamble | Name | Comma | Data | Asterisk | Checksum |
// | 8 bits | n bytes | 8 bits | n bytes | 8 bits | 2 bytes |
// | $ | | , | | | |
// +----------+---------+--------+---------+----------+----------+
// | |
// |<-------- Checksum -------->|
//
// Validate the checksum
void sempNmeaValidateChecksum(SEMP_PARSE_STATE *parse)
{
int checksum;
SEMP_SCRATCH_PAD *scratchPad = (SEMP_SCRATCH_PAD *)parse->scratchPad;
// Convert the checksum characters into binary
checksum = sempAsciiToNibble(parse->buffer[parse->length - 2]) << 4;
checksum |= sempAsciiToNibble(parse->buffer[parse->length - 1]);
// If this is a command response from a Unicore module, include the '$' in the checksum
if(strcmp((char *)scratchPad->nmea.sentenceName, "command") == 0)
parse->crc ^= '$';
// Validate the checksum
if ((checksum == parse->crc) || (parse->badCrc && (!parse->badCrc(parse))))
{
// Always add the carriage return and line feed
parse->buffer[parse->length++] = '\r';
parse->buffer[parse->length++] = '\n';
// Zero terminate the NMEA sentence, don't count this in the length
parse->buffer[parse->length] = 0;
// Process this NMEA sentence
parse->eomCallback(parse, parse->type); // Pass parser array index
}
else
{
// Display the checksum error
sempPrintf(parse->printDebug,
"SEMP: %s NMEA %s, 0x%04x (%d) bytes, bad checksum, "
"received 0x%c%c, computed: 0x%02x",
parse->parserName,
scratchPad->nmea.sentenceName,
parse->length, parse->length,
parse->buffer[parse->length - 2],
parse->buffer[parse->length - 1],
parse->crc);
// Dump the buffer
sempPrintf(parse->printDebug, "SEMP Buffer: %s", parse->buffer);
}
}
// Read the linefeed
bool sempNmeaLineFeed(SEMP_PARSE_STATE *parse, uint8_t data)
{
int checksum;
// Don't add the current character to the length
parse->length -= 1;
// Process the LF
if (data == '\n')
{
// Pass the sentence to the upper layer
sempNmeaValidateChecksum(parse);
// Start searching for a preamble byte
parse->state = sempFirstByte;
return true;
}
// Pass the sentence to the upper layer
sempNmeaValidateChecksum(parse);
// Start searching for a preamble byte
return sempFirstByte(parse, data);
}
// Read the remaining carriage return
bool sempNmeaCarriageReturn(SEMP_PARSE_STATE *parse, uint8_t data)
{
// Don't add the current character to the length
parse->length -= 1;
// Process the CR
if (data == '\r')
{
// Pass the sentence to the upper layer
sempNmeaValidateChecksum(parse);
// Start searching for a preamble byte
parse->state = sempFirstByte;
return true;
}
// Pass the sentence to the upper layer
sempNmeaValidateChecksum(parse);
// Start searching for a preamble byte
return sempFirstByte(parse, data);
}
// Read the line termination
bool sempNmeaLineTermination(SEMP_PARSE_STATE *parse, uint8_t data)
{
int checksum;
// Don't add the current character to the length
parse->length -= 1;
// Process the line termination
if (data == '\r')
{
parse->state = sempNmeaLineFeed;
return true;
}
else if (data == '\n')
{
parse->state = sempNmeaCarriageReturn;
return true;
}
// Pass the sentence to the upper layer
sempNmeaValidateChecksum(parse);
// Start searching for a preamble byte
return sempFirstByte(parse, data);
}
// Read the second checksum byte
bool sempNmeaChecksumByte2(SEMP_PARSE_STATE *parse, uint8_t data)
{
// Validate the checksum character
if (sempAsciiToNibble(parse->buffer[parse->length - 1]) >= 0)
{
parse->state = sempNmeaLineTermination;
return true;
}
// Invalid checksum character
sempPrintf(parse->printDebug,
"SEMP %s: NMEA invalid second checksum character",
parse->parserName);
// Start searching for a preamble byte
return sempFirstByte(parse, data);
}
// Read the first checksum byte
bool sempNmeaChecksumByte1(SEMP_PARSE_STATE *parse, uint8_t data)
{
// Validate the checksum character
if (sempAsciiToNibble(parse->buffer[parse->length - 1]) >= 0)
{
parse->state = sempNmeaChecksumByte2;
return true;
}
// Invalid checksum character
sempPrintf(parse->printDebug,
"SEMP %s: NMEA invalid first checksum character",
parse->parserName);
// Start searching for a preamble byte
return sempFirstByte(parse, data);
}
// Read the sentence data
bool sempNmeaFindAsterisk(SEMP_PARSE_STATE *parse, uint8_t data)
{
if (data == '*')
parse->state = sempNmeaChecksumByte1;
else
{
// Include this byte in the checksum
parse->crc ^= data;
// Verify that enough space exists in the buffer
if ((parse->length + NMEA_BUFFER_OVERHEAD) > parse->bufferLength)
{
// sentence too long
sempPrintf(parse->printDebug,
"SEMP %s: NMEA sentence too long, increase the buffer size > %d",
parse->parserName,
parse->bufferLength);
// Start searching for a preamble byte
return sempFirstByte(parse, data);
}
}
return true;
}
// Read the sentence name
bool sempNmeaFindFirstComma(SEMP_PARSE_STATE *parse, uint8_t data)
{
SEMP_SCRATCH_PAD *scratchPad = (SEMP_SCRATCH_PAD *)parse->scratchPad;
parse->crc ^= data;
if ((data != ',') || (scratchPad->nmea.sentenceNameLength == 0))
{
// Invalid data, start searching for a preamble byte
uint8_t upper = data & ~0x20;
if (((upper < 'A') || (upper > 'Z')) && ((data < '0') || (data > '9')))
{
sempPrintf(parse->printDebug,
"SEMP %s: NMEA invalid sentence name character 0x%02x",
parse->parserName, data);
return sempFirstByte(parse, data);
}
// Name too long, start searching for a preamble byte
if (scratchPad->nmea.sentenceNameLength == (sizeof(scratchPad->nmea.sentenceName) - 1))
{
sempPrintf(parse->printDebug,
"SEMP %s: NMEA sentence name > %d characters",
parse->parserName,
sizeof(scratchPad->nmea.sentenceName) - 1);
return sempFirstByte(parse, data);
}
// Save the sentence name
scratchPad->nmea.sentenceName[scratchPad->nmea.sentenceNameLength++] = data;
}
else
{
// Zero terminate the sentence name
scratchPad->nmea.sentenceName[scratchPad->nmea.sentenceNameLength++] = 0;
parse->state = sempNmeaFindAsterisk;
}
return true;
}
// Check for the preamble
bool sempNmeaPreamble(SEMP_PARSE_STATE *parse, uint8_t data)
{
SEMP_SCRATCH_PAD *scratchPad = (SEMP_SCRATCH_PAD *)parse->scratchPad;
if (data != '$')
return false;
scratchPad->nmea.sentenceNameLength = 0;
parse->state = sempNmeaFindFirstComma;
return true;
}
// Translates state value into an string, returns nullptr if not found
const char * sempNmeaGetStateName(const SEMP_PARSE_STATE *parse)
{
if (parse->state == sempNmeaPreamble)
return "sempNmeaPreamble";
if (parse->state == sempNmeaFindFirstComma)
return "sempNmeaFindFirstComma";
if (parse->state == sempNmeaFindAsterisk)
return "sempNmeaFindAsterisk";
if (parse->state == sempNmeaChecksumByte1)
return "sempNmeaChecksumByte1";
if (parse->state == sempNmeaChecksumByte2)
return "sempNmeaChecksumByte2";
if (parse->state == sempNmeaLineTermination)
return "sempNmeaLineTermination";
if (parse->state == sempNmeaCarriageReturn)
return "sempNmeaCarriageReturn";
if (parse->state == sempNmeaLineFeed)
return "sempNmeaLineFeed";
return nullptr;
}
// Return the NMEA sentence name as a string
const char * sempNmeaGetSentenceName(const SEMP_PARSE_STATE *parse)
{
SEMP_SCRATCH_PAD *scratchPad = (SEMP_SCRATCH_PAD *)parse->scratchPad;
return (const char *)scratchPad->nmea.sentenceName;
}