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