Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Path: blob/master/libraries/AP_ADSB/AP_ADSB_uAvionix_UCP.cpp
Views: 1798
/*1Copyright (C) 2021 Kraus Hamdani Aerospace Inc. All rights reserved.23This program is free software: you can redistribute it and/or modify4it under the terms of the GNU General Public License as published by5the Free Software Foundation, either version 3 of the License, or6(at your option) any later version.78This program is distributed in the hope that it will be useful,9but WITHOUT ANY WARRANTY; without even the implied warranty of10MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11GNU General Public License for more details.1213You should have received a copy of the GNU General Public License14along with this program. If not, see <http://www.gnu.org/licenses/>.1516Author: Tom Pittenger17*/1819#include "AP_ADSB_uAvionix_UCP.h"2021// This driver implements the UCP protocol from uAvionix which is a variant of the GDL90 protocol by Garmin22// https://uavionix.com/downloads/ping200X/uAvionix-UCP-Transponder-ICD-Rev-Q.pdf2324#if HAL_ADSB_UCP_ENABLED2526#include <AP_SerialManager/AP_SerialManager.h>27#include <GCS_MAVLink/GCS.h>28#include <AP_HAL/AP_HAL.h>29#include <AP_Math/AP_Math.h>30#include <AP_Math/crc.h>31#include <ctype.h>32#include <AP_Notify/AP_Notify.h>3334#include <AP_GPS/AP_GPS.h>3536extern const AP_HAL::HAL &hal;3738#define AP_ADSB_UAVIONIX_HEALTH_TIMEOUT_MS (5000UL)3940#define AP_ADSB_UAVIONIX_GCS_LOST_COMMS_LONG_TIMEOUT_MINUTES (15UL)41#define AP_ADSB_UAVIONIX_GCS_LOST_COMMS_LONG_TIMEOUT_MS (1000UL * 60UL * AP_ADSB_UAVIONIX_GCS_LOST_COMMS_LONG_TIMEOUT_MINUTES)4243#define AP_ADSB_UAVIONIX_DETECT_GROUNDSTATE 044#define AP_ADSB_UAVIONIX_EMERGENCY_STATUS_ON_LOST_LINK 04546// detect if any port is configured as uAvionix_UCP47bool AP_ADSB_uAvionix_UCP::detect()48{49return AP::serialmanager().have_serial(AP_SerialManager::SerialProtocol_ADSB, 0);50}515253// Init, called once after class is constructed54bool AP_ADSB_uAvionix_UCP::init()55{56_port = AP::serialmanager().find_serial(AP_SerialManager::SerialProtocol_ADSB, 0);57if (_port == nullptr) {58return false;59}6061_frontend.out_state.ctrl.squawkCode = 1200;62_frontend.out_state.tx_status.squawk = 1200;63_frontend.out_state.tx_status.fault |= UAVIONIX_ADSB_OUT_STATUS_FAULT_STATUS_MESSAGE_UNAVAIL;6465return true;66}676869void AP_ADSB_uAvionix_UCP::update()70{71if (_port == nullptr) {72return;73}7475const uint32_t now_ms = AP_HAL::millis();7677// -----------------------------78// read any available data on serial port79// -----------------------------80uint32_t nbytes = MIN(_port->available(), 10UL * GDL90_RX_MAX_PACKET_LENGTH);81while (nbytes-- > 0) {82uint8_t data;83if (!_port->read(data)) {84break;85}86if (parseByte(data, rx.msg, rx.status)) {87rx.last_msg_ms = now_ms;88handle_msg(rx.msg);89}90} // while nbytes919293if (run_state.last_packet_Transponder_Id_ms == 0 && run_state.request_Transponder_Id_tries < 5) {94if (now_ms - run_state.last_packet_Request_Transponder_Id_ms >= 1000)95{96request_msg(GDL90_ID_IDENTIFICATION);97run_state.request_Transponder_Id_tries++;98}99}100101if (run_state.last_packet_Transponder_Config_ms == 0 && run_state.request_Transponder_Config_tries < 5) {102if (now_ms - run_state.last_packet_Request_Transponder_Config_ms >= 1000)103{104request_msg(GDL90_ID_TRANSPONDER_CONFIG);105run_state.request_Transponder_Config_tries++;106}107}108109if (now_ms - run_state.last_packet_Transponder_Control_ms >= 1000) {110run_state.last_packet_Transponder_Control_ms = now_ms;111112// We want to use the defaults stored on the ping200X, if possible.113// Until we get the config message (or we've tried requesting it several times),114// don't send the control message.115if (run_state.last_packet_Transponder_Config_ms != 0 || run_state.request_Transponder_Config_tries >= 5) {116send_Transponder_Control();117}118}119120if ((now_ms - run_state.last_packet_GPS_ms >= 200) && (_frontend._options & uint32_t(AP_ADSB::AdsbOption::Ping200X_Send_GPS)) != 0) {121run_state.last_packet_GPS_ms = now_ms;122send_GPS_Data();123}124125// if the transponder has stopped giving us the data needed to126// fill the transponder status mavlink message, indicate status unavailable.127if ((now_ms - run_state.last_packet_Transponder_Status_ms >= 10000)128&& (now_ms - run_state.last_packet_Transponder_Heartbeat_ms >= 10000)129&& (now_ms - run_state.last_packet_Transponder_Ownship_ms >= 10000)) {130_frontend.out_state.tx_status.fault |= UAVIONIX_ADSB_OUT_STATUS_FAULT_STATUS_MESSAGE_UNAVAIL;131}132}133134135void AP_ADSB_uAvionix_UCP::handle_msg(const GDL90_RX_MESSAGE &msg)136{137switch(msg.messageId) {138case GDL90_ID_HEARTBEAT: {139// The Heartbeat message provides real-time indications of the status and operation of the140// transponder. The message will be transmitted with a period of one second for the UCP141// protocol.142memcpy(&rx.decoded.heartbeat, msg.raw, sizeof(rx.decoded.heartbeat));143run_state.last_packet_Transponder_Heartbeat_ms = AP_HAL::millis();144_frontend.out_state.tx_status.fault &= ~UAVIONIX_ADSB_OUT_STATUS_FAULT_STATUS_MESSAGE_UNAVAIL;145146if (rx.decoded.heartbeat.status.one.maintenanceRequired) {147_frontend.out_state.tx_status.fault |= UAVIONIX_ADSB_OUT_STATUS_FAULT_MAINT_REQ;148} else {149_frontend.out_state.tx_status.fault &= ~UAVIONIX_ADSB_OUT_STATUS_FAULT_MAINT_REQ;150}151152if (rx.decoded.heartbeat.status.two.functionFailureGnssUnavailable) {153_frontend.out_state.tx_status.fault |= UAVIONIX_ADSB_OUT_STATUS_FAULT_GPS_UNAVAIL;154} else {155_frontend.out_state.tx_status.fault &= ~UAVIONIX_ADSB_OUT_STATUS_FAULT_GPS_UNAVAIL;156}157158if (rx.decoded.heartbeat.status.two.functionFailureGnssNo3dFix) {159_frontend.out_state.tx_status.fault |= UAVIONIX_ADSB_OUT_STATUS_FAULT_GPS_NO_POS;160} else {161_frontend.out_state.tx_status.fault &= ~UAVIONIX_ADSB_OUT_STATUS_FAULT_GPS_NO_POS;162}163164if (rx.decoded.heartbeat.status.two.functionFailureTransmitSystem) {165_frontend.out_state.tx_status.fault |= UAVIONIX_ADSB_OUT_STATUS_FAULT_TX_SYSTEM_FAIL;166} else {167_frontend.out_state.tx_status.fault &= ~UAVIONIX_ADSB_OUT_STATUS_FAULT_TX_SYSTEM_FAIL;168}169}170break;171172case GDL90_ID_IDENTIFICATION:173run_state.last_packet_Transponder_Id_ms = AP_HAL::millis();174// The Identification message contains information used to identify the connected device. The175// Identification message will be transmitted with a period of one second regardless of data status176// or update for the UCP protocol and will be transmitted upon request for the UCP-HD protocol.177if (memcmp(&rx.decoded.identification, msg.raw, sizeof(rx.decoded.identification)) != 0) {178memcpy(&rx.decoded.identification, msg.raw, sizeof(rx.decoded.identification));179180// Firmware Part Number (not null terminated, but null padded if part number is less than 15 characters).181// Copy into a temporary string that is 1 char longer so we ensure it's null terminated182const uint8_t str_len = sizeof(rx.decoded.identification.primaryFwPartNumber);183char primaryFwPartNumber[str_len+1];184memcpy(&primaryFwPartNumber, rx.decoded.identification.primaryFwPartNumber, str_len);185primaryFwPartNumber[str_len] = 0;186187GCS_SEND_TEXT(MAV_SEVERITY_DEBUG,"ADSB:Detected %s v%u.%u.%u SN:%u %s",188get_hardware_name(rx.decoded.identification.primary.hwId),189(unsigned)rx.decoded.identification.primary.fwMajorVersion,190(unsigned)rx.decoded.identification.primary.fwMinorVersion,191(unsigned)rx.decoded.identification.primary.fwBuildVersion,192(unsigned)rx.decoded.identification.primary.serialNumber,193primaryFwPartNumber);194}195break;196197case GDL90_ID_TRANSPONDER_CONFIG:198run_state.last_packet_Transponder_Config_ms = AP_HAL::millis();199memcpy(&rx.decoded.transponder_config, msg.raw, sizeof(rx.decoded.transponder_config));200break;201202#if AP_ADSB_UAVIONIX_UCP_CAPTURE_ALL_RX_PACKETS203case GDL90_ID_OWNSHIP_REPORT:204_frontend.out_state.tx_status.fault &= ~UAVIONIX_ADSB_OUT_STATUS_FAULT_STATUS_MESSAGE_UNAVAIL;205// The Ownship message contains information on the GNSS position. If the Ownship GNSS206// position fix is invalid, the Latitude, Longitude, and NIC fields will all have the ZERO value. The207// Ownship message will be transmitted with a period of one second regardless of data status or208// update for the UCP protocol. All fields in the ownship message are transmitted MSB first209memcpy(&rx.decoded.ownship_report, msg.raw, sizeof(rx.decoded.ownship_report));210run_state.last_packet_Transponder_Ownship_ms = AP_HAL::millis();211_frontend.out_state.tx_status.NIC_NACp = rx.decoded.ownship_report.report.NIC | (rx.decoded.ownship_report.report.NACp << 4);212memcpy(_frontend.out_state.tx_status.flight_id, rx.decoded.ownship_report.report.callsign, sizeof(_frontend.out_state.tx_status.flight_id));213break;214215case GDL90_ID_OWNSHIP_GEOMETRIC_ALTITUDE:216// An Ownship Geometric Altitude message will be transmitted with a period of one second when217// the GNSS fix is valid for the UCP protocol. All fields in the Geometric Ownship Altitude218// message are transmitted MSB first.219memcpy(&rx.decoded.ownship_geometric_altitude, msg.raw, sizeof(rx.decoded.ownship_geometric_altitude));220break;221222case GDL90_ID_SENSOR_MESSAGE:223memcpy(&rx.decoded.sensor_message, msg.raw, sizeof(rx.decoded.sensor_message));224break;225226case GDL90_ID_TRANSPONDER_STATUS:227{228_frontend.out_state.tx_status.fault &= ~UAVIONIX_ADSB_OUT_STATUS_FAULT_STATUS_MESSAGE_UNAVAIL;229switch (msg.payload[0]) {230case 1: {231// version 1 of the transponder status message is sent at 1 Hz (if UCP protocol out is enabled on the transponder)232memcpy(&rx.decoded.transponder_status, msg.raw, sizeof(rx.decoded.transponder_status));233if (rx.decoded.transponder_status.identActive) {234_frontend.out_state.tx_status.state |= UAVIONIX_ADSB_OUT_STATUS_STATE_IDENT_ACTIVE;235} else {236_frontend.out_state.tx_status.state &= ~UAVIONIX_ADSB_OUT_STATUS_STATE_IDENT_ACTIVE;237}238239if (rx.decoded.transponder_status.modeAEnabled) {240_frontend.out_state.tx_status.state |= UAVIONIX_ADSB_OUT_STATUS_STATE_MODE_A_ENABLED;241} else {242_frontend.out_state.tx_status.state &= ~UAVIONIX_ADSB_OUT_STATUS_STATE_MODE_A_ENABLED;243}244245if (rx.decoded.transponder_status.modeCEnabled) {246_frontend.out_state.tx_status.state |= UAVIONIX_ADSB_OUT_STATUS_STATE_MODE_C_ENABLED;247} else {248_frontend.out_state.tx_status.state &= ~UAVIONIX_ADSB_OUT_STATUS_STATE_MODE_C_ENABLED;249}250251if (rx.decoded.transponder_status.modeSEnabled) {252_frontend.out_state.tx_status.state |= UAVIONIX_ADSB_OUT_STATUS_STATE_MODE_S_ENABLED;253} else {254_frontend.out_state.tx_status.state &= ~UAVIONIX_ADSB_OUT_STATUS_STATE_MODE_S_ENABLED;255}256257if (rx.decoded.transponder_status.es1090TxEnabled) {258_frontend.out_state.tx_status.state |= UAVIONIX_ADSB_OUT_STATUS_STATE_1090ES_TX_ENABLED;259} else {260_frontend.out_state.tx_status.state &= ~UAVIONIX_ADSB_OUT_STATUS_STATE_1090ES_TX_ENABLED;261}262263if (rx.decoded.transponder_status.x_bit) {264_frontend.out_state.tx_status.state |= UAVIONIX_ADSB_OUT_STATUS_STATE_XBIT_ENABLED;265} else {266_frontend.out_state.tx_status.state &= ~UAVIONIX_ADSB_OUT_STATUS_STATE_XBIT_ENABLED;267}268269_frontend.out_state.tx_status.squawk = rx.decoded.transponder_status.squawkCode;270271if (run_state.last_packet_Transponder_Status_ms == 0 && run_state.last_packet_Transponder_Config_ms == 0) {272// If this is the first time we've seen a status message,273// and we haven't initialized the control message from the config message,274// set initial control message contents to match transponder's current behavior.275_frontend.out_state.ctrl.modeAEnabled = rx.decoded.transponder_status.modeAEnabled;276_frontend.out_state.ctrl.modeCEnabled = rx.decoded.transponder_status.modeCEnabled;277_frontend.out_state.ctrl.modeSEnabled = rx.decoded.transponder_status.modeSEnabled;278_frontend.out_state.ctrl.es1090TxEnabled = rx.decoded.transponder_status.es1090TxEnabled;279_frontend.out_state.ctrl.squawkCode = rx.decoded.transponder_status.squawkCode;280_frontend.out_state.ctrl.x_bit = rx.decoded.transponder_status.x_bit;281}282run_state.last_packet_Transponder_Status_ms = AP_HAL::millis();283#if AP_MAVLINK_MSG_UAVIONIX_ADSB_OUT_STATUS_ENABLED284GCS_SEND_MESSAGE(MSG_UAVIONIX_ADSB_OUT_STATUS);285run_state.last_gcs_send_message_Transponder_Status_ms = AP_HAL::millis();286#endif287break;288}289case 2:290// deprecated291break;292case 3: {293// 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)294memcpy(&rx.decoded.transponder_status_v3, msg.raw, sizeof(rx.decoded.transponder_status_v3));295296if (rx.decoded.transponder_status_v3.indicatingOnGround) {297_frontend.out_state.tx_status.state |= UAVIONIX_ADSB_OUT_STATUS_STATE_ON_GROUND;298} else {299_frontend.out_state.tx_status.state &= ~UAVIONIX_ADSB_OUT_STATUS_STATE_ON_GROUND;300}301302if (rx.decoded.transponder_status_v3.fault) {303// unsure what fault is indicated, query heartbeat for more info304request_msg(GDL90_ID_HEARTBEAT);305}306307if (rx.decoded.transponder_status_v3.identActive) {308_frontend.out_state.tx_status.state |= UAVIONIX_ADSB_OUT_STATUS_STATE_IDENT_ACTIVE;309} else {310_frontend.out_state.tx_status.state &= ~UAVIONIX_ADSB_OUT_STATUS_STATE_IDENT_ACTIVE;311}312313if (rx.decoded.transponder_status_v3.modeAEnabled) {314_frontend.out_state.tx_status.state |= UAVIONIX_ADSB_OUT_STATUS_STATE_MODE_A_ENABLED;315} else {316_frontend.out_state.tx_status.state &= ~UAVIONIX_ADSB_OUT_STATUS_STATE_MODE_A_ENABLED;317}318319if (rx.decoded.transponder_status_v3.modeCEnabled) {320_frontend.out_state.tx_status.state |= UAVIONIX_ADSB_OUT_STATUS_STATE_MODE_C_ENABLED;321} else {322_frontend.out_state.tx_status.state &= ~UAVIONIX_ADSB_OUT_STATUS_STATE_MODE_C_ENABLED;323}324325if (rx.decoded.transponder_status_v3.modeSEnabled) {326_frontend.out_state.tx_status.state |= UAVIONIX_ADSB_OUT_STATUS_STATE_MODE_S_ENABLED;327} else {328_frontend.out_state.tx_status.state &= ~UAVIONIX_ADSB_OUT_STATUS_STATE_MODE_S_ENABLED;329}330331if (rx.decoded.transponder_status_v3.es1090TxEnabled) {332_frontend.out_state.tx_status.state |= UAVIONIX_ADSB_OUT_STATUS_STATE_1090ES_TX_ENABLED;333} else {334_frontend.out_state.tx_status.state &= ~UAVIONIX_ADSB_OUT_STATUS_STATE_1090ES_TX_ENABLED;335}336337_frontend.out_state.tx_status.squawk = rx.decoded.transponder_status_v3.squawkCode;338_frontend.out_state.tx_status.NIC_NACp = rx.decoded.transponder_status_v3.NIC | (rx.decoded.transponder_status_v3.NACp << 4);339_frontend.out_state.tx_status.boardTemp = rx.decoded.transponder_status_v3.temperature;340341if (run_state.last_packet_Transponder_Status_ms == 0 && run_state.last_packet_Transponder_Config_ms == 0) {342// If this is the first time we've seen a status message,343// and we haven't initialized the control message from the config message,344// set initial control message contents to match transponder's current behavior.345_frontend.out_state.ctrl.modeAEnabled = rx.decoded.transponder_status_v3.modeAEnabled;346_frontend.out_state.ctrl.modeCEnabled = rx.decoded.transponder_status_v3.modeCEnabled;347_frontend.out_state.ctrl.modeSEnabled = rx.decoded.transponder_status_v3.modeSEnabled;348_frontend.out_state.ctrl.es1090TxEnabled = rx.decoded.transponder_status_v3.es1090TxEnabled;349_frontend.out_state.ctrl.squawkCode = rx.decoded.transponder_status_v3.squawkCode;350}351run_state.last_packet_Transponder_Status_ms = AP_HAL::millis();352#if AP_MAVLINK_MSG_UAVIONIX_ADSB_OUT_STATUS_ENABLED353GCS_SEND_MESSAGE(MSG_UAVIONIX_ADSB_OUT_STATUS);354run_state.last_gcs_send_message_Transponder_Status_ms = AP_HAL::millis();355#endif356break;357}358default:359break;360}361break;362}363#endif // AP_ADSB_UAVIONIX_UCP_CAPTURE_ALL_RX_PACKETS364365case GDL90_ID_TRANSPONDER_CONTROL:366case GDL90_ID_GPS_DATA:367case GDL90_ID_MESSAGE_REQUEST:368// not handled, outbound only369break;370default:371//GCS_SEND_TEXT(MAV_SEVERITY_DEBUG,"ADSB:Unknown msg %d", (int)msg.messageId);372break;373}374}375376377const char* AP_ADSB_uAvionix_UCP::get_hardware_name(const uint8_t hwId)378{379switch(hwId) {380case 0x09: return "Ping200s";381case 0x0A: return "Ping20s";382case 0x18: return "Ping200C";383case 0x27: return "Ping20Z";384case 0x2D: return "SkyBeaconX"; // (certified)385case 0x26: //return "Ping200Z/Ping200X"; // (uncertified). Let's fallthrough and use Ping200X386case 0x2F: return "Ping200X"; // (certified)387case 0x30: return "TailBeaconX"; // (certified)388} // switch hwId389return "Unknown HW";390}391392void AP_ADSB_uAvionix_UCP::send_Transponder_Control()393{394GDL90_TRANSPONDER_CONTROL_MSG msg {};395msg.messageId = GDL90_ID_TRANSPONDER_CONTROL;396msg.version = GDL90_TRANSPONDER_CONTROL_VERSION;397398#if CONFIG_HAL_BOARD == HAL_BOARD_SITL399// when using the simulator, always declare we're on the ground to help400// inhibit chaos if this ias actually being broadcasted on real hardware401msg.airGroundState = ADSB_ON_GROUND;402#elif AP_ADSB_UAVIONIX_DETECT_GROUNDSTATE403msg.airGroundState = _frontend.out_state.is_flying ? ADSB_AIRBORNE_SUBSONIC : ADSB_ON_GROUND;404#else405msg.airGroundState = ADSB_AIRBORNE_SUBSONIC;406#endif407408msg.baroCrossChecked = ADSB_NIC_BARO_UNVERIFIED;409msg.identActive = _frontend.out_state.ctrl.identActive;410_frontend.out_state.ctrl.identActive = false; // only send identButtonActive once per request411msg.modeAEnabled = _frontend.out_state.ctrl.modeAEnabled;412msg.modeCEnabled = _frontend.out_state.ctrl.modeCEnabled;413msg.modeSEnabled = (_frontend._options & uint32_t(AP_ADSB::AdsbOption::Mode3_Only)) ? 0 : _frontend.out_state.ctrl.modeSEnabled;414msg.es1090TxEnabled = (_frontend._options & uint32_t(AP_ADSB::AdsbOption::Mode3_Only)) ? 0 : _frontend.out_state.ctrl.es1090TxEnabled;415416// if enabled via param ADSB_OPTIONS, use squawk 7400 while in any Loss-Comms related failsafe417// https://www.faa.gov/documentLibrary/media/Notice/N_JO_7110.724_5-2-9_UAS_Lost_Link_2.pdf418const AP_Notify& notify = AP::notify();419if (((_frontend._options & uint32_t(AP_ADSB::AdsbOption::Squawk_7400_FS_RC)) && notify.flags.failsafe_radio) ||420((_frontend._options & uint32_t(AP_ADSB::AdsbOption::Squawk_7400_FS_GCS)) && notify.flags.failsafe_gcs)) {421msg.squawkCode = 7400;422} else {423msg.squawkCode = _frontend.out_state.ctrl.squawkCode;424}425426#if AP_ADSB_UAVIONIX_EMERGENCY_STATUS_ON_LOST_LINK427const uint32_t last_gcs_ms = gcs().sysid_myggcs_last_seen_time_ms();428const bool gcs_lost_comms = (last_gcs_ms != 0) && (AP_HAL::millis() - last_gcs_ms > AP_ADSB_UAVIONIX_GCS_LOST_COMMS_LONG_TIMEOUT_MS);429msg.emergencyState = gcs_lost_comms ? ADSB_EMERGENCY_STATUS::ADSB_EMERGENCY_UAS_LOST_LINK : ADSB_EMERGENCY_STATUS::ADSB_EMERGENCY_NONE;430#else431msg.emergencyState = ADSB_EMERGENCY_STATUS::ADSB_EMERGENCY_NONE;432#endif433434#if GDL90_TRANSPONDER_CONTROL_VERSION == 2435msg.x_bit = 0;436#endif437438memcpy(msg.callsign, _frontend.out_state.ctrl.callsign, sizeof(msg.callsign));439440gdl90Transmit((GDL90_TX_MESSAGE&)msg, sizeof(msg));441}442443void AP_ADSB_uAvionix_UCP::send_GPS_Data()444{445GDL90_GPS_DATA_V2 msg {};446msg.messageId = GDL90_ID_GPS_DATA;447msg.version = 2;448449const AP_ADSB::Loc &gps { _frontend._my_loc };450451const GPS_FIX fix = (GPS_FIX)gps.status();452const bool fix_is_good = (fix >= GPS_FIX_3D);453const Vector3f velocity = fix_is_good ? gps.velocity() : Vector3f();454455msg.utcTime_s = gps.time_epoch_usec() * 1E-6;456msg.latitude_ddE7 = fix_is_good ? _frontend._my_loc.lat : INT32_MAX;457msg.longitude_ddE7 = fix_is_good ? _frontend._my_loc.lng : INT32_MAX;458msg.altitudeGnss_mm = fix_is_good ? (_frontend._my_loc.alt * 10): INT32_MAX;459460// Protection Limits. FD or SBAS-based depending on state bits461// Estimate HPL based on horizontal accuracy/HFOM to a probability of 10^-7:462// Using the central limit theorem for a Gaussian distribution,463// this is 5.326724*stdDev.464// Conservatively round up to 6 as a scaling factor,465// and asssume HFOM of 95% was calculated as 2*stdDev*HDOP.466// This yields a factor of 3 to estimate HPL from horizontal accuracy.467float accHoriz;468bool gotAccHoriz = gps.horizontal_accuracy(accHoriz);469msg.HPL_mm = gotAccHoriz ? 3 * accHoriz * 1E3 : UINT32_MAX; // required to calculate NIC470msg.VPL_cm = UINT32_MAX; // unused by ping200X471472// Figure of Merits473msg.horizontalFOM_mm = gotAccHoriz ? accHoriz * 1E3 : UINT32_MAX;474float accVert;475msg.verticalFOM_cm = gps.vertical_accuracy(accVert) ? accVert * 1E2 : UINT16_MAX;476float accVel;477msg.horizontalVelocityFOM_mmps = gps.speed_accuracy(accVel) ? accVel * 1E3 : UINT16_MAX;478msg.verticalVelocityFOM_mmps = msg.horizontalVelocityFOM_mmps;479480// Velocities481msg.verticalVelocity_cmps = fix_is_good ? -1.0f * velocity.z * 1E2 : INT16_MAX;482msg.northVelocity_mmps = fix_is_good ? velocity.x * 1E3 : INT32_MAX;483msg.eastVelocity_mmps = fix_is_good ? velocity.y * 1E3 : INT32_MAX;484485// State486msg.fixType = fix;487488GDL90_GPS_NAV_STATE nav_state {};489nav_state.HPLfdeActive = 1;490nav_state.fault = 0;491nav_state.HrdMagNorth = 0; // 1 means "north" is magnetic north492493msg.navState = nav_state;494msg.satsUsed = gps.num_sats();495496gdl90Transmit((GDL90_TX_MESSAGE&)msg, sizeof(msg));497}498499500bool AP_ADSB_uAvionix_UCP::hostTransmit(uint8_t *buffer, uint16_t length)501{502if (_port == nullptr || _port->txspace() < length) {503return false;504}505_port->write(buffer, length);506return true;507}508509510bool AP_ADSB_uAvionix_UCP::request_msg(const GDL90_MESSAGE_ID msg_id)511{512const GDL90_TRANSPONDER_MESSAGE_REQUEST_V2 msg = {513messageId : GDL90_ID_MESSAGE_REQUEST,514version : 2,515reqMsgId : msg_id516};517return gdl90Transmit((GDL90_TX_MESSAGE&)msg, sizeof(msg)) != 0;518}519520521uint16_t AP_ADSB_uAvionix_UCP::gdl90Transmit(GDL90_TX_MESSAGE &message, const uint16_t length)522{523uint8_t gdl90FrameBuffer[GDL90_TX_MAX_FRAME_LENGTH] {};524525const uint16_t frameCrc = crc16_ccitt_GDL90((uint8_t*)&message.raw, length, 0);526527// Set flag byte in frame buffer528gdl90FrameBuffer[0] = GDL90_FLAG_BYTE;529uint16_t frameIndex = 1;530531// Copy and stuff all payload bytes into frame buffer532for (uint16_t i = 0; i < length+2; i++) {533// Check for overflow of frame buffer534if (frameIndex >= GDL90_TX_MAX_FRAME_LENGTH) {535return 0;536}537538uint8_t data;539// Append CRC to payload540if (i == length) {541data = LOWBYTE(frameCrc);542} else if (i == length+1) {543data = HIGHBYTE(frameCrc);544} else {545data = message.raw[i];546}547548if (data == GDL90_FLAG_BYTE || data == GDL90_CONTROL_ESCAPE_BYTE) {549// Check for frame buffer overflow on stuffed byte550if (frameIndex + 2 > GDL90_TX_MAX_FRAME_LENGTH) {551return 0;552}553554// Set control break and stuff this byte555gdl90FrameBuffer[frameIndex++] = GDL90_CONTROL_ESCAPE_BYTE;556gdl90FrameBuffer[frameIndex++] = data ^ GDL90_STUFF_BYTE;557} else {558gdl90FrameBuffer[frameIndex++] = data;559}560}561562// Add end of frame indication563gdl90FrameBuffer[frameIndex++] = GDL90_FLAG_BYTE;564565// Push packet to UART566if (hostTransmit(gdl90FrameBuffer, frameIndex)) {567return frameIndex;568}569570return 0;571}572573574bool AP_ADSB_uAvionix_UCP::parseByte(const uint8_t data, GDL90_RX_MESSAGE &msg, GDL90_RX_STATUS &status)575{576switch (status.state)577{578case GDL90_RX_IDLE:579if (data == GDL90_FLAG_BYTE && status.prev_data == GDL90_FLAG_BYTE) {580status.length = 0;581status.state = GDL90_RX_IN_PACKET;582}583break;584585case GDL90_RX_IN_PACKET:586if (data == GDL90_CONTROL_ESCAPE_BYTE) {587status.state = GDL90_RX_UNSTUFF;588589} else if (data == GDL90_FLAG_BYTE) {590// packet complete! Check CRC and restart packet cycle on all pass or fail scenarios591status.state = GDL90_RX_IDLE;592593if (status.length < GDL90_OVERHEAD_LENGTH) {594// something is wrong, there's no actual data595return false;596}597598const uint8_t crc_LSB = msg.raw[status.length - 2];599const uint8_t crc_MSB = msg.raw[status.length - 1];600601// NOTE: status.length contains messageId, payload and CRC16. So status.length-3 is effective payload length602msg.crc = (uint16_t)crc_LSB | ((uint16_t)crc_MSB << 8);603const uint16_t crc = crc16_ccitt_GDL90((uint8_t*)&msg.raw, status.length-2, 0);604if (crc == msg.crc) {605status.prev_data = data;606// NOTE: this is the only path that returns true607return true;608}609610} else if (status.length < GDL90_RX_MAX_PACKET_LENGTH) {611msg.raw[status.length++] = data;612613} else {614status.state = GDL90_RX_IDLE;615}616break;617618case GDL90_RX_UNSTUFF:619msg.raw[status.length++] = data ^ GDL90_STUFF_BYTE;620status.state = GDL90_RX_IN_PACKET;621break;622}623status.prev_data = data;624return false;625}626627#endif // HAL_ADSB_UCP_ENABLED628629630631