rusEFI
The most advanced open source ECU
Loading...
Searching...
No Matches
nmea.cpp
Go to the documentation of this file.
1/**
2 * @date Dec 20, 2013
3 *
4 * @author Andrey Belomutskiy, (c) 2012-2020
5 * @author Kot_dnz
6 *
7 * This file is part of rusEfi - see http://rusefi.com
8 *
9 * rusEfi is free software; you can redistribute it and/or modify it under the terms of
10 * the GNU General Public License as published by the Free Software Foundation; either
11 * version 3 of the License, or (at your option) any later version.
12 *
13 * rusEfi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
14 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along with this program.
18 * If not, see <http://www.gnu.org/licenses/>.
19 *
20 * see #testGpsParser
21 */
22#include "pch.h"
23#include "nmea.h"
24#include "rtc_helper.h"
25
26using namespace rusefi::stringutil;
27
28static long hex2int(const char * a, const int len) {
29 int i;
30 long val = 0;
31
32 for (i = 0; i < len; i++)
33 if (a[i] <= 57)
34 val += (a[i] - 48) * (1 << (4 * (len - 1 - i))); // it's decimal number
35 else
36 val += (a[i] - 87) * (1 << (4 * (len - 1 - i))); // it's a-f -> work only with low case hex
37 return val;
38}
39
40static float gps_deg_dec(float deg_point) {
41 float ddeg;
42 float sec = modff(deg_point, &ddeg) * 60;
43 int deg = (int) (ddeg / 100);
44 int min = (int) (deg_point - (deg * 100));
45
46 float absdlat = round(deg * 1000000.);
47 float absmlat = round(min * 1000000.);
48 float absslat = round(sec * 1000000.);
49
50 return round(absdlat + (absmlat / 60) + (absslat / 3600)) / 1000000;
51}
52
53// Convert lat e lon to decimals (from deg)
54static void gps_convert_deg_to_dec(float *latitude, char ns, float *longitude, char we) {
55 float lat = (ns == 'N') ? *latitude : -1 * (*latitude);
56 float lon = (we == 'E') ? *longitude : -1 * (*longitude);
57
58 *latitude = gps_deg_dec(lat);
59 *longitude = gps_deg_dec(lon);
60}
61
62// in string collect all char till comma and convert to float
63static int str_till_comma(const char * const a, char * const dStr) {
64
65 int i = 0, sLen = strlen(a);
66 if (sLen > GPS_MAX_STRING)
67 sLen = GPS_MAX_STRING;
68
69 while (i < sLen && a[i] != 44) { // while not comma or end
70 dStr[i] = a[i];
71 i++;
72 }
73 dStr[i] = '\0';
74 return i;
75}
76
77/*
78GxGGA - name code
79Parameter Value Unit Description
80UTC hhmmss.sss Universal time coordinated
81Lat ddmm.mmmm Latitude
82Northing Indicator N=North, S=South
83Lon dddmm.mmmm Longitude
84Easting Indicator E=East, W=West
85Status 0 0=Invalid, 1=2D/3D, 2=DGPS, 6=Dead Reckoning
86SVs Used 00 Number of SVs used for Navigation
87HDOP 99.99 Horizontal Dilution of Precision
88Alt (MSL) m Altitude (above means sea level)
89Unit M=Meters
90Geoid Sep. m Geoid Separation = Alt(HAE) - Alt(MSL)
91Unit M=Meters
92Age of DGPS Corr s Age of Differential Corrections
93DGPS Ref Station ID of DGPS Reference Station
94*/
95static void nmea_parse_gpgga(char const * const nmea, loc_t *loc) {
96 char const * p = (char *)nmea;
97 char dStr[GPS_MAX_STRING];
98
99 p = strchr(p, ',') + 1; //skip time - we read date&time if Valid in GxRMC
100
101 p = strchr(p, ',') + 1; // in p string started with searching address
102 str_till_comma(p, dStr); // str to float till comma saved modified string
103 if (strlen(p) == 0) {
104 return; // if no data in field - empty data - we return
105 }
106
107 loc->latitude = atoff(dStr); // fulfil data
108
109 p = strchr(p, ',') + 1; // see above
110 switch (p[0]) {
111 case 'N':
112 loc->lat = 'N';
113 break;
114 case 'S':
115 loc->lat = 'S';
116 break;
117 case ',':
118 loc->lat = '\0';
119 break;
120 }
121
122 p = strchr(p, ',') + 1;
123 str_till_comma(p, dStr); // str to float till comma saved modified string
124 loc->longitude = atoff(dStr);
125
126 p = strchr(p, ',') + 1;
127 switch (p[0]) {
128 case 'W':
129 loc->lon = 'W';
130 break;
131 case 'E':
132 loc->lon = 'E';
133 break;
134 case ',':
135 loc->lon = '\0';
136 break;
137 }
138
139 p = strchr(p, ',') + 1;
140 str_till_comma(p, dStr); // str to float till comma saved modified string
141 loc->quality = atoi(dStr);
142
143 p = strchr(p, ',') + 1;
144 str_till_comma(p, dStr); // str to float till comma saved modified string
145 loc->satellites = atoi(dStr);
146
147 p = strchr(p, ',') + 1;
148
149 p = strchr(p, ',') + 1;
150 str_till_comma(p, dStr); // str to float till comma saved modified string
151 loc->altitude = atoff(dStr);
152}
153
154/*
155GxRMC - nmea code
156Parameter Value Unit Description
157UTC hhmmss.sss Universal time coordinated
158Status V A=Valid, V=Invalid
159Lat ddmm.mmmm Latitude
160Northing Indicator N=North, S=South
161Lon dddmm.mmmm Longitude
162Easting Indicator E=East, W=West
163SOG nots Speed Over Ground
164COG (true) ° Course Over Ground (true)
165Date ddmmyy Universal time coordinated
166Magnetic Variation ° Magnetic Variation
167Magnetic Variation E=East,W=West
168Mode Indicator N A=Autonomous, D=Differential, E=Dead Reckoning, N=None
169Navigational Status S=Safe C=Caution U=Unsafe V=Not valid
170*/
171
172static void nmea_parse_gprmc(char const * const nmea, loc_t *loc) {
173 char const * p = (char *)nmea;
174 char dStr[GPS_MAX_STRING];
175 efidatetime_t dt;
176
177 p = strchr(p, ',') + 1; // read time
178 str_till_comma(p, dStr);
179 if (strlen(dStr) > 5) {
180 dt.hour = str2int(dStr, 2);
181 dt.minute = str2int(dStr + 2, 2);
182 dt.second = str2int(dStr + 4, 2);
183 }
184
185 p = strchr(p, ',') + 1; // read field Valid status
186 str_till_comma(p, dStr);
187
188 if (dStr[0] == 'V') { // if field is invalid
189 loc->quality = 0;
190 return;
191 }
192
193 loc->quality = 4; // this is declaration that last receive field VALID
194
195 p = strchr(p, ',') + 1; // latitude
196 str_till_comma(p, dStr); // str to float till comma saved modified string
197 loc->latitude = atoff(dStr);
198
199 p = strchr(p, ',') + 1;
200 switch (p[0]) {
201 case 'N':
202 loc->lat = 'N';
203 break;
204 case 'S':
205 loc->lat = 'S';
206 break;
207 case ',':
208 loc->lat = '\0';
209 break;
210 }
211
212 p = strchr(p, ',') + 1; // longitude
213 str_till_comma(p, dStr); // str to float till comma saved modified string
214 loc->longitude = atoff(dStr);
215
216 p = strchr(p, ',') + 1;
217 switch (p[0]) {
218 case 'W':
219 loc->lon = 'W';
220 break;
221 case 'E':
222 loc->lon = 'E';
223 break;
224 case ',':
225 loc->lon = '\0';
226 break;
227 }
228
229 p = strchr(p, ',') + 1;
230 str_till_comma(p, dStr); // str to float till comma saved modified string
231 loc->speed = atoff(dStr);
232
233 p = strchr(p, ',') + 1;
234 str_till_comma(p, dStr); // str to float till comma saved modified string
235 loc->course = atoff(dStr);
236
237 p = strchr(p, ',') + 1; // read date
238 str_till_comma(p, dStr);
239 if (strlen(dStr) > 5) {
240 dt.day = str2int(dStr, 2);
241 dt.month = str2int(dStr + 2, 2);
242 dt.year = 100 + // we receive -200, but standard wait -1900 = add correction
243 str2int(dStr + 4, 2);
244 }
245
246 if (dt.year > 0 ) { // check if date field is valid
247 memcpy(&loc->time, &dt, sizeof(dt));
248 }
249}
250
251/**
252 * Get the message type (GPGGA, GPRMC, etc..)
253 *
254 * This function filters out also wrong packages (invalid checksum)
255 *
256 * @param message The NMEA message
257 * @return The type of message if it is valid
258 */
260 int checksum = nmea_valid_checksum(message);
261 if (checksum != _EMPTY) {
262 return static_cast<nmea_message_type>(checksum);
263 }
264
265 if (strstr(message, NMEA_GPGGA_STR) != NULL) {
266 return NMEA_GPGGA;
267 }
268
269 if (strstr(message, NMEA_GPRMC_STR) != NULL) {
270 return NMEA_GPRMC;
271 }
272
273 return NMEA_UNKNOWN;
274}
275
276int nmea_valid_checksum(char const * message) {
277 char p;
278 int sum = 0;
279 const char* starPtr = strrchr(message, '*');
280 if (!starPtr) {
281 return NMEA_CHECKSUM_ERR;
282 }
283 const char* int_message = starPtr + 1;
284 long checksum = hex2int(int_message, 2);
285
286 ++message;
287 while ((p = *message++) != '*') {
288 sum ^= p;
289 }
290
291 if (sum != checksum) {
292 return NMEA_CHECKSUM_ERR;
293 }
294 return _EMPTY;
295}
296
297// Compute the GPS location using decimal scale
298void gps_location(loc_t *coord, char const * const buffer) {
300
301 switch (coord->type) {
302 case NMEA_GPGGA:
303 nmea_parse_gpgga(buffer, coord);
304 gps_convert_deg_to_dec(&(coord->latitude), coord->lat, &(coord->longitude), coord->lon);
305 break;
306 case NMEA_GPRMC:
307 nmea_parse_gprmc(buffer, coord);
308 break;
309 case NMEA_UNKNOWN:
310 // unknown message type
311 break;
312 }
313}
static int str_till_comma(const char *const a, char *const dStr)
Definition nmea.cpp:63
static void nmea_parse_gprmc(char const *const nmea, loc_t *loc)
Definition nmea.cpp:172
static void nmea_parse_gpgga(char const *const nmea, loc_t *loc)
Definition nmea.cpp:95
void gps_location(loc_t *coord, char const *const buffer)
Definition nmea.cpp:298
static float gps_deg_dec(float deg_point)
Definition nmea.cpp:40
static long hex2int(const char *a, const int len)
Definition nmea.cpp:28
int nmea_valid_checksum(char const *message)
Definition nmea.cpp:276
nmea_message_type nmea_get_message_type(const char *message)
Definition nmea.cpp:259
static void gps_convert_deg_to_dec(float *latitude, char ns, float *longitude, char we)
Definition nmea.cpp:54
static int str2int(const char *a, const int len)
Definition nmea.h:48
nmea_message_type
Definition nmea.h:13
@ NMEA_UNKNOWN
Definition nmea.h:14
@ NMEA_GPGGA
Definition nmea.h:16
@ NMEA_GPRMC
Definition nmea.h:15
Real Time Clock helper.
char lat
Definition nmea.h:37
float altitude
Definition nmea.h:31
float latitude
Definition nmea.h:28
int satellites
Definition nmea.h:36
nmea_message_type type
Definition nmea.h:34
efidatetime_t time
Definition nmea.h:33
float course
Definition nmea.h:32
float longitude
Definition nmea.h:29
char lon
Definition nmea.h:38
int quality
Definition nmea.h:35
float speed
Definition nmea.h:30
uint32_t year
static BigBufferHandle buffer