1 /* 2 * Copyright (c) 2023 Trackunit Corporation 3 * 4 * SPDX-License-Identifier: Apache-2.0 5 */ 6 7 #include <zephyr/kernel.h> 8 9 #include <string.h> 10 #include <stdarg.h> 11 #include <stdarg.h> 12 13 #include "gnss_nmea0183.h" 14 #include "gnss_parse.h" 15 16 #define GNSS_NMEA0183_PICO_DEGREES_IN_DEGREE (1000000000000ULL) 17 #define GNSS_NMEA0183_PICO_DEGREES_IN_MINUTE (GNSS_NMEA0183_PICO_DEGREES_IN_DEGREE / 60ULL) 18 #define GNSS_NMEA0183_PICO_DEGREES_IN_NANO_DEGREE (1000ULL) 19 #define GNSS_NMEA0183_NANO_KNOTS_IN_MMS (1943861LL) 20 21 #define GNSS_NMEA0183_MESSAGE_SIZE_MIN (6) 22 #define GNSS_NMEA0183_MESSAGE_CHECKSUM_SIZE (3) 23 24 #define GNSS_NMEA0183_GSV_HDR_ARG_CNT (4) 25 #define GNSS_NMEA0183_GSV_SV_ARG_CNT (4) 26 27 #define GNSS_NMEA0183_GSV_PRN_GPS_RANGE (32) 28 #define GNSS_NMEA0183_GSV_PRN_SBAS_OFFSET (87) 29 #define GNSS_NMEA0183_GSV_PRN_GLONASS_OFFSET (64) 30 #define GNSS_NMEA0183_GSV_PRN_BEIDOU_OFFSET (100) 31 32 struct gsv_header_args { 33 const char *message_id; 34 const char *number_of_messages; 35 const char *message_number; 36 const char *numver_of_svs; 37 }; 38 39 struct gsv_sv_args { 40 const char *prn; 41 const char *elevation; 42 const char *azimuth; 43 const char *snr; 44 }; 45 gnss_system_from_gsv_header_args(const struct gsv_header_args * args,enum gnss_system * sv_system)46 static int gnss_system_from_gsv_header_args(const struct gsv_header_args *args, 47 enum gnss_system *sv_system) 48 { 49 switch (args->message_id[2]) { 50 case 'A': 51 *sv_system = GNSS_SYSTEM_GALILEO; 52 break; 53 case 'B': 54 *sv_system = GNSS_SYSTEM_BEIDOU; 55 break; 56 case 'P': 57 *sv_system = GNSS_SYSTEM_GPS; 58 break; 59 case 'L': 60 *sv_system = GNSS_SYSTEM_GLONASS; 61 break; 62 case 'Q': 63 *sv_system = GNSS_SYSTEM_QZSS; 64 break; 65 default: 66 return -EINVAL; 67 } 68 69 return 0; 70 } 71 align_satellite_with_gnss_system(enum gnss_system sv_system,struct gnss_satellite * satellite)72 static void align_satellite_with_gnss_system(enum gnss_system sv_system, 73 struct gnss_satellite *satellite) 74 { 75 switch (sv_system) { 76 case GNSS_SYSTEM_GPS: 77 if (satellite->prn > GNSS_NMEA0183_GSV_PRN_GPS_RANGE) { 78 satellite->system = GNSS_SYSTEM_SBAS; 79 satellite->prn += GNSS_NMEA0183_GSV_PRN_SBAS_OFFSET; 80 break; 81 } 82 83 satellite->system = GNSS_SYSTEM_GPS; 84 break; 85 86 case GNSS_SYSTEM_GLONASS: 87 satellite->system = GNSS_SYSTEM_GLONASS; 88 satellite->prn -= GNSS_NMEA0183_GSV_PRN_GLONASS_OFFSET; 89 break; 90 91 case GNSS_SYSTEM_GALILEO: 92 satellite->system = GNSS_SYSTEM_GALILEO; 93 break; 94 95 case GNSS_SYSTEM_BEIDOU: 96 satellite->system = GNSS_SYSTEM_BEIDOU; 97 satellite->prn -= GNSS_NMEA0183_GSV_PRN_BEIDOU_OFFSET; 98 break; 99 100 case GNSS_SYSTEM_QZSS: 101 satellite->system = GNSS_SYSTEM_QZSS; 102 break; 103 104 case GNSS_SYSTEM_IRNSS: 105 case GNSS_SYSTEM_IMES: 106 case GNSS_SYSTEM_SBAS: 107 break; 108 } 109 } 110 gnss_nmea0183_checksum(const char * str)111 uint8_t gnss_nmea0183_checksum(const char *str) 112 { 113 uint8_t checksum = 0; 114 size_t end; 115 116 __ASSERT(str != NULL, "str argument must be provided"); 117 118 end = strlen(str); 119 for (size_t i = 0; i < end; i++) { 120 checksum = checksum ^ str[i]; 121 } 122 123 return checksum; 124 } 125 gnss_nmea0183_snprintk(char * str,size_t size,const char * fmt,...)126 int gnss_nmea0183_snprintk(char *str, size_t size, const char *fmt, ...) 127 { 128 va_list ap; 129 uint8_t checksum; 130 int pos; 131 int len; 132 133 __ASSERT(str != NULL, "str argument must be provided"); 134 __ASSERT(fmt != NULL, "fmt argument must be provided"); 135 136 if (size < GNSS_NMEA0183_MESSAGE_SIZE_MIN) { 137 return -ENOMEM; 138 } 139 140 str[0] = '$'; 141 142 va_start(ap, fmt); 143 pos = vsnprintk(&str[1], size - 1, fmt, ap) + 1; 144 va_end(ap); 145 146 if (pos < 0) { 147 return -EINVAL; 148 } 149 150 len = pos + GNSS_NMEA0183_MESSAGE_CHECKSUM_SIZE; 151 152 if ((size - 1) < len) { 153 return -ENOMEM; 154 } 155 156 checksum = gnss_nmea0183_checksum(&str[1]); 157 pos = snprintk(&str[pos], size - pos, "*%02X", checksum); 158 if (pos != 3) { 159 return -EINVAL; 160 } 161 162 str[len] = '\0'; 163 return len; 164 } 165 gnss_nmea0183_ddmm_mmmm_to_ndeg(const char * ddmm_mmmm,int64_t * ndeg)166 int gnss_nmea0183_ddmm_mmmm_to_ndeg(const char *ddmm_mmmm, int64_t *ndeg) 167 { 168 uint64_t pico_degrees = 0; 169 int8_t decimal = -1; 170 int8_t pos = 0; 171 uint64_t increment; 172 173 __ASSERT(ddmm_mmmm != NULL, "ddmm_mmmm argument must be provided"); 174 __ASSERT(ndeg != NULL, "ndeg argument must be provided"); 175 176 /* Find decimal */ 177 while (ddmm_mmmm[pos] != '\0') { 178 /* Verify if char is decimal */ 179 if (ddmm_mmmm[pos] == '.') { 180 decimal = pos; 181 break; 182 } 183 184 /* Advance position */ 185 pos++; 186 } 187 188 /* Verify decimal was found and placed correctly */ 189 if (decimal < 1) { 190 return -EINVAL; 191 } 192 193 /* Validate potential degree fraction is within bounds */ 194 if (decimal > 1 && ddmm_mmmm[decimal - 2] > '5') { 195 return -EINVAL; 196 } 197 198 /* Convert minute fraction to pico degrees and add it to pico_degrees */ 199 pos = decimal + 1; 200 increment = (GNSS_NMEA0183_PICO_DEGREES_IN_MINUTE / 10); 201 while (ddmm_mmmm[pos] != '\0') { 202 /* Verify char is decimal */ 203 if (ddmm_mmmm[pos] < '0' || ddmm_mmmm[pos] > '9') { 204 return -EINVAL; 205 } 206 207 /* Add increment to pico_degrees */ 208 pico_degrees += (ddmm_mmmm[pos] - '0') * increment; 209 210 /* Update unit */ 211 increment /= 10; 212 213 /* Increment position */ 214 pos++; 215 } 216 217 /* Convert minutes and degrees to pico_degrees */ 218 pos = decimal - 1; 219 increment = GNSS_NMEA0183_PICO_DEGREES_IN_MINUTE; 220 while (pos >= 0) { 221 /* Check if digit switched from minutes to degrees */ 222 if ((decimal - pos) == 3) { 223 /* Reset increment to degrees */ 224 increment = GNSS_NMEA0183_PICO_DEGREES_IN_DEGREE; 225 } 226 227 /* Verify char is decimal */ 228 if (ddmm_mmmm[pos] < '0' || ddmm_mmmm[pos] > '9') { 229 return -EINVAL; 230 } 231 232 /* Add increment to pico_degrees */ 233 pico_degrees += (ddmm_mmmm[pos] - '0') * increment; 234 235 /* Update unit */ 236 increment *= 10; 237 238 /* Decrement position */ 239 pos--; 240 } 241 242 /* Convert to nano degrees */ 243 *ndeg = (int64_t)(pico_degrees / GNSS_NMEA0183_PICO_DEGREES_IN_NANO_DEGREE); 244 return 0; 245 } 246 gnss_nmea0183_validate_message(char ** argv,uint16_t argc)247 bool gnss_nmea0183_validate_message(char **argv, uint16_t argc) 248 { 249 int32_t tmp = 0; 250 uint8_t checksum = 0; 251 size_t len; 252 253 __ASSERT(argv != NULL, "argv argument must be provided"); 254 255 /* Message must contain message id and checksum */ 256 if (argc < 2) { 257 return false; 258 } 259 260 /* First argument should start with '$' which is not covered by checksum */ 261 if ((argc < 1) || (argv[0][0] != '$')) { 262 return false; 263 } 264 265 len = strlen(argv[0]); 266 for (uint16_t u = 1; u < len; u++) { 267 checksum ^= argv[0][u]; 268 } 269 checksum ^= ','; 270 271 /* Cover all except last argument which contains the checksum*/ 272 for (uint16_t i = 1; i < (argc - 1); i++) { 273 len = strlen(argv[i]); 274 for (uint16_t u = 0; u < len; u++) { 275 checksum ^= argv[i][u]; 276 } 277 checksum ^= ','; 278 } 279 280 if ((gnss_parse_atoi(argv[argc - 1], 16, &tmp) < 0) || 281 (tmp > UINT8_MAX) || 282 (tmp < 0)) { 283 return false; 284 } 285 286 return checksum == (uint8_t)tmp; 287 } 288 gnss_nmea0183_knots_to_mms(const char * str,int64_t * mms)289 int gnss_nmea0183_knots_to_mms(const char *str, int64_t *mms) 290 { 291 int ret; 292 293 __ASSERT(str != NULL, "str argument must be provided"); 294 __ASSERT(mms != NULL, "mms argument must be provided"); 295 296 ret = gnss_parse_dec_to_nano(str, mms); 297 if (ret < 0) { 298 return ret; 299 } 300 301 *mms = (*mms) / GNSS_NMEA0183_NANO_KNOTS_IN_MMS; 302 return 0; 303 } 304 gnss_nmea0183_parse_hhmmss(const char * hhmmss,struct gnss_time * utc)305 int gnss_nmea0183_parse_hhmmss(const char *hhmmss, struct gnss_time *utc) 306 { 307 int64_t i64; 308 int32_t i32; 309 char part[3] = {0}; 310 311 __ASSERT(hhmmss != NULL, "hhmmss argument must be provided"); 312 __ASSERT(utc != NULL, "utc argument must be provided"); 313 314 if (strlen(hhmmss) < 6) { 315 return -EINVAL; 316 } 317 318 memcpy(part, hhmmss, 2); 319 if ((gnss_parse_atoi(part, 10, &i32) < 0) || 320 (i32 < 0) || 321 (i32 > 23)) { 322 return -EINVAL; 323 } 324 325 utc->hour = (uint8_t)i32; 326 327 memcpy(part, &hhmmss[2], 2); 328 if ((gnss_parse_atoi(part, 10, &i32) < 0) || 329 (i32 < 0) || 330 (i32 > 59)) { 331 return -EINVAL; 332 } 333 334 utc->minute = (uint8_t)i32; 335 336 if ((gnss_parse_dec_to_milli(&hhmmss[4], &i64) < 0) || 337 (i64 < 0) || 338 (i64 > 59999)) { 339 return -EINVAL; 340 } 341 342 utc->millisecond = (uint16_t)i64; 343 return 0; 344 } 345 gnss_nmea0183_parse_ddmmyy(const char * ddmmyy,struct gnss_time * utc)346 int gnss_nmea0183_parse_ddmmyy(const char *ddmmyy, struct gnss_time *utc) 347 { 348 int32_t i32; 349 char part[3] = {0}; 350 351 __ASSERT(ddmmyy != NULL, "ddmmyy argument must be provided"); 352 __ASSERT(utc != NULL, "utc argument must be provided"); 353 354 if (strlen(ddmmyy) != 6) { 355 return -EINVAL; 356 } 357 358 memcpy(part, ddmmyy, 2); 359 if ((gnss_parse_atoi(part, 10, &i32) < 0) || 360 (i32 < 1) || 361 (i32 > 31)) { 362 return -EINVAL; 363 } 364 365 utc->month_day = (uint8_t)i32; 366 367 memcpy(part, &ddmmyy[2], 2); 368 if ((gnss_parse_atoi(part, 10, &i32) < 0) || 369 (i32 < 1) || 370 (i32 > 12)) { 371 return -EINVAL; 372 } 373 374 utc->month = (uint8_t)i32; 375 376 memcpy(part, &ddmmyy[4], 2); 377 if ((gnss_parse_atoi(part, 10, &i32) < 0) || 378 (i32 < 0) || 379 (i32 > 99)) { 380 return -EINVAL; 381 } 382 383 utc->century_year = (uint8_t)i32; 384 return 0; 385 } 386 gnss_nmea0183_parse_rmc(const char ** argv,uint16_t argc,struct gnss_data * data)387 int gnss_nmea0183_parse_rmc(const char **argv, uint16_t argc, struct gnss_data *data) 388 { 389 int64_t tmp; 390 391 __ASSERT(argv != NULL, "argv argument must be provided"); 392 __ASSERT(data != NULL, "data argument must be provided"); 393 394 if (argc < 10) { 395 return -EINVAL; 396 } 397 398 /* Validate GNSS has fix */ 399 if (argv[2][0] == 'V') { 400 return 0; 401 } 402 403 if (argv[2][0] != 'A') { 404 return -EINVAL; 405 } 406 407 /* Parse UTC time */ 408 if ((gnss_nmea0183_parse_hhmmss(argv[1], &data->utc) < 0)) { 409 return -EINVAL; 410 } 411 412 /* Validate cardinal directions */ 413 if (((argv[4][0] != 'N') && (argv[4][0] != 'S')) || 414 ((argv[6][0] != 'E') && (argv[6][0] != 'W'))) { 415 return -EINVAL; 416 } 417 418 /* Parse coordinates */ 419 if ((gnss_nmea0183_ddmm_mmmm_to_ndeg(argv[3], &data->nav_data.latitude) < 0) || 420 (gnss_nmea0183_ddmm_mmmm_to_ndeg(argv[5], &data->nav_data.longitude) < 0)) { 421 return -EINVAL; 422 } 423 424 /* Align sign of coordinates with cardinal directions */ 425 data->nav_data.latitude = argv[4][0] == 'N' 426 ? data->nav_data.latitude 427 : -data->nav_data.latitude; 428 429 data->nav_data.longitude = argv[6][0] == 'E' 430 ? data->nav_data.longitude 431 : -data->nav_data.longitude; 432 433 /* Parse speed */ 434 if ((gnss_nmea0183_knots_to_mms(argv[7], &tmp) < 0) || 435 (tmp > UINT32_MAX)) { 436 return -EINVAL; 437 } 438 439 data->nav_data.speed = (uint32_t)tmp; 440 441 /* Parse bearing */ 442 if ((gnss_parse_dec_to_milli(argv[8], &tmp) < 0) || 443 (tmp > 359999) || 444 (tmp < 0)) { 445 return -EINVAL; 446 } 447 448 data->nav_data.bearing = (uint32_t)tmp; 449 450 /* Parse UTC date */ 451 if ((gnss_nmea0183_parse_ddmmyy(argv[9], &data->utc) < 0)) { 452 return -EINVAL; 453 } 454 455 return 0; 456 } 457 parse_gga_fix_quality(const char * str,enum gnss_fix_quality * fix_quality)458 static int parse_gga_fix_quality(const char *str, enum gnss_fix_quality *fix_quality) 459 { 460 __ASSERT(str != NULL, "str argument must be provided"); 461 __ASSERT(fix_quality != NULL, "fix_quality argument must be provided"); 462 463 if ((str[1] != ((char)'\0')) || (str[0] < ((char)'0')) || (((char)'6') < str[0])) { 464 return -EINVAL; 465 } 466 467 (*fix_quality) = (enum gnss_fix_quality)(str[0] - ((char)'0')); 468 return 0; 469 } 470 fix_status_from_fix_quality(enum gnss_fix_quality fix_quality)471 static enum gnss_fix_status fix_status_from_fix_quality(enum gnss_fix_quality fix_quality) 472 { 473 enum gnss_fix_status fix_status = GNSS_FIX_STATUS_NO_FIX; 474 475 switch (fix_quality) { 476 case GNSS_FIX_QUALITY_GNSS_SPS: 477 case GNSS_FIX_QUALITY_GNSS_PPS: 478 fix_status = GNSS_FIX_STATUS_GNSS_FIX; 479 break; 480 481 case GNSS_FIX_QUALITY_DGNSS: 482 case GNSS_FIX_QUALITY_RTK: 483 case GNSS_FIX_QUALITY_FLOAT_RTK: 484 fix_status = GNSS_FIX_STATUS_DGNSS_FIX; 485 break; 486 487 case GNSS_FIX_QUALITY_ESTIMATED: 488 fix_status = GNSS_FIX_STATUS_ESTIMATED_FIX; 489 break; 490 491 default: 492 break; 493 } 494 495 return fix_status; 496 } 497 gnss_nmea0183_parse_gga(const char ** argv,uint16_t argc,struct gnss_data * data)498 int gnss_nmea0183_parse_gga(const char **argv, uint16_t argc, struct gnss_data *data) 499 { 500 int32_t tmp32; 501 int64_t tmp64; 502 503 __ASSERT(argv != NULL, "argv argument must be provided"); 504 __ASSERT(data != NULL, "data argument must be provided"); 505 506 if (argc < 12) { 507 return -EINVAL; 508 } 509 510 /* Parse fix quality and status */ 511 if (parse_gga_fix_quality(argv[6], &data->info.fix_quality) < 0) { 512 return -EINVAL; 513 } 514 515 data->info.fix_status = fix_status_from_fix_quality(data->info.fix_quality); 516 517 /* Validate GNSS has fix */ 518 if (data->info.fix_status == GNSS_FIX_STATUS_NO_FIX) { 519 return 0; 520 } 521 522 /* Parse number of satellites */ 523 if ((gnss_parse_atoi(argv[7], 10, &tmp32) < 0) || 524 (tmp32 > UINT16_MAX) || 525 (tmp32 < 0)) { 526 return -EINVAL; 527 } 528 529 data->info.satellites_cnt = (uint16_t)tmp32; 530 531 /* Parse HDOP */ 532 if ((gnss_parse_dec_to_milli(argv[8], &tmp64) < 0) || 533 (tmp64 > UINT32_MAX) || 534 (tmp64 < 0)) { 535 return -EINVAL; 536 } 537 538 data->info.hdop = (uint16_t)tmp64; 539 540 /* Parse altitude */ 541 if ((gnss_parse_dec_to_milli(argv[9], &tmp64) < 0) || 542 (tmp64 > INT32_MAX) || 543 (tmp64 < INT32_MIN)) { 544 return -EINVAL; 545 } 546 547 data->nav_data.altitude = (int32_t)tmp64; 548 549 /* Parse geoid separation */ 550 if ((gnss_parse_dec_to_milli(argv[11], &tmp64) < 0) || 551 (tmp64 > INT32_MAX) || 552 (tmp64 < INT32_MIN)) { 553 return -EINVAL; 554 } 555 556 data->info.geoid_separation = (int32_t)tmp64; 557 558 return 0; 559 } 560 parse_gsv_svs(struct gnss_satellite * satellites,const struct gsv_sv_args * svs,uint16_t svs_size)561 static int parse_gsv_svs(struct gnss_satellite *satellites, const struct gsv_sv_args *svs, 562 uint16_t svs_size) 563 { 564 int32_t i32; 565 566 for (uint16_t i = 0; i < svs_size; i++) { 567 /* Parse PRN */ 568 if ((gnss_parse_atoi(svs[i].prn, 10, &i32) < 0) || 569 (i32 < 0) || (i32 > UINT16_MAX)) { 570 return -EINVAL; 571 } 572 573 satellites[i].prn = (uint16_t)i32; 574 575 /* Parse elevation */ 576 if ((gnss_parse_atoi(svs[i].elevation, 10, &i32) < 0) || 577 (i32 < 0) || (i32 > 90)) { 578 return -EINVAL; 579 } 580 581 satellites[i].elevation = (uint8_t)i32; 582 583 /* Parse azimuth */ 584 if ((gnss_parse_atoi(svs[i].azimuth, 10, &i32) < 0) || 585 (i32 < 0) || (i32 > 359)) { 586 return -EINVAL; 587 } 588 589 satellites[i].azimuth = (uint16_t)i32; 590 591 /* Parse SNR */ 592 if (strlen(svs[i].snr) == 0) { 593 satellites[i].snr = 0; 594 satellites[i].is_tracked = false; 595 continue; 596 } 597 598 if ((gnss_parse_atoi(svs[i].snr, 10, &i32) < 0) || 599 (i32 < 0) || (i32 > 99)) { 600 return -EINVAL; 601 } 602 603 satellites[i].snr = (uint16_t)i32; 604 satellites[i].is_tracked = true; 605 } 606 607 return 0; 608 } 609 gnss_nmea0183_parse_gsv_header(const char ** argv,uint16_t argc,struct gnss_nmea0183_gsv_header * header)610 int gnss_nmea0183_parse_gsv_header(const char **argv, uint16_t argc, 611 struct gnss_nmea0183_gsv_header *header) 612 { 613 const struct gsv_header_args *args = (const struct gsv_header_args *)argv; 614 int i32; 615 616 __ASSERT(argv != NULL, "argv argument must be provided"); 617 __ASSERT(header != NULL, "header argument must be provided"); 618 619 if (argc < 4) { 620 return -EINVAL; 621 } 622 623 /* Parse GNSS sv_system */ 624 if (gnss_system_from_gsv_header_args(args, &header->system) < 0) { 625 return -EINVAL; 626 } 627 628 /* Parse number of messages */ 629 if ((gnss_parse_atoi(args->number_of_messages, 10, &i32) < 0) || 630 (i32 < 0) || (i32 > UINT16_MAX)) { 631 return -EINVAL; 632 } 633 634 header->number_of_messages = (uint16_t)i32; 635 636 /* Parse message number */ 637 if ((gnss_parse_atoi(args->message_number, 10, &i32) < 0) || 638 (i32 < 0) || (i32 > UINT16_MAX)) { 639 return -EINVAL; 640 } 641 642 header->message_number = (uint16_t)i32; 643 644 /* Parse message number */ 645 if ((gnss_parse_atoi(args->numver_of_svs, 10, &i32) < 0) || 646 (i32 < 0) || (i32 > UINT16_MAX)) { 647 return -EINVAL; 648 } 649 650 header->number_of_svs = (uint16_t)i32; 651 return 0; 652 } 653 gnss_nmea0183_parse_gsv_svs(const char ** argv,uint16_t argc,struct gnss_satellite * satellites,uint16_t size)654 int gnss_nmea0183_parse_gsv_svs(const char **argv, uint16_t argc, 655 struct gnss_satellite *satellites, uint16_t size) 656 { 657 const struct gsv_header_args *header_args = (const struct gsv_header_args *)argv; 658 const struct gsv_sv_args *sv_args = (const struct gsv_sv_args *)(argv + 4); 659 uint16_t sv_args_size; 660 enum gnss_system sv_system; 661 662 __ASSERT(argv != NULL, "argv argument must be provided"); 663 __ASSERT(satellites != NULL, "satellites argument must be provided"); 664 665 if (argc < 9) { 666 return 0; 667 } 668 669 sv_args_size = (argc - GNSS_NMEA0183_GSV_HDR_ARG_CNT) / GNSS_NMEA0183_GSV_SV_ARG_CNT; 670 671 if (size < sv_args_size) { 672 return -ENOMEM; 673 } 674 675 if (parse_gsv_svs(satellites, sv_args, sv_args_size) < 0) { 676 return -EINVAL; 677 } 678 679 if (gnss_system_from_gsv_header_args(header_args, &sv_system) < 0) { 680 return -EINVAL; 681 } 682 683 for (uint16_t i = 0; i < sv_args_size; i++) { 684 align_satellite_with_gnss_system(sv_system, &satellites[i]); 685 } 686 687 return (int)sv_args_size; 688 } 689