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] = {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             ref_datum[0] = 'W';
955             ref_datum[1] = '8';
956             ref_datum[2] = '4';
957             local_datum[0] = 'P';
958             local_datum[1] = '9';
959             local_datum[2] = '0';
960             break;
961         case LOC_GNSS_DATUM_PZ90:
962             ref_datum[0] = 'P';
963             ref_datum[1] = '9';
964             ref_datum[2] = '0';
965             local_datum[0] = 'W';
966             local_datum[1] = '8';
967             local_datum[2] = '4';
968             break;
969         default:
970             break;
971     }
972     length = snprintf(pMarker , lengthRemaining , "$%sDTM,%s,," , talker, local_datum);
973     if (length < 0 || length >= lengthRemaining) {
974         LOC_LOGE("NMEA Error in string formatting");
975         return;
976     }
977     pMarker += length;
978     lengthRemaining -= length;
979 
980     lla_offset[0] = local_lla.lat - ref_lla.lat;
981     lla_offset[1] = fmod(local_lla.lon - ref_lla.lon, 360.0);
982     if (lla_offset[1] < -180.0) {
983         lla_offset[1] += 360.0;
984     } else if ( lla_offset[1] > 180.0) {
985         lla_offset[1] -= 360.0;
986     }
987     lla_offset[2] = local_lla.alt - ref_lla.alt;
988     if (lla_offset[0] > 0.0) {
989         latHem = 'N';
990     } else {
991         latHem = 'S';
992         lla_offset[0] *= -1.0;
993     }
994     latMins = fmod(lla_offset[0] * 60.0, 60.0);
995     if (lla_offset[1] < 0.0) {
996         longHem = 'W';
997         lla_offset[1] *= -1.0;
998     }else {
999         longHem = 'E';
1000     }
1001     longMins = fmod(lla_offset[1] * 60.0, 60.0);
1002     length = snprintf(pMarker, lengthRemaining, "%02d%09.6lf,%c,%03d%09.6lf,%c,%.3lf,",
1003                      (uint8_t)floor(lla_offset[0]), latMins, latHem,
1004                      (uint8_t)floor(lla_offset[1]), longMins, longHem, lla_offset[2]);
1005     if (length < 0 || length >= lengthRemaining) {
1006         LOC_LOGE("NMEA Error in string formatting");
1007         return;
1008     }
1009     pMarker += length;
1010     lengthRemaining -= length;
1011     length = snprintf(pMarker , lengthRemaining , "%s" , ref_datum);
1012     if (length < 0 || length >= lengthRemaining) {
1013         LOC_LOGE("NMEA Error in string formatting");
1014         return;
1015     }
1016     pMarker += length;
1017     lengthRemaining -= length;
1018 
1019     length = loc_nmea_put_checksum(sentence, bufSize, false);
1020 }
1021 
1022 /*===========================================================================
1023 FUNCTION    get_utctime_with_leapsecond_transition
1024 
1025 DESCRIPTION
1026    This function returns true if the position report is generated during
1027    leap second transition period. If not, then the utc timestamp returned
1028    will be set to the timestamp in the position report. If it is,
1029    then the utc timestamp returned will need to take into account
1030    of the leap second transition so that proper calendar year/month/date
1031    can be calculated from the returned utc timestamp.
1032 
1033 DEPENDENCIES
1034    NONE
1035 
1036 RETURN VALUE
1037    true: position report is generated in leap second transition period.
1038 
1039 SIDE EFFECTS
1040    N/A
1041 
1042 ===========================================================================*/
get_utctime_with_leapsecond_transition(const UlpLocation & location,const GpsLocationExtended & locationExtended,const LocationSystemInfo & systemInfo,LocGpsUtcTime & utcPosTimestamp)1043 static bool get_utctime_with_leapsecond_transition(
1044         const UlpLocation &location,
1045         const GpsLocationExtended &locationExtended,
1046         const LocationSystemInfo &systemInfo,
1047         LocGpsUtcTime &utcPosTimestamp)
1048 {
1049     bool inTransition = false;
1050 
1051     // position report is not generated during leap second transition,
1052     // we can use the UTC timestamp from position report as is
1053     utcPosTimestamp = location.gpsLocation.timestamp;
1054 
1055     // Check whether we are in leap second transition.
1056     // If so, per NMEA spec, we need to display the extra second in format of 23:59:60
1057     // with year/month/date not getting advanced.
1058     if ((locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_GPS_TIME) &&
1059         ((systemInfo.systemInfoMask & LOCATION_SYS_INFO_LEAP_SECOND) &&
1060          (systemInfo.leapSecondSysInfo.leapSecondInfoMask &
1061           LEAP_SECOND_SYS_INFO_LEAP_SECOND_CHANGE_BIT))) {
1062 
1063         const LeapSecondChangeInfo  &leapSecondChangeInfo =
1064             systemInfo.leapSecondSysInfo.leapSecondChangeInfo;
1065         const GnssSystemTimeStructType &gpsTimestampLsChange =
1066             leapSecondChangeInfo.gpsTimestampLsChange;
1067 
1068         uint64_t gpsTimeLsChange = gpsTimestampLsChange.systemWeek * MSEC_IN_ONE_WEEK +
1069                                    gpsTimestampLsChange.systemMsec;
1070         uint64_t gpsTimePosReport = locationExtended.gpsTime.gpsWeek * MSEC_IN_ONE_WEEK +
1071                                     locationExtended.gpsTime.gpsTimeOfWeekMs;
1072         // we are only dealing with positive leap second change, as negative
1073         // leap second change has never occurred and should not occur in future
1074         if (leapSecondChangeInfo.leapSecondsAfterChange >
1075             leapSecondChangeInfo.leapSecondsBeforeChange) {
1076             // leap second adjustment is always 1 second at a time. It can happen
1077             // every quarter end and up to four times per year.
1078             if ((gpsTimePosReport >= gpsTimeLsChange) &&
1079                 (gpsTimePosReport < (gpsTimeLsChange + 1000))) {
1080                 inTransition = true;
1081                 utcPosTimestamp = gpsTimeLsChange + UTC_GPS_OFFSET_MSECS -
1082                                   leapSecondChangeInfo.leapSecondsBeforeChange * 1000;
1083 
1084                 // we substract 1000 milli-seconds from UTC timestmap in order to calculate the
1085                 // proper year, month and date during leap second transtion.
1086                 // Let us give an example, assuming leap second transition is scheduled on 2019,
1087                 // Dec 31st mid night. When leap second transition is happening,
1088                 // instead of outputting the time as 2020, Jan, 1st, 00 hour, 00 min, and 00 sec.
1089                 // The time need to be displayed as 2019, Dec, 31st, 23 hour, 59 min and 60 sec.
1090                 utcPosTimestamp -= 1000;
1091             }
1092         }
1093     }
1094     return inTransition;
1095 }
1096 
1097 /*===========================================================================
1098 FUNCTION    loc_nmea_get_fix_quality
1099 
1100 DESCRIPTION
1101    This function obtains the fix quality for GGA sentence, mode indicator
1102    for RMC and VTG sentence based on nav solution mask and tech mask in
1103    the postion report.
1104 
1105 DEPENDENCIES
1106    NONE
1107 
1108 Output parameter
1109    ggaGpsQuality: gps quality field in GGA sentence
1110    rmcModeIndicator: mode indicator field in RMC sentence
1111    vtgModeIndicator: mode indicator field in VTG sentence
1112 
1113 SIDE EFFECTS
1114    N/A
1115 
1116 ===========================================================================*/
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])1117 static void loc_nmea_get_fix_quality(const UlpLocation & location,
1118                                      const GpsLocationExtended & locationExtended,
1119                                      bool custom_gga_fix_quality,
1120                                      char ggaGpsQuality[3],
1121                                      char & rmcModeIndicator,
1122                                      char & vtgModeIndicator,
1123                                      char gnsModeIndicator[7]) {
1124 
1125     ggaGpsQuality[0] = '0'; // 0 means no fix
1126     rmcModeIndicator = 'N'; // N means no fix
1127     vtgModeIndicator = 'N'; // N means no fix
1128     memset(gnsModeIndicator, 'N', 6); // N means no fix
1129     gnsModeIndicator[6] = '\0';
1130     do {
1131         if (!(location.gpsLocation.flags & LOC_GPS_LOCATION_HAS_LAT_LONG)){
1132             break;
1133         }
1134         // NOTE: Order of the check is important
1135         if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_NAV_SOLUTION_MASK) {
1136             if (LOC_NAV_MASK_PPP_CORRECTION & locationExtended.navSolutionMask) {
1137                 ggaGpsQuality[0] = '2';    // 2 means DGPS fix
1138                 rmcModeIndicator = 'P'; // P means precise
1139                 vtgModeIndicator = 'P'; // P means precise
1140                 if (locationExtended.gnss_sv_used_ids.gps_sv_used_ids_mask ? 1 : 0)
1141                     gnsModeIndicator[0] = 'P'; // P means precise
1142                 if (locationExtended.gnss_sv_used_ids.glo_sv_used_ids_mask ? 1 : 0)
1143                     gnsModeIndicator[1] = 'P'; // P means precise
1144                 if (locationExtended.gnss_sv_used_ids.gal_sv_used_ids_mask ? 1 : 0)
1145                     gnsModeIndicator[2] = 'P'; // P means precise
1146                 if (locationExtended.gnss_sv_used_ids.bds_sv_used_ids_mask ? 1 : 0)
1147                     gnsModeIndicator[3] = 'P'; // P means precise
1148                 if (locationExtended.gnss_sv_used_ids.qzss_sv_used_ids_mask ? 1 : 0)
1149                     gnsModeIndicator[4] = 'P'; // P means precise
1150                 if (locationExtended.gnss_sv_used_ids.navic_sv_used_ids_mask ? 1 : 0)
1151                     gnsModeIndicator[5] = 'P'; // P means precise
1152                 break;
1153             } else if (LOC_NAV_MASK_RTK_FIXED_CORRECTION & locationExtended.navSolutionMask){
1154                 ggaGpsQuality[0] = '4';    // 4 means RTK Fixed fix
1155                 rmcModeIndicator = 'R'; // use R (RTK fixed)
1156                 vtgModeIndicator = 'D'; // use D (differential) as
1157                                         // no RTK fixed defined for VTG in NMEA 183 spec
1158                 if (locationExtended.gnss_sv_used_ids.gps_sv_used_ids_mask ? 1 : 0)
1159                     gnsModeIndicator[0] = 'R'; // R means RTK fixed
1160                 if (locationExtended.gnss_sv_used_ids.glo_sv_used_ids_mask ? 1 : 0)
1161                     gnsModeIndicator[1] = 'R'; // R means RTK fixed
1162                 if (locationExtended.gnss_sv_used_ids.gal_sv_used_ids_mask ? 1 : 0)
1163                     gnsModeIndicator[2] = 'R'; // R means RTK fixed
1164                 if (locationExtended.gnss_sv_used_ids.bds_sv_used_ids_mask ? 1 : 0)
1165                     gnsModeIndicator[3] = 'R'; // R means RTK fixed
1166                 if (locationExtended.gnss_sv_used_ids.qzss_sv_used_ids_mask ? 1 : 0)
1167                     gnsModeIndicator[4] = 'R'; // R means RTK fixed
1168                 if (locationExtended.gnss_sv_used_ids.navic_sv_used_ids_mask ? 1 : 0)
1169                     gnsModeIndicator[5] = 'R'; // R means RTK fixed
1170                 break;
1171             } else if (LOC_NAV_MASK_RTK_CORRECTION & locationExtended.navSolutionMask){
1172                 ggaGpsQuality[0] = '5';    // 5 means RTK float fix
1173                 rmcModeIndicator = 'F'; // F means RTK float fix
1174                 vtgModeIndicator = 'D'; // use D (differential) as
1175                                         // no RTK float defined for VTG in NMEA 183 spec
1176                 if (locationExtended.gnss_sv_used_ids.gps_sv_used_ids_mask ? 1 : 0)
1177                     gnsModeIndicator[0] = 'F'; // F means RTK float fix
1178                 if (locationExtended.gnss_sv_used_ids.glo_sv_used_ids_mask ? 1 : 0)
1179                     gnsModeIndicator[1] = 'F'; // F means RTK float fix
1180                 if (locationExtended.gnss_sv_used_ids.gal_sv_used_ids_mask ? 1 : 0)
1181                     gnsModeIndicator[2] = 'F'; // F means RTK float fix
1182                 if (locationExtended.gnss_sv_used_ids.bds_sv_used_ids_mask ? 1 : 0)
1183                     gnsModeIndicator[3] = 'F'; // F means RTK float fix
1184                 if (locationExtended.gnss_sv_used_ids.qzss_sv_used_ids_mask ? 1 : 0)
1185                     gnsModeIndicator[4] = 'F'; // F means RTK float fix
1186                 if (locationExtended.gnss_sv_used_ids.navic_sv_used_ids_mask ? 1 : 0)
1187                     gnsModeIndicator[5] = 'F'; // F means RTK float fix
1188                 break;
1189             } else if (LOC_NAV_MASK_DGNSS_CORRECTION & locationExtended.navSolutionMask){
1190                 ggaGpsQuality[0] = '2';    // 2 means DGPS fix
1191                 rmcModeIndicator = 'D'; // D means differential
1192                 vtgModeIndicator = 'D'; // D means differential
1193                 if (locationExtended.gnss_sv_used_ids.gps_sv_used_ids_mask ? 1 : 0)
1194                     gnsModeIndicator[0] = 'D'; // D means differential
1195                 if (locationExtended.gnss_sv_used_ids.glo_sv_used_ids_mask ? 1 : 0)
1196                     gnsModeIndicator[1] = 'D'; // D means differential
1197                 if (locationExtended.gnss_sv_used_ids.gal_sv_used_ids_mask ? 1 : 0)
1198                     gnsModeIndicator[2] = 'D'; // D means differential
1199                 if (locationExtended.gnss_sv_used_ids.bds_sv_used_ids_mask ? 1 : 0)
1200                     gnsModeIndicator[3] = 'D'; // D means differential
1201                 if (locationExtended.gnss_sv_used_ids.qzss_sv_used_ids_mask ? 1 : 0)
1202                     gnsModeIndicator[4] = 'D'; // D means differential
1203                 if (locationExtended.gnss_sv_used_ids.navic_sv_used_ids_mask ? 1 : 0)
1204                     gnsModeIndicator[5] = 'D'; // D means differential
1205                 break;
1206             } else if (LOC_NAV_MASK_SBAS_CORRECTION_IONO & locationExtended.navSolutionMask){
1207                 ggaGpsQuality[0] = '2';    // 2 means DGPS fix
1208                 rmcModeIndicator = 'D'; // D means differential
1209                 vtgModeIndicator = 'D'; // D means differential
1210                 if (locationExtended.gnss_sv_used_ids.gps_sv_used_ids_mask ? 1 : 0)
1211                     gnsModeIndicator[0] = 'D'; // D means differential
1212                 if (locationExtended.gnss_sv_used_ids.glo_sv_used_ids_mask ? 1 : 0)
1213                     gnsModeIndicator[1] = 'D'; // D means differential
1214                 if (locationExtended.gnss_sv_used_ids.gal_sv_used_ids_mask ? 1 : 0)
1215                     gnsModeIndicator[2] = 'D'; // D means differential
1216                 if (locationExtended.gnss_sv_used_ids.bds_sv_used_ids_mask ? 1 : 0)
1217                     gnsModeIndicator[3] = 'D'; // D means differential
1218                 if (locationExtended.gnss_sv_used_ids.qzss_sv_used_ids_mask ? 1 : 0)
1219                     gnsModeIndicator[4] = 'D'; // D means differential
1220                 if (locationExtended.gnss_sv_used_ids.navic_sv_used_ids_mask ? 1 : 0)
1221                     gnsModeIndicator[5] = 'D'; // D means differential
1222                 break;
1223             }
1224         }
1225         // NOTE: Order of the check is important
1226         if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_POS_TECH_MASK) {
1227             if (LOC_POS_TECH_MASK_SATELLITE & locationExtended.tech_mask){
1228                 ggaGpsQuality[0] = '1'; // 1 means GPS
1229                 rmcModeIndicator = 'A'; // A means autonomous
1230                 vtgModeIndicator = 'A'; // A means autonomous
1231                 if (locationExtended.gnss_sv_used_ids.gps_sv_used_ids_mask ? 1 : 0)
1232                     gnsModeIndicator[0] = 'A'; // A means autonomous
1233                 if (locationExtended.gnss_sv_used_ids.glo_sv_used_ids_mask ? 1 : 0)
1234                     gnsModeIndicator[1] = 'A'; // A means autonomous
1235                 if (locationExtended.gnss_sv_used_ids.gal_sv_used_ids_mask ? 1 : 0)
1236                     gnsModeIndicator[2] = 'A'; // A means autonomous
1237                 if (locationExtended.gnss_sv_used_ids.bds_sv_used_ids_mask ? 1 : 0)
1238                     gnsModeIndicator[3] = 'A'; // A means autonomous
1239                 if (locationExtended.gnss_sv_used_ids.qzss_sv_used_ids_mask ? 1 : 0)
1240                     gnsModeIndicator[4] = 'A'; // A means autonomous
1241                 if (locationExtended.gnss_sv_used_ids.navic_sv_used_ids_mask ? 1 : 0)
1242                     gnsModeIndicator[5] = 'A'; // A means autonomous
1243                 break;
1244             } else if (LOC_POS_TECH_MASK_SENSORS & locationExtended.tech_mask){
1245                 ggaGpsQuality[0] = '6'; // 6 means estimated (dead reckoning)
1246                 rmcModeIndicator = 'E'; // E means estimated (dead reckoning)
1247                 vtgModeIndicator = 'E'; // E means estimated (dead reckoning)
1248                 memset(gnsModeIndicator, 'E', 6); // E means estimated (dead reckoning)
1249                 break;
1250             }
1251         }
1252     } while (0);
1253 
1254     do {
1255         // check for customized nmea enabled or not
1256         // with customized GGA quality enabled
1257         // PPP fix w/o sensor: 59, PPP fix w/ sensor: 69
1258         // DGNSS/SBAS correction fix w/o sensor: 2, w/ sensor: 62
1259         // RTK fixed fix w/o sensor: 4, w/ sensor: 64
1260         // RTK float fix w/o sensor: 5, w/ sensor: 65
1261         // SPE fix w/o sensor: 1, and w/ sensor: 61
1262         // Sensor dead reckoning fix: 6
1263         if (true == custom_gga_fix_quality) {
1264             if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_NAV_SOLUTION_MASK) {
1265                 // PPP fix w/o sensor: fix quality will now be 59
1266                 // PPP fix w sensor: fix quality will now be 69
1267                 if (LOC_NAV_MASK_PPP_CORRECTION & locationExtended.navSolutionMask) {
1268                     if ((locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_POS_TECH_MASK) &&
1269                         (LOC_POS_TECH_MASK_SENSORS & locationExtended.tech_mask)) {
1270                         ggaGpsQuality[0] = '6';
1271                         ggaGpsQuality[1] = '9';
1272                     } else {
1273                         ggaGpsQuality[0] = '5';
1274                         ggaGpsQuality[1] = '9';
1275                     }
1276                     break;
1277                 }
1278             }
1279 
1280             if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_POS_TECH_MASK) {
1281                 if (LOC_POS_TECH_MASK_SENSORS & locationExtended.tech_mask){
1282                     char ggaQuality_copy = ggaGpsQuality[0];
1283                     ggaGpsQuality[0] = '6'; // 6 sensor assisted
1284                     // RTK fixed fix w/ sensor: fix quality will now be 64
1285                     // RTK float fix w/ sensor: 65
1286                     // DGNSS and/or SBAS correction fix and w/ sensor: 62
1287                     // GPS fix without correction and w/ sensor: 61
1288                     if ((LOC_NAV_MASK_RTK_FIXED_CORRECTION & locationExtended.navSolutionMask)||
1289                             (LOC_NAV_MASK_RTK_CORRECTION & locationExtended.navSolutionMask)||
1290                             (LOC_NAV_MASK_DGNSS_CORRECTION & locationExtended.navSolutionMask)||
1291                             (LOC_NAV_MASK_SBAS_CORRECTION_IONO & locationExtended.navSolutionMask)||
1292                             (LOC_POS_TECH_MASK_SATELLITE & locationExtended.tech_mask)) {
1293                         ggaGpsQuality[1] = ggaQuality_copy;
1294                         break;
1295                     }
1296                 }
1297             }
1298         }
1299     } while (0);
1300 
1301     LOC_LOGv("gps quality: %s, rmc mode indicator: %c, vtg mode indicator: %c",
1302              ggaGpsQuality, rmcModeIndicator, vtgModeIndicator);
1303 }
1304 
1305 /*===========================================================================
1306 FUNCTION    loc_nmea_generate_pos
1307 
1308 DESCRIPTION
1309    Generate NMEA sentences generated based on position report
1310    Currently below sentences are generated within this function:
1311    - $GPGSA : GPS DOP and active SVs
1312    - $GLGSA : GLONASS DOP and active SVs
1313    - $GAGSA : GALILEO DOP and active SVs
1314    - $GNGSA : GNSS DOP and active SVs
1315    - $--VTG : Track made good and ground speed
1316    - $--RMC : Recommended minimum navigation information
1317    - $--GGA : Time, position and fix related data
1318 
1319 DEPENDENCIES
1320    NONE
1321 
1322 RETURN VALUE
1323    0
1324 
1325 SIDE EFFECTS
1326    N/A
1327 
1328 ===========================================================================*/
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)1329 void loc_nmea_generate_pos(const UlpLocation &location,
1330                                const GpsLocationExtended &locationExtended,
1331                                const LocationSystemInfo &systemInfo,
1332                                unsigned char generate_nmea,
1333                                bool custom_gga_fix_quality,
1334                                std::vector<std::string> &nmeaArraystr,
1335                                int& indexOfGGA,
1336                                bool isTagBlockGroupingEnabled)
1337 {
1338     ENTRY_LOG();
1339 
1340     indexOfGGA = -1;
1341     LocGpsUtcTime utcPosTimestamp = 0;
1342     bool inLsTransition = false;
1343 
1344     inLsTransition = get_utctime_with_leapsecond_transition
1345                     (location, locationExtended, systemInfo, utcPosTimestamp);
1346 
1347     time_t utcTime(utcPosTimestamp/1000);
1348     struct tm result;
1349     tm * pTm = gmtime_r(&utcTime, &result);
1350     if (NULL == pTm) {
1351         LOC_LOGE("gmtime failed");
1352         return;
1353     }
1354 
1355     char sentence[NMEA_SENTENCE_MAX_LENGTH] = {0};
1356     char sentence_DTM[NMEA_SENTENCE_MAX_LENGTH] = {0};
1357     char sentence_RMC[NMEA_SENTENCE_MAX_LENGTH] = {0};
1358     char sentence_GNS[NMEA_SENTENCE_MAX_LENGTH] = {0};
1359     char sentence_GGA[NMEA_SENTENCE_MAX_LENGTH] = {0};
1360     char* pMarker = sentence;
1361     int lengthRemaining = sizeof(sentence);
1362     int length = 0;
1363     int utcYear = pTm->tm_year % 100; // 2 digit year
1364     int utcMonth = pTm->tm_mon + 1; // tm_mon starts at zero
1365     int utcDay = pTm->tm_mday;
1366     int utcHours = pTm->tm_hour;
1367     int utcMinutes = pTm->tm_min;
1368     int utcSeconds = pTm->tm_sec;
1369     int utcMSeconds = (location.gpsLocation.timestamp)%1000;
1370     int datum_type = loc_get_datum_type();
1371     LocEcef ecef_w84;
1372     LocEcef ecef_p90;
1373     LocLla  lla_w84;
1374     LocLla  lla_p90;
1375     LocLla  ref_lla;
1376     LocLla  local_lla;
1377 
1378     if (inLsTransition) {
1379         // During leap second transition, we need to display the extra
1380         // leap second of hour, minute, second as (23:59:60)
1381         utcHours = 23;
1382         utcMinutes = 59;
1383         utcSeconds = 60;
1384         // As UTC timestamp is freezing during leap second transition,
1385         // retrieve milli-seconds portion from GPS timestamp.
1386         utcMSeconds = locationExtended.gpsTime.gpsTimeOfWeekMs % 1000;
1387     }
1388 
1389    loc_sv_cache_info sv_cache_info = {};
1390 
1391     if (GPS_LOCATION_EXTENDED_HAS_GNSS_SV_USED_DATA & locationExtended.flags) {
1392         sv_cache_info.gps_used_mask =
1393                 locationExtended.gnss_sv_used_ids.gps_sv_used_ids_mask;
1394         sv_cache_info.glo_used_mask =
1395                 locationExtended.gnss_sv_used_ids.glo_sv_used_ids_mask;
1396         sv_cache_info.gal_used_mask =
1397                 locationExtended.gnss_sv_used_ids.gal_sv_used_ids_mask;
1398         sv_cache_info.bds_used_mask =
1399                 locationExtended.gnss_sv_used_ids.bds_sv_used_ids_mask;
1400         sv_cache_info.qzss_used_mask =
1401                 locationExtended.gnss_sv_used_ids.qzss_sv_used_ids_mask;
1402         sv_cache_info.navic_used_mask =
1403                 locationExtended.gnss_sv_used_ids.navic_sv_used_ids_mask;
1404     }
1405 
1406     if (generate_nmea) {
1407         char talker[3] = {'G', 'P', '\0'};
1408         uint32_t svUsedCount = 0;
1409         uint32_t count = 0;
1410         loc_nmea_sv_meta sv_meta;
1411         // -------------------
1412         // ---$GPGSA/$GNGSA---
1413         // -------------------
1414 
1415         count = loc_nmea_generate_GSA(locationExtended, sentence, sizeof(sentence),
1416                         loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_GPS,
1417                         GNSS_SIGNAL_GPS_L1CA, true), nmeaArraystr, isTagBlockGroupingEnabled);
1418         if (count > 0)
1419         {
1420             svUsedCount += count;
1421             talker[0] = sv_meta.talker[0];
1422             talker[1] = sv_meta.talker[1];
1423         }
1424 
1425         // -------------------
1426         // ---$GLGSA/$GNGSA---
1427         // -------------------
1428 
1429         count = loc_nmea_generate_GSA(locationExtended, sentence, sizeof(sentence),
1430                         loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_GLONASS,
1431                         GNSS_SIGNAL_GLONASS_G1, true), nmeaArraystr, isTagBlockGroupingEnabled);
1432         if (count > 0)
1433         {
1434             svUsedCount += count;
1435             talker[0] = sv_meta.talker[0];
1436             talker[1] = sv_meta.talker[1];
1437         }
1438 
1439         // -------------------
1440         // ---$GAGSA/$GNGSA---
1441         // -------------------
1442 
1443         count = loc_nmea_generate_GSA(locationExtended, sentence, sizeof(sentence),
1444                         loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_GALILEO,
1445                         GNSS_SIGNAL_GALILEO_E1, true), nmeaArraystr, isTagBlockGroupingEnabled);
1446         if (count > 0)
1447         {
1448             svUsedCount += count;
1449             talker[0] = sv_meta.talker[0];
1450             talker[1] = sv_meta.talker[1];
1451         }
1452 
1453         // ----------------------------
1454         // ---$GBGSA/$GNGSA (BEIDOU)---
1455         // ----------------------------
1456         count = loc_nmea_generate_GSA(locationExtended, sentence, sizeof(sentence),
1457                         loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_BEIDOU,
1458                         GNSS_SIGNAL_BEIDOU_B1I, true), nmeaArraystr, isTagBlockGroupingEnabled);
1459         if (count > 0)
1460         {
1461             svUsedCount += count;
1462             talker[0] = sv_meta.talker[0];
1463             talker[1] = sv_meta.talker[1];
1464         }
1465 
1466         // --------------------------
1467         // ---$GQGSA/$GNGSA (QZSS)---
1468         // --------------------------
1469 
1470         count = loc_nmea_generate_GSA(locationExtended, sentence, sizeof(sentence),
1471                         loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_QZSS,
1472                         GNSS_SIGNAL_QZSS_L1CA, true), nmeaArraystr, isTagBlockGroupingEnabled);
1473         if (count > 0)
1474         {
1475             svUsedCount += count;
1476             talker[0] = sv_meta.talker[0];
1477             talker[1] = sv_meta.talker[1];
1478         }
1479 
1480         // if svUsedCount is 0, it means we do not generate any GSA sentence yet.
1481         // in this case, generate an empty GSA sentence
1482         if (svUsedCount == 0) {
1483             strlcpy(sentence, "$GPGSA,A,1,,,,,,,,,,,,,,,,", sizeof(sentence));
1484             length = loc_nmea_put_checksum(sentence, sizeof(sentence), false);
1485             nmeaArraystr.push_back(sentence);
1486         }
1487 
1488         char ggaGpsQuality[3] = {'0', '\0', '\0'};
1489         char rmcModeIndicator = 'N';
1490         char vtgModeIndicator = 'N';
1491         char gnsModeIndicator[7] = {'N', 'N', 'N', 'N', 'N', 'N', '\0'};
1492         loc_nmea_get_fix_quality(location, locationExtended, custom_gga_fix_quality,
1493                                  ggaGpsQuality, rmcModeIndicator, vtgModeIndicator, gnsModeIndicator);
1494 
1495         // -------------------
1496         // ------$--VTG-------
1497         // -------------------
1498 
1499         pMarker = sentence;
1500         lengthRemaining = sizeof(sentence);
1501 
1502         if (location.gpsLocation.flags & LOC_GPS_LOCATION_HAS_BEARING)
1503         {
1504             float magTrack = location.gpsLocation.bearing;
1505             if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_MAG_DEV)
1506             {
1507                 magTrack = location.gpsLocation.bearing - locationExtended.magneticDeviation;
1508                 if (magTrack < 0.0)
1509                     magTrack += 360.0;
1510                 else if (magTrack > 360.0)
1511                     magTrack -= 360.0;
1512             }
1513 
1514             length = snprintf(pMarker, lengthRemaining, "$%sVTG,%.1lf,T,%.1lf,M,", talker, location.gpsLocation.bearing, magTrack);
1515         }
1516         else
1517         {
1518             length = snprintf(pMarker, lengthRemaining, "$%sVTG,,T,,M,", talker);
1519         }
1520 
1521         if (length < 0 || length >= lengthRemaining)
1522         {
1523             LOC_LOGE("NMEA Error in string formatting");
1524             return;
1525         }
1526         pMarker += length;
1527         lengthRemaining -= length;
1528 
1529         if (location.gpsLocation.flags & LOC_GPS_LOCATION_HAS_SPEED)
1530         {
1531             float speedKnots = location.gpsLocation.speed * (3600.0/1852.0);
1532             float speedKmPerHour = location.gpsLocation.speed * 3.6;
1533 
1534             length = snprintf(pMarker, lengthRemaining, "%.1lf,N,%.1lf,K,", speedKnots, speedKmPerHour);
1535         }
1536         else
1537         {
1538             length = snprintf(pMarker, lengthRemaining, ",N,,K,");
1539         }
1540 
1541         if (length < 0 || length >= lengthRemaining)
1542         {
1543             LOC_LOGE("NMEA Error in string formatting");
1544             return;
1545         }
1546         pMarker += length;
1547         lengthRemaining -= length;
1548 
1549         length = snprintf(pMarker, lengthRemaining, "%c", vtgModeIndicator);
1550 
1551         length = loc_nmea_put_checksum(sentence, sizeof(sentence), false);
1552         nmeaArraystr.push_back(sentence);
1553 
1554         memset(&ecef_w84, 0, sizeof(ecef_w84));
1555         memset(&ecef_p90, 0, sizeof(ecef_p90));
1556         memset(&lla_w84, 0, sizeof(lla_w84));
1557         memset(&lla_p90, 0, sizeof(lla_p90));
1558         memset(&ref_lla, 0, sizeof(ref_lla));
1559         memset(&local_lla, 0, sizeof(local_lla));
1560         lla_w84.lat = location.gpsLocation.latitude / 180.0 * M_PI;
1561         lla_w84.lon = location.gpsLocation.longitude / 180.0 * M_PI;
1562         lla_w84.alt = location.gpsLocation.altitude;
1563 
1564         convert_Lla_to_Ecef(lla_w84, ecef_w84);
1565         convert_WGS84_to_PZ90(ecef_w84, ecef_p90);
1566         convert_Ecef_to_Lla(ecef_p90, lla_p90);
1567 
1568         switch (datum_type) {
1569             case LOC_GNSS_DATUM_WGS84:
1570                 ref_lla.lat = location.gpsLocation.latitude;
1571                 ref_lla.lon = location.gpsLocation.longitude;
1572                 ref_lla.alt = location.gpsLocation.altitude;
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             case LOC_GNSS_DATUM_PZ90:
1578                 ref_lla.lat = lla_p90.lat / M_PI * 180.0;
1579                 ref_lla.lon = lla_p90.lon / M_PI * 180.0;
1580                 ref_lla.alt = lla_p90.alt;
1581                 local_lla.lat = location.gpsLocation.latitude;
1582                 local_lla.lon = location.gpsLocation.longitude;
1583                 local_lla.alt = location.gpsLocation.altitude;
1584                 break;
1585             default:
1586                 break;
1587         }
1588 
1589         // -------------------
1590         // ------$--DTM-------
1591         // -------------------
1592         loc_nmea_generate_DTM(ref_lla, local_lla, talker, sentence_DTM, sizeof(sentence_DTM));
1593 
1594         // -------------------
1595         // ------$--RMC-------
1596         // -------------------
1597 
1598         pMarker = sentence_RMC;
1599         lengthRemaining = sizeof(sentence_RMC);
1600 
1601         bool validFix = ((0 != sv_cache_info.gps_used_mask) ||
1602                 (0 != sv_cache_info.glo_used_mask) ||
1603                 (0 != sv_cache_info.gal_used_mask) ||
1604                 (0 != sv_cache_info.qzss_used_mask) ||
1605                 (0 != sv_cache_info.bds_used_mask));
1606 
1607         if (validFix) {
1608             length = snprintf(pMarker, lengthRemaining, "$%sRMC,%02d%02d%02d.%02d,A,",
1609                               talker, utcHours, utcMinutes, utcSeconds, utcMSeconds/10);
1610         } else {
1611             length = snprintf(pMarker, lengthRemaining, "$%sRMC,%02d%02d%02d.%02d,V,",
1612                               talker, utcHours, utcMinutes, utcSeconds, utcMSeconds/10);
1613         }
1614 
1615         if (length < 0 || length >= lengthRemaining)
1616         {
1617             LOC_LOGE("NMEA Error in string formatting");
1618             return;
1619         }
1620         pMarker += length;
1621         lengthRemaining -= length;
1622 
1623         if (location.gpsLocation.flags & LOC_GPS_LOCATION_HAS_LAT_LONG)
1624         {
1625             double latitude = ref_lla.lat;
1626             double longitude = ref_lla.lon;
1627             char latHemisphere;
1628             char lonHemisphere;
1629             double latMinutes;
1630             double lonMinutes;
1631 
1632             if (latitude > 0)
1633             {
1634                 latHemisphere = 'N';
1635             }
1636             else
1637             {
1638                 latHemisphere = 'S';
1639                 latitude *= -1.0;
1640             }
1641 
1642             if (longitude < 0)
1643             {
1644                 lonHemisphere = 'W';
1645                 longitude *= -1.0;
1646             }
1647             else
1648             {
1649                 lonHemisphere = 'E';
1650             }
1651 
1652             latMinutes = fmod(latitude * 60.0 , 60.0);
1653             lonMinutes = fmod(longitude * 60.0 , 60.0);
1654 
1655             length = snprintf(pMarker, lengthRemaining, "%02d%09.6lf,%c,%03d%09.6lf,%c,",
1656                               (uint8_t)floor(latitude), latMinutes, latHemisphere,
1657                               (uint8_t)floor(longitude),lonMinutes, lonHemisphere);
1658         }
1659         else
1660         {
1661             length = snprintf(pMarker, lengthRemaining,",,,,");
1662         }
1663 
1664         if (length < 0 || length >= lengthRemaining)
1665         {
1666             LOC_LOGE("NMEA Error in string formatting");
1667             return;
1668         }
1669         pMarker += length;
1670         lengthRemaining -= length;
1671 
1672         if (location.gpsLocation.flags & LOC_GPS_LOCATION_HAS_SPEED)
1673         {
1674             float speedKnots = location.gpsLocation.speed * (3600.0/1852.0);
1675             length = snprintf(pMarker, lengthRemaining, "%.1lf,", speedKnots);
1676         }
1677         else
1678         {
1679             length = snprintf(pMarker, lengthRemaining, ",");
1680         }
1681 
1682         if (length < 0 || length >= lengthRemaining)
1683         {
1684             LOC_LOGE("NMEA Error in string formatting");
1685             return;
1686         }
1687         pMarker += length;
1688         lengthRemaining -= length;
1689 
1690         if (location.gpsLocation.flags & LOC_GPS_LOCATION_HAS_BEARING)
1691         {
1692             length = snprintf(pMarker, lengthRemaining, "%.1lf,", location.gpsLocation.bearing);
1693         }
1694         else
1695         {
1696             length = snprintf(pMarker, lengthRemaining, ",");
1697         }
1698 
1699         if (length < 0 || length >= lengthRemaining)
1700         {
1701             LOC_LOGE("NMEA Error in string formatting");
1702             return;
1703         }
1704         pMarker += length;
1705         lengthRemaining -= length;
1706 
1707         length = snprintf(pMarker, lengthRemaining, "%2.2d%2.2d%2.2d,",
1708                           utcDay, utcMonth, utcYear);
1709 
1710         if (length < 0 || length >= lengthRemaining)
1711         {
1712             LOC_LOGE("NMEA Error in string formatting");
1713             return;
1714         }
1715         pMarker += length;
1716         lengthRemaining -= length;
1717 
1718         if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_MAG_DEV)
1719         {
1720             float magneticVariation = locationExtended.magneticDeviation;
1721             char direction;
1722             if (magneticVariation < 0.0)
1723             {
1724                 direction = 'W';
1725                 magneticVariation *= -1.0;
1726             }
1727             else
1728             {
1729                 direction = 'E';
1730             }
1731 
1732             length = snprintf(pMarker, lengthRemaining, "%.1lf,%c,",
1733                               magneticVariation, direction);
1734         }
1735         else
1736         {
1737             length = snprintf(pMarker, lengthRemaining, ",,");
1738         }
1739 
1740         if (length < 0 || length >= lengthRemaining)
1741         {
1742             LOC_LOGE("NMEA Error in string formatting");
1743             return;
1744         }
1745         pMarker += length;
1746         lengthRemaining -= length;
1747 
1748         length = snprintf(pMarker, lengthRemaining, "%c", rmcModeIndicator);
1749         pMarker += length;
1750         lengthRemaining -= length;
1751 
1752         // hardcode Navigation Status field to 'V'
1753         length = snprintf(pMarker, lengthRemaining, ",%c", 'V');
1754 
1755         length = loc_nmea_put_checksum(sentence_RMC, sizeof(sentence_RMC), false);
1756 
1757         // -------------------
1758         // ------$--GNS-------
1759         // -------------------
1760 
1761         pMarker = sentence_GNS;
1762         lengthRemaining = sizeof(sentence_GNS);
1763 
1764         length = snprintf(pMarker, lengthRemaining, "$%sGNS,%02d%02d%02d.%02d," ,
1765                           talker, utcHours, utcMinutes, utcSeconds, utcMSeconds/10);
1766 
1767         if (length < 0 || length >= lengthRemaining)
1768         {
1769             LOC_LOGE("NMEA Error in string formatting");
1770             return;
1771         }
1772         pMarker += length;
1773         lengthRemaining -= length;
1774 
1775         if (location.gpsLocation.flags & LOC_GPS_LOCATION_HAS_LAT_LONG)
1776         {
1777             double latitude = ref_lla.lat;
1778             double longitude = ref_lla.lon;
1779             char latHemisphere;
1780             char lonHemisphere;
1781             double latMinutes;
1782             double lonMinutes;
1783 
1784             if (latitude > 0)
1785             {
1786                 latHemisphere = 'N';
1787             }
1788             else
1789             {
1790                 latHemisphere = 'S';
1791                 latitude *= -1.0;
1792             }
1793 
1794             if (longitude < 0)
1795             {
1796                 lonHemisphere = 'W';
1797                 longitude *= -1.0;
1798             }
1799             else
1800             {
1801                 lonHemisphere = 'E';
1802             }
1803 
1804             latMinutes = fmod(latitude * 60.0 , 60.0);
1805             lonMinutes = fmod(longitude * 60.0 , 60.0);
1806 
1807             length = snprintf(pMarker, lengthRemaining, "%02d%09.6lf,%c,%03d%09.6lf,%c,",
1808                               (uint8_t)floor(latitude), latMinutes, latHemisphere,
1809                               (uint8_t)floor(longitude),lonMinutes, lonHemisphere);
1810         }
1811         else
1812         {
1813             length = snprintf(pMarker, lengthRemaining,",,,,");
1814         }
1815 
1816         if (length < 0 || length >= lengthRemaining)
1817         {
1818             LOC_LOGE("NMEA Error in string formatting");
1819             return;
1820         }
1821         pMarker += length;
1822         lengthRemaining -= length;
1823 
1824         length = snprintf(pMarker, lengthRemaining, "%s,", gnsModeIndicator);
1825 
1826         pMarker += length;
1827         lengthRemaining -= length;
1828 
1829         if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_DOP) {
1830             length = snprintf(pMarker, lengthRemaining, "%02d,%.1f,",
1831                               svUsedCount, locationExtended.hdop);
1832         }
1833         else {   // no hdop
1834             length = snprintf(pMarker, lengthRemaining, "%02d,,",
1835                               svUsedCount);
1836         }
1837 
1838         if (length < 0 || length >= lengthRemaining)
1839         {
1840             LOC_LOGE("NMEA Error in string formatting");
1841             return;
1842         }
1843         pMarker += length;
1844         lengthRemaining -= length;
1845 
1846         if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_ALTITUDE_MEAN_SEA_LEVEL)
1847         {
1848             length = snprintf(pMarker, lengthRemaining, "%.1lf,",
1849                               locationExtended.altitudeMeanSeaLevel);
1850         }
1851         else
1852         {
1853             length = snprintf(pMarker, lengthRemaining,",");
1854         }
1855 
1856         if (length < 0 || length >= lengthRemaining)
1857         {
1858             LOC_LOGE("NMEA Error in string formatting");
1859             return;
1860         }
1861         pMarker += length;
1862         lengthRemaining -= length;
1863 
1864         if ((location.gpsLocation.flags & LOC_GPS_LOCATION_HAS_ALTITUDE) &&
1865             (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_ALTITUDE_MEAN_SEA_LEVEL))
1866         {
1867             length = snprintf(pMarker, lengthRemaining, "%.1lf,",
1868                               ref_lla.alt - locationExtended.altitudeMeanSeaLevel);
1869         }
1870         else
1871         {
1872             length = snprintf(pMarker, lengthRemaining, ",");
1873         }
1874         if (length < 0 || length >= lengthRemaining)
1875         {
1876             LOC_LOGE("NMEA Error in string formatting");
1877             return;
1878         }
1879         pMarker += length;
1880         lengthRemaining -= length;
1881 
1882         if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_DGNSS_DATA_AGE)
1883         {
1884             length = snprintf(pMarker, lengthRemaining, "%.1f,",
1885                               (float)locationExtended.dgnssDataAgeMsec / 1000);
1886         }
1887         else
1888         {
1889             length = snprintf(pMarker, lengthRemaining, ",");
1890         }
1891         if (length < 0 || length >= lengthRemaining)
1892         {
1893             LOC_LOGE("NMEA Error in string formatting");
1894             return;
1895         }
1896         pMarker += length;
1897         lengthRemaining -= length;
1898 
1899         if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_DGNSS_REF_STATION_ID)
1900         {
1901             length = snprintf(pMarker, lengthRemaining, "%04d",
1902                               locationExtended.dgnssRefStationId);
1903             if (length < 0 || length >= lengthRemaining)
1904             {
1905                 LOC_LOGE("NMEA Error in string formatting");
1906                 return;
1907             }
1908             pMarker += length;
1909             lengthRemaining -= length;
1910         }
1911 
1912         // hardcode Navigation Status field to 'V'
1913         length = snprintf(pMarker, lengthRemaining, ",%c", 'V');
1914         pMarker += length;
1915         lengthRemaining -= length;
1916 
1917         length = loc_nmea_put_checksum(sentence_GNS, sizeof(sentence_GNS), false);
1918 
1919         // -------------------
1920         // ------$--GGA-------
1921         // -------------------
1922 
1923         pMarker = sentence_GGA;
1924         lengthRemaining = sizeof(sentence_GGA);
1925 
1926         length = snprintf(pMarker, lengthRemaining, "$%sGGA,%02d%02d%02d.%02d," ,
1927                           talker, utcHours, utcMinutes, utcSeconds, utcMSeconds/10);
1928 
1929         if (length < 0 || length >= lengthRemaining)
1930         {
1931             LOC_LOGE("NMEA Error in string formatting");
1932             return;
1933         }
1934         pMarker += length;
1935         lengthRemaining -= length;
1936 
1937         if (location.gpsLocation.flags & LOC_GPS_LOCATION_HAS_LAT_LONG)
1938         {
1939             double latitude = ref_lla.lat;
1940             double longitude = ref_lla.lon;
1941             char latHemisphere;
1942             char lonHemisphere;
1943             double latMinutes;
1944             double lonMinutes;
1945 
1946             if (latitude > 0)
1947             {
1948                 latHemisphere = 'N';
1949             }
1950             else
1951             {
1952                 latHemisphere = 'S';
1953                 latitude *= -1.0;
1954             }
1955 
1956             if (longitude < 0)
1957             {
1958                 lonHemisphere = 'W';
1959                 longitude *= -1.0;
1960             }
1961             else
1962             {
1963                 lonHemisphere = 'E';
1964             }
1965 
1966             latMinutes = fmod(latitude * 60.0 , 60.0);
1967             lonMinutes = fmod(longitude * 60.0 , 60.0);
1968 
1969             length = snprintf(pMarker, lengthRemaining, "%02d%09.6lf,%c,%03d%09.6lf,%c,",
1970                               (uint8_t)floor(latitude), latMinutes, latHemisphere,
1971                               (uint8_t)floor(longitude),lonMinutes, lonHemisphere);
1972         }
1973         else
1974         {
1975             length = snprintf(pMarker, lengthRemaining,",,,,");
1976         }
1977 
1978         if (length < 0 || length >= lengthRemaining)
1979         {
1980             LOC_LOGE("NMEA Error in string formatting");
1981             return;
1982         }
1983         pMarker += length;
1984         lengthRemaining -= length;
1985 
1986         // Number of satellites in use, 00-12
1987         if (svUsedCount > MAX_SATELLITES_IN_USE)
1988             svUsedCount = MAX_SATELLITES_IN_USE;
1989         if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_DOP)
1990         {
1991             length = snprintf(pMarker, lengthRemaining, "%s,%02d,%.1f,",
1992                               ggaGpsQuality, svUsedCount, locationExtended.hdop);
1993         }
1994         else
1995         {   // no hdop
1996             length = snprintf(pMarker, lengthRemaining, "%s,%02d,,",
1997                               ggaGpsQuality, svUsedCount);
1998         }
1999 
2000         if (length < 0 || length >= lengthRemaining)
2001         {
2002             LOC_LOGE("NMEA Error in string formatting");
2003             return;
2004         }
2005         pMarker += length;
2006         lengthRemaining -= length;
2007 
2008         if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_ALTITUDE_MEAN_SEA_LEVEL)
2009         {
2010             length = snprintf(pMarker, lengthRemaining, "%.1lf,M,",
2011                               locationExtended.altitudeMeanSeaLevel);
2012         }
2013         else
2014         {
2015             length = snprintf(pMarker, lengthRemaining,",,");
2016         }
2017 
2018         if (length < 0 || length >= lengthRemaining)
2019         {
2020             LOC_LOGE("NMEA Error in string formatting");
2021             return;
2022         }
2023         pMarker += length;
2024         lengthRemaining -= length;
2025 
2026         if ((location.gpsLocation.flags & LOC_GPS_LOCATION_HAS_ALTITUDE) &&
2027             (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_ALTITUDE_MEAN_SEA_LEVEL))
2028         {
2029             length = snprintf(pMarker, lengthRemaining, "%.1lf,M,",
2030                               ref_lla.alt - locationExtended.altitudeMeanSeaLevel);
2031         }
2032         else
2033         {
2034             length = snprintf(pMarker, lengthRemaining, ",,");
2035         }
2036         if (length < 0 || length >= lengthRemaining)
2037         {
2038             LOC_LOGE("NMEA Error in string formatting");
2039             return;
2040         }
2041         pMarker += length;
2042         lengthRemaining -= length;
2043 
2044         if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_DGNSS_DATA_AGE)
2045         {
2046             length = snprintf(pMarker, lengthRemaining, "%.1f,",
2047                               (float)locationExtended.dgnssDataAgeMsec / 1000);
2048         }
2049         else
2050         {
2051             length = snprintf(pMarker, lengthRemaining, ",");
2052         }
2053         if (length < 0 || length >= lengthRemaining)
2054         {
2055             LOC_LOGE("NMEA Error in string formatting");
2056             return;
2057         }
2058         pMarker += length;
2059         lengthRemaining -= length;
2060 
2061         if (locationExtended.flags & GPS_LOCATION_EXTENDED_HAS_DGNSS_REF_STATION_ID)
2062         {
2063             length = snprintf(pMarker, lengthRemaining, "%04d",
2064                               locationExtended.dgnssRefStationId);
2065             if (length < 0 || length >= lengthRemaining)
2066             {
2067                 LOC_LOGE("NMEA Error in string formatting");
2068                 return;
2069             }
2070             pMarker += length;
2071             lengthRemaining -= length;
2072         }
2073 
2074         length = loc_nmea_put_checksum(sentence_GGA, sizeof(sentence_GGA), false);
2075 
2076         // ------$--DTM-------
2077         nmeaArraystr.push_back(sentence_DTM);
2078         // ------$--RMC-------
2079         nmeaArraystr.push_back(sentence_RMC);
2080         if(LOC_GNSS_DATUM_PZ90 == datum_type) {
2081             // ------$--DTM-------
2082             nmeaArraystr.push_back(sentence_DTM);
2083         }
2084         // ------$--GNS-------
2085         nmeaArraystr.push_back(sentence_GNS);
2086         if(LOC_GNSS_DATUM_PZ90 == datum_type) {
2087             // ------$--DTM-------
2088             nmeaArraystr.push_back(sentence_DTM);
2089         }
2090         // ------$--GGA-------
2091         nmeaArraystr.push_back(sentence_GGA);
2092         indexOfGGA = static_cast<int>(nmeaArraystr.size() - 1);
2093     }
2094     //Send blank NMEA reports for non-final fixes
2095     else {
2096         strlcpy(sentence, "$GPGSA,A,1,,,,,,,,,,,,,,,,", sizeof(sentence));
2097         length = loc_nmea_put_checksum(sentence, sizeof(sentence), false);
2098         nmeaArraystr.push_back(sentence);
2099 
2100         strlcpy(sentence, "$GPVTG,,T,,M,,N,,K,N", sizeof(sentence));
2101         length = loc_nmea_put_checksum(sentence, sizeof(sentence), false);
2102         nmeaArraystr.push_back(sentence);
2103 
2104         strlcpy(sentence, "$GPDTM,,,,,,,,", sizeof(sentence));
2105         length = loc_nmea_put_checksum(sentence, sizeof(sentence), false);
2106         nmeaArraystr.push_back(sentence);
2107 
2108         strlcpy(sentence, "$GPRMC,,V,,,,,,,,,,N,V", sizeof(sentence));
2109         length = loc_nmea_put_checksum(sentence, sizeof(sentence), false);
2110         nmeaArraystr.push_back(sentence);
2111 
2112         strlcpy(sentence, "$GPGNS,,,,,,N,,,,,,,V", sizeof(sentence));
2113         length = loc_nmea_put_checksum(sentence, sizeof(sentence), false);
2114         nmeaArraystr.push_back(sentence);
2115 
2116         strlcpy(sentence, "$GPGGA,,,,,,0,,,,,,,,", sizeof(sentence));
2117         length = loc_nmea_put_checksum(sentence, sizeof(sentence), false);
2118         nmeaArraystr.push_back(sentence);
2119     }
2120 
2121     EXIT_LOG(%d, 0);
2122 }
2123 
2124 
2125 
2126 /*===========================================================================
2127 FUNCTION    loc_nmea_generate_sv
2128 
2129 DESCRIPTION
2130    Generate NMEA sentences generated based on sv report
2131 
2132 DEPENDENCIES
2133    NONE
2134 
2135 RETURN VALUE
2136    0
2137 
2138 SIDE EFFECTS
2139    N/A
2140 
2141 ===========================================================================*/
loc_nmea_generate_sv(const GnssSvNotification & svNotify,std::vector<std::string> & nmeaArraystr)2142 void loc_nmea_generate_sv(const GnssSvNotification &svNotify,
2143                               std::vector<std::string> &nmeaArraystr)
2144 {
2145     ENTRY_LOG();
2146 
2147     char sentence[NMEA_SENTENCE_MAX_LENGTH] = {0};
2148     loc_sv_cache_info sv_cache_info = {};
2149 
2150     //Count GPS SVs for saparating GPS from GLONASS and throw others
2151     for (uint32_t svOffset = 0; svOffset < svNotify.count; svOffset++) {
2152         if ((GNSS_SV_TYPE_GPS == svNotify.gnssSvs[svOffset].type) ||
2153             (GNSS_SV_TYPE_SBAS == svNotify.gnssSvs[svOffset].type))
2154         {
2155             if (GNSS_SIGNAL_GPS_L5 == svNotify.gnssSvs[svOffset].gnssSignalTypeMask) {
2156                 sv_cache_info.gps_l5_count++;
2157             } else if (GNSS_SIGNAL_GPS_L2 == svNotify.gnssSvs[svOffset].gnssSignalTypeMask) {
2158                 sv_cache_info.gps_l2_count++;
2159             } else {
2160                 // GNSS_SIGNAL_GPS_L1CA, GNSS_SIGNAL_SBAS_L1 or default
2161                 // If no signal type in report, it means default L1
2162                 sv_cache_info.gps_l1_count++;
2163             }
2164         }
2165         else if (GNSS_SV_TYPE_GLONASS == svNotify.gnssSvs[svOffset].type)
2166         {
2167             if (GNSS_SIGNAL_GLONASS_G2 == svNotify.gnssSvs[svOffset].gnssSignalTypeMask){
2168                 sv_cache_info.glo_g2_count++;
2169             } else {
2170                 // GNSS_SIGNAL_GLONASS_G1 or default
2171                 // If no signal type in report, it means default G1
2172                 sv_cache_info.glo_g1_count++;
2173             }
2174         }
2175         else if (GNSS_SV_TYPE_GALILEO == svNotify.gnssSvs[svOffset].type)
2176         {
2177             if(GNSS_SIGNAL_GALILEO_E5A == svNotify.gnssSvs[svOffset].gnssSignalTypeMask){
2178                 sv_cache_info.gal_e5_count++;
2179             } else if (GNSS_SIGNAL_GALILEO_E5B == svNotify.gnssSvs[svOffset].gnssSignalTypeMask) {
2180                 sv_cache_info.gal_e5b_count++;
2181             } else {
2182                 // GNSS_SIGNAL_GALILEO_E1 or default
2183                 // If no signal type in report, it means default E1
2184                 sv_cache_info.gal_e1_count++;
2185             }
2186         }
2187         else if (GNSS_SV_TYPE_QZSS == svNotify.gnssSvs[svOffset].type)
2188         {
2189             if (GNSS_SIGNAL_QZSS_L5 == svNotify.gnssSvs[svOffset].gnssSignalTypeMask) {
2190                 sv_cache_info.qzss_l5_count++;
2191             } else if (GNSS_SIGNAL_QZSS_L2 == svNotify.gnssSvs[svOffset].gnssSignalTypeMask) {
2192                 sv_cache_info.qzss_l2_count++;
2193             } else {
2194                 // GNSS_SIGNAL_QZSS_L1CA or default
2195                 // If no signal type in report, it means default L1
2196                 sv_cache_info.qzss_l1_count++;
2197             }
2198         }
2199         else if (GNSS_SV_TYPE_BEIDOU == svNotify.gnssSvs[svOffset].type)
2200         {
2201             // cache the used in fix mask, as it will be needed to send $PQGSA
2202             // during the position report
2203             if (GNSS_SV_OPTIONS_USED_IN_FIX_BIT ==
2204                 (svNotify.gnssSvs[svOffset].gnssSvOptionsMask &
2205                   GNSS_SV_OPTIONS_USED_IN_FIX_BIT))
2206             {
2207                 setSvMask(sv_cache_info.bds_used_mask, svNotify.gnssSvs[svOffset].svId);
2208             }
2209             if ((GNSS_SIGNAL_BEIDOU_B2AI == svNotify.gnssSvs[svOffset].gnssSignalTypeMask) ||
2210                    (GNSS_SIGNAL_BEIDOU_B2AQ == svNotify.gnssSvs[svOffset].gnssSignalTypeMask)) {
2211                 sv_cache_info.bds_b2_count++;
2212             } else if (GNSS_SIGNAL_BEIDOU_B1C == svNotify.gnssSvs[svOffset].gnssSignalTypeMask) {
2213                 sv_cache_info.bds_b1c_count++;
2214             } else {
2215                 // GNSS_SIGNAL_BEIDOU_B1I or default
2216                 // If no signal type in report, it means default B1I
2217                 sv_cache_info.bds_b1i_count++;
2218             }
2219         }
2220         else if (GNSS_SV_TYPE_NAVIC == svNotify.gnssSvs[svOffset].type)
2221         {
2222             // GNSS_SIGNAL_NAVIC_L5 is the only signal type for NAVIC
2223             sv_cache_info.navic_l5_count++;
2224         }
2225     }
2226 
2227     loc_nmea_sv_meta sv_meta;
2228     // ---------------------
2229     // ------$GPGSV:L1CA----
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_L1CA, false), nmeaArraystr);
2235 
2236     // ---------------------
2237     // ------$GPGSV:L5------
2238     // ---------------------
2239 
2240     loc_nmea_generate_GSV(svNotify, sentence, sizeof(sentence),
2241             loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_GPS,
2242             GNSS_SIGNAL_GPS_L5, false), nmeaArraystr);
2243 
2244     // ---------------------
2245     // ------$GPGSV:L2------
2246     // ---------------------
2247     loc_nmea_generate_GSV(svNotify, sentence, sizeof(sentence),
2248             loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_GPS,
2249             GNSS_SIGNAL_GPS_L2, false), nmeaArraystr);
2250 
2251     // ---------------------
2252     // ------$GLGSV:G1------
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_G1, false), nmeaArraystr);
2258 
2259     // ---------------------
2260     // ------$GLGSV:G2------
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_GLONASS,
2265             GNSS_SIGNAL_GLONASS_G2, false), nmeaArraystr);
2266 
2267     // ---------------------
2268     // ------$GAGSV:E1------
2269     // ---------------------
2270 
2271     loc_nmea_generate_GSV(svNotify, sentence, sizeof(sentence),
2272             loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_GALILEO,
2273             GNSS_SIGNAL_GALILEO_E1, false), nmeaArraystr);
2274 
2275     // -------------------------
2276     // ------$GAGSV:E5A---------
2277     // -------------------------
2278     loc_nmea_generate_GSV(svNotify, sentence, sizeof(sentence),
2279             loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_GALILEO,
2280             GNSS_SIGNAL_GALILEO_E5A, false), nmeaArraystr);
2281 
2282     // -------------------------
2283     // ------$GAGSV:E5B---------
2284     // -------------------------
2285     loc_nmea_generate_GSV(svNotify, sentence, sizeof(sentence),
2286             loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_GALILEO,
2287             GNSS_SIGNAL_GALILEO_E5B, false), nmeaArraystr);
2288 
2289     // -----------------------------
2290     // ------$GQGSV (QZSS):L1CA-----
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_L1CA, false), nmeaArraystr);
2296 
2297     // -----------------------------
2298     // ------$GQGSV (QZSS):L5-------
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_L5, false), nmeaArraystr);
2304 
2305     // -----------------------------
2306     // ------$GQGSV (QZSS):L2-------
2307     // -----------------------------
2308 
2309     loc_nmea_generate_GSV(svNotify, sentence, sizeof(sentence),
2310             loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_QZSS,
2311             GNSS_SIGNAL_QZSS_L2, false), nmeaArraystr);
2312 
2313 
2314     // -----------------------------
2315     // ------$GBGSV (BEIDOU:B1I)----
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_B1I, false), nmeaArraystr);
2321 
2322     // -----------------------------
2323     // ------$GBGSV (BEIDOU:B1C)----
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_B1C, false), nmeaArraystr);
2329 
2330     // -----------------------------
2331     // ------$GBGSV (BEIDOU:B2AI)---
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_BEIDOU,
2336             GNSS_SIGNAL_BEIDOU_B2AI, false), nmeaArraystr);
2337 
2338     // -----------------------------
2339     // ------$GIGSV (NAVIC:L5)------
2340     // -----------------------------
2341 
2342     loc_nmea_generate_GSV(svNotify, sentence, sizeof(sentence),
2343             loc_nmea_sv_meta_init(sv_meta, sv_cache_info, GNSS_SV_TYPE_NAVIC,
2344             GNSS_SIGNAL_NAVIC_L5,false), nmeaArraystr);
2345 
2346     EXIT_LOG(%d, 0);
2347 }
2348