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