1 /*
2  * Copyright 2023 Trackunit Corporation
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/ztest.h>
8 #include <string.h>
9 
10 #include "gnss_nmea0183.h"
11 
12 #define TEST_DDMM_MMMM_MAX_ROUNDING_ERROR_NDEG (1)
13 
14 struct test_ddmm_mmmm_sample {
15 	const char *ddmm_mmmm;
16 	int64_t ndeg;
17 };
18 
19 /*
20  * The conversion from ddmm.mmmm to decimal nano degree is
21  * ((1/60) * mm.mmmm * 1E9) + (dd * 1E9)
22  */
23 static const struct test_ddmm_mmmm_sample ddmm_mmmm_samples[] = {
24 	{.ddmm_mmmm = "00.0", .ndeg = 0},
25 	{.ddmm_mmmm = "000.0", .ndeg = 0},
26 	{.ddmm_mmmm = "9000.0000", .ndeg = 90000000000},
27 	{.ddmm_mmmm = "4530.0000", .ndeg = 45500000000},
28 	{.ddmm_mmmm = "4530.3000", .ndeg = 45505000000},
29 	{.ddmm_mmmm = "4530.3001", .ndeg = 45505001667},
30 	{.ddmm_mmmm = "4530.9999", .ndeg = 45516665000},
31 	{.ddmm_mmmm = "18000.0000", .ndeg = 180000000000}
32 };
33 
ZTEST(gnss_nmea0183,test_ddmm_mmmm)34 ZTEST(gnss_nmea0183, test_ddmm_mmmm)
35 {
36 	int64_t min_ndeg;
37 	int64_t max_ndeg;
38 	int64_t ndeg;
39 
40 	for (size_t i = 0; i < ARRAY_SIZE(ddmm_mmmm_samples); i++) {
41 		zassert_ok(gnss_nmea0183_ddmm_mmmm_to_ndeg(ddmm_mmmm_samples[i].ddmm_mmmm, &ndeg),
42 			   "Parse failed");
43 
44 		min_ndeg = ddmm_mmmm_samples[i].ndeg - TEST_DDMM_MMMM_MAX_ROUNDING_ERROR_NDEG;
45 		max_ndeg = ddmm_mmmm_samples[i].ndeg + TEST_DDMM_MMMM_MAX_ROUNDING_ERROR_NDEG;
46 		zassert_true(ndeg >= min_ndeg, "Parsed value falls below max rounding error");
47 		zassert_true(ndeg <= max_ndeg, "Parsed value is above max rounding error");
48 	}
49 
50 	/* Minutes can only go from 0 to 59.9999 */
51 	zassert_equal(gnss_nmea0183_ddmm_mmmm_to_ndeg("99.0000", &ndeg), -EINVAL,
52 		      "Parse should fail");
53 
54 	zassert_equal(gnss_nmea0183_ddmm_mmmm_to_ndeg("60.0000", &ndeg), -EINVAL,
55 		      "Parse should fail");
56 
57 	/* Missing dot */
58 	zassert_equal(gnss_nmea0183_ddmm_mmmm_to_ndeg("18000", &ndeg), -EINVAL,
59 		      "Parse should fail");
60 
61 	/* Invalid chars */
62 	zassert_equal(gnss_nmea0183_ddmm_mmmm_to_ndeg("900#.0a000", &ndeg), -EINVAL,
63 		      "Parse should fail");
64 
65 	/* Negative angle */
66 	zassert_equal(gnss_nmea0183_ddmm_mmmm_to_ndeg("-18000.0", &ndeg), -EINVAL,
67 		      "Parse should fail");
68 }
69 
70 struct test_knots_to_mms_sample {
71 	const char *str;
72 	int64_t value;
73 };
74 
75 static const struct test_knots_to_mms_sample knots_to_mms_samples[] = {
76 	{.str = "1", .value = 514},
77 	{.str = "2.2", .value = 1131},
78 	{.str = "003241.12543", .value = 1667364}
79 };
80 
ZTEST(gnss_nmea0183,test_knots_to_mms)81 ZTEST(gnss_nmea0183, test_knots_to_mms)
82 {
83 	int64_t mms;
84 
85 	for (size_t i = 0; i < ARRAY_SIZE(knots_to_mms_samples); i++) {
86 		zassert_ok(gnss_nmea0183_knots_to_mms(knots_to_mms_samples[i].str, &mms),
87 			   "Parse failed");
88 
89 		zassert_equal(knots_to_mms_samples[i].value, mms,
90 			      "Parsed value falls below max rounding error");
91 	}
92 }
93 
94 struct test_hhmmss_sample {
95 	const char *str;
96 	uint8_t hour;
97 	uint8_t minute;
98 	uint16_t millisecond;
99 };
100 
101 static const struct test_hhmmss_sample hhmmss_samples[] = {
102 	{.str = "000102", .hour = 0, .minute = 1, .millisecond = 2000},
103 	{.str = "235959.999", .hour = 23, .minute = 59, .millisecond = 59999},
104 	{.str = "000000.0", .hour = 0, .minute = 0, .millisecond = 0}
105 };
106 
ZTEST(gnss_nmea0183,test_hhmmss)107 ZTEST(gnss_nmea0183, test_hhmmss)
108 {
109 	struct gnss_time utc;
110 	int ret;
111 
112 	for (size_t i = 0; i < ARRAY_SIZE(hhmmss_samples); i++) {
113 		zassert_ok(gnss_nmea0183_parse_hhmmss(hhmmss_samples[i].str, &utc),
114 			   "Parse failed");
115 
116 		zassert_equal(hhmmss_samples[i].hour, utc.hour, "Failed to parse hour");
117 		zassert_equal(hhmmss_samples[i].minute, utc.minute, "Failed to parse minute");
118 		zassert_equal(hhmmss_samples[i].millisecond, utc.millisecond,
119 			      "Failed to parse millisecond");
120 	}
121 
122 	ret = gnss_nmea0183_parse_hhmmss("-101010", &utc);
123 	zassert_equal(ret, -EINVAL, "Should fail to parse invalid value");
124 
125 	ret = gnss_nmea0183_parse_hhmmss("01010", &utc);
126 	zassert_equal(ret, -EINVAL, "Should fail to parse invalid value");
127 
128 	ret = gnss_nmea0183_parse_hhmmss("246060.999", &utc);
129 	zassert_equal(ret, -EINVAL, "Should fail to parse invalid value");
130 
131 	ret = gnss_nmea0183_parse_hhmmss("99a9c9", &utc);
132 	zassert_equal(ret, -EINVAL, "Should fail to parse invalid value");
133 
134 	ret = gnss_nmea0183_parse_hhmmss("12121212", &utc);
135 	zassert_equal(ret, -EINVAL, "Should fail to parse invalid value");
136 }
137 
138 struct test_ddmmyy_sample {
139 	const char *str;
140 	uint8_t month_day;
141 	uint8_t month;
142 	uint16_t century_year;
143 };
144 
145 static const struct test_ddmmyy_sample ddmmyy_samples[] = {
146 	{.str = "010203", .month_day = 1, .month = 2, .century_year = 3},
147 	{.str = "311299", .month_day = 31, .month = 12, .century_year = 99},
148 	{.str = "010100", .month_day = 1, .month = 1, .century_year = 0}
149 };
150 
ZTEST(gnss_nmea0183,test_ddmmyy)151 ZTEST(gnss_nmea0183, test_ddmmyy)
152 {
153 	struct gnss_time utc;
154 	int ret;
155 
156 	for (size_t i = 0; i < ARRAY_SIZE(ddmmyy_samples); i++) {
157 		zassert_ok(gnss_nmea0183_parse_ddmmyy(ddmmyy_samples[i].str, &utc),
158 			   "Parse failed");
159 
160 		zassert_equal(ddmmyy_samples[i].month_day, utc.month_day,
161 			      "Failed to parse monthday");
162 
163 		zassert_equal(ddmmyy_samples[i].month, utc.month, "Failed to parse month");
164 		zassert_equal(ddmmyy_samples[i].century_year, utc.century_year,
165 			      "Failed to parse year");
166 	}
167 
168 	ret = gnss_nmea0183_parse_ddmmyy("000000", &utc);
169 	zassert_equal(ret, -EINVAL, "Should fail to parse invalid value");
170 
171 	ret = gnss_nmea0183_parse_ddmmyy("-12123", &utc);
172 	zassert_equal(ret, -EINVAL, "Should fail to parse invalid value");
173 
174 	ret = gnss_nmea0183_parse_ddmmyy("01010", &utc);
175 	zassert_equal(ret, -EINVAL, "Should fail to parse invalid value");
176 
177 	ret = gnss_nmea0183_parse_ddmmyy("999999", &utc);
178 	zassert_equal(ret, -EINVAL, "Should fail to parse invalid value");
179 
180 	ret = gnss_nmea0183_parse_ddmmyy("99a9c9", &utc);
181 	zassert_equal(ret, -EINVAL, "Should fail to parse invalid value");
182 
183 	ret = gnss_nmea0183_parse_ddmmyy("12121212", &utc);
184 	zassert_equal(ret, -EINVAL, "Should fail to parse invalid value");
185 }
186 
187 /* "$GNRMC,160833.099,V,,,,,,,090923,,,N,V*27" */
188 const char *rmc_argv_no_fix[15] = {
189 	"$GNRMC",
190 	"160833.099",
191 	"V",
192 	"",
193 	"",
194 	"",
195 	"",
196 	"",
197 	"",
198 	"090923",
199 	"",
200 	"",
201 	"N",
202 	"V",
203 	"27"
204 };
205 
206 static struct gnss_data data;
207 
ZTEST(gnss_nmea0183,test_parse_rmc_no_fix)208 ZTEST(gnss_nmea0183, test_parse_rmc_no_fix)
209 {
210 	int ret;
211 
212 	/* Corrupt data */
213 	memset(&data, 0xFF, sizeof(data));
214 
215 	ret = gnss_nmea0183_parse_rmc(rmc_argv_no_fix, ARRAY_SIZE(rmc_argv_no_fix), &data);
216 	zassert_ok(ret, "NMEA0183 RMC message parse should succeed");
217 }
218 
219 /* "$GNGGA,160834.099,,,,,0,0,,,M,,M,,*5E" */
220 const char *gga_argv_no_fix[16] = {
221 	"$GNGGA",
222 	"160834.099",
223 	"",
224 	"",
225 	"",
226 	"",
227 	"0",
228 	"0",
229 	"",
230 	"",
231 	"M",
232 	"",
233 	"M",
234 	"",
235 	"5E"
236 };
237 
ZTEST(gnss_nmea0183,test_parse_gga_no_fix)238 ZTEST(gnss_nmea0183, test_parse_gga_no_fix)
239 {
240 	int ret;
241 
242 	/* Corrupt data */
243 	memset(&data, 0xFF, sizeof(data));
244 
245 	ret = gnss_nmea0183_parse_gga(gga_argv_no_fix, ARRAY_SIZE(gga_argv_no_fix), &data);
246 	zassert_ok(ret, "NMEA0183 GGA message parse should succeed");
247 	zassert_equal(data.info.fix_quality, GNSS_FIX_QUALITY_INVALID,
248 		      "Incorrectly parsed fix quality");
249 
250 	zassert_equal(data.info.fix_status, GNSS_FIX_STATUS_NO_FIX,
251 		      "Incorrectly parsed fix status");
252 }
253 
254 /* "$GNRMC,160849.000,A,5709.736602,N,00957.660738,E,0.33,0.00,090923,,,A,V*03" */
255 const char *rmc_argv_fix[15] = {
256 	"$GNRMC",
257 	"160849.000",
258 	"A",
259 	"5709.736602",
260 	"N",
261 	"00957.660738",
262 	"E",
263 	"0.33",
264 	"33.31",
265 	"090923",
266 	"",
267 	"",
268 	"A",
269 	"V",
270 	"03",
271 };
272 
ZTEST(gnss_nmea0183,test_parse_rmc_fix)273 ZTEST(gnss_nmea0183, test_parse_rmc_fix)
274 {
275 	int ret;
276 
277 	/* Corrupt data */
278 	memset(&data, 0xFF, sizeof(data));
279 
280 	ret = gnss_nmea0183_parse_rmc(rmc_argv_fix, ARRAY_SIZE(rmc_argv_fix), &data);
281 	zassert_ok(ret, "NMEA0183 RMC message parse should succeed");
282 	zassert_equal(data.nav_data.latitude, 57162276699, "Incorrectly parsed latitude");
283 	zassert_equal(data.nav_data.longitude, 9961012299, "Incorrectly parsed longitude");
284 	zassert_equal(data.nav_data.speed, 169, "Incorrectly parsed speed");
285 	zassert_equal(data.nav_data.bearing, 33310, "Incorrectly parsed speed");
286 	zassert_equal(data.utc.hour, 16, "Incorrectly parsed hour");
287 	zassert_equal(data.utc.minute, 8, "Incorrectly parsed minute");
288 	zassert_equal(data.utc.millisecond, 49000, "Incorrectly parsed millisecond");
289 	zassert_equal(data.utc.month_day, 9, "Incorrectly parsed month day");
290 	zassert_equal(data.utc.month, 9, "Incorrectly parsed month");
291 	zassert_equal(data.utc.century_year, 23, "Incorrectly parsed century year");
292 }
293 
294 /* "$GNGGA,160858.000,5709.734778,N,00957.659514,E,1,6,1.41,15.234,M,42.371,M,,*72" */
295 const char *gga_argv_fix[16] = {
296 	"$GNGGA",
297 	"160858.000",
298 	"5709.734778",
299 	"N",
300 	"00957.659514",
301 	"E",
302 	"1",
303 	"6",
304 	"1.41",
305 	"15.234",
306 	"M",
307 	"42.371",
308 	"M",
309 	"",
310 	"",
311 	"72",
312 };
313 
ZTEST(gnss_nmea0183,test_parse_gga_fix)314 ZTEST(gnss_nmea0183, test_parse_gga_fix)
315 {
316 	int ret;
317 
318 	/* Corrupt data */
319 	memset(&data, 0xFF, sizeof(data));
320 
321 	ret = gnss_nmea0183_parse_gga(gga_argv_fix, ARRAY_SIZE(gga_argv_fix), &data);
322 	zassert_ok(ret, "NMEA0183 GGA message parse should succeed");
323 	zassert_equal(data.info.fix_quality, GNSS_FIX_QUALITY_GNSS_SPS,
324 		      "Incorrectly parsed fix quality");
325 
326 	zassert_equal(data.info.fix_status, GNSS_FIX_STATUS_GNSS_FIX,
327 		      "Incorrectly parsed fix status");
328 
329 	zassert_equal(data.info.satellites_cnt, 6,
330 		      "Incorrectly parsed number of satellites");
331 
332 	zassert_equal(data.info.hdop, 1410, "Incorrectly parsed HDOP");
333 	zassert_equal(data.nav_data.altitude, 15234, "Incorrectly parsed altitude");
334 }
335 
ZTEST(gnss_nmea0183,test_snprintk)336 ZTEST(gnss_nmea0183, test_snprintk)
337 {
338 	int ret;
339 	char buf[sizeof("$PAIR002,3*27")];
340 
341 	ret = gnss_nmea0183_snprintk(buf, sizeof(buf), "PAIR%03u,%u", 2, 3);
342 	zassert_equal(ret, (sizeof("$PAIR002,3*27") - 1), "Failed to format NMEA0183 message");
343 	zassert_ok(strcmp(buf, "$PAIR002,3*27"), "Incorrectly formatted NMEA0183 message");
344 
345 	ret = gnss_nmea0183_snprintk(buf, sizeof(buf) - 1, "PAIR%03u,%u", 2, 3);
346 	zassert_equal(ret, -ENOMEM, "Should fail with -ENOMEM as buffer is too small");
347 }
348 
349 /* $GPGSV,8,1,25,21,44,141,47,15,14,049,44,6,31,255,46,3,25,280,44*75 */
350 const char *gpgsv_8_1_25[21] = {
351 	"$GPGSV",
352 	"8",
353 	"1",
354 	"25",
355 	"21",
356 	"44",
357 	"141",
358 	"47",
359 	"15",
360 	"14",
361 	"049",
362 	"44",
363 	"6",
364 	"31",
365 	"255",
366 	"46",
367 	"3",
368 	"25",
369 	"280",
370 	"44",
371 	"75",
372 };
373 
374 static const struct gnss_nmea0183_gsv_header gpgsv_8_1_25_header = {
375 	.system = GNSS_SYSTEM_GPS,
376 	.number_of_messages = 8,
377 	.message_number = 1,
378 	.number_of_svs = 25
379 };
380 
381 static const struct gnss_satellite gpgsv_8_1_25_sats[] = {
382 	{.prn = 21, .elevation = 44, .azimuth = 141, .snr = 47,
383 	 .system = GNSS_SYSTEM_GPS, .is_tracked = true},
384 	{.prn = 15, .elevation = 14, .azimuth = 49, .snr = 44,
385 	 .system = GNSS_SYSTEM_GPS, .is_tracked = true},
386 	{.prn = 6, .elevation = 31, .azimuth = 255, .snr = 46,
387 	 .system = GNSS_SYSTEM_GPS, .is_tracked = true},
388 	{.prn = 3, .elevation = 25, .azimuth = 280, .snr = 44,
389 	 .system = GNSS_SYSTEM_GPS, .is_tracked = true},
390 };
391 
392 /* $GPGSV,8,2,25,18,61,057,48,22,68,320,52,27,34,268,47,24,32,076,45*76 */
393 const char *gpgsv_8_2_25[21] = {
394 	"$GPGSV",
395 	"8",
396 	"2",
397 	"25",
398 	"18",
399 	"61",
400 	"057",
401 	"48",
402 	"22",
403 	"68",
404 	"320",
405 	"52",
406 	"27",
407 	"34",
408 	"268",
409 	"47",
410 	"24",
411 	"32",
412 	"076",
413 	"45",
414 	"76",
415 };
416 
417 static const struct gnss_nmea0183_gsv_header gpgsv_8_2_25_header = {
418 	.system = GNSS_SYSTEM_GPS,
419 	.number_of_messages = 8,
420 	.message_number = 2,
421 	.number_of_svs = 25
422 };
423 
424 static const struct gnss_satellite gpgsv_8_2_25_sats[] = {
425 	{.prn = 18, .elevation = 61, .azimuth = 57, .snr = 48,
426 	 .system = GNSS_SYSTEM_GPS, .is_tracked = true},
427 	{.prn = 22, .elevation = 68, .azimuth = 320, .snr = 52,
428 	 .system = GNSS_SYSTEM_GPS, .is_tracked = true},
429 	{.prn = 27, .elevation = 34, .azimuth = 268, .snr = 47,
430 	 .system = GNSS_SYSTEM_GPS, .is_tracked = true},
431 	{.prn = 24, .elevation = 32, .azimuth = 76, .snr = 45,
432 	 .system = GNSS_SYSTEM_GPS, .is_tracked = true},
433 };
434 
435 /* $GPGSV,8,3,25,14,51,214,49,19,23,308,46*7E */
436 const char *gpgsv_8_3_25[13] = {
437 	"$GPGSV",
438 	"8",
439 	"3",
440 	"25",
441 	"14",
442 	"51",
443 	"214",
444 	"49",
445 	"19",
446 	"23",
447 	"308",
448 	"46",
449 	"7E",
450 };
451 
452 static const struct gnss_nmea0183_gsv_header gpgsv_8_3_25_header = {
453 	.system = GNSS_SYSTEM_GPS,
454 	.number_of_messages = 8,
455 	.message_number = 3,
456 	.number_of_svs = 25
457 };
458 
459 static const struct gnss_satellite gpgsv_8_3_25_sats[] = {
460 	{.prn = 14, .elevation = 51, .azimuth = 214, .snr = 49,
461 	 .system = GNSS_SYSTEM_GPS, .is_tracked = true},
462 	{.prn = 19, .elevation = 23, .azimuth = 308, .snr = 46,
463 	 .system = GNSS_SYSTEM_GPS, .is_tracked = true},
464 };
465 
466 /* $GPGSV,8,4,25,51,44,183,49,46,41,169,43,48,36,220,45*47 */
467 const char *gpgsv_8_4_25[17] = {
468 	"$GPGSV",
469 	"8",
470 	"4",
471 	"25",
472 	"51",
473 	"44",
474 	"183",
475 	"49",
476 	"46",
477 	"41",
478 	"169",
479 	"43",
480 	"48",
481 	"36",
482 	"220",
483 	"45",
484 	"47",
485 };
486 
487 static const struct gnss_nmea0183_gsv_header gpgsv_8_4_25_header = {
488 	.system = GNSS_SYSTEM_GPS,
489 	.number_of_messages = 8,
490 	.message_number = 4,
491 	.number_of_svs = 25
492 };
493 
494 static const struct gnss_satellite gpgsv_8_4_25_sats[] = {
495 	{.prn = (51 + 87), .elevation = 44, .azimuth = 183, .snr = 49,
496 	 .system = GNSS_SYSTEM_SBAS, .is_tracked = true},
497 	{.prn = (46 + 87), .elevation = 41, .azimuth = 169, .snr = 43,
498 	 .system = GNSS_SYSTEM_SBAS, .is_tracked = true},
499 	{.prn = (48 + 87), .elevation = 36, .azimuth = 220, .snr = 45,
500 	 .system = GNSS_SYSTEM_SBAS, .is_tracked = true},
501 };
502 
503 /* $GLGSV,8,5,25,82,49,219,52,76,22,051,41,83,37,316,51,67,57,010,51*6C */
504 const char *glgsv_8_5_25[21] = {
505 	"$GLGSV",
506 	"8",
507 	"5",
508 	"25",
509 	"82",
510 	"49",
511 	"219",
512 	"52",
513 	"76",
514 	"22",
515 	"051",
516 	"41",
517 	"83",
518 	"37",
519 	"316",
520 	"51",
521 	"67",
522 	"57",
523 	"010",
524 	"51",
525 	"6C",
526 };
527 
528 static const struct gnss_nmea0183_gsv_header glgsv_8_5_25_header = {
529 	.system = GNSS_SYSTEM_GLONASS,
530 	.number_of_messages = 8,
531 	.message_number = 5,
532 	.number_of_svs = 25
533 };
534 
535 static const struct gnss_satellite glgsv_8_5_25_sats[] = {
536 	{.prn = (82 - 64), .elevation = 49, .azimuth = 219, .snr = 52,
537 	 .system = GNSS_SYSTEM_GLONASS, .is_tracked = true},
538 	{.prn = (76 - 64), .elevation = 22, .azimuth = 51, .snr = 41,
539 	 .system = GNSS_SYSTEM_GLONASS, .is_tracked = true},
540 	{.prn = (83 - 64), .elevation = 37, .azimuth = 316, .snr = 51,
541 	 .system = GNSS_SYSTEM_GLONASS, .is_tracked = true},
542 	{.prn = (67 - 64), .elevation = 57, .azimuth = 10, .snr = 51,
543 	 .system = GNSS_SYSTEM_GLONASS, .is_tracked = true},
544 };
545 
546 /* $GLGSV,8,6,25,77,24,108,44,81,10,181,46,78,1,152,34,66,18,060,45*50 */
547 const char *glgsv_8_6_25[21] = {
548 	"$GLGSV",
549 	"8",
550 	"6",
551 	"25",
552 	"77",
553 	"24",
554 	"108",
555 	"44",
556 	"81",
557 	"10",
558 	"181",
559 	"46",
560 	"78",
561 	"1",
562 	"152",
563 	"34",
564 	"66",
565 	"18",
566 	"060",
567 	"45",
568 	"50",
569 };
570 
571 static const struct gnss_nmea0183_gsv_header glgsv_8_6_25_header = {
572 	.system = GNSS_SYSTEM_GLONASS,
573 	.number_of_messages = 8,
574 	.message_number = 6,
575 	.number_of_svs = 25
576 };
577 
578 static const struct gnss_satellite glgsv_8_6_25_sats[] = {
579 	{.prn = (77 - 64), .elevation = 24, .azimuth = 108, .snr = 44,
580 	 .system = GNSS_SYSTEM_GLONASS, .is_tracked = true},
581 	{.prn = (81 - 64), .elevation = 10, .azimuth = 181, .snr = 46,
582 	 .system = GNSS_SYSTEM_GLONASS, .is_tracked = true},
583 	{.prn = (78 - 64), .elevation = 1, .azimuth = 152, .snr = 34,
584 	 .system = GNSS_SYSTEM_GLONASS, .is_tracked = true},
585 	{.prn = (66 - 64), .elevation = 18, .azimuth = 60, .snr = 45,
586 	 .system = GNSS_SYSTEM_GLONASS, .is_tracked = true},
587 };
588 
589 /* $GLGSV,8,7,25,68,37,284,50*5C */
590 const char *glgsv_8_7_25[9] = {
591 	"$GLGSV",
592 	"8",
593 	"7",
594 	"25",
595 	"68",
596 	"37",
597 	"284",
598 	"50",
599 	"5C",
600 };
601 
602 static const struct gnss_nmea0183_gsv_header glgsv_8_7_25_header = {
603 	.system = GNSS_SYSTEM_GLONASS,
604 	.number_of_messages = 8,
605 	.message_number = 7,
606 	.number_of_svs = 25
607 };
608 
609 static const struct gnss_satellite glgsv_8_7_25_sats[] = {
610 	{.prn = (68 - 64), .elevation = 37, .azimuth = 284, .snr = 50,
611 	 .system = GNSS_SYSTEM_GLONASS, .is_tracked = true},
612 };
613 
614 /* $GBGSV,8,8,25,111,35,221,47,112,4,179,39,114,48,290,48*11 */
615 const char *gbgsv_8_8_25[17] = {
616 	"$GBGSV",
617 	"8",
618 	"8",
619 	"25",
620 	"111",
621 	"35",
622 	"221",
623 	"47",
624 	"112",
625 	"4",
626 	"179",
627 	"39",
628 	"114",
629 	"48",
630 	"290",
631 	"48",
632 	"11",
633 };
634 
635 static const struct gnss_nmea0183_gsv_header gbgsv_8_8_25_header = {
636 	.system = GNSS_SYSTEM_BEIDOU,
637 	.number_of_messages = 8,
638 	.message_number = 8,
639 	.number_of_svs = 25
640 };
641 
642 static const struct gnss_satellite gbgsv_8_8_25_sats[] = {
643 	{.prn = (111 - 100), .elevation = 35, .azimuth = 221, .snr = 47,
644 	 .system = GNSS_SYSTEM_BEIDOU, .is_tracked = true},
645 	{.prn = (112 - 100), .elevation = 4, .azimuth = 179, .snr = 39,
646 	 .system = GNSS_SYSTEM_BEIDOU, .is_tracked = true},
647 	{.prn = (114 - 100), .elevation = 48, .azimuth = 290, .snr = 48,
648 	 .system = GNSS_SYSTEM_BEIDOU, .is_tracked = true},
649 };
650 
651 struct test_gsv_sample {
652 	const char **argv;
653 	uint16_t argc;
654 	const struct gnss_nmea0183_gsv_header *header;
655 	const struct gnss_satellite *satellites;
656 	uint16_t number_of_svs;
657 };
658 
659 static const struct test_gsv_sample gsv_samples[] = {
660 	{.argv = gpgsv_8_1_25, .argc = ARRAY_SIZE(gpgsv_8_1_25), .header = &gpgsv_8_1_25_header,
661 	 .satellites = gpgsv_8_1_25_sats, .number_of_svs = ARRAY_SIZE(gpgsv_8_1_25_sats)},
662 	{.argv = gpgsv_8_2_25, .argc = ARRAY_SIZE(gpgsv_8_2_25), .header = &gpgsv_8_2_25_header,
663 	 .satellites = gpgsv_8_2_25_sats, .number_of_svs = ARRAY_SIZE(gpgsv_8_2_25_sats)},
664 	{.argv = gpgsv_8_3_25, .argc = ARRAY_SIZE(gpgsv_8_3_25), .header = &gpgsv_8_3_25_header,
665 	 .satellites = gpgsv_8_3_25_sats, .number_of_svs = ARRAY_SIZE(gpgsv_8_3_25_sats)},
666 	{.argv = gpgsv_8_4_25, .argc = ARRAY_SIZE(gpgsv_8_4_25), .header = &gpgsv_8_4_25_header,
667 	 .satellites = gpgsv_8_4_25_sats, .number_of_svs = ARRAY_SIZE(gpgsv_8_4_25_sats)},
668 	{.argv = glgsv_8_5_25, .argc = ARRAY_SIZE(glgsv_8_5_25), .header = &glgsv_8_5_25_header,
669 	 .satellites = glgsv_8_5_25_sats, .number_of_svs = ARRAY_SIZE(glgsv_8_5_25_sats)},
670 	{.argv = glgsv_8_6_25, .argc = ARRAY_SIZE(glgsv_8_6_25), .header = &glgsv_8_6_25_header,
671 	 .satellites = glgsv_8_6_25_sats, .number_of_svs = ARRAY_SIZE(glgsv_8_6_25_sats)},
672 	{.argv = glgsv_8_7_25, .argc = ARRAY_SIZE(glgsv_8_7_25), .header = &glgsv_8_7_25_header,
673 	 .satellites = glgsv_8_7_25_sats, .number_of_svs = ARRAY_SIZE(glgsv_8_7_25_sats)},
674 	{.argv = gbgsv_8_8_25, .argc = ARRAY_SIZE(gbgsv_8_8_25), .header = &gbgsv_8_8_25_header,
675 	 .satellites = gbgsv_8_8_25_sats, .number_of_svs = ARRAY_SIZE(gbgsv_8_8_25_sats)},
676 };
677 
ZTEST(gnss_nmea0183,test_gsv_parse_headers)678 ZTEST(gnss_nmea0183, test_gsv_parse_headers)
679 {
680 	struct gnss_nmea0183_gsv_header header;
681 	int ret;
682 
683 	for (uint16_t i = 0; i < ARRAY_SIZE(gsv_samples); i++) {
684 		ret = gnss_nmea0183_parse_gsv_header(gsv_samples[i].argv, gsv_samples[i].argc,
685 						     &header);
686 
687 		zassert_ok(ret, "Failed to parse GSV header");
688 
689 		zassert_equal(header.system, gsv_samples[i].header->system,
690 			      "Failed to parse GNSS system");
691 
692 		zassert_equal(header.number_of_messages,
693 			      gsv_samples[i].header->number_of_messages,
694 			      "Failed to parse number of messages");
695 
696 		zassert_equal(header.message_number, gsv_samples[i].header->message_number,
697 			      "Failed to parse message number");
698 
699 		zassert_equal(header.number_of_svs, gsv_samples[i].header->number_of_svs,
700 			      "Failed to parse number of space vehicles");
701 	}
702 }
703 
ZTEST(gnss_nmea0183,test_gsv_parse_satellites)704 ZTEST(gnss_nmea0183, test_gsv_parse_satellites)
705 {
706 	struct gnss_satellite satellites[4];
707 	int ret;
708 
709 	for (uint16_t i = 0; i < ARRAY_SIZE(gsv_samples); i++) {
710 		ret = gnss_nmea0183_parse_gsv_svs(gsv_samples[i].argv, gsv_samples[i].argc,
711 						  satellites, ARRAY_SIZE(satellites));
712 
713 		zassert_equal(ret, gsv_samples[i].number_of_svs,
714 			      "Incorrect number of satellites parsed");
715 
716 		for (uint16_t u = 0; u < gsv_samples[i].number_of_svs; u++) {
717 			zassert_equal(gsv_samples[i].satellites[u].prn,
718 				      satellites[u].prn,
719 				      "Failed to parse satellite prn");
720 			zassert_equal(gsv_samples[i].satellites[u].snr,
721 				      satellites[u].snr,
722 				      "Failed to parse satellite snr");
723 			zassert_equal(gsv_samples[i].satellites[u].elevation,
724 				      satellites[u].elevation,
725 				      "Failed to parse satellite elevation");
726 			zassert_equal(gsv_samples[i].satellites[u].azimuth,
727 				      satellites[u].azimuth,
728 				      "Failed to parse satellite azimuth");
729 			zassert_equal(gsv_samples[i].satellites[u].system,
730 				      satellites[u].system,
731 				      "Failed to parse satellite system");
732 			zassert_equal(gsv_samples[i].satellites[u].is_tracked,
733 				      satellites[u].is_tracked,
734 				      "Failed to parse satellite is_tracked");
735 		}
736 	}
737 }
738 
739 ZTEST_SUITE(gnss_nmea0183, NULL, NULL, NULL, NULL, NULL);
740