2021-03-07 17:05:23 -05:00
|
|
|
/*
|
|
|
|
* 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 {
|
|
|
|
|
2023-05-18 16:16:05 -04:00
|
|
|
const int APRS_MIN_LENGTH = 18; // 14 bytes address, control byte and pid. 2 CRC.
|
2021-03-07 17:05:23 -05:00
|
|
|
|
2023-05-18 16:16:05 -04:00
|
|
|
struct aprs_pos {
|
|
|
|
float latitude;
|
|
|
|
float longitude;
|
|
|
|
uint8_t symbol_code;
|
|
|
|
uint8_t sym_table_id;
|
2021-03-07 17:05:23 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
enum ADDRESS_TYPE {
|
2023-05-18 16:16:05 -04:00
|
|
|
SOURCE,
|
|
|
|
DESTINATION,
|
|
|
|
REPEATER
|
2021-03-07 17:05:23 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
class APRSPacket {
|
2023-05-18 16:16:05 -04:00
|
|
|
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 std::stoi(sub);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2021-03-07 17:05:23 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
} /* namespace aprs */
|
|
|
|
|
2023-05-18 16:16:05 -04:00
|
|
|
#endif /*__APRS_PACKET_H__*/
|