diff --git a/Conf.cpp b/Conf.cpp index 234d2f1..c7cec10 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, @@ -200,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), @@ -412,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) @@ -812,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) { @@ -1528,6 +1547,31 @@ bool CConf::getDMREnabled() const return m_dmrEnabled; } +bool CConf::getDMRTrunkingEnabled() const +{ + return m_dmrTrunkingEnabled; +} + +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..eda361d 100644 --- a/Conf.h +++ b/Conf.h @@ -173,6 +173,11 @@ public: unsigned int getDMRModeHang() const; DMR_OVCM getDMROVCM() const; bool getDMRProtect() const; + bool getDMRTrunkingEnabled() const; + bool getDMRControlChannel() const; + bool getDMRControlChannelAlternateSlot() const; + unsigned int getDMRSystemCode() const; + bool getDMRRegistrationRequired() const; #endif #if defined(USE_YSF) @@ -468,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/DMRCSBK.cpp b/DMRCSBK.cpp index 451c01e..ba311db 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 @@ -21,6 +22,7 @@ #include "BPTC19696.h" #include "Utils.h" #include "CRC.h" +#include "Log.h" #if defined(USE_DMR) @@ -37,7 +39,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,6 +50,13 @@ CDMRCSBK::~CDMRCSBK() delete[] m_data; } +void CDMRCSBK::setCSBKData(unsigned char* bytes) +{ + assert(bytes != nullptr); + + ::memcpy(m_data, bytes, 10U); +} + bool CDMRCSBK::put(const unsigned char* bytes) { assert(bytes != nullptr); @@ -54,16 +64,48 @@ bool CDMRCSBK::put(const unsigned char* bytes) CBPTC19696 bptc; bptc.decode(bytes, m_data); - m_data[10U] ^= CSBK_CRC_MASK[0U]; - m_data[11U] ^= CSBK_CRC_MASK[1U]; + switch (m_dataType) { + case DT_CSBK: + m_data[10U] ^= CSBK_CRC_MASK[0U]; + m_data[11U] ^= CSBK_CRC_MASK[1U]; + break; + + case DT_MBC_HEADER: + m_data[10U] ^= MBC_CRC_MASK[0U]; + 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; + } 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]; + switch (m_dataType) { + case DT_CSBK: + m_data[10U] ^= CSBK_CRC_MASK[0U]; + m_data[11U] ^= CSBK_CRC_MASK[1U]; + break; + + case DT_MBC_HEADER: + m_data[10U] ^= MBC_CRC_MASK[0U]; + 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; + } m_CSBKO = CSBKO(m_data[0U] & 0x3FU); m_FID = m_data[1U]; @@ -149,6 +191,24 @@ bool CDMRCSBK::put(const unsigned char* bytes) 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; m_dstId = m_data[4U] << 16 | m_data[5U] << 8 | m_data[6U]; @@ -175,13 +235,35 @@ void CDMRCSBK::get(unsigned char* bytes) const { assert(bytes != nullptr); - m_data[10U] ^= CSBK_CRC_MASK[0U]; - m_data[11U] ^= CSBK_CRC_MASK[1U]; + switch (m_dataType) { + case DT_CSBK: + 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]; + m_data[10U] ^= CSBK_CRC_MASK[0U]; + m_data[11U] ^= CSBK_CRC_MASK[1U]; + break; + + case DT_MBC_HEADER: + m_data[10U] ^= MBC_CRC_MASK[0U]; + m_data[11U] ^= MBC_CRC_MASK[1U]; + + CCRC::addCCITT162(m_data, 12U); + + m_data[10U] ^= MBC_CRC_MASK[0U]; + m_data[11U] ^= MBC_CRC_MASK[1U]; + 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; bptc.encode(m_data, bytes); @@ -249,5 +331,14 @@ void CDMRCSBK::setCBF(unsigned char cbf) m_CBF = m_data[3U] = cbf; } -#endif +void CDMRCSBK::setDataType(unsigned char dataType) +{ + m_dataType = dataType; +} +unsigned char CDMRCSBK::getDataType() const +{ + return m_dataType; +} + +#endif diff --git a/DMRCSBK.h b/DMRCSBK.h index 169023b..0b45a42 100644 --- a/DMRCSBK.h +++ b/DMRCSBK.h @@ -25,17 +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 + 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 @@ -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,9 @@ public: unsigned char getCBF() const; void setCBF(unsigned char cbf); + void setDataType(unsigned char dataType); + + unsigned char getDataType() const; private: unsigned char* m_data; @@ -81,6 +92,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 e1256ed..7570f6f 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,30 @@ 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->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); + } + } else { + if (command.slot == 1U) + m_slot1.setRCCommand(command.commandType); + else + m_slot2.setRCCommand(command.commandType); + } + } 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..1ce7ca6 100644 --- a/DMRData.h +++ b/DMRData.h @@ -19,6 +19,25 @@ #if defined(USE_DMR) +struct TrunkingCommandParameters { + unsigned int commandType = 0U; + bool trunkingParams = false; + bool channelEnable = false; + unsigned int slot = 1U; + bool ceaseTransmission = false; +}; + +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 { public: CDMRData(const CDMRData& data); @@ -73,4 +92,3 @@ private: #endif #endif - diff --git a/DMRDataHeader.cpp b/DMRDataHeader.cpp index 164cafb..dcd4e2e 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: @@ -135,6 +138,17 @@ void CDMRDataHeader::get(unsigned char* bytes) const { assert(bytes != nullptr); + // 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]; + CBPTC19696 bptc; bptc.encode(m_data, bytes); } @@ -171,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 ae273b5..dc6603a 100644 --- a/DMRDataHeader.h +++ b/DMRDataHeader.h @@ -52,9 +52,9 @@ private: bool m_F; bool m_S; unsigned char m_Ns; + bool m_UDT; }; #endif #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..45a1ece 100644 --- a/DMRNetwork.cpp +++ b/DMRNetwork.cpp @@ -123,15 +123,30 @@ 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; + 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) { + unsigned int command_type = (unsigned int)m_buffer[4U]; + + 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; + } + // Is this a data packet? if (::memcmp(m_buffer, "DMRD", 4U) != 0) return false; @@ -289,7 +304,7 @@ bool CDMRNetwork::isConnected() const return (m_addrLen != 0); } -void CDMRNetwork::close(bool sayGoodbye) +void CDMRNetwork::close() { LogMessage("DMR, Closing DMR Network"); @@ -318,7 +333,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); @@ -447,4 +462,3 @@ bool CDMRNetwork::write(const unsigned char* data, unsigned int length) } #endif - diff --git a/DMRNetwork.h b/DMRNetwork.h index 2d90953..10e6c25 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); @@ -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 3aef403..e29035f 100644 --- a/DMRSlot.cpp +++ b/DMRSlot.cpp @@ -1,5 +1,6 @@ /* * Copyright (C) 2015-2021,2023,2025,2026 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_rcCommand(0U) { 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_rcCommand = 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_rcCommand = DMRCommand::RCNoCommand; // Regenerate the LC data CDMRFullLC fullLC; fullLC.encode(*m_rfLC, data + 2U, DT_TERMINATOR_WITH_LC); @@ -484,11 +494,13 @@ 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_ovcm == DMR_OVCM::TX_ON) || (m_ovcm == DMR_OVCM::ON)) - csbk.setOVCM(true); - else if (m_ovcm == DMR_OVCM::FORCE_OFF) - csbk.setOVCM(false); + // 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) + csbk.setOVCM(false); + } bool gi = csbk.getGI(); unsigned int srcId = csbk.getSrcId(); @@ -524,7 +536,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->getDMRTrunkingEnabled()) writeQueueRF(data); writeNetworkRF(data, DT_CSBK, gi ? FLCO::GROUP : FLCO::USER_USER, srcId, dstId); @@ -563,7 +575,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 +860,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); @@ -1129,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)) + if ((m_rfState != RPT_RF_STATE::LISTENING) && (m_netState == RPT_NET_STATE::IDLE) && !m_modem->getDMRTrunkingEnabled()) return; m_networkWatchdog.start(); @@ -1685,11 +1697,13 @@ void CDMRSlot::writeNetwork(const CDMRData& dmrData) if (csbko == CSBKO::BSDWNACT) 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); + // 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) + csbk.setOVCM(false); + } bool gi = csbk.getGI(); unsigned int srcId = csbk.getSrcId(); @@ -1770,6 +1784,215 @@ 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->getDMRTrunkingEnabled()) { + 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->getDMRTrunkingEnabled()) { + 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); @@ -2024,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; @@ -2136,7 +2359,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 +2518,82 @@ void CDMRSlot::enable(bool enabled) m_enabled = enabled; } +void CDMRSlot::setRCCommand(unsigned int command) +{ + m_rcCommand = command; +} + +void CDMRSlot::createReverseChannel(unsigned char *data, CDMREMB &emb) { + if(m_rfN == 5U) { + 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]; + 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_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]; + 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_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]; + 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_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]; + 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_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]; + 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_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]; + 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..dc338eb 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 setRCCommand(unsigned int 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_rcCommand; 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(); @@ -185,4 +191,3 @@ private: #endif #endif - diff --git a/MMDVM-Host.cpp b/MMDVM-Host.cpp index 17aa06f..e266e77 100644 --- a/MMDVM-Host.cpp +++ b/MMDVM-Host.cpp @@ -627,6 +627,12 @@ int CMMDVMHost::run() DMR_OVCM ovcm = m_conf.getDMROVCM(); bool protect = m_conf.getDMRProtect(); + bool trunking = m_conf.getDMRTrunkingEnabled(); + 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(); @@ -704,6 +720,16 @@ 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 (trunking) { + m_modem->setDMRShortLC(systemCode, controlChannel, registrationRequired); + + setMode(MODE_DMR); + + if (controlChannel) + m_modem->writeDMRAloha(systemCode, registrationRequired, alternateSlot); + } } #endif @@ -813,7 +839,8 @@ int CMMDVMHost::run() if (remoteControlEnabled) m_remoteControl = new CRemoteControl(this, m_mqtt); - setMode(MODE_IDLE); + if (!m_conf.getDMRTrunkingEnabled()) + setMode(MODE_IDLE); while (!m_killed) { bool lockout = m_modem->hasLockout(); @@ -895,7 +922,7 @@ int CMMDVMHost::run() if (m_mode == MODE_IDLE) { if (m_duplex) { bool ret = m_dmr->processWakeup(data); - if (ret) { + if (ret || m_conf.getDMRTrunkingEnabled()) { m_modeTimer.setTimeout(m_dmrRFModeHang); setMode(MODE_DMR); dmrBeaconDurationTimer.stop(); @@ -909,7 +936,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.getDMRTrunkingEnabled()) { m_modem->writeDMRStart(true); m_dmrTXTimer.start(); } @@ -1008,7 +1035,7 @@ int CMMDVMHost::run() if (transparentSocket != nullptr && len > 0U) transparentSocket->write(data, len, transparentAddress, transparentAddrLen); - if (!m_fixedMode) { + if (!m_fixedMode && !m_conf.getDMRTrunkingEnabled()) { if (m_modeTimer.isRunning() && m_modeTimer.hasExpired()) setMode(MODE_IDLE); } @@ -1341,14 +1368,19 @@ int CMMDVMHost::run() dmrBeaconDurationTimer.clock(ms); if (dmrBeaconDurationTimer.isRunning() && dmrBeaconDurationTimer.hasExpired()) { - if (!m_fixedMode) + if (!m_fixedMode && !m_conf.getDMRTrunkingEnabled()) setMode(MODE_IDLE); dmrBeaconDurationTimer.stop(); } m_dmrTXTimer.clock(ms); if (m_dmrTXTimer.isRunning() && m_dmrTXTimer.hasExpired()) { - m_modem->writeDMRStart(false); + if (m_conf.getDMRTrunkingEnabled()) { + if (!m_conf.getDMRControlChannel()) + m_modem->writeDMRStart(false); + } else { + m_modem->writeDMRStart(false); + } m_dmrTXTimer.stop(); } #endif @@ -1392,7 +1424,7 @@ int CMMDVMHost::run() #if defined(USE_DMR) if (m_dmrNetwork != nullptr) { - m_dmrNetwork->close(true); + m_dmrNetwork->close(); delete m_dmrNetwork; } #endif @@ -1542,6 +1574,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(); @@ -1647,6 +1681,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); diff --git a/MMDVM-Host.ini b/MMDVM-Host.ini index a671b52..34a02f3 100644 --- a/MMDVM-Host.ini +++ b/MMDVM-Host.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/Modem.cpp b/Modem.cpp index e87f061..c5b2128 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 @@ -57,6 +60,7 @@ 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; @@ -184,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), @@ -401,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) @@ -2408,12 +2420,13 @@ bool CModem::setConfig2() buffer[26U] = 0U; buffer[27U] = 0x00U; - buffer[28U] = 0x00U; #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 @@ -2737,8 +2750,79 @@ 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::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]; + // 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_dmrTrunkingEnabled) + return true; + assert(m_port != nullptr); assert(lc != nullptr); @@ -3081,3 +3165,15 @@ void CModem::printDebug() CUtils::dump(1U, "Debug: Data", m_buffer + m_offset, m_length - m_offset); } } + +#if defined(USE_DMR) +bool CModem::getDMRTrunkingEnabled() const +{ + return m_dmrTrunkingEnabled; +} + +bool CModem::getDMRControlChannel() const +{ + return m_dmrControlChannel; +} +#endif diff --git a/Modem.h b/Modem.h index 33004d6..ccedaca 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 @@ -52,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); @@ -179,7 +183,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 setDMRShortLC(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); @@ -199,6 +205,11 @@ public: void close(); +#if defined(USE_DMR) + bool getDMRTrunkingEnabled() const; + bool getDMRControlChannel() const; +#endif + private: unsigned int m_protocolVersion; #if defined(USE_DMR) @@ -259,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; diff --git a/Version.h b/Version.h index 904951e..d581315 100644 --- a/Version.h +++ b/Version.h @@ -19,6 +19,6 @@ #if !defined(VERSION_H) #define VERSION_H -const char* VERSION = "20260528"; +const char* VERSION = "20260713"; #endif