Merge branch 'dmr_trunking'

This commit is contained in:
Jonathan Naylor 2026-07-13 15:55:00 +01:00
commit 39694753b1
18 changed files with 750 additions and 70 deletions

View file

@ -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;

10
Conf.h
View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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;
}
}
}
}

View file

@ -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

View file

@ -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

View file

@ -52,9 +52,9 @@ private:
bool m_F;
bool m_S;
unsigned char m_Ns;
bool m_UDT;
};
#endif
#endif

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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<unsigned char> 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

View file

@ -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);

View file

@ -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

100
Modem.cpp
View file

@ -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 <cmath>
#include <cstdio>
@ -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; i<DMR_FRAME_LENGTH_BYTES; i++)
{
buffer[i + 3U] = data[i+ 2U];
}
return m_port->write(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

15
Modem.h
View file

@ -23,6 +23,9 @@
#include "RingBuffer.h"
#include "Defines.h"
#include "Timer.h"
#include "DMRCSBK.h"
#include "DMRSlotType.h"
#include "Sync.h"
#include <string>
@ -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;

View file

@ -19,6 +19,6 @@
#if !defined(VERSION_H)
#define VERSION_H
const char* VERSION = "20260528";
const char* VERSION = "20260713";
#endif