/* * 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 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; } }; } /* namespace aprs */ #endif/*__APRS_PACKET_H__*/