From 4ff3bbf7660df0e49b7ccf46b490c0e21a72bb3e Mon Sep 17 00:00:00 2001 From: adrian Date: Sat, 18 Apr 2026 11:58:50 +0300 Subject: [PATCH 1/2] 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/2] 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