/* * Copyright (C) 2014 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 "app_ais.hpp" #include "portapack.hpp" using namespace portapack; #include "crc.hpp" // TODO: Move string formatting elsewhere!!! #include "ui_widget.hpp" namespace baseband { namespace ais { struct CRCBitRemap { size_t operator()(const size_t bit_index) const { return bit_index; } }; using CRCFieldReader = ::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 + 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)), }; } Latitude Packet::latitude(const size_t start_bit) const { // 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, 27) << 5) / 32; } Longitude Packet::longitude(const size_t start_bit) const { // 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; } } /* namespace ais */ } /* namespace baseband */ AISModel::AISModel() { receiver_model.set_baseband_configuration({ .mode = 3, .sampling_rate = 2457600, .decimation_factor = 4, }); receiver_model.set_baseband_bandwidth(1750000); log_file.open_for_append("ais.txt"); } bool AISModel::on_packet(const baseband::ais::Packet& packet) { // TODO: Unstuff here, not in baseband! if( !packet.is_valid() ) { return false; } if( log_file.is_ready() ) { std::string entry; entry.reserve((packet.length() + 3) / 4); for(size_t i=0; i= 10) ? ('W' + nibble) : ('0' + nibble); } entry += "\r\n"; log_file.write(entry); } return true; } namespace ui { void AISView::on_show() { Console::on_show(); auto& message_map = context().message_map(); message_map.register_handler(Message::ID::AISPacket, [this](Message* const p) { const auto message = static_cast(p); const baseband::ais::Packet packet { message->packet.payload, message->packet.bits_received }; if( this->model.on_packet(packet) ) { this->log(packet); } } ); } void AISView::on_hide() { auto& message_map = context().message_map(); message_map.unregister_handler(Message::ID::AISPacket); Console::on_hide(); } void AISView::log(const baseband::ais::Packet& packet) { std::string result { ui::to_string_dec_uint(packet.message_id(), 2) + " " + ui::to_string_dec_uint(packet.source_id(), 10) }; switch(packet.message_id()) { case 1: case 2: case 3: { const auto navigational_status = packet.read(38, 4); result += " " + baseband::ais::format_navigational_status(navigational_status); result += " " + baseband::ais::format_latlon_normalized(packet.latitude(89)); result += " " + baseband::ais::format_latlon_normalized(packet.longitude(61)); } break; case 4: { result += " " + baseband::ais::format_datetime(packet.datetime(38)); result += " " + baseband::ais::format_latlon_normalized(packet.latitude(107)); result += " " + baseband::ais::format_latlon_normalized(packet.longitude(79)); } break; case 5: { const auto call_sign = packet.text(70, 7); const auto name = packet.text(112, 20); const auto destination = packet.text(302, 20); result += " \"" + call_sign + "\" \"" + name + "\" \"" + destination + "\""; } break; case 21: { const auto name = packet.text(43, 20); result += " \"" + name + "\" " + baseband::ais::format_latlon_normalized(packet.latitude(192)) + " " + baseband::ais::format_latlon_normalized(packet.longitude(164)); } break; default: break; } writeln(result); } } /* namespace ui */