From 7229c169365724d8e0073441956f3ab9419b30cb Mon Sep 17 00:00:00 2001 From: Sean Kovacs Date: Mon, 7 Jul 2025 22:58:20 -0400 Subject: [PATCH 01/19] AP_GPS: Add support for quectel gps modules Remove setting of data found in standard messages Added debug logging for quectel module if logging enabled added quectel bits to the python scripts fix merge conflict Update libraries/AP_GPS/AP_GPS_NMEA.h Co-authored-by: Peter Barker fix other merge conflict removing this to fallback to default --- Tools/scripts/build_options.py | 1 + Tools/scripts/extract_features.py | 4 + libraries/AP_GPS/AP_GPS.cpp | 3 + libraries/AP_GPS/AP_GPS.h | 1 + libraries/AP_GPS/AP_GPS_NMEA.cpp | 280 ++++++++++++++++++++++++++++++ libraries/AP_GPS/AP_GPS_NMEA.h | 59 +++++++ libraries/AP_GPS/AP_GPS_config.h | 4 + 7 files changed, 352 insertions(+) diff --git a/Tools/scripts/build_options.py b/Tools/scripts/build_options.py index e27297f52d2791..af1095f3240c19 100644 --- a/Tools/scripts/build_options.py +++ b/Tools/scripts/build_options.py @@ -413,6 +413,7 @@ def config_option(self): Feature('GPS Drivers', 'GSOF', 'AP_GPS_GSOF_ENABLED', 'Enable GSOF GPS', 0, None), Feature('GPS Drivers', 'NMEA_GPS', 'AP_GPS_NMEA_ENABLED', 'Enable NMEA GPS', 0, None), Feature('GPS Drivers', 'NMEA_UNICORE', 'AP_GPS_NMEA_UNICORE_ENABLED', 'Enable NMEA Unicore GPS', 0, "NMEA_GPS"), + Feature('GPS Drivers', 'NMEA_QUECTEL', 'AP_GPS_NMEA_QUECTEL_ENABLED', 'Enable NMEA Quectel GPS', 0, "NMEA_GPS"), Feature('GPS Drivers', 'MAV', 'AP_GPS_MAV_ENABLED', 'Enable MAVLink GPS', 0, None), Feature('GPS Drivers', 'NOVA', 'AP_GPS_NOVA_ENABLED', 'Enable NOVA GPS', 0, None), Feature('GPS Drivers', 'SBF', 'AP_GPS_SBF_ENABLED', 'Enable SBF GPS', 0, None), diff --git a/Tools/scripts/extract_features.py b/Tools/scripts/extract_features.py index 61fad4d1d35474..f720619c7ebd42 100755 --- a/Tools/scripts/extract_features.py +++ b/Tools/scripts/extract_features.py @@ -91,6 +91,10 @@ def __init__(self, filename, nm="arm-none-eabi-nm", strings="strings"): ('AP_RANGEFINDER_HEXSOONRADAR_ENABLED', r'AP_RangeFinder_NRA24_CAN::handle_frame'), ('AP_GPS_NMEA_UNICORE_ENABLED', r'AP_GPS_NMEA::parse_agrica_field',), + ('AP_GPS_NMEA_QUECTEL_ENABLED', r'AP_GPS_NMEA::parse_pqtmver_field',), + ('AP_GPS_NMEA_QUECTEL_ENABLED', r'AP_GPS_NMEA::parse_pqtmepe_field',), + ('AP_GPS_NMEA_QUECTEL_ENABLED', r'AP_GPS_NMEA::parse_pqtmvel_field',), + ('AP_GPS_NMEA_QUECTEL_ENABLED', r'AP_GPS_NMEA::parse_pqtmpvt_field',), ('AP_GPS_{type}_ENABLED', r'AP_GPS_(?P.*)::read\b',), ('AP_OPTICALFLOW_ENABLED', 'AP_OpticalFlow::AP_OpticalFlow',), diff --git a/libraries/AP_GPS/AP_GPS.cpp b/libraries/AP_GPS/AP_GPS.cpp index 3c083165a207b6..8aa2eecafd103b 100644 --- a/libraries/AP_GPS/AP_GPS.cpp +++ b/libraries/AP_GPS/AP_GPS.cpp @@ -808,6 +808,9 @@ AP_GPS_Backend *AP_GPS::_detect_instance(const uint8_t instance) #if AP_GPS_NMEA_UNICORE_ENABLED type == GPS_TYPE_UNICORE_NMEA || type == GPS_TYPE_UNICORE_MOVINGBASE_NMEA || +#endif +#if AP_GPS_NMEA_QUECTEL_ENABLED + type == GPS_TYPE_QUECTEL_NMEA || #endif type == GPS_TYPE_ALLYSTAR) && AP_GPS_NMEA::_detect(dstate->nmea_detect_state, data)) { diff --git a/libraries/AP_GPS/AP_GPS.h b/libraries/AP_GPS/AP_GPS.h index 929ac4394b06de..bb33bda05e9870 100644 --- a/libraries/AP_GPS/AP_GPS.h +++ b/libraries/AP_GPS/AP_GPS.h @@ -112,6 +112,7 @@ class AP_GPS GPS_TYPE_UNICORE_NMEA = 24, GPS_TYPE_UNICORE_MOVINGBASE_NMEA = 25, GPS_TYPE_SBF_DUAL_ANTENNA = 26, + GPS_TYPE_QUECTEL_NMEA = 27, #if AP_SIM_GPS_ENABLED GPS_TYPE_SITL = 100, #endif diff --git a/libraries/AP_GPS/AP_GPS_NMEA.cpp b/libraries/AP_GPS/AP_GPS_NMEA.cpp index 8d8c0db18501a4..6c37a38458e9d5 100644 --- a/libraries/AP_GPS/AP_GPS_NMEA.cpp +++ b/libraries/AP_GPS/AP_GPS_NMEA.cpp @@ -281,6 +281,28 @@ bool AP_GPS_NMEA::_have_new_message() } #endif // AP_GPS_NMEA_UNICORE_ENABLED +#if AP_GPS_NMEA_QUECTEL_ENABLED + // Check for PQTM message timeouts + // Note: PQTMPVT only provides auxiliary data (undulation, VDOP, leap seconds) + // so we don't treat it as essential for GPS health + if (now - _last_PQTM_vel_ms > 500) { + if (_last_PQTM_vel_ms != 0) { + // we have lost PQTM velocity messages + state.have_vertical_velocity = false; + state.have_speed_accuracy = false; + _last_PQTM_vel_ms = 0; + } + } + if (now - _last_PQTM_acc_ms > 500) { + if (_last_PQTM_acc_ms != 0) { + // we have lost PQTM accuracy messages + state.have_horizontal_accuracy = false; + state.have_vertical_accuracy = false; + _last_PQTM_acc_ms = 0; + } + } +#endif // AP_GPS_NMEA_QUECTEL_ENABLED + _last_fix_ms = now; _last_GGA_ms = 1; @@ -508,6 +530,27 @@ bool AP_GPS_NMEA::_term_complete() break; } #endif // AP_GPS_NMEA_UNICORE_ENABLED + +#if AP_GPS_NMEA_QUECTEL_ENABLED + case _GPS_SENTENCE_PQTMVER: { + if (_have_pqtmver) { + GCS_SEND_TEXT(MAV_SEVERITY_INFO, + "NMEA Quectel %s %s", + _pqtmver.version, + _pqtmver.build_date); + } + break; + } + case _GPS_SENTENCE_PQTMEPE: + // Accuracy information already processed in parse function + break; + case _GPS_SENTENCE_PQTMVEL: + // Velocity information already processed in parse function + break; + case _GPS_SENTENCE_PQTMPVT: + // PVT information already processed in parse function + break; +#endif // AP_GPS_NMEA_QUECTEL_ENABLED } // see if we got a good message return _have_new_message(); @@ -543,6 +586,26 @@ bool AP_GPS_NMEA::_term_complete() return false; } #endif + +#if AP_GPS_NMEA_QUECTEL_ENABLED + // Check for Quectel PQTM messages + if (strcmp(_term, "PQTMVERNO") == 0 && _expect_pqtm) { + _sentence_type = _GPS_SENTENCE_PQTMVER; + return false; + } + if (strcmp(_term, "PQTMEPE") == 0 && _expect_pqtm) { + _sentence_type = _GPS_SENTENCE_PQTMEPE; + return false; + } + if (strcmp(_term, "PQTMVEL") == 0 && _expect_pqtm) { + _sentence_type = _GPS_SENTENCE_PQTMVEL; + return false; + } + if (strcmp(_term, "PQTMPVT") == 0 && _expect_pqtm) { + _sentence_type = _GPS_SENTENCE_PQTMPVT; + return false; + } +#endif // AP_GPS_NMEA_QUECTEL_ENABLED /* The first two letters of the NMEA term are the talker ID. The most common is 'GP' but there are a bunch of others @@ -670,6 +733,22 @@ bool AP_GPS_NMEA::_term_complete() break; #endif #endif + +#if AP_GPS_NMEA_QUECTEL_ENABLED + // Quectel PQTM message parsing + case _GPS_SENTENCE_PQTMVER + 1 ... _GPS_SENTENCE_PQTMVER + 3: // PQTMVER message (3 data fields) + parse_pqtmver_field(_term_number, _term); + break; + case _GPS_SENTENCE_PQTMEPE + 1 ... _GPS_SENTENCE_PQTMEPE + 6: // PQTMEPE message (6 fields) + parse_pqtmepe_field(_term_number, _term); + break; + case _GPS_SENTENCE_PQTMVEL + 1 ... _GPS_SENTENCE_PQTMVEL + 11: // PQTMVEL message (11 fields) + parse_pqtmvel_field(_term_number, _term); + break; + case _GPS_SENTENCE_PQTMPVT + 1 ... _GPS_SENTENCE_PQTMPVT + 19: // PQTMPVT message (19 fields) + parse_pqtmpvt_field(_term_number, _term); + break; +#endif // AP_GPS_NMEA_QUECTEL_ENABLED } } @@ -794,6 +873,150 @@ void AP_GPS_NMEA::parse_versiona_field(uint16_t term_number, const char *term) } #endif // AP_GPS_NMEA_UNICORE_ENABLED +#if AP_GPS_NMEA_QUECTEL_ENABLED +/* + Parse PQTM message fields for Quectel GNSS modules + */ + +/* + parse PQTMVER field - Firmware Version Output + Example: $PQTMVER,1,MODULE,LG290P03AANR01A03S,2024/04/30,10:53:07*32 + */ +void AP_GPS_NMEA::parse_pqtmver_field(uint16_t term_number, const char *term) +{ + auto &pv = _pqtmver; + + switch (term_number) { + case 1: // Version string (VerStr) + strncpy(pv.version, term, sizeof(pv.version)-1); + pv.version[sizeof(pv.version)-1] = 0; + break; + case 2: // Build date (BuildDate) + strncpy(pv.build_date, term, sizeof(pv.build_date)-1); + pv.build_date[sizeof(pv.build_date)-1] = 0; + break; + case 3: // Build time (BuildTime) + strncpy(pv.build_time, term, sizeof(pv.build_time)-1); + pv.build_time[sizeof(pv.build_time)-1] = 0; + _have_pqtmver = true; + break; + } +} + +/* + parse PQTMEPE field - Estimated Position Error + Example: $PQTMEPE,2,1.000,1.000,1.000,1.414,1.732*52 + */ +void AP_GPS_NMEA::parse_pqtmepe_field(uint16_t term_number, const char *term) +{ + auto &pe = _pqtmepe; + switch (term_number) { + case 2: // EPE North + pe.epe_north = atof(term); + break; + case 3: // EPE East + pe.epe_east = atof(term); + break; + case 4: // EPE Down + pe.epe_down = atof(term); + break; + case 5: // EPE 2D + pe.epe_2d = atof(term); + break; + case 6: // EPE 3D + pe.epe_3d = atof(term); + // Update state accuracy information + state.horizontal_accuracy = pe.epe_2d; + state.have_horizontal_accuracy = true; + state.vertical_accuracy = pe.epe_down; + state.have_vertical_accuracy = true; + _last_PQTM_acc_ms = AP_HAL::millis(); + break; + } +} + +/* + parse PQTMVEL field - Velocity Information + Example: $PQTMVEL,1,154512.100,1.251,2.452,1.245,2.752,3.021,180.512,0.124,0.254,0.250*67 + */ +void AP_GPS_NMEA::parse_pqtmvel_field(uint16_t term_number, const char *term) +{ + auto &pv = _pqtmvel; + switch (term_number) { + case 2: // UTC time + pv.time_ms = _parse_decimal_100(term) * 10; + break; + case 3: // North velocity + pv.vel_NED.x = atof(term); + break; + case 4: // East velocity + pv.vel_NED.y = atof(term); + break; + case 5: // Down velocity + pv.vel_NED.z = atof(term); + break; + case 6: // Ground speed + pv.ground_speed = atof(term); + break; + case 7: // 3D speed + pv.speed_3d = atof(term); + break; + case 8: // Heading + pv.heading = atof(term); + break; + case 9: // Ground speed accuracy + pv.ground_speed_acc = atof(term); + break; + case 10: // 3D speed accuracy + pv.speed_acc = atof(term); + break; + case 11: // Heading accuracy + pv.heading_acc = atof(term); + // Update state with velocity information + state.velocity = pv.vel_NED; + state.have_vertical_velocity = true; + state.ground_speed = pv.ground_speed; + state.ground_course = pv.heading; + state.speed_accuracy = pv.speed_acc; + state.have_speed_accuracy = true; + _last_PQTM_vel_ms = AP_HAL::millis(); + break; + } +} + +/* + parse PQTMPVT field - Only auxiliary data not available in standard NMEA + Example: $PQTMPVT,1,31075000,20221225,083737.000,,3,09,18,31.12738291,117.26372910,34.212,5.267,3.212,2.928,0.238,4.346,34.12,2.16,4.38*51 + */ +void AP_GPS_NMEA::parse_pqtmpvt_field(uint16_t term_number, const char *term) +{ + auto &pp = _pqtmpvt; + switch (term_number) { + case 2: // Time of week + pp.tow = atol(term); + break; + case 8: // Leap seconds + if (strlen(term) > 0) { + pp.leap_seconds = atol(term); + } + break; + case 12: // Geoidal separation + if (strlen(term) > 0) { + pp.sep = atof(term); + state.undulation = -pp.sep; + state.have_undulation = true; + } + break; + case 19: // PDOP + if (strlen(term) > 0) { + pp.pdop = atof(term); + state.vdop = pp.pdop * 100; + } + break; + } +} +#endif // AP_GPS_NMEA_QUECTEL_ENABLED + /* detect a NMEA GPS. Adds one byte, and returns true if the stream matches a NMEA string @@ -841,6 +1064,7 @@ void AP_GPS_NMEA::send_config(void) const auto type = get_type(); _expect_agrica = (type == AP_GPS::GPS_TYPE_UNICORE_NMEA || type == AP_GPS::GPS_TYPE_UNICORE_MOVINGBASE_NMEA); + _expect_pqtm = (type == AP_GPS::GPS_TYPE_QUECTEL_NMEA); if (gps._auto_config == AP_GPS::GPS_AUTO_CONFIG_DISABLE) { // not doing auto-config return; @@ -902,6 +1126,49 @@ void AP_GPS_NMEA::send_config(void) unsigned(rate_hz), unsigned(rate_ms)); break; +#if AP_GPS_NMEA_QUECTEL_ENABLED + case AP_GPS::GPS_TYPE_QUECTEL_NMEA: { + // Configure Quectel GNSS for PQTM message output + // Set fix rate (1Hz to 10Hz typically) + + nmea_printf(port, "$PQTMCFGFIXRATE,W,%u", rate_ms); + + // It seems per the documentation that only 1Hz is supported for non-RTCM messages + // // Configure standard NMEA message output rates + // nmea_printf(port, "$PQTMCFGMSGRATE,W,GGA,%u", rate_hz); + // nmea_printf(port, "$PQTMCFGMSGRATE,W,RMC,%u", rate_hz); + // nmea_printf(port, "$PQTMCFGMSGRATE,W,VTG,%u", rate_hz); + + // // Configure PQTM proprietary message output rates + // nmea_printf(port, "$PQTMCFGMSGRATE,W,PQTMEPE,%u,2", rate_hz); + // nmea_printf(port, "$PQTMCFGMSGRATE,W,PQTMVEL,%u,1", rate_hz); + // nmea_printf(port, "$PQTMCFGMSGRATE,W,PQTMPVT,%u,1", rate_hz); + + nmea_printf(port, "$PQTMCFGMSGRATE,W,GGA,1"); + nmea_printf(port, "$PQTMCFGMSGRATE,W,RMC,1"); + nmea_printf(port, "$PQTMCFGMSGRATE,W,VTG,1"); + + nmea_printf(port, "$PQTMCFGMSGRATE,W,PQTMVEL,1,1"); + nmea_printf(port, "$PQTMCFGMSGRATE,W,PQTMEPE,1,2"); + nmea_printf(port, "$PQTMCFGMSGRATE,W,PQTMPVT,1,1"); + + if (!_have_pqtmver) { + nmea_printf(port, "$PQTMVERNO"); +#if AP_GPS_DEBUG_LOGGING_ENABLED + nmea_printf(port, "$PQTMDEBUGON"); +#else + nmea_printf(port, "$PQTMDEBUGOFF"); +#endif + if (gps._save_config) { + // Save configuration for next startup + nmea_printf(port, "$PQTMSAVEPAR"); + } + } + + break; + } +#endif // AP_GPS_NMEA_QUECTEL_ENABLED + default: break; } @@ -933,6 +1200,11 @@ bool AP_GPS_NMEA::is_healthy(void) const // we should get vertical velocity and accuracy from PHD return _last_vvelocity_ms != 0 && _last_vaccuracy_ms != 0; +#if AP_GPS_NMEA_QUECTEL_ENABLED + case AP_GPS::GPS_TYPE_QUECTEL_NMEA: + // we should be getting PQTM messages for velocity and accuracy + return _last_PQTM_vel_ms != 0 && _last_PQTM_acc_ms != 0; +#endif // AP_GPS_NMEA_QUECTEL_ENABLED default: break; } @@ -970,6 +1242,14 @@ void AP_GPS_NMEA::Write_AP_Logger_Log_Startup_messages() const _versiona.build_date); } #endif +#if AP_GPS_NMEA_QUECTEL_ENABLED + if (_have_pqtmver) { + AP::logger().Write_MessageF("NMEA %u Quectel %s %s", + state.instance+1, + _pqtmver.version, + _pqtmver.build_date); + } +#endif // AP_GPS_NMEA_QUECTEL_ENABLED } #endif diff --git a/libraries/AP_GPS/AP_GPS_NMEA.h b/libraries/AP_GPS/AP_GPS_NMEA.h index 3109600b4ef8a2..44bfb2aef0d3df 100644 --- a/libraries/AP_GPS/AP_GPS_NMEA.h +++ b/libraries/AP_GPS/AP_GPS_NMEA.h @@ -89,6 +89,10 @@ class AP_GPS_NMEA : public AP_GPS_Backend _GPS_SENTENCE_AGRICA = 193, // extension for Unicore, 65 fields _GPS_SENTENCE_VERSIONA = 270, // extension for Unicore, version, 10 fields _GPS_SENTENCE_UNIHEADINGA = 290, // extension for Unicore, uniheadinga, 20 fields + _GPS_SENTENCE_PQTMVER = 320, // extension for Quectel, version info + _GPS_SENTENCE_PQTMEPE = 330, // extension for Quectel, estimated position error + _GPS_SENTENCE_PQTMVEL = 340, // extension for Quectel, velocity information + _GPS_SENTENCE_PQTMPVT = 360, // extension for Quectel, PVT result _GPS_SENTENCE_OTHER = 0 }; @@ -146,6 +150,15 @@ class AP_GPS_NMEA : public AP_GPS_Backend #endif #endif +#if AP_GPS_NMEA_QUECTEL_ENABLED + /* + parse PQTM message fields + */ + void parse_pqtmver_field(uint16_t term_number, const char *term); + void parse_pqtmepe_field(uint16_t term_number, const char *term); + void parse_pqtmvel_field(uint16_t term_number, const char *term); + void parse_pqtmpvt_field(uint16_t term_number, const char *term); +#endif uint8_t _parity; ///< NMEA message checksum accumulator uint32_t _crc32; ///< CRC for unicore messages @@ -257,6 +270,52 @@ class AP_GPS_NMEA : public AP_GPS_Backend } _uniheadinga; #endif #endif // AP_GPS_NMEA_UNICORE_ENABLED + +#if AP_GPS_NMEA_QUECTEL_ENABLED + /* + Quectel PQTM message parsing structures + */ + // PQTMVER parsing + struct { + char version[30]; // Version string (e.g., "LG290P03AANR01A05S") + char build_date[20]; // Build date (e.g., "2025/05/08") + char build_time[20]; // Build time (e.g., "16:23:00") + } _pqtmver; + bool _have_pqtmver; + + // PQTMEPE parsing - Estimated Position Error + struct { + float epe_north; + float epe_east; + float epe_down; + float epe_2d; + float epe_3d; + } _pqtmepe; + + // PQTMVEL parsing - Velocity Information + struct { + uint32_t time_ms; + Vector3f vel_NED; + float ground_speed; + float speed_3d; + float heading; + float ground_speed_acc; + float speed_acc; + float heading_acc; + } _pqtmvel; + + // PQTMPVT parsing - PVT Result + struct { + uint32_t tow; // Time of week + int32_t leap_seconds; // Leap seconds + float sep; // Geoidal separation (undulation) + float pdop; // PDOP (used for VDOP) + } _pqtmpvt; + + uint32_t _last_PQTM_vel_ms; + uint32_t _last_PQTM_acc_ms; +#endif // AP_GPS_NMEA_QUECTEL_ENABLED + bool _expect_pqtm; bool _expect_agrica; // last time we sent type specific config strings diff --git a/libraries/AP_GPS/AP_GPS_config.h b/libraries/AP_GPS/AP_GPS_config.h index ad6b5364a29dfd..66c005c5334570 100644 --- a/libraries/AP_GPS/AP_GPS_config.h +++ b/libraries/AP_GPS/AP_GPS_config.h @@ -73,6 +73,10 @@ #define AP_GPS_NMEA_UNICORE_ENABLED AP_GPS_NMEA_ENABLED #endif +#ifndef AP_GPS_NMEA_QUECTEL_ENABLED + #define AP_GPS_NMEA_QUECTEL_ENABLED AP_GPS_NMEA_ENABLED +#endif + #ifndef AP_GPS_NOVA_ENABLED #define AP_GPS_NOVA_ENABLED AP_GPS_BACKEND_DEFAULT_ENABLED #endif From 2b342362ca4da1a7c4e16804f2dddd10b410ca59 Mon Sep 17 00:00:00 2001 From: vidmantas zemleris Date: Sun, 1 Feb 2026 16:16:51 +0200 Subject: [PATCH 02/19] quectel nmea - apply peterbarker suggestions --- libraries/AP_GPS/AP_GPS_NMEA.cpp | 9 +++------ libraries/AP_GPS/AP_GPS_NMEA.h | 3 ++- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/libraries/AP_GPS/AP_GPS_NMEA.cpp b/libraries/AP_GPS/AP_GPS_NMEA.cpp index 6c37a38458e9d5..43b398d9294055 100644 --- a/libraries/AP_GPS/AP_GPS_NMEA.cpp +++ b/libraries/AP_GPS/AP_GPS_NMEA.cpp @@ -888,16 +888,13 @@ void AP_GPS_NMEA::parse_pqtmver_field(uint16_t term_number, const char *term) switch (term_number) { case 1: // Version string (VerStr) - strncpy(pv.version, term, sizeof(pv.version)-1); - pv.version[sizeof(pv.version)-1] = 0; + strncpy(pv.version, term, sizeof(pv.version)); break; case 2: // Build date (BuildDate) - strncpy(pv.build_date, term, sizeof(pv.build_date)-1); - pv.build_date[sizeof(pv.build_date)-1] = 0; + strncpy(pv.build_date, term, sizeof(pv.build_date)); break; case 3: // Build time (BuildTime) - strncpy(pv.build_time, term, sizeof(pv.build_time)-1); - pv.build_time[sizeof(pv.build_time)-1] = 0; + strncpy(pv.build_time, term, sizeof(pv.build_time)); _have_pqtmver = true; break; } diff --git a/libraries/AP_GPS/AP_GPS_NMEA.h b/libraries/AP_GPS/AP_GPS_NMEA.h index 44bfb2aef0d3df..a4c2b46272c9dd 100644 --- a/libraries/AP_GPS/AP_GPS_NMEA.h +++ b/libraries/AP_GPS/AP_GPS_NMEA.h @@ -314,8 +314,9 @@ class AP_GPS_NMEA : public AP_GPS_Backend uint32_t _last_PQTM_vel_ms; uint32_t _last_PQTM_acc_ms; -#endif // AP_GPS_NMEA_QUECTEL_ENABLED bool _expect_pqtm; +#endif // AP_GPS_NMEA_QUECTEL_ENABLED + bool _expect_agrica; // last time we sent type specific config strings From acafa32b7c51929fb1033b2038ae79ed94cff13e Mon Sep 17 00:00:00 2001 From: vidmantas zemleris Date: Sun, 1 Feb 2026 17:53:29 +0200 Subject: [PATCH 03/19] quectel add - pqmtar heading --- libraries/AP_GPS/AP_GPS_NMEA.cpp | 128 +++++++++++++++++++++++++++++++ libraries/AP_GPS/AP_GPS_NMEA.h | 22 ++++++ 2 files changed, 150 insertions(+) diff --git a/libraries/AP_GPS/AP_GPS_NMEA.cpp b/libraries/AP_GPS/AP_GPS_NMEA.cpp index 576f81f8cbd171..edb0daa9c654f3 100644 --- a/libraries/AP_GPS/AP_GPS_NMEA.cpp +++ b/libraries/AP_GPS/AP_GPS_NMEA.cpp @@ -605,6 +605,10 @@ bool AP_GPS_NMEA::_term_complete() _sentence_type = _GPS_SENTENCE_PQTMPVT; return false; } + if (strcmp(_term, "PQTMTAR") == 0 && _expect_pqtm) { + _sentence_type = _GPS_SENTENCE_PQTMTAR; + return false; + } #endif // AP_GPS_NMEA_QUECTEL_ENABLED /* The first two letters of the NMEA term are the talker @@ -748,6 +752,11 @@ bool AP_GPS_NMEA::_term_complete() case _GPS_SENTENCE_PQTMPVT + 1 ... _GPS_SENTENCE_PQTMPVT + 19: // PQTMPVT message (19 fields) parse_pqtmpvt_field(_term_number, _term); break; + #if GPS_MOVING_BASELINE + case _GPS_SENTENCE_PQTMTAR + 1 ... _GPS_SENTENCE_PQTMTAR + 12: // PQTMTAR message (12 fields) + parse_pqtmtar_field(_term_number, _term); + break; + #endif // GPS_MOVING_BASELINE #endif // AP_GPS_NMEA_QUECTEL_ENABLED } } @@ -1012,6 +1021,125 @@ void AP_GPS_NMEA::parse_pqtmpvt_field(uint16_t term_number, const char *term) break; } } + +#if GPS_MOVING_BASELINE + +/* + parse PQTMTAR field - Outputs the time and attitude. The attitude computation in this message is computed from the two-antenna system. + Only the LG580P supports this message. + + Examples: + - with RTK fix: + $PQTMTAR,1,215951.600,5,,0.206,-11.841884,,234.657328,89.989998,,177.466919,20*49 + - without RTK fix: + $PQTMTAR,1,215857.400,1,,0.000,,,,,,,21*64 + + + Format: + $PQTMTAR,,