mirror of
https://github.com/eried/portapack-mayhem.git
synced 2024-12-24 23:09:26 -05:00
AIS: Large refactor to separate packet decode from UI/log formatting.
This commit is contained in:
parent
4baf2a06f2
commit
b8ee19f8e6
@ -24,7 +24,6 @@
|
|||||||
#include "portapack.hpp"
|
#include "portapack.hpp"
|
||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
|
|
||||||
#include "field_reader.hpp"
|
|
||||||
#include "crc.hpp"
|
#include "crc.hpp"
|
||||||
|
|
||||||
// TODO: Move string formatting elsewhere!!!
|
// TODO: Move string formatting elsewhere!!!
|
||||||
@ -33,21 +32,12 @@ using namespace portapack;
|
|||||||
namespace baseband {
|
namespace baseband {
|
||||||
namespace ais {
|
namespace ais {
|
||||||
|
|
||||||
decoded_packet packet_decode(const std::bitset<1024>& data, const size_t data_length);
|
|
||||||
|
|
||||||
struct BitRemap {
|
|
||||||
size_t operator()(const size_t bit_index) const {
|
|
||||||
return bit_index ^ 7;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CRCBitRemap {
|
struct CRCBitRemap {
|
||||||
size_t operator()(const size_t bit_index) const {
|
size_t operator()(const size_t bit_index) const {
|
||||||
return bit_index;
|
return bit_index;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
using FieldReader = ::FieldReader<std::bitset<1024>, BitRemap>;
|
|
||||||
using CRCFieldReader = ::FieldReader<std::bitset<1024>, CRCBitRemap>;
|
using CRCFieldReader = ::FieldReader<std::bitset<1024>, CRCBitRemap>;
|
||||||
|
|
||||||
struct PacketLengthRange {
|
struct PacketLengthRange {
|
||||||
@ -158,75 +148,16 @@ struct CRCCheck {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static int32_t ais_latitude_normalized(
|
static std::string format_latlon_normalized(const int32_t 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<int32_t>(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<int32_t>(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 t = (normalized * 5) / 3;
|
||||||
const int32_t degrees = t / (100 * 10000);
|
const int32_t degrees = t / (100 * 10000);
|
||||||
const int32_t fraction = std::abs(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');
|
return ui::to_string_dec_int(degrees) + "." + ui::to_string_dec_int(fraction, 6, '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string ais_format_latitude(
|
static std::string format_datetime(
|
||||||
const FieldReader& field,
|
const DateTime& datetime
|
||||||
const size_t start_bit
|
|
||||||
) {
|
) {
|
||||||
const auto value = static_cast<int32_t>(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<int32_t>(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<uint16_t>(field.read(start_bit + 0, 14)),
|
|
||||||
static_cast<uint8_t >(field.read(start_bit + 14, 4)),
|
|
||||||
static_cast<uint8_t >(field.read(start_bit + 18, 5)),
|
|
||||||
static_cast<uint8_t >(field.read(start_bit + 23, 5)),
|
|
||||||
static_cast<uint8_t >(field.read(start_bit + 28, 6)),
|
|
||||||
static_cast<uint8_t >(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) + "/" +
|
return ui::to_string_dec_uint(datetime.year, 4) + "/" +
|
||||||
ui::to_string_dec_uint(datetime.month, 2, '0') + "/" +
|
ui::to_string_dec_uint(datetime.month, 2, '0') + "/" +
|
||||||
ui::to_string_dec_uint(datetime.day, 2, '0') + " " +
|
ui::to_string_dec_uint(datetime.day, 2, '0') + " " +
|
||||||
@ -235,26 +166,7 @@ static std::string ais_format_datetime(
|
|||||||
ui::to_string_dec_uint(datetime.second, 2, '0');
|
ui::to_string_dec_uint(datetime.second, 2, '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
static char ais_char_to_ascii(const uint8_t c) {
|
static std::string format_navigational_status(const unsigned int value) {
|
||||||
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<end_bit; i+=character_length) {
|
|
||||||
result += ais_char_to_ascii(field.read(i, character_length));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::string ais_format_navigational_status(const unsigned int value) {
|
|
||||||
switch(value) {
|
switch(value) {
|
||||||
case 0: return "under way w/engine";
|
case 0: return "under way w/engine";
|
||||||
case 1: return "at anchor";
|
case 1: return "at anchor";
|
||||||
@ -274,80 +186,88 @@ static std::string ais_format_navigational_status(const unsigned int value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
decoded_packet packet_decode(const std::bitset<1024>& payload, const size_t payload_length) {
|
static char char_to_ascii(const uint8_t c) {
|
||||||
// TODO: Unstuff here, not in baseband!
|
return (c ^ 32) + 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Packet::length() const {
|
||||||
|
return payload_length_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Packet::is_valid() const {
|
||||||
// Subtract end flag (8 bits) - one unstuffing bit (occurs during end flag).
|
// Subtract end flag (8 bits) - one unstuffing bit (occurs during end flag).
|
||||||
const size_t data_and_fcs_length = payload_length - 7;
|
const size_t data_and_fcs_length = payload_length_ - 7;
|
||||||
|
|
||||||
if( data_and_fcs_length < 38 ) {
|
if( data_and_fcs_length < 38 ) {
|
||||||
return { "short " + ui::to_string_dec_uint(data_and_fcs_length, 3), "" };
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const size_t extra_bits = data_and_fcs_length & 7;
|
const size_t extra_bits = data_and_fcs_length & 7;
|
||||||
if( extra_bits != 0 ) {
|
if( extra_bits != 0 ) {
|
||||||
return { "extra bits " + ui::to_string_dec_uint(data_and_fcs_length, 3), "" };
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
FieldReader field { payload };
|
|
||||||
|
|
||||||
const auto message_id = field.read(0, 6);
|
|
||||||
|
|
||||||
const size_t data_length = data_and_fcs_length - 16;
|
const size_t data_length = data_and_fcs_length - 16;
|
||||||
PacketLengthValidator packet_length_valid;
|
PacketLengthValidator packet_length_valid;
|
||||||
if( !packet_length_valid(message_id, data_length) ) {
|
if( !packet_length_valid(message_id(), data_length) ) {
|
||||||
return { "bad length " + ui::to_string_dec_uint(data_length, 3), "" };
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
CRCCheck crc_ok;
|
CRCCheck crc_ok;
|
||||||
if( !crc_ok(payload, data_length) ) {
|
if( !crc_ok(payload_, data_length) ) {
|
||||||
return { "crc", "" };
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto source_id = field.read(8, 30);
|
return true;
|
||||||
std::string result { ui::to_string_dec_uint(message_id, 2) + " " + ui::to_string_dec_uint(source_id, 10) };
|
}
|
||||||
|
|
||||||
switch(message_id) {
|
uint32_t Packet::message_id() const {
|
||||||
case 1:
|
return field_.read(0, 6);
|
||||||
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:
|
uint32_t Packet::source_id() const {
|
||||||
{
|
return field_.read(8, 30);
|
||||||
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:
|
uint32_t Packet::read(const size_t start_bit, const size_t length) const {
|
||||||
{
|
return field_.read(start_bit, length);
|
||||||
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:
|
std::string Packet::text(
|
||||||
{
|
const size_t start_bit,
|
||||||
const auto name = ais_read_text(field, 43, 20);
|
const size_t character_count
|
||||||
result += " \"" + name + "\" " + ais_format_latitude(field, 192) + " " + ais_format_longitude(field, 164);
|
) const {
|
||||||
}
|
std::string result;
|
||||||
break;
|
const size_t character_length = 6;
|
||||||
|
const size_t end_bit = start_bit + character_count * character_length;
|
||||||
|
for(size_t i=start_bit; i<end_bit; i+=character_length) {
|
||||||
|
result += char_to_ascii(field_.read(i, character_length));
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
return result;
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return { "OK", result };
|
DateTime Packet::datetime(const size_t start_bit) const {
|
||||||
|
return {
|
||||||
|
static_cast<uint16_t>(field_.read(start_bit + 0, 14)),
|
||||||
|
static_cast<uint8_t >(field_.read(start_bit + 14, 4)),
|
||||||
|
static_cast<uint8_t >(field_.read(start_bit + 18, 5)),
|
||||||
|
static_cast<uint8_t >(field_.read(start_bit + 23, 5)),
|
||||||
|
static_cast<uint8_t >(field_.read(start_bit + 28, 6)),
|
||||||
|
static_cast<uint8_t >(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<int32_t>(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<int32_t>(field_.read(start_bit, 28) << 4) / 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
} /* namespace ais */
|
} /* namespace ais */
|
||||||
@ -364,15 +284,27 @@ AISModel::AISModel() {
|
|||||||
log_file.open_for_append("ais.txt");
|
log_file.open_for_append("ais.txt");
|
||||||
}
|
}
|
||||||
|
|
||||||
baseband::ais::decoded_packet AISModel::on_packet(const AISPacketMessage& message) {
|
bool AISModel::on_packet(const baseband::ais::Packet& packet) {
|
||||||
const auto result = baseband::ais::packet_decode(message.packet.payload, message.packet.bits_received);
|
// TODO: Unstuff here, not in baseband!
|
||||||
|
|
||||||
|
if( !packet.is_valid() ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if( log_file.is_ready() ) {
|
if( log_file.is_ready() ) {
|
||||||
std::string entry = result.first + "/" + result.second + "\r\n";
|
std::string entry;
|
||||||
|
entry.reserve((packet.length() + 3) / 4);
|
||||||
|
|
||||||
|
for(size_t i=0; i<packet.length(); i+=4) {
|
||||||
|
const auto nibble = packet.read(i, 4);
|
||||||
|
entry += (nibble >= 10) ? ('W' + nibble) : ('0' + nibble);
|
||||||
|
}
|
||||||
|
|
||||||
|
entry += "\r\n";
|
||||||
log_file.write(entry);
|
log_file.write(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace ui {
|
namespace ui {
|
||||||
@ -384,7 +316,10 @@ void AISView::on_show() {
|
|||||||
message_map.register_handler(Message::ID::AISPacket,
|
message_map.register_handler(Message::ID::AISPacket,
|
||||||
[this](Message* const p) {
|
[this](Message* const p) {
|
||||||
const auto message = static_cast<const AISPacketMessage*>(p);
|
const auto message = static_cast<const AISPacketMessage*>(p);
|
||||||
this->log(this->model.on_packet(*message));
|
const baseband::ais::Packet packet { message->packet.payload, message->packet.bits_received };
|
||||||
|
if( this->model.on_packet(packet) ) {
|
||||||
|
this->log(packet);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -396,10 +331,50 @@ void AISView::on_hide() {
|
|||||||
Console::on_hide();
|
Console::on_hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AISView::log(const baseband::ais::decoded_packet decoded) {
|
void AISView::log(const baseband::ais::Packet& packet) {
|
||||||
if( decoded.first == "OK" ) {
|
std::string result { ui::to_string_dec_uint(packet.message_id(), 2) + " " + ui::to_string_dec_uint(packet.source_id(), 10) };
|
||||||
writeln(decoded.second);
|
|
||||||
|
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 */
|
} /* namespace ui */
|
||||||
|
@ -25,14 +25,69 @@
|
|||||||
#include "ui_console.hpp"
|
#include "ui_console.hpp"
|
||||||
#include "message.hpp"
|
#include "message.hpp"
|
||||||
#include "log_file.hpp"
|
#include "log_file.hpp"
|
||||||
|
#include "field_reader.hpp"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstddef>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <bitset>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
namespace baseband {
|
namespace baseband {
|
||||||
namespace ais {
|
namespace ais {
|
||||||
|
|
||||||
using decoded_packet = std::pair<std::string, std::string>;
|
struct BitRemap {
|
||||||
|
size_t operator()(const size_t bit_index) const {
|
||||||
|
return bit_index ^ 7;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using FieldReader = ::FieldReader<std::bitset<1024>, BitRemap>;
|
||||||
|
|
||||||
|
struct DateTime {
|
||||||
|
uint16_t year;
|
||||||
|
uint8_t month;
|
||||||
|
uint8_t day;
|
||||||
|
uint8_t hour;
|
||||||
|
uint8_t minute;
|
||||||
|
uint8_t second;
|
||||||
|
};
|
||||||
|
|
||||||
|
using Latitude = int32_t;
|
||||||
|
using Longitude = int32_t;
|
||||||
|
|
||||||
|
class Packet {
|
||||||
|
public:
|
||||||
|
constexpr Packet(
|
||||||
|
const std::bitset<1024>& payload,
|
||||||
|
const size_t payload_length
|
||||||
|
) : payload_ { payload },
|
||||||
|
payload_length_ { payload_length },
|
||||||
|
field_ { payload_ }
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t length() const;
|
||||||
|
|
||||||
|
bool is_valid() const;
|
||||||
|
|
||||||
|
uint32_t message_id() const;
|
||||||
|
uint32_t source_id() const;
|
||||||
|
|
||||||
|
uint32_t read(const size_t start_bit, const size_t length) const;
|
||||||
|
|
||||||
|
std::string text(const size_t start_bit, const size_t character_count) const;
|
||||||
|
|
||||||
|
DateTime datetime(const size_t start_bit) const;
|
||||||
|
|
||||||
|
Latitude latitude(const size_t start_bit) const;
|
||||||
|
Longitude longitude(const size_t start_bit) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::bitset<1024> payload_;
|
||||||
|
const size_t payload_length_;
|
||||||
|
const FieldReader field_;
|
||||||
|
};
|
||||||
|
|
||||||
} /* namespace ais */
|
} /* namespace ais */
|
||||||
} /* namespace baseband */
|
} /* namespace baseband */
|
||||||
@ -41,7 +96,7 @@ class AISModel {
|
|||||||
public:
|
public:
|
||||||
AISModel();
|
AISModel();
|
||||||
|
|
||||||
baseband::ais::decoded_packet on_packet(const AISPacketMessage& message);
|
bool on_packet(const baseband::ais::Packet& packet);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LogFile log_file;
|
LogFile log_file;
|
||||||
@ -57,7 +112,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
AISModel model;
|
AISModel model;
|
||||||
|
|
||||||
void log(const baseband::ais::decoded_packet decoded);
|
void log(const baseband::ais::Packet& packet);
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace ui */
|
} /* namespace ui */
|
||||||
|
Loading…
Reference in New Issue
Block a user