diff --git a/host/Host.cpp b/host/Host.cpp index 16e49c08..5803ece4 100644 --- a/host/Host.cpp +++ b/host/Host.cpp @@ -122,6 +122,15 @@ Host::Host(const std::string& confFile) : m_p25CCData(false), m_p25CtrlChannel(false), m_p25CtrlBroadcast(false), + m_siteId(1U), + m_dmrNetId(1U), + m_dmrColorCode(1U), + m_p25NAC(0x293U), + m_p25PatchSuperGroup(0xFFFFU), + m_p25NetId(0xBB800U), + m_p25SysId(1U), + m_p25RfssId(1U), + m_nxdnRAN(1U), m_activeTickDelay(5U), m_idleTickDelay(5U), m_remoteControl(NULL) @@ -568,7 +577,7 @@ int Host::run() nxdn = new nxdn::Control(m_nxdnRAN, callHang, queueSizeBytes, m_timeout, m_rfTalkgroupHang, m_modem, m_network, m_duplex, m_ridLookup, m_tidLookup, m_idenTable, rssi, nxdnDebug, nxdnVerbose); - nxdn->setOptions(m_conf, m_cwCallsign, m_voiceChNo, m_channelId, m_channelNo, true); + nxdn->setOptions(m_conf, m_cwCallsign, m_voiceChNo, m_siteId, m_channelId, m_channelNo, true); if (nxdnVerbose) { LogInfo(" Verbose: yes"); diff --git a/nxdn/Control.cpp b/nxdn/Control.cpp index 1bd8e265..42d51202 100644 --- a/nxdn/Control.cpp +++ b/nxdn/Control.cpp @@ -92,6 +92,9 @@ Control::Control(uint32_t ran, uint32_t callHang, uint32_t queueSize, uint32_t t m_modem(modem), m_network(network), m_duplex(duplex), + m_control(false), + m_dedicatedControl(false), + m_voiceOnControl(false), m_rfLastLICH(), m_rfLC(), m_netLC(), @@ -106,10 +109,14 @@ Control::Control(uint32_t ran, uint32_t callHang, uint32_t queueSize, uint32_t t m_rfLastDstId(0U), m_netState(RS_NET_IDLE), m_netLastDstId(0U), + m_ccRunning(false), + m_ccPrevRunning(false), + m_ccHalted(false), m_rfTimeout(1000U, timeout), m_rfTGHang(1000U, tgHang), m_netTimeout(1000U, timeout), m_networkWatchdog(1000U, 0U, 1500U), + m_siteData(), m_rssiMapper(rssiMapper), m_rssi(0U), m_maxRSSI(0U), @@ -176,21 +183,48 @@ void Control::reset() /// Instance of the yaml::Node class. /// /// +/// /// /// /// void Control::setOptions(yaml::Node& conf, const std::string cwCallsign, const std::vector voiceChNo, - uint8_t channelId, uint32_t channelNo, bool printOptions) + uint16_t locId, uint8_t channelId, uint32_t channelNo, bool printOptions) { yaml::Node systemConf = conf["system"]; yaml::Node nxdnProtocol = conf["protocols"]["nxdn"]; + yaml::Node control = nxdnProtocol["control"]; + m_control = control["enable"].as(false); + if (m_control) { + m_dedicatedControl = control["dedicated"].as(false); + } + else { + m_dedicatedControl = false; + } + + m_voiceOnControl = nxdnProtocol["voiceOnControl"].as(false); + m_voice->m_silenceThreshold = nxdnProtocol["silenceThreshold"].as(nxdn::DEFAULT_SILENCE_THRESHOLD); if (m_voice->m_silenceThreshold > MAX_NXDN_VOICE_ERRORS) { LogWarning(LOG_NXDN, "Silence threshold > %u, defaulting to %u", nxdn::MAX_NXDN_VOICE_ERRORS, nxdn::DEFAULT_SILENCE_THRESHOLD); m_voice->m_silenceThreshold = nxdn::DEFAULT_SILENCE_THRESHOLD; } + bool disableCompositeFlag = nxdnProtocol["disableCompositeFlag"].as(false); + uint8_t serviceClass = NXDN_SIF1_VOICE_CALL_SVC | NXDN_SIF1_DATA_CALL_SVC; + if (m_control) { + serviceClass |= NXDN_SIF1_GRP_REG_SVC; + } + + if (m_voiceOnControl) { + if (!disableCompositeFlag) { + serviceClass |= NXDN_SIF1_COMPOSITE_CONTROL; + } + } + + m_siteData = SiteData(locId, channelId, channelNo, serviceClass, false); + m_siteData.setCallsign(cwCallsign); + std::vector entries = m_idenTable->list(); for (auto it = entries.begin(); it != entries.end(); ++it) { lookups::IdenTable entry = *it; @@ -202,6 +236,10 @@ void Control::setOptions(yaml::Node& conf, const std::string cwCallsign, const s if (printOptions) { LogInfo(" Silence Threshold: %u (%.1f%%)", m_voice->m_silenceThreshold, float(m_voice->m_silenceThreshold) / 12.33F); + + if (m_control) { + LogInfo(" Voice on Control: %s", m_voiceOnControl ? "yes" : "no"); + } } if (m_voice != NULL) { diff --git a/nxdn/Control.h b/nxdn/Control.h index 63eb2688..f4c7e312 100644 --- a/nxdn/Control.h +++ b/nxdn/Control.h @@ -37,6 +37,7 @@ #include "nxdn/lc/LC.h" #include "nxdn/packet/Voice.h" #include "nxdn/packet/Data.h" +#include "nxdn/SiteData.h" #include "network/BaseNetwork.h" #include "network/RemoteControl.h" #include "lookups/RSSIInterpolator.h" @@ -80,7 +81,7 @@ namespace nxdn /// Helper to set NXDN configuration options. void setOptions(yaml::Node& conf, const std::string cwCallsign, const std::vector voiceChNo, - uint8_t channelId, uint32_t channelNo, bool printOptions); + uint16_t locId, uint8_t channelId, uint32_t channelNo, bool printOptions); /// Process a data frame from the RF interface. bool processFrame(uint8_t* data, uint32_t len); @@ -109,6 +110,9 @@ namespace nxdn network::BaseNetwork* m_network; bool m_duplex; + bool m_control; + bool m_dedicatedControl; + bool m_voiceOnControl; channel::LICH m_rfLastLICH; lc::LC m_rfLC; @@ -130,11 +134,17 @@ namespace nxdn RPT_NET_STATE m_netState; uint32_t m_netLastDstId; + bool m_ccRunning; + bool m_ccPrevRunning; + bool m_ccHalted; + Timer m_rfTimeout; Timer m_rfTGHang; Timer m_netTimeout; Timer m_networkWatchdog; + SiteData m_siteData; + lookups::RSSIInterpolator* m_rssiMapper; uint8_t m_rssi; uint8_t m_maxRSSI; diff --git a/nxdn/NXDNDefines.h b/nxdn/NXDNDefines.h index 3c397556..ecabde78 100644 --- a/nxdn/NXDNDefines.h +++ b/nxdn/NXDNDefines.h @@ -152,6 +152,8 @@ namespace nxdn const uint32_t MAX_NXDN_VOICE_ERRORS = 144U; const uint32_t MAX_NXDN_VOICE_ERRORS_STEAL = 94U; + const uint8_t NXDN_NULL_AMBE[] = { 0xB1U, 0xA8U, 0x22U, 0x25U, 0x6BU, 0xD1U, 0x6CU, 0xCFU, 0x67U }; + const uint32_t NXDN_MI_LENGTH_BYTES = 8U; const uint32_t NXDN_PCKT_INFO_LENGTH_BYTES = 3U; @@ -181,8 +183,27 @@ namespace nxdn const uint8_t NXDN_CAUSE_DISC_NORMAL = 0x01U; const uint8_t NXDN_CAUSE_DISC_NORMAL_TC = 0x02U; + const uint8_t NXDN_SIF1_DATA_CALL_SVC = 0x01U; + const uint8_t NXDN_SIF1_VOICE_CALL_SVC = 0x02U; + const uint8_t NXDN_SIF1_COMPOSITE_CONTROL = 0x04U; + const uint8_t NXDN_SIF1_AUTH_SVC = 0x08U; + const uint8_t NXDN_SIF1_GRP_REG_SVC = 0x10U; + const uint8_t NXDN_SIF1_LOC_REG_SVC = 0x20U; + const uint8_t NXDN_SIF1_MULTI_SYSTEM_SVC = 0x40U; + const uint8_t NXDN_SIF1_MULTI_SITE_SVC = 0x80U; + + const uint8_t NXDN_SIF2_IP_NETWORK = 0x10U; + const uint8_t NXDN_SIF2_PSTN_NETWORK = 0x20U; + const uint8_t NXDN_SIF2_STATUS_CALL_REM_CTRL = 0x40U; + const uint8_t NXDN_SIF2_SHORT_DATA_CALL_SVC = 0x80U; + // Common Message Types const uint8_t MESSAGE_TYPE_IDLE = 0x10U; // IDLE - Idle + const uint8_t MESSAGE_TYPE_DISC = 0x11U; // DISC - Disconnect + const uint8_t MESSAGE_TYPE_DST_ID_INFO = 0x17U; // DST_ID_INFO - Digital Station ID + const uint8_t MESSAGE_TYPE_SRV_INFO = 0x19U; // SRV_INFO - Service Information + const uint8_t MESSAGE_TYPE_CCH_INFO = 0x1AU; // CCH_INFO - Control Channel Information + const uint8_t MESSAGE_TYPE_ADJ_SITE_INFO = 0x1BU; // ADJ_SITE_INFO - Adjacent Site Information // Traffic Channel Message Types const uint8_t RTCH_MESSAGE_TYPE_VCALL = 0x01U; // VCALL - Voice Call @@ -199,6 +220,11 @@ namespace nxdn const uint8_t RTCH_MESSAGE_TYPE_SDCALL_RESP = 0x3BU; // SDCALL_RESP - Short Data Call Response // Control Channel Message Types + const uint8_t RCCH_MESSAGE_TYPE_SITE_INFO = 0x18U; // SITE_INFO - Site Information + const uint8_t RCCH_MESSAGE_TYPE_REG = 0x20U; // REG - Registration Request (ISP) / Registration Response (OSP) + const uint8_t RCCH_MESSAGE_TYPE_REG_C = 0x22U; // REG_C - Registration Clear Request (ISP) / Registration Clear Response (OSP) + const uint8_t RCCH_MESSAGE_TYPE_REG_COMM = 0x23U; // REG_COMM - Registration Command + const uint8_t RCCH_MESSAGE_TYPE_GRP_REG = 0x24U; // GRP_REG - Group Registration Request (ISP) / Group Registration Response (OSP) const uint8_t RCCH_MESSAGE_TYPE_PROP_FORM = 0x3FU; // PROP_FORM - Proprietary Form // Call Types diff --git a/nxdn/SiteData.h b/nxdn/SiteData.h new file mode 100644 index 00000000..ca16f45b --- /dev/null +++ b/nxdn/SiteData.h @@ -0,0 +1,186 @@ +/** +* Digital Voice Modem - Host Software +* GPLv2 Open Source. Use is subject to license terms. +* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +* +* @package DVM / Host Server +* +*/ +// +// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost) +// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) +// +/* +* Copyright (C) 2021 by Bryan Biedenkapp N2PLL +* +* 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 of the License, 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; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ +#if !defined(__NXDN_SITE_DATA_H__) +#define __NXDN_SITE_DATA_H__ + +#include "Defines.h" +#include "nxdn/NXDNDefines.h" + +namespace nxdn +{ + // --------------------------------------------------------------------------- + // Class Declaration + // Represents site data for NXDN. + // --------------------------------------------------------------------------- + + class HOST_SW_API SiteData { + public: + /// Initializes a new instance of the SiteData class. + SiteData() : + m_locId(1U), + m_channelId(1U), + m_channelNo(1U), + m_serviceClass(NXDN_SIF1_VOICE_CALL_SVC | NXDN_SIF1_DATA_CALL_SVC), + m_isAdjSite(false), + m_callsign("CHANGEME"), + m_requireReg(false), + m_netActive(false) + { + /* stub */ + } + /// Initializes a new instance of the SiteData class. + /// NXDN Location ID. + /// Channel ID. + /// Channel Number. + /// Service class. + /// + SiteData(uint16_t locId, uint8_t channelId, uint32_t channelNo, uint8_t serviceClass, bool requireReq) : + m_locId(locId), + m_channelId(1U), + m_channelNo(1U), + m_serviceClass(NXDN_SIF1_VOICE_CALL_SVC | NXDN_SIF1_DATA_CALL_SVC), + m_isAdjSite(false), + m_callsign("CHANGEME"), + m_requireReg(requireReq), + m_netActive(false) + { + if (m_locId > 0xFFFU) + m_locId = 0xFFFU; + + // channel id clamping + if (channelId > 15U) + channelId = 15U; + + // channel number clamping + if (m_channelNo == 0U) { // clamp to 1 + m_channelNo = 1U; + } + if (m_channelNo > 4095U) { // clamp to 4095 + m_channelNo = 4095U; + } + + m_serviceClass = serviceClass; + } + + /// Helper to set the site callsign. + /// Callsign. + void setCallsign(std::string callsign) + { + m_callsign = callsign; + } + + /// Helper to set the site network active flag. + /// Network active. + void setNetActive(bool netActive) + { + m_netActive = netActive; + } + + /// Helper to set adjacent site data. + /// NXDN Location ID. + /// Channel ID. + /// Channel Number. + /// Service class. + void setAdjSite(uint32_t locId, uint8_t rfssId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, uint8_t serviceClass) + { + if (locId > 0xFFFU) + locId = 0xFFFU; + + // channel id clamping + if (channelId > 15U) + channelId = 15U; + + // channel number clamping + if (channelNo == 0U) { // clamp to 1 + channelNo = 1U; + } + if (channelNo > 4095U) { // clamp to 4095 + channelNo = 4095U; + } + + m_locId = locId; + + m_channelId = channelId; + m_channelNo = channelNo; + + m_serviceClass = serviceClass; + + m_isAdjSite = true; + + m_callsign = "ADJSITE "; + m_netActive = true; // adjacent sites are explicitly network active + } + + /// Equals operator. + /// + /// + SiteData & operator=(const SiteData & data) + { + if (this != &data) { + m_locId = data.m_locId; + + m_channelId = data.m_channelId; + m_channelNo = data.m_channelNo; + + m_serviceClass = data.m_serviceClass; + + m_isAdjSite = data.m_isAdjSite; + + m_callsign = data.m_callsign; + + m_requireReg = data.m_requireReg; + + m_netActive = data.m_netActive; + } + + return *this; + } + + public: + /// NXDN location ID. + __READONLY_PROPERTY_PLAIN(uint16_t, locId, locId); + /// Channel ID. + __READONLY_PROPERTY_PLAIN(uint8_t, channelId, channelId); + /// Channel number. + __READONLY_PROPERTY_PLAIN(uint32_t, channelNo, channelNo); + /// Service class. + __READONLY_PROPERTY_PLAIN(uint8_t, serviceClass, serviceClass); + /// Flag indicating whether this site data is for an adjacent site. + __READONLY_PROPERTY_PLAIN(bool, isAdjSite, isAdjSite); + /// Callsign. + __READONLY_PROPERTY_PLAIN(std::string, callsign, callsign); + /// NXDN require registration. + __READONLY_PROPERTY_PLAIN(bool, requireReg, requireReg); + /// Flag indicating whether this site is a linked active network member. + __READONLY_PROPERTY_PLAIN(bool, netActive, netActive); + }; +} // namespace nxdn + +#endif // __NXDN_SITE_DATA_H__ diff --git a/nxdn/channel/CAC.cpp b/nxdn/channel/CAC.cpp index 6123f56d..e118a1f0 100644 --- a/nxdn/channel/CAC.cpp +++ b/nxdn/channel/CAC.cpp @@ -157,7 +157,7 @@ CAC& CAC::operator=(const CAC& data) } /// -/// Decode a slow associated control channel. +/// Decode a common access channel. /// /// /// True, if CAC was decoded, otherwise false. @@ -223,7 +223,7 @@ bool CAC::decode(const uint8_t* data) } /// -/// Encode a slow associated control channel. +/// Encode a common access channel. /// /// void CAC::encode(uint8_t* data) const diff --git a/nxdn/packet/Voice.cpp b/nxdn/packet/Voice.cpp index f9669be0..2cb19ee6 100644 --- a/nxdn/packet/Voice.cpp +++ b/nxdn/packet/Voice.cpp @@ -487,6 +487,19 @@ bool Voice::process(uint8_t usc, uint8_t option, uint8_t* data, uint32_t len) errors += ambe.regenerateNXDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 18U); errors += ambe.regenerateNXDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 27U); + // replace audio with silence in cases where the error rate + // has exceeded the configured threshold + if (errors > m_silenceThreshold) { + // bryanb: this is probably the wrong way to go about this... + // generate null audio + ::memcpy(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 0U, NXDN_NULL_AMBE, 9U); + ::memcpy(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 9U, NXDN_NULL_AMBE, 9U); + ::memcpy(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 18U, NXDN_NULL_AMBE, 9U); + ::memcpy(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 27U, NXDN_NULL_AMBE, 9U); + + LogWarning(LOG_RF, NXDN_MESSAGE_TYPE_VCALL ", exceeded lost audio threshold, filling in"); + } + m_rfErrs += errors; m_rfBits += 188U; @@ -506,6 +519,17 @@ bool Voice::process(uint8_t usc, uint8_t option, uint8_t* data, uint32_t len) errors += ambe.regenerateNXDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 18U); errors += ambe.regenerateNXDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 27U); + + // replace audio with silence in cases where the error rate + // has exceeded the configured threshold + if (errors > (m_silenceThreshold / 2U)) { + // bryanb: this is probably the wrong way to go about this... + // generate null audio + ::memcpy(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 18U, NXDN_NULL_AMBE, 9U); + ::memcpy(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 27U, NXDN_NULL_AMBE, 9U); + + LogWarning(LOG_RF, NXDN_MESSAGE_TYPE_VCALL ", exceeded lost audio threshold, filling in"); + } m_rfErrs += errors; m_rfBits += 94U; @@ -519,8 +543,19 @@ bool Voice::process(uint8_t usc, uint8_t option, uint8_t* data, uint32_t len) uint32_t errors = 0U; - errors += ambe.regenerateNXDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES); + errors += ambe.regenerateNXDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 0U); errors += ambe.regenerateNXDN(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 9U); + + // replace audio with silence in cases where the error rate + // has exceeded the configured threshold + if (errors > (m_silenceThreshold / 2U)) { + // bryanb: this is probably the wrong way to go about this... + // generate null audio + ::memcpy(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 0U, NXDN_NULL_AMBE, 9U); + ::memcpy(data + 2U + NXDN_FSW_LICH_SACCH_LENGTH_BYTES + 9U, NXDN_NULL_AMBE, 9U); + + LogWarning(LOG_RF, NXDN_MESSAGE_TYPE_VCALL ", exceeded lost audio threshold, filling in"); + } m_rfErrs += errors; m_rfBits += 94U; diff --git a/p25/SiteData.h b/p25/SiteData.h index 49256ee3..5b80c386 100644 --- a/p25/SiteData.h +++ b/p25/SiteData.h @@ -165,11 +165,11 @@ namespace p25 channelId = 15U; // channel number clamping - if (m_channelNo == 0U) { // clamp to 1 - m_channelNo = 1U; + if (channelNo == 0U) { // clamp to 1 + channelNo = 1U; } - if (m_channelNo > 4095U) { // clamp to 4095 - m_channelNo = 4095U; + if (channelNo > 4095U) { // clamp to 4095 + channelNo = 4095U; } m_lra = 0U; @@ -237,7 +237,7 @@ namespace p25 __READONLY_PROPERTY_PLAIN(uint8_t, channelId, channelId); /// Channel number. __READONLY_PROPERTY_PLAIN(uint32_t, channelNo, channelNo); - /// Channel number. + /// Service class. __READONLY_PROPERTY_PLAIN(uint8_t, serviceClass, serviceClass); /// Flag indicating whether this site data is for an adjacent site. __READONLY_PROPERTY_PLAIN(bool, isAdjSite, isAdjSite);