diff --git a/src/common/dmr/DMRDefines.h b/src/common/dmr/DMRDefines.h index d8e240fd..90c49116 100644 --- a/src/common/dmr/DMRDefines.h +++ b/src/common/dmr/DMRDefines.h @@ -130,6 +130,15 @@ namespace dmr const uint32_t RAW_AMBE_LENGTH_BYTES = 9U; /** @} */ + /** @name Reverse Channel Commands */ + const uint8_t RC_CEASE_TRANSMIT[5U] = { 0x07U, 0x0DU, 0x04U, 0xF1U, 0xF0U }; + const uint8_t RC_REQUEST_CEASE_TRANSMIT[5U] = { 0x02U, 0x7BU, 0x4EU, 0x48U, 0x70U }; + const uint8_t RC_MAX_POWER[5U] = { 0x01U, 0xC3U, 0xDDU, 0x3CU, 0x10U }; + const uint8_t RC_MIN_POWER[5U] = { 0x04U, 0xB5U, 0x97U, 0x85U, 0x90U }; + const uint8_t RC_POWER_INCREASE[5U] = { 0x04U, 0x5BU, 0xA7U, 0x58U, 0xA0U }; + const uint8_t RC_POWER_DECREASE[5U] = { 0x01U, 0x2DU, 0xEDU, 0xE1U, 0x20U }; + /** @} */ + /** @name Thresholds */ /** @brief TSCC maximum CSC count */ const uint16_t TSCC_MAX_CSC_CNT = 511U; diff --git a/src/common/network/RTPFNEHeader.h b/src/common/network/RTPFNEHeader.h index 3b842276..7d01b57a 100644 --- a/src/common/network/RTPFNEHeader.h +++ b/src/common/network/RTPFNEHeader.h @@ -130,6 +130,14 @@ namespace network BUSY_DENY = 0x00U, //!< Busy Deny REJECT_TRAFFIC = 0x01U, //!< Reject Active Traffic + + // DMR Reverse Channel Commands + DMR_RC_CEASE_TRANSMIT = 0xD2U, //!< DMR Reverse Channel: Cease Transmission + DMR_RC_REQUEST_CEASE_TRANSMIT = 0xD3U, //!< DMR Reverse Channel: Request Cease Transmission + DMR_RC_MAXIMUM_POWER = 0xD4U, //!< DMR Reverse Channel: Maximum Power + DMR_RC_MINIMUM_POWER = 0xD5U, //!< DMR Reverse Channel: Minimum Power + DMR_RC_POWER_INCREASE_ONE_STEP = 0xD6U, //!< DMR Reverse Channel: Increase Power One Step + DMR_RC_POWER_DECREASE_ONE_STEP = 0xD7U, //!< DMR Reverse Channel: Decrease Power One Step }; }; diff --git a/src/fne/HostFNE.cpp b/src/fne/HostFNE.cpp index cb85d22f..5d8f01d4 100644 --- a/src/fne/HostFNE.cpp +++ b/src/fne/HostFNE.cpp @@ -1058,6 +1058,12 @@ void HostFNE::processPeerDMRInCallCtrl(network::NET_ICC::ENUM command, uint32_t { switch (command) { case network::NET_ICC::REJECT_TRAFFIC: + case network::NET_ICC::DMR_RC_CEASE_TRANSMIT: + case network::NET_ICC::DMR_RC_REQUEST_CEASE_TRANSMIT: + case network::NET_ICC::DMR_RC_MAXIMUM_POWER: + case network::NET_ICC::DMR_RC_MINIMUM_POWER: + case network::NET_ICC::DMR_RC_POWER_INCREASE_ONE_STEP: + case network::NET_ICC::DMR_RC_POWER_DECREASE_ONE_STEP: m_network->processDownstreamInCallCtrl(command, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, dstId, slot, peerId, ssrc, streamId); break; diff --git a/src/fne/network/TrafficNetwork.cpp b/src/fne/network/TrafficNetwork.cpp index 176a80ae..eff07cbc 100644 --- a/src/fne/network/TrafficNetwork.cpp +++ b/src/fne/network/TrafficNetwork.cpp @@ -2508,7 +2508,15 @@ void TrafficNetwork::processInCallCtrl(network::NET_ICC::ENUM command, network:: switch (command) { case network::NET_ICC::REJECT_TRAFFIC: + case network::NET_ICC::DMR_RC_CEASE_TRANSMIT: + case network::NET_ICC::DMR_RC_REQUEST_CEASE_TRANSMIT: + case network::NET_ICC::DMR_RC_MAXIMUM_POWER: + case network::NET_ICC::DMR_RC_MINIMUM_POWER: + case network::NET_ICC::DMR_RC_POWER_INCREASE_ONE_STEP: + case network::NET_ICC::DMR_RC_POWER_DECREASE_ONE_STEP: { + const bool callTakeover = (command == network::NET_ICC::REJECT_TRAFFIC); + // is this a local peer? if (ssrc > 0 && (m_peers.find(ssrc) != m_peers.end())) { FNEPeerConnection* connection = m_peers[ssrc]; @@ -2520,26 +2528,28 @@ void TrafficNetwork::processInCallCtrl(network::NET_ICC::ENUM command, network:: // send ICC request to local peer writePeerICC(ssrc, streamId, subFunc, command, dstId, slotNo, true); - // flag the protocol call handler to allow call takeover on the next audio frame - switch (subFunc) { - case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // Encapsulated DMR data frame - m_tagDMR->triggerCallTakeover(dstId); - break; + if (callTakeover) { + // flag the protocol call handler to allow call takeover on the next audio frame + switch (subFunc) { + case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // Encapsulated DMR data frame + m_tagDMR->triggerCallTakeover(dstId); + break; - case NET_SUBFUNC::PROTOCOL_SUBFUNC_P25: // Encapsulated P25 data frame - m_tagP25->triggerCallTakeover(dstId); - break; + case NET_SUBFUNC::PROTOCOL_SUBFUNC_P25: // Encapsulated P25 data frame + m_tagP25->triggerCallTakeover(dstId); + break; - case NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN: // Encapsulated NXDN data frame - m_tagNXDN->triggerCallTakeover(dstId); - break; + case NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN: // Encapsulated NXDN data frame + m_tagNXDN->triggerCallTakeover(dstId); + break; - case NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG: // Encapsulated analog data frame - m_tagAnalog->triggerCallTakeover(dstId); - break; + case NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG: // Encapsulated analog data frame + m_tagAnalog->triggerCallTakeover(dstId); + break; - default: - break; + default: + break; + } } } } @@ -2566,26 +2576,28 @@ void TrafficNetwork::processInCallCtrl(network::NET_ICC::ENUM command, network:: } m_peers.shared_unlock(); - // flag the protocol call handler to allow call takeover on the next audio frame - switch (subFunc) { - case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // Encapsulated DMR data frame - m_tagDMR->triggerCallTakeover(dstId); - break; + if (callTakeover) { + // flag the protocol call handler to allow call takeover on the next audio frame + switch (subFunc) { + case NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR: // Encapsulated DMR data frame + m_tagDMR->triggerCallTakeover(dstId); + break; - case NET_SUBFUNC::PROTOCOL_SUBFUNC_P25: // Encapsulated P25 data frame - m_tagP25->triggerCallTakeover(dstId); - break; + case NET_SUBFUNC::PROTOCOL_SUBFUNC_P25: // Encapsulated P25 data frame + m_tagP25->triggerCallTakeover(dstId); + break; - case NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN: // Encapsulated NXDN data frame - m_tagNXDN->triggerCallTakeover(dstId); - break; + case NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN: // Encapsulated NXDN data frame + m_tagNXDN->triggerCallTakeover(dstId); + break; - case NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG: // Encapsulated analog data frame - m_tagAnalog->triggerCallTakeover(dstId); - break; + case NET_SUBFUNC::PROTOCOL_SUBFUNC_ANALOG: // Encapsulated analog data frame + m_tagAnalog->triggerCallTakeover(dstId); + break; - default: - break; + default: + break; + } } // send further up the network tree (only if ICC request came from a local peer) diff --git a/src/host/dmr/Slot.cpp b/src/host/dmr/Slot.cpp index 73aa397e..6aa2e37b 100644 --- a/src/host/dmr/Slot.cpp +++ b/src/host/dmr/Slot.cpp @@ -168,7 +168,8 @@ Slot::Slot(uint32_t slotNo, uint32_t timeout, uint32_t tgHang, uint32_t queueSiz m_notifyCC(true), m_ccDebug(debug), m_verbose(verbose), - m_debug(debug) + m_debug(debug), + m_reverseChannelCommand(network::NET_ICC::NOP) { m_interval.start(); @@ -550,6 +551,27 @@ void Slot::processInCallCtrl(network::NET_ICC::ENUM command, uint32_t dstId) m_rfLastSrcId = 0U; m_rfTGHang.stop(); m_rfState = RS_RF_REJECTED; + m_reverseChannelCommand = network::NET_ICC::NOP; + } + } + break; + + case network::NET_ICC::DMR_RC_CEASE_TRANSMIT: + case network::NET_ICC::DMR_RC_REQUEST_CEASE_TRANSMIT: + case network::NET_ICC::DMR_RC_MAXIMUM_POWER: + case network::NET_ICC::DMR_RC_MINIMUM_POWER: + case network::NET_ICC::DMR_RC_POWER_INCREASE_ONE_STEP: + case network::NET_ICC::DMR_RC_POWER_DECREASE_ONE_STEP: + { + if (m_rfState == RS_RF_AUDIO && m_rfLC != nullptr && (dstId == 0U || m_rfLC->getDstId() == dstId)) { + m_reverseChannelCommand = command; + + if (m_verbose) { + LogInfoEx(LOG_DMR, "Slot %u, set DMR reverse channel command = $%02X, dstId = %u", m_slotNo, command, dstId); + } + } + else if (m_verbose) { + LogInfoEx(LOG_DMR, "Slot %u, ignored DMR reverse channel command = $%02X, no active matching RF audio call, dstId = %u", m_slotNo, command, dstId); } } break; diff --git a/src/host/dmr/Slot.h b/src/host/dmr/Slot.h index ba96aefb..5803c658 100644 --- a/src/host/dmr/Slot.h +++ b/src/host/dmr/Slot.h @@ -445,6 +445,8 @@ namespace dmr bool m_verbose; bool m_debug; + network::NET_ICC::ENUM m_reverseChannelCommand; + static Control* s_dmr; static bool s_authoritative; diff --git a/src/host/dmr/packet/Voice.cpp b/src/host/dmr/packet/Voice.cpp index b959e70f..a57a638d 100644 --- a/src/host/dmr/packet/Voice.cpp +++ b/src/host/dmr/packet/Voice.cpp @@ -471,6 +471,12 @@ bool Voice::process(uint8_t* data, uint32_t len) // Regenerate the EMB emb.setColorCode(m_slot->s_colorCode); emb.setLCSS(lcss); + + // Emit reverse-channel data in the final voice burst of a superframe + if (m_rfN == 5U) { + applyReverseChannelCommand(m_slot->m_reverseChannelCommand, data, emb); + } + emb.encode(data + 2U); if (!m_slot->m_rfTimeout) { @@ -1315,6 +1321,45 @@ void Voice::logGPSPosition(const uint32_t srcId, const uint8_t* data) LogInfoEx(LOG_DMR, "GPS position for %u [lat %f, long %f] (Position error %s)", srcId, latitude, longitude, error); } +/* Helper to apply a DMR reverse channel command received via in-call control. */ + +bool Voice::applyReverseChannelCommand(network::NET_ICC::ENUM command, uint8_t* data, dmr::data::EMB& emb) +{ + const uint8_t* payload = nullptr; + switch (command) { + case network::NET_ICC::DMR_RC_CEASE_TRANSMIT: + payload = RC_CEASE_TRANSMIT; + break; + case network::NET_ICC::DMR_RC_REQUEST_CEASE_TRANSMIT: + payload = RC_REQUEST_CEASE_TRANSMIT; + break; + case network::NET_ICC::DMR_RC_MAXIMUM_POWER: + payload = RC_MAX_POWER; + break; + case network::NET_ICC::DMR_RC_MINIMUM_POWER: + payload = RC_MIN_POWER; + break; + case network::NET_ICC::DMR_RC_POWER_INCREASE_ONE_STEP: + payload = RC_POWER_INCREASE; + break; + case network::NET_ICC::DMR_RC_POWER_DECREASE_ONE_STEP: + payload = RC_POWER_DECREASE; + break; + default: + return false; + } + + data[16U] = (data[16U] & 0xF0U) | (payload[0U] & 0x0FU); + data[17U] = payload[1U]; + data[18U] = payload[2U]; + data[19U] = payload[3U]; + data[20U] = (data[20U] & 0x0FU) | (payload[4U] & 0xF0U); + + // Reverse-channel command data is indicated via PI in EMB. + emb.setPI(true); + return true; +} + /* Helper to insert AMBE null frames for missing audio. */ void Voice::insertNullAudio(uint8_t* data) diff --git a/src/host/dmr/packet/Voice.h b/src/host/dmr/packet/Voice.h index dd36ef77..6a52d1c6 100644 --- a/src/host/dmr/packet/Voice.h +++ b/src/host/dmr/packet/Voice.h @@ -19,6 +19,7 @@ #include "Defines.h" #include "common/dmr/data/NetData.h" +#include "common/dmr/data/EMB.h" #include "common/dmr/data/EmbeddedData.h" #include "common/dmr/lc/LC.h" #include "common/dmr/lc/PrivacyLC.h" @@ -132,6 +133,15 @@ namespace dmr */ void logGPSPosition(const uint32_t srcId, const uint8_t* data); + /** + * @brief Helper to apply a DMR reverse channel command received via in-call control. + * @param command The reverse channel command. + * @param data Buffer containing command data. + * @param emb Embedded data associated with the command. + * @return bool True if the command was successfully applied, otherwise false. + */ + bool applyReverseChannelCommand(network::NET_ICC::ENUM command, uint8_t* data, dmr::data::EMB& emb); + /** * @brief Helper to insert AMBE null frames for missing audio. * @param data Buffer containing frame data.