From 22dae61c8041fed0edad47d11fe625c768f59dd3 Mon Sep 17 00:00:00 2001 From: Pierre Kancir Date: Wed, 25 May 2022 09:55:51 +0200 Subject: [PATCH] AP_ADSB: fix line ending --- libraries/AP_ADSB/AP_ADSB_Backend.cpp | 60 +- libraries/AP_ADSB/AP_ADSB_Sagetech.cpp | 1106 ++++++++--------- libraries/AP_ADSB/AP_ADSB_Sagetech.h | 322 ++--- .../AP_ADSB/AP_ADSB_uAvionix_MAVLink.cpp | 512 ++++---- libraries/AP_ADSB/AP_ADSB_uAvionix_MAVLink.h | 88 +- 5 files changed, 1044 insertions(+), 1044 deletions(-) diff --git a/libraries/AP_ADSB/AP_ADSB_Backend.cpp b/libraries/AP_ADSB/AP_ADSB_Backend.cpp index 78ba460126..3797694300 100644 --- a/libraries/AP_ADSB/AP_ADSB_Backend.cpp +++ b/libraries/AP_ADSB/AP_ADSB_Backend.cpp @@ -1,30 +1,30 @@ -/* - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -#include "AP_ADSB_Backend.h" - -#if HAL_ADSB_ENABLED - -/* - base class constructor. -*/ -AP_ADSB_Backend::AP_ADSB_Backend(AP_ADSB &frontend, uint8_t instance) : - _frontend(frontend), - _instance(instance) -{ -} - -#endif // HAL_ADSB_ENABLED - +/* + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include "AP_ADSB_Backend.h" + +#if HAL_ADSB_ENABLED + +/* + base class constructor. +*/ +AP_ADSB_Backend::AP_ADSB_Backend(AP_ADSB &frontend, uint8_t instance) : + _frontend(frontend), + _instance(instance) +{ +} + +#endif // HAL_ADSB_ENABLED + diff --git a/libraries/AP_ADSB/AP_ADSB_Sagetech.cpp b/libraries/AP_ADSB/AP_ADSB_Sagetech.cpp index d1d902dbcd..13ebc1bf5c 100644 --- a/libraries/AP_ADSB/AP_ADSB_Sagetech.cpp +++ b/libraries/AP_ADSB/AP_ADSB_Sagetech.cpp @@ -1,553 +1,553 @@ -/* - Copyright (C) 2020 Kraus Hamdani Aerospace Inc. All rights reserved. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -#include "AP_ADSB_Sagetech.h" - -#if HAL_ADSB_SAGETECH_ENABLED -#include -#include -#include -#include -#include -#include -#include -#include - -#define SAGETECH_SCALER_LATLNG (1.0f/2.145767E-5f) // 180/(2^23) -#define SAGETECH_SCALER_KNOTS_TO_CMS ((KNOTS_TO_M_PER_SEC/0.125f) * 100.0f) -#define SAGETECH_SCALER_ALTITUDE (1.0f/0.015625f) -#define SAGETECH_SCALER_HEADING_CM ((360.0f/256.0f) * 100.0f) - -#define SAGETECH_VALIDFLAG_LATLNG (1U<<0) -#define SAGETECH_VALIDFLAG_ALTITUDE (1U<<1) -#define SAGETECH_VALIDFLAG_VELOCITY (1U<<2) -#define SAGETECH_VALIDFLAG_GND_SPEED (1U<<3) -#define SAGETECH_VALIDFLAG_HEADING (1U<<4) -#define SAGETECH_VALIDFLAG_V_RATE_GEO (1U<<5) -#define SAGETECH_VALIDFLAG_V_RATE_BARO (1U<<6) -#define SAGETECH_VALIDFLAG_EST_LATLNG (1U<<7) -#define SAGETECH_VALIDFLAG_EST_VELOCITY (1U<<8) - -// detect if any port is configured as Sagetech -bool AP_ADSB_Sagetech::detect() -{ - return AP::serialmanager().have_serial(AP_SerialManager::SerialProtocol_ADSB, 0); -} - -// Init, called once after class is constructed -bool AP_ADSB_Sagetech::init() -{ - _port = AP::serialmanager().find_serial(AP_SerialManager::SerialProtocol_ADSB, 0); - - return (_port != nullptr); -} - -void AP_ADSB_Sagetech::update() -{ - if (_port == nullptr) { - return; - } - - const uint32_t now_ms = AP_HAL::millis(); - - // ----------------------------- - // read any available data on serial port - // ----------------------------- - uint32_t nbytes = MIN(_port->available(), 10 * PAYLOAD_XP_MAX_SIZE); - while (nbytes-- > 0) { - const int16_t data = (uint8_t)_port->read(); - if (data < 0) { - break; - } - if (parse_byte_XP((uint8_t)data)) { - handle_packet_XP(message_in.packet); - } - } // while nbytes - - - // ----------------------------- - // handle timers for generating data - // ----------------------------- - if (!last_packet_initialize_ms || (now_ms - last_packet_initialize_ms >= 5000)) { - last_packet_initialize_ms = now_ms; - send_packet(MsgType_XP::Installation_Set); - - } else if (!last_packet_PreFlight_ms || (now_ms - last_packet_PreFlight_ms >= 8200)) { - last_packet_PreFlight_ms = now_ms; - // TODO: allow callsign to not require a reboot - send_packet(MsgType_XP::Preflight_Set); - - } else if (now_ms - last_packet_Operating_ms >= 1000 && ( - last_packet_Operating_ms == 0 || // send once at boot - // send as data changes - last_operating_squawk != _frontend.out_state.cfg.squawk_octal || - abs(last_operating_alt - _frontend._my_loc.alt) > 1555 || // 1493cm == 49ft. The output resolution is 100ft per bit - last_operating_rf_select != _frontend.out_state.cfg.rfSelect)) - { - last_packet_Operating_ms = now_ms; - last_operating_squawk = _frontend.out_state.cfg.squawk_octal; - last_operating_alt = _frontend._my_loc.alt; - last_operating_rf_select = _frontend.out_state.cfg.rfSelect; - send_packet(MsgType_XP::Operating_Set); - - } else if (now_ms - last_packet_GPS_ms >= (_frontend.out_state.is_flying ? 200 : 1000)) { - // 1Hz when not flying, 5Hz when flying - last_packet_GPS_ms = now_ms; - send_packet(MsgType_XP::GPS_Set); - } -} - -void AP_ADSB_Sagetech::send_packet(const MsgType_XP type) -{ - switch (type) { - case MsgType_XP::Installation_Set: - send_msg_Installation(); - break; - case MsgType_XP::Preflight_Set: - send_msg_PreFlight(); - break; - case MsgType_XP::Operating_Set: - send_msg_Operating(); - break; - case MsgType_XP::GPS_Set: - send_msg_GPS(); - break; - default: - break; - } -} - -void AP_ADSB_Sagetech::request_packet(const MsgType_XP type) -{ - // set all bytes in packet to 0 via {} so we only need to set the ones we need to - Packet_XP pkt {}; - - pkt.type = MsgType_XP::Request; - pkt.id = 0; - pkt.payload_length = 4; - - pkt.payload[0] = static_cast(type); - - send_msg(pkt); -} - - -void AP_ADSB_Sagetech::handle_packet_XP(const Packet_XP &msg) -{ - switch (msg.type) { - case MsgType_XP::ACK: - handle_ack(msg); - break; - - case MsgType_XP::Installation_Response: - case MsgType_XP::Preflight_Response: - case MsgType_XP::Status_Response: - // TODO add support for these - break; - - case MsgType_XP::ADSB_StateVector_Report: - case MsgType_XP::ADSB_ModeStatus_Report: - case MsgType_XP::TISB_StateVector_Report: - case MsgType_XP::TISB_ModeStatus_Report: - case MsgType_XP::TISB_CorasePos_Report: - case MsgType_XP::TISB_ADSB_Mgr_Report: - handle_adsb_in_msg(msg); - break; - - case MsgType_XP::Installation_Set: - case MsgType_XP::Preflight_Set: - case MsgType_XP::Operating_Set: - case MsgType_XP::GPS_Set: - case MsgType_XP::Request: - // these are out-bound only and are not expected to be received - case MsgType_XP::INVALID: - break; - } -} - -void AP_ADSB_Sagetech::handle_ack(const Packet_XP &msg) -{ - // ACK received! - const uint8_t system_state = msg.payload[2]; - transponder_type = (Transponder_Type)msg.payload[6]; - - const uint8_t prev_transponder_mode = last_ack_transponder_mode; - last_ack_transponder_mode = (system_state >> 6) & 0x03; - if (prev_transponder_mode != last_ack_transponder_mode) { - static const char *mode_names[] = {"OFF", "STBY", "ON", "ON-ALT"}; - if (last_ack_transponder_mode < ARRAY_SIZE(mode_names)) { - gcs().send_text(MAV_SEVERITY_INFO, "ADSB: RF Mode: %s", mode_names[last_ack_transponder_mode]); - } - } -} - -void AP_ADSB_Sagetech::handle_adsb_in_msg(const Packet_XP &msg) -{ - AP_ADSB::adsb_vehicle_t vehicle {}; - - vehicle.last_update_ms = AP_HAL::millis(); - - switch (msg.type) { - case MsgType_XP::ADSB_StateVector_Report: { // 0x91 - const uint16_t validFlags = le16toh_ptr(&msg.payload[8]); - vehicle.info.ICAO_address = le24toh_ptr(&msg.payload[10]); - - if (validFlags & SAGETECH_VALIDFLAG_LATLNG) { - vehicle.info.lat = ((int32_t)le24toh_ptr(&msg.payload[20])) * SAGETECH_SCALER_LATLNG; - vehicle.info.lon = ((int32_t)le24toh_ptr(&msg.payload[23])) * SAGETECH_SCALER_LATLNG; - vehicle.info.flags |= ADSB_FLAGS_VALID_COORDS; - } - - if (validFlags & SAGETECH_VALIDFLAG_ALTITUDE) { - vehicle.info.altitude = (int32_t)le24toh_ptr(&msg.payload[26]); - vehicle.info.flags |= ADSB_FLAGS_VALID_ALTITUDE; - } - - if (validFlags & SAGETECH_VALIDFLAG_VELOCITY) { - const float velNS = ((int32_t)le16toh_ptr(&msg.payload[29])) * SAGETECH_SCALER_KNOTS_TO_CMS; - const float velEW = ((int32_t)le16toh_ptr(&msg.payload[31])) * SAGETECH_SCALER_KNOTS_TO_CMS; - vehicle.info.hor_velocity = Vector2f(velEW, velNS).length(); - vehicle.info.flags |= ADSB_FLAGS_VALID_VELOCITY; - } - - if (validFlags & SAGETECH_VALIDFLAG_HEADING) { - vehicle.info.heading = ((float)msg.payload[29]) * SAGETECH_SCALER_HEADING_CM; - vehicle.info.flags |= ADSB_FLAGS_VALID_HEADING; - } - - if ((validFlags & SAGETECH_VALIDFLAG_V_RATE_GEO) || (validFlags & SAGETECH_VALIDFLAG_V_RATE_BARO)) { - vehicle.info.ver_velocity = (int16_t)le16toh_ptr(&msg.payload[38]); - vehicle.info.flags |= ADSB_FLAGS_VERTICAL_VELOCITY_VALID; - } - - _frontend.handle_adsb_vehicle(vehicle); - break; - } - case MsgType_XP::ADSB_ModeStatus_Report: // 0x92 - vehicle.info.ICAO_address = le24toh_ptr(&msg.payload[9]); - - if (msg.payload[16] != 0) { - // if string is non-null, consider it valid - memcpy(&vehicle.info, &msg.payload[16], 8); - vehicle.info.flags |= ADSB_FLAGS_VALID_CALLSIGN; - } - - _frontend.handle_adsb_vehicle(vehicle); - break; - case MsgType_XP::TISB_StateVector_Report: - case MsgType_XP::TISB_ModeStatus_Report: - case MsgType_XP::TISB_CorasePos_Report: - case MsgType_XP::TISB_ADSB_Mgr_Report: - // TODO - return; - - default: - return; - } - -} - -// handling inbound byte and process it in the state machine -// return true when a full packet has been received -bool AP_ADSB_Sagetech::parse_byte_XP(const uint8_t data) -{ - switch (message_in.state) { - default: - case ParseState::WaitingFor_Start: - if (data == 0xA5) { - message_in.state = ParseState::WaitingFor_AssmAddr; - } - break; - case ParseState::WaitingFor_AssmAddr: - message_in.state = (data == 0x01) ? ParseState::WaitingFor_MsgType : ParseState::WaitingFor_Start; - break; - case ParseState::WaitingFor_MsgType: - message_in.packet.type = static_cast(data); - message_in.state = ParseState::WaitingFor_MsgId; - break; - case ParseState::WaitingFor_MsgId: - message_in.packet.id = data; - message_in.state = ParseState::WaitingFor_PayloadLen; - break; - case ParseState::WaitingFor_PayloadLen: - message_in.packet.payload_length = data; - message_in.index = 0; - message_in.state = (data == 0) ? ParseState::WaitingFor_ChecksumFletcher : ParseState::WaitingFor_PayloadContents; - break; - - case ParseState::WaitingFor_PayloadContents: - message_in.packet.payload[message_in.index++] = data; - if (message_in.index >= message_in.packet.payload_length) { - message_in.state = ParseState::WaitingFor_ChecksumFletcher; - message_in.index = 0; - } - break; - - case ParseState::WaitingFor_ChecksumFletcher: - message_in.packet.checksumFletcher = data; - message_in.state = ParseState::WaitingFor_Checksum; - break; - case ParseState::WaitingFor_Checksum: - message_in.packet.checksum = data; - message_in.state = ParseState::WaitingFor_End; - if (checksum_verify_XP(message_in.packet)) { - handle_packet_XP(message_in.packet); - } - break; - - case ParseState::WaitingFor_End: - // we don't care if the end value matches - message_in.state = ParseState::WaitingFor_Start; - break; - } - return false; -} - -// compute Sum and FletcherSum values into a single value -// returns uint16_t with MSByte as Sum and LSByte FletcherSum -uint16_t AP_ADSB_Sagetech::checksum_generate_XP(Packet_XP &msg) const -{ - uint8_t sum = 0; - uint8_t sumFletcher = 0; - - const uint8_t header_message_format[5] { - 0xA5, // start - 0x01, // assembly address - static_cast(msg.type), - msg.id, - msg.payload_length - }; - - for (uint8_t i=0; i<5; i++) { - sum += header_message_format[i]; - sumFletcher += sum; - } - for (uint8_t i=0; i(msg.type), - msg.id, - msg.payload_length - }; - const uint8_t message_format_tail[3] { - msg.checksumFletcher, - msg.checksum, - 0x5A // end - }; - - if (_port != nullptr) { - _port->write(message_format_header, sizeof(message_format_header)); - _port->write(msg.payload, msg.payload_length); - _port->write(message_format_tail, sizeof(message_format_tail)); - } -} - -void AP_ADSB_Sagetech::send_msg_Installation() -{ - Packet_XP pkt {}; - - pkt.type = MsgType_XP::Installation_Set; - pkt.payload_length = 28; // 28== 0x1C - - // Mode C = 3, Mode S = 0 - pkt.id = (transponder_type == Transponder_Type::Mode_C) ? 3 : 0; - -// // convert a decimal 123456 to 0x123456 - // TODO: do a proper conversion. The param contains "131313" but what gets transmitted over the air is 0x200F1. - const uint32_t icao_hex = convert_base_to_decimal(16, _frontend.out_state.cfg.ICAO_id_param); - put_le24_ptr(&pkt.payload[0], icao_hex); - - memcpy(&pkt.payload[3], &_frontend.out_state.cfg.callsign, 8); - - pkt.payload[11] = 0; // airspeed MAX - - pkt.payload[12] = 0; // COM Port 0 baud, fixed at 57600 - pkt.payload[13] = 0; // COM Port 1 baud, fixed at 57600 - pkt.payload[14] = 0; // COM Port 2 baud, fixed at 57600 - - pkt.payload[15] = 1; // GPS from COM port 0 (this port) - pkt.payload[16] = 1; // GPS Integrity - - pkt.payload[17] = _frontend.out_state.cfg.emitterType / 8; // Emitter Set - pkt.payload[18] = _frontend.out_state.cfg.emitterType & 0x0F; // Emitter Type - - pkt.payload[19] = _frontend.out_state.cfg.lengthWidth; // Aircraft Size - - pkt.payload[20] = 0; // Altitude Encoder Offset - pkt.payload[21] = 0; // Altitude Encoder Offset - - pkt.payload[22] = 0x07; // ADSB In Control, enable reading everything - pkt.payload[23] = 30; // ADSB In Report max length COM Port 0 (this one) - pkt.payload[24] = 0; // ADSB In Report max length COM Port 1 - - send_msg(pkt); -} - -void AP_ADSB_Sagetech::send_msg_PreFlight() -{ - Packet_XP pkt {}; - - pkt.type = MsgType_XP::Preflight_Set; - pkt.id = 0; - pkt.payload_length = 10; - - memcpy(&pkt.payload[0], &_frontend.out_state.cfg.callsign, 8); - - memset(&pkt.payload[8], 0, 2); - - send_msg(pkt); -} - -void AP_ADSB_Sagetech::send_msg_Operating() -{ - Packet_XP pkt {}; - - pkt.type = MsgType_XP::Operating_Set; - pkt.id = 0; - pkt.payload_length = 8; - - // squawk - // param is saved as native octal so we need convert back to - // decimal because Sagetech will convert it back to octal - uint16_t squawk = convert_base_to_decimal(8, last_operating_squawk); - put_le16_ptr(&pkt.payload[0], squawk); - - // altitude - if (_frontend.out_state.cfg.rf_capable & 0x01) { - const float alt_meters = last_operating_alt * 0.01f; - const int32_t alt_feet = (int32_t)(alt_meters * FEET_TO_METERS); - const int16_t alt_feet_adj = (alt_feet + 50) / 100; // 1 = 100 feet, 1 = 149 feet, 5 = 500 feet - put_le16_ptr(&pkt.payload[2], alt_feet_adj); - - } else { - // use integrated altitude - recommend by sagetech - pkt.payload[2] = 0x80; - pkt.payload[3] = 0x00; - } - - // RF mode - pkt.payload[4] = last_operating_rf_select; - send_msg(pkt); -} - -void AP_ADSB_Sagetech::send_msg_GPS() -{ - Packet_XP pkt {}; - - pkt.type = MsgType_XP::GPS_Set; - pkt.payload_length = 52; - pkt.id = 0; - - const int32_t longitude = _frontend._my_loc.lng; - const int32_t latitude = _frontend._my_loc.lat; - - // longitude and latitude - // NOTE: these MUST be done in double or else we get roundoff in the maths - const double lon_deg = longitude * (double)1.0e-7 * (longitude < 0 ? -1 : 1); - const double lon_minutes = (lon_deg - int(lon_deg)) * 60; - snprintf((char*)&pkt.payload[0], 12, "%03u%02u.%05u", (unsigned)lon_deg, (unsigned)lon_minutes, unsigned((lon_minutes - (int)lon_minutes) * 1.0E5)); - - const double lat_deg = latitude * (double)1.0e-7 * (latitude < 0 ? -1 : 1); - const double lat_minutes = (lat_deg - int(lat_deg)) * 60; - snprintf((char*)&pkt.payload[11], 11, "%02u%02u.%05u", (unsigned)lat_deg, (unsigned)lat_minutes, unsigned((lat_minutes - (int)lat_minutes) * 1.0E5)); - - // ground speed - const Vector2f speed = AP::ahrs().groundspeed_vector(); - float speed_knots = speed.length() * M_PER_SEC_TO_KNOTS; - snprintf((char*)&pkt.payload[21], 7, "%03u.%02u", (unsigned)speed_knots, unsigned((speed_knots - (int)speed_knots) * 1.0E2)); - - // heading - float heading = wrap_360(degrees(speed.angle())); - snprintf((char*)&pkt.payload[27], 10, "%03u.%04u", unsigned(heading), unsigned((heading - (int)heading) * 1.0E4)); - - // hemisphere - uint8_t hemisphere = 0; - hemisphere |= (latitude >= 0) ? 0x01 : 0; // isNorth - hemisphere |= (longitude >= 0) ? 0x02 : 0; // isEast - hemisphere |= (AP::gps().status() < AP_GPS::GPS_OK_FIX_2D) ? 0x80 : 0; // isInvalid - pkt.payload[35] = hemisphere; - - // time - uint64_t time_usec; - if (AP::rtc().get_utc_usec(time_usec)) { - // not completely accurate, our time includes leap seconds and time_t should be without - const time_t time_sec = time_usec / 1000000; - struct tm* tm = gmtime(&time_sec); - - // format time string - snprintf((char*)&pkt.payload[36], 11, "%02u%02u%06.3f", tm->tm_hour, tm->tm_min, tm->tm_sec + (time_usec % 1000000) * 1.0e-6); - } else { - memset(&pkt.payload[36],' ', 10); - } - - send_msg(pkt); -} - -/* - * Convert base 8 or 16 to decimal. Used to convert an octal/hexadecimal value stored on a GCS as a string field in different format, but then transmitted over mavlink as a float which is always a decimal. - * baseIn: base of input number - * inputNumber: value currently in base "baseIn" to be converted to base "baseOut" - * - * Example: convert ADSB squawk octal "1200" stored in memory as 0x0280 to 0x04B0 - * uint16_t squawk_decimal = convertMathBase(8, squawk_octal); - */ -uint32_t AP_ADSB_Sagetech::convert_base_to_decimal(const uint8_t baseIn, uint32_t inputNumber) -{ - // Our only sensible input bases are 16 and 8 - if (baseIn != 8 && baseIn != 16) { - return inputNumber; - } - - uint32_t outputNumber = 0; - for (uint8_t i=0; inputNumber != 0; i++) { - outputNumber += (inputNumber % 10) * powf(10, i); - inputNumber /= 10; - } - return outputNumber; -} - -#endif // HAL_ADSB_SAGETECH_ENABLED - +/* + Copyright (C) 2020 Kraus Hamdani Aerospace Inc. All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include "AP_ADSB_Sagetech.h" + +#if HAL_ADSB_SAGETECH_ENABLED +#include +#include +#include +#include +#include +#include +#include +#include + +#define SAGETECH_SCALER_LATLNG (1.0f/2.145767E-5f) // 180/(2^23) +#define SAGETECH_SCALER_KNOTS_TO_CMS ((KNOTS_TO_M_PER_SEC/0.125f) * 100.0f) +#define SAGETECH_SCALER_ALTITUDE (1.0f/0.015625f) +#define SAGETECH_SCALER_HEADING_CM ((360.0f/256.0f) * 100.0f) + +#define SAGETECH_VALIDFLAG_LATLNG (1U<<0) +#define SAGETECH_VALIDFLAG_ALTITUDE (1U<<1) +#define SAGETECH_VALIDFLAG_VELOCITY (1U<<2) +#define SAGETECH_VALIDFLAG_GND_SPEED (1U<<3) +#define SAGETECH_VALIDFLAG_HEADING (1U<<4) +#define SAGETECH_VALIDFLAG_V_RATE_GEO (1U<<5) +#define SAGETECH_VALIDFLAG_V_RATE_BARO (1U<<6) +#define SAGETECH_VALIDFLAG_EST_LATLNG (1U<<7) +#define SAGETECH_VALIDFLAG_EST_VELOCITY (1U<<8) + +// detect if any port is configured as Sagetech +bool AP_ADSB_Sagetech::detect() +{ + return AP::serialmanager().have_serial(AP_SerialManager::SerialProtocol_ADSB, 0); +} + +// Init, called once after class is constructed +bool AP_ADSB_Sagetech::init() +{ + _port = AP::serialmanager().find_serial(AP_SerialManager::SerialProtocol_ADSB, 0); + + return (_port != nullptr); +} + +void AP_ADSB_Sagetech::update() +{ + if (_port == nullptr) { + return; + } + + const uint32_t now_ms = AP_HAL::millis(); + + // ----------------------------- + // read any available data on serial port + // ----------------------------- + uint32_t nbytes = MIN(_port->available(), 10 * PAYLOAD_XP_MAX_SIZE); + while (nbytes-- > 0) { + const int16_t data = (uint8_t)_port->read(); + if (data < 0) { + break; + } + if (parse_byte_XP((uint8_t)data)) { + handle_packet_XP(message_in.packet); + } + } // while nbytes + + + // ----------------------------- + // handle timers for generating data + // ----------------------------- + if (!last_packet_initialize_ms || (now_ms - last_packet_initialize_ms >= 5000)) { + last_packet_initialize_ms = now_ms; + send_packet(MsgType_XP::Installation_Set); + + } else if (!last_packet_PreFlight_ms || (now_ms - last_packet_PreFlight_ms >= 8200)) { + last_packet_PreFlight_ms = now_ms; + // TODO: allow callsign to not require a reboot + send_packet(MsgType_XP::Preflight_Set); + + } else if (now_ms - last_packet_Operating_ms >= 1000 && ( + last_packet_Operating_ms == 0 || // send once at boot + // send as data changes + last_operating_squawk != _frontend.out_state.cfg.squawk_octal || + abs(last_operating_alt - _frontend._my_loc.alt) > 1555 || // 1493cm == 49ft. The output resolution is 100ft per bit + last_operating_rf_select != _frontend.out_state.cfg.rfSelect)) + { + last_packet_Operating_ms = now_ms; + last_operating_squawk = _frontend.out_state.cfg.squawk_octal; + last_operating_alt = _frontend._my_loc.alt; + last_operating_rf_select = _frontend.out_state.cfg.rfSelect; + send_packet(MsgType_XP::Operating_Set); + + } else if (now_ms - last_packet_GPS_ms >= (_frontend.out_state.is_flying ? 200 : 1000)) { + // 1Hz when not flying, 5Hz when flying + last_packet_GPS_ms = now_ms; + send_packet(MsgType_XP::GPS_Set); + } +} + +void AP_ADSB_Sagetech::send_packet(const MsgType_XP type) +{ + switch (type) { + case MsgType_XP::Installation_Set: + send_msg_Installation(); + break; + case MsgType_XP::Preflight_Set: + send_msg_PreFlight(); + break; + case MsgType_XP::Operating_Set: + send_msg_Operating(); + break; + case MsgType_XP::GPS_Set: + send_msg_GPS(); + break; + default: + break; + } +} + +void AP_ADSB_Sagetech::request_packet(const MsgType_XP type) +{ + // set all bytes in packet to 0 via {} so we only need to set the ones we need to + Packet_XP pkt {}; + + pkt.type = MsgType_XP::Request; + pkt.id = 0; + pkt.payload_length = 4; + + pkt.payload[0] = static_cast(type); + + send_msg(pkt); +} + + +void AP_ADSB_Sagetech::handle_packet_XP(const Packet_XP &msg) +{ + switch (msg.type) { + case MsgType_XP::ACK: + handle_ack(msg); + break; + + case MsgType_XP::Installation_Response: + case MsgType_XP::Preflight_Response: + case MsgType_XP::Status_Response: + // TODO add support for these + break; + + case MsgType_XP::ADSB_StateVector_Report: + case MsgType_XP::ADSB_ModeStatus_Report: + case MsgType_XP::TISB_StateVector_Report: + case MsgType_XP::TISB_ModeStatus_Report: + case MsgType_XP::TISB_CorasePos_Report: + case MsgType_XP::TISB_ADSB_Mgr_Report: + handle_adsb_in_msg(msg); + break; + + case MsgType_XP::Installation_Set: + case MsgType_XP::Preflight_Set: + case MsgType_XP::Operating_Set: + case MsgType_XP::GPS_Set: + case MsgType_XP::Request: + // these are out-bound only and are not expected to be received + case MsgType_XP::INVALID: + break; + } +} + +void AP_ADSB_Sagetech::handle_ack(const Packet_XP &msg) +{ + // ACK received! + const uint8_t system_state = msg.payload[2]; + transponder_type = (Transponder_Type)msg.payload[6]; + + const uint8_t prev_transponder_mode = last_ack_transponder_mode; + last_ack_transponder_mode = (system_state >> 6) & 0x03; + if (prev_transponder_mode != last_ack_transponder_mode) { + static const char *mode_names[] = {"OFF", "STBY", "ON", "ON-ALT"}; + if (last_ack_transponder_mode < ARRAY_SIZE(mode_names)) { + gcs().send_text(MAV_SEVERITY_INFO, "ADSB: RF Mode: %s", mode_names[last_ack_transponder_mode]); + } + } +} + +void AP_ADSB_Sagetech::handle_adsb_in_msg(const Packet_XP &msg) +{ + AP_ADSB::adsb_vehicle_t vehicle {}; + + vehicle.last_update_ms = AP_HAL::millis(); + + switch (msg.type) { + case MsgType_XP::ADSB_StateVector_Report: { // 0x91 + const uint16_t validFlags = le16toh_ptr(&msg.payload[8]); + vehicle.info.ICAO_address = le24toh_ptr(&msg.payload[10]); + + if (validFlags & SAGETECH_VALIDFLAG_LATLNG) { + vehicle.info.lat = ((int32_t)le24toh_ptr(&msg.payload[20])) * SAGETECH_SCALER_LATLNG; + vehicle.info.lon = ((int32_t)le24toh_ptr(&msg.payload[23])) * SAGETECH_SCALER_LATLNG; + vehicle.info.flags |= ADSB_FLAGS_VALID_COORDS; + } + + if (validFlags & SAGETECH_VALIDFLAG_ALTITUDE) { + vehicle.info.altitude = (int32_t)le24toh_ptr(&msg.payload[26]); + vehicle.info.flags |= ADSB_FLAGS_VALID_ALTITUDE; + } + + if (validFlags & SAGETECH_VALIDFLAG_VELOCITY) { + const float velNS = ((int32_t)le16toh_ptr(&msg.payload[29])) * SAGETECH_SCALER_KNOTS_TO_CMS; + const float velEW = ((int32_t)le16toh_ptr(&msg.payload[31])) * SAGETECH_SCALER_KNOTS_TO_CMS; + vehicle.info.hor_velocity = Vector2f(velEW, velNS).length(); + vehicle.info.flags |= ADSB_FLAGS_VALID_VELOCITY; + } + + if (validFlags & SAGETECH_VALIDFLAG_HEADING) { + vehicle.info.heading = ((float)msg.payload[29]) * SAGETECH_SCALER_HEADING_CM; + vehicle.info.flags |= ADSB_FLAGS_VALID_HEADING; + } + + if ((validFlags & SAGETECH_VALIDFLAG_V_RATE_GEO) || (validFlags & SAGETECH_VALIDFLAG_V_RATE_BARO)) { + vehicle.info.ver_velocity = (int16_t)le16toh_ptr(&msg.payload[38]); + vehicle.info.flags |= ADSB_FLAGS_VERTICAL_VELOCITY_VALID; + } + + _frontend.handle_adsb_vehicle(vehicle); + break; + } + case MsgType_XP::ADSB_ModeStatus_Report: // 0x92 + vehicle.info.ICAO_address = le24toh_ptr(&msg.payload[9]); + + if (msg.payload[16] != 0) { + // if string is non-null, consider it valid + memcpy(&vehicle.info, &msg.payload[16], 8); + vehicle.info.flags |= ADSB_FLAGS_VALID_CALLSIGN; + } + + _frontend.handle_adsb_vehicle(vehicle); + break; + case MsgType_XP::TISB_StateVector_Report: + case MsgType_XP::TISB_ModeStatus_Report: + case MsgType_XP::TISB_CorasePos_Report: + case MsgType_XP::TISB_ADSB_Mgr_Report: + // TODO + return; + + default: + return; + } + +} + +// handling inbound byte and process it in the state machine +// return true when a full packet has been received +bool AP_ADSB_Sagetech::parse_byte_XP(const uint8_t data) +{ + switch (message_in.state) { + default: + case ParseState::WaitingFor_Start: + if (data == 0xA5) { + message_in.state = ParseState::WaitingFor_AssmAddr; + } + break; + case ParseState::WaitingFor_AssmAddr: + message_in.state = (data == 0x01) ? ParseState::WaitingFor_MsgType : ParseState::WaitingFor_Start; + break; + case ParseState::WaitingFor_MsgType: + message_in.packet.type = static_cast(data); + message_in.state = ParseState::WaitingFor_MsgId; + break; + case ParseState::WaitingFor_MsgId: + message_in.packet.id = data; + message_in.state = ParseState::WaitingFor_PayloadLen; + break; + case ParseState::WaitingFor_PayloadLen: + message_in.packet.payload_length = data; + message_in.index = 0; + message_in.state = (data == 0) ? ParseState::WaitingFor_ChecksumFletcher : ParseState::WaitingFor_PayloadContents; + break; + + case ParseState::WaitingFor_PayloadContents: + message_in.packet.payload[message_in.index++] = data; + if (message_in.index >= message_in.packet.payload_length) { + message_in.state = ParseState::WaitingFor_ChecksumFletcher; + message_in.index = 0; + } + break; + + case ParseState::WaitingFor_ChecksumFletcher: + message_in.packet.checksumFletcher = data; + message_in.state = ParseState::WaitingFor_Checksum; + break; + case ParseState::WaitingFor_Checksum: + message_in.packet.checksum = data; + message_in.state = ParseState::WaitingFor_End; + if (checksum_verify_XP(message_in.packet)) { + handle_packet_XP(message_in.packet); + } + break; + + case ParseState::WaitingFor_End: + // we don't care if the end value matches + message_in.state = ParseState::WaitingFor_Start; + break; + } + return false; +} + +// compute Sum and FletcherSum values into a single value +// returns uint16_t with MSByte as Sum and LSByte FletcherSum +uint16_t AP_ADSB_Sagetech::checksum_generate_XP(Packet_XP &msg) const +{ + uint8_t sum = 0; + uint8_t sumFletcher = 0; + + const uint8_t header_message_format[5] { + 0xA5, // start + 0x01, // assembly address + static_cast(msg.type), + msg.id, + msg.payload_length + }; + + for (uint8_t i=0; i<5; i++) { + sum += header_message_format[i]; + sumFletcher += sum; + } + for (uint8_t i=0; i(msg.type), + msg.id, + msg.payload_length + }; + const uint8_t message_format_tail[3] { + msg.checksumFletcher, + msg.checksum, + 0x5A // end + }; + + if (_port != nullptr) { + _port->write(message_format_header, sizeof(message_format_header)); + _port->write(msg.payload, msg.payload_length); + _port->write(message_format_tail, sizeof(message_format_tail)); + } +} + +void AP_ADSB_Sagetech::send_msg_Installation() +{ + Packet_XP pkt {}; + + pkt.type = MsgType_XP::Installation_Set; + pkt.payload_length = 28; // 28== 0x1C + + // Mode C = 3, Mode S = 0 + pkt.id = (transponder_type == Transponder_Type::Mode_C) ? 3 : 0; + +// // convert a decimal 123456 to 0x123456 + // TODO: do a proper conversion. The param contains "131313" but what gets transmitted over the air is 0x200F1. + const uint32_t icao_hex = convert_base_to_decimal(16, _frontend.out_state.cfg.ICAO_id_param); + put_le24_ptr(&pkt.payload[0], icao_hex); + + memcpy(&pkt.payload[3], &_frontend.out_state.cfg.callsign, 8); + + pkt.payload[11] = 0; // airspeed MAX + + pkt.payload[12] = 0; // COM Port 0 baud, fixed at 57600 + pkt.payload[13] = 0; // COM Port 1 baud, fixed at 57600 + pkt.payload[14] = 0; // COM Port 2 baud, fixed at 57600 + + pkt.payload[15] = 1; // GPS from COM port 0 (this port) + pkt.payload[16] = 1; // GPS Integrity + + pkt.payload[17] = _frontend.out_state.cfg.emitterType / 8; // Emitter Set + pkt.payload[18] = _frontend.out_state.cfg.emitterType & 0x0F; // Emitter Type + + pkt.payload[19] = _frontend.out_state.cfg.lengthWidth; // Aircraft Size + + pkt.payload[20] = 0; // Altitude Encoder Offset + pkt.payload[21] = 0; // Altitude Encoder Offset + + pkt.payload[22] = 0x07; // ADSB In Control, enable reading everything + pkt.payload[23] = 30; // ADSB In Report max length COM Port 0 (this one) + pkt.payload[24] = 0; // ADSB In Report max length COM Port 1 + + send_msg(pkt); +} + +void AP_ADSB_Sagetech::send_msg_PreFlight() +{ + Packet_XP pkt {}; + + pkt.type = MsgType_XP::Preflight_Set; + pkt.id = 0; + pkt.payload_length = 10; + + memcpy(&pkt.payload[0], &_frontend.out_state.cfg.callsign, 8); + + memset(&pkt.payload[8], 0, 2); + + send_msg(pkt); +} + +void AP_ADSB_Sagetech::send_msg_Operating() +{ + Packet_XP pkt {}; + + pkt.type = MsgType_XP::Operating_Set; + pkt.id = 0; + pkt.payload_length = 8; + + // squawk + // param is saved as native octal so we need convert back to + // decimal because Sagetech will convert it back to octal + uint16_t squawk = convert_base_to_decimal(8, last_operating_squawk); + put_le16_ptr(&pkt.payload[0], squawk); + + // altitude + if (_frontend.out_state.cfg.rf_capable & 0x01) { + const float alt_meters = last_operating_alt * 0.01f; + const int32_t alt_feet = (int32_t)(alt_meters * FEET_TO_METERS); + const int16_t alt_feet_adj = (alt_feet + 50) / 100; // 1 = 100 feet, 1 = 149 feet, 5 = 500 feet + put_le16_ptr(&pkt.payload[2], alt_feet_adj); + + } else { + // use integrated altitude - recommend by sagetech + pkt.payload[2] = 0x80; + pkt.payload[3] = 0x00; + } + + // RF mode + pkt.payload[4] = last_operating_rf_select; + send_msg(pkt); +} + +void AP_ADSB_Sagetech::send_msg_GPS() +{ + Packet_XP pkt {}; + + pkt.type = MsgType_XP::GPS_Set; + pkt.payload_length = 52; + pkt.id = 0; + + const int32_t longitude = _frontend._my_loc.lng; + const int32_t latitude = _frontend._my_loc.lat; + + // longitude and latitude + // NOTE: these MUST be done in double or else we get roundoff in the maths + const double lon_deg = longitude * (double)1.0e-7 * (longitude < 0 ? -1 : 1); + const double lon_minutes = (lon_deg - int(lon_deg)) * 60; + snprintf((char*)&pkt.payload[0], 12, "%03u%02u.%05u", (unsigned)lon_deg, (unsigned)lon_minutes, unsigned((lon_minutes - (int)lon_minutes) * 1.0E5)); + + const double lat_deg = latitude * (double)1.0e-7 * (latitude < 0 ? -1 : 1); + const double lat_minutes = (lat_deg - int(lat_deg)) * 60; + snprintf((char*)&pkt.payload[11], 11, "%02u%02u.%05u", (unsigned)lat_deg, (unsigned)lat_minutes, unsigned((lat_minutes - (int)lat_minutes) * 1.0E5)); + + // ground speed + const Vector2f speed = AP::ahrs().groundspeed_vector(); + float speed_knots = speed.length() * M_PER_SEC_TO_KNOTS; + snprintf((char*)&pkt.payload[21], 7, "%03u.%02u", (unsigned)speed_knots, unsigned((speed_knots - (int)speed_knots) * 1.0E2)); + + // heading + float heading = wrap_360(degrees(speed.angle())); + snprintf((char*)&pkt.payload[27], 10, "%03u.%04u", unsigned(heading), unsigned((heading - (int)heading) * 1.0E4)); + + // hemisphere + uint8_t hemisphere = 0; + hemisphere |= (latitude >= 0) ? 0x01 : 0; // isNorth + hemisphere |= (longitude >= 0) ? 0x02 : 0; // isEast + hemisphere |= (AP::gps().status() < AP_GPS::GPS_OK_FIX_2D) ? 0x80 : 0; // isInvalid + pkt.payload[35] = hemisphere; + + // time + uint64_t time_usec; + if (AP::rtc().get_utc_usec(time_usec)) { + // not completely accurate, our time includes leap seconds and time_t should be without + const time_t time_sec = time_usec / 1000000; + struct tm* tm = gmtime(&time_sec); + + // format time string + snprintf((char*)&pkt.payload[36], 11, "%02u%02u%06.3f", tm->tm_hour, tm->tm_min, tm->tm_sec + (time_usec % 1000000) * 1.0e-6); + } else { + memset(&pkt.payload[36],' ', 10); + } + + send_msg(pkt); +} + +/* + * Convert base 8 or 16 to decimal. Used to convert an octal/hexadecimal value stored on a GCS as a string field in different format, but then transmitted over mavlink as a float which is always a decimal. + * baseIn: base of input number + * inputNumber: value currently in base "baseIn" to be converted to base "baseOut" + * + * Example: convert ADSB squawk octal "1200" stored in memory as 0x0280 to 0x04B0 + * uint16_t squawk_decimal = convertMathBase(8, squawk_octal); + */ +uint32_t AP_ADSB_Sagetech::convert_base_to_decimal(const uint8_t baseIn, uint32_t inputNumber) +{ + // Our only sensible input bases are 16 and 8 + if (baseIn != 8 && baseIn != 16) { + return inputNumber; + } + + uint32_t outputNumber = 0; + for (uint8_t i=0; inputNumber != 0; i++) { + outputNumber += (inputNumber % 10) * powf(10, i); + inputNumber /= 10; + } + return outputNumber; +} + +#endif // HAL_ADSB_SAGETECH_ENABLED + diff --git a/libraries/AP_ADSB/AP_ADSB_Sagetech.h b/libraries/AP_ADSB/AP_ADSB_Sagetech.h index e7c109a454..bf825174f7 100644 --- a/libraries/AP_ADSB/AP_ADSB_Sagetech.h +++ b/libraries/AP_ADSB/AP_ADSB_Sagetech.h @@ -1,161 +1,161 @@ -#pragma once - -/* - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -#include "AP_ADSB_Backend.h" - -#ifndef HAL_ADSB_SAGETECH_ENABLED -#define HAL_ADSB_SAGETECH_ENABLED HAL_ADSB_ENABLED -#endif - -#if HAL_ADSB_SAGETECH_ENABLED -class AP_ADSB_Sagetech : public AP_ADSB_Backend { -public: - using AP_ADSB_Backend::AP_ADSB_Backend; - - // init - performs any required initialisation for this instance - bool init() override; - - // update - should be called periodically - void update() override; - - // static detection function - static bool detect(); - -private: - - static const uint32_t PAYLOAD_XP_MAX_SIZE = 52; - - enum class SystemStateBits { - Error_Transponder = (1U<<0), - Altitidue_Source = (1U<<1), - Error_GPS = (1U<<2), - Error_ICAO = (1U<<3), - Error_Over_Temperature = (1U<<4), - Error_Extended_Squitter = (1U<<5), - Mode_Transponder = (3U<<6), // 2 bit status: - }; - - enum class Transponder_Type { - Mode_C = 0x00, - Mode_S_ADSB_OUT = 0x01, - Mode_S_ADSB_OUT_and_IN = 0x02, - Unknown = 0xFF, - }; - - enum class MsgType_XP { - INVALID = 0, - Installation_Set = 0x01, - Preflight_Set = 0x02, - Operating_Set = 0x03, - GPS_Set = 0x04, - Request = 0x05, - - ACK = 0x80, - Installation_Response = 0x81, - Preflight_Response = 0x82, - Status_Response = 0x83, - ADSB_StateVector_Report = 0x91, - ADSB_ModeStatus_Report = 0x92, - TISB_StateVector_Report = 0x93, - TISB_ModeStatus_Report = 0x94, - TISB_CorasePos_Report = 0x95, - TISB_ADSB_Mgr_Report = 0x96, - }; - - enum class ParseState { - WaitingFor_Start, - WaitingFor_AssmAddr, - WaitingFor_MsgType, - WaitingFor_MsgId, - WaitingFor_PayloadLen, - WaitingFor_PayloadContents, - WaitingFor_ChecksumFletcher, - WaitingFor_Checksum, - WaitingFor_End, - }; - - struct Packet_XP { - const uint8_t start = 0xA5; - const uint8_t assemAddr = 0x01; - MsgType_XP type; - uint8_t id; - uint8_t payload_length; - uint8_t payload[PAYLOAD_XP_MAX_SIZE]; - uint8_t checksumFletcher; - uint8_t checksum; - const uint8_t end = 0x5A; - }; - - struct { - ParseState state; - uint8_t index; - Packet_XP packet; - } message_in; - - // compute Sum and FletcherSum values - uint16_t checksum_generate_XP(Packet_XP &msg) const; - bool checksum_verify_XP(Packet_XP &msg) const; - void checksum_assign_XP(Packet_XP &msg); - - - // handling inbound byte and process it in the state machine - bool parse_byte_XP(const uint8_t data); - - // handle inbound packet - void handle_packet_XP(const Packet_XP &msg); - - // send message to serial port - void send_msg(Packet_XP &msg); - - // handle inbound msgs - void handle_adsb_in_msg(const Packet_XP &msg); - void handle_ack(const Packet_XP &msg); - - // send messages to transceiver - void send_msg_Installation(); - void send_msg_PreFlight(); - void send_msg_Operating(); - void send_msg_GPS(); - - // send packet by type - void send_packet(const MsgType_XP type); - - // send msg to request a packet by type - void request_packet(const MsgType_XP type); - - // Convert base 8 or 16 to decimal. Used to convert an octal/hexadecimal value - // stored on a GCS as a string field in different format, but then transmitted - // over mavlink as a float which is always a decimal. - uint32_t convert_base_to_decimal(const uint8_t baseIn, uint32_t inputNumber); - - // timers for each out-bound packet - uint32_t last_packet_initialize_ms; - uint32_t last_packet_PreFlight_ms; - uint32_t last_packet_GPS_ms; - uint32_t last_packet_Operating_ms; - - // cached variables to compare against params so we can send msg on param change. - uint16_t last_operating_squawk; - int32_t last_operating_alt; - uint8_t last_operating_rf_select; - - // track status changes in acks - uint8_t last_ack_transponder_mode; - Transponder_Type transponder_type = Transponder_Type::Unknown; -}; -#endif // HAL_ADSB_SAGETECH_ENABLED - +#pragma once + +/* + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include "AP_ADSB_Backend.h" + +#ifndef HAL_ADSB_SAGETECH_ENABLED +#define HAL_ADSB_SAGETECH_ENABLED HAL_ADSB_ENABLED +#endif + +#if HAL_ADSB_SAGETECH_ENABLED +class AP_ADSB_Sagetech : public AP_ADSB_Backend { +public: + using AP_ADSB_Backend::AP_ADSB_Backend; + + // init - performs any required initialisation for this instance + bool init() override; + + // update - should be called periodically + void update() override; + + // static detection function + static bool detect(); + +private: + + static const uint32_t PAYLOAD_XP_MAX_SIZE = 52; + + enum class SystemStateBits { + Error_Transponder = (1U<<0), + Altitidue_Source = (1U<<1), + Error_GPS = (1U<<2), + Error_ICAO = (1U<<3), + Error_Over_Temperature = (1U<<4), + Error_Extended_Squitter = (1U<<5), + Mode_Transponder = (3U<<6), // 2 bit status: + }; + + enum class Transponder_Type { + Mode_C = 0x00, + Mode_S_ADSB_OUT = 0x01, + Mode_S_ADSB_OUT_and_IN = 0x02, + Unknown = 0xFF, + }; + + enum class MsgType_XP { + INVALID = 0, + Installation_Set = 0x01, + Preflight_Set = 0x02, + Operating_Set = 0x03, + GPS_Set = 0x04, + Request = 0x05, + + ACK = 0x80, + Installation_Response = 0x81, + Preflight_Response = 0x82, + Status_Response = 0x83, + ADSB_StateVector_Report = 0x91, + ADSB_ModeStatus_Report = 0x92, + TISB_StateVector_Report = 0x93, + TISB_ModeStatus_Report = 0x94, + TISB_CorasePos_Report = 0x95, + TISB_ADSB_Mgr_Report = 0x96, + }; + + enum class ParseState { + WaitingFor_Start, + WaitingFor_AssmAddr, + WaitingFor_MsgType, + WaitingFor_MsgId, + WaitingFor_PayloadLen, + WaitingFor_PayloadContents, + WaitingFor_ChecksumFletcher, + WaitingFor_Checksum, + WaitingFor_End, + }; + + struct Packet_XP { + const uint8_t start = 0xA5; + const uint8_t assemAddr = 0x01; + MsgType_XP type; + uint8_t id; + uint8_t payload_length; + uint8_t payload[PAYLOAD_XP_MAX_SIZE]; + uint8_t checksumFletcher; + uint8_t checksum; + const uint8_t end = 0x5A; + }; + + struct { + ParseState state; + uint8_t index; + Packet_XP packet; + } message_in; + + // compute Sum and FletcherSum values + uint16_t checksum_generate_XP(Packet_XP &msg) const; + bool checksum_verify_XP(Packet_XP &msg) const; + void checksum_assign_XP(Packet_XP &msg); + + + // handling inbound byte and process it in the state machine + bool parse_byte_XP(const uint8_t data); + + // handle inbound packet + void handle_packet_XP(const Packet_XP &msg); + + // send message to serial port + void send_msg(Packet_XP &msg); + + // handle inbound msgs + void handle_adsb_in_msg(const Packet_XP &msg); + void handle_ack(const Packet_XP &msg); + + // send messages to transceiver + void send_msg_Installation(); + void send_msg_PreFlight(); + void send_msg_Operating(); + void send_msg_GPS(); + + // send packet by type + void send_packet(const MsgType_XP type); + + // send msg to request a packet by type + void request_packet(const MsgType_XP type); + + // Convert base 8 or 16 to decimal. Used to convert an octal/hexadecimal value + // stored on a GCS as a string field in different format, but then transmitted + // over mavlink as a float which is always a decimal. + uint32_t convert_base_to_decimal(const uint8_t baseIn, uint32_t inputNumber); + + // timers for each out-bound packet + uint32_t last_packet_initialize_ms; + uint32_t last_packet_PreFlight_ms; + uint32_t last_packet_GPS_ms; + uint32_t last_packet_Operating_ms; + + // cached variables to compare against params so we can send msg on param change. + uint16_t last_operating_squawk; + int32_t last_operating_alt; + uint8_t last_operating_rf_select; + + // track status changes in acks + uint8_t last_ack_transponder_mode; + Transponder_Type transponder_type = Transponder_Type::Unknown; +}; +#endif // HAL_ADSB_SAGETECH_ENABLED + diff --git a/libraries/AP_ADSB/AP_ADSB_uAvionix_MAVLink.cpp b/libraries/AP_ADSB/AP_ADSB_uAvionix_MAVLink.cpp index fe5feee924..14a3b04808 100644 --- a/libraries/AP_ADSB/AP_ADSB_uAvionix_MAVLink.cpp +++ b/libraries/AP_ADSB/AP_ADSB_uAvionix_MAVLink.cpp @@ -1,256 +1,256 @@ -/* - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -#include "AP_ADSB_uAvionix_MAVLink.h" - -#if HAL_ADSB_UAVIONIX_MAVLINK_ENABLED -#include // for sprintf -#include -#include -#include -#include -#include - -#define ADSB_CHAN_TIMEOUT_MS 15000 - - -extern const AP_HAL::HAL& hal; - -// detect if an port is configured as MAVLink -bool AP_ADSB_uAvionix_MAVLink::detect() -{ - // this actually requires SerialProtocol_MAVLink or SerialProtocol_MAVLink2 but - // we can't have a running system with that, so its safe to assume it's already defined - return true; -} - -void AP_ADSB_uAvionix_MAVLink::update() -{ - const uint32_t now = AP_HAL::millis(); - - // send static configuration data to transceiver, every 5s - if (_frontend.out_state.chan_last_ms > 0 && now - _frontend.out_state.chan_last_ms > ADSB_CHAN_TIMEOUT_MS) { - // haven't gotten a heartbeat health status packet in a while, assume hardware failure - // TODO: reset out_state.chan - _frontend.out_state.chan = -1; - gcs().send_text(MAV_SEVERITY_ERROR, "ADSB: Transceiver heartbeat timed out"); - } else if (_frontend.out_state.chan >= 0 && !_frontend._my_loc.is_zero() && _frontend.out_state.chan < MAVLINK_COMM_NUM_BUFFERS) { - const mavlink_channel_t chan = (mavlink_channel_t)(MAVLINK_COMM_0 + _frontend.out_state.chan); - if (now - _frontend.out_state.last_config_ms >= 5000 && HAVE_PAYLOAD_SPACE(chan, UAVIONIX_ADSB_OUT_CFG)) { - _frontend.out_state.last_config_ms = now; - send_configure(chan); - } // last_config_ms - - // send dynamic data to transceiver at 5Hz - if (now - _frontend.out_state.last_report_ms >= 200 && HAVE_PAYLOAD_SPACE(chan, UAVIONIX_ADSB_OUT_DYNAMIC)) { - _frontend.out_state.last_report_ms = now; - send_dynamic_out(chan); - } // last_report_ms - } // chan_last_ms -} - -void AP_ADSB_uAvionix_MAVLink::send_dynamic_out(const mavlink_channel_t chan) const -{ - const AP_GPS &gps = AP::gps(); - const Vector3f &gps_velocity = gps.velocity(); - - const int32_t latitude = _frontend._my_loc.lat; - const int32_t longitude = _frontend._my_loc.lng; - const int32_t altGNSS = _frontend._my_loc.alt * 10; // convert cm to mm - const int16_t velVert = -1.0f * gps_velocity.z * 1E2; // convert m/s to cm/s - const int16_t nsVog = gps_velocity.x * 1E2; // convert m/s to cm/s - const int16_t ewVog = gps_velocity.y * 1E2; // convert m/s to cm/s - const uint8_t fixType = gps.status(); // this lines up perfectly with our enum - const uint8_t emStatus = 0; // TODO: implement this ENUM. no emergency = 0 - const uint8_t numSats = gps.num_sats(); - const uint16_t squawk = _frontend.out_state.cfg.squawk_octal; - - uint32_t accHoriz = UINT_MAX; - float accHoriz_f; - if (gps.horizontal_accuracy(accHoriz_f)) { - accHoriz = accHoriz_f * 1E3; // convert m to mm - } - - uint16_t accVert = USHRT_MAX; - float accVert_f; - if (gps.vertical_accuracy(accVert_f)) { - accVert = accVert_f * 1E2; // convert m to cm - } - - uint16_t accVel = USHRT_MAX; - float accVel_f; - if (gps.speed_accuracy(accVel_f)) { - accVel = accVel_f * 1E3; // convert m/s to mm/s - } - - uint16_t state = 0; - if (_frontend.out_state.is_in_auto_mode) { - state |= UAVIONIX_ADSB_OUT_DYNAMIC_STATE_AUTOPILOT_ENABLED; - } - if (!_frontend.out_state.is_flying) { - state |= UAVIONIX_ADSB_OUT_DYNAMIC_STATE_ON_GROUND; - } - - // TODO: confirm this sets utcTime correctly - const uint64_t gps_time = gps.time_epoch_usec(); - const uint32_t utcTime = gps_time / 1000000ULL; - - const AP_Baro &baro = AP::baro(); - int32_t altPres = INT_MAX; - if (baro.healthy()) { - // Altitude difference between sea level pressure and current pressure. Result in millimeters - altPres = baro.get_altitude_difference(SSL_AIR_PRESSURE, baro.get_pressure()) * 1E3; // convert m to mm; - } - - - - mavlink_msg_uavionix_adsb_out_dynamic_send( - chan, - utcTime, - latitude, - longitude, - altGNSS, - fixType, - numSats, - altPres, - accHoriz, - accVert, - accVel, - velVert, - nsVog, - ewVog, - emStatus, - state, - squawk); -} - - -/* - * To expand functionality in their HW, uAvionix has extended a few of the unused MAVLink bits to pack in more new features - * This function will override the MSB byte of the 24bit ICAO address. To ensure an invalid >24bit ICAO is never broadcasted, - * this function is used to create the encoded verison without ever writing to the actual ICAO number. It's created on-demand - */ -uint32_t AP_ADSB_uAvionix_MAVLink::encode_icao(const uint32_t icao_id) const -{ - // utilize the upper unused 8bits of the icao with special flags. - // This encoding is required for uAvionix devices that break the MAVLink spec. - - // ensure the user assignable icao is 24 bits - uint32_t encoded_icao = icao_id & 0x00FFFFFF; - - encoded_icao &= ~0x20000000; // useGnssAltitude should always be FALSE - encoded_icao |= 0x10000000; // csidLogic should always be TRUE - - //SIL/SDA are special fields that should be set to 0 with only expert user adjustment - encoded_icao &= ~0x03000000; // SDA should always be FALSE - encoded_icao &= ~0x0C000000; // SIL should always be FALSE - - return encoded_icao; -} - -/* - * To expand functionality in their HW, uAvionix has extended a few of the unused MAVLink bits to pack in more new features - * This function will override the usually-null ending char of the callsign. It always encodes the last byte [8], even if - * the callsign string is less than 9 chars and there are other zero-padded nulls. - */ -uint8_t AP_ADSB_uAvionix_MAVLink::get_encoded_callsign_null_char() -{ -// Encoding of the 8bit null char -// (LSB) - knots -// bit.1 - knots -// bit.2 - knots -// bit.3 - (unused) -// bit.4 - flag - ADSB_BITBASK_RF_CAPABILITIES_1090ES_IN -// bit.5 - flag - ADSB_BITBASK_RF_CAPABILITIES_UAT_IN -// bit.6 - flag - 0 = callsign is treated as callsign, 1 = callsign is treated as flightPlanID/Squawk -// (MSB) - (unused) - - uint8_t encoded_null = 0; - - if (_frontend.out_state.cfg.maxAircraftSpeed_knots <= 0) { - // not set or unknown. no bits set - } else if (_frontend.out_state.cfg.maxAircraftSpeed_knots <= 75) { - encoded_null |= 0x01; - } else if (_frontend.out_state.cfg.maxAircraftSpeed_knots <= 150) { - encoded_null |= 0x02; - } else if (_frontend.out_state.cfg.maxAircraftSpeed_knots <= 300) { - encoded_null |= 0x03; - } else if (_frontend.out_state.cfg.maxAircraftSpeed_knots <= 600) { - encoded_null |= 0x04; - } else if (_frontend.out_state.cfg.maxAircraftSpeed_knots <= 1200) { - encoded_null |= 0x05; - } else { - encoded_null |= 0x06; - } - - - if (_frontend.out_state.cfg.rf_capable & ADSB_BITBASK_RF_CAPABILITIES_1090ES_IN) { - encoded_null |= 0x10; - } - if (_frontend.out_state.cfg.rf_capable & ADSB_BITBASK_RF_CAPABILITIES_UAT_IN) { - encoded_null |= 0x20; - } - - - /* - If the user has an 8 digit flightPlanID assigned from a filed flight plan, this should be assigned to FlightPlanID, (assigned by remote app) - else if the user has an assigned squawk code from ATC this should be converted from 4 digit octal to 4 character alpha string and assigned to FlightPlanID, - else if a tail number is known it should be set to the tail number of the aircraft, (assigned by remote app) - else it should be left blank (all 0's) - */ - - // using the above logic, we must always assign the squawk. once we get configured - // externally then get_encoded_callsign_null_char() stops getting called - snprintf(_frontend.out_state.cfg.callsign, 5, "%04d", unsigned(_frontend.out_state.cfg.squawk_octal) & 0x1FFF); - memset(&_frontend.out_state.cfg.callsign[4], 0, 5); // clear remaining 5 chars - encoded_null |= 0x40; - - return encoded_null; -} - -/* - * populate and send MAVLINK_MSG_UAVIONIX_ADSB_OUT_CFG - */ -void AP_ADSB_uAvionix_MAVLink::send_configure(const mavlink_channel_t chan) -{ - // MAVLink spec says the 9 byte callsign field is 8 byte string with 9th byte as null. - // Here we temporarily set some flags in that null char to signify the callsign - // may be a flightplanID instead - int8_t callsign[sizeof(_frontend.out_state.cfg.callsign)]; - uint32_t icao; - - memcpy(callsign, _frontend.out_state.cfg.callsign, sizeof(_frontend.out_state.cfg.callsign)); - - if (_frontend.out_state.cfg.was_set_externally) { - // take values as-is - icao = _frontend.out_state.cfg.ICAO_id; - } else { - callsign[MAVLINK_MSG_UAVIONIX_ADSB_OUT_CFG_FIELD_CALLSIGN_LEN-1] = (int8_t)get_encoded_callsign_null_char(); - icao = encode_icao((uint32_t)_frontend.out_state.cfg.ICAO_id); - } - - mavlink_msg_uavionix_adsb_out_cfg_send( - chan, - icao, - (const char*)callsign, - (uint8_t)_frontend.out_state.cfg.emitterType, - (uint8_t)_frontend.out_state.cfg.lengthWidth, - (uint8_t)_frontend.out_state.cfg.gpsOffsetLat, - (uint8_t)_frontend.out_state.cfg.gpsOffsetLon, - _frontend.out_state.cfg.stall_speed_cm, - (uint8_t)_frontend.out_state.cfg.rfSelect); -} - -#endif // HAL_ADSB_ENABLED +/* + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include "AP_ADSB_uAvionix_MAVLink.h" + +#if HAL_ADSB_UAVIONIX_MAVLINK_ENABLED +#include // for sprintf +#include +#include +#include +#include +#include + +#define ADSB_CHAN_TIMEOUT_MS 15000 + + +extern const AP_HAL::HAL& hal; + +// detect if an port is configured as MAVLink +bool AP_ADSB_uAvionix_MAVLink::detect() +{ + // this actually requires SerialProtocol_MAVLink or SerialProtocol_MAVLink2 but + // we can't have a running system with that, so its safe to assume it's already defined + return true; +} + +void AP_ADSB_uAvionix_MAVLink::update() +{ + const uint32_t now = AP_HAL::millis(); + + // send static configuration data to transceiver, every 5s + if (_frontend.out_state.chan_last_ms > 0 && now - _frontend.out_state.chan_last_ms > ADSB_CHAN_TIMEOUT_MS) { + // haven't gotten a heartbeat health status packet in a while, assume hardware failure + // TODO: reset out_state.chan + _frontend.out_state.chan = -1; + gcs().send_text(MAV_SEVERITY_ERROR, "ADSB: Transceiver heartbeat timed out"); + } else if (_frontend.out_state.chan >= 0 && !_frontend._my_loc.is_zero() && _frontend.out_state.chan < MAVLINK_COMM_NUM_BUFFERS) { + const mavlink_channel_t chan = (mavlink_channel_t)(MAVLINK_COMM_0 + _frontend.out_state.chan); + if (now - _frontend.out_state.last_config_ms >= 5000 && HAVE_PAYLOAD_SPACE(chan, UAVIONIX_ADSB_OUT_CFG)) { + _frontend.out_state.last_config_ms = now; + send_configure(chan); + } // last_config_ms + + // send dynamic data to transceiver at 5Hz + if (now - _frontend.out_state.last_report_ms >= 200 && HAVE_PAYLOAD_SPACE(chan, UAVIONIX_ADSB_OUT_DYNAMIC)) { + _frontend.out_state.last_report_ms = now; + send_dynamic_out(chan); + } // last_report_ms + } // chan_last_ms +} + +void AP_ADSB_uAvionix_MAVLink::send_dynamic_out(const mavlink_channel_t chan) const +{ + const AP_GPS &gps = AP::gps(); + const Vector3f &gps_velocity = gps.velocity(); + + const int32_t latitude = _frontend._my_loc.lat; + const int32_t longitude = _frontend._my_loc.lng; + const int32_t altGNSS = _frontend._my_loc.alt * 10; // convert cm to mm + const int16_t velVert = -1.0f * gps_velocity.z * 1E2; // convert m/s to cm/s + const int16_t nsVog = gps_velocity.x * 1E2; // convert m/s to cm/s + const int16_t ewVog = gps_velocity.y * 1E2; // convert m/s to cm/s + const uint8_t fixType = gps.status(); // this lines up perfectly with our enum + const uint8_t emStatus = 0; // TODO: implement this ENUM. no emergency = 0 + const uint8_t numSats = gps.num_sats(); + const uint16_t squawk = _frontend.out_state.cfg.squawk_octal; + + uint32_t accHoriz = UINT_MAX; + float accHoriz_f; + if (gps.horizontal_accuracy(accHoriz_f)) { + accHoriz = accHoriz_f * 1E3; // convert m to mm + } + + uint16_t accVert = USHRT_MAX; + float accVert_f; + if (gps.vertical_accuracy(accVert_f)) { + accVert = accVert_f * 1E2; // convert m to cm + } + + uint16_t accVel = USHRT_MAX; + float accVel_f; + if (gps.speed_accuracy(accVel_f)) { + accVel = accVel_f * 1E3; // convert m/s to mm/s + } + + uint16_t state = 0; + if (_frontend.out_state.is_in_auto_mode) { + state |= UAVIONIX_ADSB_OUT_DYNAMIC_STATE_AUTOPILOT_ENABLED; + } + if (!_frontend.out_state.is_flying) { + state |= UAVIONIX_ADSB_OUT_DYNAMIC_STATE_ON_GROUND; + } + + // TODO: confirm this sets utcTime correctly + const uint64_t gps_time = gps.time_epoch_usec(); + const uint32_t utcTime = gps_time / 1000000ULL; + + const AP_Baro &baro = AP::baro(); + int32_t altPres = INT_MAX; + if (baro.healthy()) { + // Altitude difference between sea level pressure and current pressure. Result in millimeters + altPres = baro.get_altitude_difference(SSL_AIR_PRESSURE, baro.get_pressure()) * 1E3; // convert m to mm; + } + + + + mavlink_msg_uavionix_adsb_out_dynamic_send( + chan, + utcTime, + latitude, + longitude, + altGNSS, + fixType, + numSats, + altPres, + accHoriz, + accVert, + accVel, + velVert, + nsVog, + ewVog, + emStatus, + state, + squawk); +} + + +/* + * To expand functionality in their HW, uAvionix has extended a few of the unused MAVLink bits to pack in more new features + * This function will override the MSB byte of the 24bit ICAO address. To ensure an invalid >24bit ICAO is never broadcasted, + * this function is used to create the encoded verison without ever writing to the actual ICAO number. It's created on-demand + */ +uint32_t AP_ADSB_uAvionix_MAVLink::encode_icao(const uint32_t icao_id) const +{ + // utilize the upper unused 8bits of the icao with special flags. + // This encoding is required for uAvionix devices that break the MAVLink spec. + + // ensure the user assignable icao is 24 bits + uint32_t encoded_icao = icao_id & 0x00FFFFFF; + + encoded_icao &= ~0x20000000; // useGnssAltitude should always be FALSE + encoded_icao |= 0x10000000; // csidLogic should always be TRUE + + //SIL/SDA are special fields that should be set to 0 with only expert user adjustment + encoded_icao &= ~0x03000000; // SDA should always be FALSE + encoded_icao &= ~0x0C000000; // SIL should always be FALSE + + return encoded_icao; +} + +/* + * To expand functionality in their HW, uAvionix has extended a few of the unused MAVLink bits to pack in more new features + * This function will override the usually-null ending char of the callsign. It always encodes the last byte [8], even if + * the callsign string is less than 9 chars and there are other zero-padded nulls. + */ +uint8_t AP_ADSB_uAvionix_MAVLink::get_encoded_callsign_null_char() +{ +// Encoding of the 8bit null char +// (LSB) - knots +// bit.1 - knots +// bit.2 - knots +// bit.3 - (unused) +// bit.4 - flag - ADSB_BITBASK_RF_CAPABILITIES_1090ES_IN +// bit.5 - flag - ADSB_BITBASK_RF_CAPABILITIES_UAT_IN +// bit.6 - flag - 0 = callsign is treated as callsign, 1 = callsign is treated as flightPlanID/Squawk +// (MSB) - (unused) + + uint8_t encoded_null = 0; + + if (_frontend.out_state.cfg.maxAircraftSpeed_knots <= 0) { + // not set or unknown. no bits set + } else if (_frontend.out_state.cfg.maxAircraftSpeed_knots <= 75) { + encoded_null |= 0x01; + } else if (_frontend.out_state.cfg.maxAircraftSpeed_knots <= 150) { + encoded_null |= 0x02; + } else if (_frontend.out_state.cfg.maxAircraftSpeed_knots <= 300) { + encoded_null |= 0x03; + } else if (_frontend.out_state.cfg.maxAircraftSpeed_knots <= 600) { + encoded_null |= 0x04; + } else if (_frontend.out_state.cfg.maxAircraftSpeed_knots <= 1200) { + encoded_null |= 0x05; + } else { + encoded_null |= 0x06; + } + + + if (_frontend.out_state.cfg.rf_capable & ADSB_BITBASK_RF_CAPABILITIES_1090ES_IN) { + encoded_null |= 0x10; + } + if (_frontend.out_state.cfg.rf_capable & ADSB_BITBASK_RF_CAPABILITIES_UAT_IN) { + encoded_null |= 0x20; + } + + + /* + If the user has an 8 digit flightPlanID assigned from a filed flight plan, this should be assigned to FlightPlanID, (assigned by remote app) + else if the user has an assigned squawk code from ATC this should be converted from 4 digit octal to 4 character alpha string and assigned to FlightPlanID, + else if a tail number is known it should be set to the tail number of the aircraft, (assigned by remote app) + else it should be left blank (all 0's) + */ + + // using the above logic, we must always assign the squawk. once we get configured + // externally then get_encoded_callsign_null_char() stops getting called + snprintf(_frontend.out_state.cfg.callsign, 5, "%04d", unsigned(_frontend.out_state.cfg.squawk_octal) & 0x1FFF); + memset(&_frontend.out_state.cfg.callsign[4], 0, 5); // clear remaining 5 chars + encoded_null |= 0x40; + + return encoded_null; +} + +/* + * populate and send MAVLINK_MSG_UAVIONIX_ADSB_OUT_CFG + */ +void AP_ADSB_uAvionix_MAVLink::send_configure(const mavlink_channel_t chan) +{ + // MAVLink spec says the 9 byte callsign field is 8 byte string with 9th byte as null. + // Here we temporarily set some flags in that null char to signify the callsign + // may be a flightplanID instead + int8_t callsign[sizeof(_frontend.out_state.cfg.callsign)]; + uint32_t icao; + + memcpy(callsign, _frontend.out_state.cfg.callsign, sizeof(_frontend.out_state.cfg.callsign)); + + if (_frontend.out_state.cfg.was_set_externally) { + // take values as-is + icao = _frontend.out_state.cfg.ICAO_id; + } else { + callsign[MAVLINK_MSG_UAVIONIX_ADSB_OUT_CFG_FIELD_CALLSIGN_LEN-1] = (int8_t)get_encoded_callsign_null_char(); + icao = encode_icao((uint32_t)_frontend.out_state.cfg.ICAO_id); + } + + mavlink_msg_uavionix_adsb_out_cfg_send( + chan, + icao, + (const char*)callsign, + (uint8_t)_frontend.out_state.cfg.emitterType, + (uint8_t)_frontend.out_state.cfg.lengthWidth, + (uint8_t)_frontend.out_state.cfg.gpsOffsetLat, + (uint8_t)_frontend.out_state.cfg.gpsOffsetLon, + _frontend.out_state.cfg.stall_speed_cm, + (uint8_t)_frontend.out_state.cfg.rfSelect); +} + +#endif // HAL_ADSB_ENABLED diff --git a/libraries/AP_ADSB/AP_ADSB_uAvionix_MAVLink.h b/libraries/AP_ADSB/AP_ADSB_uAvionix_MAVLink.h index 6e1f699af6..59ec7f3d7e 100644 --- a/libraries/AP_ADSB/AP_ADSB_uAvionix_MAVLink.h +++ b/libraries/AP_ADSB/AP_ADSB_uAvionix_MAVLink.h @@ -1,44 +1,44 @@ -#pragma once - -/* - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -#include "AP_ADSB_Backend.h" - -#ifndef HAL_ADSB_UAVIONIX_MAVLINK_ENABLED -#define HAL_ADSB_UAVIONIX_MAVLINK_ENABLED HAL_ADSB_ENABLED -#endif - -#if HAL_ADSB_UAVIONIX_MAVLINK_ENABLED -class AP_ADSB_uAvionix_MAVLink : public AP_ADSB_Backend { -public: - using AP_ADSB_Backend::AP_ADSB_Backend; - - void update() override; - - // static detection function - static bool detect(); - -private: - // send static and dynamic data to ADSB transceiver - void send_configure(const mavlink_channel_t chan); - void send_dynamic_out(const mavlink_channel_t chan) const; - - // special helpers for uAvionix workarounds - uint32_t encode_icao(const uint32_t icao_id) const; - uint8_t get_encoded_callsign_null_char(void); -}; -#endif // HAL_ADSB_ENABLED - +#pragma once + +/* + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include "AP_ADSB_Backend.h" + +#ifndef HAL_ADSB_UAVIONIX_MAVLINK_ENABLED +#define HAL_ADSB_UAVIONIX_MAVLINK_ENABLED HAL_ADSB_ENABLED +#endif + +#if HAL_ADSB_UAVIONIX_MAVLINK_ENABLED +class AP_ADSB_uAvionix_MAVLink : public AP_ADSB_Backend { +public: + using AP_ADSB_Backend::AP_ADSB_Backend; + + void update() override; + + // static detection function + static bool detect(); + +private: + // send static and dynamic data to ADSB transceiver + void send_configure(const mavlink_channel_t chan); + void send_dynamic_out(const mavlink_channel_t chan) const; + + // special helpers for uAvionix workarounds + uint32_t encode_icao(const uint32_t icao_id) const; + uint8_t get_encoded_callsign_null_char(void); +}; +#endif // HAL_ADSB_ENABLED +