diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9f1c8c66..ce779794 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -41,6 +41,8 @@ file(GLOB dvmhost_SRC
"dmr/lc/*.cpp"
"dmr/lc/csbk/*.h"
"dmr/lc/csbk/*.cpp"
+ "dmr/lookups/*.h"
+ "dmr/lookups/*.cpp"
"dmr/packet*.h"
"dmr/packet/*.cpp"
diff --git a/config.example.yml b/config.example.yml
index 53352833..98c3132f 100644
--- a/config.example.yml
+++ b/config.example.yml
@@ -307,6 +307,10 @@ system:
# (By default this should be true, unless you are operating in a mode that will dynamically permit
# TGIDs via the permit-tg RCON command.)
authoritative: true
+ # Flag indicating whether or not this host sends permit-tg commands to voice channels for TGID operations.
+ # (By default this should be false, unless you are operating in a mode that will dynamically permit
+ # TGIDs via the permit-tg RCON command.)
+ controlPermitTG: false
# Channel Identity (corresponds to the appropriate entry in the iden_table file).
channelId: 2
# Channel Number (used to calculate actual host frequency based on the identity table).
diff --git a/dmr/Control.cpp b/dmr/Control.cpp
index 59093131..b268f2a8 100644
--- a/dmr/Control.cpp
+++ b/dmr/Control.cpp
@@ -66,7 +66,7 @@ using namespace dmr;
/// Flag indicating whether DMR verbose logging is enabled.
Control::Control(bool authoritative, uint32_t colorCode, uint32_t callHang, uint32_t queueSize, bool embeddedLCOnly,
bool dumpTAData, uint32_t timeout, uint32_t tgHang, modem::Modem* modem, network::BaseNetwork* network, bool duplex,
- lookups::RadioIdLookup* ridLookup, lookups::TalkgroupIdLookup* tidLookup, lookups::IdenTableLookup* idenTable, lookups::RSSIInterpolator* rssiMapper,
+ ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupIdLookup* tidLookup, ::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssiMapper,
uint32_t jitter, bool dumpDataPacket, bool repeatDataPacket, bool dumpCSBKData, bool debug, bool verbose) :
m_authoritative(authoritative),
m_colorCode(colorCode),
@@ -110,6 +110,7 @@ Control::~Control()
/// Helper to set DMR configuration options.
///
/// Instance of the ConfigINI class.
+///
/// Voice Channel Number list.
/// Voice Channel data map.
/// DMR Network ID.
@@ -117,12 +118,14 @@ Control::~Control()
/// Channel ID.
/// Channel Number.
///
-void Control::setOptions(yaml::Node& conf, const std::vector voiceChNo, const std::unordered_map voiceChData,
+void Control::setOptions(yaml::Node& conf, bool controlPermitTG, const std::vector voiceChNo, const std::unordered_map voiceChData,
uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool printOptions)
{
yaml::Node systemConf = conf["system"];
yaml::Node dmrProtocol = conf["protocols"]["dmr"];
+ m_controlPermitTG = controlPermitTG;
+
Slot::m_verifyReg = dmrProtocol["verifyReg"].as(false);
uint8_t nRandWait = (uint8_t)dmrProtocol["nRandWait"].as(dmr::DEFAULT_NRAND_WAIT);
@@ -149,9 +152,11 @@ void Control::setOptions(yaml::Node& conf, const std::vector voiceChNo
switch (m_tsccSlotNo) {
case 1U:
m_slot1->setTSCC(enableTSCC, dedicatedTSCC);
+ m_slot1->setControlPermitTG(m_controlPermitTG);
break;
case 2U:
m_slot2->setTSCC(enableTSCC, dedicatedTSCC);
+ m_slot2->setControlPermitTG(m_controlPermitTG);
break;
default:
LogError(LOG_DMR, "DMR, invalid slot, TSCC disabled, slotNo = %u", m_tsccSlotNo);
diff --git a/dmr/Control.h b/dmr/Control.h
index 4a662c18..d73c11eb 100644
--- a/dmr/Control.h
+++ b/dmr/Control.h
@@ -63,13 +63,13 @@ namespace dmr
/// Initializes a new instance of the Control class.
Control(bool authoritative, uint32_t colorCode, uint32_t callHang, uint32_t queueSize, bool embeddedLCOnly,
bool dumpTAData, uint32_t timeout, uint32_t tgHang, modem::Modem* modem, network::BaseNetwork* network, bool duplex,
- lookups::RadioIdLookup* ridLookup, lookups::TalkgroupIdLookup* tidLookup, lookups::IdenTableLookup* idenTable, lookups::RSSIInterpolator* rssi,
+ ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupIdLookup* tidLookup, ::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssi,
uint32_t jitter, bool dumpDataPacket, bool repeatDataPacket, bool dumpCSBKData, bool debug, bool verbose);
/// Finalizes a instance of the Control class.
~Control();
/// Helper to set DMR configuration options.
- void setOptions(yaml::Node& conf, const std::vector voiceChNo, const std::unordered_map voiceChData,
+ void setOptions(yaml::Node& conf, bool controlPermitTG, const std::vector voiceChNo, const std::unordered_map voiceChData,
uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool printOptions);
/// Gets a flag indicating whether the DMR control channel is running.
@@ -121,6 +121,7 @@ namespace dmr
friend class Slot;
bool m_authoritative;
+ bool m_controlPermitTG;
uint32_t m_colorCode;
@@ -130,9 +131,9 @@ namespace dmr
Slot* m_slot1;
Slot* m_slot2;
- lookups::IdenTableLookup* m_idenTable;
- lookups::RadioIdLookup* m_ridLookup;
- lookups::TalkgroupIdLookup* m_tidLookup;
+ ::lookups::IdenTableLookup* m_idenTable;
+ ::lookups::RadioIdLookup* m_ridLookup;
+ ::lookups::TalkgroupIdLookup* m_tidLookup;
uint8_t m_tsccSlotNo;
bool m_ccRunning;
diff --git a/dmr/Slot.cpp b/dmr/Slot.cpp
index ddcbce84..65d8a809 100644
--- a/dmr/Slot.cpp
+++ b/dmr/Slot.cpp
@@ -71,16 +71,16 @@ network::BaseNetwork* Slot::m_network = nullptr;
bool Slot::m_duplex = true;
-lookups::IdenTableLookup* Slot::m_idenTable = nullptr;
-lookups::RadioIdLookup* Slot::m_ridLookup = nullptr;
-lookups::TalkgroupIdLookup* Slot::m_tidLookup = nullptr;
-lookups::AffiliationLookup *Slot::m_affiliations = nullptr;
+::lookups::IdenTableLookup* Slot::m_idenTable = nullptr;
+::lookups::RadioIdLookup* Slot::m_ridLookup = nullptr;
+::lookups::TalkgroupIdLookup* Slot::m_tidLookup = nullptr;
+dmr::lookups::DMRAffiliationLookup *Slot::m_affiliations = nullptr;
-lookups::IdenTable Slot::m_idenEntry = lookups::IdenTable();
+::lookups::IdenTable Slot::m_idenEntry = ::lookups::IdenTable();
uint32_t Slot::m_hangCount = 3U * 17U;
-lookups::RSSIInterpolator* Slot::m_rssiMapper = nullptr;
+::lookups::RSSIInterpolator* Slot::m_rssiMapper = nullptr;
uint32_t Slot::m_jitterTime = 360U;
uint32_t Slot::m_jitterSlots = 6U;
@@ -574,6 +574,7 @@ void Slot::setTSCC(bool enable, bool dedicated)
m_dedicatedTSCC = dedicated;
if (m_enableTSCC) {
m_modem->setDMRIgnoreCACH_AT(m_slotNo);
+ m_affiliations->setSlotForChannelTSCC(m_channelNo, m_slotNo);
}
}
@@ -606,8 +607,8 @@ void Slot::setSilenceThreshold(uint32_t threshold)
///
///
void Slot::init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData siteData, bool embeddedLCOnly, bool dumpTAData, uint32_t callHang, modem::Modem* modem,
- network::BaseNetwork* network, bool duplex, lookups::RadioIdLookup* ridLookup, lookups::TalkgroupIdLookup* tidLookup,
- lookups::IdenTableLookup* idenTable, lookups::RSSIInterpolator* rssiMapper, uint32_t jitter, bool verbose)
+ network::BaseNetwork* network, bool duplex, ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupIdLookup* tidLookup,
+ ::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssiMapper, uint32_t jitter, bool verbose)
{
assert(dmr != nullptr);
assert(modem != nullptr);
@@ -635,7 +636,7 @@ void Slot::init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData s
m_idenTable = idenTable;
m_ridLookup = ridLookup;
m_tidLookup = tidLookup;
- m_affiliations = new lookups::AffiliationLookup("DMR Affiliations", verbose);
+ m_affiliations = new dmr::lookups::DMRAffiliationLookup(verbose);
m_hangCount = callHang * 17U;
@@ -666,15 +667,15 @@ void Slot::init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData s
/// Channel ID.
/// Channel Number.
///
-void Slot::setSiteData(const std::vector voiceChNo, const std::unordered_map voiceChData,
+void Slot::setSiteData(const std::vector voiceChNo, const std::unordered_map voiceChData,
uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool requireReg)
{
m_siteData = SiteData(SITE_MODEL_SMALL, netId, siteId, 3U, requireReg);
m_channelNo = channelNo;
- std::vector entries = m_idenTable->list();
+ std::vector<::lookups::IdenTable> entries = m_idenTable->list();
for (auto it = entries.begin(); it != entries.end(); ++it) {
- lookups::IdenTable entry = *it;
+ ::lookups::IdenTable entry = *it;
if (entry.channelId() == channelId) {
m_idenEntry = entry;
break;
diff --git a/dmr/Slot.h b/dmr/Slot.h
index 8354a1c6..2aff50a5 100644
--- a/dmr/Slot.h
+++ b/dmr/Slot.h
@@ -34,6 +34,7 @@
#include "Defines.h"
#include "dmr/Control.h"
#include "dmr/SiteData.h"
+#include "dmr/lookups/DMRAffiliationLookup.h"
#include "dmr/packet/ControlSignaling.h"
#include "dmr/packet/Data.h"
#include "dmr/packet/Voice.h"
@@ -43,7 +44,6 @@
#include "lookups/IdenTableLookup.h"
#include "lookups/RadioIdLookup.h"
#include "lookups/TalkgroupIdLookup.h"
-#include "lookups/AffiliationLookup.h"
#include "RingBuffer.h"
#include "StopWatch.h"
#include "Timer.h"
@@ -105,15 +105,17 @@ namespace dmr
/// Helper to enable and configure TSCC support for this slot.
void setTSCC(bool enable, bool dedicated);
+ /// Sets a flag indicating whether the DMR control channel can send permit-tg to voice channels.
+ void setControlPermitTG(bool controlPermitTG) { m_controlPermitTG = controlPermitTG; }
/// Helper to set the voice error silence threshold.
void setSilenceThreshold(uint32_t threshold);
/// Helper to initialize the slot processor.
static void init(Control* dmr, bool authoritative, uint32_t colorCode, SiteData siteData, bool embeddedLCOnly, bool dumpTAData, uint32_t callHang, modem::Modem* modem,
- network::BaseNetwork* network, bool duplex, lookups::RadioIdLookup* ridLookup, lookups::TalkgroupIdLookup* tidLookup,
- lookups::IdenTableLookup* idenTable, lookups::RSSIInterpolator* rssiMapper, uint32_t jitter, bool verbose);
+ network::BaseNetwork* network, bool duplex, ::lookups::RadioIdLookup* ridLookup, ::lookups::TalkgroupIdLookup* tidLookup,
+ ::lookups::IdenTableLookup* idenTable, ::lookups::RSSIInterpolator* rssiMapper, uint32_t jitter, bool verbose);
/// Sets local configured site data.
- static void setSiteData(const std::vector voiceChNo, const std::unordered_map voiceChData,
+ static void setSiteData(const std::vector voiceChNo, const std::unordered_map voiceChData,
uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool requireReq);
/// Sets TSCC Aloha configuration.
static void setAlohaConfig(uint8_t nRandWait, uint8_t backOff);
@@ -188,6 +190,8 @@ namespace dmr
bool m_enableTSCC;
bool m_dedicatedTSCC;
+ bool m_controlPermitTG;
+
bool m_verbose;
bool m_debug;
@@ -208,16 +212,16 @@ namespace dmr
static bool m_duplex;
- static lookups::IdenTableLookup* m_idenTable;
- static lookups::RadioIdLookup* m_ridLookup;
- static lookups::TalkgroupIdLookup* m_tidLookup;
- static lookups::AffiliationLookup* m_affiliations;
+ static ::lookups::IdenTableLookup* m_idenTable;
+ static ::lookups::RadioIdLookup* m_ridLookup;
+ static ::lookups::TalkgroupIdLookup* m_tidLookup;
+ static lookups::DMRAffiliationLookup* m_affiliations;
- static lookups::IdenTable m_idenEntry;
+ static ::lookups::IdenTable m_idenEntry;
static uint32_t m_hangCount;
- static lookups::RSSIInterpolator* m_rssiMapper;
+ static ::lookups::RSSIInterpolator* m_rssiMapper;
static uint32_t m_jitterTime;
static uint32_t m_jitterSlots;
diff --git a/dmr/lc/CSBK.cpp b/dmr/lc/CSBK.cpp
index db53593c..b7ff2299 100644
--- a/dmr/lc/CSBK.cpp
+++ b/dmr/lc/CSBK.cpp
@@ -88,7 +88,7 @@ CSBK::CSBK() :
m_logicalCh1(DMR_CHNULL),
m_logicalCh2(DMR_CHNULL),
m_slotNo(0U),
- m_siteIdenEntry(lookups::IdenTable())
+ m_siteIdenEntry(::lookups::IdenTable())
{
/* stub */
}
diff --git a/dmr/lc/CSBK.h b/dmr/lc/CSBK.h
index a8bbb28b..b0d2d1b1 100644
--- a/dmr/lc/CSBK.h
+++ b/dmr/lc/CSBK.h
@@ -133,7 +133,7 @@ namespace dmr
/** Local Site data */
/// Local Site Identity Entry.
- __PROTECTED_PROPERTY_PLAIN(lookups::IdenTable, siteIdenEntry, siteIdenEntry);
+ __PROTECTED_PROPERTY_PLAIN(::lookups::IdenTable, siteIdenEntry, siteIdenEntry);
protected:
static bool m_verbose;
diff --git a/dmr/lookups/DMRAffiliationLookup.cpp b/dmr/lookups/DMRAffiliationLookup.cpp
new file mode 100644
index 00000000..2e062fa6
--- /dev/null
+++ b/dmr/lookups/DMRAffiliationLookup.cpp
@@ -0,0 +1,316 @@
+/**
+* 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 Software
+*
+*/
+/*
+* Copyright (C) 2023 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.
+*/
+#include "dmr/lookups/DMRAffiliationLookup.h"
+#include "Log.h"
+
+using namespace dmr::lookups;
+
+#include
+#include
+#include
+#include
+
+// ---------------------------------------------------------------------------
+// Public Class Members
+// ---------------------------------------------------------------------------
+
+///
+/// Initializes a new instance of the DMRAffiliationLookup class.
+///
+/// Flag indicating whether verbose logging is enabled.
+DMRAffiliationLookup::DMRAffiliationLookup(bool verbose) : ::lookups::AffiliationLookup("DMR Affiliation", verbose),
+ m_grantChSlotTable(),
+ m_tsccChNo(0U),
+ m_tsccSlot(0U)
+{
+ /* stub */
+}
+
+///
+/// Finalizes a instance of the DMRAffiliationLookup class.
+///
+DMRAffiliationLookup::~DMRAffiliationLookup()
+{
+ /* stub */
+}
+
+///
+/// Helper to grant a channel.
+///
+///
+///
+///
+bool DMRAffiliationLookup::grantCh(uint32_t dstId, uint32_t grantTimeout)
+{
+ uint32_t chNo = m_rfChTable.at(0);
+ uint8_t slot = getAvailableSlotForChannel(chNo);
+
+ if (slot == 0U) {
+ return false;
+ }
+
+ return grantChSlot(dstId, slot, grantTimeout);
+}
+
+///
+/// Helper to grant a channel and slot.
+///
+///
+///
+///
+///
+bool DMRAffiliationLookup::grantChSlot(uint32_t dstId, uint8_t slot, uint32_t grantTimeout)
+{
+ if (dstId == 0U) {
+ return false;
+ }
+
+ if (!isRFChAvailable()) {
+ return false;
+ }
+
+ uint32_t chNo = m_rfChTable.at(0);
+ if (chNo == m_tsccChNo && slot == m_tsccSlot) {
+ return false;
+ }
+
+ if (getAvailableSlotForChannel(chNo) == 0U || chNo == m_tsccChNo) {
+ auto it = std::find(m_rfChTable.begin(), m_rfChTable.end(), chNo);
+ m_rfChTable.erase(it);
+ }
+
+ m_grantChTable[dstId] = chNo;
+ m_grantChSlotTable[dstId] = std::make_tuple(chNo, slot);
+ m_rfGrantChCnt++;
+
+ m_grantTimers[dstId] = Timer(1000U, grantTimeout);
+ m_grantTimers[dstId].start();
+
+ if (m_verbose) {
+ LogMessage(LOG_HOST, "%s, granting channel, chNo = %u, slot = %u, dstId = %u",
+ m_name, chNo, slot, dstId);
+ }
+
+ return true;
+}
+
+///
+/// Helper to release the channel grant for the destination ID.
+///
+///
+///
+bool DMRAffiliationLookup::releaseGrant(uint32_t dstId, bool releaseAll)
+{
+ if (dstId == 0U && !releaseAll) {
+ return false;
+ }
+
+ // are we trying to release all grants?
+ if (dstId == 0U && releaseAll) {
+ LogWarning(LOG_HOST, "%s, force releasing all channel grants", m_name);
+
+ std::vector gntsToRel = std::vector();
+ for (auto it = m_grantChTable.begin(); it != m_grantChTable.end(); ++it) {
+ uint32_t dstId = it->first;
+ gntsToRel.push_back(dstId);
+ }
+
+ // release grants
+ for (auto it = gntsToRel.begin(); it != gntsToRel.end(); ++it) {
+ releaseGrant(*it, false);
+ }
+
+ return true;
+ }
+
+ if (isGranted(dstId)) {
+ uint32_t chNo = m_grantChTable.at(dstId);
+ std::tuple slotData = m_grantChSlotTable.at(dstId);
+ uint8_t slot = std::get<1>(slotData);
+
+ if (m_verbose) {
+ LogMessage(LOG_HOST, "%s, releasing channel grant, chNo = %u, slot = %u, dstId = %u",
+ m_name, chNo, slot, dstId);
+ }
+
+ m_grantChTable[dstId] = 0U;
+ m_grantChSlotTable.erase(dstId);
+
+ auto it = std::find(m_rfChTable.begin(), m_rfChTable.end(), chNo);
+ if (it == m_rfChTable.end()) {
+ m_rfChTable.push_back(chNo);
+ }
+
+ if (m_rfGrantChCnt > 0U) {
+ m_rfGrantChCnt--;
+ }
+ else {
+ m_rfGrantChCnt = 0U;
+ }
+
+ m_grantTimers[dstId].stop();
+ return true;
+ }
+
+ return false;
+}
+
+///
+/// Helper to determine if the channel number is busy.
+///
+///
+///
+bool DMRAffiliationLookup::isChBusy(uint32_t chNo) const
+{
+ if (chNo == 0U) {
+ return false;
+ }
+
+ // lookup dynamic channel grant table entry
+ for (auto it = m_grantChTable.begin(); it != m_grantChTable.end(); ++it) {
+ if (it->second == chNo) {
+ uint8_t slotCount = 0U;
+ if (chNo == m_tsccChNo) {
+ slotCount++; // one slot is *always* used for TSCC
+ }
+
+ for (auto it = m_grantChSlotTable.begin(); it != m_grantChSlotTable.end(); ++it) {
+ uint32_t foundChNo = std::get<0>(it->second);
+ if (foundChNo == chNo)
+ slotCount++;
+ }
+
+ if (slotCount == 2U) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ return false;
+}
+
+///
+/// Helper to get the slot granted for the given destination ID.
+///
+///
+///
+uint8_t DMRAffiliationLookup::getGrantedSlot(uint32_t dstId) const
+{
+ if (dstId == 0U) {
+ return 0U;
+ }
+
+ // lookup dynamic channel grant table entry
+ for (auto it = m_grantChSlotTable.begin(); it != m_grantChSlotTable.end(); ++it) {
+ if (it->first == dstId) {
+ uint8_t slot = std::get<1>(it->second);
+ return slot;
+ }
+ }
+
+ return 0U;
+}
+
+///
+/// Helper to set a slot for the given channel as being the TSCC.
+///
+///
+///
+///
+void DMRAffiliationLookup::setSlotForChannelTSCC(uint32_t chNo, uint8_t slot)
+{
+ assert(chNo != 0U);
+ if ((slot == 0U) || (slot > 2U)) {
+ return;
+ }
+
+ m_tsccChNo = chNo;
+ m_tsccSlot = slot;
+}
+
+///
+/// Helper to determine the first available slot for given the channel number.
+///
+///
+///
+uint8_t DMRAffiliationLookup::getAvailableSlotForChannel(uint32_t chNo) const
+{
+ if (chNo == 0U) {
+ return 0U;
+ }
+
+ uint8_t slot = 1U;
+
+ // lookup dynamic channel slot grant table entry
+ bool grantedSlot = false;
+ int slotCount = 0U;
+ for (auto it = m_grantChSlotTable.begin(); it != m_grantChSlotTable.end(); ++it) {
+ uint32_t foundChNo = std::get<0>(it->second);
+ if (foundChNo == chNo)
+ {
+ uint8_t foundSlot = std::get<1>(it->second);
+ if (slot == foundSlot) {
+ switch (foundSlot) {
+ case 1U:
+ slot = 2U;
+ break;
+ case 2U:
+ slot = 1U;
+ break;
+ }
+
+ grantedSlot = true;
+ slotCount++;
+ }
+ }
+ }
+
+ if (slotCount == 2U) {
+ slot = 0U;
+ return slot;
+ }
+
+ // are we trying to assign the TSCC slot?
+ if (chNo == m_tsccChNo && slot == m_tsccSlot) {
+ if (!grantedSlot) {
+ // since we didn't find a slot being granted out -- utilize the slot opposing the TSCC
+ switch (m_tsccSlot) {
+ case 1U:
+ slot = 2U;
+ break;
+ case 2U:
+ slot = 1U;
+ break;
+ }
+ } else {
+ slot = 0U; // TSCC is not assignable
+ }
+ }
+
+ return slot;
+}
diff --git a/dmr/lookups/DMRAffiliationLookup.h b/dmr/lookups/DMRAffiliationLookup.h
new file mode 100644
index 00000000..149ea0cc
--- /dev/null
+++ b/dmr/lookups/DMRAffiliationLookup.h
@@ -0,0 +1,77 @@
+/**
+* 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 Software
+*
+*/
+/*
+* Copyright (C) 2023 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(__DMR_AFFILIATION_LOOKUP_H__)
+#define __DMR_AFFILIATION_LOOKUP_H__
+
+#include "Defines.h"
+#include "lookups/AffiliationLookup.h"
+
+#include
+
+namespace dmr
+{
+ namespace lookups
+ {
+ // ---------------------------------------------------------------------------
+ // Class Declaration
+ // Implements a lookup table class that contains DMR slot grant
+ // information.
+ // ---------------------------------------------------------------------------
+
+ class HOST_SW_API DMRAffiliationLookup : public ::lookups::AffiliationLookup {
+ public:
+ /// Initializes a new instance of the DMRAffiliationLookup class.
+ DMRAffiliationLookup(bool verbose);
+ /// Finalizes a instance of the DMRAffiliationLookup class.
+ virtual ~DMRAffiliationLookup();
+
+ /// Helper to grant a channel.
+ virtual bool grantCh(uint32_t dstId, uint32_t grantTimeout);
+ /// Helper to grant a channel and slot.
+ bool grantChSlot(uint32_t dstId, uint8_t slot, uint32_t grantTimeout);
+ /// Helper to release the channel grant for the destination ID.
+ virtual bool releaseGrant(uint32_t dstId, bool releaseAll);
+ /// Helper to determine if the channel number is busy.
+ virtual bool isChBusy(uint32_t chNo) const;
+ /// Helper to get the slot granted for the given destination ID.
+ uint8_t getGrantedSlot(uint32_t dstId) const;
+
+ /// Helper to set a slot for the given channel as being the TSCC.
+ void setSlotForChannelTSCC(uint32_t chNo, uint8_t slot);
+
+ /// Helper to determine the first available slot for given the channel number.
+ uint8_t getAvailableSlotForChannel(uint32_t chNo) const;
+
+ protected:
+ std::unordered_map> m_grantChSlotTable;
+
+ uint32_t m_tsccChNo;
+ uint8_t m_tsccSlot;
+ };
+ } // namespace lookups
+} // namespace dmr
+
+#endif // __DMR_AFFILIATION_LOOKUP_H__
diff --git a/dmr/packet/ControlSignaling.cpp b/dmr/packet/ControlSignaling.cpp
index eba4bae8..c17ae6dd 100644
--- a/dmr/packet/ControlSignaling.cpp
+++ b/dmr/packet/ControlSignaling.cpp
@@ -38,6 +38,7 @@
#include "dmr/Sync.h"
#include "edac/BPTC19696.h"
#include "edac/CRC.h"
+#include "remote/RemoteCommand.h"
#include "Log.h"
#include "Utils.h"
@@ -737,6 +738,8 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_
{
Slot *m_tscc = m_slot->m_dmr->getTSCCSlot();
+ uint8_t slot = 0U;
+
bool emergency = ((serviceOptions & 0xFFU) & 0x80U) == 0x80U; // Emergency Flag
bool privacy = ((serviceOptions & 0xFFU) & 0x40U) == 0x40U; // Privacy Flag
bool broadcast = ((serviceOptions & 0xFFU) & 0x10U) == 0x10U; // Broadcast Flag
@@ -812,13 +815,14 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_
else {
if (m_tscc->m_affiliations->grantCh(dstId, GRANT_TIMER_TIMEOUT)) {
chNo = m_tscc->m_affiliations->getGrantedCh(dstId);
-
+ slot = m_tscc->m_affiliations->getGrantedSlot(dstId);
//m_tscc->m_siteData.setChCnt(m_tscc->m_affiliations->getRFChCnt() + m_tscc->m_affiliations->getGrantedRFChCnt());
}
}
}
else {
chNo = m_tscc->m_affiliations->getGrantedCh(dstId);
+ slot = m_tscc->m_affiliations->getGrantedSlot(dstId);
m_tscc->m_affiliations->touchGrant(dstId);
}
@@ -829,15 +833,23 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_
::ActivityLog("DMR", true, "Slot %u group grant request from %u to TG %u", m_tscc->m_slotNo, srcId, dstId);
}
- //
- // TODO TODO: Implement RCON callback for authoritative CC to trigger permit-tg
- //
+ // callback RCON to permit-tg on the specified voice channel
+ if (m_tscc->m_authoritative && m_tscc->m_controlPermitTG) {
+ ::lookups::VoiceChData voiceChData = m_tscc->m_affiliations->getRFChData(chNo);
+ if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) {
+ std::stringstream ss;
+ ss << "permit-tg " << modem::DVM_STATE::STATE_DMR << " " << dstId << " " << slot;
+
+ RemoteCommand::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
+ ss.str(), m_tscc->m_debug);
+ }
+ }
std::unique_ptr csbk = new_unique(CSBK_TV_GRANT);
if (broadcast)
csbk->setCSBKO(CSBKO_BTV_GRANT);
csbk->setLogicalCh1(chNo);
- csbk->setSlotNo(1U); // eah? this can't be okay...
+ csbk->setSlotNo(slot);
if (m_verbose) {
LogMessage((net) ? LOG_NET : LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_GRP_VOICE_CALL (Group Voice Call), emerg = %u, privacy = %u, broadcast = %u, prio = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u",
@@ -856,13 +868,21 @@ bool ControlSignaling::writeRF_CSBK_Grant(uint32_t srcId, uint32_t dstId, uint8_
::ActivityLog("DMR", true, "Slot %u individual grant request from %u to TG %u", m_tscc->m_slotNo, srcId, dstId);
}
- //
- // TODO TODO: Implement RCON callback for authoritative CC to trigger permit-tg
- //
+ // callback RCON to permit-tg on the specified voice channel
+ if (m_tscc->m_authoritative && m_tscc->m_controlPermitTG) {
+ ::lookups::VoiceChData voiceChData = m_tscc->m_affiliations->getRFChData(chNo);
+ if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0) {
+ std::stringstream ss;
+ ss << "permit-tg " << modem::DVM_STATE::STATE_DMR << " " << dstId << " " << slot;
+
+ RemoteCommand::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
+ ss.str(), m_tscc->m_debug);
+ }
+ }
std::unique_ptr csbk = new_unique(CSBK_PV_GRANT);
csbk->setLogicalCh1(chNo);
- csbk->setSlotNo(1U); // eah? this can't be okay...
+ csbk->setSlotNo(slot);
if (m_verbose) {
LogMessage((net) ? LOG_NET : LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_IND_VOICE_CALL (Individual Voice Call), emerg = %u, privacy = %u, broadcast = %u, prio = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u",
@@ -895,6 +915,8 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u
{
Slot *m_tscc = m_slot->m_dmr->getTSCCSlot();
+ uint8_t slot = 0U;
+
bool emergency = ((serviceOptions & 0xFFU) & 0x80U) == 0x80U; // Emergency Flag
bool privacy = ((serviceOptions & 0xFFU) & 0x40U) == 0x40U; // Privacy Flag
bool broadcast = ((serviceOptions & 0xFFU) & 0x10U) == 0x10U; // Broadcast Flag
@@ -970,6 +992,7 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u
else {
if (m_tscc->m_affiliations->grantCh(dstId, GRANT_TIMER_TIMEOUT)) {
chNo = m_tscc->m_affiliations->getGrantedCh(dstId);
+ slot = m_tscc->m_affiliations->getGrantedSlot(dstId);
//m_tscc->m_siteData.setChCnt(m_tscc->m_affiliations->getRFChCnt() + m_tscc->m_affiliations->getGrantedRFChCnt());
}
@@ -977,6 +1000,7 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u
}
else {
chNo = m_tscc->m_affiliations->getGrantedCh(dstId);
+ slot = m_tscc->m_affiliations->getGrantedSlot(dstId);
m_tscc->m_affiliations->touchGrant(dstId);
}
@@ -990,7 +1014,7 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u
std::unique_ptr csbk = new_unique(CSBK_TD_GRANT);
csbk->setLogicalCh1(chNo);
- csbk->setSlotNo(1U); // eah? this can't be okay...
+ csbk->setSlotNo(slot);
if (m_verbose) {
LogMessage((net) ? LOG_NET : LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_GRP_DATA_CALL (Group Data Call), emerg = %u, privacy = %u, broadcast = %u, prio = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u",
@@ -1011,7 +1035,7 @@ bool ControlSignaling::writeRF_CSBK_Data_Grant(uint32_t srcId, uint32_t dstId, u
std::unique_ptr csbk = new_unique(CSBK_PD_GRANT);
csbk->setLogicalCh1(chNo);
- csbk->setSlotNo(1U); // eah? this can't be okay...
+ csbk->setSlotNo(slot);
if (m_verbose) {
LogMessage((net) ? LOG_NET : LOG_RF, "DMR Slot %u, DT_CSBK, CSBKO_RAND (Random Access), SVC_KIND_IND_DATA_CALL (Individual Data Call), emerg = %u, privacy = %u, broadcast = %u, prio = %u, chNo = %u, slot = %u, srcId = %u, dstId = %u",
diff --git a/host/Host.cpp b/host/Host.cpp
index 697136b6..0a3f0e98 100644
--- a/host/Host.cpp
+++ b/host/Host.cpp
@@ -140,6 +140,7 @@ Host::Host(const std::string& confFile) :
m_p25QueueSizeBytes(2592U), // 12 frames
m_nxdnQueueSizeBytes(1488U), // 31 frames
m_authoritative(true),
+ m_controlPermitTG(false),
m_activeTickDelay(5U),
m_idleTickDelay(5U),
m_remoteControl(nullptr)
@@ -433,7 +434,7 @@ int Host::run()
dmr = std::unique_ptr(new dmr::Control(m_authoritative, m_dmrColorCode, callHang, m_dmrQueueSizeBytes, embeddedLCOnly, dumpTAData, m_timeout, m_rfTalkgroupHang,
m_modem, m_network, m_duplex, m_ridLookup, m_tidLookup, m_idenTable, rssi, jitter, dmrDumpDataPacket, dmrRepeatDataPacket,
dmrDumpCsbkData, dmrDebug, dmrVerbose));
- dmr->setOptions(m_conf, m_voiceChNo, m_voiceChData, m_dmrNetId, m_siteId, m_channelId, m_channelNo, true);
+ dmr->setOptions(m_conf, m_controlPermitTG, m_voiceChNo, m_voiceChData, m_dmrNetId, m_siteId, m_channelId, m_channelNo, true);
if (dmrCtrlChannel) {
dmr->setCCRunning(true);
@@ -506,7 +507,7 @@ int Host::run()
p25 = std::unique_ptr(new p25::Control(m_authoritative, m_p25NAC, callHang, m_p25QueueSizeBytes, m_modem, m_network, m_timeout, m_rfTalkgroupHang,
m_duplex, m_ridLookup, m_tidLookup, m_idenTable, rssi, p25DumpDataPacket, p25RepeatDataPacket,
p25DumpTsbkData, p25Debug, p25Verbose));
- p25->setOptions(m_conf, m_cwCallsign, m_voiceChNo, m_voiceChData, m_p25PatchSuperGroup, m_p25NetId, m_p25SysId, m_p25RfssId,
+ p25->setOptions(m_conf, m_controlPermitTG, m_cwCallsign, m_voiceChNo, m_voiceChData, m_p25PatchSuperGroup, m_p25NetId, m_p25SysId, m_p25RfssId,
m_siteId, m_channelId, m_channelNo, true);
if (p25CtrlChannel) {
@@ -571,7 +572,7 @@ int Host::run()
nxdn = std::unique_ptr(new nxdn::Control(m_authoritative, m_nxdnRAN, callHang, m_nxdnQueueSizeBytes, m_timeout, m_rfTalkgroupHang,
m_modem, m_network, m_duplex, m_ridLookup, m_tidLookup, m_idenTable, rssi,
nxdnDumpRcchData, nxdnDebug, nxdnVerbose));
- nxdn->setOptions(m_conf, m_cwCallsign, m_voiceChNo, m_voiceChData, m_siteId, m_channelId, m_channelNo, true);
+ nxdn->setOptions(m_conf, m_controlPermitTG, m_cwCallsign, m_voiceChNo, m_voiceChData, m_siteId, m_channelId, m_channelNo, true);
if (nxdnCtrlChannel) {
nxdn->setCCRunning(true);
@@ -1925,7 +1926,11 @@ bool Host::readParams()
LogInfo(" P25 RFSS Id: $%02X", m_p25RfssId);
if (!m_authoritative) {
+ m_controlPermitTG = false;
LogWarning(LOG_HOST, "Host is non-authoritative, this requires RCON to \"permit-tg\" for VCs and \"grant-tg\" for CCs!");
+ } else {
+ m_controlPermitTG = rfssConfig["controlPermitTG"].as(false);
+ LogInfo(" Control Permit TG: %s", m_controlPermitTG ? "yes" : "no");
}
}
else {
diff --git a/host/Host.h b/host/Host.h
index 6d925b09..ce05b1cc 100644
--- a/host/Host.h
+++ b/host/Host.h
@@ -147,6 +147,7 @@ private:
uint32_t m_nxdnQueueSizeBytes;
bool m_authoritative;
+ bool m_controlPermitTG;
uint8_t m_activeTickDelay;
uint8_t m_idleTickDelay;
diff --git a/nxdn/Control.cpp b/nxdn/Control.cpp
index 5ae6f256..8d759b33 100644
--- a/nxdn/Control.cpp
+++ b/nxdn/Control.cpp
@@ -94,6 +94,7 @@ Control::Control(bool authoritative, uint32_t ran, uint32_t callHang, uint32_t q
m_voice(nullptr),
m_data(nullptr),
m_authoritative(authoritative),
+ m_controlPermitTG(false),
m_ran(ran),
m_timeout(timeout),
m_modem(modem),
@@ -203,6 +204,7 @@ void Control::reset()
/// Helper to set NXDN configuration options.
///
/// Instance of the yaml::Node class.
+///
///
/// Voice Channel Number list.
/// Voice Channel data map.
@@ -210,13 +212,15 @@ void Control::reset()
/// Channel ID.
/// Channel Number.
///
-void Control::setOptions(yaml::Node& conf, const std::string cwCallsign, const std::vector voiceChNo,
+void Control::setOptions(yaml::Node& conf, bool controlPermitTG, const std::string cwCallsign, const std::vector voiceChNo,
const std::unordered_map voiceChData, uint16_t locId,
uint8_t channelId, uint32_t channelNo, bool printOptions)
{
yaml::Node systemConf = conf["system"];
yaml::Node nxdnProtocol = conf["protocols"]["nxdn"];
+ m_controlPermitTG = controlPermitTG;
+
m_trunk->m_verifyAff = nxdnProtocol["verifyAff"].as(false);
m_trunk->m_verifyReg = nxdnProtocol["verifyReg"].as(false);
diff --git a/nxdn/Control.h b/nxdn/Control.h
index b3ded7c3..8cbf425a 100644
--- a/nxdn/Control.h
+++ b/nxdn/Control.h
@@ -83,7 +83,7 @@ namespace nxdn
void reset();
/// Helper to set NXDN configuration options.
- void setOptions(yaml::Node& conf, const std::string cwCallsign, const std::vector voiceChNo,
+ void setOptions(yaml::Node& conf, bool controlPermitTG, const std::string cwCallsign, const std::vector voiceChNo,
const std::unordered_map voiceChData, uint16_t locId,
uint8_t channelId, uint32_t channelNo, bool printOptions);
@@ -130,6 +130,7 @@ namespace nxdn
packet::Trunk* m_trunk;
bool m_authoritative;
+ bool m_controlPermitTG;
uint32_t m_ran;
uint32_t m_timeout;
diff --git a/nxdn/packet/Trunk.cpp b/nxdn/packet/Trunk.cpp
index f5290e6c..dc12b020 100644
--- a/nxdn/packet/Trunk.cpp
+++ b/nxdn/packet/Trunk.cpp
@@ -36,6 +36,7 @@
#include "nxdn/Sync.h"
#include "nxdn/NXDNUtils.h"
#include "edac/CRC.h"
+#include "remote/RemoteCommand.h"
#include "HostMain.h"
#include "Log.h"
#include "Utils.h"
@@ -508,9 +509,18 @@ bool Trunk::writeRF_Message_Grant(uint32_t srcId, uint32_t dstId, uint8_t servic
}
}
- //
- // TODO TODO: Implement RCON callback for authoritative CC to trigger permit-tg
- //
+ // callback RCON to permit-tg on the specified voice channel
+ if (m_nxdn->m_authoritative && m_nxdn->m_controlPermitTG) {
+ ::lookups::VoiceChData voiceChData = m_nxdn->m_affiliations.getRFChData(chNo);
+ if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 &&
+ chNo != m_nxdn->m_siteData.channelNo()) {
+ std::stringstream ss;
+ ss << "permit-tg " << modem::DVM_STATE::STATE_NXDN << " " << dstId;
+
+ RemoteCommand::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
+ ss.str(), m_nxdn->m_debug);
+ }
+ }
std::unique_ptr rcch = new_unique(rcch::MESSAGE_TYPE_VCALL_CONN);
rcch->setMessageType(RTCH_MESSAGE_TYPE_VCALL);
diff --git a/p25/Control.cpp b/p25/Control.cpp
index 709668b5..ea731cef 100644
--- a/p25/Control.cpp
+++ b/p25/Control.cpp
@@ -90,6 +90,7 @@ Control::Control(bool authoritative, uint32_t nac, uint32_t callHang, uint32_t q
m_data(nullptr),
m_trunk(nullptr),
m_authoritative(authoritative),
+ m_controlPermitTG(false),
m_nac(nac),
m_txNAC(nac),
m_timeout(timeout),
@@ -207,6 +208,7 @@ void Control::reset()
/// Helper to set P25 configuration options.
///
/// Instance of the yaml::Node class.
+///
///
/// Voice Channel Number list.
/// Voice Channel data map.
@@ -218,13 +220,15 @@ void Control::reset()
/// Channel ID.
/// Channel Number.
///
-void Control::setOptions(yaml::Node& conf, const std::string cwCallsign, const std::vector voiceChNo,
+void Control::setOptions(yaml::Node& conf, bool controlPermitTG, const std::string cwCallsign, const std::vector voiceChNo,
const std::unordered_map voiceChData, uint32_t pSuperGroup, uint32_t netId,
uint32_t sysId, uint8_t rfssId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool printOptions)
{
yaml::Node systemConf = conf["system"];
yaml::Node p25Protocol = conf["protocols"]["p25"];
+ m_controlPermitTG = controlPermitTG;
+
m_tduPreambleCount = p25Protocol["tduPreambleCount"].as(8U);
m_trunk->m_patchSuperGroup = pSuperGroup;
diff --git a/p25/Control.h b/p25/Control.h
index 59cc1261..5d7c1fda 100644
--- a/p25/Control.h
+++ b/p25/Control.h
@@ -85,7 +85,7 @@ namespace p25
void reset();
/// Helper to set P25 configuration options.
- void setOptions(yaml::Node& conf, const std::string cwCallsign, const std::vector voiceChNo,
+ void setOptions(yaml::Node& conf, bool controlPermitTG, const std::string cwCallsign, const std::vector voiceChNo,
const std::unordered_map voiceChData, uint32_t pSuperGroup, uint32_t netId,
uint32_t sysId, uint8_t rfssId, uint8_t siteId, uint8_t channelId, uint32_t channelNo, bool printOptions);
@@ -146,6 +146,7 @@ namespace p25
friend class lookups::P25AffiliationLookup;
bool m_authoritative;
+ bool m_controlPermitTG;
uint32_t m_nac;
uint32_t m_txNAC;
diff --git a/p25/packet/Trunk.cpp b/p25/packet/Trunk.cpp
index 48b41e4f..539b3b58 100644
--- a/p25/packet/Trunk.cpp
+++ b/p25/packet/Trunk.cpp
@@ -35,6 +35,7 @@
#include "p25/P25Utils.h"
#include "p25/Sync.h"
#include "edac/CRC.h"
+#include "remote/RemoteCommand.h"
#include "Log.h"
#include "Utils.h"
@@ -2221,9 +2222,18 @@ bool Trunk::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_t serviceOp
::ActivityLog("P25", true, "group grant request from %u to TG %u", srcId, dstId);
}
- //
- // TODO TODO: Implement RCON callback for authoritative CC to trigger permit-tg
- //
+ // callback RCON to permit-tg on the specified voice channel
+ if (m_p25->m_authoritative && m_p25->m_controlPermitTG) {
+ ::lookups::VoiceChData voiceChData = m_p25->m_affiliations.getRFChData(chNo);
+ if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 &&
+ chNo != m_p25->m_siteData.channelNo()) {
+ std::stringstream ss;
+ ss << "permit-tg " << modem::DVM_STATE::STATE_P25 << " " << dstId;
+
+ RemoteCommand::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
+ ss.str(), m_p25->m_debug);
+ }
+ }
std::unique_ptr iosp = new_unique(IOSP_GRP_VCH);
iosp->setMFId(m_lastMFID);
@@ -2247,9 +2257,18 @@ bool Trunk::writeRF_TSDU_Grant(uint32_t srcId, uint32_t dstId, uint8_t serviceOp
::ActivityLog("P25", true, "unit-to-unit grant request from %u to %u", srcId, dstId);
}
- //
- // TODO TODO: Implement RCON callback for authoritative CC to trigger permit-tg
- //
+ // callback RCON to permit-tg on the specified voice channel
+ if (m_p25->m_authoritative && m_p25->m_controlPermitTG) {
+ ::lookups::VoiceChData voiceChData = m_p25->m_affiliations.getRFChData(chNo);
+ if (voiceChData.isValidCh() && !voiceChData.address().empty() && voiceChData.port() > 0 &&
+ chNo != m_p25->m_siteData.channelNo()) {
+ std::stringstream ss;
+ ss << "permit-tg " << modem::DVM_STATE::STATE_P25 << " " << dstId;
+
+ RemoteCommand::send(voiceChData.address(), voiceChData.port(), voiceChData.password(),
+ ss.str(), m_p25->m_debug);
+ }
+ }
std::unique_ptr iosp = new_unique(IOSP_UU_VCH);
iosp->setMFId(m_lastMFID);
diff --git a/remote/RemoteCommand.cpp b/remote/RemoteCommand.cpp
index 4e486c7a..539e4766 100644
--- a/remote/RemoteCommand.cpp
+++ b/remote/RemoteCommand.cpp
@@ -96,6 +96,25 @@ RemoteCommand::~RemoteCommand()
/// EXIT_SUCCESS, if command was sent, otherwise EXIT_FAILURE.
int RemoteCommand::send(const std::string& command)
{
+ assert(!m_address.empty());
+ assert(m_port > 0U);
+
+ return send(m_address, m_port, m_password, command, m_debug);
+}
+
+///
+/// Sends remote control command to the specified modem.
+///
+/// Network Hostname/IP address to connect to.
+/// Network port number.
+/// Authentication password.
+/// Command string to send to remote modem.
+/// Flag indicating whether debug is enabled.
+/// EXIT_SUCCESS, if command was sent, otherwise EXIT_FAILURE.
+int RemoteCommand::send(const std::string& address, uint32_t port, const std::string& password, const std::string& command, bool debug)
+{
+ assert(!address.empty());
+ assert(port > 0U);
UDPSocket socket(0U);
bool ret = socket.open();
@@ -108,22 +127,22 @@ int RemoteCommand::send(const std::string& command)
sockaddr_storage addr;
uint32_t addrLen;
- if (UDPSocket::lookup(m_address, m_port, addr, addrLen) != 0) {
+ if (UDPSocket::lookup(address, port, addr, addrLen) != 0) {
::LogError(LOG_HOST, "Could not lookup the address of remote");
return ERRNO_ADDR_LOOKUP;
}
- ::LogInfoEx(LOG_HOST, "sending RCON command \"%s\" to %s:%u", command.c_str(), m_address.c_str(), m_port);
+ ::LogInfoEx(LOG_HOST, "sending RCON command \"%s\" to %s:%u", command.c_str(), address.c_str(), port);
buffer[0U] = RCON_FRAME_START;
buffer[1U] = START_OF_TEXT;
- if (!m_password.empty()) {
- size_t size = m_password.size();
+ if (!password.empty()) {
+ size_t size = password.size();
uint8_t* in = new uint8_t[size];
for (size_t i = 0U; i < size; i++)
- in[i] = m_password.at(i);
+ in[i] = password.at(i);
uint8_t out[32U];
::memset(out, 0x00U, 32U);
@@ -139,7 +158,7 @@ int RemoteCommand::send(const std::string& command)
buffer[35U + command.size()] = END_OF_TEXT;
- if (m_debug)
+ if (debug)
Utils::dump(1U, "RCON Sent", (uint8_t*)buffer, 36U + command.size());
ret = socket.write((uint8_t *)buffer, 36U + command.size(), addr, addrLen);
@@ -166,23 +185,23 @@ int RemoteCommand::send(const std::string& command)
if (offs + len > RESPONSE_BUFFER_LEN)
break;
- if (m_debug)
+ if (debug)
::LogDebug(LOG_RCON, "RemoteCommand::send() block len = %u, offs = %u", len - 3, offs);
buffer[len] = '\0';
- if (m_debug)
+ if (debug)
Utils::dump(1U, "RCON Received", (uint8_t*)buffer, len);
// make sure this is an RCON response
if (buffer[0U] != RCON_FRAME_START) {
- ::LogError(LOG_HOST, "Invalid response from host %s:%u", m_address.c_str(), m_port);
+ ::LogError(LOG_HOST, "Invalid response from host %s:%u", address.c_str(), port);
socket.close();
return EXIT_FAILURE;
}
if (buffer[1U] != START_OF_TEXT) {
- ::LogError(LOG_HOST, "Invalid response from host %s:%u", m_address.c_str(), m_port);
+ ::LogError(LOG_HOST, "Invalid response from host %s:%u", address.c_str(), port);
socket.close();
return EXIT_FAILURE;
}
diff --git a/remote/RemoteCommand.h b/remote/RemoteCommand.h
index 0cb81073..effe8afa 100644
--- a/remote/RemoteCommand.h
+++ b/remote/RemoteCommand.h
@@ -51,6 +51,9 @@ public:
/// Sends remote control command to the specified modem.
int send(const std::string& command);
+ /// Sends remote control command to the specified modem.
+ static int send(const std::string& address, uint32_t port, const std::string& password, const std::string& command, bool debug = false);
+
private:
std::string m_address;
uint32_t m_port;