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