LCOV - code coverage report
Current view: top level - AP_ADSB - AP_ADSB_uAvionix_UCP.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 295 0.0 %
Date: 2026-09-09 11:02:08 Functions: 0 11 0.0 %

          Line data    Source code
       1             : /*
       2             :    Copyright (C) 2021  Kraus Hamdani Aerospace Inc. All rights reserved.
       3             : 
       4             :    This program is free software: you can redistribute it and/or modify
       5             :    it under the terms of the GNU General Public License as published by
       6             :    the Free Software Foundation, either version 3 of the License, or
       7             :    (at your option) any later version.
       8             : 
       9             :    This program is distributed in the hope that it will be useful,
      10             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :    GNU General Public License for more details.
      13             : 
      14             :    You should have received a copy of the GNU General Public License
      15             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.
      16             : 
      17             :    Author: Tom Pittenger
      18             :  */
      19             : 
      20             : #include "AP_ADSB_uAvionix_UCP.h"
      21             : 
      22             : // This driver implements the UCP protocol from uAvionix which is a variant of the GDL90 protocol by Garmin
      23             : // https://uavionix.com/downloads/ping200X/uAvionix-UCP-Transponder-ICD-Rev-Q.pdf
      24             : 
      25             : #if HAL_ADSB_UCP_ENABLED
      26             : 
      27             : #include <AP_SerialManager/AP_SerialManager.h>
      28             : #include <GCS_MAVLink/GCS.h>
      29             : #include <AP_HAL/AP_HAL.h>
      30             : #include <AP_Math/AP_Math.h>
      31             : #include <AP_Math/crc.h>
      32             : #include <ctype.h>
      33             : #include <AP_Notify/AP_Notify.h>
      34             : 
      35             : #include <AP_GPS/AP_GPS.h>
      36             : 
      37             : extern const AP_HAL::HAL &hal;
      38             : 
      39             : #define AP_ADSB_UAVIONIX_HEALTH_TIMEOUT_MS                     (5000UL)
      40             : 
      41             : #define AP_ADSB_UAVIONIX_GCS_LOST_COMMS_LONG_TIMEOUT_MINUTES    (15UL)
      42             : #define AP_ADSB_UAVIONIX_GCS_LOST_COMMS_LONG_TIMEOUT_MS         (1000UL * 60UL * AP_ADSB_UAVIONIX_GCS_LOST_COMMS_LONG_TIMEOUT_MINUTES)
      43             : 
      44             : #define AP_ADSB_UAVIONIX_DETECT_GROUNDSTATE                     0
      45             : #define AP_ADSB_UAVIONIX_EMERGENCY_STATUS_ON_LOST_LINK          0
      46             : 
      47             : // detect if any port is configured as uAvionix_UCP
      48           0 : bool AP_ADSB_uAvionix_UCP::detect()
      49             : {
      50           0 :     return AP::serialmanager().have_serial(AP_SerialManager::SerialProtocol_ADSB, 0);
      51             : }
      52             : 
      53             : 
      54             : // Init, called once after class is constructed
      55           0 : bool AP_ADSB_uAvionix_UCP::init()
      56             : {
      57           0 :     _port = AP::serialmanager().find_serial(AP_SerialManager::SerialProtocol_ADSB, 0);
      58           0 :     if (_port == nullptr) {
      59           0 :         return false;
      60             :     }
      61             : 
      62           0 :     _frontend.out_state.ctrl.squawkCode = 1200;
      63           0 :     _frontend.out_state.tx_status.squawk = 1200;
      64           0 :     _frontend.out_state.tx_status.fault |= UAVIONIX_ADSB_OUT_STATUS_FAULT_STATUS_MESSAGE_UNAVAIL;
      65             : 
      66           0 :     return true;
      67             : }
      68             : 
      69             : 
      70           0 : void AP_ADSB_uAvionix_UCP::update()
      71             : {
      72           0 :     if (_port == nullptr) {
      73           0 :         return;
      74             :     }
      75             : 
      76           0 :     const uint32_t now_ms = AP_HAL::millis();
      77             : 
      78             :     // -----------------------------
      79             :     // read any available data on serial port
      80             :     // -----------------------------
      81           0 :     uint32_t nbytes = MIN(_port->available(), 10UL * GDL90_RX_MAX_PACKET_LENGTH);
      82           0 :     while (nbytes-- > 0) {
      83             :         uint8_t data;
      84           0 :         if (!_port->read(data)) {
      85           0 :             break;
      86             :         }
      87           0 :         if (parseByte(data, rx.msg, rx.status)) {
      88           0 :             rx.last_msg_ms = now_ms;
      89           0 :             handle_msg(rx.msg);
      90             :         }
      91             :     } // while nbytes
      92             : 
      93             : 
      94           0 :     if (run_state.last_packet_Transponder_Id_ms == 0 && run_state.request_Transponder_Id_tries < 5) {
      95           0 :         if (now_ms - run_state.last_packet_Request_Transponder_Id_ms >= 1000)
      96             :         {
      97           0 :             request_msg(GDL90_ID_IDENTIFICATION);
      98           0 :             run_state.request_Transponder_Id_tries++;
      99             :         }
     100             :     }
     101             : 
     102           0 :     if (run_state.last_packet_Transponder_Config_ms == 0 && run_state.request_Transponder_Config_tries < 5) {
     103           0 :         if (now_ms - run_state.last_packet_Request_Transponder_Config_ms >= 1000)
     104             :         {
     105           0 :             request_msg(GDL90_ID_TRANSPONDER_CONFIG);
     106           0 :             run_state.request_Transponder_Config_tries++;
     107             :         }
     108             :     }
     109             : 
     110           0 :    if (now_ms - run_state.last_packet_Transponder_Control_ms >= 1000) {
     111           0 :         run_state.last_packet_Transponder_Control_ms = now_ms;
     112             : 
     113             :         // We want to use the defaults stored on the ping200X, if possible.
     114             :         // Until we get the config message (or we've tried requesting it several times),
     115             :         // don't send the control message.
     116           0 :         if (run_state.last_packet_Transponder_Config_ms != 0 || run_state.request_Transponder_Config_tries >= 5) {
     117           0 :             send_Transponder_Control();
     118             :         }
     119             :     }
     120             : 
     121           0 :     if ((now_ms - run_state.last_packet_GPS_ms >= 200) && (_frontend._options & uint32_t(AP_ADSB::AdsbOption::Ping200X_Send_GPS)) != 0) {
     122           0 :         run_state.last_packet_GPS_ms = now_ms;
     123           0 :         send_GPS_Data();
     124             :     }
     125             : 
     126             :     // if the transponder has stopped giving us the data needed to
     127             :     // fill the transponder status mavlink message, indicate status unavailable.
     128           0 :     if ((now_ms - run_state.last_packet_Transponder_Status_ms >= 10000)
     129           0 :         && (now_ms - run_state.last_packet_Transponder_Heartbeat_ms >= 10000)
     130           0 :         && (now_ms - run_state.last_packet_Transponder_Ownship_ms >= 10000)) {
     131           0 :         _frontend.out_state.tx_status.fault |= UAVIONIX_ADSB_OUT_STATUS_FAULT_STATUS_MESSAGE_UNAVAIL;
     132             :     }
     133             : }
     134             : 
     135             : 
     136           0 : void AP_ADSB_uAvionix_UCP::handle_msg(const GDL90_RX_MESSAGE &msg)
     137             : {
     138           0 :     switch(msg.messageId) {
     139           0 :     case GDL90_ID_HEARTBEAT: {
     140             :         // The Heartbeat message provides real-time indications of the status and operation of the
     141             :         // transponder. The message will be transmitted with a period of one second for the UCP
     142             :         // protocol.
     143           0 :         memcpy(&rx.decoded.heartbeat, msg.raw, sizeof(rx.decoded.heartbeat));
     144           0 :         run_state.last_packet_Transponder_Heartbeat_ms = AP_HAL::millis();
     145           0 :         _frontend.out_state.tx_status.fault &= ~UAVIONIX_ADSB_OUT_STATUS_FAULT_STATUS_MESSAGE_UNAVAIL;
     146             : 
     147           0 :         if (rx.decoded.heartbeat.status.one.maintenanceRequired) {
     148           0 :             _frontend.out_state.tx_status.fault |= UAVIONIX_ADSB_OUT_STATUS_FAULT_MAINT_REQ;
     149             :         } else {
     150           0 :             _frontend.out_state.tx_status.fault &= ~UAVIONIX_ADSB_OUT_STATUS_FAULT_MAINT_REQ;
     151             :         }
     152             : 
     153           0 :         if (rx.decoded.heartbeat.status.two.functionFailureGnssUnavailable) {
     154           0 :             _frontend.out_state.tx_status.fault |= UAVIONIX_ADSB_OUT_STATUS_FAULT_GPS_UNAVAIL;
     155             :         } else {
     156           0 :             _frontend.out_state.tx_status.fault &= ~UAVIONIX_ADSB_OUT_STATUS_FAULT_GPS_UNAVAIL;
     157             :         }
     158             : 
     159           0 :         if (rx.decoded.heartbeat.status.two.functionFailureGnssNo3dFix) {
     160           0 :             _frontend.out_state.tx_status.fault |= UAVIONIX_ADSB_OUT_STATUS_FAULT_GPS_NO_POS;
     161             :         } else {
     162           0 :             _frontend.out_state.tx_status.fault &= ~UAVIONIX_ADSB_OUT_STATUS_FAULT_GPS_NO_POS;
     163             :         }
     164             : 
     165           0 :         if (rx.decoded.heartbeat.status.two.functionFailureTransmitSystem) {
     166           0 :             _frontend.out_state.tx_status.fault |= UAVIONIX_ADSB_OUT_STATUS_FAULT_TX_SYSTEM_FAIL;
     167             :         } else {
     168           0 :             _frontend.out_state.tx_status.fault &= ~UAVIONIX_ADSB_OUT_STATUS_FAULT_TX_SYSTEM_FAIL;
     169             :         }
     170             :         }
     171           0 :         break;
     172             : 
     173           0 :     case GDL90_ID_IDENTIFICATION:
     174           0 :         run_state.last_packet_Transponder_Id_ms = AP_HAL::millis();
     175             :         // The Identification message contains information used to identify the connected device. The
     176             :         // Identification message will be transmitted with a period of one second regardless of data status
     177             :         // or update for the UCP protocol and will be transmitted upon request for the UCP-HD protocol.
     178           0 :         if (memcmp(&rx.decoded.identification, msg.raw, sizeof(rx.decoded.identification)) != 0) {
     179           0 :             memcpy(&rx.decoded.identification, msg.raw, sizeof(rx.decoded.identification));
     180             : 
     181             :             // Firmware Part Number (not null terminated, but null padded if part number is less than 15 characters).
     182             :             // Copy into a temporary string that is 1 char longer so we ensure it's null terminated
     183           0 :             const uint8_t str_len = sizeof(rx.decoded.identification.primaryFwPartNumber);
     184             :             char primaryFwPartNumber[str_len+1];
     185           0 :             memcpy(&primaryFwPartNumber, rx.decoded.identification.primaryFwPartNumber, str_len);
     186           0 :             primaryFwPartNumber[str_len] = 0;
     187             : 
     188           0 :             GCS_SEND_TEXT(MAV_SEVERITY_DEBUG,"ADSB:Detected %s v%u.%u.%u SN:%u %s",
     189             :                 get_hardware_name(rx.decoded.identification.primary.hwId),
     190             :                 (unsigned)rx.decoded.identification.primary.fwMajorVersion,
     191             :                 (unsigned)rx.decoded.identification.primary.fwMinorVersion,
     192             :                 (unsigned)rx.decoded.identification.primary.fwBuildVersion,
     193             :                 (unsigned)rx.decoded.identification.primary.serialNumber,
     194             :                 primaryFwPartNumber);
     195             :         }
     196           0 :         break;
     197             : 
     198           0 :     case GDL90_ID_TRANSPONDER_CONFIG:
     199           0 :         run_state.last_packet_Transponder_Config_ms = AP_HAL::millis();
     200           0 :         memcpy(&rx.decoded.transponder_config, msg.raw, sizeof(rx.decoded.transponder_config));
     201           0 :         break;
     202             : 
     203             : #if AP_ADSB_UAVIONIX_UCP_CAPTURE_ALL_RX_PACKETS
     204           0 :     case GDL90_ID_OWNSHIP_REPORT:
     205           0 :         _frontend.out_state.tx_status.fault &= ~UAVIONIX_ADSB_OUT_STATUS_FAULT_STATUS_MESSAGE_UNAVAIL;
     206             :         // The Ownship message contains information on the GNSS position. If the Ownship GNSS
     207             :         // position fix is invalid, the Latitude, Longitude, and NIC fields will all have the ZERO value. The
     208             :         // Ownship message will be transmitted with a period of one second regardless of data status or
     209             :         // update for the UCP protocol. All fields in the ownship message are transmitted MSB first
     210           0 :         memcpy(&rx.decoded.ownship_report, msg.raw, sizeof(rx.decoded.ownship_report));
     211           0 :         run_state.last_packet_Transponder_Ownship_ms = AP_HAL::millis();
     212           0 :         _frontend.out_state.tx_status.NIC_NACp = rx.decoded.ownship_report.report.NIC | (rx.decoded.ownship_report.report.NACp << 4);
     213           0 :         memcpy(_frontend.out_state.tx_status.flight_id, rx.decoded.ownship_report.report.callsign, sizeof(_frontend.out_state.tx_status.flight_id));
     214           0 :         break;
     215             : 
     216           0 :     case GDL90_ID_OWNSHIP_GEOMETRIC_ALTITUDE:
     217             :         // An Ownship Geometric Altitude message will be transmitted with a period of one second when
     218             :         // the GNSS fix is valid for the UCP protocol. All fields in the Geometric Ownship Altitude
     219             :         // message are transmitted MSB first.
     220           0 :         memcpy(&rx.decoded.ownship_geometric_altitude, msg.raw, sizeof(rx.decoded.ownship_geometric_altitude));
     221           0 :         break;
     222             : 
     223           0 :     case GDL90_ID_SENSOR_MESSAGE:
     224           0 :         memcpy(&rx.decoded.sensor_message, msg.raw, sizeof(rx.decoded.sensor_message));
     225           0 :         break;
     226             : 
     227           0 :     case GDL90_ID_TRANSPONDER_STATUS:
     228             :     {
     229           0 :         _frontend.out_state.tx_status.fault &= ~UAVIONIX_ADSB_OUT_STATUS_FAULT_STATUS_MESSAGE_UNAVAIL;
     230           0 :         switch (msg.payload[0]) {
     231           0 :         case 1: {
     232             :             // version 1 of the transponder status message is sent at 1 Hz (if UCP protocol out is enabled on the transponder)
     233           0 :             memcpy(&rx.decoded.transponder_status, msg.raw, sizeof(rx.decoded.transponder_status));
     234           0 :             if (rx.decoded.transponder_status.identActive) {
     235           0 :                 _frontend.out_state.tx_status.state |= UAVIONIX_ADSB_OUT_STATUS_STATE_IDENT_ACTIVE;
     236             :             } else {
     237           0 :                 _frontend.out_state.tx_status.state &= ~UAVIONIX_ADSB_OUT_STATUS_STATE_IDENT_ACTIVE;
     238             :             }
     239             : 
     240           0 :             if (rx.decoded.transponder_status.modeAEnabled) {
     241           0 :                 _frontend.out_state.tx_status.state |= UAVIONIX_ADSB_OUT_STATUS_STATE_MODE_A_ENABLED;
     242             :             } else {
     243           0 :                 _frontend.out_state.tx_status.state &= ~UAVIONIX_ADSB_OUT_STATUS_STATE_MODE_A_ENABLED;
     244             :             }
     245             : 
     246           0 :             if (rx.decoded.transponder_status.modeCEnabled) {
     247           0 :                 _frontend.out_state.tx_status.state |= UAVIONIX_ADSB_OUT_STATUS_STATE_MODE_C_ENABLED;
     248             :             } else {
     249           0 :                 _frontend.out_state.tx_status.state &= ~UAVIONIX_ADSB_OUT_STATUS_STATE_MODE_C_ENABLED;
     250             :             }
     251             : 
     252           0 :             if (rx.decoded.transponder_status.modeSEnabled) {
     253           0 :                 _frontend.out_state.tx_status.state |= UAVIONIX_ADSB_OUT_STATUS_STATE_MODE_S_ENABLED;
     254             :             } else {
     255           0 :                 _frontend.out_state.tx_status.state &= ~UAVIONIX_ADSB_OUT_STATUS_STATE_MODE_S_ENABLED;
     256             :             }
     257             : 
     258           0 :             if (rx.decoded.transponder_status.es1090TxEnabled) {
     259           0 :                 _frontend.out_state.tx_status.state |= UAVIONIX_ADSB_OUT_STATUS_STATE_1090ES_TX_ENABLED;
     260             :             } else {
     261           0 :                 _frontend.out_state.tx_status.state &= ~UAVIONIX_ADSB_OUT_STATUS_STATE_1090ES_TX_ENABLED;
     262             :             }
     263             : 
     264           0 :             if (rx.decoded.transponder_status.x_bit) {
     265           0 :                 _frontend.out_state.tx_status.state |= UAVIONIX_ADSB_OUT_STATUS_STATE_XBIT_ENABLED;
     266             :             } else {
     267           0 :                 _frontend.out_state.tx_status.state &= ~UAVIONIX_ADSB_OUT_STATUS_STATE_XBIT_ENABLED;
     268             :             }
     269             : 
     270           0 :             _frontend.out_state.tx_status.squawk = rx.decoded.transponder_status.squawkCode;
     271             : 
     272           0 :             if (run_state.last_packet_Transponder_Status_ms == 0 && run_state.last_packet_Transponder_Config_ms == 0) {
     273             :                 // If this is the first time we've seen a status message,
     274             :                 // and we haven't initialized the control message from the config message,
     275             :                 // set initial control message contents to match transponder's current behavior.
     276           0 :                 _frontend.out_state.ctrl.modeAEnabled = rx.decoded.transponder_status.modeAEnabled;
     277           0 :                 _frontend.out_state.ctrl.modeCEnabled = rx.decoded.transponder_status.modeCEnabled;
     278           0 :                 _frontend.out_state.ctrl.modeSEnabled = rx.decoded.transponder_status.modeSEnabled;
     279           0 :                 _frontend.out_state.ctrl.es1090TxEnabled = rx.decoded.transponder_status.es1090TxEnabled;
     280           0 :                 _frontend.out_state.ctrl.squawkCode = rx.decoded.transponder_status.squawkCode;
     281           0 :                 _frontend.out_state.ctrl.x_bit = rx.decoded.transponder_status.x_bit;
     282             :             }
     283           0 :             run_state.last_packet_Transponder_Status_ms = AP_HAL::millis();
     284             : #if AP_MAVLINK_MSG_UAVIONIX_ADSB_OUT_STATUS_ENABLED
     285           0 :             GCS_SEND_MESSAGE(MSG_UAVIONIX_ADSB_OUT_STATUS);
     286           0 :             run_state.last_gcs_send_message_Transponder_Status_ms = AP_HAL::millis();
     287             : #endif
     288           0 :             break;
     289             :         }
     290           0 :         case 2:
     291             :             // deprecated
     292           0 :             break;
     293           0 :         case 3: {
     294             :             // Version 3 of the transponder status message is sent in response to the transponder control message (if UCP-HD protocol out is enabled on the transponder)
     295           0 :             memcpy(&rx.decoded.transponder_status_v3, msg.raw, sizeof(rx.decoded.transponder_status_v3));
     296             : 
     297           0 :             if (rx.decoded.transponder_status_v3.indicatingOnGround) {
     298           0 :                 _frontend.out_state.tx_status.state |= UAVIONIX_ADSB_OUT_STATUS_STATE_ON_GROUND;
     299             :             } else {
     300           0 :                 _frontend.out_state.tx_status.state &= ~UAVIONIX_ADSB_OUT_STATUS_STATE_ON_GROUND;
     301             :             }
     302             : 
     303           0 :             if (rx.decoded.transponder_status_v3.fault) {
     304             :                 // unsure what fault is indicated, query heartbeat for more info
     305           0 :                 request_msg(GDL90_ID_HEARTBEAT);
     306             :             }
     307             : 
     308           0 :             if (rx.decoded.transponder_status_v3.identActive) {
     309           0 :                 _frontend.out_state.tx_status.state |= UAVIONIX_ADSB_OUT_STATUS_STATE_IDENT_ACTIVE;
     310             :             } else {
     311           0 :                 _frontend.out_state.tx_status.state &= ~UAVIONIX_ADSB_OUT_STATUS_STATE_IDENT_ACTIVE;
     312             :             }
     313             : 
     314           0 :             if (rx.decoded.transponder_status_v3.modeAEnabled) {
     315           0 :                 _frontend.out_state.tx_status.state |= UAVIONIX_ADSB_OUT_STATUS_STATE_MODE_A_ENABLED;
     316             :             } else {
     317           0 :                 _frontend.out_state.tx_status.state &= ~UAVIONIX_ADSB_OUT_STATUS_STATE_MODE_A_ENABLED;
     318             :             }
     319             : 
     320           0 :             if (rx.decoded.transponder_status_v3.modeCEnabled) {
     321           0 :                 _frontend.out_state.tx_status.state |= UAVIONIX_ADSB_OUT_STATUS_STATE_MODE_C_ENABLED;
     322             :             } else {
     323           0 :                 _frontend.out_state.tx_status.state &= ~UAVIONIX_ADSB_OUT_STATUS_STATE_MODE_C_ENABLED;
     324             :             }
     325             : 
     326           0 :             if (rx.decoded.transponder_status_v3.modeSEnabled) {
     327           0 :                 _frontend.out_state.tx_status.state |= UAVIONIX_ADSB_OUT_STATUS_STATE_MODE_S_ENABLED;
     328             :             } else {
     329           0 :                 _frontend.out_state.tx_status.state &= ~UAVIONIX_ADSB_OUT_STATUS_STATE_MODE_S_ENABLED;
     330             :             }
     331             : 
     332           0 :             if (rx.decoded.transponder_status_v3.es1090TxEnabled) {
     333           0 :                 _frontend.out_state.tx_status.state |= UAVIONIX_ADSB_OUT_STATUS_STATE_1090ES_TX_ENABLED;
     334             :             } else {
     335           0 :                 _frontend.out_state.tx_status.state &= ~UAVIONIX_ADSB_OUT_STATUS_STATE_1090ES_TX_ENABLED;
     336             :             }
     337             : 
     338           0 :             _frontend.out_state.tx_status.squawk = rx.decoded.transponder_status_v3.squawkCode;
     339           0 :             _frontend.out_state.tx_status.NIC_NACp = rx.decoded.transponder_status_v3.NIC | (rx.decoded.transponder_status_v3.NACp << 4);
     340           0 :             _frontend.out_state.tx_status.boardTemp = rx.decoded.transponder_status_v3.temperature;
     341             : 
     342           0 :             if (run_state.last_packet_Transponder_Status_ms == 0 && run_state.last_packet_Transponder_Config_ms == 0) {
     343             :                 // If this is the first time we've seen a status message,
     344             :                 // and we haven't initialized the control message from the config message,
     345             :                 // set initial control message contents to match transponder's current behavior.
     346           0 :                 _frontend.out_state.ctrl.modeAEnabled = rx.decoded.transponder_status_v3.modeAEnabled;
     347           0 :                 _frontend.out_state.ctrl.modeCEnabled = rx.decoded.transponder_status_v3.modeCEnabled;
     348           0 :                 _frontend.out_state.ctrl.modeSEnabled = rx.decoded.transponder_status_v3.modeSEnabled;
     349           0 :                 _frontend.out_state.ctrl.es1090TxEnabled = rx.decoded.transponder_status_v3.es1090TxEnabled;
     350           0 :                 _frontend.out_state.ctrl.squawkCode = rx.decoded.transponder_status_v3.squawkCode;
     351             :             }
     352           0 :             run_state.last_packet_Transponder_Status_ms = AP_HAL::millis();
     353             : #if AP_MAVLINK_MSG_UAVIONIX_ADSB_OUT_STATUS_ENABLED
     354           0 :             GCS_SEND_MESSAGE(MSG_UAVIONIX_ADSB_OUT_STATUS);
     355           0 :             run_state.last_gcs_send_message_Transponder_Status_ms = AP_HAL::millis();
     356             : #endif
     357           0 :             break;
     358             :         }
     359           0 :         default:
     360           0 :             break;
     361             :         }
     362           0 :         break;
     363             :     }
     364             : #endif // AP_ADSB_UAVIONIX_UCP_CAPTURE_ALL_RX_PACKETS
     365             : 
     366           0 :     case GDL90_ID_TRANSPONDER_CONTROL:
     367             :     case GDL90_ID_GPS_DATA:
     368             :     case GDL90_ID_MESSAGE_REQUEST:
     369             :         // not handled, outbound only
     370           0 :         break;
     371           0 :     default:
     372             :         //GCS_SEND_TEXT(MAV_SEVERITY_DEBUG,"ADSB:Unknown msg %d", (int)msg.messageId);
     373           0 :         break;
     374             :     }
     375           0 : }
     376             : 
     377             : 
     378           0 : const char* AP_ADSB_uAvionix_UCP::get_hardware_name(const uint8_t hwId)
     379             : {
     380           0 :     switch(hwId) {
     381           0 :         case 0x09: return "Ping200s";
     382           0 :         case 0x0A: return "Ping20s";
     383           0 :         case 0x18: return "Ping200C";
     384           0 :         case 0x27: return "Ping20Z";
     385           0 :         case 0x2D: return "SkyBeaconX";             // (certified)
     386           0 :         case 0x26: //return "Ping200Z/Ping200X";    // (uncertified). Let's fallthrough and use Ping200X
     387           0 :         case 0x2F: return "Ping200X";               // (certified)
     388           0 :         case 0x30: return "TailBeaconX";            // (certified)
     389             :     } // switch hwId
     390           0 :     return "Unknown HW";
     391             : }
     392             : 
     393           0 : void AP_ADSB_uAvionix_UCP::send_Transponder_Control()
     394             : {
     395           0 :     GDL90_TRANSPONDER_CONTROL_MSG msg {};
     396           0 :     msg.messageId = GDL90_ID_TRANSPONDER_CONTROL;
     397           0 :     msg.version = GDL90_TRANSPONDER_CONTROL_VERSION;
     398             : 
     399             : #if CONFIG_HAL_BOARD == HAL_BOARD_SITL
     400             :     // when using the simulator, always declare we're on the ground to help
     401             :     // inhibit chaos if this ias actually being broadcasted on real hardware
     402           0 :     msg.airGroundState =  ADSB_ON_GROUND;
     403             : #elif AP_ADSB_UAVIONIX_DETECT_GROUNDSTATE
     404             :     msg.airGroundState =  _frontend.out_state.is_flying ? ADSB_AIRBORNE_SUBSONIC : ADSB_ON_GROUND;
     405             : #else
     406           0 :     msg.airGroundState = ADSB_AIRBORNE_SUBSONIC;
     407             : #endif
     408             : 
     409           0 :     msg.baroCrossChecked = ADSB_NIC_BARO_UNVERIFIED;
     410           0 :     msg.identActive = _frontend.out_state.ctrl.identActive;
     411           0 :     _frontend.out_state.ctrl.identActive = false; // only send identButtonActive once per request
     412           0 :     msg.modeAEnabled = _frontend.out_state.ctrl.modeAEnabled;
     413           0 :     msg.modeCEnabled = _frontend.out_state.ctrl.modeCEnabled;
     414           0 :     msg.modeSEnabled = (_frontend._options & uint32_t(AP_ADSB::AdsbOption::Mode3_Only)) ? 0 : _frontend.out_state.ctrl.modeSEnabled;
     415           0 :     msg.es1090TxEnabled = (_frontend._options & uint32_t(AP_ADSB::AdsbOption::Mode3_Only)) ? 0 : _frontend.out_state.ctrl.es1090TxEnabled;
     416             : 
     417             :     // if enabled via param ADSB_OPTIONS, use squawk 7400 while in any Loss-Comms related failsafe
     418             :     // https://www.faa.gov/documentLibrary/media/Notice/N_JO_7110.724_5-2-9_UAS_Lost_Link_2.pdf
     419           0 :     const AP_Notify& notify = AP::notify();
     420           0 :     if (((_frontend._options & uint32_t(AP_ADSB::AdsbOption::Squawk_7400_FS_RC)) && notify.flags.failsafe_radio) ||
     421           0 :         ((_frontend._options & uint32_t(AP_ADSB::AdsbOption::Squawk_7400_FS_GCS)) && notify.flags.failsafe_gcs)) {
     422           0 :         msg.squawkCode = 7400;
     423             :     } else {
     424           0 :         msg.squawkCode = _frontend.out_state.ctrl.squawkCode;
     425             :     }
     426             : 
     427             : #if AP_ADSB_UAVIONIX_EMERGENCY_STATUS_ON_LOST_LINK
     428             :     const uint32_t last_gcs_ms = gcs().sysid_mygcs_last_seen_time_ms();
     429             :     const bool gcs_lost_comms = (last_gcs_ms != 0) && (AP_HAL::millis() - last_gcs_ms > AP_ADSB_UAVIONIX_GCS_LOST_COMMS_LONG_TIMEOUT_MS);
     430             :     msg.emergencyState = gcs_lost_comms ? ADSB_EMERGENCY_STATUS::ADSB_EMERGENCY_UAS_LOST_LINK : ADSB_EMERGENCY_STATUS::ADSB_EMERGENCY_NONE;
     431             : #else
     432           0 :     msg.emergencyState = ADSB_EMERGENCY_STATUS::ADSB_EMERGENCY_NONE;
     433             : #endif
     434             : 
     435             : #if GDL90_TRANSPONDER_CONTROL_VERSION == 2
     436           0 :     msg.x_bit = 0;
     437             : #endif
     438             : 
     439           0 :     memcpy(msg.callsign, _frontend.out_state.ctrl.callsign, sizeof(msg.callsign));
     440             : 
     441           0 :     gdl90Transmit((GDL90_TX_MESSAGE&)msg, sizeof(msg));
     442           0 : }
     443             : 
     444           0 : void AP_ADSB_uAvionix_UCP::send_GPS_Data()
     445             : {
     446           0 :     GDL90_GPS_DATA_V2 msg {};
     447           0 :     msg.messageId = GDL90_ID_GPS_DATA;
     448           0 :     msg.version = 2;
     449             : 
     450           0 :     const AP_ADSB::Loc &gps { _frontend._my_loc };
     451             : 
     452           0 :     const GPS_FIX fix = (GPS_FIX)gps.status();
     453           0 :     const bool fix_is_good = (fix >= GPS_FIX_3D);
     454           0 :     const Vector3f velocity = fix_is_good ? gps.velocity() : Vector3f();
     455             : 
     456           0 :     msg.utcTime_s = gps.time_epoch_usec() * 1E-6;
     457           0 :     msg.latitude_ddE7 = fix_is_good ? _frontend._my_loc.lat : INT32_MAX;
     458           0 :     msg.longitude_ddE7 = fix_is_good ? _frontend._my_loc.lng : INT32_MAX;
     459           0 :     msg.altitudeGnss_mm = fix_is_good ? (_frontend._my_loc.alt * 10): INT32_MAX;
     460             : 
     461             :     // Protection Limits. FD or SBAS-based depending on state bits
     462             :     // Estimate HPL based on horizontal accuracy/HFOM to a probability of 10^-7:
     463             :     //  Using the central limit theorem for a Gaussian distribution,
     464             :     //  this is 5.326724*stdDev.
     465             :     //  Conservatively round up to 6 as a scaling factor,
     466             :     //  and asssume HFOM of 95% was calculated as 2*stdDev*HDOP.
     467             :     //  This yields a factor of 3 to estimate HPL from horizontal accuracy.
     468             :     float accHoriz;
     469           0 :     bool gotAccHoriz = gps.horizontal_accuracy(accHoriz);
     470           0 :     msg.HPL_mm = gotAccHoriz ? 3 * accHoriz * 1E3 : UINT32_MAX; // required to calculate NIC
     471           0 :     msg.VPL_cm = UINT32_MAX; // unused by ping200X
     472             : 
     473             :     // Figure of Merits
     474           0 :     msg.horizontalFOM_mm = gotAccHoriz ? accHoriz * 1E3 : UINT32_MAX;
     475             :     float accVert;
     476           0 :     msg.verticalFOM_cm = gps.vertical_accuracy(accVert) ? accVert * 1E2 : UINT16_MAX;
     477             :     float accVel;
     478           0 :     msg.horizontalVelocityFOM_mmps = gps.speed_accuracy(accVel) ? accVel * 1E3 : UINT16_MAX;
     479           0 :     msg.verticalVelocityFOM_mmps = msg.horizontalVelocityFOM_mmps;
     480             : 
     481             :     // Velocities
     482           0 :     msg.verticalVelocity_cmps = fix_is_good ? -1.0f * velocity.z * 1E2 : INT16_MAX;
     483           0 :     msg.northVelocity_mmps = fix_is_good ? velocity.x * 1E3 : INT32_MAX;
     484           0 :     msg.eastVelocity_mmps = fix_is_good ? velocity.y * 1E3 : INT32_MAX;
     485             : 
     486             :     // State
     487           0 :     msg.fixType = fix;
     488             : 
     489           0 :     GDL90_GPS_NAV_STATE nav_state {};
     490           0 :     nav_state.HPLfdeActive = 1;
     491           0 :     nav_state.fault = 0;
     492           0 :     nav_state.HrdMagNorth = 0;  // 1 means "north" is magnetic north
     493             : 
     494           0 :     msg.navState = nav_state;
     495           0 :     msg.satsUsed = gps.num_sats();
     496             : 
     497           0 :     gdl90Transmit((GDL90_TX_MESSAGE&)msg, sizeof(msg));
     498           0 : }
     499             : 
     500             : 
     501           0 : bool AP_ADSB_uAvionix_UCP::hostTransmit(uint8_t *buffer, uint16_t length)
     502             : {
     503           0 :     if (_port == nullptr || _port->txspace() < length) {
     504           0 :       return false;
     505             :     }
     506           0 :     _port->write(buffer, length);
     507           0 :     return true;
     508             : }
     509             : 
     510             : 
     511           0 : bool AP_ADSB_uAvionix_UCP::request_msg(const GDL90_MESSAGE_ID msg_id)
     512             : {
     513           0 :     const GDL90_TRANSPONDER_MESSAGE_REQUEST_V2 msg = {
     514             :       messageId : GDL90_ID_MESSAGE_REQUEST,
     515             :       version   : 2,
     516             :       reqMsgId  : msg_id
     517           0 :     };
     518           0 :     return gdl90Transmit((GDL90_TX_MESSAGE&)msg, sizeof(msg)) != 0;
     519             : }
     520             : 
     521             : 
     522           0 : uint16_t AP_ADSB_uAvionix_UCP::gdl90Transmit(GDL90_TX_MESSAGE &message, const uint16_t length)
     523             : {
     524           0 :     uint8_t gdl90FrameBuffer[GDL90_TX_MAX_FRAME_LENGTH] {};
     525             : 
     526           0 :     const uint16_t frameCrc = crc16_ccitt_GDL90((uint8_t*)&message.raw, length, 0);
     527             : 
     528             :     // Set flag byte in frame buffer
     529           0 :     gdl90FrameBuffer[0] = GDL90_FLAG_BYTE;
     530           0 :     uint16_t frameIndex = 1;
     531             : 
     532             :     // Copy and stuff all payload bytes into frame buffer
     533           0 :     for (uint16_t i = 0; i < length+2; i++) {
     534             :         // Check for overflow of frame buffer
     535           0 :         if (frameIndex >= GDL90_TX_MAX_FRAME_LENGTH) {
     536           0 :             return 0;
     537             :         }
     538             : 
     539             :         uint8_t data;
     540             :         // Append CRC to payload
     541           0 :         if (i == length) {
     542           0 :             data = LOWBYTE(frameCrc);
     543           0 :         } else if (i == length+1) {
     544           0 :             data = HIGHBYTE(frameCrc);
     545             :         } else {
     546           0 :             data = message.raw[i];
     547             :         }
     548             : 
     549           0 :         if (data == GDL90_FLAG_BYTE || data == GDL90_CONTROL_ESCAPE_BYTE) {
     550             :             // Check for frame buffer overflow on stuffed byte
     551           0 :             if (frameIndex + 2 > GDL90_TX_MAX_FRAME_LENGTH) {
     552           0 :               return 0;
     553             :             }
     554             : 
     555             :             // Set control break and stuff this byte
     556           0 :             gdl90FrameBuffer[frameIndex++] = GDL90_CONTROL_ESCAPE_BYTE;
     557           0 :             gdl90FrameBuffer[frameIndex++] = data ^ GDL90_STUFF_BYTE;
     558             :         } else {
     559           0 :             gdl90FrameBuffer[frameIndex++] = data;
     560             :         }
     561             :     }
     562             : 
     563             :     // Add end of frame indication
     564           0 :     gdl90FrameBuffer[frameIndex++] = GDL90_FLAG_BYTE;
     565             : 
     566             :     // Push packet to UART
     567           0 :     if (hostTransmit(gdl90FrameBuffer, frameIndex)) {
     568           0 :         return frameIndex;
     569             :     }
     570             : 
     571           0 :     return 0;
     572             : }
     573             : 
     574             : 
     575           0 : bool AP_ADSB_uAvionix_UCP::parseByte(const uint8_t data, GDL90_RX_MESSAGE &msg, GDL90_RX_STATUS &status)
     576             : {
     577           0 :     switch (status.state)
     578             :     {
     579           0 :     case GDL90_RX_IDLE:
     580           0 :         if (data == GDL90_FLAG_BYTE && status.prev_data == GDL90_FLAG_BYTE) {
     581           0 :             status.length = 0;
     582           0 :             status.state = GDL90_RX_IN_PACKET;
     583             :         }
     584           0 :         break;
     585             : 
     586           0 :     case GDL90_RX_IN_PACKET:
     587           0 :         if (data == GDL90_CONTROL_ESCAPE_BYTE) {
     588           0 :             status.state = GDL90_RX_UNSTUFF;
     589             : 
     590           0 :         } else if (data == GDL90_FLAG_BYTE) {
     591             :             // packet complete! Check CRC and restart packet cycle on all pass or fail scenarios
     592           0 :             status.state = GDL90_RX_IDLE;
     593             : 
     594           0 :             if (status.length < GDL90_OVERHEAD_LENGTH) {
     595             :                 // something is wrong, there's no actual data
     596           0 :                 return false;
     597             :             }
     598             : 
     599           0 :             const uint8_t crc_LSB = msg.raw[status.length - 2];
     600           0 :             const uint8_t crc_MSB = msg.raw[status.length - 1];
     601             : 
     602             :             // NOTE: status.length contains messageId, payload and CRC16. So status.length-3 is effective payload length
     603           0 :             msg.crc = (uint16_t)crc_LSB | ((uint16_t)crc_MSB << 8);
     604           0 :             const uint16_t crc = crc16_ccitt_GDL90((uint8_t*)&msg.raw, status.length-2, 0);
     605           0 :             if (crc == msg.crc) {
     606           0 :                 status.prev_data = data;
     607             :                 // NOTE: this is the only path that returns true
     608           0 :                 return true;
     609             :             }
     610             : 
     611           0 :         } else if (status.length < GDL90_RX_MAX_PACKET_LENGTH) {
     612           0 :             msg.raw[status.length++] = data;
     613             : 
     614             :         } else {
     615           0 :             status.state = GDL90_RX_IDLE;
     616             :         }
     617           0 :         break;
     618             : 
     619           0 :     case GDL90_RX_UNSTUFF:
     620           0 :         if (status.length < ARRAY_SIZE(msg.raw)) {
     621           0 :             msg.raw[status.length++] = data ^ GDL90_STUFF_BYTE;
     622           0 :             status.state = GDL90_RX_IN_PACKET;
     623             :         } else {
     624           0 :             status.state = GDL90_RX_IDLE;
     625             :         }
     626           0 :         break;
     627             :     }
     628           0 :     status.prev_data = data;
     629           0 :     return false;
     630             : }
     631             : 
     632             : #endif // HAL_ADSB_UCP_ENABLED
     633             : 

Generated by: LCOV version 1.14