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 	return 0;
549 }
550 
parse_gsv_svs(struct gnss_satellite * satellites,const struct gsv_sv_args * svs,uint16_t svs_size)551 static int parse_gsv_svs(struct gnss_satellite *satellites, const struct gsv_sv_args *svs,
552 			 uint16_t svs_size)
553 {
554 	int32_t i32;
555 
556 	for (uint16_t i = 0; i < svs_size; i++) {
557 		/* Parse PRN */
558 		if ((gnss_parse_atoi(svs[i].prn, 10, &i32) < 0) ||
559 		    (i32 < 0) || (i32 > UINT16_MAX)) {
560 			return -EINVAL;
561 		}
562 
563 		satellites[i].prn = (uint16_t)i32;
564 
565 		/* Parse elevation */
566 		if ((gnss_parse_atoi(svs[i].elevation, 10, &i32) < 0) ||
567 		    (i32 < 0) || (i32 > 90)) {
568 			return -EINVAL;
569 		}
570 
571 		satellites[i].elevation = (uint8_t)i32;
572 
573 		/* Parse azimuth */
574 		if ((gnss_parse_atoi(svs[i].azimuth, 10, &i32) < 0) ||
575 		    (i32 < 0) || (i32 > 359)) {
576 			return -EINVAL;
577 		}
578 
579 		satellites[i].azimuth = (uint16_t)i32;
580 
581 		/* Parse SNR */
582 		if (strlen(svs[i].snr) == 0) {
583 			satellites[i].snr = 0;
584 			satellites[i].is_tracked = false;
585 			continue;
586 		}
587 
588 		if ((gnss_parse_atoi(svs[i].snr, 10, &i32) < 0) ||
589 			(i32 < 0) || (i32 > 99)) {
590 			return -EINVAL;
591 		}
592 
593 		satellites[i].snr = (uint16_t)i32;
594 		satellites[i].is_tracked = true;
595 	}
596 
597 	return 0;
598 }
599 
gnss_nmea0183_parse_gsv_header(const char ** argv,uint16_t argc,struct gnss_nmea0183_gsv_header * header)600 int gnss_nmea0183_parse_gsv_header(const char **argv, uint16_t argc,
601 				   struct gnss_nmea0183_gsv_header *header)
602 {
603 	const struct gsv_header_args *args = (const struct gsv_header_args *)argv;
604 	int i32;
605 
606 	__ASSERT(argv != NULL, "argv argument must be provided");
607 	__ASSERT(header != NULL, "header argument must be provided");
608 
609 	if (argc < 4) {
610 		return -EINVAL;
611 	}
612 
613 	/* Parse GNSS sv_system */
614 	if (gnss_system_from_gsv_header_args(args, &header->system) < 0) {
615 		return -EINVAL;
616 	}
617 
618 	/* Parse number of messages */
619 	if ((gnss_parse_atoi(args->number_of_messages, 10, &i32) < 0) ||
620 		(i32 < 0) || (i32 > UINT16_MAX)) {
621 		return -EINVAL;
622 	}
623 
624 	header->number_of_messages = (uint16_t)i32;
625 
626 	/* Parse message number */
627 	if ((gnss_parse_atoi(args->message_number, 10, &i32) < 0) ||
628 		(i32 < 0) || (i32 > UINT16_MAX)) {
629 		return -EINVAL;
630 	}
631 
632 	header->message_number = (uint16_t)i32;
633 
634 	/* Parse message number */
635 	if ((gnss_parse_atoi(args->numver_of_svs, 10, &i32) < 0) ||
636 		(i32 < 0) || (i32 > UINT16_MAX)) {
637 		return -EINVAL;
638 	}
639 
640 	header->number_of_svs = (uint16_t)i32;
641 	return 0;
642 }
643 
gnss_nmea0183_parse_gsv_svs(const char ** argv,uint16_t argc,struct gnss_satellite * satellites,uint16_t size)644 int gnss_nmea0183_parse_gsv_svs(const char **argv, uint16_t argc,
645 				struct gnss_satellite *satellites, uint16_t size)
646 {
647 	const struct gsv_header_args *header_args = (const struct gsv_header_args *)argv;
648 	const struct gsv_sv_args *sv_args = (const struct gsv_sv_args *)(argv + 4);
649 	uint16_t sv_args_size;
650 	enum gnss_system sv_system;
651 
652 	__ASSERT(argv != NULL, "argv argument must be provided");
653 	__ASSERT(satellites != NULL, "satellites argument must be provided");
654 
655 	if (argc < 9) {
656 		return 0;
657 	}
658 
659 	sv_args_size = (argc - GNSS_NMEA0183_GSV_HDR_ARG_CNT) / GNSS_NMEA0183_GSV_SV_ARG_CNT;
660 
661 	if (size < sv_args_size) {
662 		return -ENOMEM;
663 	}
664 
665 	if (parse_gsv_svs(satellites, sv_args, sv_args_size) < 0) {
666 		return -EINVAL;
667 	}
668 
669 	if (gnss_system_from_gsv_header_args(header_args, &sv_system) < 0) {
670 		return -EINVAL;
671 	}
672 
673 	for (uint16_t i = 0; i < sv_args_size; i++) {
674 		align_satellite_with_gnss_system(sv_system, &satellites[i]);
675 	}
676 
677 	return (int)sv_args_size;
678 }
679