1 /* Copyright (c) 2012-2020, The Linux Foundation. All rights reserved.
2 *
3 * Redistribution and use in source and binary forms, with or without
4 * modification, are permitted provided that the following conditions are
5 * met:
6 * * Redistributions of source code must retain the above copyright
7 * notice, this list of conditions and the following disclaimer.
8 * * Redistributions in binary form must reproduce the above
9 * copyright notice, this list of conditions and the following
10 * disclaimer in the documentation and/or other materials provided
11 * with the distribution.
12 * * Neither the name of The Linux Foundation nor the names of its
13 * contributors may be used to endorse or promote products derived
14 * from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
23 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
24 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
25 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
26 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 *
28 */
29
30 #define LOG_NDEBUG 0
31 #define LOG_TAG "LocSvc_nmea"
32 #include <loc_nmea.h>
33 #include <math.h>
34 #include <log_util.h>
35 #include <loc_pla.h>
36 #include <loc_cfg.h>
37
38 #define GLONASS_SV_ID_OFFSET 64
39 #define SBAS_SV_ID_OFFSET (87)
40 #define QZSS_SV_ID_OFFSET (192)
41 #define BDS_SV_ID_OFFSET (200)
42 #define GALILEO_SV_ID_OFFSET (300)
43 #define NAVIC_SV_ID_OFFSET (400)
44 #define MAX_SV_COUNT_SUPPORTED_IN_ONE_CONSTELLATION 64
45 #define MAX_SATELLITES_IN_USE 12
46 #define MSEC_IN_ONE_WEEK 604800000ULL
47 #define UTC_GPS_OFFSET_MSECS 315964800000ULL
48 #define MAX_TAG_BLOCK_GROUP_CODE (99999)
49
50 // GNSS system id according to NMEA spec
51 #define SYSTEM_ID_GPS 1
52 #define SYSTEM_ID_GLONASS 2
53 #define SYSTEM_ID_GALILEO 3
54 #define SYSTEM_ID_BDS 4
55 #define SYSTEM_ID_QZSS 5
56 #define SYSTEM_ID_NAVIC 6
57
58 //GNSS signal id according to NMEA spec
59 #define SIGNAL_ID_ALL_SIGNALS 0
60 #define SIGNAL_ID_GPS_L1CA 1
61 #define SIGNAL_ID_GPS_L1P 2
62 #define SIGNAL_ID_GPS_L1M 3
63 #define SIGNAL_ID_GPS_L2P 4
64 #define SIGNAL_ID_GPS_L2CM 5
65 #define SIGNAL_ID_GPS_L2CL 6
66 #define SIGNAL_ID_GPS_L5I 7
67 #define SIGNAL_ID_GPS_L5Q 8
68
69
70 #define SIGNAL_ID_GLO_G1CA 1
71 #define SIGNAL_ID_GLO_G1P 2
72 #define SIGNAL_ID_GLO_G2CA 3
73 #define SIGNAL_ID_GLO_G2P 4
74
75
76 #define SIGNAL_ID_GAL_E5A 1
77 #define SIGNAL_ID_GAL_E5B 2
78 #define SIGNAL_ID_GAL_E5AB 3
79 #define SIGNAL_ID_GAL_E6A 4
80 #define SIGNAL_ID_GAL_E6BC 5
81 #define SIGNAL_ID_GAL_L1A 6
82 #define SIGNAL_ID_GAL_L1BC 7
83
84 #define SIGNAL_ID_BDS_B1I 1
85 #define SIGNAL_ID_BDS_B1Q 2
86 #define SIGNAL_ID_BDS_B1C 3
87 #define SIGNAL_ID_BDS_B1A 4
88 #define SIGNAL_ID_BDS_B2A 5
89 #define SIGNAL_ID_BDS_B2B 6
90 #define SIGNAL_ID_BDS_B2AB 7
91 #define SIGNAL_ID_BDS_B3I 8
92 #define SIGNAL_ID_BDS_B3Q 9
93 #define SIGNAL_ID_BDS_B3A 0xA
94 #define SIGNAL_ID_BDS_B2I 0xB
95 #define SIGNAL_ID_BDS_B2Q 0xC
96
97 #define SIGNAL_ID_QZSS_L1CA 1
98 #define SIGNAL_ID_QZSS_L1CD 2
99 #define SIGNAL_ID_QZSS_L1CP 3
100 #define SIGNAL_ID_QZSS_LIS 4
101 #define SIGNAL_ID_QZSS_L2CM 5
102 #define SIGNAL_ID_QZSS_L2CL 6
103 #define SIGNAL_ID_QZSS_L5I 7
104 #define SIGNAL_ID_QZSS_L5Q 8
105 #define SIGNAL_ID_QZSS_L6D 9
106 #define SIGNAL_ID_QZSS_L6E 0xA
107
108 #define SIGNAL_ID_NAVIC_L5SPS 1
109 #define SIGNAL_ID_NAVIC_SSPS 2
110 #define SIGNAL_ID_NAVIC_L5RS 3
111 #define SIGNAL_ID_NAVIC_SRS 4
112 #define SIGNAL_ID_NAVIC_L1SPS 5
113
114
115 typedef struct loc_nmea_sv_meta_s
116 {
117 char talker[3];
118 uint32_t svTypeMask;
119 uint64_t mask;
120 uint32_t svCount;
121 uint32_t totalSvUsedCount;
122 uint32_t svIdOffset;
123 uint32_t signalId;
124 uint32_t systemId;
125 } loc_nmea_sv_meta;
126
127 typedef struct loc_sv_cache_info_s
128 {
129 uint64_t gps_used_mask;
130 uint64_t glo_used_mask;
131 uint64_t gal_used_mask;
132 uint64_t qzss_used_mask;
133 uint64_t bds_used_mask;
134 uint64_t navic_used_mask;
135 uint32_t gps_l1_count;
136 uint32_t gps_l2_count;
137 uint32_t gps_l5_count;
138 uint32_t glo_g1_count;
139 uint32_t glo_g2_count;
140 uint32_t gal_e1_count;
141 uint32_t gal_e5_count;
142 uint32_t gal_e5b_count;
143 uint32_t qzss_l1_count;
144 uint32_t qzss_l2_count;
145 uint32_t qzss_l5_count;
146 uint32_t bds_b1i_count;
147 uint32_t bds_b1c_count;
148 uint32_t bds_b2_count;
149 uint32_t navic_l5_count;
150 float hdop;
151 float pdop;
152 float vdop;
153 } loc_sv_cache_info;
154
155 /*===========================================================================
156 FUNCTION convert_Lla_to_Ecef
157
158 DESCRIPTION
159 Convert LLA to ECEF
160
161 DEPENDENCIES
162 NONE
163
164 RETURN VALUE
165 NONE
166
167 SIDE EFFECTS
168 N/A
169
170 ===========================================================================*/
convert_Lla_to_Ecef(const LocLla & plla,LocEcef & pecef)171 static void convert_Lla_to_Ecef(const LocLla& plla, LocEcef& pecef)
172 {
173 double r;
174
175 r = MAJA / sqrt(1.0 - ESQR * sin(plla.lat) * sin(plla.lat));
176 pecef.X = (r + plla.alt) * cos(plla.lat) * cos(plla.lon);
177 pecef.Y = (r + plla.alt) * cos(plla.lat) * sin(plla.lon);
178 pecef.Z = (r * OMES + plla.alt) * sin(plla.lat);
179 }
180
181 /*===========================================================================
182 FUNCTION convert_WGS84_to_PZ90
183
184 DESCRIPTION
185 Convert datum from WGS84 to PZ90
186
187 DEPENDENCIES
188 NONE
189
190 RETURN VALUE
191 NONE
192
193 SIDE EFFECTS
194 N/A
195
196 ===========================================================================*/
convert_WGS84_to_PZ90(const LocEcef & pWGS84,LocEcef & pPZ90)197 static void convert_WGS84_to_PZ90(const LocEcef& pWGS84, LocEcef& pPZ90)
198 {
199 double deltaX = DatumConstFromWGS84[0];
200 double deltaY = DatumConstFromWGS84[1];
201 double deltaZ = DatumConstFromWGS84[2];
202 double deltaScale = DatumConstFromWGS84[3];
203 double rotX = DatumConstFromWGS84[4];
204 double rotY = DatumConstFromWGS84[5];
205 double rotZ = DatumConstFromWGS84[6];
206
207 pPZ90.X = deltaX + deltaScale * (pWGS84.X + rotZ * pWGS84.Y - rotY * pWGS84.Z);
208 pPZ90.Y = deltaY + deltaScale * (pWGS84.Y - rotZ * pWGS84.X + rotX * pWGS84.Z);
209 pPZ90.Z = deltaZ + deltaScale * (pWGS84.Z + rotY * pWGS84.X - rotX * pWGS84.Y);
210 }
211
212 /*===========================================================================
213 FUNCTION convert_Ecef_to_Lla
214
215 DESCRIPTION
216 Convert ECEF to LLA
217
218 DEPENDENCIES
219 NONE
220
221 RETURN VALUE
222 NONE
223
224 SIDE EFFECTS
225 N/A
226
227 ===========================================================================*/
convert_Ecef_to_Lla(const LocEcef & pecef,LocLla & plla)228 static void convert_Ecef_to_Lla(const LocEcef& pecef, LocLla& plla)
229 {
230 double p, r;
231 double EcefA = C_PZ90A;
232 double EcefB = C_PZ90B;
233 double Ecef1Mf;
234 double EcefE2;
235 double Mu;
236 double Smu;
237 double Cmu;
238 double Phi;
239 double Sphi;
240 double N;
241
242 p = sqrt(pecef.X * pecef.X + pecef.Y * pecef.Y);
243 r = sqrt(p * p + pecef.Z * pecef.Z);
244 if (r < 1.0) {
245 plla.lat = 1.0;
246 plla.lon = 1.0;
247 plla.alt = 1.0;
248 }
249 Ecef1Mf = 1.0 - (EcefA - EcefB) / EcefA;
250 EcefE2 = 1.0 - (EcefB * EcefB) / (EcefA * EcefA);
251 if (p > 1.0) {
252 Mu = atan2(pecef.Z * (Ecef1Mf + EcefE2 * EcefA / r), p);
253 } else {
254 if (pecef.Z > 0.0) {
255 Mu = M_PI / 2.0;
256 } else {
257 Mu = -M_PI / 2.0;
258 }
259 }
260 Smu = sin(Mu);
261 Cmu = cos(Mu);
262 Phi = atan2(pecef.Z * Ecef1Mf + EcefE2 * EcefA * Smu * Smu * Smu,
263 Ecef1Mf * (p - EcefE2 * EcefA * Cmu * Cmu * Cmu));
264 Sphi = sin(Phi);
265 N = EcefA / sqrt(1.0 - EcefE2 * Sphi * Sphi);
266 plla.alt = p * cos(Phi) + pecef.Z * Sphi - EcefA * EcefA/N;
267 plla.lat = Phi;
268 if ( p > 1.0) {
269 plla.lon = atan2(pecef.Y, pecef.X);
270 } else {
271 plla.lon = 0.0;
272 }
273 }
274
275 /*===========================================================================
276 FUNCTION convert_signalType_to_signalId
277
278 DESCRIPTION
279 convert signalType to signal ID
280
281 DEPENDENCIES
282 NONE
283
284 RETURN VALUE
285 value of signal ID
286
287 SIDE EFFECTS
288 N/A
289
290 ===========================================================================*/
convert_signalType_to_signalId(GnssSignalTypeMask signalType)291 static uint32_t convert_signalType_to_signalId(GnssSignalTypeMask signalType)
292 {
293 uint32_t signalId = SIGNAL_ID_ALL_SIGNALS;
294
295 switch (signalType) {
296 case GNSS_SIGNAL_GPS_L1CA:
297 case GNSS_SIGNAL_SBAS_L1:
298 signalId = SIGNAL_ID_GPS_L1CA;
299 break;
300 case GNSS_SIGNAL_GPS_L2:
301 signalId = SIGNAL_ID_GPS_L2CL;
302 break;
303 case GNSS_SIGNAL_GPS_L5:
304 signalId = SIGNAL_ID_GPS_L5Q;
305 break;
306 case GNSS_SIGNAL_GLONASS_G1:
307 signalId = SIGNAL_ID_GLO_G1CA;
308 break;
309 case GNSS_SIGNAL_GLONASS_G2:
310 signalId = SIGNAL_ID_GLO_G2CA;
311 break;
312 case GNSS_SIGNAL_GALILEO_E1:
313 signalId = SIGNAL_ID_GAL_L1BC;
314 break;
315 case GNSS_SIGNAL_GALILEO_E5A:
316 signalId = SIGNAL_ID_GAL_E5A;
317 break;
318 case GNSS_SIGNAL_GALILEO_E5B:
319 signalId = SIGNAL_ID_GAL_E5B;
320 break;
321 case GNSS_SIGNAL_QZSS_L1CA:
322 signalId = SIGNAL_ID_QZSS_L1CA;
323 break;
324 case GNSS_SIGNAL_QZSS_L2:
325 signalId = SIGNAL_ID_QZSS_L2CL;
326 break;
327 case GNSS_SIGNAL_QZSS_L5:
328 signalId = SIGNAL_ID_QZSS_L5Q;
329 break;
330 case GNSS_SIGNAL_BEIDOU_B1I:
331 signalId = SIGNAL_ID_BDS_B1I;
332 break;
333 case GNSS_SIGNAL_BEIDOU_B1C:
334 signalId = SIGNAL_ID_BDS_B1C;
335 break;
336 case GNSS_SIGNAL_BEIDOU_B2I:
337 signalId = SIGNAL_ID_BDS_B2I;
338 break;
339 case GNSS_SIGNAL_BEIDOU_B2AI:
340 case GNSS_SIGNAL_BEIDOU_B2AQ:
341 signalId = SIGNAL_ID_BDS_B2A;
342 break;
343 case GNSS_SIGNAL_NAVIC_L5:
344 signalId = SIGNAL_ID_NAVIC_L5SPS;
345 break;
346 default:
347 signalId = SIGNAL_ID_ALL_SIGNALS;
348 }
349
350 return signalId;
351
352 }
353
354 /*===========================================================================
355 FUNCTION get_sv_count_from_mask
356
357 DESCRIPTION
358 get the sv count from bit mask
359
360 DEPENDENCIES
361 NONE
362
363 RETURN VALUE
364 value of sv count
365
366 SIDE EFFECTS
367 N/A
368
369 ===========================================================================*/
get_sv_count_from_mask(uint64_t svMask,int totalSvCount)370 static uint32_t get_sv_count_from_mask(uint64_t svMask, int totalSvCount)
371 {
372 int index = 0;
373 uint32_t svCount = 0;
374
375 if(totalSvCount > MAX_SV_COUNT_SUPPORTED_IN_ONE_CONSTELLATION) {
376 LOC_LOGE("total SV count in this constellation %d exceeded limit %d",
377 totalSvCount, MAX_SV_COUNT_SUPPORTED_IN_ONE_CONSTELLATION);
378 }
379 for(index = 0; index < totalSvCount; index++) {
380 if(svMask & 0x1)
381 svCount += 1;
382 svMask >>= 1;
383 }
384 return svCount;
385 }
386
387 /*===========================================================================
388 FUNCTION loc_nmea_sv_meta_init
389
390 DESCRIPTION
391 Init loc_nmea_sv_meta passed in
392
393 DEPENDENCIES
394 NONE
395
396 RETURN VALUE
397 Pointer to loc_nmea_sv_meta
398
399 SIDE EFFECTS
400 N/A
401
402 ===========================================================================*/
loc_nmea_sv_meta_init(loc_nmea_sv_meta & sv_meta,loc_sv_cache_info & sv_cache_info,GnssSvType svType,GnssSignalTypeMask signalType,bool needCombine)403 static loc_nmea_sv_meta* loc_nmea_sv_meta_init(loc_nmea_sv_meta& sv_meta,
404 loc_sv_cache_info& sv_cache_info,
405 GnssSvType svType,
406 GnssSignalTypeMask signalType,
407 bool needCombine)
408 {
409 memset(&sv_meta, 0, sizeof(sv_meta));
410 sv_meta.svTypeMask = (1 << svType);
411
412 switch (svType)
413 {
414 case GNSS_SV_TYPE_GPS:
415 sv_meta.talker[0] = 'G';
416 sv_meta.talker[1] = 'P';
417 sv_meta.mask = sv_cache_info.gps_used_mask;
418 sv_meta.systemId = SYSTEM_ID_GPS;
419 sv_meta.svTypeMask |= (1 << GNSS_SV_TYPE_SBAS);
420 switch (signalType) {
421 case GNSS_SIGNAL_GPS_L1CA:
422 sv_meta.svCount = sv_cache_info.gps_l1_count;
423 break;
424 case GNSS_SIGNAL_GPS_L5:
425 sv_meta.svCount = sv_cache_info.gps_l5_count;
426 break;
427 case GNSS_SIGNAL_GPS_L2:
428 sv_meta.svCount = sv_cache_info.gps_l2_count;
429 break;
430 }
431 break;
432 case GNSS_SV_TYPE_GLONASS:
433 sv_meta.talker[0] = 'G';
434 sv_meta.talker[1] = 'L';
435 sv_meta.mask = sv_cache_info.glo_used_mask;
436 // GLONASS SV ids are from 65-96
437 sv_meta.svIdOffset = GLONASS_SV_ID_OFFSET;
438 sv_meta.systemId = SYSTEM_ID_GLONASS;
439 switch (signalType) {
440 case GNSS_SIGNAL_GLONASS_G1:
441 sv_meta.svCount = sv_cache_info.glo_g1_count;
442 break;
443 case GNSS_SIGNAL_GLONASS_G2:
444 sv_meta.svCount = sv_cache_info.glo_g2_count;
445 break;
446 }
447 break;
448 case GNSS_SV_TYPE_GALILEO:
449 sv_meta.talker[0] = 'G';
450 sv_meta.talker[1] = 'A';
451 sv_meta.mask = sv_cache_info.gal_used_mask;
452 // GALILEO SV ids are from 301-336, So keep svIdOffset 300
453 sv_meta.svIdOffset = GALILEO_SV_ID_OFFSET;
454 sv_meta.systemId = SYSTEM_ID_GALILEO;
455 switch (signalType) {
456 case GNSS_SIGNAL_GALILEO_E1:
457 sv_meta.svCount = sv_cache_info.gal_e1_count;
458 break;
459 case GNSS_SIGNAL_GALILEO_E5A:
460 sv_meta.svCount = sv_cache_info.gal_e5_count;
461 break;
462 case GNSS_SIGNAL_GALILEO_E5B:
463 sv_meta.svCount = sv_cache_info.gal_e5b_count;
464 break;
465 }
466 break;
467 case GNSS_SV_TYPE_QZSS:
468 sv_meta.talker[0] = 'G';
469 sv_meta.talker[1] = 'Q';
470 sv_meta.mask = sv_cache_info.qzss_used_mask;
471 // QZSS SV ids are from 193-199. So keep svIdOffset 192
472 sv_meta.svIdOffset = QZSS_SV_ID_OFFSET;
473 sv_meta.systemId = SYSTEM_ID_QZSS;
474 switch (signalType) {
475 case GNSS_SIGNAL_QZSS_L1CA:
476 sv_meta.svCount = sv_cache_info.qzss_l1_count;
477 break;
478 case GNSS_SIGNAL_QZSS_L2:
479 sv_meta.svCount = sv_cache_info.qzss_l2_count;
480 break;
481 case GNSS_SIGNAL_QZSS_L5:
482 sv_meta.svCount = sv_cache_info.qzss_l5_count;
483 break;
484 }
485 break;
486 case GNSS_SV_TYPE_BEIDOU:
487 sv_meta.talker[0] = 'G';
488 sv_meta.talker[1] = 'B';
489 sv_meta.mask = sv_cache_info.bds_used_mask;
490 // BDS SV ids are from 201-237. So keep svIdOffset 200
491 sv_meta.svIdOffset = BDS_SV_ID_OFFSET;
492 sv_meta.systemId = SYSTEM_ID_BDS;
493 switch (signalType) {
494 case GNSS_SIGNAL_BEIDOU_B1I:
495 sv_meta.svCount = sv_cache_info.bds_b1i_count;
496 break;
497 case GNSS_SIGNAL_BEIDOU_B1C:
498 sv_meta.svCount = sv_cache_info.bds_b1c_count;
499 break;
500 case GNSS_SIGNAL_BEIDOU_B2AI:
501 sv_meta.svCount = sv_cache_info.bds_b2_count;
502 break;
503 }
504 break;
505 case GNSS_SV_TYPE_NAVIC:
506 sv_meta.talker[0] = 'G';
507 sv_meta.talker[1] = 'I';
508 sv_meta.mask = sv_cache_info.navic_used_mask;
509 // NAVIC SV ids are from 401-414. So keep svIdOffset 400
510 sv_meta.svIdOffset = NAVIC_SV_ID_OFFSET;
511 sv_meta.systemId = SYSTEM_ID_NAVIC;
512 switch (signalType) {
513 case GNSS_SIGNAL_NAVIC_L5:
514 sv_meta.svCount = sv_cache_info.navic_l5_count;
515 break;
516 }
517 break;
518 default:
519 LOC_LOGE("NMEA Error unknow constellation type: %d", svType);
520 return NULL;
521 }
522 sv_meta.signalId = convert_signalType_to_signalId(signalType);
523 sv_meta.totalSvUsedCount =
524 get_sv_count_from_mask(sv_cache_info.gps_used_mask,
525 GPS_SV_PRN_MAX - GPS_SV_PRN_MIN + 1) +
526 get_sv_count_from_mask(sv_cache_info.glo_used_mask,
527 GLO_SV_PRN_MAX - GLO_SV_PRN_MIN + 1) +
528 get_sv_count_from_mask(sv_cache_info.gal_used_mask,
529 GAL_SV_PRN_MAX - GAL_SV_PRN_MIN + 1) +
530 get_sv_count_from_mask(sv_cache_info.qzss_used_mask,
531 QZSS_SV_PRN_MAX - QZSS_SV_PRN_MIN + 1) +
532 get_sv_count_from_mask(sv_cache_info.bds_used_mask,
533 BDS_SV_PRN_MAX - BDS_SV_PRN_MIN + 1) +
534 get_sv_count_from_mask(sv_cache_info.navic_used_mask,
535 NAVIC_SV_PRN_MAX - NAVIC_SV_PRN_MIN + 1);
536 if (needCombine &&
537 (sv_cache_info.gps_used_mask ? 1 : 0) +
538 (sv_cache_info.glo_used_mask ? 1 : 0) +
539 (sv_cache_info.gal_used_mask ? 1 : 0) +
540 (sv_cache_info.qzss_used_mask ? 1 : 0) +
541 (sv_cache_info.bds_used_mask ? 1 : 0) +
542 (sv_cache_info.navic_used_mask ? 1 : 0) > 1)
543 {
544 // If GPS, GLONASS, Galileo, QZSS, BDS etc. are combined
545 // to obtain the reported position solution,
546 // talker shall be set to GN, to indicate that
547 // the satellites are used in a combined solution
548 sv_meta.talker[0] = 'G';
549 sv_meta.talker[1] = 'N';
550 }
551 return &sv_meta;
552 }
553
554 /*===========================================================================
555 FUNCTION loc_nmea_put_checksum
556
557 DESCRIPTION
558 Generate NMEA sentences generated based on position report
559
560 DEPENDENCIES
561 NONE
562
563 RETURN VALUE
564 Total length of the nmea sentence
565
566 SIDE EFFECTS
567 N/A
568
569 ===========================================================================*/
loc_nmea_put_checksum(char * pNmea,int maxSize,bool isTagBlock)570 static int loc_nmea_put_checksum(char *pNmea, int maxSize, bool isTagBlock)
571 {
572 uint8_t checksum = 0;
573 int length = 0;
574 int checksumLength = 0;
575 if(NULL == pNmea)
576 return 0;
577
578 pNmea++; //skip the $ or / for Tag Block
579 while (*pNmea != '\0')
580 {
581 checksum ^= *pNmea++;
582 length++;
583 }
584
585 if (isTagBlock) {
586 // length now contains tag block sentence string length not including / sign.
587 checksumLength = snprintf(pNmea, (maxSize-length-1), "*%02X\\", checksum);
588 } else {
589 // length now contains nmea sentence string length not including $ sign.
590 checksumLength = snprintf(pNmea, (maxSize-length-1), "*%02X\r\n", checksum);
591 }
592 // total length of nmea sentence is length of nmea sentence inc $ sign plus
593 // length of checksum (+1 is to cover the $ character in the length).
594 return (length + checksumLength + 1);
595 }
596
597 /*===========================================================================
598 FUNCTION loc_nmea_generate_GSA
599
600 DESCRIPTION
601 Generate NMEA GSA sentences generated based on position report
602 Currently below sentences are generated:
603 - $GPGSA : GPS DOP and active SVs
604 - $GLGSA : GLONASS DOP and active SVs
605 - $GAGSA : GALILEO DOP and active SVs
606 - $GNGSA : GNSS DOP and active SVs
607
608 DEPENDENCIES
609 NONE
610
611 RETURN VALUE
612 Number of SVs used
613
614 SIDE EFFECTS
615 N/A
616
617 ===========================================================================*/
loc_nmea_generate_GSA(const GpsLocationExtended & locationExtended,char * sentence,int bufSize,loc_nmea_sv_meta * sv_meta_p,std::vector<std::string> & nmeaArraystr,bool isTagBlockGroupingEnabled)618 static uint32_t loc_nmea_generate_GSA(const GpsLocationExtended &locationExtended,
619 char* sentence,
620 int bufSize,
621 loc_nmea_sv_meta* sv_meta_p,
622 std::vector<std::string> &nmeaArraystr,
623 bool isTagBlockGroupingEnabled)
624 {
625 if (!sentence || bufSize <= 0 || !sv_meta_p)
626 {
627 LOC_LOGE("NMEA Error invalid arguments.");
628 return 0;
629 }
630
631 char* pMarker = sentence;
632 int lengthRemaining = bufSize;
633 int length = 0;
634 int lengthTagBlock = 0;
635
636 uint32_t svUsedCount = 0;
637 uint32_t svUsedList[64] = {0};
638 uint32_t sentenceCount = 0;
639 uint32_t sentenceNumber = 1;
640 size_t svNumber = 1;
641 static uint32_t code = 1;
642
643 char fixType = '\0';
644
645 const char* talker = sv_meta_p->talker;
646 uint32_t svIdOffset = sv_meta_p->svIdOffset;
647 uint64_t mask = sv_meta_p->mask;
648
649 if (!(sv_meta_p->svTypeMask & (1 << GNSS_SV_TYPE_GLONASS))) {
650 svIdOffset = 0;
651 }
652
653 for (uint8_t i = 1; mask > 0 && svUsedCount < 64; i++)
654 {
655 if (mask & 1)
656 svUsedList[svUsedCount++] = i + svIdOffset;
657 mask = mask >> 1;
658 }
659
660 if (svUsedCount == 0) {
661 return 0;
662 } else {
663 sentenceNumber = 1;
664 sentenceCount = svUsedCount / 12 + (svUsedCount % 12 != 0);
665 svNumber = 1;
666 }
667 while (sentenceNumber <= sentenceCount) {
668 pMarker = sentence;
669 lengthRemaining = bufSize;
670 if (svUsedCount > 12 && isTagBlockGroupingEnabled) {
671 lengthTagBlock = snprintf(pMarker, lengthRemaining, "\\g:%d-%d-%d", sentenceNumber,
672 sentenceCount, code);
673 if (MAX_TAG_BLOCK_GROUP_CODE == code) {
674 code = 1;
675 }
676 lengthTagBlock = loc_nmea_put_checksum(sentence, bufSize, true);
677 pMarker += lengthTagBlock;
678 lengthRemaining -= lengthTagBlock;
679 }
680 if (sv_meta_p->totalSvUsedCount == 0)
681 fixType = '1'; // no fix
682 else if (sv_meta_p->totalSvUsedCount <= 3)
683 fixType = '2'; // 2D fix
684 else
685 fixType = '3'; // 3D fix
686
687 // Start printing the sentence
688 // Format: $--GSA,a,x,xx,xx,xx,xx,xx,xx,xx,xx,xx,xx,xx,xx,p.p,h.h,v.v,s*cc
689 // a : Mode : A : Automatic, allowed to automatically switch 2D/3D
690 // x : Fixtype : 1 (no fix), 2 (2D fix), 3 (3D fix)
691 // xx : 12 SV ID
692 // p.p : Position DOP (Dilution of Precision)
693 // h.h : Horizontal DOP
694 // v.v : Vertical DOP
695 // s : GNSS System Id
696 // cc : Checksum value
697 length = snprintf(pMarker, lengthRemaining, "$%sGSA,A,%c,", talker, fixType);
698 if (length < 0 || length >= lengthRemaining) {
699 LOC_LOGE("NMEA Error in string formatting");
700 return 0;
701 }
702 pMarker += length;
703 lengthRemaining -= length;
704
705 // Add 12 satellite IDs
706 for (uint8_t i = 0; i < 12; i++, svNumber++)
707 {
708 if (svNumber <= svUsedCount)
709 length = snprintf(pMarker, lengthRemaining, "%02d,", svUsedList[svNumber - 1]);
710 else
711 length = snprintf(pMarker, lengthRemaining, ",");
712
713 if (length < 0 || length >= lengthRemaining) {
714 LOC_LOGE("NMEA Error in string formatting");
715 return 0;
716 }
717 pMarker += length;
718 lengthRemaining -= length;
719 }
720
721 // Add the position/horizontal/vertical DOP values
722 if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_DOP)
723 {
724 length = snprintf(pMarker, lengthRemaining, "%.1f,%.1f,%.1f,",
725 locationExtended.pdop,
726 locationExtended.hdop,
727 locationExtended.vdop);
728 }
729 else
730 { // no dop
731 length = snprintf(pMarker, lengthRemaining, ",,,");
732 }
733 pMarker += length;
734 lengthRemaining -= length;
735
736 // system id
737 length = snprintf(pMarker, lengthRemaining, "%d", sv_meta_p->systemId);
738 pMarker += length;
739 lengthRemaining -= length;
740
741 /* Sentence is ready, add checksum and broadcast */
742 length = loc_nmea_put_checksum(sentence + lengthTagBlock, bufSize - lengthTagBlock, false);
743 nmeaArraystr.push_back(sentence);
744 sentenceNumber++;
745 if (!isTagBlockGroupingEnabled) {
746 break;
747 }
748 }
749 if (svUsedCount > 12 && isTagBlockGroupingEnabled) {
750 code++;
751 }
752 return svUsedCount;
753 }
754
755 /*===========================================================================
756 FUNCTION loc_nmea_generate_GSV
757
758 DESCRIPTION
759 Generate NMEA GSV sentences generated based on sv report
760 Currently below sentences are generated:
761 - $GPGSV: GPS Satellites in View
762 - $GLGSV: GLONASS Satellites in View
763 - $GAGSV: GALILEO Satellites in View
764
765 DEPENDENCIES
766 NONE
767
768 RETURN VALUE
769 NONE
770
771 SIDE EFFECTS
772 N/A
773
774 ===========================================================================*/
loc_nmea_generate_GSV(const GnssSvNotification & svNotify,char * sentence,int bufSize,loc_nmea_sv_meta * sv_meta_p,std::vector<std::string> & nmeaArraystr)775 static void loc_nmea_generate_GSV(const GnssSvNotification &svNotify,
776 char* sentence,
777 int bufSize,
778 loc_nmea_sv_meta* sv_meta_p,
779 std::vector<std::string> &nmeaArraystr)
780 {
781 if (!sentence || bufSize <= 0)
782 {
783 LOC_LOGE("NMEA Error invalid argument.");
784 return;
785 }
786
787 char* pMarker = sentence;
788 int lengthRemaining = bufSize;
789 int length = 0;
790 int sentenceCount = 0;
791 int sentenceNumber = 1;
792 size_t svNumber = 1;
793
794 const char* talker = sv_meta_p->talker;
795 uint32_t svIdOffset = sv_meta_p->svIdOffset;
796 int svCount = sv_meta_p->svCount;
797 if (svCount <= 0)
798 {
799 LOC_LOGV("No SV in view for talker ID:%s, signal ID:%X", talker, sv_meta_p->signalId);
800 return;
801 }
802
803 if ((1 << GNSS_SV_TYPE_GLONASS) & sv_meta_p->svTypeMask) {
804 svIdOffset = 0;
805 }
806 svNumber = 1;
807 sentenceNumber = 1;
808 sentenceCount = svCount / 4 + (svCount % 4 != 0);
809
810 while (sentenceNumber <= sentenceCount)
811 {
812 pMarker = sentence;
813 lengthRemaining = bufSize;
814
815 length = snprintf(pMarker, lengthRemaining, "$%sGSV,%d,%d,%02d",
816 talker, sentenceCount, sentenceNumber, svCount);
817
818 if (length < 0 || length >= lengthRemaining)
819 {
820 LOC_LOGE("NMEA Error in string formatting");
821 return;
822 }
823 pMarker += length;
824 lengthRemaining -= length;
825
826 for (int i=0; (svNumber <= svNotify.count) && (i < 4); svNumber++)
827 {
828 GnssSignalTypeMask signalType = svNotify.gnssSvs[svNumber-1].gnssSignalTypeMask;
829 if (0 == signalType) {
830 // If no signal type in report, it means default L1,G1,E1,B1I
831 switch (svNotify.gnssSvs[svNumber - 1].type)
832 {
833 case GNSS_SV_TYPE_GPS:
834 signalType = GNSS_SIGNAL_GPS_L1CA;
835 break;
836 case GNSS_SV_TYPE_GLONASS:
837 signalType = GNSS_SIGNAL_GLONASS_G1;
838 break;
839 case GNSS_SV_TYPE_GALILEO:
840 signalType = GNSS_SIGNAL_GALILEO_E1;
841 break;
842 case GNSS_SV_TYPE_QZSS:
843 signalType = GNSS_SIGNAL_QZSS_L1CA;
844 break;
845 case GNSS_SV_TYPE_BEIDOU:
846 signalType = GNSS_SIGNAL_BEIDOU_B1I;
847 break;
848 case GNSS_SV_TYPE_SBAS:
849 signalType = GNSS_SIGNAL_SBAS_L1;
850 break;
851 case GNSS_SV_TYPE_NAVIC:
852 signalType = GNSS_SIGNAL_NAVIC_L5;
853 break;
854 default:
855 LOC_LOGE("NMEA Error unknow constellation type: %d",
856 svNotify.gnssSvs[svNumber - 1].type);
857 continue;
858 }
859 }
860
861 if ((sv_meta_p->svTypeMask & (1 << svNotify.gnssSvs[svNumber - 1].type)) &&
862 sv_meta_p->signalId == convert_signalType_to_signalId(signalType))
863 {
864 if (GNSS_SV_TYPE_SBAS == svNotify.gnssSvs[svNumber - 1].type) {
865 svIdOffset = SBAS_SV_ID_OFFSET;
866 }
867 if (GNSS_SV_TYPE_GLONASS == svNotify.gnssSvs[svNumber - 1].type &&
868 GLO_SV_PRN_SLOT_UNKNOWN == svNotify.gnssSvs[svNumber - 1].svId) {
869 length = snprintf(pMarker, lengthRemaining, ",,%02d,%03d,",
870 (int)(0.5 + svNotify.gnssSvs[svNumber - 1].elevation), //float to int
871 (int)(0.5 + svNotify.gnssSvs[svNumber - 1].azimuth)); //float to int
872 } else {
873 length = snprintf(pMarker, lengthRemaining, ",%02d,%02d,%03d,",
874 svNotify.gnssSvs[svNumber - 1].svId - svIdOffset,
875 (int)(0.5 + svNotify.gnssSvs[svNumber - 1].elevation), //float to int
876 (int)(0.5 + svNotify.gnssSvs[svNumber - 1].azimuth)); //float to int
877 }
878 if (length < 0 || length >= lengthRemaining)
879 {
880 LOC_LOGE("NMEA Error in string formatting");
881 return;
882 }
883 pMarker += length;
884 lengthRemaining -= length;
885
886 if (svNotify.gnssSvs[svNumber - 1].cN0Dbhz > 0)
887 {
888 length = snprintf(pMarker, lengthRemaining,"%02d",
889 (int)(0.5 + svNotify.gnssSvs[svNumber - 1].cN0Dbhz)); //float to int
890
891 if (length < 0 || length >= lengthRemaining)
892 {
893 LOC_LOGE("NMEA Error in string formatting");
894 return;
895 }
896 pMarker += length;
897 lengthRemaining -= length;
898 }
899
900 i++;
901 }
902
903 }
904
905 // append signalId
906 length = snprintf(pMarker, lengthRemaining,",%X",sv_meta_p->signalId);
907 pMarker += length;
908 lengthRemaining -= length;
909
910 length = loc_nmea_put_checksum(sentence, bufSize, false);
911 nmeaArraystr.push_back(sentence);
912 sentenceNumber++;
913
914 } //while
915 }
916
917 /*===========================================================================
918 FUNCTION loc_nmea_generate_DTM
919
920 DESCRIPTION
921 Generate NMEA DTM sentences generated based on position report
922
923 DEPENDENCIES
924 NONE
925
926 RETURN VALUE
927 NONE
928
929 SIDE EFFECTS
930 N/A
931
932 ===========================================================================*/
loc_nmea_generate_DTM(const LocLla & ref_lla,const LocLla & local_lla,char * talker,char * sentence,int bufSize)933 static void loc_nmea_generate_DTM(const LocLla &ref_lla,
934 const LocLla &local_lla,
935 char *talker,
936 char *sentence,
937 int bufSize)
938 {
939 char* pMarker = sentence;
940 int lengthRemaining = bufSize;
941 int length = 0;
942 int datum_type;
943 char ref_datum[4] = {'W', '8', '4', '\0'};
944 char local_datum[4] = {0};
945 double lla_offset[3] = {0};
946 char latHem, longHem;
947 double latMins, longMins;
948
949
950
951 datum_type = loc_get_datum_type();
952 switch (datum_type) {
953 case LOC_GNSS_DATUM_WGS84:
954 local_datum[0] = 'W';
955 local_datum[1] = '8';
956 local_datum[2] = '4';
957 break;
958 case LOC_GNSS_DATUM_PZ90:
959 local_datum[0] = 'P';
960 local_datum[1] = '9';
961 local_datum[2] = '0';
962 break;
963 default:
964 break;
965 }
966 length = snprintf(pMarker , lengthRemaining , "$%sDTM,%s,," , talker, local_datum);
967 if (length < 0 || length >= lengthRemaining) {
968 LOC_LOGE("NMEA Error in string formatting");
969 return;
970 }
971 pMarker += length;
972 lengthRemaining -= length;
973
974 lla_offset[0] = local_lla.lat - ref_lla.lat;
975 lla_offset[1] = fmod(local_lla.lon - ref_lla.lon, 360.0);
976 if (lla_offset[1] < -180.0) {
977 lla_offset[1] += 360.0;
978 } else if ( lla_offset[1] > 180.0) {
979 lla_offset[1] -= 360.0;
980 }
981 lla_offset[2] = local_lla.alt - ref_lla.alt;
982 if (lla_offset[0] >= 0.0) {
983 latHem = 'N';
984 } else {
985 latHem = 'S';
986 lla_offset[0] *= -1.0;
987 }
988 latMins = fmod(lla_offset[0] * 60.0, 60.0);
989 if (lla_offset[1] < 0.0) {
990 longHem = 'W';
991 lla_offset[1] *= -1.0;
992 }else {
993 longHem = 'E';
994 }
995 longMins = fmod(lla_offset[1] * 60.0, 60.0);
996 length = snprintf(pMarker, lengthRemaining, "%02d%09.6lf,%c,%03d%09.6lf,%c,%.3lf,",
997 (uint8_t)floor(lla_offset[0]), latMins, latHem,
998 (uint8_t)floor(lla_offset[1]), longMins, longHem, lla_offset[2]);
999 if (length < 0 || length >= lengthRemaining) {
1000 LOC_LOGE("NMEA Error in string formatting");
1001 return;
1002 }
1003 pMarker += length;
1004 lengthRemaining -= length;
1005 length = snprintf(pMarker , lengthRemaining , "%s" , ref_datum);
1006 if (length < 0 || length >= lengthRemaining) {
1007 LOC_LOGE("NMEA Error in string formatting");
1008 return;
1009 }
1010 pMarker += length;
1011 lengthRemaining -= length;
1012
1013 length = loc_nmea_put_checksum(sentence, bufSize, false);
1014 }
1015
1016 /*===========================================================================
1017 FUNCTION get_utctime_with_leapsecond_transition
1018
1019 DESCRIPTION
1020 This function returns true if the position report is generated during
1021 leap second transition period. If not, then the utc timestamp returned
1022 will be set to the timestamp in the position report. If it is,
1023 then the utc timestamp returned will need to take into account
1024 of the leap second transition so that proper calendar year/month/date
1025 can be calculated from the returned utc timestamp.
1026
1027 DEPENDENCIES
1028 NONE
1029
1030 RETURN VALUE
1031 true: position report is generated in leap second transition period.
1032
1033 SIDE EFFECTS
1034 N/A
1035
1036 ===========================================================================*/
get_utctime_with_leapsecond_transition(const UlpLocation & location,const GpsLocationExtended & locationExtended,const LocationSystemInfo & systemInfo,LocGpsUtcTime & utcPosTimestamp)1037 static bool get_utctime_with_leapsecond_transition(
1038 const UlpLocation &location,
1039 const GpsLocationExtended &locationExtended,
1040 const LocationSystemInfo &systemInfo,
1041 LocGpsUtcTime &utcPosTimestamp)
1042 {
1043 bool inTransition = false;
1044
1045 // position report is not generated during leap second transition,
1046 // we can use the UTC timestamp from position report as is
1047 utcPosTimestamp = location.gpsLocation.timestamp;
1048
1049 // Check whether we are in leap second transition.
1050 // If so, per NMEA spec, we need to display the extra second in format of 23:59:60
1051 // with year/month/date not getting advanced.
1052 if ((locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_GPS_TIME) &&
1053 ((systemInfo.systemInfoMask & LOCATION_SYS_INFO_LEAP_SECOND) &&
1054 (systemInfo.leapSecondSysInfo.leapSecondInfoMask &
1055 LEAP_SECOND_SYS_INFO_LEAP_SECOND_CHANGE_BIT))) {
1056
1057 const LeapSecondChangeInfo &leapSecondChangeInfo =
1058 systemInfo.leapSecondSysInfo.leapSecondChangeInfo;
1059 const GnssSystemTimeStructType &gpsTimestampLsChange =
1060 leapSecondChangeInfo.gpsTimestampLsChange;
1061
1062 uint64_t gpsTimeLsChange = gpsTimestampLsChange.systemWeek * MSEC_IN_ONE_WEEK +
1063 gpsTimestampLsChange.systemMsec;
1064 uint64_t gpsTimePosReport = locationExtended.gpsTime.gpsWeek * MSEC_IN_ONE_WEEK +
1065 locationExtended.gpsTime.gpsTimeOfWeekMs;
1066 // we are only dealing with positive leap second change, as negative
1067 // leap second change has never occurred and should not occur in future
1068 if (leapSecondChangeInfo.leapSecondsAfterChange >
1069 leapSecondChangeInfo.leapSecondsBeforeChange) {
1070 // leap second adjustment is always 1 second at a time. It can happen
1071 // every quarter end and up to four times per year.
1072 if ((gpsTimePosReport >= gpsTimeLsChange) &&
1073 (gpsTimePosReport < (gpsTimeLsChange + 1000))) {
1074 inTransition = true;
1075 utcPosTimestamp = gpsTimeLsChange + UTC_GPS_OFFSET_MSECS -
1076 leapSecondChangeInfo.leapSecondsBeforeChange * 1000;
1077
1078 // we substract 1000 milli-seconds from UTC timestmap in order to calculate the
1079 // proper year, month and date during leap second transtion.
1080 // Let us give an example, assuming leap second transition is scheduled on 2019,
1081 // Dec 31st mid night. When leap second transition is happening,
1082 // instead of outputting the time as 2020, Jan, 1st, 00 hour, 00 min, and 00 sec.
1083 // The time need to be displayed as 2019, Dec, 31st, 23 hour, 59 min and 60 sec.
1084 utcPosTimestamp -= 1000;
1085 }
1086 }
1087 }
1088 return inTransition;
1089 }
1090
1091 /*===========================================================================
1092 FUNCTION loc_nmea_get_fix_quality
1093
1094 DESCRIPTION
1095 This function obtains the fix quality for GGA sentence, mode indicator
1096 for RMC and VTG sentence based on nav solution mask and tech mask in
1097 the postion report.
1098
1099 DEPENDENCIES
1100 NONE
1101
1102 Output parameter
1103 ggaGpsQuality: gps quality field in GGA sentence
1104 rmcModeIndicator: mode indicator field in RMC sentence
1105 vtgModeIndicator: mode indicator field in VTG sentence
1106
1107 SIDE EFFECTS
1108 N/A
1109
1110 ===========================================================================*/
loc_nmea_get_fix_quality(const UlpLocation & location,const GpsLocationExtended & locationExtended,bool custom_gga_fix_quality,char ggaGpsQuality[3],char & rmcModeIndicator,char & vtgModeIndicator,char gnsModeIndicator[7])1111 static void loc_nmea_get_fix_quality(const UlpLocation & location,
1112 const GpsLocationExtended & locationExtended,
1113 bool custom_gga_fix_quality,
1114 char ggaGpsQuality[3],
1115 char & rmcModeIndicator,
1116 char & vtgModeIndicator,
1117 char gnsModeIndicator[7]) {
1118
1119 ggaGpsQuality[0] = '0'; // 0 means no fix
1120 rmcModeIndicator = 'N'; // N means no fix
1121 vtgModeIndicator = 'N'; // N means no fix
1122 memset(gnsModeIndicator, 'N', 6); // N means no fix
1123 gnsModeIndicator[6] = '\0';
1124 do {
1125 if (!(location.gpsLocation.flags & LOC_GPS_LOCATION_HAS_LAT_LONG)){
1126 break;
1127 }
1128 // NOTE: Order of the check is important
1129 if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_NAV_SOLUTION_MASK) {
1130 if (LOC_NAV_MASK_PPP_CORRECTION & locationExtended.navSolutionMask) {
1131 ggaGpsQuality[0] = '2'; // 2 means DGPS fix
1132 rmcModeIndicator = 'P'; // P means precise
1133 vtgModeIndicator = 'P'; // P means precise
1134 if (locationExtended.gnss_sv_used_ids.gps_sv_used_ids_mask ? 1 : 0)
1135 gnsModeIndicator[0] = 'P'; // P means precise
1136 if (locationExtended.gnss_sv_used_ids.glo_sv_used_ids_mask ? 1 : 0)
1137 gnsModeIndicator[1] = 'P'; // P means precise
1138 if (locationExtended.gnss_sv_used_ids.gal_sv_used_ids_mask ? 1 : 0)
1139 gnsModeIndicator[2] = 'P'; // P means precise
1140 if (locationExtended.gnss_sv_used_ids.bds_sv_used_ids_mask ? 1 : 0)
1141 gnsModeIndicator[3] = 'P'; // P means precise
1142 if (locationExtended.gnss_sv_used_ids.qzss_sv_used_ids_mask ? 1 : 0)
1143 gnsModeIndicator[4] = 'P'; // P means precise
1144 if (locationExtended.gnss_sv_used_ids.navic_sv_used_ids_mask ? 1 : 0)
1145 gnsModeIndicator[5] = 'P'; // P means precise
1146 break;
1147 } else if (LOC_NAV_MASK_RTK_FIXED_CORRECTION & locationExtended.navSolutionMask){
1148 ggaGpsQuality[0] = '4'; // 4 means RTK Fixed fix
1149 rmcModeIndicator = 'R'; // use R (RTK fixed)
1150 vtgModeIndicator = 'D'; // use D (differential) as
1151 // no RTK fixed defined for VTG in NMEA 183 spec
1152 if (locationExtended.gnss_sv_used_ids.gps_sv_used_ids_mask ? 1 : 0)
1153 gnsModeIndicator[0] = 'R'; // R means RTK fixed
1154 if (locationExtended.gnss_sv_used_ids.glo_sv_used_ids_mask ? 1 : 0)
1155 gnsModeIndicator[1] = 'R'; // R means RTK fixed
1156 if (locationExtended.gnss_sv_used_ids.gal_sv_used_ids_mask ? 1 : 0)
1157 gnsModeIndicator[2] = 'R'; // R means RTK fixed
1158 if (locationExtended.gnss_sv_used_ids.bds_sv_used_ids_mask ? 1 : 0)
1159 gnsModeIndicator[3] = 'R'; // R means RTK fixed
1160 if (locationExtended.gnss_sv_used_ids.qzss_sv_used_ids_mask ? 1 : 0)
1161 gnsModeIndicator[4] = 'R'; // R means RTK fixed
1162 if (locationExtended.gnss_sv_used_ids.navic_sv_used_ids_mask ? 1 : 0)
1163 gnsModeIndicator[5] = 'R'; // R means RTK fixed
1164 break;
1165 } else if (LOC_NAV_MASK_RTK_CORRECTION & locationExtended.navSolutionMask){
1166 ggaGpsQuality[0] = '5'; // 5 means RTK float fix
1167 rmcModeIndicator = 'F'; // F means RTK float fix
1168 vtgModeIndicator = 'D'; // use D (differential) as
1169 // no RTK float defined for VTG in NMEA 183 spec
1170 if (locationExtended.gnss_sv_used_ids.gps_sv_used_ids_mask ? 1 : 0)
1171 gnsModeIndicator[0] = 'F'; // F means RTK float fix
1172 if (locationExtended.gnss_sv_used_ids.glo_sv_used_ids_mask ? 1 : 0)
1173 gnsModeIndicator[1] = 'F'; // F means RTK float fix
1174 if (locationExtended.gnss_sv_used_ids.gal_sv_used_ids_mask ? 1 : 0)
1175 gnsModeIndicator[2] = 'F'; // F means RTK float fix
1176 if (locationExtended.gnss_sv_used_ids.bds_sv_used_ids_mask ? 1 : 0)
1177 gnsModeIndicator[3] = 'F'; // F means RTK float fix
1178 if (locationExtended.gnss_sv_used_ids.qzss_sv_used_ids_mask ? 1 : 0)
1179 gnsModeIndicator[4] = 'F'; // F means RTK float fix
1180 if (locationExtended.gnss_sv_used_ids.navic_sv_used_ids_mask ? 1 : 0)
1181 gnsModeIndicator[5] = 'F'; // F means RTK float fix
1182 break;
1183 } else if (LOC_NAV_MASK_DGNSS_CORRECTION & locationExtended.navSolutionMask){
1184 ggaGpsQuality[0] = '2'; // 2 means DGPS fix
1185 rmcModeIndicator = 'D'; // D means differential
1186 vtgModeIndicator = 'D'; // D means differential
1187 if (locationExtended.gnss_sv_used_ids.gps_sv_used_ids_mask ? 1 : 0)
1188 gnsModeIndicator[0] = 'D'; // D means differential
1189 if (locationExtended.gnss_sv_used_ids.glo_sv_used_ids_mask ? 1 : 0)
1190 gnsModeIndicator[1] = 'D'; // D means differential
1191 if (locationExtended.gnss_sv_used_ids.gal_sv_used_ids_mask ? 1 : 0)
1192 gnsModeIndicator[2] = 'D'; // D means differential
1193 if (locationExtended.gnss_sv_used_ids.bds_sv_used_ids_mask ? 1 : 0)
1194 gnsModeIndicator[3] = 'D'; // D means differential
1195 if (locationExtended.gnss_sv_used_ids.qzss_sv_used_ids_mask ? 1 : 0)
1196 gnsModeIndicator[4] = 'D'; // D means differential
1197 if (locationExtended.gnss_sv_used_ids.navic_sv_used_ids_mask ? 1 : 0)
1198 gnsModeIndicator[5] = 'D'; // D means differential
1199 break;
1200 } else if (LOC_NAV_MASK_SBAS_CORRECTION_IONO & locationExtended.navSolutionMask){
1201 ggaGpsQuality[0] = '2'; // 2 means DGPS fix
1202 rmcModeIndicator = 'D'; // D means differential
1203 vtgModeIndicator = 'D'; // D means differential
1204 if (locationExtended.gnss_sv_used_ids.gps_sv_used_ids_mask ? 1 : 0)
1205 gnsModeIndicator[0] = 'D'; // D means differential
1206 if (locationExtended.gnss_sv_used_ids.glo_sv_used_ids_mask ? 1 : 0)
1207 gnsModeIndicator[1] = 'D'; // D means differential
1208 if (locationExtended.gnss_sv_used_ids.gal_sv_used_ids_mask ? 1 : 0)
1209 gnsModeIndicator[2] = 'D'; // D means differential
1210 if (locationExtended.gnss_sv_used_ids.bds_sv_used_ids_mask ? 1 : 0)
1211 gnsModeIndicator[3] = 'D'; // D means differential
1212 if (locationExtended.gnss_sv_used_ids.qzss_sv_used_ids_mask ? 1 : 0)
1213 gnsModeIndicator[4] = 'D'; // D means differential
1214 if (locationExtended.gnss_sv_used_ids.navic_sv_used_ids_mask ? 1 : 0)
1215 gnsModeIndicator[5] = 'D'; // D means differential
1216 break;
1217 }
1218 }
1219 // NOTE: Order of the check is important
1220 if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_POS_TECH_MASK) {
1221 if (LOC_POS_TECH_MASK_SATELLITE & locationExtended.tech_mask){
1222 ggaGpsQuality[0] = '1'; // 1 means GPS
1223 rmcModeIndicator = 'A'; // A means autonomous
1224 vtgModeIndicator = 'A'; // A means autonomous
1225 if (locationExtended.gnss_sv_used_ids.gps_sv_used_ids_mask ? 1 : 0)
1226 gnsModeIndicator[0] = 'A'; // A means autonomous
1227 if (locationExtended.gnss_sv_used_ids.glo_sv_used_ids_mask ? 1 : 0)
1228 gnsModeIndicator[1] = 'A'; // A means autonomous
1229 if (locationExtended.gnss_sv_used_ids.gal_sv_used_ids_mask ? 1 : 0)
1230 gnsModeIndicator[2] = 'A'; // A means autonomous
1231 if (locationExtended.gnss_sv_used_ids.bds_sv_used_ids_mask ? 1 : 0)
1232 gnsModeIndicator[3] = 'A'; // A means autonomous
1233 if (locationExtended.gnss_sv_used_ids.qzss_sv_used_ids_mask ? 1 : 0)
1234 gnsModeIndicator[4] = 'A'; // A means autonomous
1235 if (locationExtended.gnss_sv_used_ids.navic_sv_used_ids_mask ? 1 : 0)
1236 gnsModeIndicator[5] = 'A'; // A means autonomous
1237 break;
1238 } else if (LOC_POS_TECH_MASK_SENSORS & locationExtended.tech_mask){
1239 ggaGpsQuality[0] = '6'; // 6 means estimated (dead reckoning)
1240 rmcModeIndicator = 'E'; // E means estimated (dead reckoning)
1241 vtgModeIndicator = 'E'; // E means estimated (dead reckoning)
1242 memset(gnsModeIndicator, 'E', 6); // E means estimated (dead reckoning)
1243 break;
1244 }
1245 }
1246 } while (0);
1247
1248 do {
1249 // check for customized nmea enabled or not
1250 // with customized GGA quality enabled
1251 // PPP fix w/o sensor: 59, PPP fix w/ sensor: 69
1252 // DGNSS/SBAS correction fix w/o sensor: 2, w/ sensor: 62
1253 // RTK fixed fix w/o sensor: 4, w/ sensor: 64
1254 // RTK float fix w/o sensor: 5, w/ sensor: 65
1255 // SPE fix w/o sensor: 1, and w/ sensor: 61
1256 // Sensor dead reckoning fix: 6
1257 if (true == custom_gga_fix_quality) {
1258 if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_NAV_SOLUTION_MASK) {
1259 // PPP fix w/o sensor: fix quality will now be 59
1260 // PPP fix w sensor: fix quality will now be 69
1261 if (LOC_NAV_MASK_PPP_CORRECTION & locationExtended.navSolutionMask) {
1262 if ((locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_POS_TECH_MASK) &&
1263 (LOC_POS_TECH_MASK_SENSORS & locationExtended.tech_mask)) {
1264 ggaGpsQuality[0] = '6';
1265 ggaGpsQuality[1] = '9';
1266 } else {
1267 ggaGpsQuality[0] = '5';
1268 ggaGpsQuality[1] = '9';
1269 }
1270 break;
1271 }
1272 }
1273
1274 if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_POS_TECH_MASK) {
1275 if (LOC_POS_TECH_MASK_SENSORS & locationExtended.tech_mask){
1276 char ggaQuality_copy = ggaGpsQuality[0];
1277 ggaGpsQuality[0] = '6'; // 6 sensor assisted
1278 // RTK fixed fix w/ sensor: fix quality will now be 64
1279 // RTK float fix w/ sensor: 65
1280 // DGNSS and/or SBAS correction fix and w/ sensor: 62
1281 // GPS fix without correction and w/ sensor: 61
1282 if ((LOC_NAV_MASK_RTK_FIXED_CORRECTION & locationExtended.navSolutionMask)||
1283 (LOC_NAV_MASK_RTK_CORRECTION & locationExtended.navSolutionMask)||
1284 (LOC_NAV_MASK_DGNSS_CORRECTION & locationExtended.navSolutionMask)||
1285 (LOC_NAV_MASK_SBAS_CORRECTION_IONO & locationExtended.navSolutionMask)||
1286 (LOC_POS_TECH_MASK_SATELLITE & locationExtended.tech_mask)) {
1287 ggaGpsQuality[1] = ggaQuality_copy;
1288 break;
1289 }
1290 }
1291 }
1292 }
1293 } while (0);
1294
1295 LOC_LOGv("gps quality: %s, rmc mode indicator: %c, vtg mode indicator: %c",
1296 ggaGpsQuality, rmcModeIndicator, vtgModeIndicator);
1297 }
1298
1299 /*===========================================================================
1300 FUNCTION loc_nmea_generate_pos
1301
1302 DESCRIPTION
1303 Generate NMEA sentences generated based on position report
1304 Currently below sentences are generated within this function:
1305 - $GPGSA : GPS DOP and active SVs
1306 - $GLGSA : GLONASS DOP and active SVs
1307 - $GAGSA : GALILEO DOP and active SVs
1308 - $GNGSA : GNSS DOP and active SVs
1309 - $--VTG : Track made good and ground speed
1310 - $--RMC : Recommended minimum navigation information
1311 - $--GGA : Time, position and fix related data
1312
1313 DEPENDENCIES
1314 NONE
1315
1316 RETURN VALUE
1317 0
1318
1319 SIDE EFFECTS
1320 N/A
1321
1322 ===========================================================================*/
loc_nmea_generate_pos(const UlpLocation & location,const GpsLocationExtended & locationExtended,const LocationSystemInfo & systemInfo,unsigned char generate_nmea,bool custom_gga_fix_quality,std::vector<std::string> & nmeaArraystr,int & indexOfGGA,bool isTagBlockGroupingEnabled)1323 void loc_nmea_generate_pos(const UlpLocation &location,
1324 const GpsLocationExtended &locationExtended,
1325 const LocationSystemInfo &systemInfo,
1326 unsigned char generate_nmea,
1327 bool custom_gga_fix_quality,
1328 std::vector<std::string> &nmeaArraystr,
1329 int& indexOfGGA,
1330 bool isTagBlockGroupingEnabled)
1331 {
1332 ENTRY_LOG();
1333
1334 indexOfGGA = -1;
1335 LocGpsUtcTime utcPosTimestamp = 0;
1336 bool inLsTransition = false;
1337
1338 inLsTransition = get_utctime_with_leapsecond_transition
1339 (location, locationExtended, systemInfo, utcPosTimestamp);
1340
1341 time_t utcTime(utcPosTimestamp/1000);
1342 struct tm result;
1343 tm * pTm = gmtime_r(&utcTime, &result);
1344 if (NULL == pTm) {
1345 LOC_LOGE("gmtime failed");
1346 return;
1347 }
1348
1349 char sentence[NMEA_SENTENCE_MAX_LENGTH] = {0};
1350 char sentence_DTM[NMEA_SENTENCE_MAX_LENGTH] = {0};
1351 char sentence_RMC[NMEA_SENTENCE_MAX_LENGTH] = {0};
1352 char sentence_GNS[NMEA_SENTENCE_MAX_LENGTH] = {0};
1353 char sentence_GGA[NMEA_SENTENCE_MAX_LENGTH] = {0};
1354 char* pMarker = sentence;
1355 int lengthRemaining = sizeof(sentence);
1356 int length = 0;
1357 int utcYear = pTm->tm_year % 100; // 2 digit year
1358 int utcMonth = pTm->tm_mon + 1; // tm_mon starts at zero
1359 int utcDay = pTm->tm_mday;
1360 int utcHours = pTm->tm_hour;
1361 int utcMinutes = pTm->tm_min;
1362 int utcSeconds = pTm->tm_sec;
1363 int utcMSeconds = (location.gpsLocation.timestamp)%1000;
1364 int datum_type = loc_get_datum_type();
1365 LocEcef ecef_w84;
1366 LocEcef ecef_p90;
1367 LocLla lla_w84;
1368 LocLla lla_p90;
1369 LocLla ref_lla;
1370 LocLla local_lla;
1371
1372 if (inLsTransition) {
1373 // During leap second transition, we need to display the extra
1374 // leap second of hour, minute, second as (23:59:60)
1375 utcHours = 23;
1376 utcMinutes = 59;
1377 utcSeconds = 60;
1378 // As UTC timestamp is freezing during leap second transition,
1379 // retrieve milli-seconds portion from GPS timestamp.
1380 utcMSeconds = locationExtended.gpsTime.gpsTimeOfWeekMs % 1000;
1381 }
1382
1383 loc_sv_cache_info sv_cache_info = {};
1384
1385 if (GPS_LOCATION_EXTENDED_HAS_GNSS_SV_USED_DATA & locationExtended.flags) {
1386 sv_cache_info.gps_used_mask =
1387 locationExtended.gnss_sv_used_ids.gps_sv_used_ids_mask;
1388 sv_cache_info.glo_used_mask =
1389 locationExtended.gnss_sv_used_ids.glo_sv_used_ids_mask;
1390 sv_cache_info.gal_used_mask =
1391 locationExtended.gnss_sv_used_ids.gal_sv_used_ids_mask;
1392 sv_cache_info.bds_used_mask =
1393 locationExtended.gnss_sv_used_ids.bds_sv_used_ids_mask;
1394 sv_cache_info.qzss_used_mask =
1395 locationExtended.gnss_sv_used_ids.qzss_sv_used_ids_mask;
1396 sv_cache_info.navic_used_mask =
1397 locationExtended.gnss_sv_used_ids.navic_sv_used_ids_mask;
1398 }
1399
1400 if (generate_nmea) {
1401 char talker[3] = {'G', 'P', '\0'};
1402 uint32_t svUsedCount = 0;
1403 uint32_t count = 0;
1404 loc_nmea_sv_meta sv_meta;
1405 // -------------------
1406 // ---$GPGSA/$GNGSA---
1407 // -------------------
1408
1409 count = loc_nmea_generate_GSA(locationExtended, sentence, sizeof(sentence),
1410 loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_GPS,
1411 GNSS_SIGNAL_GPS_L1CA, true), nmeaArraystr, isTagBlockGroupingEnabled);
1412 if (count > 0)
1413 {
1414 svUsedCount += count;
1415 talker[0] = sv_meta.talker[0];
1416 talker[1] = sv_meta.talker[1];
1417 }
1418
1419 // -------------------
1420 // ---$GLGSA/$GNGSA---
1421 // -------------------
1422
1423 count = loc_nmea_generate_GSA(locationExtended, sentence, sizeof(sentence),
1424 loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_GLONASS,
1425 GNSS_SIGNAL_GLONASS_G1, true), nmeaArraystr, isTagBlockGroupingEnabled);
1426 if (count > 0)
1427 {
1428 svUsedCount += count;
1429 talker[0] = sv_meta.talker[0];
1430 talker[1] = sv_meta.talker[1];
1431 }
1432
1433 // -------------------
1434 // ---$GAGSA/$GNGSA---
1435 // -------------------
1436
1437 count = loc_nmea_generate_GSA(locationExtended, sentence, sizeof(sentence),
1438 loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_GALILEO,
1439 GNSS_SIGNAL_GALILEO_E1, true), nmeaArraystr, isTagBlockGroupingEnabled);
1440 if (count > 0)
1441 {
1442 svUsedCount += count;
1443 talker[0] = sv_meta.talker[0];
1444 talker[1] = sv_meta.talker[1];
1445 }
1446
1447 // ----------------------------
1448 // ---$GBGSA/$GNGSA (BEIDOU)---
1449 // ----------------------------
1450 count = loc_nmea_generate_GSA(locationExtended, sentence, sizeof(sentence),
1451 loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_BEIDOU,
1452 GNSS_SIGNAL_BEIDOU_B1I, true), nmeaArraystr, isTagBlockGroupingEnabled);
1453 if (count > 0)
1454 {
1455 svUsedCount += count;
1456 talker[0] = sv_meta.talker[0];
1457 talker[1] = sv_meta.talker[1];
1458 }
1459
1460 // --------------------------
1461 // ---$GQGSA/$GNGSA (QZSS)---
1462 // --------------------------
1463
1464 count = loc_nmea_generate_GSA(locationExtended, sentence, sizeof(sentence),
1465 loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_QZSS,
1466 GNSS_SIGNAL_QZSS_L1CA, true), nmeaArraystr, isTagBlockGroupingEnabled);
1467 if (count > 0)
1468 {
1469 svUsedCount += count;
1470 talker[0] = sv_meta.talker[0];
1471 talker[1] = sv_meta.talker[1];
1472 }
1473
1474 // if svUsedCount is 0, it means we do not generate any GSA sentence yet.
1475 // in this case, generate an empty GSA sentence
1476 if (svUsedCount == 0) {
1477 strlcpy(sentence, "$GPGSA,A,1,,,,,,,,,,,,,,,,", sizeof(sentence));
1478 length = loc_nmea_put_checksum(sentence, sizeof(sentence), false);
1479 nmeaArraystr.push_back(sentence);
1480 }
1481
1482 char ggaGpsQuality[3] = {'0', '\0', '\0'};
1483 char rmcModeIndicator = 'N';
1484 char vtgModeIndicator = 'N';
1485 char gnsModeIndicator[7] = {'N', 'N', 'N', 'N', 'N', 'N', '\0'};
1486 loc_nmea_get_fix_quality(location, locationExtended, custom_gga_fix_quality,
1487 ggaGpsQuality, rmcModeIndicator, vtgModeIndicator, gnsModeIndicator);
1488
1489 // -------------------
1490 // ------$--VTG-------
1491 // -------------------
1492
1493 pMarker = sentence;
1494 lengthRemaining = sizeof(sentence);
1495
1496 if (location.gpsLocation.flags & LOC_GPS_LOCATION_HAS_BEARING)
1497 {
1498 float magTrack = location.gpsLocation.bearing;
1499 if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_MAG_DEV)
1500 {
1501 magTrack = location.gpsLocation.bearing - locationExtended.magneticDeviation;
1502 if (magTrack < 0.0)
1503 magTrack += 360.0;
1504 else if (magTrack > 360.0)
1505 magTrack -= 360.0;
1506 }
1507
1508 length = snprintf(pMarker, lengthRemaining, "$%sVTG,%.1lf,T,%.1lf,M,", talker, location.gpsLocation.bearing, magTrack);
1509 }
1510 else
1511 {
1512 length = snprintf(pMarker, lengthRemaining, "$%sVTG,,T,,M,", talker);
1513 }
1514
1515 if (length < 0 || length >= lengthRemaining)
1516 {
1517 LOC_LOGE("NMEA Error in string formatting");
1518 return;
1519 }
1520 pMarker += length;
1521 lengthRemaining -= length;
1522
1523 if (location.gpsLocation.flags & LOC_GPS_LOCATION_HAS_SPEED)
1524 {
1525 float speedKnots = location.gpsLocation.speed * (3600.0/1852.0);
1526 float speedKmPerHour = location.gpsLocation.speed * 3.6;
1527
1528 length = snprintf(pMarker, lengthRemaining, "%.1lf,N,%.1lf,K,", speedKnots, speedKmPerHour);
1529 }
1530 else
1531 {
1532 length = snprintf(pMarker, lengthRemaining, ",N,,K,");
1533 }
1534
1535 if (length < 0 || length >= lengthRemaining)
1536 {
1537 LOC_LOGE("NMEA Error in string formatting");
1538 return;
1539 }
1540 pMarker += length;
1541 lengthRemaining -= length;
1542
1543 length = snprintf(pMarker, lengthRemaining, "%c", vtgModeIndicator);
1544
1545 length = loc_nmea_put_checksum(sentence, sizeof(sentence), false);
1546 nmeaArraystr.push_back(sentence);
1547
1548 memset(&ecef_w84, 0, sizeof(ecef_w84));
1549 memset(&ecef_p90, 0, sizeof(ecef_p90));
1550 memset(&lla_w84, 0, sizeof(lla_w84));
1551 memset(&lla_p90, 0, sizeof(lla_p90));
1552 memset(&ref_lla, 0, sizeof(ref_lla));
1553 memset(&local_lla, 0, sizeof(local_lla));
1554 lla_w84.lat = location.gpsLocation.latitude / 180.0 * M_PI;
1555 lla_w84.lon = location.gpsLocation.longitude / 180.0 * M_PI;
1556 lla_w84.alt = location.gpsLocation.altitude;
1557
1558 convert_Lla_to_Ecef(lla_w84, ecef_w84);
1559 convert_WGS84_to_PZ90(ecef_w84, ecef_p90);
1560 convert_Ecef_to_Lla(ecef_p90, lla_p90);
1561
1562 ref_lla.lat = location.gpsLocation.latitude;
1563 ref_lla.lon = location.gpsLocation.longitude;
1564 ref_lla.alt = location.gpsLocation.altitude;
1565
1566 switch (datum_type) {
1567 case LOC_GNSS_DATUM_WGS84:
1568 local_lla.lat = location.gpsLocation.latitude;
1569 local_lla.lon = location.gpsLocation.longitude;
1570 local_lla.alt = location.gpsLocation.altitude;
1571 break;
1572 case LOC_GNSS_DATUM_PZ90:
1573 local_lla.lat = lla_p90.lat / M_PI * 180.0;
1574 local_lla.lon = lla_p90.lon / M_PI * 180.0;
1575 local_lla.alt = lla_p90.alt;
1576 break;
1577 default:
1578 break;
1579 }
1580
1581 // -------------------
1582 // ------$--DTM-------
1583 // -------------------
1584 loc_nmea_generate_DTM(ref_lla, local_lla, talker, sentence_DTM, sizeof(sentence_DTM));
1585
1586 // -------------------
1587 // ------$--RMC-------
1588 // -------------------
1589
1590 pMarker = sentence_RMC;
1591 lengthRemaining = sizeof(sentence_RMC);
1592
1593 bool validFix = ((0 != sv_cache_info.gps_used_mask) ||
1594 (0 != sv_cache_info.glo_used_mask) ||
1595 (0 != sv_cache_info.gal_used_mask) ||
1596 (0 != sv_cache_info.qzss_used_mask) ||
1597 (0 != sv_cache_info.bds_used_mask));
1598
1599 if (validFix) {
1600 length = snprintf(pMarker, lengthRemaining, "$%sRMC,%02d%02d%02d.%02d,A,",
1601 talker, utcHours, utcMinutes, utcSeconds, utcMSeconds/10);
1602 } else {
1603 length = snprintf(pMarker, lengthRemaining, "$%sRMC,%02d%02d%02d.%02d,V,",
1604 talker, utcHours, utcMinutes, utcSeconds, utcMSeconds/10);
1605 }
1606
1607 if (length < 0 || length >= lengthRemaining)
1608 {
1609 LOC_LOGE("NMEA Error in string formatting");
1610 return;
1611 }
1612 pMarker += length;
1613 lengthRemaining -= length;
1614
1615 if (location.gpsLocation.flags & LOC_GPS_LOCATION_HAS_LAT_LONG)
1616 {
1617 double latitude = ref_lla.lat;
1618 double longitude = ref_lla.lon;
1619 char latHemisphere;
1620 char lonHemisphere;
1621 double latMinutes;
1622 double lonMinutes;
1623
1624 if (latitude > 0)
1625 {
1626 latHemisphere = 'N';
1627 }
1628 else
1629 {
1630 latHemisphere = 'S';
1631 latitude *= -1.0;
1632 }
1633
1634 if (longitude < 0)
1635 {
1636 lonHemisphere = 'W';
1637 longitude *= -1.0;
1638 }
1639 else
1640 {
1641 lonHemisphere = 'E';
1642 }
1643
1644 latMinutes = fmod(latitude * 60.0 , 60.0);
1645 lonMinutes = fmod(longitude * 60.0 , 60.0);
1646
1647 length = snprintf(pMarker, lengthRemaining, "%02d%09.6lf,%c,%03d%09.6lf,%c,",
1648 (uint8_t)floor(latitude), latMinutes, latHemisphere,
1649 (uint8_t)floor(longitude),lonMinutes, lonHemisphere);
1650 }
1651 else
1652 {
1653 length = snprintf(pMarker, lengthRemaining,",,,,");
1654 }
1655
1656 if (length < 0 || length >= lengthRemaining)
1657 {
1658 LOC_LOGE("NMEA Error in string formatting");
1659 return;
1660 }
1661 pMarker += length;
1662 lengthRemaining -= length;
1663
1664 if (location.gpsLocation.flags & LOC_GPS_LOCATION_HAS_SPEED)
1665 {
1666 float speedKnots = location.gpsLocation.speed * (3600.0/1852.0);
1667 length = snprintf(pMarker, lengthRemaining, "%.1lf,", speedKnots);
1668 }
1669 else
1670 {
1671 length = snprintf(pMarker, lengthRemaining, ",");
1672 }
1673
1674 if (length < 0 || length >= lengthRemaining)
1675 {
1676 LOC_LOGE("NMEA Error in string formatting");
1677 return;
1678 }
1679 pMarker += length;
1680 lengthRemaining -= length;
1681
1682 if (location.gpsLocation.flags & LOC_GPS_LOCATION_HAS_BEARING)
1683 {
1684 length = snprintf(pMarker, lengthRemaining, "%.1lf,", location.gpsLocation.bearing);
1685 }
1686 else
1687 {
1688 length = snprintf(pMarker, lengthRemaining, ",");
1689 }
1690
1691 if (length < 0 || length >= lengthRemaining)
1692 {
1693 LOC_LOGE("NMEA Error in string formatting");
1694 return;
1695 }
1696 pMarker += length;
1697 lengthRemaining -= length;
1698
1699 length = snprintf(pMarker, lengthRemaining, "%2.2d%2.2d%2.2d,",
1700 utcDay, utcMonth, utcYear);
1701
1702 if (length < 0 || length >= lengthRemaining)
1703 {
1704 LOC_LOGE("NMEA Error in string formatting");
1705 return;
1706 }
1707 pMarker += length;
1708 lengthRemaining -= length;
1709
1710 if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_MAG_DEV)
1711 {
1712 float magneticVariation = locationExtended.magneticDeviation;
1713 char direction;
1714 if (magneticVariation < 0.0)
1715 {
1716 direction = 'W';
1717 magneticVariation *= -1.0;
1718 }
1719 else
1720 {
1721 direction = 'E';
1722 }
1723
1724 length = snprintf(pMarker, lengthRemaining, "%.1lf,%c,",
1725 magneticVariation, direction);
1726 }
1727 else
1728 {
1729 length = snprintf(pMarker, lengthRemaining, ",,");
1730 }
1731
1732 if (length < 0 || length >= lengthRemaining)
1733 {
1734 LOC_LOGE("NMEA Error in string formatting");
1735 return;
1736 }
1737 pMarker += length;
1738 lengthRemaining -= length;
1739
1740 length = snprintf(pMarker, lengthRemaining, "%c", rmcModeIndicator);
1741 pMarker += length;
1742 lengthRemaining -= length;
1743
1744 // hardcode Navigation Status field to 'V'
1745 length = snprintf(pMarker, lengthRemaining, ",%c", 'V');
1746
1747 length = loc_nmea_put_checksum(sentence_RMC, sizeof(sentence_RMC), false);
1748
1749 // -------------------
1750 // ------$--GNS-------
1751 // -------------------
1752
1753 pMarker = sentence_GNS;
1754 lengthRemaining = sizeof(sentence_GNS);
1755
1756 length = snprintf(pMarker, lengthRemaining, "$%sGNS,%02d%02d%02d.%02d," ,
1757 talker, utcHours, utcMinutes, utcSeconds, utcMSeconds/10);
1758
1759 if (length < 0 || length >= lengthRemaining)
1760 {
1761 LOC_LOGE("NMEA Error in string formatting");
1762 return;
1763 }
1764 pMarker += length;
1765 lengthRemaining -= length;
1766
1767 if (location.gpsLocation.flags & LOC_GPS_LOCATION_HAS_LAT_LONG)
1768 {
1769 double latitude = ref_lla.lat;
1770 double longitude = ref_lla.lon;
1771 char latHemisphere;
1772 char lonHemisphere;
1773 double latMinutes;
1774 double lonMinutes;
1775
1776 if (latitude > 0)
1777 {
1778 latHemisphere = 'N';
1779 }
1780 else
1781 {
1782 latHemisphere = 'S';
1783 latitude *= -1.0;
1784 }
1785
1786 if (longitude < 0)
1787 {
1788 lonHemisphere = 'W';
1789 longitude *= -1.0;
1790 }
1791 else
1792 {
1793 lonHemisphere = 'E';
1794 }
1795
1796 latMinutes = fmod(latitude * 60.0 , 60.0);
1797 lonMinutes = fmod(longitude * 60.0 , 60.0);
1798
1799 length = snprintf(pMarker, lengthRemaining, "%02d%09.6lf,%c,%03d%09.6lf,%c,",
1800 (uint8_t)floor(latitude), latMinutes, latHemisphere,
1801 (uint8_t)floor(longitude),lonMinutes, lonHemisphere);
1802 }
1803 else
1804 {
1805 length = snprintf(pMarker, lengthRemaining,",,,,");
1806 }
1807
1808 if (length < 0 || length >= lengthRemaining)
1809 {
1810 LOC_LOGE("NMEA Error in string formatting");
1811 return;
1812 }
1813 pMarker += length;
1814 lengthRemaining -= length;
1815
1816 length = snprintf(pMarker, lengthRemaining, "%s,", gnsModeIndicator);
1817
1818 pMarker += length;
1819 lengthRemaining -= length;
1820
1821 if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_DOP) {
1822 length = snprintf(pMarker, lengthRemaining, "%02d,%.1f,",
1823 svUsedCount, locationExtended.hdop);
1824 }
1825 else { // no hdop
1826 length = snprintf(pMarker, lengthRemaining, "%02d,,",
1827 svUsedCount);
1828 }
1829
1830 if (length < 0 || length >= lengthRemaining)
1831 {
1832 LOC_LOGE("NMEA Error in string formatting");
1833 return;
1834 }
1835 pMarker += length;
1836 lengthRemaining -= length;
1837
1838 if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_ALTITUDE_MEAN_SEA_LEVEL)
1839 {
1840 length = snprintf(pMarker, lengthRemaining, "%.1lf,",
1841 locationExtended.altitudeMeanSeaLevel);
1842 }
1843 else
1844 {
1845 length = snprintf(pMarker, lengthRemaining,",");
1846 }
1847
1848 if (length < 0 || length >= lengthRemaining)
1849 {
1850 LOC_LOGE("NMEA Error in string formatting");
1851 return;
1852 }
1853 pMarker += length;
1854 lengthRemaining -= length;
1855
1856 if ((location.gpsLocation.flags & LOC_GPS_LOCATION_HAS_ALTITUDE) &&
1857 (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_ALTITUDE_MEAN_SEA_LEVEL))
1858 {
1859 length = snprintf(pMarker, lengthRemaining, "%.1lf,",
1860 ref_lla.alt - locationExtended.altitudeMeanSeaLevel);
1861 }
1862 else
1863 {
1864 length = snprintf(pMarker, lengthRemaining, ",");
1865 }
1866 if (length < 0 || length >= lengthRemaining)
1867 {
1868 LOC_LOGE("NMEA Error in string formatting");
1869 return;
1870 }
1871 pMarker += length;
1872 lengthRemaining -= length;
1873
1874 if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_DGNSS_DATA_AGE)
1875 {
1876 length = snprintf(pMarker, lengthRemaining, "%.1f,",
1877 (float)locationExtended.dgnssDataAgeMsec / 1000);
1878 }
1879 else
1880 {
1881 length = snprintf(pMarker, lengthRemaining, ",");
1882 }
1883 if (length < 0 || length >= lengthRemaining)
1884 {
1885 LOC_LOGE("NMEA Error in string formatting");
1886 return;
1887 }
1888 pMarker += length;
1889 lengthRemaining -= length;
1890
1891 if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_DGNSS_REF_STATION_ID)
1892 {
1893 length = snprintf(pMarker, lengthRemaining, "%04d",
1894 locationExtended.dgnssRefStationId);
1895 if (length < 0 || length >= lengthRemaining)
1896 {
1897 LOC_LOGE("NMEA Error in string formatting");
1898 return;
1899 }
1900 pMarker += length;
1901 lengthRemaining -= length;
1902 }
1903
1904 // hardcode Navigation Status field to 'V'
1905 length = snprintf(pMarker, lengthRemaining, ",%c", 'V');
1906 pMarker += length;
1907 lengthRemaining -= length;
1908
1909 length = loc_nmea_put_checksum(sentence_GNS, sizeof(sentence_GNS), false);
1910
1911 // -------------------
1912 // ------$--GGA-------
1913 // -------------------
1914
1915 pMarker = sentence_GGA;
1916 lengthRemaining = sizeof(sentence_GGA);
1917
1918 length = snprintf(pMarker, lengthRemaining, "$%sGGA,%02d%02d%02d.%02d," ,
1919 talker, utcHours, utcMinutes, utcSeconds, utcMSeconds/10);
1920
1921 if (length < 0 || length >= lengthRemaining)
1922 {
1923 LOC_LOGE("NMEA Error in string formatting");
1924 return;
1925 }
1926 pMarker += length;
1927 lengthRemaining -= length;
1928
1929 if (location.gpsLocation.flags & LOC_GPS_LOCATION_HAS_LAT_LONG)
1930 {
1931 double latitude = ref_lla.lat;
1932 double longitude = ref_lla.lon;
1933 char latHemisphere;
1934 char lonHemisphere;
1935 double latMinutes;
1936 double lonMinutes;
1937
1938 if (latitude > 0)
1939 {
1940 latHemisphere = 'N';
1941 }
1942 else
1943 {
1944 latHemisphere = 'S';
1945 latitude *= -1.0;
1946 }
1947
1948 if (longitude < 0)
1949 {
1950 lonHemisphere = 'W';
1951 longitude *= -1.0;
1952 }
1953 else
1954 {
1955 lonHemisphere = 'E';
1956 }
1957
1958 latMinutes = fmod(latitude * 60.0 , 60.0);
1959 lonMinutes = fmod(longitude * 60.0 , 60.0);
1960
1961 length = snprintf(pMarker, lengthRemaining, "%02d%09.6lf,%c,%03d%09.6lf,%c,",
1962 (uint8_t)floor(latitude), latMinutes, latHemisphere,
1963 (uint8_t)floor(longitude),lonMinutes, lonHemisphere);
1964 }
1965 else
1966 {
1967 length = snprintf(pMarker, lengthRemaining,",,,,");
1968 }
1969
1970 if (length < 0 || length >= lengthRemaining)
1971 {
1972 LOC_LOGE("NMEA Error in string formatting");
1973 return;
1974 }
1975 pMarker += length;
1976 lengthRemaining -= length;
1977
1978 // Number of satellites in use, 00-12
1979 if (svUsedCount > MAX_SATELLITES_IN_USE)
1980 svUsedCount = MAX_SATELLITES_IN_USE;
1981 if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_DOP)
1982 {
1983 length = snprintf(pMarker, lengthRemaining, "%s,%02d,%.1f,",
1984 ggaGpsQuality, svUsedCount, locationExtended.hdop);
1985 }
1986 else
1987 { // no hdop
1988 length = snprintf(pMarker, lengthRemaining, "%s,%02d,,",
1989 ggaGpsQuality, svUsedCount);
1990 }
1991
1992 if (length < 0 || length >= lengthRemaining)
1993 {
1994 LOC_LOGE("NMEA Error in string formatting");
1995 return;
1996 }
1997 pMarker += length;
1998 lengthRemaining -= length;
1999
2000 if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_ALTITUDE_MEAN_SEA_LEVEL)
2001 {
2002 length = snprintf(pMarker, lengthRemaining, "%.1lf,M,",
2003 locationExtended.altitudeMeanSeaLevel);
2004 }
2005 else
2006 {
2007 length = snprintf(pMarker, lengthRemaining,",,");
2008 }
2009
2010 if (length < 0 || length >= lengthRemaining)
2011 {
2012 LOC_LOGE("NMEA Error in string formatting");
2013 return;
2014 }
2015 pMarker += length;
2016 lengthRemaining -= length;
2017
2018 if ((location.gpsLocation.flags & LOC_GPS_LOCATION_HAS_ALTITUDE) &&
2019 (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_ALTITUDE_MEAN_SEA_LEVEL))
2020 {
2021 length = snprintf(pMarker, lengthRemaining, "%.1lf,M,",
2022 ref_lla.alt - locationExtended.altitudeMeanSeaLevel);
2023 }
2024 else
2025 {
2026 length = snprintf(pMarker, lengthRemaining, ",,");
2027 }
2028 if (length < 0 || length >= lengthRemaining)
2029 {
2030 LOC_LOGE("NMEA Error in string formatting");
2031 return;
2032 }
2033 pMarker += length;
2034 lengthRemaining -= length;
2035
2036 if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_DGNSS_DATA_AGE)
2037 {
2038 length = snprintf(pMarker, lengthRemaining, "%.1f,",
2039 (float)locationExtended.dgnssDataAgeMsec / 1000);
2040 }
2041 else
2042 {
2043 length = snprintf(pMarker, lengthRemaining, ",");
2044 }
2045 if (length < 0 || length >= lengthRemaining)
2046 {
2047 LOC_LOGE("NMEA Error in string formatting");
2048 return;
2049 }
2050 pMarker += length;
2051 lengthRemaining -= length;
2052
2053 if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_DGNSS_REF_STATION_ID)
2054 {
2055 length = snprintf(pMarker, lengthRemaining, "%04d",
2056 locationExtended.dgnssRefStationId);
2057 if (length < 0 || length >= lengthRemaining)
2058 {
2059 LOC_LOGE("NMEA Error in string formatting");
2060 return;
2061 }
2062 pMarker += length;
2063 lengthRemaining -= length;
2064 }
2065
2066 length = loc_nmea_put_checksum(sentence_GGA, sizeof(sentence_GGA), false);
2067
2068 // ------$--DTM-------
2069 nmeaArraystr.push_back(sentence_DTM);
2070 // ------$--RMC-------
2071 nmeaArraystr.push_back(sentence_RMC);
2072 if(LOC_GNSS_DATUM_PZ90 == datum_type) {
2073 // ------$--DTM-------
2074 nmeaArraystr.push_back(sentence_DTM);
2075 }
2076 // ------$--GNS-------
2077 nmeaArraystr.push_back(sentence_GNS);
2078 if(LOC_GNSS_DATUM_PZ90 == datum_type) {
2079 // ------$--DTM-------
2080 nmeaArraystr.push_back(sentence_DTM);
2081 }
2082 // ------$--GGA-------
2083 nmeaArraystr.push_back(sentence_GGA);
2084 indexOfGGA = static_cast<int>(nmeaArraystr.size() - 1);
2085 }
2086 //Send blank NMEA reports for non-final fixes
2087 else {
2088 strlcpy(sentence, "$GPGSA,A,1,,,,,,,,,,,,,,,,", sizeof(sentence));
2089 length = loc_nmea_put_checksum(sentence, sizeof(sentence), false);
2090 nmeaArraystr.push_back(sentence);
2091
2092 strlcpy(sentence, "$GPVTG,,T,,M,,N,,K,N", sizeof(sentence));
2093 length = loc_nmea_put_checksum(sentence, sizeof(sentence), false);
2094 nmeaArraystr.push_back(sentence);
2095
2096 strlcpy(sentence, "$GPDTM,,,,,,,,", sizeof(sentence));
2097 length = loc_nmea_put_checksum(sentence, sizeof(sentence), false);
2098 nmeaArraystr.push_back(sentence);
2099
2100 strlcpy(sentence, "$GPRMC,,V,,,,,,,,,,N,V", sizeof(sentence));
2101 length = loc_nmea_put_checksum(sentence, sizeof(sentence), false);
2102 nmeaArraystr.push_back(sentence);
2103
2104 strlcpy(sentence, "$GPGNS,,,,,,N,,,,,,,V", sizeof(sentence));
2105 length = loc_nmea_put_checksum(sentence, sizeof(sentence), false);
2106 nmeaArraystr.push_back(sentence);
2107
2108 strlcpy(sentence, "$GPGGA,,,,,,0,,,,,,,,", sizeof(sentence));
2109 length = loc_nmea_put_checksum(sentence, sizeof(sentence), false);
2110 nmeaArraystr.push_back(sentence);
2111 }
2112
2113 EXIT_LOG(%d, 0);
2114 }
2115
2116
2117
2118 /*===========================================================================
2119 FUNCTION loc_nmea_generate_sv
2120
2121 DESCRIPTION
2122 Generate NMEA sentences generated based on sv report
2123
2124 DEPENDENCIES
2125 NONE
2126
2127 RETURN VALUE
2128 0
2129
2130 SIDE EFFECTS
2131 N/A
2132
2133 ===========================================================================*/
loc_nmea_generate_sv(const GnssSvNotification & svNotify,std::vector<std::string> & nmeaArraystr)2134 void loc_nmea_generate_sv(const GnssSvNotification &svNotify,
2135 std::vector<std::string> &nmeaArraystr)
2136 {
2137 ENTRY_LOG();
2138
2139 char sentence[NMEA_SENTENCE_MAX_LENGTH] = {0};
2140 loc_sv_cache_info sv_cache_info = {};
2141
2142 //Count GPS SVs for saparating GPS from GLONASS and throw others
2143 for (uint32_t svOffset = 0; svOffset < svNotify.count; svOffset++) {
2144 if ((GNSS_SV_TYPE_GPS == svNotify.gnssSvs[svOffset].type) ||
2145 (GNSS_SV_TYPE_SBAS == svNotify.gnssSvs[svOffset].type))
2146 {
2147 if (GNSS_SIGNAL_GPS_L5 == svNotify.gnssSvs[svOffset].gnssSignalTypeMask) {
2148 sv_cache_info.gps_l5_count++;
2149 } else if (GNSS_SIGNAL_GPS_L2 == svNotify.gnssSvs[svOffset].gnssSignalTypeMask) {
2150 sv_cache_info.gps_l2_count++;
2151 } else {
2152 // GNSS_SIGNAL_GPS_L1CA, GNSS_SIGNAL_SBAS_L1 or default
2153 // If no signal type in report, it means default L1
2154 sv_cache_info.gps_l1_count++;
2155 }
2156 }
2157 else if (GNSS_SV_TYPE_GLONASS == svNotify.gnssSvs[svOffset].type)
2158 {
2159 if (GNSS_SIGNAL_GLONASS_G2 == svNotify.gnssSvs[svOffset].gnssSignalTypeMask){
2160 sv_cache_info.glo_g2_count++;
2161 } else {
2162 // GNSS_SIGNAL_GLONASS_G1 or default
2163 // If no signal type in report, it means default G1
2164 sv_cache_info.glo_g1_count++;
2165 }
2166 }
2167 else if (GNSS_SV_TYPE_GALILEO == svNotify.gnssSvs[svOffset].type)
2168 {
2169 if(GNSS_SIGNAL_GALILEO_E5A == svNotify.gnssSvs[svOffset].gnssSignalTypeMask){
2170 sv_cache_info.gal_e5_count++;
2171 } else if (GNSS_SIGNAL_GALILEO_E5B == svNotify.gnssSvs[svOffset].gnssSignalTypeMask) {
2172 sv_cache_info.gal_e5b_count++;
2173 } else {
2174 // GNSS_SIGNAL_GALILEO_E1 or default
2175 // If no signal type in report, it means default E1
2176 sv_cache_info.gal_e1_count++;
2177 }
2178 }
2179 else if (GNSS_SV_TYPE_QZSS == svNotify.gnssSvs[svOffset].type)
2180 {
2181 if (GNSS_SIGNAL_QZSS_L5 == svNotify.gnssSvs[svOffset].gnssSignalTypeMask) {
2182 sv_cache_info.qzss_l5_count++;
2183 } else if (GNSS_SIGNAL_QZSS_L2 == svNotify.gnssSvs[svOffset].gnssSignalTypeMask) {
2184 sv_cache_info.qzss_l2_count++;
2185 } else {
2186 // GNSS_SIGNAL_QZSS_L1CA or default
2187 // If no signal type in report, it means default L1
2188 sv_cache_info.qzss_l1_count++;
2189 }
2190 }
2191 else if (GNSS_SV_TYPE_BEIDOU == svNotify.gnssSvs[svOffset].type)
2192 {
2193 // cache the used in fix mask, as it will be needed to send $PQGSA
2194 // during the position report
2195 if (GNSS_SV_OPTIONS_USED_IN_FIX_BIT ==
2196 (svNotify.gnssSvs[svOffset].gnssSvOptionsMask &
2197 GNSS_SV_OPTIONS_USED_IN_FIX_BIT))
2198 {
2199 setSvMask(sv_cache_info.bds_used_mask, svNotify.gnssSvs[svOffset].svId);
2200 }
2201 if ((GNSS_SIGNAL_BEIDOU_B2AI == svNotify.gnssSvs[svOffset].gnssSignalTypeMask) ||
2202 (GNSS_SIGNAL_BEIDOU_B2AQ == svNotify.gnssSvs[svOffset].gnssSignalTypeMask)) {
2203 sv_cache_info.bds_b2_count++;
2204 } else if (GNSS_SIGNAL_BEIDOU_B1C == svNotify.gnssSvs[svOffset].gnssSignalTypeMask) {
2205 sv_cache_info.bds_b1c_count++;
2206 } else {
2207 // GNSS_SIGNAL_BEIDOU_B1I or default
2208 // If no signal type in report, it means default B1I
2209 sv_cache_info.bds_b1i_count++;
2210 }
2211 }
2212 else if (GNSS_SV_TYPE_NAVIC == svNotify.gnssSvs[svOffset].type)
2213 {
2214 // GNSS_SIGNAL_NAVIC_L5 is the only signal type for NAVIC
2215 sv_cache_info.navic_l5_count++;
2216 }
2217 }
2218
2219 loc_nmea_sv_meta sv_meta;
2220 // ---------------------
2221 // ------$GPGSV:L1CA----
2222 // ---------------------
2223
2224 loc_nmea_generate_GSV(svNotify, sentence, sizeof(sentence),
2225 loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_GPS,
2226 GNSS_SIGNAL_GPS_L1CA, false), nmeaArraystr);
2227
2228 // ---------------------
2229 // ------$GPGSV:L5------
2230 // ---------------------
2231
2232 loc_nmea_generate_GSV(svNotify, sentence, sizeof(sentence),
2233 loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_GPS,
2234 GNSS_SIGNAL_GPS_L5, false), nmeaArraystr);
2235
2236 // ---------------------
2237 // ------$GPGSV:L2------
2238 // ---------------------
2239 loc_nmea_generate_GSV(svNotify, sentence, sizeof(sentence),
2240 loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_GPS,
2241 GNSS_SIGNAL_GPS_L2, false), nmeaArraystr);
2242
2243 // ---------------------
2244 // ------$GLGSV:G1------
2245 // ---------------------
2246
2247 loc_nmea_generate_GSV(svNotify, sentence, sizeof(sentence),
2248 loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_GLONASS,
2249 GNSS_SIGNAL_GLONASS_G1, false), nmeaArraystr);
2250
2251 // ---------------------
2252 // ------$GLGSV:G2------
2253 // ---------------------
2254
2255 loc_nmea_generate_GSV(svNotify, sentence, sizeof(sentence),
2256 loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_GLONASS,
2257 GNSS_SIGNAL_GLONASS_G2, false), nmeaArraystr);
2258
2259 // ---------------------
2260 // ------$GAGSV:E1------
2261 // ---------------------
2262
2263 loc_nmea_generate_GSV(svNotify, sentence, sizeof(sentence),
2264 loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_GALILEO,
2265 GNSS_SIGNAL_GALILEO_E1, false), nmeaArraystr);
2266
2267 // -------------------------
2268 // ------$GAGSV:E5A---------
2269 // -------------------------
2270 loc_nmea_generate_GSV(svNotify, sentence, sizeof(sentence),
2271 loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_GALILEO,
2272 GNSS_SIGNAL_GALILEO_E5A, false), nmeaArraystr);
2273
2274 // -------------------------
2275 // ------$GAGSV:E5B---------
2276 // -------------------------
2277 loc_nmea_generate_GSV(svNotify, sentence, sizeof(sentence),
2278 loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_GALILEO,
2279 GNSS_SIGNAL_GALILEO_E5B, false), nmeaArraystr);
2280
2281 // -----------------------------
2282 // ------$GQGSV (QZSS):L1CA-----
2283 // -----------------------------
2284
2285 loc_nmea_generate_GSV(svNotify, sentence, sizeof(sentence),
2286 loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_QZSS,
2287 GNSS_SIGNAL_QZSS_L1CA, false), nmeaArraystr);
2288
2289 // -----------------------------
2290 // ------$GQGSV (QZSS):L5-------
2291 // -----------------------------
2292
2293 loc_nmea_generate_GSV(svNotify, sentence, sizeof(sentence),
2294 loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_QZSS,
2295 GNSS_SIGNAL_QZSS_L5, false), nmeaArraystr);
2296
2297 // -----------------------------
2298 // ------$GQGSV (QZSS):L2-------
2299 // -----------------------------
2300
2301 loc_nmea_generate_GSV(svNotify, sentence, sizeof(sentence),
2302 loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_QZSS,
2303 GNSS_SIGNAL_QZSS_L2, false), nmeaArraystr);
2304
2305
2306 // -----------------------------
2307 // ------$GBGSV (BEIDOU:B1I)----
2308 // -----------------------------
2309
2310 loc_nmea_generate_GSV(svNotify, sentence, sizeof(sentence),
2311 loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_BEIDOU,
2312 GNSS_SIGNAL_BEIDOU_B1I, false), nmeaArraystr);
2313
2314 // -----------------------------
2315 // ------$GBGSV (BEIDOU:B1C)----
2316 // -----------------------------
2317
2318 loc_nmea_generate_GSV(svNotify, sentence, sizeof(sentence),
2319 loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_BEIDOU,
2320 GNSS_SIGNAL_BEIDOU_B1C, false), nmeaArraystr);
2321
2322 // -----------------------------
2323 // ------$GBGSV (BEIDOU:B2AI)---
2324 // -----------------------------
2325
2326 loc_nmea_generate_GSV(svNotify, sentence, sizeof(sentence),
2327 loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_BEIDOU,
2328 GNSS_SIGNAL_BEIDOU_B2AI, false), nmeaArraystr);
2329
2330 // -----------------------------
2331 // ------$GIGSV (NAVIC:L5)------
2332 // -----------------------------
2333
2334 loc_nmea_generate_GSV(svNotify, sentence, sizeof(sentence),
2335 loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_NAVIC,
2336 GNSS_SIGNAL_NAVIC_L5,false), nmeaArraystr);
2337
2338 EXIT_LOG(%d, 0);
2339 }
2340