From 9e694ce8367796ccdb4a3a91d1fed943af18de5d Mon Sep 17 00:00:00 2001 From: Jared Boone Date: Fri, 6 Nov 2015 13:53:04 -0800 Subject: [PATCH] AIS decoding and really bad UI. The decoder needs a serious refactoring/decoupling. The UI just dumps bits of the received packets into a console window, whcih scrolls too quickly in an AIS-dense area with a good antenna. --- firmware/application/Makefile | 1 + firmware/application/ui_receiver.cpp | 12 +- firmware/application/ui_receiver.hpp | 3 + firmware/common/ais_baseband.cpp | 371 +++++++++++++++++++++++++++ firmware/common/ais_baseband.hpp | 149 +---------- 5 files changed, 388 insertions(+), 148 deletions(-) create mode 100644 firmware/common/ais_baseband.cpp diff --git a/firmware/application/Makefile b/firmware/application/Makefile index 8b9b5680d..c2b9237e5 100755 --- a/firmware/application/Makefile +++ b/firmware/application/Makefile @@ -167,6 +167,7 @@ CPPSRC = main.cpp \ ui_spectrum.cpp \ receiver_model.cpp \ spectrum_color_lut.cpp \ + ais_baseband.cpp \ ../common/utility.cpp \ ../common/chibios_cpp.cpp \ ../common/debug.cpp \ diff --git a/firmware/application/ui_receiver.cpp b/firmware/application/ui_receiver.cpp index 53ee51316..7f6ff3cf2 100644 --- a/firmware/application/ui_receiver.cpp +++ b/firmware/application/ui_receiver.cpp @@ -27,6 +27,8 @@ #include "portapack.hpp" using namespace portapack; +#include "ais_baseband.hpp" + namespace ui { /* BasebandBandwidthField ************************************************/ @@ -496,8 +498,8 @@ ReceiverView::~ReceiverView() { void ReceiverView::on_show() { context().message_map.register_handler(Message::ID::FSKPacket, [this](Message* const p) { - auto message = static_cast(p); - (void)message; + const auto message = static_cast(p); + this->on_packet_ais(*message); } ); } @@ -506,7 +508,13 @@ void ReceiverView::on_hide() { context().message_map.unregister_handler(Message::ID::FSKPacket); } +void ReceiverView::on_packet_ais(const FSKPacketMessage& message) { + const auto result = baseband::ais::packet_decode(message.packet.payload, message.packet.bits_received); + auto console = reinterpret_cast(widget_content.get()); + if( result.first == "OK" ) { + console->writeln(result.second); + } } void ReceiverView::focus() { diff --git a/firmware/application/ui_receiver.hpp b/firmware/application/ui_receiver.hpp index 7af05feec..b3f8ff460 100644 --- a/firmware/application/ui_receiver.hpp +++ b/firmware/application/ui_receiver.hpp @@ -415,6 +415,7 @@ private: { " AM ", 0 }, { "NFM ", 1 }, { "WFM ", 2 }, + { "AIS ", 3 }, { "SPEC", 4 }, } }; @@ -470,6 +471,8 @@ private: void on_headphone_volume_changed(int32_t v); // void on_baseband_oversampling_changed(int32_t v); void on_edit_frequency(); + + void on_packet_ais(const FSKPacketMessage& message); }; } /* namespace ui */ diff --git a/firmware/common/ais_baseband.cpp b/firmware/common/ais_baseband.cpp new file mode 100644 index 000000000..5d37b00d2 --- /dev/null +++ b/firmware/common/ais_baseband.cpp @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * + * 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 "ais_baseband.hpp" + +#include +#include +#include +#include + +#include "crc.hpp" + +// TODO: Move string formatting elsewhere!!! +#include "ui_widget.hpp" + +namespace baseband { + +template +class FieldReader { +public: + constexpr FieldReader( + const T& data + ) : data { data } + { + } + + uint32_t read(const size_t start_bit, const size_t length) const { + uint32_t value = 0; + for(size_t i=start_bit; i<(start_bit + length); i++) { + value = (value << 1) | data[bit_remap(i)]; + } + return value; + } + +private: + const T& data; + const BitRemap bit_remap { }; +}; + +namespace ais { + +struct BitRemap { + size_t operator()(const size_t bit_index) const { + return bit_index ^ 7; + } +}; + +struct CRCBitRemap { + size_t operator()(const size_t bit_index) const { + return bit_index; + } +}; + +using FieldReader = baseband::FieldReader, BitRemap>; +using CRCFieldReader = baseband::FieldReader, CRCBitRemap>; + +struct PacketLengthRange { + constexpr PacketLengthRange( + ) : min_bytes { 0 }, + max_bytes { 0 } + { + } + + constexpr PacketLengthRange( + const uint16_t min_bits, + const uint16_t max_bits + ) : min_bytes { static_cast(min_bits / 8U) }, + max_bytes { static_cast(max_bits / 8U) } + { + // static_assert((min_bits & 7) == 0, "minimum bits not a multiple of 8"); + // static_assert((max_bits & 7) == 0, "minimum bits not a multiple of 8"); + } + + bool contains(const size_t bit_count) const { + return !is_above(bit_count) && !is_below(bit_count); + } + + bool is_above(const size_t bit_count) const { + return (min() > bit_count); + } + + bool is_below(const size_t bit_count) const { + return (max() < bit_count); + } + + size_t min() const { + return min_bytes * 8; + } + + size_t max() const { + return max_bytes * 8; + } + +private: + const uint8_t min_bytes; + const uint8_t max_bytes; +}; + +static constexpr std::array packet_length_range { { + { 0, 0 }, // 0 + { 168, 168 }, // 1 + { 168, 168 }, // 2 + { 168, 168 }, // 3 + { 168, 168 }, // 4 + { 424, 424 }, // 5 + { 0, 0 }, // 6 + { 0, 0 }, // 7 + { 0, 1008 }, // 8 + { 0, 0 }, // 9 + { 0, 0 }, // 10 + { 0, 0 }, // 11 + { 0, 0 }, // 12 + { 0, 0 }, // 13 + { 0, 0 }, // 14 + { 0, 0 }, // 15 + { 0, 0 }, // 16 + { 0, 0 }, // 17 + { 168, 168 }, // 18 + { 0, 0 }, // 19 + { 72, 160 }, // 20 + { 272, 360 }, // 21 + { 168, 168 }, // 22 + { 160, 160 }, // 23 + { 160, 168 }, // 24 + { 0, 168 }, // 25 + { 0, 0 }, // 26 + { 0, 0 }, // 27 + { 0, 0 }, // 28 + { 0, 0 }, // 29 + { 0, 0 }, // 30 + { 0, 0 }, // 31 +} }; + +struct PacketLengthValidator { + bool operator()(const uint_fast8_t message_id, const size_t length) { + return packet_length_range[message_id].contains(length); + } +}; + +struct PacketTooLong { + bool operator()(const uint_fast8_t message_id, const size_t length) { + return packet_length_range[message_id].is_below(length); + } +}; + +struct CRCCheck { + bool operator()(const std::bitset<1024>& payload, const size_t data_length) { + CRCFieldReader field_crc { payload }; + CRC ais_fcs { 0x1021 }; + + uint16_t crc_calculated = 0xffff; + + for(size_t i=0; i(field.read(start_bit, 27) << 5) / 32; +} + +static int32_t ais_longitude_normalized( + const FieldReader& field, + const size_t start_bit +) { + // Shifting and dividing is to sign-extend the source field. + // TODO: There's probably a more elegant way to do it. + return static_cast(field.read(start_bit, 28) << 4) / 16; +} + +static std::string ais_format_latlon_normalized(const int32_t normalized) { + const int32_t t = (normalized * 5) / 3; + const int32_t degrees = t / (100 * 10000); + const int32_t fraction = std::abs(t) % (100 * 10000); + return ui::to_string_dec_int(degrees) + "." + ui::to_string_dec_int(fraction, 6, '0'); +} + +static std::string ais_format_latitude( + const FieldReader& field, + const size_t start_bit +) { + const auto value = static_cast(field.read(start_bit, 27) << 5) / 32; + return ais_format_latlon_normalized(value); +} + +static std::string ais_format_longitude( + const FieldReader& field, + const size_t start_bit +) { + const auto value = static_cast(field.read(start_bit, 28) << 4) / 16; + return ais_format_latlon_normalized(value); +} + +struct ais_datetime { + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; +}; + +static ais_datetime ais_get_datetime( + const FieldReader& field, + const size_t start_bit +) { + return { + static_cast(field.read(start_bit + 0, 14)), + static_cast(field.read(start_bit + 14, 4)), + static_cast(field.read(start_bit + 18, 5)), + static_cast(field.read(start_bit + 23, 5)), + static_cast(field.read(start_bit + 28, 6)), + static_cast(field.read(start_bit + 34, 6)), + }; +} + +static std::string ais_format_datetime( + const FieldReader& field, + const size_t start_bit +) { + const auto datetime = ais_get_datetime(field, start_bit); + return ui::to_string_dec_uint(datetime.year, 4) + "/" + + ui::to_string_dec_uint(datetime.month, 2, '0') + "/" + + ui::to_string_dec_uint(datetime.day, 2, '0') + " " + + ui::to_string_dec_uint(datetime.hour, 2, '0') + ":" + + ui::to_string_dec_uint(datetime.minute, 2, '0') + ":" + + ui::to_string_dec_uint(datetime.second, 2, '0'); +} + +static char ais_char_to_ascii(const uint8_t c) { + return (c ^ 32) + 32; +} + +static std::string ais_read_text( + const FieldReader& field, + const size_t start_bit, + const size_t character_count +) { + std::string result; + const size_t character_length = 6; + const size_t end_bit = start_bit + character_count * character_length; + for(size_t i=start_bit; i& data, const size_t data_length) { + if( data_length < 38 ) { + return { "short " + ui::to_string_dec_uint(data_length, 3), "" }; + } + + const size_t extra_bits = data_length & 7; + if( extra_bits != 0 ) { + return { "extra bits " + ui::to_string_dec_uint(data_length, 3), "" }; + } + + FieldReader field { data }; + + const auto message_id = field.read(0, 6); + + const size_t payload_length = data_length - 16; + PacketLengthValidator packet_length_valid; + if( !packet_length_valid(message_id, payload_length) ) { + return { "bad length " + ui::to_string_dec_uint(payload_length, 3), "" }; + } + + CRCCheck crc_ok; + if( !crc_ok(data, payload_length) ) { + return { "crc", "" }; + } + + const auto source_id = field.read(8, 30); + std::string result { ui::to_string_dec_uint(message_id, 2) + " " + ui::to_string_dec_uint(source_id, 10) }; + + switch(message_id) { + case 1: + case 2: + case 3: + { + const auto navigational_status = field.read(38, 4); + result += " " + ais_format_navigational_status(navigational_status); + result += " " + ais_format_latlon_normalized(ais_latitude_normalized(field, 89)); + result += " " + ais_format_latlon_normalized(ais_longitude_normalized(field, 61)); + } + break; + + case 4: + { + result += " " + ais_format_datetime(field, 38); + result += " " + ais_format_latlon_normalized(ais_latitude_normalized(field, 107)); + result += " " + ais_format_latlon_normalized(ais_longitude_normalized(field, 79)); + } + break; + + case 5: + { + const auto call_sign = ais_read_text(field, 70, 7); + const auto name = ais_read_text(field, 112, 20); + const auto destination = ais_read_text(field, 302, 20); + result += " \"" + call_sign + "\" \"" + name + "\" \"" + destination + "\""; + } + break; + + case 21: + { + const auto name = ais_read_text(field, 43, 20); + result += " \"" + name + "\" " + ais_format_latitude(field, 192) + " " + ais_format_longitude(field, 164); + } + break; + + default: + break; + } + + return { "OK", result }; +} + +} /* namespace ais */ +} /* namespace baseband */ diff --git a/firmware/common/ais_baseband.hpp b/firmware/common/ais_baseband.hpp index 945900890..0dac7dd15 100644 --- a/firmware/common/ais_baseband.hpp +++ b/firmware/common/ais_baseband.hpp @@ -22,38 +22,14 @@ #ifndef __AIS_BASEBAND_H__ #define __AIS_BASEBAND_H__ -#include "crc.hpp" - #include #include #include #include #include +#include namespace baseband { - -template -class FieldReader { -public: - constexpr FieldReader( - const T& data - ) : data { data } - { - } - - uint32_t read(const size_t start_bit, const size_t length) const { - uint32_t value = 0; - for(size_t i=start_bit; i<(start_bit + length); i++) { - value = (value << 1) | data[bit_remap(i)]; - } - return value; - } - -private: - const T& data; - const BitRemap bit_remap { }; -}; - namespace ais { // RRC length should be about 4 x the symbol length to do a good job of @@ -209,128 +185,9 @@ constexpr std::array, 128> rrc_taps_128_decim_1_p { { { -1.1001696231e-04f, 4.5570517881e-05f }, { -2.5877077742e-04f, 5.1472707944e-05f }, } }; -struct BitRemap { - size_t operator()(const size_t bit_index) const { - return bit_index ^ 7; - } -}; +using decoded_packet = std::pair; -struct CRCBitRemap { - size_t operator()(const size_t bit_index) const { - return bit_index; - } -}; - -using FieldReader = baseband::FieldReader, BitRemap>; -using CRCFieldReader = baseband::FieldReader, CRCBitRemap>; - -struct PacketLengthRange { - constexpr PacketLengthRange( - ) : min_bytes { 0 }, - max_bytes { 0 } - { - } - - constexpr PacketLengthRange( - const uint16_t min_bits, - const uint16_t max_bits - ) : min_bytes { static_cast(min_bits / 8U) }, - max_bytes { static_cast(max_bits / 8U) } - { - // static_assert((min_bits & 7) == 0, "minimum bits not a multiple of 8"); - // static_assert((max_bits & 7) == 0, "minimum bits not a multiple of 8"); - } - - bool contains(const size_t bit_count) const { - return !is_above(bit_count) && !is_below(bit_count); - } - - bool is_above(const size_t bit_count) const { - return (min() > bit_count); - } - - bool is_below(const size_t bit_count) const { - return (max() < bit_count); - } - - size_t min() const { - return min_bytes * 8; - } - - size_t max() const { - return max_bytes * 8; - } - -private: - const uint8_t min_bytes; - const uint8_t max_bytes; -}; - -constexpr std::array packet_length_range { { - { 0, 0 }, // 0 - { 168, 168 }, // 1 - { 168, 168 }, // 2 - { 168, 168 }, // 3 - { 168, 168 }, // 4 - { 424, 424 }, // 5 - { 0, 0 }, // 6 - { 0, 0 }, // 7 - { 0, 1008 }, // 8 - { 0, 0 }, // 9 - { 0, 0 }, // 10 - { 0, 0 }, // 11 - { 0, 0 }, // 12 - { 0, 0 }, // 13 - { 0, 0 }, // 14 - { 0, 0 }, // 15 - { 0, 0 }, // 16 - { 0, 0 }, // 17 - { 168, 168 }, // 18 - { 0, 0 }, // 19 - { 72, 160 }, // 20 - { 272, 360 }, // 21 - { 168, 168 }, // 22 - { 160, 160 }, // 23 - { 160, 168 }, // 24 - { 0, 168 }, // 25 - { 0, 0 }, // 26 - { 0, 0 }, // 27 - { 0, 0 }, // 28 - { 0, 0 }, // 29 - { 0, 0 }, // 30 - { 0, 0 }, // 31 -} }; - -struct PacketLengthValidator { - bool operator()(const uint_fast8_t message_id, const size_t length) { - return packet_length_range[message_id].contains(length); - } -}; - -struct PacketTooLong { - bool operator()(const uint_fast8_t message_id, const size_t length) { - return packet_length_range[message_id].is_below(length); - } -}; - -struct CRCCheck { - bool operator()(const std::bitset<1024>& payload, const size_t data_length) { - CRCFieldReader field_crc { payload }; - CRC ais_fcs { 0x1021 }; - - uint16_t crc_calculated = 0xffff; - - for(size_t i=0; i& data, const size_t data_length); } /* namespace ais */ } /* namespace baseband */