LCOV - code coverage report
Current view: top level - AP_GPS - AP_GPS_NMEA.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 285 563 50.6 %
Date: 2026-09-09 11:02:08 Functions: 11 14 78.6 %

          Line data    Source code
       1             : /*
       2             :    This program is free software: you can redistribute it and/or modify
       3             :    it under the terms of the GNU General Public License as published by
       4             :    the Free Software Foundation, either version 3 of the License, or
       5             :    (at your option) any later version.
       6             : 
       7             :    This program is distributed in the hope that it will be useful,
       8             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
       9             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      10             :    GNU General Public License for more details.
      11             : 
      12             :    You should have received a copy of the GNU General Public License
      13             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.
      14             :  */
      15             : 
      16             : //
      17             : // NMEA parser, adapted by Michael Smith from TinyGPS v9:
      18             : //
      19             : // TinyGPS - a small GPS library for Arduino providing basic NMEA parsing
      20             : // Copyright (C) 2008-9 Mikal Hart
      21             : // All rights reserved.
      22             : //
      23             : 
      24             : /// @file       AP_GPS_NMEA.cpp
      25             : /// @brief      NMEA protocol parser
      26             : ///
      27             : /// This is a lightweight NMEA parser, derived originally from the
      28             : /// TinyGPS parser by Mikal Hart.
      29             : ///
      30             : 
      31             : #include <AP_Common/AP_Common.h>
      32             : #include <AP_Common/NMEA.h>
      33             : #include <GCS_MAVLink/GCS.h>
      34             : #include <AP_Logger/AP_Logger.h>
      35             : 
      36             : #include <ctype.h>
      37             : #include <stdint.h>
      38             : #include <stdlib.h>
      39             : #include <stdio.h>
      40             : 
      41             : #include "AP_GPS_NMEA.h"
      42             : 
      43             : #if AP_GPS_NMEA_ENABLED
      44             : extern const AP_HAL::HAL& hal;
      45             : 
      46             : #ifndef AP_GPS_NMEA_CONFIG_PERIOD_MS
      47             : // how often we send board specific config commands
      48             : #define AP_GPS_NMEA_CONFIG_PERIOD_MS 15000U
      49             : #endif
      50             : 
      51             : // a quiet nan for invalid values
      52             : #define QNAN nanf("GPS")
      53             : 
      54             : // Convenience macros //////////////////////////////////////////////////////////
      55             : //
      56             : #define DIGIT_TO_VAL(_x)        (_x - '0')
      57             : #define hexdigit(x) ((x)>9?'A'+((x)-10):'0'+(x))
      58             : 
      59        3721 : bool AP_GPS_NMEA::read(void)
      60             : {
      61             :     int16_t numc;
      62        3721 :     bool parsed = false;
      63             : 
      64        3721 :     send_config();
      65             : 
      66        3721 :     numc = port->available();
      67       89040 :     while (numc--) {
      68       85319 :         char c = port->read();
      69             : #if AP_GPS_DEBUG_LOGGING_ENABLED
      70             :         log_data((const uint8_t *)&c, 1);
      71             : #endif
      72       85319 :         if (_decode(c)) {
      73         403 :             parsed = true;
      74             :         }
      75             :     }
      76        3721 :     return parsed;
      77             : }
      78             : 
      79             : /*
      80             :   decode one character, return true if we have successfully completed a sentence, false otherwise
      81             :  */
      82       85319 : bool AP_GPS_NMEA::_decode(char c)
      83             : {
      84       85319 :     _sentence_length++;
      85             :         
      86       85319 :     switch (c) {
      87           0 :     case ';':
      88             :         // header separator for unicore
      89           0 :         if (!_is_unicore) {
      90           0 :             return false;
      91             :         }
      92             :         FALLTHROUGH;
      93             :     case ',': // term terminators
      94       13926 :         _parity ^= c;
      95       13926 :         if (_is_unicore) {
      96           2 :             _crc32 = crc_crc32(_crc32, (const uint8_t *)&c, 1);
      97             :         }
      98             :         FALLTHROUGH;
      99             :     case '\r':
     100             :     case '\n':
     101             :     case '*': {
     102       18327 :         if (_sentence_done) {
     103        1502 :             return false;
     104             :         }
     105       16825 :         bool valid_sentence = false;
     106       16825 :         if (_term_offset < sizeof(_term)) {
     107       16825 :             _term[_term_offset] = 0;
     108       16825 :             valid_sentence = _term_complete();
     109             :         }
     110       16825 :         ++_term_number;
     111       16825 :         _term_offset = 0;
     112       16825 :         _is_checksum_term = c == '*';
     113       16825 :         return valid_sentence;
     114             :     }
     115             : 
     116        1461 :     case '$': // sentence begin
     117             :     case '#': // unicore message begin
     118        1461 :         _is_unicore = (c == '#');
     119        1461 :         _term_number = _term_offset = 0;
     120        1461 :         _parity = 0;
     121        1461 :         _crc32 = 0;
     122        1461 :         _sentence_type = _GPS_SENTENCE_OTHER;
     123        1461 :         _is_checksum_term = false;
     124        1461 :         _sentence_length = 1;
     125        1461 :         _sentence_done = false;
     126        1461 :         _new_gps_yaw = QNAN;
     127        1461 :         return false;
     128             :     }
     129             : 
     130             :     // ordinary characters
     131       65531 :     if (_term_offset < sizeof(_term) - 1)
     132       61605 :         _term[_term_offset++] = c;
     133       65531 :     if (!_is_checksum_term) {
     134       62633 :         _parity ^= c;
     135       62633 :         if (_is_unicore) {
     136          63 :             _crc32 = crc_crc32(_crc32, (const uint8_t *)&c, 1);
     137             :         }
     138             :     }
     139             : 
     140       65531 :     return false;
     141             : }
     142             : 
     143        3456 : int32_t AP_GPS_NMEA::_parse_decimal_100(const char *p)
     144             : {
     145        3456 :     char *endptr = nullptr;
     146        3456 :     long ret = 100 * strtol(p, &endptr, 10);
     147        3456 :     int sign = ret < 0 ? -1 : 1;
     148             : 
     149        3456 :     if (ret >= (long)INT32_MAX) {
     150           0 :         return INT32_MAX;
     151             :     }
     152        3456 :     if (ret <= (long)INT32_MIN) {
     153           0 :         return INT32_MIN;
     154             :     }
     155        3456 :     if (endptr == nullptr || *endptr != '.') {
     156           4 :         return ret;
     157             :     }
     158             : 
     159        3452 :     if (isdigit(endptr[1])) {
     160        3452 :         ret += sign * 10 * DIGIT_TO_VAL(endptr[1]);
     161        3452 :         if (isdigit(endptr[2])) {
     162        3041 :             ret += sign * DIGIT_TO_VAL(endptr[2]);
     163        3041 :             if (isdigit(endptr[3])) {
     164         715 :                 ret += sign * (DIGIT_TO_VAL(endptr[3]) >= 5);
     165             :             }
     166             :         }
     167             :     }
     168        3452 :     return ret;
     169             : }
     170             : 
     171             : /*
     172             :   parse a NMEA latitude/longitude degree value. The result is in degrees*1e7
     173             :  */
     174        1620 : uint32_t AP_GPS_NMEA::_parse_degrees()
     175             : {
     176             :     char *p, *q;
     177        1620 :     uint8_t deg = 0, min = 0;
     178        1620 :     float frac_min = 0;
     179        1620 :     int32_t ret = 0;
     180             : 
     181             :     // scan for decimal point or end of field
     182        8910 :     for (p = _term; *p && isdigit(*p); p++)
     183             :         ;
     184        1620 :     q = _term;
     185             : 
     186             :     // convert degrees
     187        5670 :     while ((p - q) > 2 && *q) {
     188        4050 :         if (deg)
     189        2430 :             deg *= 10;
     190        4050 :         deg += DIGIT_TO_VAL(*q++);
     191             :     }
     192             : 
     193             :     // convert minutes
     194        4860 :     while (p > q && *q) {
     195        3240 :         if (min)
     196        1207 :             min *= 10;
     197        3240 :         min += DIGIT_TO_VAL(*q++);
     198             :     }
     199             : 
     200             :     // convert fractional minutes
     201        1620 :     if (*p == '.') {
     202        1620 :         q = p + 1;
     203        1620 :         float frac_scale = 0.1f;
     204        9720 :         while (*q && isdigit(*q)) {
     205        8100 :             frac_min += DIGIT_TO_VAL(*q) * frac_scale;
     206        8100 :             q++;
     207        8100 :             frac_scale *= 0.1f;
     208             :         }
     209             :     }
     210        1620 :     ret = (deg * (int32_t)10000000UL);
     211        1620 :     ret += (min * (int32_t)10000000UL / 60);
     212        1620 :     ret += (int32_t) (frac_min * (1.0e7f / 60.0f));
     213        1620 :     return ret;
     214             : }
     215             : 
     216             : /*
     217             :   see if we have a new set of NMEA messages
     218             :  */
     219        1449 : bool AP_GPS_NMEA::_have_new_message()
     220             : {
     221        1449 :     if (_last_RMC_ms == 0 ||
     222        1446 :         _last_GGA_ms == 0) {
     223           9 :         return false;
     224             :     }
     225        1440 :     uint32_t now = AP_HAL::millis();
     226        1440 :     if (now - _last_RMC_ms > 150 ||
     227         425 :         now - _last_GGA_ms > 150) {
     228        1037 :         return false;
     229             :     }
     230         403 :     if (_last_VTG_ms != 0 && 
     231         348 :         now - _last_VTG_ms > 150) {
     232           0 :         return false;
     233             :     }
     234             : 
     235             :     /*
     236             :       if we have seen a message with 3D velocity data messages then
     237             :       wait for them again. This is important as the
     238             :       have_vertical_velocity field will be overwritten by
     239             :       fill_3d_velocity()
     240             :      */
     241         403 :     if (_last_vvelocity_ms != 0 &&
     242           0 :         now - _last_vvelocity_ms > 150 &&
     243           0 :         now - _last_vvelocity_ms < 1000) {
     244             :         // waiting on a message with velocity
     245           0 :         return false;
     246             :     }
     247         403 :     if (_last_vaccuracy_ms != 0 &&
     248           0 :         now - _last_vaccuracy_ms > 150 &&
     249           0 :         now - _last_vaccuracy_ms < 1000) {
     250             :         // waiting on a message with velocity accuracy
     251           0 :         return false;
     252             :     }
     253             : 
     254             :     // prevent these messages being used again
     255         403 :     if (_last_VTG_ms != 0) {
     256         348 :         _last_VTG_ms = 1;
     257             :     }
     258             : 
     259         403 :     if (now - _last_yaw_ms > 300) {
     260             :         // we have lost GPS yaw
     261         117 :         state.have_gps_yaw = false;
     262             :     }
     263             : 
     264         403 :     if (now - _last_KSXT_pos_ms > 500) {
     265             :         // we have lost KSXT
     266         403 :         _last_KSXT_pos_ms = 0;
     267             :     }
     268             : 
     269             : #if AP_GPS_NMEA_UNICORE_ENABLED
     270         403 :     if (now - _last_AGRICA_ms > 500) {
     271         403 :         if (_last_AGRICA_ms != 0) {
     272             :             // we have lost AGRICA
     273           0 :             state.have_gps_yaw = false;
     274             : #if AP_GPS_MB_YAW_OFFSET_ENABLED
     275             :             // the moving baseline offset must not outlive the yaw solution it
     276             :             // was calculated for
     277           0 :             state.mb_yaw_offset.zero();
     278             : #endif
     279           0 :             state.have_vertical_velocity = false;
     280           0 :             state.have_speed_accuracy = false;
     281           0 :             state.have_horizontal_accuracy = false;
     282           0 :             state.have_vertical_accuracy = false;
     283           0 :             state.have_undulation = false;
     284           0 :             _last_AGRICA_ms = 0;
     285             :         }
     286             :     }
     287             : #endif // AP_GPS_NMEA_UNICORE_ENABLED
     288             : 
     289         403 :     _last_fix_ms = now;
     290             : 
     291         403 :     _last_GGA_ms = 1;
     292         403 :     _last_RMC_ms = 1;
     293         403 :     return true;
     294             : }
     295             : 
     296             : // Processes a just-completed term
     297             : // Returns true if new sentence has just passed checksum test and is validated
     298       16825 : bool AP_GPS_NMEA::_term_complete()
     299             : {
     300             :     // handle the last term in a message
     301       16825 :     if (_is_checksum_term) {
     302        1449 :         _sentence_done = true;
     303        1449 :         const uint32_t crc = strtoul(_term, nullptr, 16);
     304        1449 :         const bool crc_ok = _is_unicore? (_crc32 == crc) : (_parity == crc);
     305        1449 :         if (crc_ok) {
     306        1449 :             uint32_t now = AP_HAL::millis();
     307        1449 :             switch (_sentence_type) {
     308         407 :             case _GPS_SENTENCE_RMC:
     309         407 :                 _last_RMC_ms = now;
     310             :                 //time                        = _new_time;
     311             :                 //date                        = _new_date;
     312         407 :                 if (_last_KSXT_pos_ms == 0 && _last_AGRICA_ms == 0) {
     313         407 :                     state.location.lat     = _new_latitude;
     314         407 :                     state.location.lng     = _new_longitude;
     315             :                 }
     316         407 :                 if (_last_3D_velocity_ms == 0 ||
     317           0 :                     now - _last_3D_velocity_ms > 1000) {
     318         407 :                     state.ground_speed     = _new_speed*0.01f;
     319         407 :                     state.ground_course    = wrap_360(_new_course*0.01f);
     320             :                 }
     321         407 :                 if (state.status >= AP_GPS_FixType::FIX_3D) {
     322         403 :                     make_gps_time(_new_date, _new_time * 10);
     323         403 :                     if (_last_AGRICA_ms != 0) {
     324           0 :                         state.time_week_ms = _last_itow_ms;
     325             :                     }
     326             :                 }
     327         407 :                 set_uart_timestamp(_sentence_length);
     328         407 :                 state.last_gps_time_ms = now;
     329         407 :                 if (_last_vvelocity_ms == 0 ||
     330           0 :                     now - _last_vvelocity_ms > 1000) {
     331         407 :                     fill_3d_velocity();
     332             :                 }
     333         407 :                 break;
     334         403 :             case _GPS_SENTENCE_GGA:
     335         403 :                 _last_GGA_ms = now;
     336         403 :                 if (_last_KSXT_pos_ms == 0 && _last_AGRICA_ms == 0) {
     337         403 :                     set_alt_amsl_cm(state, _new_altitude);
     338         403 :                     state.location.lat  = _new_latitude;
     339         403 :                     state.location.lng  = _new_longitude;
     340             :                 }
     341         403 :                 state.num_sats      = _new_satellite_count;
     342         403 :                 state.hdop          = _new_hdop;
     343         403 :                 switch(_new_quality_indicator) {
     344           0 :                 case 0: // Fix not available or invalid
     345           0 :                     state.status = AP_GPS_FixType::NONE;
     346           0 :                     break;
     347         348 :                 case 1: // GPS SPS Mode, fix valid
     348         348 :                     state.status = AP_GPS_FixType::FIX_3D;
     349         348 :                     break;
     350           0 :                 case 2: // Differential GPS, SPS Mode, fix valid
     351           0 :                     state.status = AP_GPS_FixType::DGPS;
     352           0 :                     break;
     353           0 :                 case 3: // GPS PPS Mode, fix valid
     354           0 :                     state.status = AP_GPS_FixType::FIX_3D;
     355           0 :                     break;
     356          55 :                 case 4: // Real Time Kinematic. System used in RTK mode with fixed integers
     357          55 :                     state.status = AP_GPS_FixType::RTK_FIXED;
     358          55 :                     break;
     359           0 :                 case 5: // Float RTK. Satellite system used in RTK mode, floating integers
     360           0 :                     state.status = AP_GPS_FixType::RTK_FLOAT;
     361           0 :                     break;
     362           0 :                 case 6: // Estimated (dead reckoning) Mode
     363           0 :                     state.status = AP_GPS_FixType::NONE;
     364           0 :                     break;
     365           0 :                 default://to maintain compatibility with MAV_GPS_INPUT and others
     366           0 :                     state.status = AP_GPS_FixType::FIX_3D;
     367           0 :                     break;
     368             :                 }
     369         403 :                 break;
     370         351 :             case _GPS_SENTENCE_VTG:
     371         351 :                 _last_VTG_ms = now;
     372         351 :                 if (_last_3D_velocity_ms == 0 ||
     373           0 :                     now - _last_3D_velocity_ms > 1000) {
     374         351 :                     state.ground_speed  = _new_speed*0.01f;
     375         351 :                     state.ground_course = wrap_360(_new_course*0.01f);
     376         351 :                     if (_last_vvelocity_ms == 0 ||
     377           0 :                         now - _last_vvelocity_ms > 1000) {
     378         351 :                         fill_3d_velocity();
     379             :                     }
     380             :                 }
     381             :                 // VTG has no fix indicator, can't change fix status
     382         351 :                 break;
     383         288 :             case _GPS_SENTENCE_HDT:
     384             :             case _GPS_SENTENCE_THS:
     385         288 :                 if (_last_AGRICA_ms != 0 || _expect_agrica) {
     386             :                     // use AGRICA
     387             :                     break;
     388             :                 }
     389         288 :                 if (isnan(_new_gps_yaw)) {
     390             :                     // empty sentence
     391           0 :                     break;
     392             :                 }
     393         288 :                 _last_yaw_ms = now;
     394         288 :                 state.gps_yaw = wrap_360(_new_gps_yaw*0.01f);
     395         288 :                 state.have_gps_yaw = true;
     396         288 :                 state.gps_yaw_time_ms = now;
     397             : #if AP_GPS_MB_YAW_OFFSET_ENABLED
     398             :                 // this yaw is the device-reported heading, not one calculated
     399             :                 // from a moving baseline offset; clear any stale offset so no
     400             :                 // attitude correction is applied to it
     401         288 :                 state.mb_yaw_offset.zero();
     402             : #endif
     403             :                 // remember that we are setup to provide yaw. With
     404             :                 // a NMEA GPS we can only tell if the GPS is
     405             :                 // configured to provide yaw when it first sends a
     406             :                 // HDT sentence.
     407         288 :                 state.gps_yaw_configured = true;
     408         288 :                 break;
     409           0 :             case _GPS_SENTENCE_PHD:
     410           0 :                 if (_last_AGRICA_ms != 0) {
     411             :                     // prefer AGRICA
     412           0 :                     break;
     413             :                 }
     414           0 :                 if (_phd.msg_id == 12) {
     415           0 :                     state.velocity.x = _phd.fields[0] * 0.01;
     416           0 :                     state.velocity.y = _phd.fields[1] * 0.01;
     417           0 :                     state.velocity.z = _phd.fields[2] * 0.01;
     418           0 :                     state.have_vertical_velocity = true;
     419           0 :                     _last_vvelocity_ms = now;
     420             :                     // we prefer a true 3D velocity when available
     421           0 :                     velocity_to_speed_course(state);
     422           0 :                     _last_3D_velocity_ms = now;
     423           0 :                 } else if (_phd.msg_id == 26) {
     424           0 :                     state.horizontal_accuracy = MAX(_phd.fields[0],_phd.fields[1]) * 0.001;
     425           0 :                     state.have_horizontal_accuracy = true;
     426           0 :                     state.vertical_accuracy = _phd.fields[2] * 0.001;
     427           0 :                     state.have_vertical_accuracy = true;
     428           0 :                     state.speed_accuracy = MAX(_phd.fields[3],_phd.fields[4]) * 0.001;
     429           0 :                     state.have_speed_accuracy = true;
     430           0 :                     _last_vaccuracy_ms = now;
     431             :                 }
     432           0 :                 break;
     433           0 :             case _GPS_SENTENCE_KSXT:
     434           0 :                 if (_last_AGRICA_ms != 0 || _expect_agrica) {
     435             :                     // prefer AGRICA
     436             :                     break;
     437             :                 }
     438           0 :                 state.location.lat     = _ksxt.fields[2]*1.0e7;
     439           0 :                 state.location.lng     = _ksxt.fields[1]*1.0e7;
     440           0 :                 set_alt_amsl_cm(state, _ksxt.fields[3]*1.0e2);
     441           0 :                 _last_KSXT_pos_ms = now;
     442           0 :                 if (_ksxt.fields[9] >= 1) {
     443             :                     // we have 3D fix
     444           0 :                     constexpr float kmh_to_mps = 1.0 / 3.6;
     445           0 :                     state.velocity.y = _ksxt.fields[16] * kmh_to_mps;
     446           0 :                     state.velocity.x = _ksxt.fields[17] * kmh_to_mps;
     447           0 :                     state.velocity.z = _ksxt.fields[18] * -kmh_to_mps;
     448           0 :                     state.have_vertical_velocity = true;
     449           0 :                     _last_vvelocity_ms = now;
     450             :                     // we prefer a true 3D velocity when available
     451           0 :                     velocity_to_speed_course(state);
     452           0 :                     _last_3D_velocity_ms = now;
     453             :                 }
     454           0 :                 if (is_equal(3.0f, float(_ksxt.fields[10]))) {
     455             :                     // have good yaw (from RTK fixed moving baseline solution)
     456           0 :                     _last_yaw_ms = now;
     457           0 :                     state.gps_yaw = _ksxt.fields[4];
     458           0 :                     state.have_gps_yaw = true;
     459           0 :                     state.gps_yaw_time_ms = now;
     460           0 :                     state.gps_yaw_configured = true;
     461             : #if AP_GPS_MB_YAW_OFFSET_ENABLED
     462             :                     // this yaw is the receiver's own heading, not one calculated
     463             :                     // from the configured moving baseline offset; clear any stale
     464             :                     // offset so no attitude correction is applied to it
     465           0 :                     state.mb_yaw_offset.zero();
     466             : #endif
     467             :                 }
     468           0 :                 break;
     469             : #if AP_GPS_NMEA_UNICORE_ENABLED
     470           0 :             case _GPS_SENTENCE_AGRICA: {
     471           0 :                 const auto &ag = _agrica;
     472           0 :                 _last_AGRICA_ms = now;
     473           0 :                 _last_vvelocity_ms = now;
     474           0 :                 _last_vaccuracy_ms = now;
     475           0 :                 _last_3D_velocity_ms = now;
     476           0 :                 state.location.lat = ag.lat*1.0e7;
     477           0 :                 state.location.lng = ag.lng*1.0e7;
     478           0 :                 state.undulation   = -ag.undulation;
     479           0 :                 state.have_undulation = true;
     480           0 :                 set_alt_amsl_cm(state, ag.alt*1.0e2);
     481           0 :                 state.velocity = ag.vel_NED;
     482           0 :                 velocity_to_speed_course(state);
     483           0 :                 state.speed_accuracy = ag.vel_stddev.length();
     484           0 :                 state.horizontal_accuracy = ag.pos_stddev.xy().length();
     485           0 :                 state.vertical_accuracy = ag.pos_stddev.z;
     486           0 :                 state.have_vertical_velocity = true;
     487           0 :                 state.have_speed_accuracy = true;
     488           0 :                 state.have_horizontal_accuracy = true;
     489           0 :                 state.have_vertical_accuracy = true;
     490           0 :                 check_new_itow(ag.itow, _sentence_length);
     491           0 :                 break;
     492             :             }
     493           0 :             case _GPS_SENTENCE_VERSIONA: {
     494           0 :                 _have_unicore_versiona = true;
     495           0 :                 GCS_SEND_TEXT(MAV_SEVERITY_INFO,
     496             :                               "NMEA %s %s %s",
     497             :                               _versiona.type,
     498             :                               _versiona.version,
     499             :                               _versiona.build_date);
     500           0 :                 break;
     501             :             }
     502           0 :             case _GPS_SENTENCE_UNIHEADINGA: {
     503             : #if GPS_MOVING_BASELINE
     504           0 :                 const auto &ag = _agrica;
     505           0 :                 const auto &uh = _uniheadinga;
     506           0 :                 if (now - _last_AGRICA_ms > 500 || ag.heading_status != 4) {
     507             :                     // we need heading_status from AGRICA
     508           0 :                     state.have_gps_yaw = false;
     509           0 :                     break;
     510             :                 }
     511           0 :                 const float dist = uh.baseline_length;
     512           0 :                 const float bearing = uh.heading;
     513           0 :                 const float alt_diff = dist*sinf(radians(-uh.pitch));
     514           0 :                 state.relPosHeading = bearing;
     515           0 :                 state.relPosLength = dist;
     516           0 :                 state.relPosD = alt_diff;
     517           0 :                 state.relposheading_ts = now;
     518           0 :                 if (calculate_moving_base_yaw(bearing, dist, alt_diff)) {
     519           0 :                     state.have_gps_yaw_accuracy = true;
     520           0 :                     state.gps_yaw_accuracy = uh.heading_sd;
     521           0 :                     _last_yaw_ms = now;
     522             :                 }
     523           0 :                 state.gps_yaw_configured = true;
     524             : #endif // GPS_MOVING_BASELINE
     525           0 :                 break;
     526             :             }
     527             : #endif // AP_GPS_NMEA_UNICORE_ENABLED
     528             :             }
     529             :             // see if we got a good message
     530        1449 :             return _have_new_message();
     531             :         }
     532             :         // we got a bad message, ignore it
     533           0 :         return false;
     534             :     }
     535             : 
     536             :     // the first term determines the sentence type
     537       15376 :     if (_term_number == 0) {
     538             :         /*
     539             :           special case for $PHD message
     540             :          */
     541        1465 :         if (strcmp(_term, "PHD") == 0) {
     542           0 :             _sentence_type = _GPS_SENTENCE_PHD;
     543           0 :             return false;
     544             :         }
     545        1465 :         if (strcmp(_term, "KSXT") == 0) {
     546           0 :             _sentence_type = _GPS_SENTENCE_KSXT;
     547           0 :             return false;
     548             :         }
     549             : #if AP_GPS_NMEA_UNICORE_ENABLED
     550        1465 :         if (strcmp(_term, "AGRICA") == 0 && _expect_agrica) {
     551           0 :             _sentence_type = _GPS_SENTENCE_AGRICA;
     552           0 :             return false;
     553             :         }
     554        1465 :         if (strcmp(_term, "VERSIONA") == 0) {
     555           0 :             _sentence_type = _GPS_SENTENCE_VERSIONA;
     556           0 :             return false;
     557             :         }
     558        1465 :         if (strcmp(_term, "UNIHEADINGA") == 0 && _expect_agrica) {
     559           0 :             _sentence_type = _GPS_SENTENCE_UNIHEADINGA;
     560           0 :             return false;
     561             :         }
     562             : #endif
     563             :         /*
     564             :           The first two letters of the NMEA term are the talker
     565             :           ID. The most common is 'GP' but there are a bunch of others
     566             :           that are valid. We accept any two characters here.
     567             :          */
     568        1465 :         if (_term[0] < 'A' || _term[0] > 'Z' ||
     569        1449 :             _term[1] < 'A' || _term[1] > 'Z') {
     570          16 :             _sentence_type = _GPS_SENTENCE_OTHER;
     571          16 :             return false;
     572             :         }
     573        1449 :         const char *term_type = &_term[2];
     574        1449 :         if (strcmp(term_type, "RMC") == 0) {
     575         407 :             _sentence_type = _GPS_SENTENCE_RMC;
     576        1042 :         } else if (strcmp(term_type, "GGA") == 0) {
     577         403 :             _sentence_type = _GPS_SENTENCE_GGA;
     578         639 :         } else if (strcmp(term_type, "HDT") == 0) {
     579         288 :             _sentence_type = _GPS_SENTENCE_HDT;
     580         351 :         } else if (strcmp(term_type, "THS") == 0) {
     581           0 :             _sentence_type = _GPS_SENTENCE_THS;
     582         351 :         } else if (strcmp(term_type, "VTG") == 0) {
     583         351 :             _sentence_type = _GPS_SENTENCE_VTG;
     584             :         } else {
     585           0 :             _sentence_type = _GPS_SENTENCE_OTHER;
     586             :         }
     587        1449 :         return false;
     588             :     }
     589             : 
     590             :     // 32 = RMC, 64 = GGA, 96 = VTG, 128 = HDT, 160 = THS
     591       13911 :     if (_sentence_type != _GPS_SENTENCE_OTHER && _term[0]) {
     592       12234 :         switch (_sentence_type + _term_number) {
     593             :         // operational status
     594             :         //
     595         407 :         case _GPS_SENTENCE_RMC + 2: // validity (RMC)
     596         407 :             break;
     597         403 :         case _GPS_SENTENCE_GGA + 6: // Fix data (GGA)
     598         403 :             if (_term[0] > '0') {
     599         403 :                 _new_quality_indicator = _term[0] - '0';
     600             :             } else {
     601           0 :                 _new_quality_indicator = 0;
     602             :             }
     603         403 :             break;
     604         403 :         case _GPS_SENTENCE_GGA + 7: // satellite count (GGA)
     605         403 :             _new_satellite_count = atol(_term);
     606         403 :             break;
     607         403 :         case _GPS_SENTENCE_GGA + 8: // HDOP (GGA)
     608         403 :             _new_hdop = (uint16_t)_parse_decimal_100(_term);
     609         403 :             break;
     610             : 
     611             :         // time and date
     612             :         //
     613         810 :         case _GPS_SENTENCE_RMC + 1: // Time (RMC)
     614             :         case _GPS_SENTENCE_GGA + 1: // Time (GGA)
     615         810 :             _new_time = _parse_decimal_100(_term);
     616         810 :             break;
     617         407 :         case _GPS_SENTENCE_RMC + 9: // Date (GPRMC)
     618         407 :             _new_date = atol(_term);
     619         407 :             break;
     620             : 
     621             :         // location
     622             :         //
     623         810 :         case _GPS_SENTENCE_RMC + 3: // Latitude
     624             :         case _GPS_SENTENCE_GGA + 2:
     625         810 :             _new_latitude = _parse_degrees();
     626         810 :             break;
     627         810 :         case _GPS_SENTENCE_RMC + 4: // N/S
     628             :         case _GPS_SENTENCE_GGA + 3:
     629         810 :             if (_term[0] == 'S')
     630         236 :                 _new_latitude = -_new_latitude;
     631         810 :             break;
     632         810 :         case _GPS_SENTENCE_RMC + 5: // Longitude
     633             :         case _GPS_SENTENCE_GGA + 4:
     634         810 :             _new_longitude = _parse_degrees();
     635         810 :             break;
     636         810 :         case _GPS_SENTENCE_RMC + 6: // E/W
     637             :         case _GPS_SENTENCE_GGA + 5:
     638         810 :             if (_term[0] == 'W')
     639         574 :                 _new_longitude = -_new_longitude;
     640         810 :             break;
     641         403 :         case _GPS_SENTENCE_GGA + 9: // Altitude (GPGGA)
     642         403 :             _new_altitude = _parse_decimal_100(_term);
     643         403 :             break;
     644             : 
     645             :         // course and speed
     646             :         //
     647         758 :         case _GPS_SENTENCE_RMC + 7: // Speed (GPRMC)
     648             :         case _GPS_SENTENCE_VTG + 5: // Speed (VTG)
     649         758 :             _new_speed = (_parse_decimal_100(_term) * 514) / 1000;       // knots-> m/sec, approximates * 0.514
     650         758 :             break;
     651         288 :         case _GPS_SENTENCE_HDT + 1: // Course (HDT)
     652         288 :             _new_gps_yaw = _parse_decimal_100(_term);
     653         288 :             break;
     654           0 :         case _GPS_SENTENCE_THS + 1: // Course (THS)
     655           0 :             _new_gps_yaw = _parse_decimal_100(_term);
     656           0 :             break;
     657         758 :         case _GPS_SENTENCE_RMC + 8: // Course (GPRMC)
     658             :         case _GPS_SENTENCE_VTG + 1: // Course (VTG)
     659         758 :             _new_course = _parse_decimal_100(_term);
     660         758 :             break;
     661             : 
     662           0 :         case _GPS_SENTENCE_PHD + 1: // PHD class
     663           0 :             _phd.msg_class = atol(_term);
     664           0 :             break;
     665           0 :         case _GPS_SENTENCE_PHD + 2: // PHD message
     666           0 :             _phd.msg_id = atol(_term);
     667           0 :             break;
     668           0 :         case _GPS_SENTENCE_PHD + 5: // PHD message, itow
     669           0 :             _phd.itow = strtoul(_term, nullptr, 10);
     670           0 :             break;
     671           0 :         case _GPS_SENTENCE_PHD + 6 ... _GPS_SENTENCE_PHD + 11: // PHD message, fields
     672           0 :             _phd.fields[_term_number-6] = atol(_term);
     673           0 :             break;
     674           0 :         case _GPS_SENTENCE_KSXT + 1 ... _GPS_SENTENCE_KSXT + 22: // KSXT message, fields
     675           0 :             _ksxt.fields[_term_number-1] = atof(_term);
     676           0 :             break;
     677             : #if AP_GPS_NMEA_UNICORE_ENABLED
     678           0 :         case _GPS_SENTENCE_AGRICA + 1 ... _GPS_SENTENCE_AGRICA + 65: // AGRICA message
     679           0 :             parse_agrica_field(_term_number, _term);
     680           0 :             break;
     681           0 :         case _GPS_SENTENCE_VERSIONA + 1 ... _GPS_SENTENCE_VERSIONA + 20:
     682           0 :             parse_versiona_field(_term_number, _term);
     683           0 :             break;
     684             : #if GPS_MOVING_BASELINE
     685           0 :         case _GPS_SENTENCE_UNIHEADINGA + 1 ... _GPS_SENTENCE_UNIHEADINGA + 28: // UNIHEADINGA message
     686           0 :             parse_uniheadinga_field(_term_number, _term);
     687           0 :             break;
     688             : #endif
     689             : #endif
     690             :         }
     691             :     }
     692             : 
     693       13911 :     return false;
     694             : }
     695             : 
     696             : #if AP_GPS_NMEA_UNICORE_ENABLED
     697             : /*
     698             :   parse an AGRICA message term
     699             : 
     700             :   Example:
     701             :      #AGRICA,82,GPS,FINE,2237,176366400,0,0,18,15;GNSS,232,22,11,22,0,59,8,1,5,8,12,0,0.0000,0.0000,0.0000,0.0000,0.0000,0.0000,296.4656,-26.5685,0.0000,0.005,0.000,0.000,-0.005,0.044,0.032,0.038,-35.33142715815,149.13181842030,609.1494,-4471799.0368,2672944.7758,-3668288.9857,1.3923,1.5128,3.2272,2.3026,2.1633,2.1586,0.00000000000,0.00000000000,0.0000,0.00000000000,0.00000000000,0.0000,176366400,0.000,66.175285,18.972784,0.000000,0.000000,5,0,0,0*9f704dad
     702             :  */
     703           0 : void AP_GPS_NMEA::parse_agrica_field(uint16_t term_number, const char *term)
     704             : {
     705           0 :     auto &ag = _agrica;
     706             :     // subtract 8 to align term numbers with reference manual
     707             :     // look for "Unicore Reference Command Manual" to find the specification
     708           0 :     const uint8_t hdr_align = 8;
     709           0 :     if (term_number < hdr_align) {
     710             :         // discard header;
     711           0 :         return;
     712             :     }
     713           0 :     term_number -= hdr_align;
     714             :     // useful for debugging
     715             :     //::printf("AGRICA[%u]=%s\n", unsigned(term_number), term);
     716           0 :     switch (term_number) {
     717           0 :     case 10:
     718           0 :         ag.rtk_status = atol(term);
     719           0 :         break;
     720           0 :     case 11:
     721           0 :         ag.heading_status = atol(term);
     722           0 :         break;
     723           0 :     case 25 ... 26:
     724           0 :         ag.vel_NED[term_number-25] = strtof(term, nullptr);
     725           0 :         break;
     726           0 :     case 27:
     727             :         // AGRIC gives velocity up
     728           0 :         ag.vel_NED.z = -strtof(term, nullptr);
     729           0 :         break;
     730           0 :     case 28 ... 30:
     731           0 :         ag.vel_stddev[term_number-28] = strtof(term, nullptr);
     732           0 :         break;
     733           0 :     case 31:
     734           0 :         ag.lat = atof(term);
     735           0 :         break;
     736           0 :     case 32:
     737           0 :         ag.lng = atof(term);
     738           0 :         break;
     739           0 :     case 33:
     740           0 :         ag.alt = strtof(term, nullptr);
     741           0 :         break;
     742           0 :     case 49:
     743           0 :         ag.itow = atol(term);
     744           0 :         break;
     745           0 :     case 37 ... 39:
     746           0 :         ag.pos_stddev[term_number-37] = strtof(term, nullptr);
     747           0 :         break;
     748           0 :     case 52:
     749           0 :         ag.undulation = strtof(term, nullptr);
     750           0 :         break;
     751             :     }
     752             : }
     753             : 
     754             : #if GPS_MOVING_BASELINE
     755             : /*
     756             :   parse a UNIHEADINGA message term
     757             : 
     758             :   Example:
     759             :     #UNIHEADINGA,79,GPS,FINE,2242,167498200,0,0,18,22;SOL_COMPUTED,L1_INT,2.7889,296.7233,-25.7710,0.0000,0.1127,0.1812,"999",49,37,37,0,3,00,1,51*d50af0ea
     760             :  */
     761           0 : void AP_GPS_NMEA::parse_uniheadinga_field(uint16_t term_number, const char *term)
     762             : {
     763           0 :     const uint8_t hdr_align = 8;
     764           0 :     if (term_number < hdr_align) {
     765             :         // discard header;
     766           0 :         return;
     767             :     }
     768           0 :     term_number -= hdr_align;
     769             :     // useful for debugging
     770             :     // ::printf("UNIHEADINGA[%u]=%s\n", unsigned(term_number), term);
     771           0 :     auto &uh = _uniheadinga;
     772           0 :     switch (term_number) {
     773           0 :     case 4:
     774           0 :         uh.baseline_length = strtof(term, nullptr);
     775           0 :         break;
     776           0 :     case 5:
     777           0 :         uh.heading = strtof(term, nullptr);
     778           0 :         break;
     779           0 :     case 6:
     780           0 :         uh.pitch = strtof(term, nullptr);
     781           0 :         break;
     782           0 :     case 8:
     783           0 :         uh.heading_sd = strtof(term, nullptr);
     784           0 :         break;
     785             :     }
     786             : }
     787             : #endif // GPS_MOVING_BASELINE
     788             : 
     789             : // parse VERSIONA fields
     790           0 : void AP_GPS_NMEA::parse_versiona_field(uint16_t term_number, const char *term)
     791             : {
     792             :     // printf useful for debugging
     793             :     // ::printf("VERSIONA[%u]='%s'\n", term_number, term);
     794           0 :     auto &v = _versiona;
     795             : #pragma GCC diagnostic push
     796             : #if defined(__GNUC__) &&  __GNUC__ >= 9
     797             : #pragma GCC diagnostic ignored "-Wstringop-truncation"
     798             : #endif
     799           0 :     switch (term_number) {
     800           0 :     case 10:
     801           0 :         strncpy(v.type, _term, sizeof(v.type)-1);
     802           0 :         break;
     803           0 :     case 11:
     804           0 :         strncpy(v.version, _term, sizeof(v.version)-1);
     805           0 :         break;
     806           0 :     case 15:
     807           0 :         strncpy(v.build_date, _term, sizeof(v.build_date)-1);
     808           0 :         break;
     809             :     }
     810             : #pragma GCC diagnostic pop
     811           0 : }
     812             : #endif // AP_GPS_NMEA_UNICORE_ENABLED
     813             : 
     814             : /*
     815             :   detect a NMEA GPS. Adds one byte, and returns true if the stream
     816             :   matches a NMEA string
     817             :  */
     818             : bool
     819         299 : AP_GPS_NMEA::_detect(struct NMEA_detect_state &state, uint8_t data)
     820             : {
     821         299 :         switch (state.step) {
     822           4 :         case 0:
     823           4 :                 state.ck = 0;
     824           4 :                 if ('$' == data) {
     825           4 :                         state.step++;
     826             :                 }
     827           4 :                 break;
     828         287 :         case 1:
     829         287 :                 if ('*' == data) {
     830           4 :                         state.step++;
     831             :                 } else {
     832         283 :                         state.ck ^= data;
     833             :                 }
     834         287 :                 break;
     835           4 :         case 2:
     836           4 :                 if (hexdigit(state.ck>>4) == data) {
     837           4 :                         state.step++;
     838             :                 } else {
     839           0 :                         state.step = 0;
     840             :                 }
     841           4 :                 break;
     842           4 :         case 3:
     843           4 :                 if (hexdigit(state.ck&0xF) == data) {
     844           4 :             state.step = 0;
     845           4 :                         return true;
     846             :                 }
     847           0 :                 state.step = 0;
     848           0 :                 break;
     849             :     }
     850         295 :     return false;
     851             : }
     852             : 
     853             : /*
     854             :   send type specific config strings
     855             :  */
     856        3721 : void AP_GPS_NMEA::send_config(void)
     857             : {
     858        3721 :     const auto type = get_type();
     859        3721 :     _expect_agrica = (type == AP_GPS::GPS_TYPE_UNICORE_NMEA ||
     860             :                       type == AP_GPS::GPS_TYPE_UNICORE_MOVINGBASE_NMEA);
     861        3721 :     if (gps._auto_config == AP_GPS::GPS_AUTO_CONFIG_DISABLE) {
     862             :         // not doing auto-config
     863        2868 :         return;
     864             :     }
     865         853 :     uint32_t now_ms = AP_HAL::millis();
     866         853 :     if (now_ms - last_config_ms < AP_GPS_NMEA_CONFIG_PERIOD_MS) {
     867         849 :         return;
     868             :     }
     869           4 :     last_config_ms = now_ms;
     870           4 :     const uint16_t rate_ms = params.rate_ms;
     871             : #if AP_GPS_NMEA_UNICORE_ENABLED
     872           4 :     const float rate_s = rate_ms * 0.001;
     873             : #endif
     874           4 :     const uint8_t rate_hz = 1000U / rate_ms;
     875             : 
     876           4 :     switch (get_type()) {
     877             : #if AP_GPS_NMEA_UNICORE_ENABLED
     878           0 :     case AP_GPS::GPS_TYPE_UNICORE_MOVINGBASE_NMEA:
     879           0 :         port->printf("\r\nCONFIG HEADING FIXLENGTH\r\n" \
     880             :                      "CONFIG UNDULATION AUTO\r\n" \
     881             :                      "CONFIG\r\n" \
     882             :                      "UNIHEADINGA %.3f\r\n",
     883             :                      rate_s);
     884           0 :         state.gps_yaw_configured = true;
     885             :         FALLTHROUGH;
     886             : 
     887           0 :     case AP_GPS::GPS_TYPE_UNICORE_NMEA: {
     888           0 :         port->printf("\r\nAGRICA %.3f\r\n" \
     889             :                      "MODE MOVINGBASE\r\n" \
     890             :                      "GNGGA %.3f\r\n" \
     891             :                      "GNRMC %.3f\r\n",
     892             :                      rate_s, rate_s, rate_s);
     893           0 :         if (!_have_unicore_versiona) {
     894             :             // get version information for logging if we don't have it yet
     895           0 :             port->printf("VERSIONA\r\n");
     896           0 :             if (gps._save_config) {
     897             :                 // save config changes for fast startup
     898           0 :                 port->printf("SAVECONFIG\r\n");
     899             :             }
     900             :         }
     901           0 :         break;
     902             :     }
     903             : #endif // AP_GPS_NMEA_UNICORE_ENABLED
     904             : 
     905           0 :     case AP_GPS::GPS_TYPE_HEMI: {
     906           0 :         port->printf(
     907             :         "$JATT,NMEAHE,0\r\n" /* Prefix of GP on the HDT message */      \
     908             :         "$JASC,GPGGA,%u\r\n" /* GGA at 5Hz */                            \
     909             :         "$JASC,GPRMC,%u\r\n" /* RMC at 5Hz */                            \
     910             :         "$JASC,GPVTG,%u\r\n" /* VTG at 5Hz */                            \
     911             :         "$JASC,GPHDT,%u\r\n" /* HDT at 5Hz */                            \
     912             :         "$JMODE,SBASR,YES\r\n" /* Enable SBAS */,
     913             :         rate_hz, rate_hz, rate_hz, rate_hz);
     914           0 :         break;
     915             :     }
     916             : 
     917           0 :     case AP_GPS::GPS_TYPE_ALLYSTAR:
     918           0 :         nmea_printf(port, "$PHD,06,42,UUUUTTTT,BB,0,%u,55,0,%u,0,0,0",
     919             :                     unsigned(rate_hz), unsigned(rate_ms));
     920           0 :         break;
     921             : 
     922           4 :     default:
     923           4 :         break;
     924             :     }
     925             : 
     926             : #ifdef AP_GPS_NMEA_CUSTOM_CONFIG_STRING
     927             :     // allow for custom config strings, useful for peripherals
     928             :     port->printf("%s\r\n", AP_GPS_NMEA_CUSTOM_CONFIG_STRING);
     929             : #endif
     930             : }
     931             : 
     932             : /*
     933             :   return health status
     934             :  */
     935         615 : bool AP_GPS_NMEA::is_healthy(void) const
     936             : {
     937         615 :     switch (get_type()) {
     938             : #if AP_GPS_NMEA_UNICORE_ENABLED
     939           0 :     case AP_GPS::GPS_TYPE_UNICORE_MOVINGBASE_NMEA:
     940             :     case AP_GPS::GPS_TYPE_UNICORE_NMEA:
     941             :         // we should be getting AGRICA messages
     942           0 :         return _last_AGRICA_ms != 0;
     943             : #endif // AP_GPS_NMEA_UNICORE_ENABLED
     944             : 
     945           0 :     case AP_GPS::GPS_TYPE_HEMI:
     946             :         // we should be getting HDR for yaw
     947           0 :         return _last_yaw_ms != 0;
     948             : 
     949           0 :     case AP_GPS::GPS_TYPE_ALLYSTAR:
     950             :         // we should get vertical velocity and accuracy from PHD
     951           0 :         return _last_vvelocity_ms != 0 && _last_vaccuracy_ms != 0;
     952             : 
     953         615 :     default:
     954         615 :         break;
     955             :     }
     956         615 :     return true;
     957             : }
     958             : 
     959             : // get the velocity lag
     960       50641 : bool AP_GPS_NMEA::get_lag(float &lag_sec) const
     961             : {
     962       50641 :     switch (get_type()) {
     963             : #if AP_GPS_NMEA_UNICORE_ENABLED
     964           0 :     case AP_GPS::GPS_TYPE_UNICORE_MOVINGBASE_NMEA:
     965             :     case AP_GPS::GPS_TYPE_UNICORE_NMEA:
     966           0 :         lag_sec = 0.14;
     967           0 :         break;
     968             : #endif // AP_GPS_NMEA_UNICORE_ENABLED
     969             : 
     970       50641 :     default:
     971       50641 :         lag_sec = 0.2;
     972       50641 :         break;
     973             :     }
     974       50641 :     return true;
     975             : }
     976             : 
     977             : #if HAL_LOGGING_ENABLED
     978           1 : void AP_GPS_NMEA::Write_AP_Logger_Log_Startup_messages() const
     979             : {
     980           1 :     AP_GPS_Backend::Write_AP_Logger_Log_Startup_messages();
     981             : #if AP_GPS_NMEA_UNICORE_ENABLED
     982           1 :     if (_have_unicore_versiona) {
     983           0 :         AP::logger().Write_MessageF("NMEA %u %s %s %s",
     984           0 :                                     state.instance+1,
     985           0 :                                     _versiona.type,
     986           0 :                                     _versiona.version,
     987           0 :                                     _versiona.build_date);
     988             :     }
     989             : #endif
     990           1 : }
     991             : #endif
     992             : 
     993             : #endif // AP_GPS_NMEA_ENABLED

Generated by: LCOV version 1.14