From b629027b6594a9ae6f70b43a4808ce4a710466c9 Mon Sep 17 00:00:00 2001 From: Refadon Date: Tue, 7 Jul 2026 13:46:41 +0000 Subject: [PATCH 1/2] feat: add native support for parsing NMEA HDT true heading sentence --- minmea.c | 16 ++++++++++++++++ minmea.h | 12 ++++++++---- tests.c | 24 +++++++++++++++++++++++- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/minmea.c b/minmea.c index 9e11ca5..16d62bc 100644 --- a/minmea.c +++ b/minmea.c @@ -365,6 +365,7 @@ struct sentence_id_map_entry sentence_id_map[] = { { "RMC", MINMEA_SENTENCE_RMC }, { "VTG", MINMEA_SENTENCE_VTG }, { "ZDA", MINMEA_SENTENCE_ZDA }, + { "HDT", MINMEA_SENTENCE_HDT }, }; const char* minmea_sentence(enum minmea_sentence_id id) { @@ -640,6 +641,21 @@ bool minmea_parse_zda(struct minmea_sentence_zda *frame, const char *sentence) return true; } +bool minmea_parse_hdt(struct minmea_sentence_hdt *frame, const char *sentence) +{ /* Scan using 't' for the union type payload pointer, 'f' for float, 'c' for char */ + if (!minmea_scan(sentence, "tfc", + &frame->type, + &frame->heading, + &frame->true_heading) + ) return false; + + /* Verify if the extracted token matches the specific HDT layout code */ + if (memcmp(frame->type.sentence_id, "HDT", sizeof(frame->type.sentence_id))) + return false; + return true; + +} + int minmea_getdatetime(struct tm *tm, const struct minmea_date *date, const struct minmea_time *time_) { if (date->year == -1 || time_->hours == -1) diff --git a/minmea.h b/minmea.h index da2ab4f..85d96e0 100644 --- a/minmea.h +++ b/minmea.h @@ -38,6 +38,7 @@ enum minmea_sentence_id { MINMEA_SENTENCE_RMC, MINMEA_SENTENCE_VTG, MINMEA_SENTENCE_ZDA, + MINMEA_SENTENCE_HDT, }; struct minmea_float { @@ -196,6 +197,12 @@ struct minmea_sentence_zda { int minute_offset; }; +struct minmea_sentence_hdt { + union minmea_type type; + struct minmea_float heading; + char true_heading; +}; + /** * Calculate raw sentence checksum. Does not check sentence integrity. */ @@ -249,6 +256,7 @@ bool minmea_parse_gst(struct minmea_sentence_gst *frame, const char *sentence); bool minmea_parse_gsv(struct minmea_sentence_gsv *frame, const char *sentence); bool minmea_parse_vtg(struct minmea_sentence_vtg *frame, const char *sentence); bool minmea_parse_zda(struct minmea_sentence_zda *frame, const char *sentence); +bool minmea_parse_hdt(struct minmea_sentence_hdt *frame, const char *sentence); /** * Convert GPS UTC date/time representation to a UNIX calendar time. @@ -311,10 +319,6 @@ static inline bool minmea_isfield(char c) { return isprint((unsigned char) c) && c != ',' && c != '*'; } -#ifdef __cplusplus -} -#endif - #endif /* MINMEA_H */ /* vim: set ts=4 sw=4 et: */ diff --git a/tests.c b/tests.c index 938cfea..c4d8748 100644 --- a/tests.c +++ b/tests.c @@ -49,6 +49,7 @@ static const char *valid_sentences_checksum[] = { "$GPGST,024603.00,3.2,6.6,4.7,47.3,5.8,5.6,22.0*58", "$GPZDA,160012.71,11,03,2004,-1,00*7D", "$GNGBS,170556.00,3.0,2.9,8.3,,,,*5C", + "$HEHDT,234.56,T*19", NULL, }; @@ -64,6 +65,7 @@ static const char *invalid_sentences[] = { "$GPTXT,hello\n ", "$GPTXT,hello\r*24", "$GPTXT,hello\r\n$", + "$HEHDT,234.56,T*1E", NULL, }; @@ -902,7 +904,7 @@ START_TEST(test_minmea_parse_gsv6) const char *sentence = "$GPGSV,3,1,11,09,57,333,29.0,07,54,227,,04,49,043,24.5,03,43,129,,1*67"; struct minmea_sentence_gsv frame = {}; static const struct minmea_sentence_gsv expected = { - .type = {"GP", "GSV"}, + .type = { .talker_id = "GP", .sentence_id = "GSV", .null_terminator = '\0' }, .total_msgs = 3, .msg_nr = 1, .total_sats = 11, @@ -1029,6 +1031,25 @@ START_TEST(test_minmea_parse_zda1) } END_TEST +START_TEST(test_minmea_parse_hdt) +{ + /* Standard gyrocompass heading sentence for validation */ + const char *hdt_sentence = "$HEHDT,234.56,T*19\r\n"; + struct minmea_sentence_hdt frame; + + /* Verify sentence ID mapping */ + ck_assert_int_eq(minmea_sentence_id(hdt_sentence, false), MINMEA_SENTENCE_HDT); + + /* Verify parser execution state */ + ck_assert(minmea_parse_hdt(&frame, hdt_sentence) == true); + + /* Verify mapped data indicators and structures */ + ck_assert_int_eq(frame.true_heading, 'T'); + ck_assert_int_eq(frame.heading.value, 23456); + ck_assert_int_eq(frame.heading.scale, 100); +} +END_TEST + START_TEST(test_minmea_usage1) { const char *sentences[] = { @@ -1252,6 +1273,7 @@ static Suite *minmea_suite(void) tcase_add_test(tc_parse, test_minmea_parse_vtg2); tcase_add_test(tc_parse, test_minmea_parse_vtg3); tcase_add_test(tc_parse, test_minmea_parse_zda1); + tcase_add_test(tc_parse, test_minmea_parse_hdt); suite_add_tcase(s, tc_parse); TCase *tc_usage = tcase_create("minmea_usage"); From 9e5c175d979164591d8e39e66a59bd4c3cf45865 Mon Sep 17 00:00:00 2001 From: Refadon Date: Tue, 7 Jul 2026 13:53:50 +0000 Subject: [PATCH 2/2] feat: add native support for parsing NMEA HDT true heading sentence --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5d66e9a..8094dfd 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ systems. * ``GSA`` (DOP and active satellites) * ``GST`` (Pseudorange Noise Statistics) * ``GSV`` (Satellites in view) +* ``HDT`` (Heading, True) * ``RMC`` (Recommended Minimum: position, velocity, time) * ``VTG`` (Track made good and Ground speed) * ``ZDA`` (Time & Date - UTC, day, month, year and local time zone)