/** * 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 * */ // // 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) 2015,2016,2017,2018 Jonathan Naylor, G4KLX * Copyright (C) 2017-2022 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; version 2 of the License. * * 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. */ #include "Defines.h" #include "dmr/Slot.h" #include "dmr/acl/AccessControl.h" #include "dmr/lc/FullLC.h" #include "dmr/lc/ShortLC.h" #include "dmr/lc/CSBK.h" #include "dmr/SlotType.h" #include "dmr/Sync.h" #include "edac/BPTC19696.h" #include "edac/CRC.h" #include "Log.h" #include "Utils.h" using namespace dmr; using namespace dmr::packet; #include #include #include #include // --------------------------------------------------------------------------- // Constants // --------------------------------------------------------------------------- const uint16_t TSCC_MAX_CNT = 511U; // --------------------------------------------------------------------------- // Static Class Members // --------------------------------------------------------------------------- Control* Slot::m_dmr = NULL; uint32_t Slot::m_colorCode = 0U; SiteData Slot::m_siteData = SiteData(); uint32_t Slot::m_channelNo = 0U; bool Slot::m_embeddedLCOnly = false; bool Slot::m_dumpTAData = true; modem::Modem* Slot::m_modem = NULL; network::BaseNetwork* Slot::m_network = NULL; bool Slot::m_duplex = true; lookups::IdenTableLookup* Slot::m_idenTable = NULL; lookups::RadioIdLookup* Slot::m_ridLookup = NULL; lookups::TalkgroupIdLookup* Slot::m_tidLookup = NULL; lookups::AffiliationLookup *Slot::m_affiliations = NULL; lookups::IdenTable Slot::m_idenEntry = lookups::IdenTable(); uint32_t Slot::m_hangCount = 3U * 17U; lookups::RSSIInterpolator* Slot::m_rssiMapper = NULL; uint32_t Slot::m_jitterTime = 360U; uint32_t Slot::m_jitterSlots = 6U; uint8_t* Slot::m_idle = NULL; uint8_t Slot::m_flco1; uint8_t Slot::m_id1 = 0U; bool Slot::m_voice1 = true; uint8_t Slot::m_flco2; uint8_t Slot::m_id2 = 0U; bool Slot::m_voice2 = true; bool Slot::m_verifyReg = false; uint16_t Slot::m_tsccCnt = 0U; uint8_t Slot::m_alohaNRandWait = DEFAULT_NRAND_WAIT; uint8_t Slot::m_alohaBackOff = 1U; // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- /// /// Initializes a new instance of the Slot class. /// /// DMR slot number. /// Modem frame buffer queue size (bytes). /// Transmit timeout. /// Amount of time to hang on the last talkgroup mode from RF. /// /// /// /// Flag indicating whether DMR debug is enabled. /// Flag indicating whether DMR verbose logging is enabled. Slot::Slot(uint32_t slotNo, uint32_t timeout, uint32_t tgHang, uint32_t queueSize, bool dumpDataPacket, bool repeatDataPacket, bool dumpCSBKData, bool debug, bool verbose) : m_slotNo(slotNo), m_queue(queueSize, "DMR Slot Frame"), m_rfState(RS_RF_LISTENING), m_rfLastDstId(0U), m_netState(RS_NET_IDLE), m_netLastDstId(0U), m_rfLC(NULL), m_rfPrivacyLC(NULL), m_rfDataHeader(NULL), m_rfSeqNo(0U), m_netLC(NULL), m_netPrivacyLC(NULL), m_netDataHeader(NULL), m_networkWatchdog(1000U, 0U, 1500U), m_rfTimeoutTimer(1000U, timeout), m_rfTGHang(1000U, tgHang), m_netTimeoutTimer(1000U, timeout), m_packetTimer(1000U, 0U, 50U), m_ccPacketInterval(1000U, 0U, 5U), m_interval(), m_elapsed(), m_rfFrames(0U), m_netFrames(0U), m_netLost(0U), m_netMissed(0U), m_rfBits(1U), m_netBits(1U), m_rfErrs(0U), m_netErrs(0U), m_rfTimeout(false), m_netTimeout(false), m_rssi(0U), m_maxRSSI(0U), m_minRSSI(0U), m_aveRSSI(0U), m_rssiCount(0U), m_silenceThreshold(DEFAULT_SILENCE_THRESHOLD), m_ccSeq(0U), m_ccRunning(false), m_ccPrevRunning(false), m_ccHalted(false), m_enableTSCC(false), m_dumpCSBKData(dumpCSBKData), m_verbose(verbose), m_debug(debug) { m_interval.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); } /// /// Finalizes a instance of the Slot class. /// Slot::~Slot() { delete m_voice; delete m_data; delete m_control; } /// /// Process DMR data frame from the RF interface. /// /// Buffer containing data frame. /// Length of data frame. /// bool Slot::processFrame(uint8_t *data, uint32_t len) { assert(data != NULL); if (data[0U] == modem::TAG_LOST && m_rfState == RS_RF_AUDIO) { if (m_rssi != 0U) { ::ActivityLog("DMR", true, "Slot %u RF voice transmission lost, %.1f seconds, BER: %.1f%%, RSSI: -%u/-%u/-%u dBm", m_slotNo, float(m_rfFrames) / 16.667F, float(m_rfErrs * 100U) / float(m_rfBits), m_minRSSI, m_maxRSSI, m_aveRSSI / m_rssiCount); } else { ::ActivityLog("DMR", true, "Slot %u RF voice transmission lost, %.1f seconds, BER: %.1f%%", m_slotNo, float(m_rfFrames) / 16.667F, float(m_rfErrs * 100U) / float(m_rfBits)); } LogMessage(LOG_RF, "DMR Slot %u, total frames: %d, total bits: %d, errors: %d, BER: %.4f%%", m_slotNo, m_rfFrames, m_rfBits, m_rfErrs, float(m_rfErrs * 100U) / float(m_rfBits)); if (m_rfTimeout) { writeEndRF(); return false; } else { writeEndRF(true); return true; } } if (data[0U] == modem::TAG_LOST && m_rfState == RS_RF_DATA) { ::ActivityLog("DMR", true, "Slot %u, RF data transmission lost", m_slotNo); writeEndRF(); return false; } if (data[0U] == modem::TAG_LOST) { m_rfState = RS_RF_LISTENING; m_rfLastDstId = 0U; m_rfTGHang.stop(); return false; } // Have we got RSSI bytes on the end? if (len == (DMR_FRAME_LENGTH_BYTES + 4U)) { uint16_t raw = 0U; raw |= (data[35U] << 8) & 0xFF00U; raw |= (data[36U] << 0) & 0x00FFU; // Convert the raw RSSI to dBm int rssi = m_rssiMapper->interpolate(raw); if (m_verbose) { LogMessage(LOG_RF, "DMR Slot %u, raw RSSI = %u, reported RSSI = %d dBm", m_slotNo, raw, rssi); } // RSSI is always reported as positive m_rssi = (rssi >= 0) ? rssi : -rssi; if (m_rssi > m_minRSSI) m_minRSSI = m_rssi; if (m_rssi < m_maxRSSI) m_maxRSSI = m_rssi; m_aveRSSI += m_rssi; m_rssiCount++; } bool dataSync = (data[1U] & DMR_SYNC_DATA) == DMR_SYNC_DATA; bool voiceSync = (data[1U] & DMR_SYNC_VOICE) == DMR_SYNC_VOICE; if (!(dataSync || voiceSync) && m_rfState == RS_RF_LISTENING) { uint8_t sync[DMR_SYNC_LENGTH_BYTES]; ::memcpy(sync, data + 2U, DMR_SYNC_LENGTH_BYTES); // count data sync errors uint8_t dataErrs = 0U; for (uint8_t i = 0U; i < DMR_SYNC_LENGTH_BYTES; i++) dataErrs += Utils::countBits8(sync[i] ^ DMR_MS_DATA_SYNC_BYTES[i]); // count voice sync errors uint8_t voiceErrs = 0U; for (uint8_t i = 0U; i < DMR_SYNC_LENGTH_BYTES; i++) voiceErrs += Utils::countBits8(sync[i] ^ DMR_MS_VOICE_SYNC_BYTES[i]); LogWarning(LOG_RF, "DMR, possible sync word rejected, dataErrs = %u, voiceErrs = %u, sync word = %02X %02X %02X %02X %02X %02X", dataErrs, voiceErrs, sync[0U], sync[1U], sync[2U], sync[3U], sync[4U], sync[5U]); } if ((dataSync || voiceSync) && m_debug) { Utils::symbols("!!! *Rx DMR", data + 2U, len - 2U); } if ((dataSync || voiceSync) && m_rfState != RS_RF_LISTENING) m_rfTGHang.start(); // write and process TSCC CSBKs and short LC if (m_enableTSCC && m_dedicatedTSCC) { if (dataSync) { uint8_t dataType = data[1U] & 0x0FU; switch (dataType) { case DT_CSBK: return m_control->process(data, len); default: break; } } return false; } if (dataSync) { uint8_t dataType = data[1U] & 0x0FU; switch (dataType) { case DT_CSBK: return m_control->process(data, len); case DT_VOICE_LC_HEADER: case DT_VOICE_PI_HEADER: return m_voice->process(data, len); case DT_TERMINATOR_WITH_LC: case DT_DATA_HEADER: case DT_RATE_12_DATA: case DT_RATE_34_DATA: case DT_RATE_1_DATA: default: return m_data->process(data, len); } } return m_voice->process(data, len); } /// /// Get frame data from data ring buffer. /// /// Buffer to store frame data. /// Length of frame data retreived. uint32_t Slot::getFrame(uint8_t* data) { assert(data != NULL); if (m_queue.isEmpty()) return 0U; uint8_t len = 0U; m_queue.getData(&len, 1U); m_queue.getData(data, len); return len; } /// /// Process a data frame from the network. /// /// void Slot::processNetwork(const data::Data& dmrData) { // don't process network frames if the RF modem isn't in a listening state if (m_rfState != RS_RF_LISTENING) { LogWarning(LOG_NET, "Traffic collision detect, preempting new network traffic to existing RF traffic!"); return; } // don't process network frames if the destination ID's don't match and the network TG hang timer is running if (m_rfLastDstId != 0U) { if (m_rfLastDstId != dmrData.getDstId() && (m_rfTGHang.isRunning() && !m_rfTGHang.hasExpired())) { return; } if (m_rfLastDstId == dmrData.getDstId() && (m_rfTGHang.isRunning() && !m_rfTGHang.hasExpired())) { m_rfTGHang.start(); } } m_networkWatchdog.start(); uint8_t dataType = dmrData.getDataType(); switch (dataType) { case DT_CSBK: m_control->processNetwork(dmrData); break; case DT_VOICE_LC_HEADER: case DT_VOICE_PI_HEADER: case DT_VOICE_SYNC: case DT_VOICE: m_voice->processNetwork(dmrData); break; case DT_TERMINATOR_WITH_LC: case DT_DATA_HEADER: case DT_RATE_12_DATA: case DT_RATE_34_DATA: case DT_RATE_1_DATA: default: m_data->processNetwork(dmrData); break; } } /// /// Updates the DMR slot processor. /// void Slot::clock() { uint32_t ms = m_interval.elapsed(); m_interval.start(); if (m_network != NULL) { if (m_network->getStatus() == network::NET_STAT_RUNNING) { m_siteData.setNetActive(true); } else { m_siteData.setNetActive(false); } } // if we have control enabled; do clocking to generate a CC data stream if (m_enableTSCC) { if (m_ccRunning && !m_ccPacketInterval.isRunning()) { m_ccPacketInterval.start(); } if (m_ccHalted) { if (!m_ccRunning) { m_ccHalted = false; m_ccPrevRunning = m_ccRunning; m_queue.clear(); // clear the frame buffer } } else { m_ccPacketInterval.clock(ms); if (!m_ccPacketInterval.isRunning()) { m_ccPacketInterval.start(); } if (m_ccPacketInterval.isRunning() && m_ccPacketInterval.hasExpired()) { if (m_ccRunning) { // increment the TSCC counter on every slot 1 clock m_tsccCnt++; if (m_tsccCnt == TSCC_MAX_CNT) { m_tsccCnt = 0U; } if (m_ccSeq == 3U) { m_ccSeq = 0U; } setShortLC_TSCC(m_siteData, m_tsccCnt); writeRF_ControlData(m_tsccCnt, m_ccSeq); m_ccSeq++; } m_ccPacketInterval.start(); } } if (m_ccPrevRunning && !m_ccRunning) { m_queue.clear(); // clear the frame buffer m_ccPrevRunning = m_ccRunning; } } m_rfTimeoutTimer.clock(ms); if (m_rfTimeoutTimer.isRunning() && m_rfTimeoutTimer.hasExpired()) { if (!m_rfTimeout) { LogMessage(LOG_RF, "DMR Slot %u, user has timed out", m_slotNo); m_rfTimeout = true; } } m_netTimeoutTimer.clock(ms); if (m_netTimeoutTimer.isRunning() && m_netTimeoutTimer.hasExpired()) { if (!m_netTimeout) { LogMessage(LOG_NET, "DMR Slot %u, user has timed out", m_slotNo); m_netTimeout = true; } } if (m_rfTGHang.isRunning()) { m_rfTGHang.clock(ms); if (m_rfTGHang.hasExpired()) { m_rfTGHang.stop(); if (m_verbose) { LogMessage(LOG_RF, "Slot %u, talkgroup hang has expired, lastDstId = %u", m_slotNo, m_rfLastDstId); } m_rfLastDstId = 0U; } } if (m_netState == RS_NET_AUDIO || m_netState == RS_NET_DATA) { m_networkWatchdog.clock(ms); if (m_networkWatchdog.hasExpired()) { if (m_netState == RS_NET_AUDIO) { // We've received the voice header haven't we? m_netFrames += 1U; ::ActivityLog("DMR", false, "Slot %u network watchdog has expired, %.1f seconds, %u%% packet loss, BER: %.1f%%", m_slotNo, float(m_netFrames) / 16.667F, (m_netLost * 100U) / m_netFrames, float(m_netErrs * 100U) / float(m_netBits)); writeEndNet(true); } else { ::ActivityLog("DMR", false, "Slot %u network watchdog has expired", m_slotNo); writeEndNet(); } } } if (m_netState == RS_NET_AUDIO) { m_packetTimer.clock(ms); if (m_packetTimer.isRunning() && m_packetTimer.hasExpired()) { uint32_t elapsed = m_elapsed.elapsed(); if (elapsed >= m_jitterTime) { LogWarning(LOG_NET, "DMR Slot %u, lost audio for %ums filling in", m_slotNo, elapsed); m_voice->insertSilence(m_jitterSlots); m_elapsed.start(); } m_packetTimer.start(); } } if (m_rfState == RS_RF_REJECTED) { m_queue.clear(); m_rfFrames = 0U; m_rfErrs = 0U; m_rfBits = 1U; m_netFrames = 0U; m_netLost = 0U; if (m_network != NULL) m_network->resetDMR(m_slotNo); m_rfState = RS_RF_LISTENING; } } /// /// Helper to change the debug and verbose state. /// /// Flag indicating whether DMR debug is enabled. /// Flag indicating whether DMR verbose logging is enabled. void Slot::setDebugVerbose(bool debug, bool verbose) { m_debug = m_voice->m_debug = m_data->m_debug = debug = m_control->m_debug; m_verbose = m_voice->m_verbose = m_data->m_verbose = verbose = m_control->m_verbose; } /// /// Helper to enable and configure TSCC support for this slot. /// /// Flag indicating whether DMR TSCC is enabled on this slot. /// Flag indicating whether DMR TSCC is dedicated on this slot. void Slot::setTSCC(bool enable, bool dedicated) { m_enableTSCC = enable; m_dedicatedTSCC = dedicated; if (m_enableTSCC) { m_modem->setDMRIgnoreCACH_AT(m_slotNo); } } /// /// Helper to set the voice error silence threshold. /// /// void Slot::setSilenceThreshold(uint32_t threshold) { m_silenceThreshold = threshold; } /// /// Helper to initialize the DMR slot processor. /// /// Instance of the Control class. /// DMR access color code. /// DMR site data. /// /// /// Amount of hangtime for a DMR call. /// Instance of the Modem class. /// Instance of the BaseNetwork class. /// Flag indicating full-duplex operation. /// Instance of the RadioIdLookup class. /// Instance of the TalkgroupIdLookup class. /// Instance of the IdenTableLookup class. /// Instance of the RSSIInterpolator class. /// /// void Slot::init(Control* dmr, 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) { assert(dmr != NULL); assert(modem != NULL); assert(ridLookup != NULL); assert(tidLookup != NULL); assert(idenTable != NULL); assert(rssiMapper != NULL); m_dmr = dmr; m_colorCode = colorCode; m_siteData = siteData; m_embeddedLCOnly = embeddedLCOnly; m_dumpTAData = dumpTAData; m_modem = modem; m_network = network; m_duplex = duplex; m_idenTable = idenTable; m_ridLookup = ridLookup; m_tidLookup = tidLookup; m_affiliations = new lookups::AffiliationLookup("DMR Affiliations", verbose); m_hangCount = callHang * 17U; m_rssiMapper = rssiMapper; m_jitterTime = jitter; float jitter_tmp = float(jitter) / 360.0F; m_jitterSlots = (uint32_t)(std::ceil(jitter_tmp) * 6.0F); m_idle = new uint8_t[DMR_FRAME_LENGTH_BYTES + 2U]; ::memcpy(m_idle, DMR_IDLE_DATA, DMR_FRAME_LENGTH_BYTES + 2U); // Generate the Slot Type for the Idle frame SlotType slotType; slotType.setColorCode(colorCode); slotType.setDataType(DT_IDLE); slotType.encode(m_idle + 2U); } /// /// Sets local configured site data. /// /// DMR Network ID. /// DMR Site ID. /// Channel ID. /// Channel Number. /// void Slot::setSiteData(uint32_t netId, uint8_t siteId, uint8_t channelId, uint32_t channelNo) { m_siteData = SiteData(SITE_MODEL_SMALL, netId, siteId, 3U, true); m_channelNo = channelNo; std::vector entries = m_idenTable->list(); for (auto it = entries.begin(); it != entries.end(); ++it) { lookups::IdenTable entry = *it; if (entry.channelId() == channelId) { m_idenEntry = entry; break; } } } /// /// Sets TSCC Aloha configuration. /// /// /// void Slot::setAlohaConfig(uint8_t nRandWait, uint8_t backOff) { m_alohaNRandWait = nRandWait; m_alohaBackOff = backOff; } // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- /// /// Add data frame to the data ring buffer. /// /// /// void Slot::addFrame(const uint8_t *data, bool net) { assert(data != NULL); if (!net) { if (m_netState != RS_NET_IDLE) return; } uint8_t len = DMR_FRAME_LENGTH_BYTES + 2U; uint32_t space = m_queue.freeSpace(); if (space < (len + 1U)) { if (!net) { uint32_t queueLen = m_queue.length(); m_queue.resize(queueLen + (DMR_FRAME_LENGTH_BYTES + 2U)); LogError(LOG_DMR, "Slot %u, overflow in the DMR slot queue; queue free is %u, needed %u; resized was %u is %u", m_slotNo, space, len, queueLen, m_queue.length()); return; } else { LogError(LOG_DMR, "Slot %u, overflow in the DMR slot queue while writing network data; queue free is %u, needed %u", m_slotNo, space, len); return; } } if (m_debug) { Utils::symbols("!!! *Tx DMR", data + 2U, len - 2U); } m_queue.addData(&len, 1U); m_queue.addData(data, len); } /// /// Write data frame to the network. /// /// /// /// void Slot::writeNetwork(const uint8_t* data, uint8_t dataType, uint8_t errors) { assert(data != NULL); assert(m_rfLC != NULL); writeNetwork(data, dataType, m_rfLC->getFLCO(), m_rfLC->getSrcId(), m_rfLC->getDstId(), errors); } /// /// Write data frame to the network. /// /// /// /// /// /// /// void Slot::writeNetwork(const uint8_t* data, uint8_t dataType, uint8_t flco, uint32_t srcId, uint32_t dstId, uint8_t errors) { assert(data != NULL); if (m_netState != RS_NET_IDLE) return; if (m_network == NULL) return; data::Data dmrData; dmrData.setSlotNo(m_slotNo); dmrData.setDataType(dataType); dmrData.setSrcId(srcId); dmrData.setDstId(dstId); dmrData.setFLCO(flco); dmrData.setN(m_voice->m_rfN); dmrData.setSeqNo(m_rfSeqNo); dmrData.setBER(errors); dmrData.setRSSI(m_rssi); m_rfSeqNo++; dmrData.setData(data + 2U); m_network->writeDMR(dmrData); } /// /// Helper to write RF end of frame data. /// /// void Slot::writeEndRF(bool writeEnd) { m_rfState = RS_RF_LISTENING; if (m_netState == RS_NET_IDLE) { if (m_enableTSCC) setShortLC_TSCC(m_siteData, m_tsccCnt); else setShortLC(m_slotNo, 0U); } if (writeEnd) { if (m_netState == RS_NET_IDLE && m_duplex && !m_rfTimeout) { // Create a dummy start end frame uint8_t data[DMR_FRAME_LENGTH_BYTES + 2U]; Sync::addDMRDataSync(data + 2U, m_duplex); lc::FullLC fullLC; fullLC.encode(*m_rfLC, data + 2U, DT_TERMINATOR_WITH_LC); SlotType slotType; slotType.setColorCode(m_colorCode); slotType.setDataType(DT_TERMINATOR_WITH_LC); slotType.encode(data + 2U); data[0U] = modem::TAG_EOT; data[1U] = 0x00U; for (uint32_t i = 0U; i < m_hangCount; i++) addFrame(data); } } m_data->m_pduDataOffset = 0U; if (m_network != NULL) m_network->resetDMR(m_slotNo); m_rfTimeoutTimer.stop(); m_rfTimeout = false; m_rfFrames = 0U; m_rfErrs = 0U; m_rfBits = 1U; delete m_rfLC; if (m_rfPrivacyLC != NULL) delete m_rfPrivacyLC; if (m_rfDataHeader != NULL) delete m_rfDataHeader; m_rfLC = NULL; m_rfPrivacyLC = NULL; m_rfDataHeader = NULL; } /// /// Helper to write network end of frame data. /// /// void Slot::writeEndNet(bool writeEnd) { m_netState = RS_NET_IDLE; setShortLC(m_slotNo, 0U); m_voice->m_lastFrameValid = false; if (writeEnd && !m_netTimeout) { // Create a dummy start end frame uint8_t data[DMR_FRAME_LENGTH_BYTES + 2U]; Sync::addDMRDataSync(data + 2U, m_duplex); lc::FullLC fullLC; fullLC.encode(*m_netLC, data + 2U, DT_TERMINATOR_WITH_LC); SlotType slotType; slotType.setColorCode(m_colorCode); slotType.setDataType(DT_TERMINATOR_WITH_LC); slotType.encode(data + 2U); data[0U] = modem::TAG_EOT; data[1U] = 0x00U; if (m_duplex) { for (uint32_t i = 0U; i < m_hangCount; i++) addFrame(data, true); } else { for (uint32_t i = 0U; i < 3U; i++) addFrame(data, true); } } m_data->m_pduDataOffset = 0U; if (m_network != NULL) m_network->resetDMR(m_slotNo); m_networkWatchdog.stop(); m_netTimeoutTimer.stop(); m_packetTimer.stop(); m_netTimeout = false; m_netFrames = 0U; m_netLost = 0U; m_netErrs = 0U; m_netBits = 1U; delete m_netLC; if (m_netPrivacyLC != NULL) delete m_netPrivacyLC; if (m_netDataHeader != NULL) delete m_netDataHeader; m_netLC = NULL; m_netPrivacyLC = NULL; m_netDataHeader = NULL; } /// /// Helper to write control channel packet data. /// /// /// void Slot::writeRF_ControlData(uint16_t frameCnt, uint8_t n) { uint8_t i = 0U, seqCnt = 0U; if (!m_enableTSCC) return; // don't add any frames if the queue is full uint8_t len = DMR_FRAME_LENGTH_BYTES + 2U; uint32_t space = m_queue.freeSpace(); if (space < (len + 1U)) { m_ccSeq--; if (m_ccSeq < 0U) m_ccSeq = 0U; return; } // loop to generate 3 control sequences if (frameCnt == 511U) { seqCnt = 3U; } // should we insert the Git Hash burst? bool hash = (frameCnt % 256U) == 0U; if (hash) { m_control->writeRF_TSCC_Git_Hash(); if (seqCnt > 0U) n++; return; } do { if (m_debug) { LogDebug(LOG_DMR, "writeRF_ControlData, frameCnt = %u, seq = %u", frameCnt, n); } switch (n) { 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(); break; } if (seqCnt > 0U) n++; i++; } while (i <= seqCnt); } /// /// /// /// /// /// /// void Slot::setShortLC(uint32_t slotNo, uint32_t id, uint8_t flco, bool voice) { assert(m_modem != NULL); switch (slotNo) { case 1U: m_id1 = 0U; m_flco1 = flco; m_voice1 = voice; if (id != 0U) { uint8_t buffer[3U]; buffer[0U] = (id << 16) & 0xFFU; buffer[1U] = (id << 8) & 0xFFU; buffer[2U] = (id << 0) & 0xFFU; m_id1 = edac::CRC::crc8(buffer, 3U); } break; case 2U: m_id2 = 0U; m_flco2 = flco; m_voice2 = voice; if (id != 0U) { uint8_t buffer[3U]; buffer[0U] = (id << 16) & 0xFFU; buffer[1U] = (id << 8) & 0xFFU; buffer[2U] = (id << 0) & 0xFFU; m_id2 = edac::CRC::crc8(buffer, 3U); } break; default: LogError(LOG_DMR, "invalid slot number passed to setShortLC, slotNo = %u", slotNo); return; } // If we have no activity to report, let the modem send the null Short LC when it's ready if (m_id1 == 0U && m_id2 == 0U) return; uint8_t lc[5U]; lc[0U] = SLCO_ACT; lc[1U] = 0x00U; lc[2U] = 0x00U; lc[3U] = 0x00U; if (m_id1 != 0U) { lc[2U] = m_id1; if (m_voice1) { if (m_flco1 == FLCO_GROUP) lc[1U] |= 0x80U; else lc[1U] |= 0x90U; } else { if (m_flco1 == FLCO_GROUP) lc[1U] |= 0xB0U; else lc[1U] |= 0xA0U; } } if (m_id2 != 0U) { lc[3U] = m_id2; if (m_voice2) { if (m_flco2 == FLCO_GROUP) lc[1U] |= 0x08U; else lc[1U] |= 0x09U; } else { if (m_flco2 == FLCO_GROUP) lc[1U] |= 0x0BU; else lc[1U] |= 0x0AU; } } lc[4U] = edac::CRC::crc8(lc, 4U); uint8_t sLC[9U]; lc::ShortLC shortLC; shortLC.encode(lc, sLC); m_modem->writeDMRShortLC(sLC); } /// /// /// /// /// /// void Slot::setShortLC_TSCC(SiteData siteData, uint16_t counter) { assert(m_modem != NULL); uint8_t lc[5U]; uint32_t lcValue = 0U; lcValue = SLCO_TSCC; lcValue = (lcValue << 2) + siteData.siteModel(); switch (siteData.siteModel()) { case SITE_MODEL_TINY: { lcValue = (lcValue << 9) + siteData.netId(); lcValue = (lcValue << 3) + siteData.siteId(); } break; case SITE_MODEL_SMALL: { lcValue = (lcValue << 7) + siteData.netId(); lcValue = (lcValue << 5) + siteData.siteId(); } break; case SITE_MODEL_LARGE: { lcValue = (lcValue << 5) + siteData.netId(); lcValue = (lcValue << 7) + siteData.siteId(); } break; case SITE_MODEL_HUGE: { lcValue = (lcValue << 2) + siteData.netId(); lcValue = (lcValue << 10) + siteData.siteId(); } break; } lcValue = (lcValue << 1) + ((siteData.requireReg()) ? 1U : 0U); lcValue = (lcValue << 9) + (counter & 0x1FFU); // split value into bytes lc[0U] = (uint8_t)((lcValue >> 24) & 0xFFU); lc[1U] = (uint8_t)((lcValue >> 16) & 0xFFU); lc[2U] = (uint8_t)((lcValue >> 8) & 0xFFU); lc[3U] = (uint8_t)((lcValue >> 0) & 0xFFU); lc[4U] = edac::CRC::crc8(lc, 4U); uint8_t sLC[9U]; lc::ShortLC shortLC; shortLC.encode(lc, sLC); m_modem->writeDMRShortLC(sLC); }