mayhem-firmware/firmware/common/aprs_packet.hpp
2024-11-08 08:12:50 +01:00

481 lines
14 KiB
C++

/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
*
* 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.
*/
#ifndef __APRS_PACKET_H__
#define __APRS_PACKET_H__
#include <cstdint>
#include <cstddef>
#include <cctype>
#include "baseband.hpp"
namespace aprs {
const int APRS_MIN_LENGTH = 18; // 14 bytes address, control byte and pid. 2 CRC.
struct aprs_pos {
float latitude;
float longitude;
uint8_t symbol_code;
uint8_t sym_table_id;
};
enum ADDRESS_TYPE {
SOURCE,
DESTINATION,
REPEATER
};
class APRSPacket {
public:
void set_timestamp(const Timestamp& value) {
timestamp_ = value;
}
Timestamp timestamp() const {
return timestamp_;
}
void set(const size_t index, const uint8_t data) {
payload[index] = data;
if (index + 1 > payload_size) {
payload_size = index + 1;
}
}
uint32_t operator[](const size_t index) const {
return payload[index];
}
uint8_t size() const {
return payload_size;
}
void set_valid_checksum(const bool valid) {
valid_checksum = valid;
}
bool is_valid_checksum() const {
return valid_checksum;
}
uint64_t get_source() {
uint64_t source = 0x0;
for (uint8_t i = SOURCE_START; i < SOURCE_START + ADDRESS_SIZE; i++) {
source |= (((uint64_t)payload[i]) << ((i - SOURCE_START) * 8));
}
return source;
}
std::string get_source_formatted() {
parse_address(SOURCE_START, SOURCE);
return std::string(address_buffer);
}
std::string get_destination_formatted() {
parse_address(DESTINATION_START, DESTINATION);
return std::string(address_buffer);
}
std::string get_digipeaters_formatted() {
uint8_t position = DIGIPEATER_START;
bool has_more = parse_address(SOURCE_START, REPEATER);
std::string repeaters = "";
while (has_more) {
has_more = parse_address(position, REPEATER);
repeaters += std::string(address_buffer);
position += ADDRESS_SIZE;
if (has_more) {
repeaters += ">";
}
}
return repeaters;
}
uint8_t get_number_of_digipeaters() {
uint8_t position = DIGIPEATER_START;
bool has_more = parse_address(SOURCE_START, REPEATER);
uint8_t repeaters = 0;
while (has_more) {
has_more = parse_address(position, REPEATER);
position += ADDRESS_SIZE;
repeaters++;
}
return repeaters;
}
uint8_t get_information_start_index() {
return DIGIPEATER_START + (get_number_of_digipeaters() * ADDRESS_SIZE) + 2;
}
std::string get_information_text_formatted() {
std::string information_text = "";
for (uint8_t i = get_information_start_index(); i < payload_size - 2; i++) {
information_text += payload[i];
}
return information_text;
}
std::string get_stream_text() {
std::string stream = get_source_formatted() + ">" + get_destination_formatted() + ";" + get_digipeaters_formatted() + ";" + get_information_text_formatted();
return stream;
}
char get_data_type_identifier() {
char ident = '\0';
for (uint8_t i = get_information_start_index(); i < payload_size - 2; i++) {
ident = payload[i];
break;
}
return ident;
}
bool has_position() {
char ident = get_data_type_identifier();
return ident == '!' ||
ident == '=' ||
ident == '/' ||
ident == '@' ||
ident == ';' ||
ident == '`' ||
ident == '\'' ||
ident == 0x1d ||
ident == 0x1c;
}
aprs_pos get_position() {
aprs::aprs_pos pos;
char ident = get_data_type_identifier();
std::string info_text = get_information_text_formatted();
std::string lat_str, lng_str;
char first;
// bool supports_compression = true;
bool is_mic_e_format = false;
std::string::size_type start;
switch (ident) {
case '/':
case '@':
start = 8;
break;
case '=':
case '!':
start = 1;
break;
case ';':
start = 18;
// supports_compression = false;
break;
case '`':
case '\'':
case 0x1c:
case 0x1d:
is_mic_e_format = true;
break;
default:
return pos;
}
if (is_mic_e_format) {
parse_mic_e_format(pos);
} else {
if (start < info_text.size()) {
first = info_text.at(start);
if (std::isdigit(first)) {
if (start + 18 < info_text.size()) {
lat_str = info_text.substr(start, 8);
pos.sym_table_id = info_text.at(start + 8);
lng_str = info_text.substr(start + 9, 9);
pos.symbol_code = info_text.at(start + 18);
pos.latitude = parse_lat_str(lat_str);
pos.longitude = parse_lng_str(lng_str);
}
} else {
if (start + 9 < info_text.size()) {
pos.sym_table_id = info_text.at(start);
lat_str = info_text.substr(start + 1, 4);
lng_str = info_text.substr(start + 5, 4);
pos.symbol_code = info_text.at(start + 9);
pos.latitude = parse_lat_str_cmp(lat_str);
pos.longitude = parse_lng_str_cmp(lng_str);
}
}
}
}
return pos;
}
void clear() {
payload_size = 0;
}
private:
const uint8_t DIGIPEATER_START = 14;
const uint8_t SOURCE_START = 7;
const uint8_t DESTINATION_START = 0;
const uint8_t ADDRESS_SIZE = 7;
bool valid_checksum = false;
uint8_t payload[256];
char address_buffer[15];
uint8_t payload_size = 0;
Timestamp timestamp_{};
float parse_lat_str_cmp(const std::string& lat_str) {
return 90.0 - ((lat_str.at(0) - 33) * (91 * 91 * 91) + (lat_str.at(1) - 33) * (91 * 91) + (lat_str.at(2) - 33) * 91 + (lat_str.at(3))) / 380926.0;
}
float parse_lng_str_cmp(const std::string& lng_str) {
return -180.0 + ((lng_str.at(0) - 33) * (91 * 91 * 91) + (lng_str.at(1) - 33) * (91 * 91) + (lng_str.at(2) - 33) * 91 + (lng_str.at(3))) / 190463.0;
}
uint8_t parse_digits(const std::string& str) {
if (str.at(0) == ' ') {
return 0;
}
uint8_t end = str.find_last_not_of(' ') + 1;
std::string sub = str.substr(0, end);
if (!is_digits(sub)) {
return 0;
} else {
return atoi(sub.c_str());
}
}
bool is_digits(const std::string& str) {
return str.find_last_not_of("0123456789") == std::string::npos;
}
float parse_lat_str(const std::string& lat_str) {
float lat = 0.0;
std::string str_lat_deg = lat_str.substr(0, 2);
std::string str_lat_min = lat_str.substr(2, 2);
std::string str_lat_hund = lat_str.substr(5, 2);
std::string dir = lat_str.substr(7, 1);
uint8_t lat_deg = parse_digits(str_lat_deg);
uint8_t lat_min = parse_digits(str_lat_min);
uint8_t lat_hund = parse_digits(str_lat_hund);
lat += lat_deg;
lat += (lat_min + (lat_hund / 100.0)) / 60.0;
if (dir.c_str()[0] == 'S') {
lat = -lat;
}
return lat;
}
float parse_lng_str(std::string& lng_str) {
float lng = 0.0;
std::string str_lng_deg = lng_str.substr(0, 3);
std::string str_lng_min = lng_str.substr(3, 2);
std::string str_lng_hund = lng_str.substr(6, 2);
std::string dir = lng_str.substr(8, 1);
uint8_t lng_deg = parse_digits(str_lng_deg);
uint8_t lng_min = parse_digits(str_lng_min);
uint8_t lng_hund = parse_digits(str_lng_hund);
lng += lng_deg;
lng += (lng_min + (lng_hund / 100.0)) / 60.0;
if (dir.c_str()[0] == 'W') {
lng = -lng;
}
return lng;
}
void parse_mic_e_format(aprs::aprs_pos& pos) {
std::string lat_str = "";
std::string lng_str = "";
bool is_north = false;
bool is_west = false;
uint8_t lng_offset = 0;
for (uint8_t i = DESTINATION_START; i < DESTINATION_START + ADDRESS_SIZE - 1; i++) {
uint8_t ascii = payload[i] >> 1;
lat_str += get_mic_e_lat_digit(ascii);
if (i - DESTINATION_START == 3) {
lat_str += ".";
}
if (i - DESTINATION_START == 3) {
is_north = is_mic_e_lat_N(ascii);
}
if (i - DESTINATION_START == 4) {
lng_offset = get_mic_e_lng_offset(ascii);
}
if (i - DESTINATION_START == 5) {
is_west = is_mic_e_lng_W(ascii);
}
}
if (is_north) {
lat_str += "N";
} else {
lat_str += "S";
}
pos.latitude = parse_lat_str(lat_str);
float lng = 0.0;
uint8_t information_start = get_information_start_index() + 1;
for (uint8_t i = information_start; i < information_start + 3 && i < payload_size - 2; i++) {
uint8_t ascii = payload[i];
if (i - information_start == 0) { // deg
ascii -= 28;
ascii += lng_offset;
if (ascii >= 180 && ascii <= 189) {
ascii -= 80;
} else if (ascii >= 190 && ascii <= 199) {
ascii -= 190;
}
lng += ascii;
} else if (i - information_start == 1) { // min
ascii -= 28;
if (ascii >= 60) {
ascii -= 60;
}
lng += ascii / 60.0;
} else if (i - information_start == 2) { // hundredth minutes
ascii -= 28;
lng += (ascii / 100.0) / 60.0;
}
}
if (is_west) {
lng = -lng;
}
pos.longitude = lng;
}
uint8_t get_mic_e_lat_digit(uint8_t ascii) {
if (ascii >= '0' && ascii <= '9') {
return ascii;
}
if (ascii >= 'A' && ascii <= 'J') {
return ascii - 17;
}
if (ascii >= 'P' && ascii <= 'Y') {
return ascii - 32;
}
if (ascii == 'K' || ascii == 'L' || ascii == 'Z') {
return ' ';
}
return '\0';
}
bool is_mic_e_lat_N(uint8_t ascii) {
if (ascii >= 'P' && ascii <= 'Z') {
return true;
}
return false; // not technical definition, but the other case is invalid
}
bool is_mic_e_lng_W(uint8_t ascii) {
if (ascii >= 'P' && ascii <= 'Z') {
return true;
}
return false; // not technical definition, but the other case is invalid
}
uint8_t get_mic_e_lng_offset(uint8_t ascii) {
if (ascii >= 'P' && ascii <= 'Z') {
return 100;
}
return 0; // not technical definition, but the other case is invalid
}
bool parse_address(uint8_t start, ADDRESS_TYPE address_type) {
uint8_t byte = 0;
uint8_t has_more = false;
uint8_t ssid = 0;
uint8_t buffer_index = 0;
for (uint8_t i = start; i < start + ADDRESS_SIZE && i < payload_size - 2; i++) {
byte = payload[i];
if (i - start == 6) {
has_more = (byte & 0x1) == 0;
ssid = (byte >> 1) & 0x0F;
if (ssid != 0 || address_type == REPEATER) {
address_buffer[buffer_index++] = '-';
if (ssid < 10) {
address_buffer[buffer_index++] = '0' + ssid;
address_buffer[buffer_index++] = '\0';
} else {
address_buffer[buffer_index++] = '1';
address_buffer[buffer_index++] = '0' + ssid - 10;
address_buffer[buffer_index++] = '\0';
}
} else {
address_buffer[buffer_index++] = '\0';
}
} else {
byte >>= 1;
if (byte != ' ') {
address_buffer[buffer_index++] = byte;
}
}
}
return has_more;
}
};
} /* namespace aprs */
#endif /*__APRS_PACKET_H__*/