diff --git a/configs/fne-config.example.yml b/configs/fne-config.example.yml index b2614b6f..7e3afa43 100644 --- a/configs/fne-config.example.yml +++ b/configs/fne-config.example.yml @@ -71,7 +71,7 @@ master: # Flag indicating whether or not a parrot TG call will generate a grant demand. parrotGrantDemand: true - # Flag indicating whether or not a P25 ADJ_STS_BCAST will pass to any peers. + # Flag indicating whether or not a adjacent site broadcasts will pass to any peers. disallowAdjStsBcast: false # Flag indicating whether or not a P25 ADJ_STS_BCAST will pass to connected external peers. disallowExtAdjStsBcast: true diff --git a/src/common/dmr/lc/csbk/CSBKFactory.cpp b/src/common/dmr/lc/csbk/CSBKFactory.cpp index 18de0296..304bd32b 100644 --- a/src/common/dmr/lc/csbk/CSBKFactory.cpp +++ b/src/common/dmr/lc/csbk/CSBKFactory.cpp @@ -112,6 +112,8 @@ std::unique_ptr CSBKFactory::createCSBK(const uint8_t* data, uint8_t dataT /** Tier 3 */ case CSBKO_ACK_RSP: return decode(new CSBK_ACK_RSP(), data); + case CSBKO_BROADCAST: + return decode(new CSBK_BROADCAST(), data); case CSBKO_MAINT: return decode(new CSBK_MAINT(), data); diff --git a/src/common/dmr/lc/csbk/CSBK_BROADCAST.cpp b/src/common/dmr/lc/csbk/CSBK_BROADCAST.cpp index 1a4d2de7..119e643a 100644 --- a/src/common/dmr/lc/csbk/CSBK_BROADCAST.cpp +++ b/src/common/dmr/lc/csbk/CSBK_BROADCAST.cpp @@ -31,6 +31,8 @@ CSBK_BROADCAST::CSBK_BROADCAST() : CSBK(), m_hibernating(false), m_annWdCh1(false), m_annWdCh2(false), + m_requireReg(false), + m_systemId(0U), m_backoffNo(1U) { m_CSBKO = CSBKO_BROADCAST; @@ -45,7 +47,34 @@ bool CSBK_BROADCAST::decode(const uint8_t* data) { assert(data != nullptr); - /* stub */ + uint8_t csbk[DMR_CSBK_LENGTH_BYTES]; + ::memset(csbk, 0x00U, DMR_CSBK_LENGTH_BYTES); + + bool ret = CSBK::decode(data, csbk); + if (!ret) + return false; + + ulong64_t csbkValue = CSBK::toValue(csbk); + + m_anncType = ((csbkValue >> 59) & 0x0FU); // Announcement Type + + switch (m_anncType) + { + case BCAST_ANNC_ANN_WD_TSCC: + // Broadcast Params 1 + m_colorCode = (uint8_t)((csbkValue >> 51) & 0x0FU); // Color Code 1 + m_annWdCh1 = ((csbkValue >> 44) & 0x04U) == 0x04U; // Announce/Withdraw Channel 1 + m_annWdCh2 = ((csbkValue >> 44) & 0x02U) == 0x02U; // Announce/Withdraw Channel 2 + + m_requireReg = ((csbkValue >> 44) & 0x01U) == 0x01U; // Require Registration + m_backoffNo = (uint8_t)((csbkValue >> 40) & 0x0FU); // Backoff Number + m_systemId = (uint8_t)((csbkValue >> 24) & 0xFFFFU); // Site Identity + + // Broadcast Params 2 + m_logicalCh1 = (uint32_t)((csbkValue >> 12) & 0xFFFU); // Logical Channel 1 + m_logicalCh2 = (uint32_t)(csbkValue & 0xFFFU); // Logical Channel 2 + break; + } return true; } @@ -74,9 +103,9 @@ void CSBK_BROADCAST::encode(uint8_t* data) csbkValue = (csbkValue << 1) + ((m_annWdCh1) ? 1U : 0U); // Announce/Withdraw Channel 1 csbkValue = (csbkValue << 1) + ((m_annWdCh2) ? 1U : 0U); // Announce/Withdraw Channel 2 - csbkValue = (csbkValue << 1) + ((m_siteData.requireReg()) ? 1U : 0U); // Require Registration + csbkValue = (csbkValue << 1) + ((m_requireReg) ? 1U : 0U); // Require Registration csbkValue = (csbkValue << 4) + (m_backoffNo & 0x0FU); // Backoff Number - csbkValue = (csbkValue << 16) + m_siteData.systemIdentity(); // Site Identity + csbkValue = (csbkValue << 16) + (m_systemId & 0xFFFFU); // Site Identity // Broadcast Params 2 csbkValue = (csbkValue << 12) + (m_logicalCh1 & 0xFFFU); // Logical Channel 1 diff --git a/src/common/dmr/lc/csbk/CSBK_BROADCAST.h b/src/common/dmr/lc/csbk/CSBK_BROADCAST.h index 9c21292d..a2ce2ca6 100644 --- a/src/common/dmr/lc/csbk/CSBK_BROADCAST.h +++ b/src/common/dmr/lc/csbk/CSBK_BROADCAST.h @@ -51,6 +51,11 @@ namespace dmr /// Broadcast Announce/Withdraw Channel 2 Flag. __PROPERTY(bool, annWdCh2, AnnWdCh2); + /// Require Registration. + __PROPERTY(bool, requireReg, RequireReg); + /// System Identity. + __PROPERTY(uint32_t, systemId, SystemId); + /// Backoff Number. __PROPERTY(uint8_t, backoffNo, BackoffNo); diff --git a/src/fne/network/FNENetwork.cpp b/src/fne/network/FNENetwork.cpp index 9e8a75b7..3735f3b0 100644 --- a/src/fne/network/FNENetwork.cpp +++ b/src/fne/network/FNENetwork.cpp @@ -159,7 +159,7 @@ void FNENetwork::setOptions(yaml::Node& conf, bool printOptions) if (printOptions) { LogInfo(" Maximum Permitted Connections: %u", m_softConnLimit); - LogInfo(" Disable P25 ADJ_STS_BCAST to any peers: %s", m_disallowAdjStsBcast ? "yes" : "no"); + LogInfo(" Disable adjacent site broadcasts to any peers: %s", m_disallowAdjStsBcast ? "yes" : "no"); if (m_disallowAdjStsBcast) { LogWarning(LOG_NET, "NOTICE: All P25 ADJ_STS_BCAST messages will be blocked and dropped!"); } diff --git a/src/fne/network/fne/TagDMRData.cpp b/src/fne/network/fne/TagDMRData.cpp index 370122ab..26bf549a 100644 --- a/src/fne/network/fne/TagDMRData.cpp +++ b/src/fne/network/fne/TagDMRData.cpp @@ -544,6 +544,27 @@ bool TagDMRData::processCSBK(uint8_t* buffer, uint32_t peerId, dmr::data::Data& .request(m_network->m_influxServer); } } + + switch (csbk->getCSBKO()) { + case CSBKO_BROADCAST: + { + lc::csbk::CSBK_BROADCAST* osp = static_cast(csbk.get()); + if (osp->getAnncType() == BCAST_ANNC_ANN_WD_TSCC) { + if (m_network->m_disallowAdjStsBcast) { + // LogWarning(LOG_NET, "PEER %u, passing BCAST_ANNC_ANN_WD_TSCC to internal peers is prohibited, dropping", peerId); + return false; + } else { + if (m_network->m_verbose) { + LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, %s, sysId = $%03X, chNo = %u, peerId = %u", dmrData.getSlotNo(), csbk->toString().c_str(), + osp->getSystemId(), osp->getLogicalCh1(), peerId); + } + } + } + } + break; + default: + break; + } } else { LogWarning(LOG_NET, "PEER %u, passing CSBK that failed to decode? csbk == nullptr", peerId); } diff --git a/src/host/Host.cpp b/src/host/Host.cpp index 28395037..2ceb5fc1 100644 --- a/src/host/Host.cpp +++ b/src/host/Host.cpp @@ -1240,7 +1240,6 @@ int Host::run() if (m_state != STATE_NXDN) setState(STATE_NXDN); - //nxdn->writeAdjSSNetwork(); nxdn->setCCRunning(true); // hide this message for continuous CC -- otherwise display every time we process @@ -1281,7 +1280,6 @@ int Host::run() // the network if (nxdnBcastIntervalTimer.isRunning() && nxdnBcastIntervalTimer.hasExpired()) { if ((m_state == STATE_IDLE || m_state == STATE_NXDN) && !m_modem->hasTX()) { - //nxdn->writeAdjSSNetwork(); nxdnBcastIntervalTimer.start(); } } diff --git a/src/host/dmr/Slot.cpp b/src/host/dmr/Slot.cpp index 2181d4ef..1caea09f 100644 --- a/src/host/dmr/Slot.cpp +++ b/src/host/dmr/Slot.cpp @@ -83,6 +83,13 @@ bool Slot::m_verifyReg = false; uint8_t Slot::m_alohaNRandWait = DEFAULT_NRAND_WAIT; uint8_t Slot::m_alohaBackOff = 1U; +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const uint32_t ADJ_SITE_TIMER_TIMEOUT = 60U; +const uint32_t ADJ_SITE_UPDATE_CNT = 5U; + // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- @@ -125,6 +132,10 @@ Slot::Slot(uint32_t slotNo, uint32_t timeout, uint32_t tgHang, uint32_t queueSiz m_netTimeoutTimer(1000U, timeout), m_netTGHang(1000U, 2U), m_packetTimer(1000U, 0U, 50U), + m_adjSiteTable(), + m_adjSiteUpdateCnt(), + m_adjSiteUpdateTimer(1000U), + m_adjSiteUpdateInterval(ADJ_SITE_TIMER_TIMEOUT), m_adjSiteUpdate(1000U, 75U), m_ccPacketInterval(1000U, 0U, DMR_SLOT_TIME), m_interval(), @@ -158,6 +169,7 @@ Slot::Slot(uint32_t slotNo, uint32_t timeout, uint32_t tgHang, uint32_t queueSiz m_tsccPayloadGroup(false), m_tsccPayloadVoice(true), m_tsccPayloadActRetry(1000U, 0U, 250U), + m_tsccAdjSSCnt(0U), m_disableGrantSrcIdCheck(false), m_lastLateEntry(0U), m_supervisor(false), @@ -167,6 +179,13 @@ Slot::Slot(uint32_t slotNo, uint32_t timeout, uint32_t tgHang, uint32_t queueSiz { m_interval.start(); + m_adjSiteTable.clear(); + m_adjSiteUpdateCnt.clear(); + + m_adjSiteUpdateInterval = ADJ_SITE_TIMER_TIMEOUT; + m_adjSiteUpdateTimer.setTimeout(m_adjSiteUpdateInterval); + m_adjSiteUpdateTimer.start(); + m_voice = new Voice(this, m_network, m_embeddedLCOnly, m_dumpTAData, debug, verbose); m_data = new Data(this, m_network, dumpDataPacket, repeatDataPacket, debug, verbose); m_control = new ControlSignaling(this, m_network, dumpCSBKData, debug, verbose); @@ -475,17 +494,43 @@ void Slot::clock() } // do we need to network announce ourselves? - if (!m_adjSiteUpdate.isRunning()) { - m_adjSiteUpdate.start(); + if (!m_adjSiteUpdateTimer.isRunning()) { + m_control->writeAdjSSNetwork(); + m_adjSiteUpdateTimer.start(); } - m_adjSiteUpdate.clock(ms); - if (m_adjSiteUpdate.isRunning() && m_adjSiteUpdate.hasExpired()) { + m_adjSiteUpdateTimer.clock(ms); + if (m_adjSiteUpdateTimer.isRunning() && m_adjSiteUpdateTimer.hasExpired()) { if (m_rfState == RS_RF_LISTENING && m_netState == RS_NET_IDLE) { + m_control->writeAdjSSNetwork(); if (m_network != nullptr) m_network->announceAffiliationUpdate(m_affiliations->grpAffTable()); - m_adjSiteUpdate.start(); + m_adjSiteUpdateTimer.start(); + } + } + + // clock adjacent site and SCCB update timers + m_adjSiteUpdateTimer.clock(ms); + if (m_adjSiteUpdateTimer.isRunning() && m_adjSiteUpdateTimer.hasExpired()) { + // update adjacent site data + for (auto& entry : m_adjSiteUpdateCnt) { + uint8_t siteId = entry.first; + uint8_t updateCnt = entry.second; + if (updateCnt > 0U) { + updateCnt--; + } + + if (updateCnt == 0U) { + AdjSiteData siteData = m_adjSiteTable[siteId]; + LogWarning(LOG_NET, "DMR, Adjacent Site Status Expired, no data [FAILED], sysId = $%03X, chNo = %u", + siteData.systemIdentity, siteData.channelNo); + } + + entry.second = updateCnt; } + + m_adjSiteUpdateTimer.setTimeout(m_adjSiteUpdateInterval); + m_adjSiteUpdateTimer.start(); } if (m_ccPrevRunning && !m_ccRunning) { @@ -1374,47 +1419,72 @@ void Slot::writeRF_ControlData(uint16_t frameCnt, uint8_t n) switch (n) { + /** required data */ + case 0: + default: + m_control->writeRF_TSCC_Bcast_Sys_Parm(); + break; + case 1: + m_control->writeRF_TSCC_Aloha(); + break; + case 2: + m_control->writeRF_TSCC_Bcast_Ann_Wd(m_channelNo, true, m_siteData.systemIdentity(), m_siteData.requireReg()); + break; case 3: - { - std::unordered_map grants = m_affiliations->grantTable(); - if (grants.size() > 0) { - uint32_t j = 0U; - if (m_lastLateEntry > grants.size()) { - m_lastLateEntry = 0U; - } + { + std::unordered_map grants = m_affiliations->grantTable(); + if (grants.size() > 0) { + uint32_t j = 0U; + if (m_lastLateEntry > grants.size()) { + m_lastLateEntry = 0U; + } + + for (auto entry : grants) { + if (j == m_lastLateEntry) { + uint32_t dstId = entry.first; + uint32_t srcId = m_affiliations->getGrantedSrcId(dstId); + bool grp = m_affiliations->isGroup(dstId); - for (auto entry : grants) { - if (j == m_lastLateEntry) { - uint32_t dstId = entry.first; - uint32_t srcId = m_affiliations->getGrantedSrcId(dstId); - bool grp = m_affiliations->isGroup(dstId); + if (m_debug) { + LogDebug(LOG_DMR, "writeRF_ControlData, frameCnt = %u, seq = %u, late entry, dstId = %u, srcId = %u", frameCnt, n, dstId, srcId); + } - if (m_debug) { - LogDebug(LOG_DMR, "writeRF_ControlData, frameCnt = %u, seq = %u, late entry, dstId = %u, srcId = %u", frameCnt, n, dstId, srcId); + m_control->writeRF_CSBK_Grant_LateEntry(dstId, srcId, grp); + m_lastLateEntry = j++; + break; } - m_control->writeRF_CSBK_Grant_LateEntry(dstId, srcId, grp); - m_lastLateEntry = j++; - break; + j++; } - - j++; + } + else { + m_control->writeRF_TSCC_Bcast_Sys_Parm(); } } - else { - m_control->writeRF_TSCC_Bcast_Sys_Parm(); - } - } - break; - case 2: - m_control->writeRF_TSCC_Bcast_Ann_Wd(m_channelNo, true); break; - case 1: - m_control->writeRF_TSCC_Aloha(); - break; - case 0: - default: - m_control->writeRF_TSCC_Bcast_Sys_Parm(); + /** extra data */ + case 4: + // write ADJSS + if (m_adjSiteTable.size() > 0) { + if (m_tsccAdjSSCnt >= m_adjSiteTable.size()) + m_tsccAdjSSCnt = 0U; + + uint8_t i = 0U; + for (auto entry : m_adjSiteTable) { + // no good very bad way of skipping entries... + if (i != m_tsccAdjSSCnt) { + i++; + continue; + } + else { + AdjSiteData site = entry.second; + m_control->writeRF_TSCC_Bcast_Ann_Wd(site.channelNo, true, site.systemIdentity, site.requireReg); + m_tsccAdjSSCnt++; + break; + } + } + break; + } break; } diff --git a/src/host/dmr/Slot.h b/src/host/dmr/Slot.h index 1dfa7428..26b2580b 100644 --- a/src/host/dmr/Slot.h +++ b/src/host/dmr/Slot.h @@ -45,6 +45,22 @@ namespace dmr namespace packet { class HOST_SW_API Data; } namespace packet { class HOST_SW_API ControlSignaling; } + // --------------------------------------------------------------------------- + // Structure Declaration + // This structure contains shortened data for adjacent sites. + // --------------------------------------------------------------------------- + + struct AdjSiteData + { + public: + /// Channel Number. + uint32_t channelNo; + /// System Identity. + uint32_t systemIdentity; + /// DMR require registration. + bool requireReg; + }; + // --------------------------------------------------------------------------- // Class Declaration // This class implements core logic for handling DMR slots. @@ -164,6 +180,11 @@ namespace dmr Timer m_netTGHang; Timer m_packetTimer; + std::unordered_map m_adjSiteTable; + std::unordered_map m_adjSiteUpdateCnt; + + Timer m_adjSiteUpdateTimer; + uint32_t m_adjSiteUpdateInterval; Timer m_adjSiteUpdate; Timer m_ccPacketInterval; @@ -208,6 +229,7 @@ namespace dmr bool m_tsccPayloadGroup; bool m_tsccPayloadVoice; Timer m_tsccPayloadActRetry; + uint8_t m_tsccAdjSSCnt; bool m_disableGrantSrcIdCheck; diff --git a/src/host/dmr/packet/ControlSignaling.cpp b/src/host/dmr/packet/ControlSignaling.cpp index 1a3ce697..d61836d9 100644 --- a/src/host/dmr/packet/ControlSignaling.cpp +++ b/src/host/dmr/packet/ControlSignaling.cpp @@ -118,6 +118,7 @@ using namespace dmr::packet; // Constants // --------------------------------------------------------------------------- +const uint32_t ADJ_SITE_UPDATE_CNT = 5U; const uint32_t GRANT_TIMER_TIMEOUT = 15U; // --------------------------------------------------------------------------- @@ -405,6 +406,40 @@ void ControlSignaling::processNetwork(const data::Data & dmrData) if (csbko == CSBKO_BSDWNACT) return; + // handle updating internal adjacent site information + if (csbko == CSBKO_BROADCAST) { + CSBK_BROADCAST* osp = static_cast(csbk.get()); + if (osp->getAnncType() == BCAST_ANNC_ANN_WD_TSCC) { + if (!m_slot->m_enableTSCC) { + return; + } + + if (osp->getSystemId() != m_slot->m_siteData.systemIdentity()) { + // update site table data + AdjSiteData site; + try { + site = m_slot->m_adjSiteTable.at(osp->getSystemId()); + } catch (...) { + site = AdjSiteData(); + } + + if (m_verbose) { + LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, %s, sysId = $%03X, chNo = %u", m_slot->m_slotNo, csbk->toString().c_str(), + osp->getSystemId(), osp->getLogicalCh1()); + } + + site.channelNo = osp->getLogicalCh1(); + site.systemIdentity = osp->getSystemId(); + site.requireReg = osp->getRequireReg(); + + m_slot->m_adjSiteTable[site.systemIdentity] = site; + m_slot->m_adjSiteUpdateCnt[site.systemIdentity] = ADJ_SITE_UPDATE_CNT; + } + + return; + } + } + uint32_t srcId = csbk->getSrcId(); uint32_t dstId = csbk->getDstId(); @@ -583,6 +618,35 @@ void ControlSignaling::processNetwork(const data::Data & dmrData) } } +/// +/// Helper to write DMR adjacent site information to the network. +/// +void ControlSignaling::writeAdjSSNetwork() +{ + if (!m_slot->m_enableTSCC) { + return; + } + + if (m_slot->m_network != nullptr) { + // transmit adjacent site broadcast + std::unique_ptr csbk = std::make_unique(); + csbk->siteIdenEntry(m_slot->m_idenEntry); + csbk->setCdef(false); + csbk->setAnncType(BCAST_ANNC_ANN_WD_TSCC); + csbk->setLogicalCh1(m_slot->m_channelNo); + csbk->setAnnWdCh1(true); + csbk->setSystemId(m_slot->m_siteData.systemIdentity()); + csbk->setRequireReg(m_slot->m_siteData.requireReg()); + + if (m_verbose) { + LogMessage(LOG_NET, "DMR Slot %u, DT_CSBK, %s, network announce, sysId = $%03X, chNo = %u", m_slot->m_slotNo, csbk->toString().c_str(), + m_slot->m_siteData.systemIdentity(), m_slot->m_channelNo); + } + + writeNet_CSBK(csbk.get()); + } +} + /// /// Helper to write a extended function packet on the RF interface. /// @@ -703,6 +767,39 @@ void ControlSignaling::writeRF_CSBK(lc::CSBK* csbk, bool imm) m_slot->addFrame(data, false, imm); } +/// +/// Helper to write a network CSBK. +/// +/// +void ControlSignaling::writeNet_CSBK(lc::CSBK* csbk) +{ + uint8_t data[DMR_FRAME_LENGTH_BYTES + 2U]; + ::memset(data + 2U, 0x00U, DMR_FRAME_LENGTH_BYTES); + + SlotType slotType; + slotType.setColorCode(m_slot->m_colorCode); + slotType.setDataType(DT_CSBK); + + // Regenerate the CSBK data + csbk->encode(data + 2U); + + // Regenerate the Slot Type + slotType.encode(data + 2U); + + // Convert the Data Sync to be from the BS or MS as needed + Sync::addDMRDataSync(data + 2U, true); + + m_slot->m_rfSeqNo = 0U; + + data[0U] = modem::TAG_DATA; + data[1U] = 0x00U; + + if (m_slot->m_duplex) + m_slot->addFrame(data); + + m_slot->writeNetwork(data, DT_CSBK, csbk->getGI() ? FLCO_GROUP : FLCO_PRIVATE, csbk->getSrcId(), csbk->getDstId(), 0U, true); +} + /* ** Control Signalling Logic */ @@ -1428,7 +1525,9 @@ void ControlSignaling::writeRF_TSCC_Aloha() /// /// /// -void ControlSignaling::writeRF_TSCC_Bcast_Ann_Wd(uint32_t channelNo, bool annWd) +/// +/// +void ControlSignaling::writeRF_TSCC_Bcast_Ann_Wd(uint32_t channelNo, bool annWd, uint32_t systemIdentity, bool requireReg) { m_slot->m_rfSeqNo = 0U; @@ -1438,6 +1537,8 @@ void ControlSignaling::writeRF_TSCC_Bcast_Ann_Wd(uint32_t channelNo, bool annWd) csbk->setAnncType(BCAST_ANNC_ANN_WD_TSCC); csbk->setLogicalCh1(channelNo); csbk->setAnnWdCh1(annWd); + csbk->setSystemId(systemIdentity); + csbk->setRequireReg(requireReg); if (m_debug) { LogMessage(LOG_RF, "DMR Slot %u, DT_CSBK, %s, channelNo = %u, annWd = %u", diff --git a/src/host/dmr/packet/ControlSignaling.h b/src/host/dmr/packet/ControlSignaling.h index edd98087..4096e011 100644 --- a/src/host/dmr/packet/ControlSignaling.h +++ b/src/host/dmr/packet/ControlSignaling.h @@ -44,13 +44,17 @@ namespace dmr // packets. // --------------------------------------------------------------------------- - class HOST_SW_API ControlSignaling { + class HOST_SW_API ControlSignaling + { public: /// Process a data frame from the RF interface. bool process(uint8_t* data, uint32_t len); /// Process a data frame from the network. void processNetwork(const data::Data& dmrData); + /// Helper to write P25 adjacent site information to the network. + void writeAdjSSNetwork(); + /// Helper to write a extended function packet on the RF interface. void writeRF_Ext_Func(uint32_t func, uint32_t arg, uint32_t dstId); /// Helper to write a call alert packet on the RF interface. @@ -77,6 +81,8 @@ namespace dmr void writeRF_CSBK_Imm(lc::CSBK *csbk) { writeRF_CSBK(csbk, true); } /// Helper to write a CSBK packet. void writeRF_CSBK(lc::CSBK* csbk, bool imm = false); + /// Helper to write a network CSBK packet. + void writeNet_CSBK(lc::CSBK* csbk); /* ** Control Signalling Logic @@ -103,7 +109,7 @@ namespace dmr /// Helper to write a TSCC Aloha broadcast packet on the RF interface. void writeRF_TSCC_Aloha(); /// Helper to write a TSCC Ann-Wd broadcast packet on the RF interface. - void writeRF_TSCC_Bcast_Ann_Wd(uint32_t channelNo, bool annWd); + void writeRF_TSCC_Bcast_Ann_Wd(uint32_t channelNo, bool annWd, uint32_t systemIdentity, bool requireReg); /// Helper to write a TSCC Sys_Parm broadcast packet on the RF interface. void writeRF_TSCC_Bcast_Sys_Parm(); /// Helper to write a TSCC Git Hash broadcast packet on the RF interface.