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