DMR trunking patches

This commit is contained in:
adrian 2026-04-18 11:58:50 +03:00
parent b5b119fa8e
commit 4ff3bbf766
16 changed files with 696 additions and 55 deletions

View file

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

10
Conf.h
View file

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

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

View file

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

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

View file

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

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

View file

@ -52,6 +52,7 @@ private:
bool m_F;
bool m_S;
unsigned char m_Ns;
bool m_UDT;
};
#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,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);

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

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

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 setReverseChannelCommand(unsigned int rc_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_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();

View file

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

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

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