From 4ff3bbf7660df0e49b7ccf46b490c0e21a72bb3e Mon Sep 17 00:00:00 2001 From: adrian Date: Sat, 18 Apr 2026 11:58:50 +0300 Subject: [PATCH 1/7] DMR trunking patches --- Conf.cpp | 40 ++++++ Conf.h | 10 ++ DMRCSBK.cpp | 89 ++++++++++-- DMRCSBK.h | 13 +- DMRControl.cpp | 39 +++++- DMRData.h | 19 +++ DMRDataHeader.cpp | 16 ++- DMRDataHeader.h | 1 + DMRDefines.h | 3 + DMRNetwork.cpp | 26 +++- DMRNetwork.h | 2 +- DMRSlot.cpp | 341 +++++++++++++++++++++++++++++++++++++++++++--- DMRSlot.h | 6 + MMDVMHost.cpp | 40 ++++-- Modem.cpp | 93 ++++++++++++- Modem.h | 13 +- 16 files changed, 696 insertions(+), 55 deletions(-) diff --git a/Conf.cpp b/Conf.cpp index b03ff90..615d845 100644 --- a/Conf.cpp +++ b/Conf.cpp @@ -181,6 +181,11 @@ m_dmrEnabled(false), m_dmrBeacons(DMR_BEACONS::OFF), m_dmrBeaconInterval(60U), m_dmrBeaconDuration(3U), +m_dmrTrunking(false), +m_dmrControlChannel(false), +m_dmrControlChannelAlternateSlot(false), +m_dmrSystemCode(0), +m_dmrRegistrationRequired(true), #endif m_dmrId(0U), #if defined(USE_DMR) @@ -729,6 +734,16 @@ bool CConf::read() } else if (section == SECTION::DMR) { if (::strcmp(key, "Enable") == 0) m_dmrEnabled = ::atoi(value) == 1; + if (::strcmp(key, "Trunking") == 0) + m_dmrTrunking = ::atoi(value) == 1; + if (::strcmp(key, "ControlChannel") == 0) + m_dmrControlChannel = ::atoi(value) == 1; + if (::strcmp(key, "ControlChannelAlternateSlot") == 0) + m_dmrControlChannelAlternateSlot = ::atoi(value) == 1; + if (::strcmp(key, "SystemCode") == 0) + m_dmrSystemCode = (unsigned int)::atoi(value); + if (::strcmp(key, "RegistrationRequired") == 0) + m_dmrRegistrationRequired = ::atoi(value) == 1; else if (::strcmp(key, "Beacons") == 0) m_dmrBeacons = ::atoi(value) == 1 ? DMR_BEACONS::NETWORK : DMR_BEACONS::OFF; else if (::strcmp(key, "BeaconInterval") == 0) { @@ -1528,6 +1543,31 @@ bool CConf::getDMREnabled() const return m_dmrEnabled; } +bool CConf::getDMRTrunking() const +{ + return m_dmrTrunking; +} + +bool CConf::getDMRControlChannel() const +{ + return m_dmrControlChannel; +} + +bool CConf::getDMRControlChannelAlternateSlot() const +{ + return m_dmrControlChannelAlternateSlot; +} + +unsigned int CConf::getDMRSystemCode() const +{ + return m_dmrSystemCode; +} + +bool CConf::getDMRRegistrationRequired() const +{ + return m_dmrRegistrationRequired; +} + DMR_BEACONS CConf::getDMRBeacons() const { return m_dmrBeacons; diff --git a/Conf.h b/Conf.h index a6813d8..e125941 100644 --- a/Conf.h +++ b/Conf.h @@ -173,6 +173,11 @@ public: unsigned int getDMRModeHang() const; DMR_OVCM getDMROVCM() const; bool getDMRProtect() const; + bool getDMRTrunking() const; + bool getDMRControlChannel() const; + bool getDMRControlChannelAlternateSlot() const; + unsigned int getDMRSystemCode() const; + bool getDMRRegistrationRequired() const; #endif #if defined(USE_YSF) @@ -449,6 +454,11 @@ private: DMR_BEACONS m_dmrBeacons; unsigned int m_dmrBeaconInterval; unsigned int m_dmrBeaconDuration; + bool m_dmrTrunking; + bool m_dmrControlChannel; + bool m_dmrControlChannelAlternateSlot; + unsigned int m_dmrSystemCode; + bool m_dmrRegistrationRequired; #endif unsigned int m_dmrId; #if defined(USE_DMR) diff --git a/DMRCSBK.cpp b/DMRCSBK.cpp index 451c01e..194070f 100644 --- a/DMRCSBK.cpp +++ b/DMRCSBK.cpp @@ -1,6 +1,7 @@ /* * Copyright (C) 2015,2016,2020,2021,2022,2023,2025 by Jonathan Naylor G4KLX * Copyright (C) 2019 by Patrick Maier DK5MP + * Copyright (C) 2026 by Adrian Musceac YO8RZZ * * 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 @@ -37,7 +38,8 @@ m_srcId(0U), m_dstId(0U), m_dataContent(false), m_CBF(0U), -m_OVCM(false) +m_OVCM(false), +m_dataType(DT_CSBK) { m_data = new unsigned char[12U]; } @@ -47,23 +49,46 @@ CDMRCSBK::~CDMRCSBK() delete[] m_data; } +void CDMRCSBK::setCSBKData(unsigned char* bytes) +{ + for(unsigned int i=0; i<10; i++) + { + m_data[i] = bytes[i]; + } +} + bool CDMRCSBK::put(const unsigned char* bytes) { assert(bytes != nullptr); CBPTC19696 bptc; bptc.decode(bytes, m_data); - - m_data[10U] ^= CSBK_CRC_MASK[0U]; - m_data[11U] ^= CSBK_CRC_MASK[1U]; + if(m_dataType == DT_CSBK) + { + m_data[10U] ^= CSBK_CRC_MASK[0U]; + m_data[11U] ^= CSBK_CRC_MASK[1U]; + } + else if(m_dataType == DT_MBC_HEADER) + { + m_data[10U] ^= MBC_CRC_MASK[0U]; + m_data[11U] ^= MBC_CRC_MASK[1U]; + } bool valid = CCRC::checkCCITT162(m_data, 12U); if (!valid) return false; // Restore the checksum - m_data[10U] ^= CSBK_CRC_MASK[0U]; - m_data[11U] ^= CSBK_CRC_MASK[1U]; + if(m_dataType == DT_CSBK) + { + m_data[10U] ^= CSBK_CRC_MASK[0U]; + m_data[11U] ^= CSBK_CRC_MASK[1U]; + } + else if(m_dataType == DT_MBC_HEADER) + { + m_data[10U] ^= MBC_CRC_MASK[0U]; + m_data[11U] ^= MBC_CRC_MASK[1U]; + } m_CSBKO = CSBKO(m_data[0U] & 0x3FU); m_FID = m_data[1U]; @@ -148,6 +173,22 @@ bool CDMRCSBK::put(const unsigned char* bytes) m_dataContent = false; m_CBF = 0U; break; + case CSBKO::ACKU: + m_GI = false; + m_dstId = m_data[4U] << 16 | m_data[5U] << 8 | m_data[6U]; + m_srcId = m_data[7U] << 16 | m_data[8U] << 8 | m_data[9U]; + m_dataContent = false; + m_CBF = m_data[3U]; + CUtils::dump(1U, "ACKU CSBK", m_data, 12U); + break; + case CSBKO::MAINT: + m_GI = false; + m_dstId = m_data[4U] << 16 | m_data[5U] << 8 | m_data[6U]; + m_srcId = m_data[7U] << 16 | m_data[8U] << 8 | m_data[9U]; + m_dataContent = false; + m_CBF = m_data[3U]; + CUtils::dump(1U, "MAINT CSBK", m_data, 12U); + break; case CSBKO::CALL_EMERGENCY: m_GI = true; @@ -174,14 +215,30 @@ bool CDMRCSBK::put(const unsigned char* bytes) void CDMRCSBK::get(unsigned char* bytes) const { assert(bytes != nullptr); + if(m_dataType == DT_CSBK) + { + m_data[10U] ^= CSBK_CRC_MASK[0U]; + m_data[11U] ^= CSBK_CRC_MASK[1U]; - m_data[10U] ^= CSBK_CRC_MASK[0U]; - m_data[11U] ^= CSBK_CRC_MASK[1U]; + CCRC::addCCITT162(m_data, 12U); - CCRC::addCCITT162(m_data, 12U); + m_data[10U] ^= CSBK_CRC_MASK[0U]; + m_data[11U] ^= CSBK_CRC_MASK[1U]; + } + else if(m_dataType == DT_MBC_HEADER) + { + m_data[10U] ^= MBC_CRC_MASK[0U]; + m_data[11U] ^= MBC_CRC_MASK[1U]; - m_data[10U] ^= CSBK_CRC_MASK[0U]; - m_data[11U] ^= CSBK_CRC_MASK[1U]; + CCRC::addCCITT162(m_data, 12U); + + m_data[10U] ^= MBC_CRC_MASK[0U]; + m_data[11U] ^= MBC_CRC_MASK[1U]; + } + else if(m_dataType == DT_MBC_CONTINUATION) + { + CCRC::addCCITT162(m_data, 12U); + } CBPTC19696 bptc; bptc.encode(m_data, bytes); @@ -249,5 +306,15 @@ void CDMRCSBK::setCBF(unsigned char cbf) m_CBF = m_data[3U] = cbf; } +void CDMRCSBK::setDataType(unsigned char dataType) +{ + m_dataType = dataType; +} + +unsigned char CDMRCSBK::getDataType() +{ + return m_dataType; +} + #endif diff --git a/DMRCSBK.h b/DMRCSBK.h index 169023b..84124bd 100644 --- a/DMRCSBK.h +++ b/DMRCSBK.h @@ -35,7 +35,14 @@ enum class CSBKO : unsigned char { NACKRSP = 0x26, CALL_EMERGENCY = 0x27, BSDWNACT = 0x38, - PRECCSBK = 0x3D + PRECCSBK = 0x3D, + MAINT = 0x2A, + TV_GRANT = 0x31, + PV_GRANT = 0x30, + AHOY = 0x1C, + ACKU = 0x21, + P_CLEAR = 0x2E, + C_BCAST = 0x28 }; class CDMRCSBK @@ -47,6 +54,7 @@ public: bool put(const unsigned char* bytes); void get(unsigned char* bytes) const; + void setCSBKData(unsigned char* bytes); // Generic fields CSBKO getCSBKO() const; @@ -69,6 +77,8 @@ public: unsigned char getCBF() const; void setCBF(unsigned char cbf); + void setDataType(unsigned char dataType); + unsigned char getDataType(); private: unsigned char* m_data; @@ -81,6 +91,7 @@ private: bool m_dataContent; unsigned char m_CBF; bool m_OVCM; + unsigned char m_dataType; }; #endif diff --git a/DMRControl.cpp b/DMRControl.cpp index 88bab80..f6ec77c 100644 --- a/DMRControl.cpp +++ b/DMRControl.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2015-2021,2023,2025 Jonathan Naylor, G4KLX + * Copyright (C) 2026 Adrian Musceac, YO8RZZ * * 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 @@ -108,13 +109,39 @@ void CDMRControl::clock() { if (m_network != nullptr) { CDMRData data; - bool ret = m_network->read(data); + TrunkingCommandParameters command; + bool ret = m_network->read(data, command); if (ret) { - unsigned int slotNo = data.getSlotNo(); - switch (slotNo) { - case 1U: m_slot1.writeNetwork(data); break; - case 2U: m_slot2.writeNetwork(data); break; - default: LogError("Invalid slot no %u", slotNo); break; + if(m_modem->getDMRTrunking() && command.trunkingParams) { + switch(command.commandType) { + case DMRCommand::ChannelEnableDisable: + { + if(command.slot == 1) { + if(!m_modem->getControlChannel()) + m_slot1.enable(command.channelEnable); + } + else { + m_slot2.enable(command.channelEnable); + } + } + break; + default: + { + if(command.slot == 1) + m_slot1.setReverseChannelCommand(command.commandType); + else + m_slot2.setReverseChannelCommand(command.commandType); + } + break; + } + } + else { + unsigned int slotNo = data.getSlotNo(); + switch (slotNo) { + case 1U: m_slot1.writeNetwork(data); break; + case 2U: m_slot2.writeNetwork(data); break; + default: LogError("Invalid slot no %u", slotNo); break; + } } } } diff --git a/DMRData.h b/DMRData.h index 15f7c25..e021b99 100644 --- a/DMRData.h +++ b/DMRData.h @@ -19,6 +19,25 @@ #if defined(USE_DMR) +struct TrunkingCommandParameters { + unsigned int commandType = 0; + bool trunkingParams = false; + bool channelEnable = false; + unsigned int slot = 1; + bool ceaseTransmission = false; +}; + +enum DMRCommand { + ChannelEnableDisable = 1, + RCCeaseTransmission = 2, + RCRequestCeaseTransmission = 3, + RCPowerIncreaseOneStep = 4, + RCPowerDecreaseOneStep = 5, + RCMaximumPower = 6, + RCMinimumPower = 7, + RCNoCommand = 0, +}; + class CDMRData { public: CDMRData(const CDMRData& data); diff --git a/DMRDataHeader.cpp b/DMRDataHeader.cpp index 164cafb..1ee1b8b 100644 --- a/DMRDataHeader.cpp +++ b/DMRDataHeader.cpp @@ -1,6 +1,7 @@ /* * Copyright (C) 2012 by Ian Wraith * Copyright (C) 2015,2016,2017,2023,2025 by Jonathan Naylor G4KLX + * Copyright (C) 2026 by Adrian Musceac YO8RZZ * * 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 @@ -42,7 +43,8 @@ m_dstId(0U), m_blocks(0U), m_F(false), m_S(false), -m_Ns(0U) +m_Ns(0U), +m_UDT(false) { m_data = new unsigned char[12U]; } @@ -121,6 +123,7 @@ bool CDMRDataHeader::put(const unsigned char* bytes) case DPF_UDT: CUtils::dump(1U, "DMR, Unified Data Transport Header", m_data, 12U); m_blocks = (m_data[8U] & 0x03U) + 1U; + m_UDT = true; break; default: @@ -134,6 +137,17 @@ bool CDMRDataHeader::put(const unsigned char* bytes) void CDMRDataHeader::get(unsigned char* bytes) const { assert(bytes != nullptr); + assert(bytes != NULL); + if(m_UDT) + { + // Table B.1: CSBK/MBC/UDT Opcode List + // Convert to Unified Data Transport outbound Header + m_data[9U] &= 0xFE; + } + CCRC::addCCITT162(m_data, 12U); + // Restore the checksum + m_data[10U] ^= DATA_HEADER_CRC_MASK[0U]; + m_data[11U] ^= DATA_HEADER_CRC_MASK[1U]; CBPTC19696 bptc; bptc.encode(m_data, bytes); diff --git a/DMRDataHeader.h b/DMRDataHeader.h index ae273b5..04ef0ba 100644 --- a/DMRDataHeader.h +++ b/DMRDataHeader.h @@ -52,6 +52,7 @@ private: bool m_F; bool m_S; unsigned char m_Ns; + bool m_UDT; }; #endif diff --git a/DMRDefines.h b/DMRDefines.h index 7c2d47f..fed922b 100644 --- a/DMRDefines.h +++ b/DMRDefines.h @@ -73,6 +73,7 @@ const unsigned char TERMINATOR_WITH_LC_CRC_MASK[] = {0x99U, 0x99U, 0x99U}; const unsigned char PI_HEADER_CRC_MASK[] = {0x69U, 0x69U}; const unsigned char DATA_HEADER_CRC_MASK[] = {0xCCU, 0xCCU}; const unsigned char CSBK_CRC_MASK[] = {0xA5U, 0xA5U}; +const unsigned char MBC_CRC_MASK[] = {0xAAU, 0xAAU}; const unsigned int DMR_SLOT_TIME = 60U; const unsigned int AMBE_PER_SLOT = 3U; @@ -82,6 +83,8 @@ const unsigned char DT_VOICE_PI_HEADER = 0x00U; const unsigned char DT_VOICE_LC_HEADER = 0x01U; const unsigned char DT_TERMINATOR_WITH_LC = 0x02U; const unsigned char DT_CSBK = 0x03U; +const unsigned char DT_MBC_HEADER = 0x04U; +const unsigned char DT_MBC_CONTINUATION = 0x05U; const unsigned char DT_DATA_HEADER = 0x06U; const unsigned char DT_RATE_12_DATA = 0x07U; const unsigned char DT_RATE_34_DATA = 0x08U; diff --git a/DMRNetwork.cpp b/DMRNetwork.cpp index 19808fd..f62c70c 100644 --- a/DMRNetwork.cpp +++ b/DMRNetwork.cpp @@ -123,7 +123,7 @@ void CDMRNetwork::enable(bool enabled) m_enabled = enabled; } -bool CDMRNetwork::read(CDMRData& data) +bool CDMRNetwork::read(CDMRData& data, TrunkingCommandParameters &command) { if (m_rxData.isEmpty()) return false; @@ -131,6 +131,28 @@ bool CDMRNetwork::read(CDMRData& data) unsigned char length = 0U; m_rxData.getData(&length, 1U); m_rxData.getData(m_buffer, length); + if (::memcmp(m_buffer, "DMRT", 4U) == 0) + { + unsigned int command_type = (unsigned int)m_buffer[4U]; + switch(command_type) + { + case DMRCommand::ChannelEnableDisable: + { + command.channelEnable = (m_buffer[5U] & 0x01U) == 0x01U ? true : false; + } + break; + default: + break; + } + command.slot = (m_buffer[5U] & 0x80U) == 0x80U ? 2U : 1U; + command.commandType = command_type; + command.trunkingParams = true; + return true; + } + else + { + command.trunkingParams = false; + } // Is this a data packet? if (::memcmp(m_buffer, "DMRD", 4U) != 0) @@ -318,7 +340,7 @@ void CDMRNetwork::clock(unsigned int ms) if (m_debug) CUtils::dump(1U, "DMR Network Received", m_buffer, length); - if (::memcmp(m_buffer, "DMRD", 4U) == 0) { + if ((::memcmp(m_buffer, "DMRD", 4U) == 0) || (::memcmp(m_buffer, "DMRT", 4U) == 0)) { if (m_enabled) { unsigned char len = length; m_rxData.addData(&len, 1U); diff --git a/DMRNetwork.h b/DMRNetwork.h index 2d90953..c9e76c0 100644 --- a/DMRNetwork.h +++ b/DMRNetwork.h @@ -43,7 +43,7 @@ public: void enable(bool enabled); - bool read(CDMRData& data); + bool read(CDMRData& data, TrunkingCommandParameters &command); bool write(const CDMRData& data); diff --git a/DMRSlot.cpp b/DMRSlot.cpp index d00b456..45e2066 100644 --- a/DMRSlot.cpp +++ b/DMRSlot.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2015-2021,2023,2025 Jonathan Naylor, G4KLX + * Copyright (C) 2026 Adrian Musceac, YO8RZZ * * 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 @@ -20,7 +21,6 @@ #include "BPTC19696.h" #include "DMRSlot.h" #include "DMRCSBK.h" -#include "DMREMB.h" #include "Utils.h" #include "Sync.h" #include "CRC.h" @@ -71,6 +71,14 @@ const unsigned int NO_HEADERS_SIMPLEX = 8U; const unsigned int NO_HEADERS_DUPLEX = 3U; const unsigned int NO_PREAMBLE_CSBK = 15U; + +const unsigned int RC_CEASE_TRANSMIT[5] = {0x07, 0x0D, 0x04, 0xF1, 0xF0}; +const unsigned int RC_REQUEST_CEASE_TRANSMIT[5] = {0x02, 0x7B, 0x4E, 0x48, 0x70 }; +const unsigned int RC_MAX_POWER[5] = {0x01, 0xC3, 0xDD, 0x3C, 0x10 }; +const unsigned int RC_MIN_POWER[5] = {0x04, 0xB5, 0x97, 0x85, 0x90 }; +const unsigned int RC_POWER_INCREASE[5] = {0x04, 0x5B, 0xA7, 0x58, 0xA0 }; +const unsigned int RC_POWER_DECREASE[5] = {0x01, 0x2D, 0xED, 0xE1, 0x20 }; + const unsigned int RSSI_COUNT = 3U; // 3 * 360ms = 1080ms const unsigned int BER_COUNT = 18U * 141U; // 18 * 60ms = 1080ms @@ -124,7 +132,8 @@ m_rssiAccum(0), m_rssiCount(0U), m_bitErrsAccum(0U), m_bitsCount(0U), -m_enabled(true) +m_enabled(true), +m_reverseChannelCommand(0) { m_lastFrame = new unsigned char[DMR_FRAME_LENGTH_BYTES + 2U]; @@ -227,6 +236,7 @@ bool CDMRSlot::writeModem(unsigned char *data, unsigned int len) if (dataType == DT_VOICE_LC_HEADER) { if (m_rfState == RPT_RF_STATE::AUDIO) return true; + m_reverseChannelCommand = DMRCommand::RCNoCommand; CDMRFullLC fullLC; CDMRLC* lc = fullLC.decode(data + 2U, DT_VOICE_LC_HEADER); @@ -363,7 +373,7 @@ bool CDMRSlot::writeModem(unsigned char *data, unsigned int len) } else if (dataType == DT_TERMINATOR_WITH_LC) { if (m_rfState != RPT_RF_STATE::AUDIO) return false; - + m_reverseChannelCommand = DMRCommand::RCNoCommand; // Regenerate the LC data CDMRFullLC fullLC; fullLC.encode(*m_rfLC, data + 2U, DT_TERMINATOR_WITH_LC); @@ -381,7 +391,7 @@ bool CDMRSlot::writeModem(unsigned char *data, unsigned int len) writeNetworkRF(data, DT_TERMINATOR_WITH_LC); if (m_duplex) { - for (unsigned int i = 0U; i < m_hangCount; i++) + for (unsigned int i = 0U; i <= m_hangCount; i++) writeQueueRF(data); } } @@ -485,10 +495,13 @@ bool CDMRSlot::writeModem(unsigned char *data, unsigned int len) return false; // set the OVCM bit for the supported csbk - if ((m_ovcm == DMR_OVCM::TX_ON) || (m_ovcm == DMR_OVCM::ON)) - csbk.setOVCM(true); - else if (m_ovcm == DMR_OVCM::FORCE_OFF) - csbk.setOVCM(false); + if(!m_modem->getDMRTrunking()) + { + if ((m_ovcm == DMR_OVCM::TX_ON) || (m_ovcm == DMR_OVCM::ON)) + csbk.setOVCM(true); + else if (m_ovcm == DMR_OVCM::FORCE_OFF) + csbk.setOVCM(false); + } bool gi = csbk.getGI(); unsigned int srcId = csbk.getSrcId(); @@ -524,7 +537,7 @@ bool CDMRSlot::writeModem(unsigned char *data, unsigned int len) data[0U] = TAG_DATA; data[1U] = 0x00U; - if (m_duplex) + if (m_duplex && !m_modem->getDMRTrunking()) writeQueueRF(data); writeNetworkRF(data, DT_CSBK, gi ? FLCO::GROUP : FLCO::USER_USER, srcId, dstId); @@ -563,7 +576,7 @@ bool CDMRSlot::writeModem(unsigned char *data, unsigned int len) writeJSONRF("csbk", "Call Emergency", srcId, src, gi, dstId); break; default: - LogWarning("DMR Slot %u, unhandled RF CSBK type - 0x%02X", m_slotNo, csbko); + LogMessage("DMR Slot %u, received unhandled CSBK from %s to %s%s", m_slotNo, src.c_str(), gi ? "TG " : "", dst.c_str()); break; } @@ -848,7 +861,7 @@ bool CDMRSlot::writeModem(unsigned char *data, unsigned int len) emb.setLCSS(lcss); emb.getData(data + 2U); } - + createReverseChannel(data, emb); if (m_duplex) writeQueueRF(data); @@ -1051,7 +1064,7 @@ void CDMRSlot::writeEndRF(bool writeEnd) data[0U] = TAG_EOT; data[1U] = 0x00U; - for (unsigned int i = 0U; i < m_hangCount; i++) + for (unsigned int i = 0U; i <= m_hangCount; i++) writeQueueRF(data); } } @@ -1099,7 +1112,7 @@ void CDMRSlot::writeEndNet(bool writeEnd) data[1U] = 0x00U; if (m_duplex) { - for (unsigned int i = 0U; i < m_hangCount; i++) + for (unsigned int i = 0U; i <= m_hangCount; i++) writeQueueNet(data); } else { for (unsigned int i = 0U; i < 3U; i++) @@ -1129,7 +1142,7 @@ void CDMRSlot::writeNetwork(const CDMRData& dmrData) if (!m_enabled) return; - if ((m_rfState != RPT_RF_STATE::LISTENING) && (m_netState == RPT_NET_STATE::IDLE)) + if ((m_rfState != RPT_RF_STATE::LISTENING) && (m_netState == RPT_NET_STATE::IDLE) && !m_modem->getDMRTrunking()) return; m_networkWatchdog.start(); @@ -1335,7 +1348,7 @@ void CDMRSlot::writeNetwork(const CDMRData& dmrData) data[1U] = 0x00U; if (m_duplex) { - for (unsigned int i = 0U; i < m_hangCount; i++) + for (unsigned int i = 0U; i <= m_hangCount; i++) writeQueueNet(data); } else { for (unsigned int i = 0U; i < 3U; i++) @@ -1686,10 +1699,13 @@ void CDMRSlot::writeNetwork(const CDMRData& dmrData) return; // set the OVCM bit for the supported csbk - if ((m_ovcm == DMR_OVCM::RX_ON) || (m_ovcm == DMR_OVCM::ON)) - csbk.setOVCM(true); - else if (m_ovcm == DMR_OVCM::FORCE_OFF) - csbk.setOVCM(false); + if(!m_modem->getDMRTrunking()) + { + if ((m_ovcm == DMR_OVCM::RX_ON) || (m_ovcm == DMR_OVCM::ON)) + csbk.setOVCM(true); + else if (m_ovcm == DMR_OVCM::FORCE_OFF) + csbk.setOVCM(false); + } bool gi = csbk.getGI(); unsigned int srcId = csbk.getSrcId(); @@ -1770,6 +1786,216 @@ void CDMRSlot::writeNetwork(const CDMRData& dmrData) break; } + // If data preamble, signal its existence + if ((csbko == CSBKO::PRECCSBK) && csbk.getDataContent()) + setShortLC(m_slotNo, dstId, gi ? FLCO::GROUP : FLCO::USER_USER, ACTIVITY_TYPE::DATA); + } else if (dataType == DT_MBC_HEADER) { + CDMRCSBK csbk; + csbk.setDataType(DT_MBC_HEADER); + bool valid = csbk.put(data + 2U); + if (!valid) { + LogMessage("DMR Slot %u, unable to decode the network CSBK", m_slotNo); + return; + } + + CSBKO csbko = csbk.getCSBKO(); + if (csbko == CSBKO::BSDWNACT) + return; + + // set the OVCM bit for the supported csbk + if(!m_modem->getDMRTrunking()) + { + if ((m_ovcm == DMR_OVCM::RX_ON) || (m_ovcm == DMR_OVCM::ON)) + csbk.setOVCM(true); + else if (m_ovcm == DMR_OVCM::FORCE_OFF) + csbk.setOVCM(false); + } + + bool gi = csbk.getGI(); + unsigned int srcId = csbk.getSrcId(); + unsigned int dstId = csbk.getDstId(); + + // Regenerate the CSBK data + csbk.get(data + 2U); + + // Regenerate the Slot Type + CDMRSlotType slotType; + slotType.setDataType(DT_MBC_HEADER); + slotType.putData(data + 2U); + slotType.setColorCode(m_colorCode); + slotType.getData(data + 2U); + + // Convert the Data Sync to be from the BS or MS as needed + CSync::addDMRDataSync(data + 2U, m_duplex); + + data[0U] = TAG_DATA; + data[1U] = 0x00U; + + if ((csbko == CSBKO::PRECCSBK) && csbk.getDataContent()) { + unsigned int cbf = NO_PREAMBLE_CSBK + csbk.getCBF() - 1U; + for (unsigned int i = 0U; i < NO_PREAMBLE_CSBK; i++, cbf--) { + // Change blocks to follow + csbk.setCBF(cbf); + + // Regenerate the CSBK data + csbk.get(data + 2U); + + // Regenerate the Slot Type + CDMRSlotType slotType; + slotType.putData(data + 2U); + slotType.setColorCode(m_colorCode); + slotType.getData(data + 2U); + + // Convert the Data Sync to be from the BS or MS as needed + CSync::addDMRDataSync(data + 2U, m_duplex); + + writeQueueNet(data); + } + } else + writeQueueNet(data); + + std::string src = m_lookup->find(srcId); + std::string dst = m_lookup->find(dstId); + + switch (csbko) { + case CSBKO::UUVREQ: + LogMessage("DMR Slot %u, received network Unit to Unit Voice Service Request CSBK from %s to %s%s", m_slotNo, src.c_str(), gi ? "TG ": "", dst.c_str()); + writeJSONNet("csbk", "Unit to Unit Voice Service Request", srcId, src, gi, dstId); + break; + case CSBKO::UUANSRSP: + LogMessage("DMR Slot %u, received network Unit to Unit Voice Service Answer Response CSBK from %s to %s%s", m_slotNo, src.c_str(), gi ? "TG ": "", dst.c_str()); + writeJSONNet("csbk", "Unit to Unit Voice Service Answer Response", srcId, src, gi, dstId); + break; + case CSBKO::NACKRSP: + LogMessage("DMR Slot %u, received network Negative Acknowledgment Response CSBK from %s to %s%s", m_slotNo, src.c_str(), gi ? "TG ": "", dst.c_str()); + writeJSONNet("csbk", "UNegative Acknowledgment Response", srcId, src, gi, dstId); + break; + case CSBKO::PRECCSBK: + LogMessage("DMR Slot %u, received network %s Preamble CSBK (%u to follow) from %s to %s%s", m_slotNo, csbk.getDataContent() ? "Data" : "CSBK", csbk.getCBF(), src.c_str(), gi ? "TG " : "", dst.c_str()); + writeJSONNet("csbk", "Preamble", srcId, src, gi, dstId); + break; + case CSBKO::CALL_ALERT: + LogMessage("DMR Slot %u, received network Call Alert CSBK from %s to %s%s", m_slotNo, src.c_str(), gi ? "TG " : "", dst.c_str()); + writeJSONNet("csbk", "Call Alert", srcId, src, gi, dstId); + break; + case CSBKO::CALL_ALERT_ACK: + LogMessage("DMR Slot %u, received network Call Alert Ack CSBK from %s to %s%s", m_slotNo, src.c_str(), gi ? "TG " : "", dst.c_str()); + writeJSONNet("csbk", "Call Alert Ack", srcId, src, gi, dstId); + break; + case CSBKO::RADIO_CHECK: + LogMessage("DMR Slot %u, received network Radio Check %s CSBK from %s to %s%s", m_slotNo, /* TBD */ 1 ? "Req" : "Ack", src.c_str(), gi ? "TG " : "", dst.c_str()); + writeJSONNet("csbk", "Radio Check", srcId, src, gi, dstId); + break; + default: + LogWarning("DMR Slot %u, unhandled network CSBK type - 0x%02X", m_slotNo, csbko); + break; + } + + // If data preamble, signal its existence + if ((csbko == CSBKO::PRECCSBK) && csbk.getDataContent()) + setShortLC(m_slotNo, dstId, gi ? FLCO::GROUP : FLCO::USER_USER, ACTIVITY_TYPE::DATA); + } else if (dataType == DT_MBC_CONTINUATION) { + CDMRCSBK csbk; + csbk.setDataType(DT_MBC_CONTINUATION); + bool valid = csbk.put(data + 2U); + if (!valid) { + LogMessage("DMR Slot %u, unable to decode the network CSBK", m_slotNo); + return; + } + + CSBKO csbko = csbk.getCSBKO(); + if (csbko == CSBKO::BSDWNACT) + return; + + // set the OVCM bit for the supported csbk + if(!m_modem->getDMRTrunking()) + { + if ((m_ovcm == DMR_OVCM::RX_ON) || (m_ovcm == DMR_OVCM::ON)) + csbk.setOVCM(true); + else if (m_ovcm == DMR_OVCM::FORCE_OFF) + csbk.setOVCM(false); + } + + bool gi = csbk.getGI(); + unsigned int srcId = csbk.getSrcId(); + unsigned int dstId = csbk.getDstId(); + + // Regenerate the CSBK data + csbk.get(data + 2U); + + // Regenerate the Slot Type + CDMRSlotType slotType; + slotType.setDataType(DT_MBC_CONTINUATION); + slotType.putData(data + 2U); + slotType.setColorCode(m_colorCode); + slotType.getData(data + 2U); + + // Convert the Data Sync to be from the BS or MS as needed + CSync::addDMRDataSync(data + 2U, m_duplex); + + data[0U] = TAG_DATA; + data[1U] = 0x00U; + + if ((csbko == CSBKO::PRECCSBK) && csbk.getDataContent()) { + unsigned int cbf = NO_PREAMBLE_CSBK + csbk.getCBF() - 1U; + for (unsigned int i = 0U; i < NO_PREAMBLE_CSBK; i++, cbf--) { + // Change blocks to follow + csbk.setCBF(cbf); + + // Regenerate the CSBK data + csbk.get(data + 2U); + + // Regenerate the Slot Type + CDMRSlotType slotType; + slotType.putData(data + 2U); + slotType.setColorCode(m_colorCode); + slotType.getData(data + 2U); + + // Convert the Data Sync to be from the BS or MS as needed + CSync::addDMRDataSync(data + 2U, m_duplex); + + writeQueueNet(data); + } + } else + writeQueueNet(data); + + std::string src = m_lookup->find(srcId); + std::string dst = m_lookup->find(dstId); + + switch (csbko) { + case CSBKO::UUVREQ: + LogMessage("DMR Slot %u, received network Unit to Unit Voice Service Request CSBK from %s to %s%s", m_slotNo, src.c_str(), gi ? "TG ": "", dst.c_str()); + writeJSONNet("csbk", "Unit to Unit Voice Service Request", srcId, src, gi, dstId); + break; + case CSBKO::UUANSRSP: + LogMessage("DMR Slot %u, received network Unit to Unit Voice Service Answer Response CSBK from %s to %s%s", m_slotNo, src.c_str(), gi ? "TG ": "", dst.c_str()); + writeJSONNet("csbk", "Unit to Unit Voice Service Answer Response", srcId, src, gi, dstId); + break; + case CSBKO::NACKRSP: + LogMessage("DMR Slot %u, received network Negative Acknowledgment Response CSBK from %s to %s%s", m_slotNo, src.c_str(), gi ? "TG ": "", dst.c_str()); + writeJSONNet("csbk", "UNegative Acknowledgment Response", srcId, src, gi, dstId); + break; + case CSBKO::PRECCSBK: + LogMessage("DMR Slot %u, received network %s Preamble CSBK (%u to follow) from %s to %s%s", m_slotNo, csbk.getDataContent() ? "Data" : "CSBK", csbk.getCBF(), src.c_str(), gi ? "TG " : "", dst.c_str()); + writeJSONNet("csbk", "Preamble", srcId, src, gi, dstId); + break; + case CSBKO::CALL_ALERT: + LogMessage("DMR Slot %u, received network Call Alert CSBK from %s to %s%s", m_slotNo, src.c_str(), gi ? "TG " : "", dst.c_str()); + writeJSONNet("csbk", "Call Alert", srcId, src, gi, dstId); + break; + case CSBKO::CALL_ALERT_ACK: + LogMessage("DMR Slot %u, received network Call Alert Ack CSBK from %s to %s%s", m_slotNo, src.c_str(), gi ? "TG " : "", dst.c_str()); + writeJSONNet("csbk", "Call Alert Ack", srcId, src, gi, dstId); + break; + case CSBKO::RADIO_CHECK: + LogMessage("DMR Slot %u, received network Radio Check %s CSBK from %s to %s%s", m_slotNo, /* TBD */ 1 ? "Req" : "Ack", src.c_str(), gi ? "TG " : "", dst.c_str()); + writeJSONNet("csbk", "Radio Check", srcId, src, gi, dstId); + break; + default: + LogWarning("DMR Slot %u, unhandled network CSBK type - 0x%02X", m_slotNo, csbko); + break; + } + // If data preamble, signal its existence if ((csbko == CSBKO::PRECCSBK) && csbk.getDataContent()) setShortLC(m_slotNo, dstId, gi ? FLCO::GROUP : FLCO::USER_USER, ACTIVITY_TYPE::DATA); @@ -2136,7 +2362,7 @@ void CDMRSlot::setShortLC(unsigned int slotNo, unsigned int id, FLCO flco, ACTIV CDMRShortLC shortLC; shortLC.encode(lc, sLC); - m_modem->writeDMRShortLC(sLC); + m_modem->writeDMRShortLC(sLC, false); } bool CDMRSlot::insertSilence(const unsigned char* data, unsigned char seqNo) @@ -2295,6 +2521,81 @@ void CDMRSlot::enable(bool enabled) m_enabled = enabled; } +void CDMRSlot::setReverseChannelCommand(unsigned int rc_command) { + m_reverseChannelCommand = rc_command; +} + +void CDMRSlot::createReverseChannel(unsigned char *data, CDMREMB &emb) { + if(m_rfN == 5U) { + if (m_reverseChannelCommand == DMRCommand::RCCeaseTransmission) { + data[16U] = (data[16U] & 0xF0U) | (RC_CEASE_TRANSMIT[0U] & 0x0FU); + data[17U] = RC_CEASE_TRANSMIT[1U]; + data[18U] = RC_CEASE_TRANSMIT[2U]; + data[19U] = RC_CEASE_TRANSMIT[3U]; + data[20U] = (data[20U] & 0x0FU) | (RC_CEASE_TRANSMIT[4U] & 0xF0U); + emb.setColorCode(m_colorCode); + emb.setLCSS(0); + emb.setPI(true); + emb.getData(data + 2U); + } + else if (m_reverseChannelCommand == DMRCommand::RCRequestCeaseTransmission) { + data[16U] = (data[16U] & 0xF0U) | (RC_REQUEST_CEASE_TRANSMIT[0U] & 0x0FU); + data[17U] = RC_REQUEST_CEASE_TRANSMIT[1U]; + data[18U] = RC_REQUEST_CEASE_TRANSMIT[2U]; + data[19U] = RC_REQUEST_CEASE_TRANSMIT[3U]; + data[20U] = (data[20U] & 0x0FU) | (RC_REQUEST_CEASE_TRANSMIT[4U] & 0xF0U); + emb.setColorCode(m_colorCode); + emb.setLCSS(0); + emb.setPI(true); + emb.getData(data + 2U); + } + else if (m_reverseChannelCommand == DMRCommand::RCMaximumPower) { + data[16U] = (data[16U] & 0xF0U) | (RC_MAX_POWER[0U] & 0x0FU); + data[17U] = RC_MAX_POWER[1U]; + data[18U] = RC_MAX_POWER[2U]; + data[19U] = RC_MAX_POWER[3U]; + data[20U] = (data[20U] & 0x0FU) | (RC_MAX_POWER[4U] & 0xF0U); + emb.setColorCode(m_colorCode); + emb.setLCSS(0); + emb.setPI(true); + emb.getData(data + 2U); + } + else if (m_reverseChannelCommand == DMRCommand::RCMinimumPower) { + data[16U] = (data[16U] & 0xF0U) | (RC_MIN_POWER[0U] & 0x0FU); + data[17U] = RC_MIN_POWER[1U]; + data[18U] = RC_MIN_POWER[2U]; + data[19U] = RC_MIN_POWER[3U]; + data[20U] = (data[20U] & 0x0FU) | (RC_MIN_POWER[4U] & 0xF0U); + emb.setColorCode(m_colorCode); + emb.setLCSS(0); + emb.setPI(true); + emb.getData(data + 2U); + } + else if (m_reverseChannelCommand == DMRCommand::RCPowerIncreaseOneStep) { + data[16U] = (data[16U] & 0xF0U) | (RC_POWER_INCREASE[0U] & 0x0FU); + data[17U] = RC_POWER_INCREASE[1U]; + data[18U] = RC_POWER_INCREASE[2U]; + data[19U] = RC_POWER_INCREASE[3U]; + data[20U] = (data[20U] & 0x0FU) | (RC_POWER_INCREASE[4U] & 0xF0U); + emb.setColorCode(m_colorCode); + emb.setLCSS(0); + emb.setPI(true); + emb.getData(data + 2U); + } + else if (m_reverseChannelCommand == DMRCommand::RCPowerDecreaseOneStep) { + data[16U] = (data[16U] & 0xF0U) | (RC_POWER_DECREASE[0U] & 0x0FU); + data[17U] = RC_POWER_DECREASE[1U]; + data[18U] = RC_POWER_DECREASE[2U]; + data[19U] = RC_POWER_DECREASE[3U]; + data[20U] = (data[20U] & 0x0FU) | (RC_POWER_DECREASE[4U] & 0xF0U); + emb.setColorCode(m_colorCode); + emb.setLCSS(0); + emb.setPI(true); + emb.getData(data + 2U); + } + } +} + void CDMRSlot::writeJSONRSSI() { if (m_rssi == 0U) diff --git a/DMRSlot.h b/DMRSlot.h index efeafd7..0fdd2cb 100644 --- a/DMRSlot.h +++ b/DMRSlot.h @@ -29,6 +29,7 @@ #include "AMBEFEC.h" #include "DMRSlot.h" #include "DMRData.h" +#include "DMREMB.h" #include "Defines.h" #include "Timer.h" #include "Modem.h" @@ -67,6 +68,8 @@ public: static void init(unsigned int colorCode, bool embeddedLCOnly, bool dumpTAData, unsigned int callHang, CModem* modem, CDMRNetwork* network, bool duplex, CDMRLookup* lookup, CRSSIInterpolator* rssiMapper, unsigned int jitter, DMR_OVCM ovcm, bool protect); + void setReverseChannelCommand(unsigned int rc_command); + private: unsigned int m_slotNo; CRingBuffer m_queue; @@ -145,6 +148,7 @@ private: static FLCO m_flco2; static unsigned char m_id2; static ACTIVITY_TYPE m_activity2; + unsigned int m_reverseChannelCommand; void logGPSPosition(const unsigned char* data); @@ -159,6 +163,8 @@ private: bool insertSilence(const unsigned char* data, unsigned char seqNo); void insertSilence(unsigned int count); + void createReverseChannel(unsigned char *data, CDMREMB &emb); + static void setShortLC(unsigned int slotNo, unsigned int id, FLCO flco = FLCO::GROUP, ACTIVITY_TYPE type = ACTIVITY_TYPE::NONE); void writeJSONRSSI(); diff --git a/MMDVMHost.cpp b/MMDVMHost.cpp index 4dba290..103c487 100644 --- a/MMDVMHost.cpp +++ b/MMDVMHost.cpp @@ -704,6 +704,18 @@ int CMMDVMHost::run() m_dmr = new CDMRControl(id, colorCode, callHang, selfOnly, embeddedLCOnly, dumpTAData, prefixes, blackList, whiteList, slot1TGWhiteList, slot2TGWhiteList, m_timeout, m_modem, m_dmrNetwork, m_duplex, m_dmrLookup, rssi, jitter, ovcm, protect); m_dmrTXTimer.setTimeout(txHang); + + // Tier III options + if(m_conf.getDMRTrunking()) + { + setMode(MODE_DMR); + m_modem->setShortLC(m_conf.getDMRSystemCode(), m_conf.getDMRControlChannel(), m_conf.getDMRRegistrationRequired()); + if(m_conf.getDMRControlChannel()) + { + m_modem->writeDMRAloha(m_conf.getDMRSystemCode(), m_conf.getDMRRegistrationRequired(), m_conf.getDMRControlChannelAlternateSlot()); + } + } + } #endif @@ -812,8 +824,8 @@ int CMMDVMHost::run() bool remoteControlEnabled = m_conf.getRemoteControlEnabled(); if (remoteControlEnabled) m_remoteControl = new CRemoteControl(this, m_mqtt); - - setMode(MODE_IDLE); + if(!m_conf.getDMRTrunking()) + setMode(MODE_IDLE); while (!m_killed) { bool lockout = m_modem->hasLockout(); @@ -895,7 +907,7 @@ int CMMDVMHost::run() if (m_mode == MODE_IDLE) { if (m_duplex) { bool ret = m_dmr->processWakeup(data); - if (ret) { + if (ret || m_conf.getDMRTrunking()) { m_modeTimer.setTimeout(m_dmrRFModeHang); setMode(MODE_DMR); dmrBeaconDurationTimer.stop(); @@ -909,7 +921,7 @@ int CMMDVMHost::run() } else if (m_mode == MODE_DMR) { if (m_duplex && !m_modem->hasTX()) { bool ret = m_dmr->processWakeup(data); - if (ret) { + if (ret || m_conf.getDMRTrunking()) { m_modem->writeDMRStart(true); m_dmrTXTimer.start(); } @@ -1008,7 +1020,7 @@ int CMMDVMHost::run() if (transparentSocket != nullptr && len > 0U) transparentSocket->write(data, len, transparentAddress, transparentAddrLen); - if (!m_fixedMode) { + if (!m_fixedMode && !m_conf.getDMRTrunking()) { if (m_modeTimer.isRunning() && m_modeTimer.hasExpired()) setMode(MODE_IDLE); } @@ -1341,14 +1353,24 @@ int CMMDVMHost::run() dmrBeaconDurationTimer.clock(ms); if (dmrBeaconDurationTimer.isRunning() && dmrBeaconDurationTimer.hasExpired()) { - if (!m_fixedMode) + if (!m_fixedMode && !m_conf.getDMRTrunking()) setMode(MODE_IDLE); dmrBeaconDurationTimer.stop(); } m_dmrTXTimer.clock(ms); if (m_dmrTXTimer.isRunning() && m_dmrTXTimer.hasExpired()) { - m_modem->writeDMRStart(false); + if(m_conf.getDMRTrunking()) + { + if(!m_conf.getDMRControlChannel()) + { + m_modem->writeDMRStart(false); + } + } + else + { + m_modem->writeDMRStart(false); + } m_dmrTXTimer.stop(); } #endif @@ -1566,6 +1588,8 @@ bool CMMDVMHost::createModem() int txDCOffset = m_conf.getModemTXDCOffset(); float rfLevel = m_conf.getModemRFLevel(); bool useCOSAsLockout = m_conf.getModemUseCOSAsLockout(); + bool trunking = m_conf.getDMRTrunking(); + bool controlChannel = m_conf.getDMRControlChannel(); LogInfo("Modem Parameters"); LogInfo(" Protocol: %s", protocol.c_str()); @@ -1625,7 +1649,7 @@ bool CMMDVMHost::createModem() LogInfo(" RX Frequency: %uHz (%uHz)", rxFrequency, rxFrequency + rxOffset); LogInfo(" Use COS as Lockout: %s", useCOSAsLockout ? "yes" : "no"); - m_modem = new CModem(m_duplex, rxInvert, txInvert, pttInvert, txDelay, dmrDelay, useCOSAsLockout, trace, debug); + m_modem = new CModem(m_duplex, rxInvert, txInvert, pttInvert, txDelay, dmrDelay, useCOSAsLockout, trace, debug, trunking, controlChannel); IModemPort* port = nullptr; if (protocol == "uart") diff --git a/Modem.cpp b/Modem.cpp index e87f061..aa72ada 100644 --- a/Modem.cpp +++ b/Modem.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2011-2018,2020,2021,2023,2025 by Jonathan Naylor G4KLX + * Copyright (C) 2026 by Adrian Musceac YO8RZZ * * 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 @@ -26,6 +27,8 @@ #include "Modem.h" #include "Utils.h" #include "Log.h" +#include "CRC.h" +#include "DMRShortLC.h" #include #include @@ -62,6 +65,7 @@ const unsigned char MMDVM_DMR_LOST1 = 0x19U; const unsigned char MMDVM_DMR_DATA2 = 0x1AU; const unsigned char MMDVM_DMR_LOST2 = 0x1BU; const unsigned char MMDVM_DMR_SHORTLC = 0x1CU; +const unsigned char MMDVM_DMR_ALOHA = 0x14U; const unsigned char MMDVM_DMR_START = 0x1DU; const unsigned char MMDVM_DMR_ABORT = 0x1EU; #endif @@ -124,7 +128,7 @@ const unsigned char CAP1_NXDN = 0x10U; const unsigned char CAP1_FM = 0x40U; const unsigned char CAP2_POCSAG = 0x01U; -CModem::CModem(bool duplex, bool rxInvert, bool txInvert, bool pttInvert, unsigned int txDelay, unsigned int dmrDelay, bool useCOSAsLockout, bool trace, bool debug) : +CModem::CModem(bool duplex, bool rxInvert, bool txInvert, bool pttInvert, unsigned int txDelay, unsigned int dmrDelay, bool useCOSAsLockout, bool trace, bool debug, bool trunking, bool controlChannel) : m_protocolVersion(0U), #if defined(USE_DMR) m_dmrColorCode(0U), @@ -312,6 +316,8 @@ m_fmExtEnable(false), #endif m_capabilities1(0x00U), m_capabilities2(0x00U), +m_trunking(trunking), +m_controlChannel(controlChannel), m_serialDataLen(0U) { m_buffer = new unsigned char[BUFFER_LENGTH]; @@ -2408,7 +2414,7 @@ bool CModem::setConfig2() buffer[26U] = 0U; buffer[27U] = 0x00U; - buffer[28U] = 0x00U; + buffer[28U] = (m_trunking ? 1 : 0) << 7; #if defined(USE_DMR) buffer[29U] = m_dmrColorCode; @@ -2737,8 +2743,78 @@ bool CModem::writeDMRAbort(unsigned int slotNo) return m_port->write(buffer, 4U) == 4; } -bool CModem::writeDMRShortLC(const unsigned char* lc) +bool CModem::writeDMRAloha(unsigned int systemCode, bool registrationRequired, bool alternateSlot) { + // 7.1.1.1.4 Aloha (C_ALOHA) CSBK PDU + assert(m_port != NULL); + CDMRCSBK csbk; + unsigned char data[DMR_FRAME_LENGTH_BYTES + 2U]; + unsigned int tsccas = alternateSlot ? 1 : 0; + unsigned int timeslot_synchronization = 0; + unsigned int version = 2; // 3 bits + unsigned int offset_timing = 0; // MMDVM uses aligned timing + unsigned int net_connection = 1; + unsigned int mask = 0; // 5 bits + unsigned int service_function = 0; // 2 bits (ALL) + unsigned int n_rand_wait = 5; // 4 bits + unsigned int backoff = 4; // 4 bits + unsigned int ALLMSI = 0xFFFED4; + + data[2U] = 0x99; + data[3U] = 0x00; // FID + data[4U] = (unsigned char)((tsccas << 6) | (timeslot_synchronization << 5) | ((version & 0x07) << 2) | (offset_timing << 1) | net_connection); + data[5U] = (unsigned char)(((mask & 0x1F) << 3) | ((service_function & 0x03) << 1) | ((n_rand_wait & 0x0F) >> 3)); // TODO + data[6U] = (unsigned char)(((n_rand_wait & 0x0F) << 5) | (((unsigned int)registrationRequired) << 4) | (backoff & 0xF)); // TODO + data[7U] = (unsigned char)((systemCode >> 6) & 0xFF); + data[8U] = (unsigned char)(((systemCode << 2) | 0x03) & 0xFF); // PAR hardcoded to 11 + // MS Address + data[9U] = (unsigned char)(ALLMSI >> 16); + data[10U] = (unsigned char)((ALLMSI >> 8) & 0xFF); + data[11U] = (unsigned char)(ALLMSI & 0xFF); + csbk.setCSBKData(data + 2U); + csbk.get(data + 2U); + + CDMRSlotType slotType; + slotType.setColorCode(m_dmrColorCode); + slotType.setDataType(DT_CSBK); + slotType.getData(data + 2U); + + CSync::addDMRDataSync(data + 2U, m_duplex); + unsigned char buffer[DMR_FRAME_LENGTH_BYTES + 3U]; + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = DMR_FRAME_LENGTH_BYTES + 3U; + buffer[2U] = MMDVM_DMR_ALOHA; + for(unsigned int i=0; iwrite(buffer, DMR_FRAME_LENGTH_BYTES + 3U) == 12; +} + +void CModem::setShortLC(unsigned int systemCode, bool isControlChannel, bool registrationRequired) +{ + // Used for setting CACH (C_SysParm and P_SysParm) on TSCC and Payload Ch + unsigned char lc[5U]; + // 7.1.2.1 Control Channel System Parameters + // 7.1.2.2 Payload Channel System Parameters + lc[0U] = isControlChannel? 0x2U : 0x3U; + lc[1U] = (unsigned char)((systemCode >> 6) & 0XFF); + unsigned int regi = (unsigned int) registrationRequired; + lc[2U] = (unsigned char)(((systemCode << 2) & 0xFF) | (regi << 1)); // 6 bit System code, 1 bit Reg_Required, rest of bits CSC + lc[3U] = 0x01U; // CSC: This should increment from 1 to 511, but not implemented yet + lc[4U] = CCRC::crc8(lc, 4U); + unsigned char sLC[9U]; + CDMRShortLC shortLC; + shortLC.encode(lc, sLC); + this->writeDMRShortLC(sLC, true); +} + +bool CModem::writeDMRShortLC(const unsigned char* lc, bool control) +{ + if(!control && m_trunking) + return true; assert(m_port != nullptr); assert(lc != nullptr); @@ -3081,3 +3157,14 @@ void CModem::printDebug() CUtils::dump(1U, "Debug: Data", m_buffer + m_offset, m_length - m_offset); } } + +bool CModem::getDMRTrunking() const +{ + return m_trunking; +} + +bool CModem::getControlChannel() const +{ + return m_controlChannel; +} + diff --git a/Modem.h b/Modem.h index 33004d6..8f55c5e 100644 --- a/Modem.h +++ b/Modem.h @@ -23,6 +23,9 @@ #include "RingBuffer.h" #include "Defines.h" #include "Timer.h" +#include "DMRCSBK.h" +#include "DMRSlotType.h" +#include "Sync.h" #include @@ -42,7 +45,7 @@ enum class SERIAL_STATE { class CModem { public: - CModem(bool duplex, bool rxInvert, bool txInvert, bool pttInvert, unsigned int txDelay, unsigned int dmrDelay, bool useCOSAsLockout, bool trace, bool debug); + CModem(bool duplex, bool rxInvert, bool txInvert, bool pttInvert, unsigned int txDelay, unsigned int dmrDelay, bool useCOSAsLockout, bool trace, bool debug, bool trunking, bool controlChannel); ~CModem(); void setPort(IModemPort* port); @@ -179,7 +182,9 @@ public: #if defined(USE_DMR) bool writeDMRStart(bool tx); - bool writeDMRShortLC(const unsigned char* lc); + bool writeDMRAloha(unsigned int systemCode, bool registrationRequired, bool alternateSlot); + void setShortLC(unsigned int systemCode, bool isControlChannel, bool registrationRequired); + bool writeDMRShortLC(const unsigned char* lc, bool control); bool writeDMRAbort(unsigned int slotNo); #endif bool writeTransparentData(const unsigned char* data, unsigned int length); @@ -198,6 +203,8 @@ public: void clock(unsigned int ms); void close(); + bool getDMRTrunking() const; + bool getControlChannel() const; private: unsigned int m_protocolVersion; @@ -388,6 +395,8 @@ private: #endif unsigned char m_capabilities1; unsigned char m_capabilities2; + bool m_trunking; + bool m_controlChannel; bool readVersion(); bool readStatus(); From 4007f86df232634838011acc4b6a15930d4b8f9c Mon Sep 17 00:00:00 2001 From: adrian Date: Sun, 19 Apr 2026 11:29:18 +0300 Subject: [PATCH 2/7] Change DMR ALOHA header to 0x17 --- Modem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modem.cpp b/Modem.cpp index aa72ada..a15fa91 100644 --- a/Modem.cpp +++ b/Modem.cpp @@ -60,12 +60,12 @@ const unsigned char MMDVM_DSTAR_EOT = 0x13U; #endif #if defined(USE_DMR) +const unsigned char MMDVM_DMR_ALOHA = 0x17U; const unsigned char MMDVM_DMR_DATA1 = 0x18U; const unsigned char MMDVM_DMR_LOST1 = 0x19U; const unsigned char MMDVM_DMR_DATA2 = 0x1AU; const unsigned char MMDVM_DMR_LOST2 = 0x1BU; const unsigned char MMDVM_DMR_SHORTLC = 0x1CU; -const unsigned char MMDVM_DMR_ALOHA = 0x14U; const unsigned char MMDVM_DMR_START = 0x1DU; const unsigned char MMDVM_DMR_ABORT = 0x1EU; #endif From 858f6e739918eb65c5501fe7b7f703aaf49ce55a Mon Sep 17 00:00:00 2001 From: Jonathan Naylor Date: Mon, 27 Apr 2026 12:27:51 +0100 Subject: [PATCH 3/7] Clean up the DMR trunking changes. --- Conf.cpp | 2 +- DMRCSBK.cpp | 66 ++++++++++++++++++++++++++++++----------------- DMRCSBK.h | 39 ++++++++++++++-------------- DMRControl.cpp | 37 ++++++++++---------------- DMRData.h | 29 ++++++++++----------- DMRDataHeader.cpp | 18 ++++++------- DMRDataHeader.h | 1 - DMRNetwork.cpp | 34 ++++++++++-------------- DMRNetwork.h | 5 ++-- DMRSlot.cpp | 49 +++++++++++++++++------------------ DMRSlot.h | 7 +++-- MMDVMHost.cpp | 37 +++++++++++++++++--------- Modem.cpp | 31 ++++++++++++++-------- Modem.h | 16 +++++++----- 14 files changed, 196 insertions(+), 175 deletions(-) diff --git a/Conf.cpp b/Conf.cpp index 615d845..2b2cc1d 100644 --- a/Conf.cpp +++ b/Conf.cpp @@ -184,7 +184,7 @@ m_dmrBeaconDuration(3U), m_dmrTrunking(false), m_dmrControlChannel(false), m_dmrControlChannelAlternateSlot(false), -m_dmrSystemCode(0), +m_dmrSystemCode(0U), m_dmrRegistrationRequired(true), #endif m_dmrId(0U), diff --git a/DMRCSBK.cpp b/DMRCSBK.cpp index 194070f..4c8505a 100644 --- a/DMRCSBK.cpp +++ b/DMRCSBK.cpp @@ -22,6 +22,7 @@ #include "BPTC19696.h" #include "Utils.h" #include "CRC.h" +#include "Log.h" #if defined(USE_DMR) @@ -51,10 +52,9 @@ CDMRCSBK::~CDMRCSBK() void CDMRCSBK::setCSBKData(unsigned char* bytes) { - for(unsigned int i=0; i<10; i++) - { - m_data[i] = bytes[i]; - } + assert(bytes != nullptr); + + ::memcpy(m_data, bytes, 10U); } bool CDMRCSBK::put(const unsigned char* bytes) @@ -63,15 +63,21 @@ bool CDMRCSBK::put(const unsigned char* bytes) CBPTC19696 bptc; bptc.decode(bytes, m_data); - if(m_dataType == DT_CSBK) - { + + switch (m_dataType) { + case DT_CSBK: m_data[10U] ^= CSBK_CRC_MASK[0U]; m_data[11U] ^= CSBK_CRC_MASK[1U]; - } - else if(m_dataType == DT_MBC_HEADER) - { + break; + + case DT_MBC_HEADER: m_data[10U] ^= MBC_CRC_MASK[0U]; m_data[11U] ^= MBC_CRC_MASK[1U]; + break; + + default: + LogError("Unknown DMR CSBK data type: 0x%02X", m_dataType); + return false; } bool valid = CCRC::checkCCITT162(m_data, 12U); @@ -79,15 +85,20 @@ bool CDMRCSBK::put(const unsigned char* bytes) return false; // Restore the checksum - if(m_dataType == DT_CSBK) - { + switch (m_dataType) { + case DT_CSBK: m_data[10U] ^= CSBK_CRC_MASK[0U]; m_data[11U] ^= CSBK_CRC_MASK[1U]; - } - else if(m_dataType == DT_MBC_HEADER) - { + break; + + case DT_MBC_HEADER: m_data[10U] ^= MBC_CRC_MASK[0U]; m_data[11U] ^= MBC_CRC_MASK[1U]; + break; + + default: + LogError("Unknown DMR CSBK data type: 0x%02X", m_dataType); + return false; } m_CSBKO = CSBKO(m_data[0U] & 0x3FU); @@ -173,6 +184,7 @@ bool CDMRCSBK::put(const unsigned char* bytes) m_dataContent = false; m_CBF = 0U; break; + case CSBKO::ACKU: m_GI = false; m_dstId = m_data[4U] << 16 | m_data[5U] << 8 | m_data[6U]; @@ -181,6 +193,7 @@ bool CDMRCSBK::put(const unsigned char* bytes) m_CBF = m_data[3U]; CUtils::dump(1U, "ACKU CSBK", m_data, 12U); break; + case CSBKO::MAINT: m_GI = false; m_dstId = m_data[4U] << 16 | m_data[5U] << 8 | m_data[6U]; @@ -215,8 +228,9 @@ bool CDMRCSBK::put(const unsigned char* bytes) void CDMRCSBK::get(unsigned char* bytes) const { assert(bytes != nullptr); - if(m_dataType == DT_CSBK) - { + + switch (m_dataType) { + case DT_CSBK: m_data[10U] ^= CSBK_CRC_MASK[0U]; m_data[11U] ^= CSBK_CRC_MASK[1U]; @@ -224,9 +238,9 @@ void CDMRCSBK::get(unsigned char* bytes) const m_data[10U] ^= CSBK_CRC_MASK[0U]; m_data[11U] ^= CSBK_CRC_MASK[1U]; - } - else if(m_dataType == DT_MBC_HEADER) - { + break; + + case DT_MBC_HEADER: m_data[10U] ^= MBC_CRC_MASK[0U]; m_data[11U] ^= MBC_CRC_MASK[1U]; @@ -234,10 +248,15 @@ void CDMRCSBK::get(unsigned char* bytes) const m_data[10U] ^= MBC_CRC_MASK[0U]; m_data[11U] ^= MBC_CRC_MASK[1U]; - } - else if(m_dataType == DT_MBC_CONTINUATION) - { + break; + + case DT_MBC_CONTINUATION: CCRC::addCCITT162(m_data, 12U); + break; + + default: + LogError("Unknown DMR CSBK data type: 0x%02X", m_dataType); + return; } CBPTC19696 bptc; @@ -311,10 +330,9 @@ void CDMRCSBK::setDataType(unsigned char dataType) m_dataType = dataType; } -unsigned char CDMRCSBK::getDataType() +unsigned char CDMRCSBK::getDataType() const { return m_dataType; } #endif - diff --git a/DMRCSBK.h b/DMRCSBK.h index 84124bd..0b45a42 100644 --- a/DMRCSBK.h +++ b/DMRCSBK.h @@ -25,24 +25,24 @@ #if defined(USE_DMR) enum class CSBKO : unsigned char { - NONE = 0x00, - UUVREQ = 0x04, - UUANSRSP = 0x05, - CTCSBK = 0x07, - CALL_ALERT = 0x1F, - CALL_ALERT_ACK = 0x20, - RADIO_CHECK = 0x24, - NACKRSP = 0x26, - CALL_EMERGENCY = 0x27, - BSDWNACT = 0x38, - PRECCSBK = 0x3D, - MAINT = 0x2A, - TV_GRANT = 0x31, - PV_GRANT = 0x30, - AHOY = 0x1C, - ACKU = 0x21, - P_CLEAR = 0x2E, - C_BCAST = 0x28 + NONE = 0x00U, + UUVREQ = 0x04U, + UUANSRSP = 0x05U, + CTCSBK = 0x07U, + CALL_ALERT = 0x1FU, + CALL_ALERT_ACK = 0x20U, + RADIO_CHECK = 0x24U, + NACKRSP = 0x26U, + CALL_EMERGENCY = 0x27U, + BSDWNACT = 0x38U, + PRECCSBK = 0x3DU, + MAINT = 0x2AU, + TV_GRANT = 0x31U, + PV_GRANT = 0x30U, + AHOY = 0x1CU, + ACKU = 0x21U, + P_CLEAR = 0x2EU, + C_BCAST = 0x28U }; class CDMRCSBK @@ -78,7 +78,8 @@ public: void setCBF(unsigned char cbf); void setDataType(unsigned char dataType); - unsigned char getDataType(); + + unsigned char getDataType() const; private: unsigned char* m_data; diff --git a/DMRControl.cpp b/DMRControl.cpp index f6ec77c..ef6b540 100644 --- a/DMRControl.cpp +++ b/DMRControl.cpp @@ -112,35 +112,26 @@ void CDMRControl::clock() TrunkingCommandParameters command; bool ret = m_network->read(data, command); if (ret) { - if(m_modem->getDMRTrunking() && command.trunkingParams) { - switch(command.commandType) { - case DMRCommand::ChannelEnableDisable: - { - if(command.slot == 1) { - if(!m_modem->getControlChannel()) - m_slot1.enable(command.channelEnable); - } - else { - m_slot2.enable(command.channelEnable); - } + if (m_modem->getDMRTrunkingEnabled() && command.trunkingParams) { + if (command.commandType == DMRCommand::ChannelEnableDisable) { + if (command.slot == 1U) { + if (!m_modem->getDMRControlChannel()) + m_slot1.enable(command.channelEnable); + } else { + m_slot2.enable(command.channelEnable); } - break; - default: - { - if(command.slot == 1) - m_slot1.setReverseChannelCommand(command.commandType); - else - m_slot2.setReverseChannelCommand(command.commandType); - } - break; + } else { + if (command.slot == 1U) + m_slot1.setRCCommand(command.commandType); + else + m_slot2.setRCCommand(command.commandType); } - } - else { + } else { unsigned int slotNo = data.getSlotNo(); switch (slotNo) { case 1U: m_slot1.writeNetwork(data); break; case 2U: m_slot2.writeNetwork(data); break; - default: LogError("Invalid slot no %u", slotNo); break; + default: LogError("Invalid slot no: %u", slotNo); break; } } } diff --git a/DMRData.h b/DMRData.h index e021b99..1ce7ca6 100644 --- a/DMRData.h +++ b/DMRData.h @@ -20,22 +20,22 @@ #if defined(USE_DMR) struct TrunkingCommandParameters { - unsigned int commandType = 0; - bool trunkingParams = false; - bool channelEnable = false; - unsigned int slot = 1; - bool ceaseTransmission = false; + unsigned int commandType = 0U; + bool trunkingParams = false; + bool channelEnable = false; + unsigned int slot = 1U; + bool ceaseTransmission = false; }; -enum DMRCommand { - ChannelEnableDisable = 1, - RCCeaseTransmission = 2, - RCRequestCeaseTransmission = 3, - RCPowerIncreaseOneStep = 4, - RCPowerDecreaseOneStep = 5, - RCMaximumPower = 6, - RCMinimumPower = 7, - RCNoCommand = 0, +namespace DMRCommand { + const unsigned int RCNoCommand = 0U; + const unsigned int ChannelEnableDisable = 1U; + const unsigned int RCCeaseTransmission = 2U; + const unsigned int RCRequestCeaseTransmission = 3U; + const unsigned int RCPowerIncreaseOneStep = 4U; + const unsigned int RCPowerDecreaseOneStep = 5U; + const unsigned int RCMaximumPower = 6U; + const unsigned int RCMinimumPower = 7U; }; class CDMRData { @@ -92,4 +92,3 @@ private: #endif #endif - diff --git a/DMRDataHeader.cpp b/DMRDataHeader.cpp index 1ee1b8b..dcd4e2e 100644 --- a/DMRDataHeader.cpp +++ b/DMRDataHeader.cpp @@ -123,7 +123,7 @@ bool CDMRDataHeader::put(const unsigned char* bytes) case DPF_UDT: CUtils::dump(1U, "DMR, Unified Data Transport Header", m_data, 12U); m_blocks = (m_data[8U] & 0x03U) + 1U; - m_UDT = true; + m_UDT = true; break; default: @@ -137,14 +137,14 @@ bool CDMRDataHeader::put(const unsigned char* bytes) void CDMRDataHeader::get(unsigned char* bytes) const { assert(bytes != nullptr); - assert(bytes != NULL); - if(m_UDT) - { - // Table B.1: CSBK/MBC/UDT Opcode List - // Convert to Unified Data Transport outbound Header - m_data[9U] &= 0xFE; - } + + // Table B.1: CSBK/MBC/UDT Opcode List + // Convert to Unified Data Transport outbound Header + if (m_UDT) + m_data[9U] &= 0xFEU; + CCRC::addCCITT162(m_data, 12U); + // Restore the checksum m_data[10U] ^= DATA_HEADER_CRC_MASK[0U]; m_data[11U] ^= DATA_HEADER_CRC_MASK[1U]; @@ -185,10 +185,10 @@ CDMRDataHeader& CDMRDataHeader::operator=(const CDMRDataHeader& header) m_F = header.m_F; m_S = header.m_S; m_Ns = header.m_Ns; + m_UDT = header.m_UDT; } return *this; } #endif - diff --git a/DMRDataHeader.h b/DMRDataHeader.h index 04ef0ba..dc6603a 100644 --- a/DMRDataHeader.h +++ b/DMRDataHeader.h @@ -58,4 +58,3 @@ private: #endif #endif - diff --git a/DMRNetwork.cpp b/DMRNetwork.cpp index f62c70c..45a1ece 100644 --- a/DMRNetwork.cpp +++ b/DMRNetwork.cpp @@ -123,36 +123,29 @@ void CDMRNetwork::enable(bool enabled) m_enabled = enabled; } -bool CDMRNetwork::read(CDMRData& data, TrunkingCommandParameters &command) +bool CDMRNetwork::read(CDMRData& data, TrunkingCommandParameters& command) { if (m_rxData.isEmpty()) return false; + command.trunkingParams = false; + unsigned char length = 0U; m_rxData.getData(&length, 1U); m_rxData.getData(m_buffer, length); - if (::memcmp(m_buffer, "DMRT", 4U) == 0) - { + + if (::memcmp(m_buffer, "DMRT", 4U) == 0) { unsigned int command_type = (unsigned int)m_buffer[4U]; - switch(command_type) - { - case DMRCommand::ChannelEnableDisable: - { - command.channelEnable = (m_buffer[5U] & 0x01U) == 0x01U ? true : false; - } - break; - default: - break; - } - command.slot = (m_buffer[5U] & 0x80U) == 0x80U ? 2U : 1U; - command.commandType = command_type; + + if (command_type == DMRCommand::ChannelEnableDisable) + command.channelEnable = ((m_buffer[5U] & 0x01U) == 0x01U); + + command.slot = (m_buffer[5U] & 0x80U) == 0x80U ? 2U : 1U; + command.commandType = command_type; command.trunkingParams = true; + return true; } - else - { - command.trunkingParams = false; - } // Is this a data packet? if (::memcmp(m_buffer, "DMRD", 4U) != 0) @@ -311,7 +304,7 @@ bool CDMRNetwork::isConnected() const return (m_addrLen != 0); } -void CDMRNetwork::close(bool sayGoodbye) +void CDMRNetwork::close() { LogMessage("DMR, Closing DMR Network"); @@ -469,4 +462,3 @@ bool CDMRNetwork::write(const unsigned char* data, unsigned int length) } #endif - diff --git a/DMRNetwork.h b/DMRNetwork.h index c9e76c0..10e6c25 100644 --- a/DMRNetwork.h +++ b/DMRNetwork.h @@ -43,7 +43,7 @@ public: void enable(bool enabled); - bool read(CDMRData& data, TrunkingCommandParameters &command); + bool read(CDMRData& data, TrunkingCommandParameters& command); bool write(const CDMRData& data); @@ -57,7 +57,7 @@ public: bool isConnected() const; - void close(bool sayGoodbye); + void close(); private: std::string m_addressStr; @@ -93,4 +93,3 @@ private: #endif #endif - diff --git a/DMRSlot.cpp b/DMRSlot.cpp index 45e2066..1bbbeb7 100644 --- a/DMRSlot.cpp +++ b/DMRSlot.cpp @@ -133,7 +133,7 @@ m_rssiCount(0U), m_bitErrsAccum(0U), m_bitsCount(0U), m_enabled(true), -m_reverseChannelCommand(0) +m_rcCommand(0U) { m_lastFrame = new unsigned char[DMR_FRAME_LENGTH_BYTES + 2U]; @@ -236,7 +236,7 @@ bool CDMRSlot::writeModem(unsigned char *data, unsigned int len) if (dataType == DT_VOICE_LC_HEADER) { if (m_rfState == RPT_RF_STATE::AUDIO) return true; - m_reverseChannelCommand = DMRCommand::RCNoCommand; + m_rcCommand = DMRCommand::RCNoCommand; CDMRFullLC fullLC; CDMRLC* lc = fullLC.decode(data + 2U, DT_VOICE_LC_HEADER); @@ -373,7 +373,7 @@ bool CDMRSlot::writeModem(unsigned char *data, unsigned int len) } else if (dataType == DT_TERMINATOR_WITH_LC) { if (m_rfState != RPT_RF_STATE::AUDIO) return false; - m_reverseChannelCommand = DMRCommand::RCNoCommand; + m_rcCommand = DMRCommand::RCNoCommand; // Regenerate the LC data CDMRFullLC fullLC; fullLC.encode(*m_rfLC, data + 2U, DT_TERMINATOR_WITH_LC); @@ -494,9 +494,8 @@ bool CDMRSlot::writeModem(unsigned char *data, unsigned int len) if (csbko == CSBKO::BSDWNACT) return false; - // set the OVCM bit for the supported csbk - if(!m_modem->getDMRTrunking()) - { + // Set the OVCM bit for the supported CSBK + if (!m_modem->getDMRTrunkingEnabled()) { if ((m_ovcm == DMR_OVCM::TX_ON) || (m_ovcm == DMR_OVCM::ON)) csbk.setOVCM(true); else if (m_ovcm == DMR_OVCM::FORCE_OFF) @@ -537,7 +536,7 @@ bool CDMRSlot::writeModem(unsigned char *data, unsigned int len) data[0U] = TAG_DATA; data[1U] = 0x00U; - if (m_duplex && !m_modem->getDMRTrunking()) + if (m_duplex && !m_modem->getDMRTrunkingEnabled()) writeQueueRF(data); writeNetworkRF(data, DT_CSBK, gi ? FLCO::GROUP : FLCO::USER_USER, srcId, dstId); @@ -1142,7 +1141,7 @@ void CDMRSlot::writeNetwork(const CDMRData& dmrData) if (!m_enabled) return; - if ((m_rfState != RPT_RF_STATE::LISTENING) && (m_netState == RPT_NET_STATE::IDLE) && !m_modem->getDMRTrunking()) + if ((m_rfState != RPT_RF_STATE::LISTENING) && (m_netState == RPT_NET_STATE::IDLE) && !m_modem->getDMRTrunkingEnabled()) return; m_networkWatchdog.start(); @@ -1698,9 +1697,8 @@ void CDMRSlot::writeNetwork(const CDMRData& dmrData) if (csbko == CSBKO::BSDWNACT) return; - // set the OVCM bit for the supported csbk - if(!m_modem->getDMRTrunking()) - { + // Set the OVCM bit for the supported CSBK + if (!m_modem->getDMRTrunkingEnabled()) { if ((m_ovcm == DMR_OVCM::RX_ON) || (m_ovcm == DMR_OVCM::ON)) csbk.setOVCM(true); else if (m_ovcm == DMR_OVCM::FORCE_OFF) @@ -1803,8 +1801,7 @@ void CDMRSlot::writeNetwork(const CDMRData& dmrData) return; // set the OVCM bit for the supported csbk - if(!m_modem->getDMRTrunking()) - { + if (!m_modem->getDMRTrunkingEnabled()) { if ((m_ovcm == DMR_OVCM::RX_ON) || (m_ovcm == DMR_OVCM::ON)) csbk.setOVCM(true); else if (m_ovcm == DMR_OVCM::FORCE_OFF) @@ -1894,7 +1891,8 @@ void CDMRSlot::writeNetwork(const CDMRData& dmrData) // If data preamble, signal its existence if ((csbko == CSBKO::PRECCSBK) && csbk.getDataContent()) setShortLC(m_slotNo, dstId, gi ? FLCO::GROUP : FLCO::USER_USER, ACTIVITY_TYPE::DATA); - } else if (dataType == DT_MBC_CONTINUATION) { + + } else if (dataType == DT_MBC_CONTINUATION) { CDMRCSBK csbk; csbk.setDataType(DT_MBC_CONTINUATION); bool valid = csbk.put(data + 2U); @@ -1907,9 +1905,8 @@ void CDMRSlot::writeNetwork(const CDMRData& dmrData) if (csbko == CSBKO::BSDWNACT) return; - // set the OVCM bit for the supported csbk - if(!m_modem->getDMRTrunking()) - { + // Set the OVCM bit for the supported CSBK + if (!m_modem->getDMRTrunkingEnabled()) { if ((m_ovcm == DMR_OVCM::RX_ON) || (m_ovcm == DMR_OVCM::ON)) csbk.setOVCM(true); else if (m_ovcm == DMR_OVCM::FORCE_OFF) @@ -2521,13 +2518,14 @@ void CDMRSlot::enable(bool enabled) m_enabled = enabled; } -void CDMRSlot::setReverseChannelCommand(unsigned int rc_command) { - m_reverseChannelCommand = rc_command; +void CDMRSlot::setRCCommand(unsigned int command) +{ + m_rcCommand = command; } void CDMRSlot::createReverseChannel(unsigned char *data, CDMREMB &emb) { if(m_rfN == 5U) { - if (m_reverseChannelCommand == DMRCommand::RCCeaseTransmission) { + if (m_rcCommand == DMRCommand::RCCeaseTransmission) { data[16U] = (data[16U] & 0xF0U) | (RC_CEASE_TRANSMIT[0U] & 0x0FU); data[17U] = RC_CEASE_TRANSMIT[1U]; data[18U] = RC_CEASE_TRANSMIT[2U]; @@ -2538,7 +2536,7 @@ void CDMRSlot::createReverseChannel(unsigned char *data, CDMREMB &emb) { emb.setPI(true); emb.getData(data + 2U); } - else if (m_reverseChannelCommand == DMRCommand::RCRequestCeaseTransmission) { + else if (m_rcCommand == DMRCommand::RCRequestCeaseTransmission) { data[16U] = (data[16U] & 0xF0U) | (RC_REQUEST_CEASE_TRANSMIT[0U] & 0x0FU); data[17U] = RC_REQUEST_CEASE_TRANSMIT[1U]; data[18U] = RC_REQUEST_CEASE_TRANSMIT[2U]; @@ -2549,7 +2547,7 @@ void CDMRSlot::createReverseChannel(unsigned char *data, CDMREMB &emb) { emb.setPI(true); emb.getData(data + 2U); } - else if (m_reverseChannelCommand == DMRCommand::RCMaximumPower) { + else if (m_rcCommand == DMRCommand::RCMaximumPower) { data[16U] = (data[16U] & 0xF0U) | (RC_MAX_POWER[0U] & 0x0FU); data[17U] = RC_MAX_POWER[1U]; data[18U] = RC_MAX_POWER[2U]; @@ -2560,7 +2558,7 @@ void CDMRSlot::createReverseChannel(unsigned char *data, CDMREMB &emb) { emb.setPI(true); emb.getData(data + 2U); } - else if (m_reverseChannelCommand == DMRCommand::RCMinimumPower) { + else if (m_rcCommand == DMRCommand::RCMinimumPower) { data[16U] = (data[16U] & 0xF0U) | (RC_MIN_POWER[0U] & 0x0FU); data[17U] = RC_MIN_POWER[1U]; data[18U] = RC_MIN_POWER[2U]; @@ -2571,7 +2569,7 @@ void CDMRSlot::createReverseChannel(unsigned char *data, CDMREMB &emb) { emb.setPI(true); emb.getData(data + 2U); } - else if (m_reverseChannelCommand == DMRCommand::RCPowerIncreaseOneStep) { + else if (m_rcCommand == DMRCommand::RCPowerIncreaseOneStep) { data[16U] = (data[16U] & 0xF0U) | (RC_POWER_INCREASE[0U] & 0x0FU); data[17U] = RC_POWER_INCREASE[1U]; data[18U] = RC_POWER_INCREASE[2U]; @@ -2582,7 +2580,7 @@ void CDMRSlot::createReverseChannel(unsigned char *data, CDMREMB &emb) { emb.setPI(true); emb.getData(data + 2U); } - else if (m_reverseChannelCommand == DMRCommand::RCPowerDecreaseOneStep) { + else if (m_rcCommand == DMRCommand::RCPowerDecreaseOneStep) { data[16U] = (data[16U] & 0xF0U) | (RC_POWER_DECREASE[0U] & 0x0FU); data[17U] = RC_POWER_DECREASE[1U]; data[18U] = RC_POWER_DECREASE[2U]; @@ -2824,4 +2822,3 @@ void CDMRSlot::writeJSON(nlohmann::json& json, const char* source, const char* a } #endif - diff --git a/DMRSlot.h b/DMRSlot.h index 0fdd2cb..dc338eb 100644 --- a/DMRSlot.h +++ b/DMRSlot.h @@ -68,7 +68,7 @@ public: static void init(unsigned int colorCode, bool embeddedLCOnly, bool dumpTAData, unsigned int callHang, CModem* modem, CDMRNetwork* network, bool duplex, CDMRLookup* lookup, CRSSIInterpolator* rssiMapper, unsigned int jitter, DMR_OVCM ovcm, bool protect); - void setReverseChannelCommand(unsigned int rc_command); + void setRCCommand(unsigned int command); private: unsigned int m_slotNo; @@ -148,7 +148,7 @@ private: static FLCO m_flco2; static unsigned char m_id2; static ACTIVITY_TYPE m_activity2; - unsigned int m_reverseChannelCommand; + unsigned int m_rcCommand; void logGPSPosition(const unsigned char* data); @@ -163,7 +163,7 @@ private: bool insertSilence(const unsigned char* data, unsigned char seqNo); void insertSilence(unsigned int count); - void createReverseChannel(unsigned char *data, CDMREMB &emb); + void createReverseChannel(unsigned char* data, CDMREMB& emb); static void setShortLC(unsigned int slotNo, unsigned int id, FLCO flco = FLCO::GROUP, ACTIVITY_TYPE type = ACTIVITY_TYPE::NONE); @@ -191,4 +191,3 @@ private: #endif #endif - diff --git a/MMDVMHost.cpp b/MMDVMHost.cpp index 103c487..da27298 100644 --- a/MMDVMHost.cpp +++ b/MMDVMHost.cpp @@ -627,6 +627,12 @@ int CMMDVMHost::run() DMR_OVCM ovcm = m_conf.getDMROVCM(); bool protect = m_conf.getDMRProtect(); + bool trunking = m_conf.getDMRTrunking(); + bool controlChannel = m_conf.getDMRControlChannel(); + unsigned int systemCode = m_conf.getDMRSystemCode(); + bool registrationRequired = m_conf.getDMRRegistrationRequired(); + bool alternateSlot = m_conf.getDMRControlChannelAlternateSlot(); + if (txHang > m_dmrRFModeHang) txHang = m_dmrRFModeHang; @@ -672,6 +678,16 @@ int CMMDVMHost::run() if (protect) LogInfo(" Protect: yes"); + if (trunking) { + LogInfo(" Trunking: yes"); + LogInfo(" Control Channel: %s", controlChannel ? "yes" : "no"); + LogInfo(" System Code: %u", systemCode); + LogInfo(" Registration Required: %s", registrationRequired ? "yes" : "no"); + LogInfo(" Alternate Slot: %s", alternateSlot ? "yes" : "no"); + } else { + LogInfo(" Trunking: no"); + } + switch (dmrBeacons) { case DMR_BEACONS::NETWORK: { unsigned int dmrBeaconDuration = m_conf.getDMRBeaconDuration(); @@ -706,16 +722,15 @@ int CMMDVMHost::run() m_dmrTXTimer.setTimeout(txHang); // Tier III options - if(m_conf.getDMRTrunking()) - { + if (trunking) { setMode(MODE_DMR); - m_modem->setShortLC(m_conf.getDMRSystemCode(), m_conf.getDMRControlChannel(), m_conf.getDMRRegistrationRequired()); - if(m_conf.getDMRControlChannel()) - { - m_modem->writeDMRAloha(m_conf.getDMRSystemCode(), m_conf.getDMRRegistrationRequired(), m_conf.getDMRControlChannelAlternateSlot()); - } - } + m_modem->setDMRTrunkingParams(controlChannel); + m_modem->setDMRShortLC(systemCode, controlChannel, registrationRequired); + + if (controlChannel) + m_modem->writeDMRAloha(systemCode, registrationRequired, alternateSlot); + } } #endif @@ -1414,7 +1429,7 @@ int CMMDVMHost::run() #if defined(USE_DMR) if (m_dmrNetwork != nullptr) { - m_dmrNetwork->close(true); + m_dmrNetwork->close(); delete m_dmrNetwork; } #endif @@ -1588,8 +1603,6 @@ bool CMMDVMHost::createModem() int txDCOffset = m_conf.getModemTXDCOffset(); float rfLevel = m_conf.getModemRFLevel(); bool useCOSAsLockout = m_conf.getModemUseCOSAsLockout(); - bool trunking = m_conf.getDMRTrunking(); - bool controlChannel = m_conf.getDMRControlChannel(); LogInfo("Modem Parameters"); LogInfo(" Protocol: %s", protocol.c_str()); @@ -1649,7 +1662,7 @@ bool CMMDVMHost::createModem() LogInfo(" RX Frequency: %uHz (%uHz)", rxFrequency, rxFrequency + rxOffset); LogInfo(" Use COS as Lockout: %s", useCOSAsLockout ? "yes" : "no"); - m_modem = new CModem(m_duplex, rxInvert, txInvert, pttInvert, txDelay, dmrDelay, useCOSAsLockout, trace, debug, trunking, controlChannel); + m_modem = new CModem(m_duplex, rxInvert, txInvert, pttInvert, txDelay, dmrDelay, useCOSAsLockout, trace, debug); IModemPort* port = nullptr; if (protocol == "uart") diff --git a/Modem.cpp b/Modem.cpp index a15fa91..c5b2128 100644 --- a/Modem.cpp +++ b/Modem.cpp @@ -128,7 +128,7 @@ const unsigned char CAP1_NXDN = 0x10U; const unsigned char CAP1_FM = 0x40U; const unsigned char CAP2_POCSAG = 0x01U; -CModem::CModem(bool duplex, bool rxInvert, bool txInvert, bool pttInvert, unsigned int txDelay, unsigned int dmrDelay, bool useCOSAsLockout, bool trace, bool debug, bool trunking, bool controlChannel) : +CModem::CModem(bool duplex, bool rxInvert, bool txInvert, bool pttInvert, unsigned int txDelay, unsigned int dmrDelay, bool useCOSAsLockout, bool trace, bool debug) : m_protocolVersion(0U), #if defined(USE_DMR) m_dmrColorCode(0U), @@ -188,6 +188,8 @@ m_dstarEnabled(false), #endif #if defined(USE_DMR) m_dmrEnabled(false), +m_dmrTrunkingEnabled(false), +m_dmrControlChannel(false), #endif #if defined(USE_YSF) m_ysfEnabled(false), @@ -316,8 +318,6 @@ m_fmExtEnable(false), #endif m_capabilities1(0x00U), m_capabilities2(0x00U), -m_trunking(trunking), -m_controlChannel(controlChannel), m_serialDataLen(0U) { m_buffer = new unsigned char[BUFFER_LENGTH]; @@ -407,6 +407,12 @@ void CModem::setDMRParams(unsigned int colorCode) m_dmrColorCode = colorCode; } + +void CModem::setDMRTrunkingParams(bool controlChannel) +{ + m_dmrTrunkingEnabled = true; + m_dmrControlChannel = controlChannel; +} #endif #if defined(USE_YSF) @@ -2414,12 +2420,13 @@ bool CModem::setConfig2() buffer[26U] = 0U; buffer[27U] = 0x00U; - buffer[28U] = (m_trunking ? 1 : 0) << 7; #if defined(USE_DMR) + buffer[28U] = m_dmrTrunkingEnabled ? 0x80U : 0x00U; buffer[29U] = m_dmrColorCode; buffer[30U] = m_dmrDelay; #else + buffer[28U] = 0U; buffer[29U] = 0U; buffer[30U] = 0U; #endif @@ -2793,7 +2800,7 @@ bool CModem::writeDMRAloha(unsigned int systemCode, bool registrationRequired, b return m_port->write(buffer, DMR_FRAME_LENGTH_BYTES + 3U) == 12; } -void CModem::setShortLC(unsigned int systemCode, bool isControlChannel, bool registrationRequired) +void CModem::setDMRShortLC(unsigned int systemCode, bool isControlChannel, bool registrationRequired) { // Used for setting CACH (C_SysParm and P_SysParm) on TSCC and Payload Ch unsigned char lc[5U]; @@ -2813,8 +2820,9 @@ void CModem::setShortLC(unsigned int systemCode, bool isControlChannel, bool reg bool CModem::writeDMRShortLC(const unsigned char* lc, bool control) { - if(!control && m_trunking) + if (!control && m_dmrTrunkingEnabled) return true; + assert(m_port != nullptr); assert(lc != nullptr); @@ -3158,13 +3166,14 @@ void CModem::printDebug() } } -bool CModem::getDMRTrunking() const +#if defined(USE_DMR) +bool CModem::getDMRTrunkingEnabled() const { - return m_trunking; + return m_dmrTrunkingEnabled; } -bool CModem::getControlChannel() const +bool CModem::getDMRControlChannel() const { - return m_controlChannel; + return m_dmrControlChannel; } - +#endif diff --git a/Modem.h b/Modem.h index 8f55c5e..ccedaca 100644 --- a/Modem.h +++ b/Modem.h @@ -45,7 +45,7 @@ enum class SERIAL_STATE { class CModem { public: - CModem(bool duplex, bool rxInvert, bool txInvert, bool pttInvert, unsigned int txDelay, unsigned int dmrDelay, bool useCOSAsLockout, bool trace, bool debug, bool trunking, bool controlChannel); + CModem(bool duplex, bool rxInvert, bool txInvert, bool pttInvert, unsigned int txDelay, unsigned int dmrDelay, bool useCOSAsLockout, bool trace, bool debug); ~CModem(); void setPort(IModemPort* port); @@ -55,6 +55,7 @@ public: #if defined(USE_DMR) void setDMRParams(unsigned int colorCode); + void setDMRTrunkingParams(bool controlChannel); #endif #if defined(USE_YSF) void setYSFParams(bool loDev, unsigned int txHang); @@ -183,7 +184,7 @@ public: #if defined(USE_DMR) bool writeDMRStart(bool tx); bool writeDMRAloha(unsigned int systemCode, bool registrationRequired, bool alternateSlot); - void setShortLC(unsigned int systemCode, bool isControlChannel, bool registrationRequired); + void setDMRShortLC(unsigned int systemCode, bool isControlChannel, bool registrationRequired); bool writeDMRShortLC(const unsigned char* lc, bool control); bool writeDMRAbort(unsigned int slotNo); #endif @@ -203,8 +204,11 @@ public: void clock(unsigned int ms); void close(); - bool getDMRTrunking() const; - bool getControlChannel() const; + +#if defined(USE_DMR) + bool getDMRTrunkingEnabled() const; + bool getDMRControlChannel() const; +#endif private: unsigned int m_protocolVersion; @@ -266,6 +270,8 @@ private: #endif #if defined(USE_DMR) bool m_dmrEnabled; + bool m_dmrTrunkingEnabled; + bool m_dmrControlChannel; #endif #if defined(USE_YSF) bool m_ysfEnabled; @@ -395,8 +401,6 @@ private: #endif unsigned char m_capabilities1; unsigned char m_capabilities2; - bool m_trunking; - bool m_controlChannel; bool readVersion(); bool readStatus(); From 3f966d3c4f09a5cebbfc6e0b20f51063f2b5147d Mon Sep 17 00:00:00 2001 From: Jonathan Naylor Date: Mon, 27 Apr 2026 12:44:22 +0100 Subject: [PATCH 4/7] Configiation file changes for DMR trunking. --- Conf.cpp | 38 +++++++++++++++++++++----------------- Conf.h | 12 ++++++------ MMDVMHost.cpp | 24 ++++++++++-------------- MMDVMHost.ini | 8 ++++++++ Version.h | 2 +- 5 files changed, 46 insertions(+), 38 deletions(-) diff --git a/Conf.cpp b/Conf.cpp index 2b2cc1d..7bc69a1 100644 --- a/Conf.cpp +++ b/Conf.cpp @@ -47,6 +47,7 @@ enum class SECTION { #endif #if defined(USE_DMR) DMR, + DMR_TRUNKING, #endif #if defined(USE_YSF) FUSION, @@ -181,11 +182,6 @@ m_dmrEnabled(false), m_dmrBeacons(DMR_BEACONS::OFF), m_dmrBeaconInterval(60U), m_dmrBeaconDuration(3U), -m_dmrTrunking(false), -m_dmrControlChannel(false), -m_dmrControlChannelAlternateSlot(false), -m_dmrSystemCode(0U), -m_dmrRegistrationRequired(true), #endif m_dmrId(0U), #if defined(USE_DMR) @@ -205,6 +201,11 @@ m_dmrModeHang(10U), #if defined(USE_DMR) m_dmrOVCM(DMR_OVCM::OFF), m_dmrProtect(false), +m_dmrTrunkingEnabled(false), +m_dmrControlChannel(false), +m_dmrControlChannelAlternateSlot(false), +m_dmrSystemCode(0U), +m_dmrRegistrationRequired(true), #endif #if defined(USE_YSF) m_fusionEnabled(false), @@ -417,6 +418,8 @@ bool CConf::read() #if defined(USE_DMR) else if (::strncmp(buffer, "[DMR]", 5U) == 0) section = SECTION::DMR; + else if (::strncmp(buffer, "[DMR Trunking]", 14U) == 0) + section = SECTION::DMR_TRUNKING; #endif #if defined(USE_YSF) else if (::strncmp(buffer, "[System Fusion]", 15U) == 0) @@ -734,16 +737,6 @@ bool CConf::read() } else if (section == SECTION::DMR) { if (::strcmp(key, "Enable") == 0) m_dmrEnabled = ::atoi(value) == 1; - if (::strcmp(key, "Trunking") == 0) - m_dmrTrunking = ::atoi(value) == 1; - if (::strcmp(key, "ControlChannel") == 0) - m_dmrControlChannel = ::atoi(value) == 1; - if (::strcmp(key, "ControlChannelAlternateSlot") == 0) - m_dmrControlChannelAlternateSlot = ::atoi(value) == 1; - if (::strcmp(key, "SystemCode") == 0) - m_dmrSystemCode = (unsigned int)::atoi(value); - if (::strcmp(key, "RegistrationRequired") == 0) - m_dmrRegistrationRequired = ::atoi(value) == 1; else if (::strcmp(key, "Beacons") == 0) m_dmrBeacons = ::atoi(value) == 1 ? DMR_BEACONS::NETWORK : DMR_BEACONS::OFF; else if (::strcmp(key, "BeaconInterval") == 0) { @@ -827,6 +820,17 @@ bool CConf::read() } } else if (::strcmp(key, "Protect") == 0) m_dmrProtect = ::atoi(value) == 1; + } else if (section == SECTION::DMR_TRUNKING) { + if (::strcmp(key, "Enable") == 0) + m_dmrTrunkingEnabled = ::atoi(value) == 1; + else if (::strcmp(key, "ControlChannel") == 0) + m_dmrControlChannel = ::atoi(value) == 1; + else if (::strcmp(key, "ControlChannelAlternateSlot") == 0) + m_dmrControlChannelAlternateSlot = ::atoi(value) == 1; + else if (::strcmp(key, "SystemCode") == 0) + m_dmrSystemCode = (unsigned int)::atoi(value); + else if (::strcmp(key, "RegistrationRequired") == 0) + m_dmrRegistrationRequired = ::atoi(value) == 1; #endif #if defined(USE_YSF) } else if (section == SECTION::FUSION) { @@ -1543,9 +1547,9 @@ bool CConf::getDMREnabled() const return m_dmrEnabled; } -bool CConf::getDMRTrunking() const +bool CConf::getDMRTrunkingEnabled() const { - return m_dmrTrunking; + return m_dmrTrunkingEnabled; } bool CConf::getDMRControlChannel() const diff --git a/Conf.h b/Conf.h index e125941..eda361d 100644 --- a/Conf.h +++ b/Conf.h @@ -173,7 +173,7 @@ public: unsigned int getDMRModeHang() const; DMR_OVCM getDMROVCM() const; bool getDMRProtect() const; - bool getDMRTrunking() const; + bool getDMRTrunkingEnabled() const; bool getDMRControlChannel() const; bool getDMRControlChannelAlternateSlot() const; unsigned int getDMRSystemCode() const; @@ -454,11 +454,6 @@ private: DMR_BEACONS m_dmrBeacons; unsigned int m_dmrBeaconInterval; unsigned int m_dmrBeaconDuration; - bool m_dmrTrunking; - bool m_dmrControlChannel; - bool m_dmrControlChannelAlternateSlot; - unsigned int m_dmrSystemCode; - bool m_dmrRegistrationRequired; #endif unsigned int m_dmrId; #if defined(USE_DMR) @@ -478,6 +473,11 @@ private: #if defined(USE_DMR) DMR_OVCM m_dmrOVCM; bool m_dmrProtect; + bool m_dmrTrunkingEnabled; + bool m_dmrControlChannel; + bool m_dmrControlChannelAlternateSlot; + unsigned int m_dmrSystemCode; + bool m_dmrRegistrationRequired; #endif #if defined(USE_YSF) diff --git a/MMDVMHost.cpp b/MMDVMHost.cpp index da27298..e2bbb35 100644 --- a/MMDVMHost.cpp +++ b/MMDVMHost.cpp @@ -627,7 +627,7 @@ int CMMDVMHost::run() DMR_OVCM ovcm = m_conf.getDMROVCM(); bool protect = m_conf.getDMRProtect(); - bool trunking = m_conf.getDMRTrunking(); + bool trunking = m_conf.getDMRTrunkingEnabled(); bool controlChannel = m_conf.getDMRControlChannel(); unsigned int systemCode = m_conf.getDMRSystemCode(); bool registrationRequired = m_conf.getDMRRegistrationRequired(); @@ -839,7 +839,8 @@ int CMMDVMHost::run() bool remoteControlEnabled = m_conf.getRemoteControlEnabled(); if (remoteControlEnabled) m_remoteControl = new CRemoteControl(this, m_mqtt); - if(!m_conf.getDMRTrunking()) + + if (!m_conf.getDMRTrunkingEnabled()) setMode(MODE_IDLE); while (!m_killed) { @@ -922,7 +923,7 @@ int CMMDVMHost::run() if (m_mode == MODE_IDLE) { if (m_duplex) { bool ret = m_dmr->processWakeup(data); - if (ret || m_conf.getDMRTrunking()) { + if (ret || m_conf.getDMRTrunkingEnabled()) { m_modeTimer.setTimeout(m_dmrRFModeHang); setMode(MODE_DMR); dmrBeaconDurationTimer.stop(); @@ -936,7 +937,7 @@ int CMMDVMHost::run() } else if (m_mode == MODE_DMR) { if (m_duplex && !m_modem->hasTX()) { bool ret = m_dmr->processWakeup(data); - if (ret || m_conf.getDMRTrunking()) { + if (ret || m_conf.getDMRTrunkingEnabled()) { m_modem->writeDMRStart(true); m_dmrTXTimer.start(); } @@ -1035,7 +1036,7 @@ int CMMDVMHost::run() if (transparentSocket != nullptr && len > 0U) transparentSocket->write(data, len, transparentAddress, transparentAddrLen); - if (!m_fixedMode && !m_conf.getDMRTrunking()) { + if (!m_fixedMode && !m_conf.getDMRTrunkingEnabled()) { if (m_modeTimer.isRunning() && m_modeTimer.hasExpired()) setMode(MODE_IDLE); } @@ -1368,22 +1369,17 @@ int CMMDVMHost::run() dmrBeaconDurationTimer.clock(ms); if (dmrBeaconDurationTimer.isRunning() && dmrBeaconDurationTimer.hasExpired()) { - if (!m_fixedMode && !m_conf.getDMRTrunking()) + if (!m_fixedMode && !m_conf.getDMRTrunkingEnabled()) setMode(MODE_IDLE); dmrBeaconDurationTimer.stop(); } m_dmrTXTimer.clock(ms); if (m_dmrTXTimer.isRunning() && m_dmrTXTimer.hasExpired()) { - if(m_conf.getDMRTrunking()) - { - if(!m_conf.getDMRControlChannel()) - { + if (m_conf.getDMRTrunkingEnabled()) { + if (!m_conf.getDMRControlChannel()) m_modem->writeDMRStart(false); - } - } - else - { + } else { m_modem->writeDMRStart(false); } m_dmrTXTimer.stop(); diff --git a/MMDVMHost.ini b/MMDVMHost.ini index d879a1c..3e34a36 100644 --- a/MMDVMHost.ini +++ b/MMDVMHost.ini @@ -125,6 +125,14 @@ TXHang=4 # ModeHang=10 # OVCM Values, 0=off, 1=rx_on, 2=tx_on, 3=both_on, 4=force off # OVCM=0 +Protect=0 + +[DMR Trunking] +Enable=0 +ControlChannel=0 +ControlChannelAlternateSlot=0 +SystemCode=0 +RegistrationRequired=0 [System Fusion] Enable=0 diff --git a/Version.h b/Version.h index 78a8161..b39055c 100644 --- a/Version.h +++ b/Version.h @@ -19,6 +19,6 @@ #if !defined(VERSION_H) #define VERSION_H -const char* VERSION = "20260417"; +const char* VERSION = "20260427"; #endif From 04e31b7b64e060a98872085e01863f42ce196b21 Mon Sep 17 00:00:00 2001 From: adrian Date: Fri, 1 May 2026 15:35:31 +0300 Subject: [PATCH 5/7] Fix modem initialization sequence for trunking and MBC_CONTINUATION parsing --- DMRCSBK.cpp | 6 ++++++ MMDVMHost.cpp | 9 +++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/DMRCSBK.cpp b/DMRCSBK.cpp index 4c8505a..ba311db 100644 --- a/DMRCSBK.cpp +++ b/DMRCSBK.cpp @@ -75,6 +75,9 @@ bool CDMRCSBK::put(const unsigned char* bytes) m_data[11U] ^= MBC_CRC_MASK[1U]; break; + case DT_MBC_CONTINUATION: + break; + default: LogError("Unknown DMR CSBK data type: 0x%02X", m_dataType); return false; @@ -96,6 +99,9 @@ bool CDMRCSBK::put(const unsigned char* bytes) m_data[11U] ^= MBC_CRC_MASK[1U]; break; + case DT_MBC_CONTINUATION: + break; + default: LogError("Unknown DMR CSBK data type: 0x%02X", m_dataType); return false; diff --git a/MMDVMHost.cpp b/MMDVMHost.cpp index e2bbb35..5161c15 100644 --- a/MMDVMHost.cpp +++ b/MMDVMHost.cpp @@ -723,11 +723,8 @@ int CMMDVMHost::run() // Tier III options if (trunking) { - setMode(MODE_DMR); - - m_modem->setDMRTrunkingParams(controlChannel); m_modem->setDMRShortLC(systemCode, controlChannel, registrationRequired); - + setMode(MODE_DMR); if (controlChannel) m_modem->writeDMRAloha(systemCode, registrationRequired, alternateSlot); } @@ -1575,6 +1572,8 @@ bool CMMDVMHost::createModem() bool debug = m_conf.getModemDebug(); #if defined(USE_DMR) unsigned int colorCode = m_conf.getDMRColorCode(); + bool trunking = m_conf.getDMRTrunkingEnabled(); + bool controlChannel = m_conf.getDMRControlChannel(); #endif #if defined(USE_YSF) bool lowDeviation = m_conf.getFusionLowDeviation(); @@ -1680,6 +1679,8 @@ bool CMMDVMHost::createModem() m_modem->setRFParams(rxFrequency, rxOffset, txFrequency, txOffset, txDCOffset, rxDCOffset, rfLevel, pocsagFrequency); #if defined(USE_DMR) m_modem->setDMRParams(colorCode); + if(trunking) + m_modem->setDMRTrunkingParams(controlChannel); #endif #if defined(USE_YSF) m_modem->setYSFParams(lowDeviation, ysfTXHang); From a32493b3e424b4cbc3eaa19f7323ac0b42e37f81 Mon Sep 17 00:00:00 2001 From: Jonathan Naylor Date: Wed, 6 May 2026 16:17:40 +0100 Subject: [PATCH 6/7] Small cleanups. --- MMDVMHost.cpp | 4 +++- Version.h | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/MMDVMHost.cpp b/MMDVMHost.cpp index 5161c15..3f47cac 100644 --- a/MMDVMHost.cpp +++ b/MMDVMHost.cpp @@ -724,7 +724,9 @@ int CMMDVMHost::run() // Tier III options if (trunking) { m_modem->setDMRShortLC(systemCode, controlChannel, registrationRequired); + setMode(MODE_DMR); + if (controlChannel) m_modem->writeDMRAloha(systemCode, registrationRequired, alternateSlot); } @@ -1679,7 +1681,7 @@ bool CMMDVMHost::createModem() m_modem->setRFParams(rxFrequency, rxOffset, txFrequency, txOffset, txDCOffset, rxDCOffset, rfLevel, pocsagFrequency); #if defined(USE_DMR) m_modem->setDMRParams(colorCode); - if(trunking) + if (trunking) m_modem->setDMRTrunkingParams(controlChannel); #endif #if defined(USE_YSF) diff --git a/Version.h b/Version.h index b39055c..1284cfd 100644 --- a/Version.h +++ b/Version.h @@ -19,6 +19,6 @@ #if !defined(VERSION_H) #define VERSION_H -const char* VERSION = "20260427"; +const char* VERSION = "20260506"; #endif From 9226ed5e5a788483e8191df7ad1728aa5f46c826 Mon Sep 17 00:00:00 2001 From: adrian Date: Wed, 1 Jul 2026 20:51:30 +0300 Subject: [PATCH 7/7] Fix inadvertently off by 1 second call hang in non-trunking mode --- DMRSlot.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/DMRSlot.cpp b/DMRSlot.cpp index 1bbbeb7..dc709a0 100644 --- a/DMRSlot.cpp +++ b/DMRSlot.cpp @@ -391,7 +391,7 @@ bool CDMRSlot::writeModem(unsigned char *data, unsigned int len) writeNetworkRF(data, DT_TERMINATOR_WITH_LC); if (m_duplex) { - for (unsigned int i = 0U; i <= m_hangCount; i++) + for (unsigned int i = 0U; i < m_hangCount; i++) writeQueueRF(data); } } @@ -1063,7 +1063,7 @@ void CDMRSlot::writeEndRF(bool writeEnd) data[0U] = TAG_EOT; data[1U] = 0x00U; - for (unsigned int i = 0U; i <= m_hangCount; i++) + for (unsigned int i = 0U; i < m_hangCount; i++) writeQueueRF(data); } } @@ -1111,7 +1111,7 @@ void CDMRSlot::writeEndNet(bool writeEnd) data[1U] = 0x00U; if (m_duplex) { - for (unsigned int i = 0U; i <= m_hangCount; i++) + for (unsigned int i = 0U; i < m_hangCount; i++) writeQueueNet(data); } else { for (unsigned int i = 0U; i < 3U; i++) @@ -1347,7 +1347,7 @@ void CDMRSlot::writeNetwork(const CDMRData& dmrData) data[1U] = 0x00U; if (m_duplex) { - for (unsigned int i = 0U; i <= m_hangCount; i++) + for (unsigned int i = 0U; i < m_hangCount; i++) writeQueueNet(data); } else { for (unsigned int i = 0U; i < 3U; i++) @@ -2247,7 +2247,7 @@ void CDMRSlot::init(unsigned int colorCode, bool embeddedLCOnly, bool dumpTAData m_network = network; m_duplex = duplex; m_lookup = lookup; - m_hangCount = callHang * 17U; + m_hangCount = (!m_modem->getDMRTrunkingEnabled()) ? callHang * 17U : callHang; m_ovcm = ovcm; m_protect = protect;