LCOV - code coverage report
Current view: top level - AP_ExternalAHRS - AP_ExternalAHRS_Aeron_plx.h (source / functions) Hit Total Coverage
Test: lcov.info Lines: 3 3 100.0 %
Date: 2026-09-09 11:02:08 Functions: 2 2 100.0 %

          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             :    ATH- Vinayak Pundhir (VP)
      16             :  */
      17             : 
      18             : /*
      19             :   Aeron Systems PLX series INS — External AHRS backend.
      20             : 
      21             :   Supports NAV_PARA1 (0xAAA1), NAV_PARA2 (0xA2A2), SENS_PARA (0xA2A3),
      22             :   GPS_PARA (0xA2A4), DEV_INFO (0xA2A6), EXTD_GNSS (0xA2A8)
      23             : 
      24             :   The driver runs a dedicated update thread that reads bytes from the
      25             :   UART, runs them through a state-machine parser, then dispatches each
      26             :   CRC-valid frame to a publisher that re-casts the payload as a packed
      27             :   on-wire struct and pushes the values into the AP_ExternalAHRS state
      28             :   plus the INS / Baro / Compass / GPS frontends.
      29             : 
      30             :   Recommended usage (Plane / Copter / Quadplane):
      31             : 
      32             :       param set AHRS_EKF_TYPE     11
      33             :       param set EAHRS_TYPE        10
      34             :       param set EAHRS_RATE        <SENS_PARA rate, e.g. 200>
      35             :       param set SERIAL*_PROTOCOL  36
      36             :       param set SERIAL*_BAUD      921
      37             :       param set GPS1_TYPE         21
      38             : 
      39             :   EAHRS_RATE must match the actual SENS_PARA rate, and SENS_PARA rate
      40             :   must be at least the vehicle's SCHED_LOOP_RATE (400 Hz copter,
      41             :   300 Hz VTOL, 50 Hz plane) when using IMU data.
      42             :  */
      43             : 
      44             : #pragma once
      45             : 
      46             : #include "AP_ExternalAHRS_config.h"
      47             : 
      48             : #if AP_EXTERNAL_AHRS_AERON_PLX_ENABLED
      49             : 
      50             : #include "AP_ExternalAHRS_backend.h"
      51             : 
      52             : class AP_ExternalAHRS_Aeron_plx : public AP_ExternalAHRS_backend
      53             : {
      54             : public:
      55             :     AP_ExternalAHRS_Aeron_plx(AP_ExternalAHRS *frontend, AP_ExternalAHRS::state_t &state);
      56             : 
      57             :     // AP_ExternalAHRS_backend interface
      58             :     int8_t      get_port() const override;
      59             :     const char *get_name() const override;
      60             :     bool        healthy() const override;
      61             :     bool        initialised() const override;
      62             :     bool        pre_arm_check(char *failure_msg, uint8_t failure_msg_len) const override;
      63             :     void        get_filter_status(nav_filter_status &status) const override;
      64             :     bool        get_variances(float &velVar, float &posVar, float &hgtVar,
      65             :                               Vector3f &magVar, float &tasVar) const override;
      66        8163 :     void        update() override {}    // all work happens in update_thread()
      67             : 
      68             : protected:
      69          50 :     uint8_t num_gps_sensors() const override
      70             :     {
      71          50 :         return 1;
      72             :     }
      73             : 
      74             : private:
      75             : 
      76             :     // packet identifiers (PRP_ID field, big-endian on the wire)
      77             :     enum class AeronPacketID : uint16_t {
      78             :         NAV_PARA1       = 0xAAA1,
      79             :         NAV_PARA2       = 0xA2A2,
      80             :         SENS_PARA       = 0xA2A3,
      81             :         GPS_PARA        = 0xA2A4,
      82             :         DEV_INFO        = 0xA2A6,
      83             :         EXTD_GNSS       = 0xA2A8,
      84             :         HEADING_S       = 0x0165,
      85             :         H_SPEED_S       = 0x0166,
      86             :         V_SPEED_S       = 0x0167,
      87             :         POSITION_S      = 0x0168,
      88             :         ALTITUDE_S      = 0x0169,
      89             :         GNSS_PKT        = 0x0212,
      90             :         GNSS_FIX_STATUS = 0x0067,
      91             :         EXT_SENSORS_MAG = 0x0117,
      92             :         EXT_SENSORS_ASP = 0x0362,
      93             :     };
      94             : 
      95             :     // GNSS fix types reported by PLX (raw value field inside gps_status)
      96             :     enum class AeronGnssFix : uint8_t {
      97             :         NO_FIX    = 0,
      98             :         GNSS_FIX  = 1,
      99             :         SBAS_FIX  = 2,
     100             :         RTK_FIX   = 3,
     101             :         RTK_FLOAT = 4,
     102             :     };
     103             : 
     104             :     // jamming/spoofing detection levels
     105             :     enum class AeronJamSpoof : uint8_t {
     106             :         NOT_DETECTED = 1,
     107             :         WARNING      = 2,
     108             :         CRITICAL     = 3,
     109             :     };
     110             : 
     111             :     // hardware status flag positions inside NAV_PARA2.hw_status
     112             :     enum class AeronHwStatus : uint8_t {
     113             :         GNSS     = 0,
     114             :         ACC      = 1,
     115             :         GYR      = 2,
     116             :         MAG      = 3,
     117             :         BARO     = 4,
     118             :         SUP_VLTG = 5,
     119             :         CM_PRT   = 10,
     120             :         RAM      = 11,
     121             :         FIRMWARE = 12,
     122             :         CONFIG   = 13,
     123             :         EXT_MAG  = 22,
     124             :         EXT_ASP  = 23,
     125             :     };
     126             : 
     127             :     // Deferred GCS messages - collected during one drain pass and emitted
     128             :     // once at the end, so GCS_SEND_TEXT() calls don't get sprinkled through
     129             :     // the byte-by-byte parser loop.
     130             :     struct AeronDeferredMsgs {
     131             :         bool     crc_fail;
     132             :         bool     bad_length;    // recognised ID but payload too short for it
     133             :         bool     unknown_pkt;   // unrecognised packet ID (unknown_id is valid)
     134             :         bool     ext_mag_checked;
     135             :         bool     ext_asp_checked;
     136             :         bool     stale_data;
     137             :         uint16_t unknown_id;    // only meaningful when unknown_pkt is set
     138             :     };
     139             : 
     140             :     // ---- packed on-wire payload layouts ----
     141             :     // These are cast directly over rx_buf[8 .. payload_end] once the CRC
     142             :     // has validated the frame, so the layout must exactly match the
     143             :     // PLX3 wire format. The matching SIM_Aeron structs carry the same
     144             :     // static_asserts.
     145             : 
     146             :     struct PACKED NavPara1Payload {
     147             :         uint32_t epoch_time;
     148             :         uint32_t microseconds;
     149             :         uint32_t ins_status;
     150             :         uint32_t hw_status;
     151             :         float    euler[3];
     152             :         float    course;
     153             :         float    velocity_ned[3];
     154             :         double   position[2];
     155             :         float    height_abv_ellip;
     156             :     };
     157             :     static_assert(sizeof(NavPara1Payload) == 64, "NavPara1Payload size mismatch");
     158             : 
     159             :     struct PACKED NavPara2Payload {
     160             :         uint32_t epoch_time;
     161             :         uint32_t microseconds;
     162             :         uint32_t ins_status;
     163             :         uint32_t hw_status;
     164             :         float    quat[4];
     165             :         float    body_vel[3];
     166             :         double   ecef_pos[3];
     167             :         float    altitude;
     168             :     };
     169             :     static_assert(sizeof(NavPara2Payload) == 72, "NavPara2Payload size mismatch");
     170             : 
     171             :     struct PACKED SensParaPayload {
     172             :         uint32_t epoch_time;
     173             :         uint32_t microseconds;
     174             :         float    temperature;
     175             :         float    gyro[3];
     176             :         float    accel[3];
     177             :         float    mag[3];
     178             :         float    euler_rates[3];
     179             :         float    baro_pressure;
     180             :         float    baro_temperature;
     181             :         float    baro_altitude;
     182             :     };
     183             :     static_assert(sizeof(SensParaPayload) == 72, "SensParaPayload size mismatch");
     184             : 
     185             :     struct PACKED GpsParaPayload {
     186             :         uint32_t epoch_time;
     187             :         uint32_t microseconds;
     188             :         uint32_t gps_status;
     189             :         double   gps_position[2];
     190             :         float    gps_height_abv_ellip;
     191             :         float    gps_undulation;
     192             :         float    pdop;
     193             :         float    hdop;
     194             :         float    gdop;
     195             :     };
     196             :     static_assert(sizeof(GpsParaPayload) == 48, "GpsParaPayload size mismatch");
     197             : 
     198             :     struct PACKED ExtdGnssPayload {
     199             :         float    hpa;
     200             :         float    vpa;
     201             :         float    hva;
     202             :         float    vdop;
     203             :         double   gnss_vned[3];
     204             :     };
     205             :     static_assert(sizeof(ExtdGnssPayload) == 40, "ExtdGnssPayload size mismatch");
     206             : 
     207             :     // ---- hw_status reporting table ----
     208             :     // One row per status flag we track. Used by report_hw_status() to
     209             :     // emit both the fault and the matching "okay" message, so the value
     210             :     // -> name mapping lives in exactly one place.
     211             :     struct HwStatusEntry {
     212             :         AeronHwStatus  status;
     213             :         uint8_t        severity;    // a MAV_SEVERITY_* value
     214             :         const char    *fault_msg;
     215             :         const char    *clear_msg;   // nullptr -> never emit a recovery message
     216             :     };
     217             :     static const HwStatusEntry hw_status_table[];
     218             : 
     219             :     // Protects every field of `shared` (the cross-thread cache written by
     220             :     // the parser thread's publish_* handlers and read by the const
     221             :     // accessors get_filter_status()/report_hw_status()/get_variances() on
     222             :     // other threads).
     223             :     //
     224             :     // Lock ordering: the frontend `state` struct has its own lock
     225             :     // (state.sem, owned by the base class). These two locks CAN nest - the
     226             :     // frontend holds state.sem while calling get_filter_status(), which
     227             :     // then takes arn_sem. To avoid an AB-BA deadlock the order is fixed:
     228             :     //   state.sem is the OUTER lock, arn_sem is the INNER lock.
     229             :     // i.e. code may take arn_sem while holding state.sem, but must never
     230             :     // take state.sem while holding arn_sem. The publish_* handlers respect
     231             :     // this by snapshotting `shared` under arn_sem and RELEASING it before
     232             :     // acquiring state.sem to write `state`.
     233             :     mutable HAL_Semaphore arn_sem;
     234             : 
     235             :     void update_thread();
     236             :     void check_and_decode();
     237             :     void report_hw_status();
     238             :     void parse_byte(uint8_t byte, AeronDeferredMsgs &msgs);
     239             :     void handle_chunk_buf_packet(uint16_t prp_id, const uint8_t *payload,
     240             :                                  uint16_t payload_len, AeronDeferredMsgs &msgs);
     241             : 
     242             :     void publish_nav_para1(const NavPara1Payload &p);
     243             :     void publish_nav_para2(const NavPara2Payload &p);
     244             :     void publish_sens_para(const SensParaPayload &p);
     245             :     void publish_gps_para(const GpsParaPayload &p, AeronDeferredMsgs &msgs);
     246             :     void publish_extd_gnss(const ExtdGnssPayload &p);
     247             : 
     248             :     // GPS formatting - combines GPS_PARA + EXTD_GNSS + NAV_PARA1 fields
     249             :     void format_and_push_gps(AeronDeferredMsgs &msgs);
     250             : 
     251             :     // Single source of truth for decoding the PLX GPS status word into an
     252             :     // ArduPilot fix type, so the AP_GPS publication path and
     253             :     // get_filter_status() cannot reach different conclusions from the same
     254             :     // status word.
     255             :     static AP_GPS_FixType fix_type_from_status(uint32_t status);
     256             : 
     257             :     // EXTD_GNSS freshness. Caller must hold arn_sem.
     258             :     bool extd_fresh_locked() const;
     259             : 
     260             :     // utility
     261             :     void send_deferred_messages(const AeronDeferredMsgs &msgs);
     262             :     // Sends only the requested queries. Returns false if a request could
     263             :     // not be written to the UART in full.
     264             :     bool query_ext_sensors(bool want_mag, bool want_asp);
     265             :     void check_for_query_ext_sensors();
     266             : 
     267             :     // UART interface (read-only after construction)
     268             :     AP_HAL::UARTDriver *uart;
     269             :     uint32_t            baudrate;
     270             :     int8_t              port_num;
     271             :     bool                setup_complete;
     272             : 
     273             :     // Shared sensor data
     274             :     struct SharedData {
     275             :         // NAV_PARA2
     276             :         uint32_t nav2_hw_status;
     277             :         // GPS_PARA
     278             :         uint32_t gps_epoch_s;
     279             :         uint32_t gps_status;
     280             :         uint32_t gps_microseconds;
     281             :         float    gps_undulation_m;
     282             :         float    gps_hdop;
     283             :         double   gps_position[2];
     284             :         float    gps_height_ellip_m;
     285             :         // EXTD_GNSS
     286             :         float    cust_hpa_m;
     287             :         float    cust_vpa_m;
     288             :         float    cust_hva_mps;
     289             :         float    cust_vdop;
     290             :         double   cust_gnss_vel_ned_mps[3];
     291             :         // external sensor presence / response-received flags
     292             :         uint8_t  ext_mag_present;
     293             :         uint8_t  ext_asp_present;
     294             :         bool     ext_mag_checked;
     295             :         bool     ext_asp_checked;
     296             :     };
     297             : 
     298             :     SharedData shared;
     299             : 
     300             :     // parser state
     301             :     enum class ParseState : uint8_t {
     302             :         RESET = 0,
     303             :         SYNC,
     304             :         LEN_HIGH,
     305             :         LEN_LOW,
     306             :         PAYLOAD,
     307             :         CRC_BYTE,
     308             :     };
     309             : 
     310             :     ParseState parse_state;
     311             :     uint8_t    sync_count;
     312             :     uint16_t   pkt_length;
     313             :     uint16_t   write_idx;
     314             :     // A frame is bounded by the LEN sanity check to pkt_length <=
     315             :     // PKT_LEN_MAX (255) plus one CRC byte, so the largest index written
     316             :     // is 255 - rx_buf[256] holds any valid frame exactly.
     317             :     uint8_t    rx_buf[256];
     318             : 
     319             :     // Per-packet-group staleness timestamps.
     320             :     volatile uint32_t last_sens_ms;
     321             :     volatile uint32_t last_nav1_ms;
     322             :     volatile uint32_t last_nav2_ms;
     323             :     volatile uint32_t last_gnss_ms;
     324             :     volatile uint32_t last_extd_ms;
     325             : 
     326             :     // GNSS state derived on demand from shared.gps_status (fix bit),
     327             :     // not stored - see get_filter_status().
     328             :     uint32_t last_jam_warn_ms;
     329             : 
     330             :     // init / gating flags
     331             :     uint32_t prev_nav_ms;     // last NAV timestamp seen by check_for_query_ext_sensors
     332             : 
     333             :     // Bounded retry for the external-sensor presence queries. The PLX answers
     334             :     // whether or not the sensor is fitted, so an unanswered query means the
     335             :     // request or its reply was lost rather than that the sensor is absent.
     336             :     // Retry the outstanding ones a few times, then stop: firmware that does
     337             :     // not implement the query would otherwise retry forever. Giving up leaves
     338             :     // the *_checked flags false, which keeps EXT_MAG/EXT_ASP unreportable in
     339             :     // report_hw_status() - the safe default.
     340             :     static constexpr uint32_t EXT_QUERY_RETRY_MS  = 2000;
     341             :     static constexpr uint8_t  EXT_QUERY_MAX_TRIES = 5;
     342             :     uint32_t last_ext_query_ms;
     343             :     uint8_t  ext_query_tries;
     344             : 
     345             :     // throttled-warning state for hw_status flags. Rising edges fire
     346             :     // immediately; persistent faults re-warn every HEALTH_REPEAT_INTERVAL_MS.
     347             :     // last_warned_ms is indexed by table ROW (0..HW_STATUS_COUNT-1), so its
     348             :     // size tracks the number of table entries and does not depend on the
     349             :     // enum bit values. A static_assert in the .cpp keeps this in sync with
     350             :     // ARRAY_SIZE(hw_status_table).
     351             :     static constexpr uint8_t HW_STATUS_COUNT = 12;
     352             :     uint32_t last_warned_stat;
     353             :     uint32_t last_warned_ms[HW_STATUS_COUNT];
     354             : 
     355             :     // hw_status bits that mean the device cannot be trusted to navigate.
     356             :     // Manual 7.1.2.9 Table 15 rates all of these High severity and PBIT:
     357             :     // they are power-on self-test failures with no recovery path, which is
     358             :     // why hw_status_table gives the first three no clear_msg. Config-memory
     359             :     // failure in particular leaves the device streaming fresh packets from a
     360             :     // wrong configuration, which no staleness check can catch.
     361             :     //
     362             :     // ACC and GYR are included because the PLX's own fused solution is built
     363             :     // on them: a unit reporting a non-functional accelerometer or gyro is no
     364             :     // more trustworthy fused than raw. Being PBIT, they are set at power-on
     365             :     // rather than mid-flight, so this fails pre-arm rather than dropping the
     366             :     // AHRS in the air. MAG and BARO are deliberately absent - the PLX
     367             :     // navigates without them, so those are gated at the publication site
     368             :     // instead (see publish_sens_para).
     369             :     static constexpr uint32_t HW_STATUS_FATAL_MASK =
     370             :           (1U << uint8_t(AeronHwStatus::RAM))
     371             :         | (1U << uint8_t(AeronHwStatus::FIRMWARE))
     372             :         | (1U << uint8_t(AeronHwStatus::CONFIG))
     373             :         | (1U << uint8_t(AeronHwStatus::ACC))
     374             :         | (1U << uint8_t(AeronHwStatus::GYR));
     375             : 
     376             :     // Throttle state for the noisy parser-side GCS messages.
     377             :     uint32_t last_crc_warn_ms;
     378             :     uint32_t last_garbage_warn_ms;
     379             :     uint32_t last_stale_data_ms;
     380             : 
     381             :     // Per-message log decimation timestamps (high-rate messages only).
     382             :     uint32_t last_aern_log_ms;
     383             :     uint32_t last_aers_log_ms;
     384             : 
     385             :     // Per-group staleness deadlines for healthy().
     386             :     static constexpr uint32_t SENS_TIMEOUT_MS = 40;
     387             :     static constexpr uint32_t NAV_TIMEOUT_MS  = 200;
     388             :     static constexpr uint32_t GNSS_TIMEOUT_MS = 500;
     389             : 
     390             :     // EXTD_GNSS validity window. Past this, the GPS message is not
     391             :     // published at all (see format_and_push_gps) so no stale accuracy or
     392             :     // velocity reaches AP_GPS.
     393             :     static constexpr uint32_t EXTD_VALIDITY_MS = 500;
     394             : 
     395             :     // NAV gap beyond which the PLX is assumed to have reset (re-query
     396             :     // external sensors on resumption).
     397             :     static constexpr uint32_t DEVICE_RESET_GAP_MS = 500;
     398             : 
     399             :     // At how much interval in ms should we shout health messages?
     400             :     static constexpr uint32_t HEALTH_REPEAT_INTERVAL_MS = 10000;
     401             : 
     402             :     // Minimum spacing between repeated parser-side telemetry warnings.
     403             :     static constexpr uint32_t WARN_THROTTLE_MS = 5000;
     404             : 
     405             :     // State Machine gears
     406             :     static constexpr uint8_t  SYNC_BYTE                 = 0x05;
     407             :     static constexpr uint8_t  SYNC_REQUIRED             = 4;
     408             :     static constexpr uint16_t PKT_LEN_MIN               = 8;
     409             :     static constexpr uint16_t PKT_LEN_MAX               = 255;
     410             : 
     411             :     // GPS_PARA status word: bit 10 is the "GNSS fix present" flag,
     412             :     // bits 0-9 the visible-satellite count and bits 11-14 the fix type
     413             :     // code (manual 7.1.2.10, Table 16).
     414             :     static constexpr uint32_t GPS_STATUS_FIX_BIT       = 0x00000400U;
     415             :     static constexpr uint32_t GPS_STATUS_NUMSAT_MASK   = 0x000003FFU;
     416             :     static constexpr uint32_t GPS_STATUS_FIXTYPE_MASK  = 0x00007800U;
     417             :     static constexpr uint8_t  GPS_STATUS_FIXTYPE_SHIFT = 11;
     418             : };
     419             : 
     420             : #endif  // AP_EXTERNAL_AHRS_AERON_PLX_ENABLED

Generated by: LCOV version 1.14