From 1ad092f341c94aebd1efa4fb29a8d603612f8bc8 Mon Sep 17 00:00:00 2001 From: Sandbox <143267209+Win-Sandbox@users.noreply.github.com> Date: Mon, 23 Feb 2026 04:29:20 +0800 Subject: [PATCH] ACARS Rx App using libacars (#3029) * Using same decode as libacrars * Enable ACARS RX * indent + fix warning * fixed CRC calculations, length validation, message print Co-authored-by: gullradriel --- .../external/acars_rx/acars_app.cpp | 77 +++++++- .../external/acars_rx/acars_app.hpp | 22 ++- .../application/external/acars_rx/main.cpp | 166 +++++++++--------- firmware/application/external/external.cmake | 6 +- 4 files changed, 180 insertions(+), 91 deletions(-) diff --git a/firmware/application/external/acars_rx/acars_app.cpp b/firmware/application/external/acars_rx/acars_app.cpp index d7b5079d0..c88307198 100644 --- a/firmware/application/external/acars_rx/acars_app.cpp +++ b/firmware/application/external/acars_rx/acars_app.cpp @@ -33,6 +33,76 @@ using namespace portapack; namespace ui::external_app::acars_rx { +// ACARS frame field layout (0-based byte offsets): +// [0] SOH framing byte (skipped) +// [1..7] Aircraft registration (7 chars) +// [8] STX framing byte (skipped) +// [9..10] Label (2 chars) +// [11] Block ID (1 char) +// [12..14] Message number (3 chars) +// [15..20] Flight ID (6 chars) +// [21..N-3] Free-text payload (variable) +// [N-2..N-1] CRC-16/CCITT (2 bytes, MSB first, NOT part of payload) +// +// Minimum valid frame: 21-byte fixed header + 2 CRC bytes = 23 bytes. +// (A zero-length payload is legal per spec; we require at least 23 bytes.) +static constexpr std::string::size_type kAcarsCrcLen = 2; +static constexpr std::string::size_type kAcarsHeaderLen = 21; +static constexpr std::string::size_type kAcarsMinLen = kAcarsHeaderLen + kAcarsCrcLen; + +// CRC-16/CCITT: poly 0x1021, init 0x0000, no reflection, no final XOR. +// This is the variant used by ACARS (same as XMODEM CRC). +static uint16_t acars_crc16(const std::string& data, std::string::size_type len) { + uint16_t crc = 0x0000; + for (std::string::size_type i = 0; i < len; ++i) { + crc ^= static_cast(static_cast(data[i])) << 8; + for (int bit = 0; bit < 8; ++bit) { + if (crc & 0x8000) + crc = static_cast((crc << 1) ^ 0x1021); + else + crc = static_cast(crc << 1); + } + } + return crc; +} + +AcarsDecoded acars_decode(const std::string& raw) { + AcarsDecoded result; + if (raw.size() < kAcarsMinLen) { + result.txt = "ACARS message too short (" + std::to_string(raw.size()) + + " bytes, need " + std::to_string(kAcarsMinLen) + ")"; + return result; + } + + // Verify CRC: computed over everything except the last 2 bytes, + // then compared against those 2 bytes (MSB first). + const std::string::size_type payload_len = raw.size() - kAcarsCrcLen; + const uint16_t computed = acars_crc16(raw, payload_len); + const uint16_t received = (static_cast(static_cast(raw[raw.size() - 2])) << 8) | + static_cast(static_cast(raw[raw.size() - 1])); + result.crc_ok = (computed == received); + + result.reg = raw.substr(1, 7); + result.label = raw.substr(9, 2); + result.block_id = raw[11]; + result.msg_num = raw.substr(12, 3); + result.flight_id = raw.substr(15, 6); + // Payload sits between end of fixed header and the 2 trailing CRC bytes. + if (payload_len > kAcarsHeaderLen) + result.txt = raw.substr(kAcarsHeaderLen, payload_len - kAcarsHeaderLen); + return result; +} + +std::string acars_format(const AcarsDecoded& msg) { + return std::string("ACARS Decoded Result\nCRC: ") + (msg.crc_ok ? "OK" : "FAIL") + + "\nRegistration: " + msg.reg + + "\nLabel: " + msg.label + + "\nBlockID: " + msg.block_id + + "\nMsgNum: " + msg.msg_num + + "\nFlightID: " + msg.flight_id + + "\nMessage: " + msg.txt; +} + void ACARSLogger::log_str(std::string msg) { log_file.write_entry(msg); } @@ -77,17 +147,16 @@ void ACARSAppView::focus() { void ACARSAppView::on_packet(const ACARSPacketMessage* packet) { std::string console_info; - if (packet->state == 255) { // got a packet, parse it, and display rtc::RTC datetime; rtc_time::now(datetime); - // todo parity error recovery console_info = to_string_datetime(datetime, HMS); console_info += ": "; - console_info += packet->message; + std::string message{packet->message, packet->message + packet->msg_len}; + AcarsDecoded decoded = acars_decode(message); + console_info += acars_format(decoded); console.writeln(console_info); - // Log raw data whatever it contains if (logger && logging) logger->log_str(console_info); } else { diff --git a/firmware/application/external/acars_rx/acars_app.hpp b/firmware/application/external/acars_rx/acars_app.hpp index 8e5175ce3..b8889cc8b 100644 --- a/firmware/application/external/acars_rx/acars_app.hpp +++ b/firmware/application/external/acars_rx/acars_app.hpp @@ -33,6 +33,26 @@ namespace ui::external_app::acars_rx { +// Decoded ACARS message fields extracted from a raw frame. +// CRC-16/CCITT (poly 0x1021, init 0x0000) is verified against the two +// trailing bytes of the raw frame; crc_ok reflects that result. +struct AcarsDecoded { + bool crc_ok{false}; + std::string reg{}; + std::string label{}; + std::string flight_id{}; + std::string msg_num{}; + char block_id{'\0'}; + std::string txt{}; +}; + +// Decode a raw ACARS frame: verify CRC-16/CCITT and extract fixed-offset fields. +// Returns a partially-filled AcarsDecoded (txt error only) if the frame is too short. +AcarsDecoded acars_decode(const std::string& raw); + +// Format a decoded ACARS message for display or logging. +std::string acars_format(const AcarsDecoded& msg); + class ACARSLogger { public: Optional append(const std::filesystem::path& filename) { @@ -106,4 +126,4 @@ class ACARSAppView : public View { } // namespace ui::external_app::acars_rx -#endif /*__ACARS_APP_H__*/ \ No newline at end of file +#endif /*__ACARS_APP_H__*/ diff --git a/firmware/application/external/acars_rx/main.cpp b/firmware/application/external/acars_rx/main.cpp index d11adcb8f..0d2af54fe 100644 --- a/firmware/application/external/acars_rx/main.cpp +++ b/firmware/application/external/acars_rx/main.cpp @@ -1,83 +1,83 @@ -/* - * Copyright (C) 2023 Bernd Herzog - * - * This file is part of PortaPack. - * - * 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 2, 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; see the file COPYING. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, - * Boston, MA 02110-1301, USA. - */ - -#include "ui.hpp" -#include "acars_app.hpp" -#include "ui_navigation.hpp" -#include "external_app.hpp" - -namespace ui::external_app::acars_rx { -void initialize_app(ui::NavigationView& nav) { - nav.push(); -} -} // namespace ui::external_app::acars_rx - -extern "C" { - -__attribute__((section(".external_app.app_acars_rx.application_information"), used)) application_information_t _application_information_acars_rx = { - /*.memory_location = */ (uint8_t*)0x00000000, - /*.externalAppEntry = */ ui::external_app::acars_rx::initialize_app, - /*.header_version = */ CURRENT_HEADER_VERSION, - /*.app_version = */ VERSION_MD5, - - /*.app_name = */ "ACARS", - /*.bitmap_data = */ { - 0x80, - 0x01, - 0xC0, - 0x03, - 0xC0, - 0x03, - 0xC0, - 0x03, - 0xC0, - 0x03, - 0xE0, - 0x07, - 0xF8, - 0x1F, - 0xFE, - 0x7F, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xC0, - 0x03, - 0xC0, - 0x03, - 0xC0, - 0x03, - 0xE0, - 0x07, - 0xF0, - 0x0F, - 0xF8, - 0x1F, - }, - /*.icon_color = */ ui::Color::orange().v, - /*.menu_location = */ app_location_t::RX, - /*.desired_menu_position = */ -1, - - /*.m4_app_tag = portapack::spi_flash::image_tag_acars */ {'P', 'A', 'C', 'A'}, - /*.m4_app_offset = */ 0x00000000, // will be filled at compile time -}; -} +/* + * Copyright (C) 2023 Bernd Herzog + * + * This file is part of PortaPack. + * + * 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 2, 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; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#include "ui.hpp" +#include "acars_app.hpp" +#include "ui_navigation.hpp" +#include "external_app.hpp" + +namespace ui::external_app::acars_rx { +void initialize_app(ui::NavigationView& nav) { + nav.push(); +} +} // namespace ui::external_app::acars_rx + +extern "C" { + +__attribute__((section(".external_app.app_acars_rx.application_information"), used)) application_information_t _application_information_acars_rx = { + /*.memory_location = */ (uint8_t*)0x00000000, + /*.externalAppEntry = */ ui::external_app::acars_rx::initialize_app, + /*.header_version = */ CURRENT_HEADER_VERSION, + /*.app_version = */ VERSION_MD5, + + /*.app_name = */ "ACARS", + /*.bitmap_data = */ { + 0x80, + 0x01, + 0xC0, + 0x03, + 0xC0, + 0x03, + 0xC0, + 0x03, + 0xC0, + 0x03, + 0xE0, + 0x07, + 0xF8, + 0x1F, + 0xFE, + 0x7F, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xC0, + 0x03, + 0xC0, + 0x03, + 0xC0, + 0x03, + 0xE0, + 0x07, + 0xF0, + 0x0F, + 0xF8, + 0x1F, + }, + /*.icon_color = */ ui::Color::orange().v, + /*.menu_location = */ app_location_t::RX, + /*.desired_menu_position = */ -1, + + /*.m4_app_tag = portapack::spi_flash::image_tag_acars */ {'P', 'A', 'C', 'A'}, + /*.m4_app_offset = */ 0x00000000, // will be filled at compile time +}; +} diff --git a/firmware/application/external/external.cmake b/firmware/application/external/external.cmake index f79692436..19cda4d2d 100644 --- a/firmware/application/external/external.cmake +++ b/firmware/application/external/external.cmake @@ -113,8 +113,8 @@ set(EXTCPPSRC external/random_password/sha512.cpp #acars - #external/acars_rx/main.cpp - #external/acars_rx/acars_app.cpp + external/acars_rx/main.cpp + external/acars_rx/acars_app.cpp #wefax_rx 192 bytes external/wefax_rx/main.cpp @@ -337,7 +337,7 @@ set(EXTAPPLIST sstvtx sstvrx random_password - # acars_rx --not working + acars_rx wefax_rx noaaapt_rx shoppingcart_lock