mirror of
https://github.com/g4klx/MMDVMHost.git
synced 2026-03-20 12:04:40 +01:00
Several JSON message builders conditionally omit fields when their values are empty or null: - DMRSlot, P25Control, NXDNControl: source_info omitted when the callsign/ID lookup returns an empty string - DStarControl: reflector omitted in RF start messages and when the reflector pointer is null in network start messages - YSFControl: reflector omitted in RF start messages Downstream consumers (such as Display-Driver) that access these fields via nlohmann::json const operator[] crash with an assertion failure when the key is absent. Always emit these fields with an empty string default so that the JSON schema is consistent regardless of lookup results or message source.
1391 lines
35 KiB
C++
1391 lines
35 KiB
C++
/*
|
|
* Copyright (C) 2016-2019,2021,2023,2024,2025 by Jonathan Naylor G4KLX
|
|
* Copyright (C) 2018 by Bryan Biedenkapp <gatekeep@gmail.com> N2PLL
|
|
*
|
|
* 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
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#include "P25Control.h"
|
|
#include "P25Defines.h"
|
|
#include "P25Trellis.h"
|
|
#include "P25Utils.h"
|
|
#include "Utils.h"
|
|
#include "Sync.h"
|
|
#include "CRC.h"
|
|
#include "Log.h"
|
|
|
|
#if defined(USE_P25)
|
|
|
|
#include <cassert>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <ctime>
|
|
|
|
const unsigned int RSSI_COUNT = 6U; // 6 * 180ms = 1080ms
|
|
const unsigned int BER_COUNT = 6U * 1233U; // 6 * 180ms = 1080ms
|
|
|
|
const unsigned char BIT_MASK_TABLE[] = {0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U};
|
|
|
|
#define WRITE_BIT(p,i,b) p[(i)>>3] = (b) ? (p[(i)>>3] | BIT_MASK_TABLE[(i)&7]) : (p[(i)>>3] & ~BIT_MASK_TABLE[(i)&7])
|
|
#define READ_BIT(p,i) (p[(i)>>3] & BIT_MASK_TABLE[(i)&7])
|
|
|
|
CP25Control::CP25Control(unsigned int nac, unsigned int id, bool selfOnly, bool uidOverride, CP25Network* network, unsigned int timeout, bool duplex, CDMRLookup* lookup, bool remoteGateway, CRSSIInterpolator* rssiMapper) :
|
|
m_nac(nac),
|
|
m_id(id),
|
|
m_selfOnly(selfOnly),
|
|
m_uidOverride(uidOverride),
|
|
m_remoteGateway(remoteGateway),
|
|
m_network(network),
|
|
m_duplex(duplex),
|
|
m_lookup(lookup),
|
|
m_queue(1000U, "P25 Control"),
|
|
m_rfState(RPT_RF_STATE::LISTENING),
|
|
m_netState(RPT_NET_STATE::IDLE),
|
|
m_rfTimeout(1000U, timeout),
|
|
m_netTimeout(1000U, timeout),
|
|
m_networkWatchdog(1000U, 0U, 1500U),
|
|
m_rfFrames(0U),
|
|
m_rfBits(0U),
|
|
m_rfErrs(0U),
|
|
// m_rfUndecodableLC(0U),
|
|
m_netFrames(0U),
|
|
m_netLost(0U),
|
|
m_rfDataFrames(0U),
|
|
m_nid(nac),
|
|
m_lastDUID(P25_DUID_TERM),
|
|
m_audio(),
|
|
m_rfData(),
|
|
// m_rfLastLDU1(),
|
|
// m_rfLastLDU2(),
|
|
m_netData(),
|
|
m_rfLSD(),
|
|
m_netLSD(),
|
|
m_netLDU1(nullptr),
|
|
m_netLDU2(nullptr),
|
|
m_lastIMBE(nullptr),
|
|
m_rfLDU(nullptr),
|
|
m_rfPDU(nullptr),
|
|
m_rfPDUCount(0U),
|
|
m_rfPDUBits(0U),
|
|
m_rssiMapper(rssiMapper),
|
|
m_rssi(0),
|
|
m_maxRSSI(0),
|
|
m_minRSSI(0),
|
|
m_aveRSSI(0),
|
|
m_rssiCountTotal(0U),
|
|
m_rssiAccum(0),
|
|
m_rssiCount(0U),
|
|
m_bitsCount(0U),
|
|
m_bitErrsAccum(0U),
|
|
m_enabled(true)
|
|
{
|
|
assert(lookup != nullptr);
|
|
assert(rssiMapper != nullptr);
|
|
|
|
m_netLDU1 = new unsigned char[9U * 25U];
|
|
m_netLDU2 = new unsigned char[9U * 25U];
|
|
|
|
::memset(m_netLDU1, 0x00U, 9U * 25U);
|
|
::memset(m_netLDU2, 0x00U, 9U * 25U);
|
|
|
|
m_lastIMBE = new unsigned char[11U];
|
|
::memcpy(m_lastIMBE, P25_NULL_IMBE, 11U);
|
|
|
|
m_rfLDU = new unsigned char[P25_LDU_FRAME_LENGTH_BYTES];
|
|
::memset(m_rfLDU, 0x00U, P25_LDU_FRAME_LENGTH_BYTES);
|
|
|
|
m_rfPDU = new unsigned char[P25_MAX_PDU_COUNT * P25_LDU_FRAME_LENGTH_BYTES + 2U];
|
|
::memset(m_rfPDU, 0x00U, P25_MAX_PDU_COUNT * P25_LDU_FRAME_LENGTH_BYTES + 2U);
|
|
}
|
|
|
|
CP25Control::~CP25Control()
|
|
{
|
|
delete[] m_netLDU1;
|
|
delete[] m_netLDU2;
|
|
delete[] m_lastIMBE;
|
|
delete[] m_rfLDU;
|
|
delete[] m_rfPDU;
|
|
}
|
|
|
|
bool CP25Control::writeModem(unsigned char* data, unsigned int len)
|
|
{
|
|
assert(data != nullptr);
|
|
|
|
if (!m_enabled)
|
|
return false;
|
|
|
|
bool sync = data[1U] == 0x01U;
|
|
|
|
if ((data[0U] == TAG_LOST) && (m_rfState == RPT_RF_STATE::AUDIO)) {
|
|
bool grp = m_rfData.getLCF() == P25_LCF_GROUP;
|
|
unsigned int dstId = m_rfData.getDstId();
|
|
unsigned int srcId = m_rfData.getSrcId();
|
|
std::string source = m_lookup->find(srcId);
|
|
|
|
if (m_rssi != 0) {
|
|
LogMessage("P25, transmission lost from %s to %s%u, %.1f seconds, BER: %.1f%%, RSSI: %d/%d/%d dBm", source.c_str(), grp ? "TG " : "", dstId, float(m_rfFrames) / 5.56F, float(m_rfErrs * 100U) / float(m_rfBits), m_minRSSI, m_maxRSSI, m_aveRSSI / int(m_rssiCountTotal));
|
|
writeJSONRF("lost", float(m_rfFrames) / 5.56F, float(m_rfErrs * 100U) / float(m_rfBits), m_minRSSI, m_maxRSSI, m_aveRSSI / int(m_rssiCountTotal));
|
|
} else {
|
|
LogMessage("P25, transmission lost from %s to %s%u, %.1f seconds, BER: %.1f%%", source.c_str(), grp ? "TG " : "", dstId, float(m_rfFrames) / 5.56F, float(m_rfErrs * 100U) / float(m_rfBits));
|
|
writeJSONRF("lost", float(m_rfFrames) / 5.56F, float(m_rfErrs * 100U) / float(m_rfBits));
|
|
}
|
|
|
|
// LogMessage("P25, total frames: %d, bits: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_rfFrames, m_rfBits, m_rfUndecodableLC, m_rfErrs, float(m_rfErrs * 100U) / float(m_rfBits));
|
|
|
|
writeNetwork(m_rfLDU, m_lastDUID, true);
|
|
writeNetwork(data + 2U, P25_DUID_TERM, true);
|
|
m_rfState = RPT_RF_STATE::LISTENING;
|
|
m_rfTimeout.stop();
|
|
m_rfData.reset();
|
|
|
|
return false;
|
|
}
|
|
|
|
if ((data[0U] == TAG_LOST) && (m_rfState == RPT_RF_STATE::DATA)) {
|
|
m_rfState = RPT_RF_STATE::LISTENING;
|
|
m_rfPDUCount = 0U;
|
|
m_rfPDUBits = 0U;
|
|
|
|
return false;
|
|
}
|
|
|
|
if (data[0U] == TAG_LOST) {
|
|
m_rfState = RPT_RF_STATE::LISTENING;
|
|
return false;
|
|
}
|
|
|
|
if (!sync && (m_rfState == RPT_RF_STATE::LISTENING))
|
|
return false;
|
|
|
|
// Decode the NID
|
|
bool valid = m_nid.decode(data + 2U);
|
|
|
|
if ((m_rfState == RPT_RF_STATE::LISTENING) && !valid)
|
|
return false;
|
|
|
|
unsigned char duid = m_nid.getDUID();
|
|
if (!valid) {
|
|
switch (m_lastDUID) {
|
|
case P25_DUID_HEADER:
|
|
case P25_DUID_LDU2:
|
|
duid = P25_DUID_LDU1;
|
|
break;
|
|
case P25_DUID_LDU1:
|
|
duid = P25_DUID_LDU2;
|
|
break;
|
|
case P25_DUID_PDU:
|
|
duid = P25_DUID_PDU;
|
|
break;
|
|
case P25_DUID_TSDU:
|
|
duid = P25_DUID_TSDU;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Have we got RSSI bytes on the end of a P25 LDU?
|
|
if (len == (P25_LDU_FRAME_LENGTH_BYTES + 4U)) {
|
|
uint16_t raw = 0U;
|
|
raw |= (data[218U] << 8) & 0xFF00U;
|
|
raw |= (data[219U] << 0) & 0x00FFU;
|
|
|
|
// Convert the raw RSSI to dBm
|
|
m_rssi = m_rssiMapper->interpolate(raw);
|
|
if (m_rssi != 0)
|
|
LogDebug("P25, raw RSSI: %u, reported RSSI: %d dBm", raw, m_rssi);
|
|
|
|
if (m_rssi < m_minRSSI)
|
|
m_minRSSI = m_rssi;
|
|
if (m_rssi > m_maxRSSI)
|
|
m_maxRSSI = m_rssi;
|
|
|
|
m_aveRSSI += m_rssi;
|
|
m_rssiCountTotal++;
|
|
|
|
m_rssiAccum += m_rssi;
|
|
m_rssiCount++;
|
|
}
|
|
|
|
if (duid == P25_DUID_HEADER) {
|
|
if (m_rfState == RPT_RF_STATE::LISTENING) {
|
|
m_rfData.reset();
|
|
bool ret = m_rfData.decodeHeader(data + 2U);
|
|
if (!ret) {
|
|
m_lastDUID = duid;
|
|
return false;
|
|
}
|
|
|
|
LogMessage("P25, received RF header");
|
|
|
|
m_lastDUID = duid;
|
|
return true;
|
|
}
|
|
} else if (duid == P25_DUID_LDU1) {
|
|
if (m_rfState == RPT_RF_STATE::LISTENING) {
|
|
m_rfData.reset();
|
|
bool ret = m_rfData.decodeLDU1(data + 2U);
|
|
if (!ret) {
|
|
m_lastDUID = duid;
|
|
return false;
|
|
}
|
|
|
|
unsigned int srcId = m_rfData.getSrcId();
|
|
|
|
if (m_selfOnly) {
|
|
if (m_id > 99999999U) { // Check that the Config DMR-ID is bigger than 8 digits
|
|
if (srcId != m_id / 100U)
|
|
return false;
|
|
}
|
|
|
|
else if (m_id > 9999999U) { // Check that the Config DMR-ID is bigger than 7 digits
|
|
if (srcId != m_id / 10U)
|
|
return false;
|
|
}
|
|
|
|
else if (srcId != m_id) { // All other cases
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!m_uidOverride) {
|
|
bool found = m_lookup->exists(srcId);
|
|
if (!found)
|
|
return false;
|
|
}
|
|
|
|
bool grp = m_rfData.getLCF() == P25_LCF_GROUP;
|
|
unsigned int dstId = m_rfData.getDstId();
|
|
std::string source = m_lookup->find(srcId);
|
|
|
|
LogMessage("P25, received RF voice transmission from %s to %s%u", source.c_str(), grp ? "TG " : "", dstId);
|
|
writeJSONRF("start", srcId, source, grp, dstId);
|
|
|
|
m_rfState = RPT_RF_STATE::AUDIO;
|
|
|
|
m_minRSSI = m_rssi;
|
|
m_maxRSSI = m_rssi;
|
|
m_aveRSSI = m_rssi;
|
|
m_rssiCountTotal = 1U;
|
|
|
|
m_rssiAccum = m_rssi;
|
|
m_rssiCount = 1U;
|
|
|
|
m_bitErrsAccum = 0U;
|
|
m_bitsCount = 0U;
|
|
|
|
createRFHeader();
|
|
writeNetwork(data + 2U, P25_DUID_HEADER, false);
|
|
} else if (m_rfState == RPT_RF_STATE::AUDIO) {
|
|
writeNetwork(m_rfLDU, m_lastDUID, false);
|
|
}
|
|
|
|
if (m_rfState == RPT_RF_STATE::AUDIO) {
|
|
/*
|
|
bool ret = m_rfData.decodeLDU1(data + 2U);
|
|
if (!ret) {
|
|
LogWarning("P25, LDU1 undecodable LC, using last LDU1 LC");
|
|
m_rfData = m_rfLastLDU1;
|
|
m_rfUndecodableLC++;
|
|
} else {
|
|
m_rfLastLDU1 = m_rfData;
|
|
}
|
|
*/
|
|
// Regenerate Sync
|
|
CSync::addP25Sync(data + 2U);
|
|
|
|
// Regenerate NID
|
|
m_nid.encode(data + 2U, P25_DUID_LDU1);
|
|
|
|
// Regenerate LDU1 Data
|
|
m_rfData.encodeLDU1(data + 2U);
|
|
|
|
// Regenerate the Low Speed Data
|
|
m_rfLSD.process(data + 2U);
|
|
|
|
// Regenerate Audio
|
|
unsigned int errors = m_audio.process(data + 2U);
|
|
LogDebug("P25, LDU1 audio, errs: %u/1233 (%.1f%%)", errors, float(errors) / 12.33F);
|
|
|
|
m_rfBits += 1233U;
|
|
m_rfErrs += errors;
|
|
m_rfFrames++;
|
|
m_lastDUID = duid;
|
|
|
|
m_bitsCount += 1233U;
|
|
m_bitErrsAccum += errors;
|
|
writeJSONBER();
|
|
|
|
// Add busy bits, inbound busy
|
|
addBusyBits(data + 2U, P25_LDU_FRAME_LENGTH_BITS, false, true);
|
|
|
|
::memcpy(m_rfLDU, data + 2U, P25_LDU_FRAME_LENGTH_BYTES);
|
|
|
|
if (m_duplex) {
|
|
data[0U] = TAG_DATA;
|
|
data[1U] = 0x00U;
|
|
writeQueueRF(data, P25_LDU_FRAME_LENGTH_BYTES + 2U);
|
|
}
|
|
|
|
writeJSONRSSI();
|
|
|
|
return true;
|
|
}
|
|
} else if (duid == P25_DUID_LDU2) {
|
|
if (m_rfState == RPT_RF_STATE::AUDIO) {
|
|
/*
|
|
bool ret = m_rfData.decodeLDU2(data + 2U);
|
|
if (!ret) {
|
|
LogWarning("P25, LDU2 undecodable LC, using last LDU2 LC");
|
|
m_rfData = m_rfLastLDU2;
|
|
m_rfUndecodableLC++;
|
|
} else {
|
|
m_rfLastLDU2 = m_rfData;
|
|
}
|
|
*/
|
|
writeNetwork(m_rfLDU, m_lastDUID, false);
|
|
|
|
// Regenerate Sync
|
|
CSync::addP25Sync(data + 2U);
|
|
|
|
// Regenerate NID
|
|
m_nid.encode(data + 2U, P25_DUID_LDU2);
|
|
|
|
// Add the LDU2 data
|
|
m_rfData.encodeLDU2(data + 2U);
|
|
|
|
// Regenerate the Low Speed Data
|
|
m_rfLSD.process(data + 2U);
|
|
|
|
// Regenerate Audio
|
|
unsigned int errors = m_audio.process(data + 2U);
|
|
LogDebug("P25, LDU2 audio, errs: %u/1233 (%.1f%%)", errors, float(errors) / 12.33F);
|
|
|
|
m_rfBits += 1233U;
|
|
m_rfErrs += errors;
|
|
m_rfFrames++;
|
|
m_lastDUID = duid;
|
|
|
|
m_bitsCount += 1233U;
|
|
m_bitErrsAccum += errors;
|
|
writeJSONBER();
|
|
|
|
// Add busy bits, inbound busy
|
|
addBusyBits(data + 2U, P25_LDU_FRAME_LENGTH_BITS, false, true);
|
|
|
|
::memcpy(m_rfLDU, data + 2U, P25_LDU_FRAME_LENGTH_BYTES);
|
|
|
|
if (m_duplex) {
|
|
data[0U] = TAG_DATA;
|
|
data[1U] = 0x00U;
|
|
writeQueueRF(data, P25_LDU_FRAME_LENGTH_BYTES + 2U);
|
|
}
|
|
|
|
writeJSONRSSI();
|
|
|
|
return true;
|
|
}
|
|
} else if (duid == P25_DUID_TSDU) {
|
|
if (m_rfState != RPT_RF_STATE::DATA) {
|
|
m_rfPDUCount = 0U;
|
|
m_rfPDUBits = 0U;
|
|
m_rfState = RPT_RF_STATE::DATA;
|
|
m_rfDataFrames = 0U;
|
|
}
|
|
|
|
bool ret = m_rfData.decodeTSDU(data + 2U);
|
|
if (!ret) {
|
|
m_lastDUID = duid;
|
|
return false;
|
|
}
|
|
|
|
bool grp = m_rfData.getLCF() == P25_LCF_GROUP;
|
|
unsigned int dstId = m_rfData.getDstId();
|
|
std::string source = m_lookup->find(m_rfData.getSrcId());
|
|
|
|
unsigned char data[P25_TSDU_FRAME_LENGTH_BYTES + 2U];
|
|
|
|
switch (m_rfData.getLCF()) {
|
|
case P25_LCF_GROUP: {
|
|
// Handle Group Voice Channel User - respond with Group Voice Channel Grant for talk permit
|
|
LogMessage("P25, received RF TSDU transmission, GROUP VOICE CH USER from %s to TG %u", source.c_str(), dstId);
|
|
::memset(data + 2U, 0x00U, P25_TSDU_FRAME_LENGTH_BYTES);
|
|
|
|
// Regenerate Sync
|
|
CSync::addP25Sync(data + 2U);
|
|
|
|
// Regenerate NID
|
|
m_nid.encode(data + 2U, P25_DUID_TSDU);
|
|
|
|
// Build a Group Voice Channel Grant response
|
|
// Save original LCF and set Grant LCF for encoding
|
|
unsigned char originalLcf = m_rfData.getLCF();
|
|
m_rfData.setLCF(P25_LCF_GROUP);
|
|
m_rfData.setServiceType(0x00U); // Service options: 0x00 = routine priority, no emergency
|
|
|
|
// Encode TSDU with Grant response
|
|
m_rfData.encodeTSDU(data + 2U);
|
|
|
|
// Restore original LCF
|
|
m_rfData.setLCF(originalLcf);
|
|
|
|
// Add busy bits - outbound busy (channel granted)
|
|
addBusyBits(data + 2U, P25_TSDU_FRAME_LENGTH_BITS, true, false);
|
|
|
|
// Set first busy bits to 1,0 (outbound busy)
|
|
setBusyBits(data + 2U, P25_SS0_START, true, false);
|
|
|
|
if (m_duplex) {
|
|
data[0U] = TAG_DATA;
|
|
data[1U] = 0x00U;
|
|
|
|
writeQueueRF(data, P25_TSDU_FRAME_LENGTH_BYTES + 2U);
|
|
}
|
|
}
|
|
break;
|
|
case P25_LCF_TSBK_CALL_ALERT:
|
|
LogMessage("P25, received RF TSDU transmission, CALL ALERT from %s to %s%u", source.c_str(), grp ? "TG " : "", dstId);
|
|
::memset(data + 2U, 0x00U, P25_TSDU_FRAME_LENGTH_BYTES);
|
|
|
|
// Regenerate Sync
|
|
CSync::addP25Sync(data + 2U);
|
|
|
|
// Regenerate NID
|
|
m_nid.encode(data + 2U, P25_DUID_TSDU);
|
|
|
|
// Regenerate TDULC Data
|
|
m_rfData.encodeTSDU(data + 2U);
|
|
|
|
// Add busy bits, inbound busy
|
|
addBusyBits(data + 2U, P25_TSDU_FRAME_LENGTH_BITS, false, true);
|
|
|
|
// Set first busy bits to 1,1
|
|
setBusyBits(data + 2U, P25_SS0_START, true, true);
|
|
|
|
if (m_duplex) {
|
|
data[0U] = TAG_DATA;
|
|
data[1U] = 0x00U;
|
|
|
|
writeQueueRF(data, P25_TSDU_FRAME_LENGTH_BYTES + 2U);
|
|
}
|
|
break;
|
|
case P25_LCF_TSBK_ACK_RSP_FNE:
|
|
LogMessage("P25, received RF TSDU transmission, ACK RESPONSE FNE from %s to %s%u", source.c_str(), grp ? "TG " : "", dstId);
|
|
::memset(data + 2U, 0x00U, P25_TSDU_FRAME_LENGTH_BYTES);
|
|
|
|
// Regenerate Sync
|
|
CSync::addP25Sync(data + 2U);
|
|
|
|
// Regenerate NID
|
|
m_nid.encode(data + 2U, P25_DUID_TSDU);
|
|
|
|
// Regenerate TDULC Data
|
|
m_rfData.encodeTSDU(data + 2U);
|
|
|
|
// Add busy bits, inbound busy
|
|
addBusyBits(data + 2U, P25_TSDU_FRAME_LENGTH_BITS, false, true);
|
|
|
|
// Set first busy bits to 1,1
|
|
setBusyBits(data + 2U, P25_SS0_START, true, true);
|
|
|
|
if (m_duplex) {
|
|
data[0U] = TAG_DATA;
|
|
data[1U] = 0x00U;
|
|
|
|
writeQueueRF(data, P25_TSDU_FRAME_LENGTH_BYTES + 2U);
|
|
}
|
|
break;
|
|
default:
|
|
LogMessage("P25, recieved RF TSDU transmission, unhandled LCF $%02X", m_rfData.getLCF());
|
|
break;
|
|
}
|
|
|
|
m_rfState = RPT_RF_STATE::LISTENING;
|
|
return true;
|
|
} else if (duid == P25_DUID_TERM || duid == P25_DUID_TERM_LC) {
|
|
if (m_rfState == RPT_RF_STATE::AUDIO) {
|
|
writeNetwork(m_rfLDU, m_lastDUID, true);
|
|
|
|
::memset(data + 2U, 0x00U, P25_TERM_FRAME_LENGTH_BYTES);
|
|
|
|
// Regenerate Sync
|
|
CSync::addP25Sync(data + 2U);
|
|
|
|
// Regenerate NID
|
|
m_nid.encode(data + 2U, P25_DUID_TERM);
|
|
|
|
// Add busy bits, inbound busy
|
|
addBusyBits(data + 2U, P25_TERM_FRAME_LENGTH_BITS, false, true);
|
|
|
|
bool grp = m_rfData.getLCF() == P25_LCF_GROUP;
|
|
unsigned int dstId = m_rfData.getDstId();
|
|
unsigned int srcId = m_rfData.getSrcId();
|
|
std::string source = m_lookup->find(srcId);
|
|
|
|
m_rfState = RPT_RF_STATE::LISTENING;
|
|
m_rfTimeout.stop();
|
|
m_rfData.reset();
|
|
m_lastDUID = duid;
|
|
|
|
if (m_rssi != 0) {
|
|
LogMessage("P25, received RF end of voice transmission from %s to %s%u, %.1f seconds, BER: %.1f%%, RSSI: %d/%d/%d dBm", source.c_str(), grp ? "TG " : "", dstId, float(m_rfFrames) / 5.56F, float(m_rfErrs * 100U) / float(m_rfBits), m_minRSSI, m_maxRSSI, m_aveRSSI / int(m_rssiCountTotal));
|
|
writeJSONRF("end", float(m_rfFrames) / 5.56F, float(m_rfErrs * 100U) / float(m_rfBits), m_minRSSI, m_maxRSSI, m_aveRSSI / int(m_rssiCountTotal));
|
|
} else {
|
|
LogMessage("P25, received RF end of voice transmission from %s to %s%u, %.1f seconds, BER: %.1f%%", source.c_str(), grp ? "TG " : "", dstId, float(m_rfFrames) / 5.56F, float(m_rfErrs * 100U) / float(m_rfBits));
|
|
writeJSONRF("end", float(m_rfFrames) / 5.56F, float(m_rfErrs * 100U) / float(m_rfBits));
|
|
}
|
|
|
|
// LogMessage("P25, total frames: %d, bits: %d, undecodable LC: %d, errors: %d, BER: %.4f%%", m_rfFrames, m_rfBits, m_rfUndecodableLC, m_rfErrs, float(m_rfErrs * 100U) / float(m_rfBits));
|
|
|
|
writeNetwork(data + 2U, P25_DUID_TERM, true);
|
|
|
|
if (m_duplex) {
|
|
data[0U] = TAG_EOT;
|
|
data[1U] = 0x00U;
|
|
writeQueueRF(data, P25_TERM_FRAME_LENGTH_BYTES + 2U);
|
|
}
|
|
}
|
|
} else if (duid == P25_DUID_PDU) {
|
|
if (m_rfState != RPT_RF_STATE::DATA) {
|
|
m_rfPDUCount = 0U;
|
|
m_rfPDUBits = 0U;
|
|
m_rfState = RPT_RF_STATE::DATA;
|
|
m_rfDataFrames = 0U;
|
|
}
|
|
|
|
unsigned int start = m_rfPDUCount * P25_LDU_FRAME_LENGTH_BITS;
|
|
|
|
unsigned char buffer[P25_LDU_FRAME_LENGTH_BYTES];
|
|
unsigned int bits = CP25Utils::decode(data + 2U, buffer, start, start + P25_LDU_FRAME_LENGTH_BITS);
|
|
|
|
for (unsigned int i = 0U; i < bits; i++, m_rfPDUBits++) {
|
|
bool b = READ_BIT(buffer, i);
|
|
WRITE_BIT(m_rfPDU, m_rfPDUBits, b);
|
|
}
|
|
|
|
if (m_rfPDUCount == 0U) {
|
|
CP25Trellis trellis;
|
|
unsigned char header[P25_PDU_HEADER_LENGTH_BYTES];
|
|
bool valid = trellis.decode12(m_rfPDU + P25_SYNC_LENGTH_BYTES + P25_NID_LENGTH_BYTES, header);
|
|
if (valid)
|
|
valid = CCRC::checkCCITT162(header, P25_PDU_HEADER_LENGTH_BYTES);
|
|
|
|
if (valid) {
|
|
unsigned int llId = (header[3U] << 16) + (header[4U] << 8) + header[5U];
|
|
unsigned int sap = header[1U] & 0x3FU;
|
|
m_rfDataFrames = header[6U] & 0x7FU;
|
|
|
|
LogMessage("P25, received RF data transmission for Local Link Id %u, SAP %u, %u blocks", llId, sap, m_rfDataFrames);
|
|
} else {
|
|
m_rfPDUCount = 0U;
|
|
m_rfPDUBits = 0U;
|
|
m_rfState = RPT_RF_STATE::LISTENING;
|
|
m_rfDataFrames = 0U;
|
|
}
|
|
}
|
|
|
|
if (m_rfState == RPT_RF_STATE::DATA) {
|
|
m_rfPDUCount++;
|
|
|
|
unsigned int bitLength = ((m_rfDataFrames + 1U) * P25_PDU_FEC_LENGTH_BITS) + P25_SYNC_LENGTH_BITS + P25_NID_LENGTH_BITS;
|
|
|
|
if (m_rfPDUBits >= bitLength) {
|
|
unsigned int offset = P25_SYNC_LENGTH_BYTES + P25_NID_LENGTH_BYTES;
|
|
|
|
// Regenerate the PDU header
|
|
CP25Trellis trellis;
|
|
unsigned char header[P25_PDU_HEADER_LENGTH_BYTES];
|
|
trellis.decode12(m_rfPDU + offset, header);
|
|
trellis.encode12(header, m_rfPDU + offset);
|
|
offset += P25_PDU_FEC_LENGTH_BITS;
|
|
|
|
// Regenerate the PDU data
|
|
for (unsigned int i = 0U; i < m_rfDataFrames; i++) {
|
|
unsigned char data[P25_PDU_CONFIRMED_LENGTH_BYTES];
|
|
|
|
bool valid = trellis.decode34(m_rfPDU + offset, data);
|
|
if (valid) {
|
|
trellis.encode34(data, m_rfPDU + offset);
|
|
} else {
|
|
valid = trellis.decode12(m_rfPDU + offset, data);
|
|
if (valid)
|
|
trellis.encode12(data, m_rfPDU + offset);
|
|
}
|
|
|
|
offset += P25_PDU_FEC_LENGTH_BITS;
|
|
}
|
|
|
|
unsigned char pdu[1024U];
|
|
|
|
// Add the data
|
|
unsigned int newBitLength = CP25Utils::encode(m_rfPDU, pdu + 2U, bitLength);
|
|
unsigned int newByteLength = newBitLength / 8U;
|
|
if ((newBitLength % 8U) > 0U)
|
|
newByteLength++;
|
|
|
|
// Regenerate Sync
|
|
CSync::addP25Sync(pdu + 2U);
|
|
|
|
// Regenerate NID
|
|
m_nid.encode(pdu + 2U, P25_DUID_PDU);
|
|
|
|
// Add busy bits, inbound busy
|
|
addBusyBits(pdu + 2U, newBitLength, false, true);
|
|
|
|
if (m_duplex) {
|
|
pdu[0U] = TAG_DATA;
|
|
pdu[1U] = 0x00U;
|
|
writeQueueRF(pdu, newByteLength + 2U);
|
|
}
|
|
|
|
LogMessage("P25, ended RF data transmission");
|
|
|
|
m_rfPDUCount = 0U;
|
|
m_rfPDUBits = 0U;
|
|
m_rfState = RPT_RF_STATE::LISTENING;
|
|
m_rfDataFrames = 0U;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
unsigned int CP25Control::readModem(unsigned char* data)
|
|
{
|
|
assert(data != nullptr);
|
|
|
|
if (m_queue.isEmpty())
|
|
return 0U;
|
|
|
|
unsigned char len = 0U;
|
|
m_queue.getData(&len, 1U);
|
|
|
|
m_queue.getData(data, len);
|
|
|
|
return len;
|
|
}
|
|
|
|
void CP25Control::writeNetwork()
|
|
{
|
|
unsigned char data[100U];
|
|
|
|
if (m_network == nullptr)
|
|
return;
|
|
|
|
unsigned int length = m_network->read(data, 100U);
|
|
if (length == 0U)
|
|
return;
|
|
|
|
if (!m_enabled)
|
|
return;
|
|
|
|
if ((m_rfState != RPT_RF_STATE::LISTENING) && (m_netState == RPT_NET_STATE::IDLE))
|
|
return;
|
|
|
|
m_networkWatchdog.start();
|
|
|
|
switch (data[0U]) {
|
|
case 0x62U:
|
|
::memcpy(m_netLDU1 + 0U, data, 22U);
|
|
checkNetLDU2();
|
|
break;
|
|
case 0x63U:
|
|
::memcpy(m_netLDU1 + 25U, data, 14U);
|
|
checkNetLDU2();
|
|
break;
|
|
case 0x64U:
|
|
::memcpy(m_netLDU1 + 50U, data, 17U);
|
|
checkNetLDU2();
|
|
break;
|
|
case 0x65U:
|
|
::memcpy(m_netLDU1 + 75U, data, 17U);
|
|
checkNetLDU2();
|
|
break;
|
|
case 0x66U:
|
|
::memcpy(m_netLDU1 + 100U, data, 17U);
|
|
checkNetLDU2();
|
|
break;
|
|
case 0x67U:
|
|
::memcpy(m_netLDU1 + 125U, data, 17U);
|
|
checkNetLDU2();
|
|
break;
|
|
case 0x68U:
|
|
::memcpy(m_netLDU1 + 150U, data, 17U);
|
|
checkNetLDU2();
|
|
break;
|
|
case 0x69U:
|
|
::memcpy(m_netLDU1 + 175U, data, 17U);
|
|
checkNetLDU2();
|
|
break;
|
|
case 0x6AU:
|
|
::memcpy(m_netLDU1 + 200U, data, 16U);
|
|
checkNetLDU2();
|
|
if (m_netState != RPT_NET_STATE::IDLE)
|
|
createNetLDU1();
|
|
break;
|
|
case 0x6BU:
|
|
::memcpy(m_netLDU2 + 0U, data, 22U);
|
|
checkNetLDU1();
|
|
break;
|
|
case 0x6CU:
|
|
::memcpy(m_netLDU2 + 25U, data, 14U);
|
|
checkNetLDU1();
|
|
break;
|
|
case 0x6DU:
|
|
::memcpy(m_netLDU2 + 50U, data, 17U);
|
|
checkNetLDU1();
|
|
break;
|
|
case 0x6EU:
|
|
::memcpy(m_netLDU2 + 75U, data, 17U);
|
|
checkNetLDU1();
|
|
break;
|
|
case 0x6FU:
|
|
::memcpy(m_netLDU2 + 100U, data, 17U);
|
|
checkNetLDU1();
|
|
break;
|
|
case 0x70U:
|
|
::memcpy(m_netLDU2 + 125U, data, 17U);
|
|
checkNetLDU1();
|
|
break;
|
|
case 0x71U:
|
|
::memcpy(m_netLDU2 + 150U, data, 17U);
|
|
checkNetLDU1();
|
|
break;
|
|
case 0x72U:
|
|
::memcpy(m_netLDU2 + 175U, data, 17U);
|
|
checkNetLDU1();
|
|
break;
|
|
case 0x73U:
|
|
::memcpy(m_netLDU2 + 200U, data, 16U);
|
|
if (m_netState == RPT_NET_STATE::IDLE) {
|
|
createNetHeader();
|
|
createNetLDU1();
|
|
} else {
|
|
checkNetLDU1();
|
|
}
|
|
createNetLDU2();
|
|
break;
|
|
case 0x80U:
|
|
createNetTerminator();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CP25Control::clock(unsigned int ms)
|
|
{
|
|
if (m_network != nullptr)
|
|
writeNetwork();
|
|
|
|
if (!m_enabled)
|
|
return;
|
|
|
|
m_rfTimeout.clock(ms);
|
|
m_netTimeout.clock(ms);
|
|
|
|
if (m_netState == RPT_NET_STATE::AUDIO) {
|
|
m_networkWatchdog.clock(ms);
|
|
|
|
if (m_networkWatchdog.hasExpired()) {
|
|
LogMessage("P25, network watchdog has expired, %.1f seconds, %u%% packet loss", float(m_netFrames) / 50.0F, (m_netLost * 100U) / m_netFrames);
|
|
writeJSONNet("lost", float(m_netFrames) / 50.0F, float(m_netLost * 100U) / float(m_netFrames));
|
|
|
|
m_networkWatchdog.stop();
|
|
m_netState = RPT_NET_STATE::IDLE;
|
|
m_netData.reset();
|
|
m_netTimeout.stop();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CP25Control::writeQueueRF(const unsigned char* data, unsigned int length)
|
|
{
|
|
assert(data != nullptr);
|
|
|
|
if (m_rfTimeout.isRunning() && m_rfTimeout.hasExpired())
|
|
return;
|
|
|
|
unsigned int space = m_queue.freeSpace();
|
|
if (space < (length + 1U)) {
|
|
LogError("P25, overflow in the P25 RF queue");
|
|
return;
|
|
}
|
|
|
|
unsigned char len = length;
|
|
m_queue.addData(&len, 1U);
|
|
|
|
m_queue.addData(data, len);
|
|
}
|
|
|
|
void CP25Control::writeQueueNet(const unsigned char* data, unsigned int length)
|
|
{
|
|
assert(data != nullptr);
|
|
|
|
if (m_netTimeout.isRunning() && m_netTimeout.hasExpired())
|
|
return;
|
|
|
|
unsigned int space = m_queue.freeSpace();
|
|
if (space < (length + 1U)) {
|
|
LogError("P25, overflow in the P25 RF queue");
|
|
return;
|
|
}
|
|
|
|
unsigned char len = length;
|
|
m_queue.addData(&len, 1U);
|
|
|
|
m_queue.addData(data, len);
|
|
}
|
|
|
|
void CP25Control::writeNetwork(const unsigned char *data, unsigned char type, bool end)
|
|
{
|
|
assert(data != nullptr);
|
|
|
|
if (m_network == nullptr)
|
|
return;
|
|
|
|
if (m_rfTimeout.isRunning() && m_rfTimeout.hasExpired())
|
|
return;
|
|
|
|
switch (type)
|
|
{
|
|
case P25_DUID_LDU1:
|
|
m_network->writeLDU1(data, m_rfData, m_rfLSD, end);
|
|
break;
|
|
case P25_DUID_LDU2:
|
|
m_network->writeLDU2(data, m_rfData, m_rfLSD, end);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CP25Control::setBusyBits(unsigned char* data, unsigned int ssOffset, bool b1, bool b2)
|
|
{
|
|
assert(data != nullptr);
|
|
|
|
WRITE_BIT(data, ssOffset, b1);
|
|
WRITE_BIT(data, ssOffset + 1U, b2);
|
|
}
|
|
|
|
void CP25Control::addBusyBits(unsigned char* data, unsigned int length, bool b1, bool b2)
|
|
{
|
|
assert(data != nullptr);
|
|
|
|
for (unsigned int ss0Pos = P25_SS0_START; ss0Pos < length; ss0Pos += P25_INCREMENT) {
|
|
unsigned int ss1Pos = ss0Pos + 1U;
|
|
WRITE_BIT(data, ss0Pos, b1);
|
|
WRITE_BIT(data, ss1Pos, b2);
|
|
}
|
|
}
|
|
|
|
void CP25Control::checkNetLDU1()
|
|
{
|
|
if (m_netState == RPT_NET_STATE::IDLE)
|
|
return;
|
|
|
|
// Check for an unflushed LDU1
|
|
if (m_netLDU1[0U] != 0x00U || m_netLDU1[25U] != 0x00U || m_netLDU1[50U] != 0x00U ||
|
|
m_netLDU1[75U] != 0x00U || m_netLDU1[100U] != 0x00U || m_netLDU1[125U] != 0x00U ||
|
|
m_netLDU1[150U] != 0x00U || m_netLDU1[175U] != 0x00U || m_netLDU1[200U] != 0x00U)
|
|
createNetLDU1();
|
|
}
|
|
|
|
void CP25Control::checkNetLDU2()
|
|
{
|
|
if (m_netState == RPT_NET_STATE::IDLE)
|
|
return;
|
|
|
|
// Check for an unflushed LDU1
|
|
if (m_netLDU2[0U] != 0x00U || m_netLDU2[25U] != 0x00U || m_netLDU2[50U] != 0x00U ||
|
|
m_netLDU2[75U] != 0x00U || m_netLDU2[100U] != 0x00U || m_netLDU2[125U] != 0x00U ||
|
|
m_netLDU2[150U] != 0x00U || m_netLDU2[175U] != 0x00U || m_netLDU2[200U] != 0x00U)
|
|
createNetLDU2();
|
|
}
|
|
|
|
void CP25Control::insertMissingAudio(unsigned char* data)
|
|
{
|
|
if (data[0U] == 0x00U) {
|
|
::memcpy(data + 10U, m_lastIMBE, 11U);
|
|
m_netLost++;
|
|
} else {
|
|
::memcpy(m_lastIMBE, data + 10U, 11U);
|
|
}
|
|
|
|
if (data[25U] == 0x00U) {
|
|
::memcpy(data + 26U, m_lastIMBE, 11U);
|
|
m_netLost++;
|
|
} else {
|
|
::memcpy(m_lastIMBE, data + 26U, 11U);
|
|
}
|
|
|
|
if (data[50U] == 0x00U) {
|
|
::memcpy(data + 55U, m_lastIMBE, 11U);
|
|
m_netLost++;
|
|
} else {
|
|
::memcpy(m_lastIMBE, data + 55U, 11U);
|
|
}
|
|
|
|
if (data[75U] == 0x00U) {
|
|
::memcpy(data + 80U, m_lastIMBE, 11U);
|
|
m_netLost++;
|
|
} else {
|
|
::memcpy(m_lastIMBE, data + 80U, 11U);
|
|
}
|
|
|
|
if (data[100U] == 0x00U) {
|
|
::memcpy(data + 105U, m_lastIMBE, 11U);
|
|
m_netLost++;
|
|
} else {
|
|
::memcpy(m_lastIMBE, data + 105U, 11U);
|
|
}
|
|
|
|
if (data[125U] == 0x00U) {
|
|
::memcpy(data + 130U, m_lastIMBE, 11U);
|
|
m_netLost++;
|
|
} else {
|
|
::memcpy(m_lastIMBE, data + 130U, 11U);
|
|
}
|
|
|
|
if (data[150U] == 0x00U) {
|
|
::memcpy(data + 155U, m_lastIMBE, 11U);
|
|
m_netLost++;
|
|
} else {
|
|
::memcpy(m_lastIMBE, data + 155U, 11U);
|
|
}
|
|
|
|
if (data[175U] == 0x00U) {
|
|
::memcpy(data + 180U, m_lastIMBE, 11U);
|
|
m_netLost++;
|
|
} else {
|
|
::memcpy(m_lastIMBE, data + 180U, 11U);
|
|
}
|
|
|
|
if (data[200U] == 0x00U) {
|
|
::memcpy(data + 204U, m_lastIMBE, 11U);
|
|
m_netLost++;
|
|
} else {
|
|
::memcpy(m_lastIMBE, data + 204U, 11U);
|
|
}
|
|
}
|
|
|
|
void CP25Control::createRFHeader()
|
|
{
|
|
unsigned char buffer[P25_HDR_FRAME_LENGTH_BYTES + 2U];
|
|
::memset(buffer, 0x00U, P25_HDR_FRAME_LENGTH_BYTES + 2U);
|
|
|
|
buffer[0U] = TAG_HEADER;
|
|
buffer[1U] = 0x00U;
|
|
|
|
// Add the sync
|
|
CSync::addP25Sync(buffer + 2U);
|
|
|
|
// Add the NID
|
|
m_nid.encode(buffer + 2U, P25_DUID_HEADER);
|
|
|
|
// Add the header
|
|
m_rfData.encodeHeader(buffer + 2U);
|
|
|
|
// Add busy bits, inbound busy
|
|
addBusyBits(buffer + 2U, P25_HDR_FRAME_LENGTH_BITS, false, true);
|
|
|
|
m_rfFrames = 0U;
|
|
m_rfErrs = 0U;
|
|
// m_rfUndecodableLC = 0U;
|
|
// m_rfLastLDU1.reset();
|
|
// m_rfLastLDU2.reset();
|
|
m_rfBits = 1U;
|
|
m_rfTimeout.start();
|
|
m_lastDUID = P25_DUID_HEADER;
|
|
::memset(m_rfLDU, 0x00U, P25_LDU_FRAME_LENGTH_BYTES);
|
|
|
|
if (m_duplex) {
|
|
buffer[0U] = TAG_HEADER;
|
|
buffer[1U] = 0x00U;
|
|
writeQueueRF(buffer, P25_HDR_FRAME_LENGTH_BYTES + 2U);
|
|
}
|
|
}
|
|
|
|
void CP25Control::createNetHeader()
|
|
{
|
|
unsigned char lcf = m_netLDU1[51U];
|
|
unsigned char mfId = m_netLDU1[52U];
|
|
unsigned int dstId = (m_netLDU1[76U] << 16) + (m_netLDU1[77U] << 8) + m_netLDU1[78U];
|
|
unsigned int srcId = (m_netLDU1[101U] << 16) + (m_netLDU1[102U] << 8) + m_netLDU1[103U];
|
|
|
|
// unsigned char algId = m_netLDU2[126U];
|
|
// unsigned int kId = (m_netLDU2[127U] << 8) + m_netLDU2[128U];
|
|
|
|
// unsigned char mi[P25_MI_LENGTH_BYTES];
|
|
// ::memcpy(mi + 0U, m_netLDU2 + 51U, 3U);
|
|
// ::memcpy(mi + 3U, m_netLDU2 + 76U, 3U);
|
|
// ::memcpy(mi + 6U, m_netLDU2 + 101U, 3U);
|
|
|
|
m_netData.reset();
|
|
// m_netData.setMI(mi);
|
|
// m_netData.setAlgId(algId);
|
|
// m_netData.setKId(kId);
|
|
m_netData.setLCF(lcf);
|
|
m_netData.setMFId(mfId);
|
|
m_netData.setSrcId(srcId);
|
|
m_netData.setDstId(dstId);
|
|
|
|
std::string source = m_lookup->find(srcId);
|
|
|
|
LogMessage("P25, received network transmission from %s to %s%u", source.c_str(), lcf == P25_LCF_GROUP ? "TG " : "", dstId);
|
|
writeJSONNet("start", srcId, source, lcf == P25_LCF_GROUP, dstId);
|
|
|
|
m_netState = RPT_NET_STATE::AUDIO;
|
|
m_netTimeout.start();
|
|
m_netFrames = 0U;
|
|
m_netLost = 0U;
|
|
|
|
unsigned char buffer[P25_HDR_FRAME_LENGTH_BYTES + 2U];
|
|
::memset(buffer, 0x00U, P25_HDR_FRAME_LENGTH_BYTES + 2U);
|
|
|
|
buffer[0U] = TAG_HEADER;
|
|
buffer[1U] = 0x00U;
|
|
|
|
// Add the sync
|
|
CSync::addP25Sync(buffer + 2U);
|
|
|
|
// Add the NID
|
|
m_nid.encode(buffer + 2U, P25_DUID_HEADER);
|
|
|
|
// Add the header
|
|
m_netData.encodeHeader(buffer + 2U);
|
|
|
|
// Add busy bits
|
|
if (m_remoteGateway)
|
|
// Add busy bits, inbound/outbound
|
|
addBusyBits(buffer + 2U, P25_HDR_FRAME_LENGTH_BITS, true, false);
|
|
else if (m_duplex)
|
|
// Add busy bits, inbound idle
|
|
addBusyBits(buffer + 2U, P25_HDR_FRAME_LENGTH_BITS, true, true);
|
|
else
|
|
// Add busy bits, inbound busy
|
|
addBusyBits(buffer + 2U, P25_HDR_FRAME_LENGTH_BITS, false, true);
|
|
|
|
writeQueueNet(buffer, P25_HDR_FRAME_LENGTH_BYTES + 2U);
|
|
}
|
|
|
|
void CP25Control::createNetLDU1()
|
|
{
|
|
insertMissingAudio(m_netLDU1);
|
|
|
|
unsigned char buffer[P25_LDU_FRAME_LENGTH_BYTES + 2U];
|
|
::memset(buffer, 0x00U, P25_LDU_FRAME_LENGTH_BYTES + 2U);
|
|
|
|
buffer[0U] = TAG_DATA;
|
|
buffer[1U] = 0x00U;
|
|
|
|
// Add the sync
|
|
CSync::addP25Sync(buffer + 2U);
|
|
|
|
// Add the NID
|
|
m_nid.encode(buffer + 2U, P25_DUID_LDU1);
|
|
|
|
// Add the LDU1 data
|
|
m_netData.encodeLDU1(buffer + 2U);
|
|
|
|
// Add the Audio
|
|
m_audio.encode(buffer + 2U, m_netLDU1 + 10U, 0U);
|
|
m_audio.encode(buffer + 2U, m_netLDU1 + 26U, 1U);
|
|
m_audio.encode(buffer + 2U, m_netLDU1 + 55U, 2U);
|
|
m_audio.encode(buffer + 2U, m_netLDU1 + 80U, 3U);
|
|
m_audio.encode(buffer + 2U, m_netLDU1 + 105U, 4U);
|
|
m_audio.encode(buffer + 2U, m_netLDU1 + 130U, 5U);
|
|
m_audio.encode(buffer + 2U, m_netLDU1 + 155U, 6U);
|
|
m_audio.encode(buffer + 2U, m_netLDU1 + 180U, 7U);
|
|
m_audio.encode(buffer + 2U, m_netLDU1 + 204U, 8U);
|
|
|
|
// Add the Low Speed Data
|
|
m_netLSD.setLSD1(m_netLDU1[201U]);
|
|
m_netLSD.setLSD2(m_netLDU1[202U]);
|
|
m_netLSD.encode(buffer + 2U);
|
|
|
|
// Add busy bits
|
|
if (m_remoteGateway)
|
|
// Add busy bits, inbound/outbound
|
|
addBusyBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, true, false);
|
|
else if (m_duplex)
|
|
// Add busy bits, inbound idle
|
|
addBusyBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, true, true);
|
|
else
|
|
// Add busy bits, inbound busy
|
|
addBusyBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, false, true);
|
|
|
|
writeQueueNet(buffer, P25_LDU_FRAME_LENGTH_BYTES + 2U);
|
|
|
|
::memset(m_netLDU1, 0x00U, 9U * 25U);
|
|
|
|
m_netFrames += 9U;
|
|
}
|
|
|
|
void CP25Control::createNetLDU2()
|
|
{
|
|
insertMissingAudio(m_netLDU2);
|
|
|
|
unsigned char buffer[P25_LDU_FRAME_LENGTH_BYTES + 2U];
|
|
::memset(buffer, 0x00U, P25_LDU_FRAME_LENGTH_BYTES + 2U);
|
|
|
|
buffer[0U] = TAG_DATA;
|
|
buffer[1U] = 0x00U;
|
|
|
|
// Add the sync
|
|
CSync::addP25Sync(buffer + 2U);
|
|
|
|
// Add the NID
|
|
m_nid.encode(buffer + 2U, P25_DUID_LDU2);
|
|
|
|
// Add the LDU2 data
|
|
m_netData.encodeLDU2(buffer + 2U);
|
|
|
|
// Add the Audio
|
|
m_audio.encode(buffer + 2U, m_netLDU2 + 10U, 0U);
|
|
m_audio.encode(buffer + 2U, m_netLDU2 + 26U, 1U);
|
|
m_audio.encode(buffer + 2U, m_netLDU2 + 55U, 2U);
|
|
m_audio.encode(buffer + 2U, m_netLDU2 + 80U, 3U);
|
|
m_audio.encode(buffer + 2U, m_netLDU2 + 105U, 4U);
|
|
m_audio.encode(buffer + 2U, m_netLDU2 + 130U, 5U);
|
|
m_audio.encode(buffer + 2U, m_netLDU2 + 155U, 6U);
|
|
m_audio.encode(buffer + 2U, m_netLDU2 + 180U, 7U);
|
|
m_audio.encode(buffer + 2U, m_netLDU2 + 204U, 8U);
|
|
|
|
// Add the Low Speed Data
|
|
m_netLSD.setLSD1(m_netLDU2[201U]);
|
|
m_netLSD.setLSD2(m_netLDU2[202U]);
|
|
m_netLSD.encode(buffer + 2U);
|
|
|
|
// Add busy bits
|
|
if (m_remoteGateway)
|
|
// Add busy bits, inbound/outbound
|
|
addBusyBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, true, false);
|
|
else if (m_duplex)
|
|
// Add busy bits, inbound idle
|
|
addBusyBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, true, true);
|
|
else
|
|
// Add busy bits, inbound busy
|
|
addBusyBits(buffer + 2U, P25_LDU_FRAME_LENGTH_BITS, false, true);
|
|
|
|
writeQueueNet(buffer, P25_LDU_FRAME_LENGTH_BYTES + 2U);
|
|
|
|
::memset(m_netLDU2, 0x00U, 9U * 25U);
|
|
|
|
m_netFrames += 9U;
|
|
}
|
|
|
|
void CP25Control::createNetTerminator()
|
|
{
|
|
unsigned char buffer[P25_TERM_FRAME_LENGTH_BYTES + 2U];
|
|
::memset(buffer, 0x00U, P25_TERM_FRAME_LENGTH_BYTES + 2U);
|
|
|
|
buffer[0U] = TAG_EOT;
|
|
buffer[1U] = 0x00U;
|
|
|
|
// Add the sync
|
|
CSync::addP25Sync(buffer + 2U);
|
|
|
|
// Add the NID
|
|
m_nid.encode(buffer + 2U, P25_DUID_TERM);
|
|
|
|
// Add busy bits
|
|
if (m_remoteGateway)
|
|
// Add busy bits, inbound/outbound
|
|
addBusyBits(buffer + 2U, P25_TERM_FRAME_LENGTH_BITS, true, false);
|
|
else if (m_duplex)
|
|
// Add busy bits, inbound idle
|
|
addBusyBits(buffer + 2U, P25_TERM_FRAME_LENGTH_BITS, true, true);
|
|
else
|
|
// Add busy bits, inbound busy
|
|
addBusyBits(buffer + 2U, P25_TERM_FRAME_LENGTH_BITS, false, true);
|
|
|
|
writeQueueNet(buffer, P25_TERM_FRAME_LENGTH_BYTES + 2U);
|
|
|
|
unsigned int dstId = m_netData.getDstId();
|
|
unsigned int srcId = m_netData.getSrcId();
|
|
std::string source = m_lookup->find(srcId);
|
|
|
|
LogMessage("P25, network end of transmission from %s to %s%u, %.1f seconds, %u%% packet loss", source.c_str(), m_netData.getLCF() == P25_LCF_GROUP ? "TG " : "", dstId, float(m_netFrames) / 50.0F, (m_netLost * 100U) / m_netFrames);
|
|
writeJSONNet("end", float(m_netFrames) / 50.0F, float(m_netLost * 100U) / float(m_netFrames));
|
|
|
|
m_netTimeout.stop();
|
|
m_networkWatchdog.stop();
|
|
m_netData.reset();
|
|
m_netState = RPT_NET_STATE::IDLE;
|
|
}
|
|
|
|
bool CP25Control::isBusy() const
|
|
{
|
|
return (m_rfState != RPT_RF_STATE::LISTENING) || (m_netState != RPT_NET_STATE::IDLE);
|
|
}
|
|
|
|
void CP25Control::enable(bool enabled)
|
|
{
|
|
if (!enabled && m_enabled) {
|
|
m_queue.clear();
|
|
|
|
// Reset the RF section
|
|
m_rfState = RPT_RF_STATE::LISTENING;
|
|
m_rfTimeout.stop();
|
|
m_rfData.reset();
|
|
|
|
// Reset the networking section
|
|
m_netTimeout.stop();
|
|
m_networkWatchdog.stop();
|
|
m_netData.reset();
|
|
m_netState = RPT_NET_STATE::IDLE;
|
|
}
|
|
|
|
m_enabled = enabled;
|
|
}
|
|
|
|
void CP25Control::writeJSONRSSI()
|
|
{
|
|
if (m_rssi == 0)
|
|
return;
|
|
|
|
if (m_rssiCount >= RSSI_COUNT) {
|
|
nlohmann::json json;
|
|
|
|
json["timestamp"] = CUtils::createTimestamp();
|
|
json["mode"] = "P25";
|
|
|
|
json["value"] = m_rssiAccum / int(m_rssiCount);
|
|
|
|
WriteJSON("RSSI", json);
|
|
|
|
m_rssiAccum = 0;
|
|
m_rssiCount = 0U;
|
|
}
|
|
}
|
|
|
|
void CP25Control::writeJSONBER()
|
|
{
|
|
if (m_bitsCount >= BER_COUNT) {
|
|
nlohmann::json json;
|
|
|
|
json["timestamp"] = CUtils::createTimestamp();
|
|
json["mode"] = "P25";
|
|
|
|
json["value"] = float(m_bitErrsAccum * 100U) / float(m_bitsCount);
|
|
|
|
WriteJSON("BER", json);
|
|
|
|
m_bitErrsAccum = 0U;
|
|
m_bitsCount = 1U;
|
|
}
|
|
}
|
|
|
|
void CP25Control::writeJSONRF(const char* action, unsigned int srcId, const std::string& srcInfo, bool grp, unsigned int dstId)
|
|
{
|
|
assert(action != nullptr);
|
|
|
|
nlohmann::json json;
|
|
|
|
writeJSON(json, "rf", action, srcId, srcInfo, grp, dstId);
|
|
|
|
WriteJSON("P25", json);
|
|
}
|
|
|
|
void CP25Control::writeJSONRF(const char* action, float duration, float ber)
|
|
{
|
|
assert(action != nullptr);
|
|
|
|
nlohmann::json json;
|
|
|
|
writeJSON(json, action);
|
|
|
|
json["duration"] = duration;
|
|
json["ber"] = ber;
|
|
|
|
WriteJSON("P25", json);
|
|
}
|
|
|
|
void CP25Control::writeJSONRF(const char* action, float duration, float ber, int minRSSI, int maxRSSI, int aveRSSI)
|
|
{
|
|
assert(action != nullptr);
|
|
|
|
nlohmann::json json;
|
|
|
|
writeJSON(json, action);
|
|
|
|
json["duration"] = duration;
|
|
json["ber"] = ber;
|
|
|
|
nlohmann::json rssi;
|
|
rssi["min"] = minRSSI;
|
|
rssi["max"] = maxRSSI;
|
|
rssi["ave"] = aveRSSI;
|
|
|
|
json["rssi"] = rssi;
|
|
|
|
WriteJSON("P25", json);
|
|
}
|
|
|
|
void CP25Control::writeJSONNet(const char* action, unsigned int srcId, const std::string& srcInfo, bool grp, unsigned int dstId)
|
|
{
|
|
assert(action != nullptr);
|
|
|
|
nlohmann::json json;
|
|
|
|
writeJSON(json, "network", action, srcId, srcInfo, grp, dstId);
|
|
|
|
WriteJSON("P25", json);
|
|
}
|
|
|
|
void CP25Control::writeJSONNet(const char* action, float duration, float loss)
|
|
{
|
|
assert(action != nullptr);
|
|
|
|
nlohmann::json json;
|
|
|
|
writeJSON(json, action);
|
|
|
|
json["duration"] = duration;
|
|
json["loss"] = loss;
|
|
|
|
WriteJSON("P25", json);
|
|
}
|
|
|
|
void CP25Control::writeJSON(nlohmann::json& json, const char* action)
|
|
{
|
|
assert(action != nullptr);
|
|
|
|
json["timestamp"] = CUtils::createTimestamp();
|
|
json["action"] = action;
|
|
}
|
|
|
|
void CP25Control::writeJSON(nlohmann::json& json, const char* source, const char* action, unsigned int srcId, const std::string& srcInfo, bool grp, unsigned int dstId)
|
|
{
|
|
assert(source != nullptr);
|
|
assert(action != nullptr);
|
|
|
|
json["timestamp"] = CUtils::createTimestamp();
|
|
json["source"] = source;
|
|
json["action"] = action;
|
|
json["source_id"] = int(srcId);
|
|
json["destination_id"] = int(dstId);
|
|
json["destination_type"] = grp ? "group" : "individual";
|
|
|
|
json["source_info"] = srcInfo;
|
|
}
|
|
|
|
#endif
|
|
|