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