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