/* * 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 #include #include #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__*/