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);