diff --git a/docs/CONFIGURE.md b/docs/CONFIGURE.md index 02b2064e2..e81bcc32f 100644 --- a/docs/CONFIGURE.md +++ b/docs/CONFIGURE.md @@ -272,6 +272,7 @@ During the status display, each source will report the running average as well a | bandplanSpacing | | | number | *SmartNet, 400_custom only* The channel spacing, specified in Hz. Typically this is *25000*. | | bandplanOffset | | | number | *SmartNet, 400_custom only* The offset used to calculate frequencies. | | customFrequencyTableFile | | | string | *P25 only* The filename for a CSV file that provides information about the P25 custom frequency tables. The format for the file is described below. | +| knownSitesFile | | | string | *P25 only* Optional CSV of known sites. Advertised neighboring RFSS/site IDs are matched to this file and printed with the site's name. CSV columns: `RFSS,SITEID,NAME`. | | decodeMDC | | false | **true** / **false** | *Conventional systems only* enable the MDC-1200 signaling decoder. | | decodeFSync | | false | **true** / **false** | *Conventional systems only* enable the Fleet Sync signaling decoder. | | decodeStar | | false | **true** / **false** | *Conventional systems only* enable the Star signaling decoder. | @@ -918,3 +919,11 @@ A **Header Row** is required for the file and the headers must match the column | 2 | FDMA | 762.00625 | 6.25 | +30 | | 3 | TDMA | 851.01250 | 12.5 | -45 | | 4 | TDMA | 762.00625 | 12.5 | +30 | + +## knownSitesFile + +For P25 trunked systems, `knownSitesFile` may point to a CSV used to label adjacent sites in the periodic control-channel information output. Neighbor frequency and RFSS/site IDs are learned from P25 adjacent-status broadcasts; the CSV supplies the site name. Neighbors that do not match an entry are highlighted in yellow when console colors are enabled. Neighbor information is still printed when no file is configured. + +| RFSS | SITEID | NAME | +|------|--------|------| +| 1 | 7 | North Ridge | diff --git a/trunk-recorder/config.cc b/trunk-recorder/config.cc index a5af3ed0c..11fc86d41 100755 --- a/trunk-recorder/config.cc +++ b/trunk-recorder/config.cc @@ -283,6 +283,13 @@ bool load_config(string config_file, Config &config, gr::top_block_sptr &tb, std system->set_system_type(element["type"]); BOOST_LOG_TRIVIAL(info) << "System Type: " << system->get_system_type(); + if (system->get_system_type() == "p25") { + std::string known_sites_file = element.value("knownSitesFile", ""); + if (!known_sites_file.empty()) { + system->set_known_sites_file(known_sites_file); + BOOST_LOG_TRIVIAL(info) << "Known Sites File: " << known_sites_file; + } + } // If it is a conventional System if ((system->get_system_type() == "conventional") || (system->get_system_type() == "conventionalP25") || (system->get_system_type() == "conventionalDMR") || (system->get_system_type() == "conventionalSIGMF")) { diff --git a/trunk-recorder/monitor_systems.cc b/trunk-recorder/monitor_systems.cc index 6147c6508..e9e234cbb 100644 --- a/trunk-recorder/monitor_systems.cc +++ b/trunk-recorder/monitor_systems.cc @@ -226,6 +226,9 @@ void print_status(std::vector &sources, std::vector &systems if ((sys->get_system_type() != "conventional") && (sys->get_system_type() != "conventionalP25") && (sys->get_system_type() != "conventionalDMR") && (sys->get_system_type() != "conventionalSIGMF")) { BOOST_LOG_TRIVIAL(info) << "[" << sys->get_short_name() << "]\t" << format_freq(sys->get_current_control_channel()) << "\t" << sys->get_decode_rate() << " msg/sec"; + if (sys->get_system_type() == "p25") { + for (const auto &site : sys->get_adjacent_sites()) BOOST_LOG_TRIVIAL(info) << site; + } // SmartNet's offset comes from the decoded FSK levels, so only trust it // while the control channel is actually decoding. @@ -653,6 +656,10 @@ void handle_message(std::vector messages, System *sys, Config &con current_system_sysid(message, sys); break; + case ADJACENT_SITE: + if (sys->get_system_type() == "p25") sys->update_adjacent_site(message); + break; + case STATUS: current_system_status(message, sys); break; diff --git a/trunk-recorder/systems/p25_parser.cc b/trunk-recorder/systems/p25_parser.cc index 757854818..aa6407feb 100644 --- a/trunk-recorder/systems/p25_parser.cc +++ b/trunk-recorder/systems/p25_parser.cc @@ -295,6 +295,11 @@ std::vector P25Parser::decode_mbt_data(unsigned long opcode, boost unsigned long stid = bitset_shift_mask(header, 16, 0xff); unsigned long ch1 = bitset_shift_mask(mbt_data, 80, 0xffff); unsigned long ch2 = bitset_shift_mask(mbt_data, 64, 0xffff); + unsigned long f1 = channel_id_to_frequency(ch1, sys_num); + message.message_type = ADJACENT_SITE; + message.neighbor_rfss = rfid; + message.neighbor_site_id = stid; + message.freq = f1; BOOST_LOG_TRIVIAL(debug) << "mbt3c adjacent status " << "syid " << syid << " rfid " << rfid << " stid " << stid << " ch1 " << ch1 << " ch2 " << ch2; } else if (opcode == 0x04) { // Unit to Unit Voice Service Channel Grant -Extended (UU_V_CH_GRANT) @@ -984,6 +989,10 @@ std::vector P25Parser::decode_tsbk(boost::dynamic_bitset<> &tsbk, unsigned long stid = bitset_shift_mask(tsbk, 40, 0xff); unsigned long ch1 = bitset_shift_mask(tsbk, 24, 0xffff); unsigned long f1 = channel_id_to_frequency(ch1, sys_num); + message.message_type = ADJACENT_SITE; + message.neighbor_rfss = rfid; + message.neighbor_site_id = stid; + message.freq = f1; BOOST_LOG_TRIVIAL(debug) << "tsbk3c\tAdjacent Status\t rfid " << std::dec << rfid << " stid " << stid << " ch1 " << channel_to_string(ch1, sys_num) << "(" << channel_id_to_freq_string(ch1, sys_num) << ") "; if (f1) { @@ -1194,4 +1203,4 @@ std::vector P25Parser::parse_message(gr::message::sptr msg, System } messages.push_back(message); return messages; -} \ No newline at end of file +} diff --git a/trunk-recorder/systems/parser.h b/trunk-recorder/systems/parser.h index e338497e9..5454769ff 100644 --- a/trunk-recorder/systems/parser.h +++ b/trunk-recorder/systems/parser.h @@ -23,6 +23,7 @@ enum MessageType { INVALID_CC_MESSAGE = 16, TDULC = 17, CALL_ALERT = 18, + ADJACENT_SITE = 19, UNKNOWN = 99 }; @@ -54,6 +55,8 @@ struct TrunkMessage { unsigned long wacn; PatchData patch_data; unsigned long opcode; + unsigned long neighbor_rfss = 0; + unsigned long neighbor_site_id = 0; }; diff --git a/trunk-recorder/systems/system.h b/trunk-recorder/systems/system.h index 47b7704c5..30f022417 100644 --- a/trunk-recorder/systems/system.h +++ b/trunk-recorder/systems/system.h @@ -153,6 +153,9 @@ class System { virtual const char *get_xor_mask() = 0; virtual bool update_status(TrunkMessage message) = 0; virtual bool update_sysid(TrunkMessage message) = 0; + virtual void update_adjacent_site(TrunkMessage message) = 0; + virtual void set_known_sites_file(std::string file) = 0; + virtual std::vector get_adjacent_sites() = 0; virtual int get_sys_num() = 0; virtual void set_system_type(std::string) = 0; virtual std::string get_talkgroups_file() = 0; @@ -270,4 +273,4 @@ class System { virtual void set_dmr_variant(const std::string &v) = 0; virtual std::string get_dmr_variant() = 0; }; -#endif \ No newline at end of file +#endif diff --git a/trunk-recorder/systems/system_impl.cc b/trunk-recorder/systems/system_impl.cc index e3076eead..9f555a7db 100644 --- a/trunk-recorder/systems/system_impl.cc +++ b/trunk-recorder/systems/system_impl.cc @@ -1,5 +1,8 @@ #include "system_impl.h" #include "system.h" +#include "../formatter.h" +#include +#include System *System::make(int sys_num) { return (System *)new System_impl(sys_num); @@ -277,6 +280,50 @@ bool System_impl::update_sysid(TrunkMessage message) { return false; } +void System_impl::set_known_sites_file(std::string file) { + known_sites_file = file; + if (file.empty()) return; + std::ifstream input(file); + if (!input) { + BOOST_LOG_TRIVIAL(error) << "Unable to open known sites file: " << file; + return; + } + std::string line; + std::getline(input, line); // RFSS,SITEID,NAME + while (std::getline(input, line)) { + std::stringstream row(line); + std::string rfss, site, name; + if (!std::getline(row, rfss, ',') || !std::getline(row, site, ',') || !std::getline(row, name)) continue; + if (name.size() >= 2 && name.front() == '"' && name.back() == '"') { + name = name.substr(1, name.size() - 2); + size_t quote = 0; + while ((quote = name.find("\"\"", quote)) != std::string::npos) name.replace(quote, 2, "\""); + } + try { known_sites[{std::stoi(rfss), std::stoi(site)}] = name; } + catch (...) { BOOST_LOG_TRIVIAL(warning) << "Ignoring malformed known site row: " << line; } + } + BOOST_LOG_TRIVIAL(info) << "Loaded " << known_sites.size() << " known P25 sites from " << file; +} + +void System_impl::update_adjacent_site(TrunkMessage message) { + adjacent_sites[{static_cast(message.neighbor_rfss), static_cast(message.neighbor_site_id)}] = message.freq; +} + +std::vector System_impl::get_adjacent_sites() { + std::vector result; + for (const auto &site : adjacent_sites) { + std::ostringstream line; + auto known = known_sites.find(site.first); + if (known == known_sites.end()) line << Color::YEL; + line << "[" << short_name << "]\tNeighbour RFSS " << site.first.first << " Site " << site.first.second; + if (known != known_sites.end()) line << " (" << known->second << ")"; + if (site.second > 0) line << " Control Channel " << format_freq(site.second); + if (known == known_sites.end()) line << Color::RST; + result.push_back(line.str()); + } + return result; +} + gr::msg_queue::sptr System_impl::get_msg_queue() { return msg_queue; } @@ -981,4 +1028,4 @@ void System_impl::set_dmr_variant(const std::string &v) { std::string System_impl::get_dmr_variant() { return dmr_variant; -} \ No newline at end of file +} diff --git a/trunk-recorder/systems/system_impl.h b/trunk-recorder/systems/system_impl.h index a3ac6600a..27dd6be4d 100644 --- a/trunk-recorder/systems/system_impl.h +++ b/trunk-recorder/systems/system_impl.h @@ -53,6 +53,9 @@ class System_impl : public System { unsigned long nac; int sys_rfss; int sys_site_id; + std::string known_sites_file; + std::map, std::string> known_sites; + std::map, double> adjacent_sites; public: Talkgroups *talkgroups; @@ -242,6 +245,9 @@ class System_impl : public System { const char *get_xor_mask() override; bool update_status(TrunkMessage message) override; bool update_sysid(TrunkMessage message) override; + void update_adjacent_site(TrunkMessage message) override; + void set_known_sites_file(std::string file) override; + std::vector get_adjacent_sites() override; int get_sys_num() override; void set_system_type(std::string) override; std::string get_talkgroups_file() override;