b2844bc0a6
FEC recalculation always reports errors for null AMBE frames, and there is no DTMF present by definition. In practice, null AMBE data is often (always?) sent by the Kenwood TH-D74 in the first 21 voice frames, and I've also seen it at the end of a fast data transmission.
1289 lines
34 KiB
C++
1289 lines
34 KiB
C++
/*
|
|
* Copyright (C) 2015-2019 Jonathan Naylor, G4KLX
|
|
*
|
|
* 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; version 2 of the License.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include "DStarControl.h"
|
|
#include "Utils.h"
|
|
#include "Sync.h"
|
|
#include "Log.h"
|
|
|
|
#include <cstdio>
|
|
#include <cassert>
|
|
#include <ctime>
|
|
#include <algorithm>
|
|
#include <functional>
|
|
|
|
const unsigned int MAX_SYNC_BIT_ERRORS = 2U;
|
|
const unsigned int FAST_DATA_BEEP_GRACE_FRAMES = 6U;
|
|
|
|
bool CallsignCompare(const std::string& arg, const unsigned char* my)
|
|
{
|
|
for (unsigned int i = 0U; i < (DSTAR_LONG_CALLSIGN_LENGTH - 1U); i++) {
|
|
if (arg.at(i) != my[i])
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// #define DUMP_DSTAR
|
|
|
|
CDStarControl::CDStarControl(const std::string& callsign, const std::string& module, bool selfOnly, bool ackReply, unsigned int ackTime, bool ackMessage, bool errorReply, const std::vector<std::string>& blackList, const std::vector<std::string>& whiteList, CDStarNetwork* network, CDisplay* display, unsigned int timeout, bool duplex, bool remoteGateway, CRSSIInterpolator* rssiMapper) :
|
|
m_callsign(NULL),
|
|
m_gateway(NULL),
|
|
m_selfOnly(selfOnly),
|
|
m_ackReply(ackReply),
|
|
m_ackMessage(ackMessage),
|
|
m_errorReply(errorReply),
|
|
m_remoteGateway(remoteGateway),
|
|
m_blackList(blackList),
|
|
m_whiteList(whiteList),
|
|
m_network(network),
|
|
m_display(display),
|
|
m_duplex(duplex),
|
|
m_queue(5000U, "D-Star Control"),
|
|
m_rfHeader(),
|
|
m_netHeader(),
|
|
m_rfState(RS_RF_LISTENING),
|
|
m_netState(RS_NET_IDLE),
|
|
m_net(false),
|
|
m_slowData(),
|
|
m_rfN(0U),
|
|
m_netN(0U),
|
|
m_networkWatchdog(1000U, 0U, 1500U),
|
|
m_rfTimeoutTimer(1000U, timeout),
|
|
m_netTimeoutTimer(1000U, timeout),
|
|
m_packetTimer(1000U, 0U, 300U),
|
|
m_ackTimer(1000U, 0U, ackTime),
|
|
m_errTimer(1000U, 0U, ackTime),
|
|
m_interval(),
|
|
m_elapsed(),
|
|
m_rfFrames(0U),
|
|
m_netFrames(0U),
|
|
m_netLost(0U),
|
|
m_fec(),
|
|
m_rfBits(1U),
|
|
m_netBits(1U),
|
|
m_rfErrs(0U),
|
|
m_netErrs(0U),
|
|
m_lastFrame(NULL),
|
|
m_lastFrameValid(false),
|
|
m_rssiMapper(rssiMapper),
|
|
m_rssi(0U),
|
|
m_maxRSSI(0U),
|
|
m_minRSSI(0U),
|
|
m_aveRSSI(0U),
|
|
m_rssiCount(0U),
|
|
m_enabled(true),
|
|
m_fp(NULL),
|
|
m_rfVoiceSyncData(NULL),
|
|
m_rfVoiceSyncDataLen(0U),
|
|
m_netVoiceSyncData(NULL),
|
|
m_netVoiceSyncDataLen(0U),
|
|
m_rfNextFrameIsFastData(false),
|
|
m_netNextFrameIsFastData(false),
|
|
m_rfSkipDTMFBlankingFrames(0U),
|
|
m_netSkipDTMFBlankingFrames(0U)
|
|
{
|
|
assert(display != NULL);
|
|
assert(rssiMapper != NULL);
|
|
|
|
m_callsign = new unsigned char[DSTAR_LONG_CALLSIGN_LENGTH];
|
|
m_gateway = new unsigned char[DSTAR_LONG_CALLSIGN_LENGTH];
|
|
|
|
m_lastFrame = new unsigned char[DSTAR_FRAME_LENGTH_BYTES + 1U];
|
|
m_rfVoiceSyncData = new unsigned char[MODEM_DATA_LEN];
|
|
m_netVoiceSyncData = new unsigned char[MODEM_DATA_LEN];
|
|
|
|
std::string call = callsign;
|
|
call.resize(DSTAR_LONG_CALLSIGN_LENGTH - 1U, ' ');
|
|
std::string mod = module;
|
|
mod.resize(1U, ' ');
|
|
call.append(mod);
|
|
|
|
std::string gate = callsign;
|
|
gate.resize(DSTAR_LONG_CALLSIGN_LENGTH - 1U, ' ');
|
|
gate.append("G");
|
|
|
|
for (unsigned int i = 0U; i < DSTAR_LONG_CALLSIGN_LENGTH; i++) {
|
|
m_callsign[i] = call.at(i);
|
|
m_gateway[i] = gate.at(i);
|
|
}
|
|
|
|
m_interval.start();
|
|
}
|
|
|
|
CDStarControl::~CDStarControl()
|
|
{
|
|
delete[] m_callsign;
|
|
delete[] m_gateway;
|
|
delete[] m_lastFrame;
|
|
delete[] m_rfVoiceSyncData;
|
|
delete[] m_netVoiceSyncData;
|
|
}
|
|
|
|
unsigned int CDStarControl::maybeFixupVoiceFrame(
|
|
unsigned char* data,
|
|
unsigned int len,
|
|
unsigned int offset,
|
|
const char* log_prefix,
|
|
unsigned char n,
|
|
bool blank_dtmf,
|
|
unsigned char* voice_sync_data,
|
|
unsigned int* voice_sync_data_len,
|
|
bool* next_frame_is_fast_data,
|
|
unsigned int* skip_dtmf_blanking_frames
|
|
)
|
|
{
|
|
unsigned int errors = 0U;
|
|
unsigned char mini_header = data[offset + 9U] ^ DSTAR_SCRAMBLER_BYTES[0U];
|
|
unsigned char mini_header_type = mini_header & DSTAR_SLOW_DATA_TYPE_MASK;
|
|
|
|
if (n == 0U) {
|
|
LogMessage("%s frame %u: delaying FEC and DTMF processing of first voice frame", log_prefix, n);
|
|
::memcpy(voice_sync_data, data, MODEM_DATA_LEN);
|
|
*voice_sync_data_len = len;
|
|
} else if ((n % 2U != 0U) &&
|
|
((mini_header_type == DSTAR_SLOW_DATA_TYPE_FASTDATA01) ||
|
|
(mini_header_type == DSTAR_SLOW_DATA_TYPE_FASTDATA16))) {
|
|
*next_frame_is_fast_data = true;
|
|
if (blank_dtmf)
|
|
*skip_dtmf_blanking_frames = FAST_DATA_BEEP_GRACE_FRAMES;
|
|
LogMessage("%s frame %u: found fast data", log_prefix, n);
|
|
} else if (*next_frame_is_fast_data == true) {
|
|
*next_frame_is_fast_data = false;
|
|
if (blank_dtmf)
|
|
*skip_dtmf_blanking_frames = FAST_DATA_BEEP_GRACE_FRAMES;
|
|
LogMessage("%s frame %u: found fast data (cont.)", log_prefix, n);
|
|
} else {
|
|
bool voice_sync_data_is_null_ambe_data = false;
|
|
bool data_is_null_ambe_data = false;
|
|
if ((n == 1U) && (::memcmp(voice_sync_data + offset, DSTAR_NULL_AMBE_DATA_BYTES_SCRAMBLED, DSTAR_VOICE_FRAME_LENGTH_BYTES) == 0)) {
|
|
LogMessage("%s frame 0: *** Null AMBE data detected in voice frame ***", log_prefix);
|
|
voice_sync_data_is_null_ambe_data = true;
|
|
}
|
|
if (::memcmp(data + offset, DSTAR_NULL_AMBE_DATA_BYTES_SCRAMBLED, DSTAR_VOICE_FRAME_LENGTH_BYTES) == 0) {
|
|
LogMessage("%s frame %u: *** Null AMBE data detected in voice frame ***", log_prefix, n);
|
|
data_is_null_ambe_data = true;
|
|
}
|
|
|
|
if ((n == 1U) && !voice_sync_data_is_null_ambe_data) {
|
|
LogMessage("%s frame 0: *** REGENERATING FEC ***", log_prefix);
|
|
errors += m_fec.regenerateDStar(voice_sync_data + offset);
|
|
}
|
|
if (!data_is_null_ambe_data) {
|
|
LogMessage("%s frame %u: *** REGENERATING FEC ***", log_prefix, n);
|
|
errors += m_fec.regenerateDStar(data + offset);
|
|
}
|
|
|
|
if (blank_dtmf && (*skip_dtmf_blanking_frames > 0U)) {
|
|
(*skip_dtmf_blanking_frames)--;
|
|
if (n == 1U)
|
|
LogMessage("%s frame 0: *** Not BLANKING DTMF (left to skip: %u) ***",
|
|
log_prefix, *skip_dtmf_blanking_frames);
|
|
LogMessage("%s frame %u: *** Not BLANKING DTMF (left to skip: %u) ***",
|
|
log_prefix, n, *skip_dtmf_blanking_frames);
|
|
} else if (blank_dtmf && (*skip_dtmf_blanking_frames == 0U)) {
|
|
if ((n == 1U) && !voice_sync_data_is_null_ambe_data) {
|
|
LogMessage("%s frame 0: *** BLANKING DTMF ***", log_prefix);
|
|
blankDTMF(voice_sync_data + offset);
|
|
}
|
|
if (!data_is_null_ambe_data) {
|
|
LogMessage("%s frame %u: *** BLANKING DTMF ***", log_prefix, n);
|
|
blankDTMF(data + offset);
|
|
}
|
|
}
|
|
}
|
|
|
|
return errors;
|
|
}
|
|
|
|
bool CDStarControl::writeModem(unsigned char *data, unsigned int len)
|
|
{
|
|
assert(data != NULL);
|
|
|
|
if (!m_enabled)
|
|
return false;
|
|
|
|
unsigned char type = data[0U];
|
|
|
|
if (type == TAG_LOST && m_rfState == RS_RF_AUDIO) {
|
|
unsigned char my1[DSTAR_LONG_CALLSIGN_LENGTH];
|
|
unsigned char my2[DSTAR_SHORT_CALLSIGN_LENGTH];
|
|
unsigned char your[DSTAR_LONG_CALLSIGN_LENGTH];
|
|
m_rfHeader.getMyCall1(my1);
|
|
m_rfHeader.getMyCall2(my2);
|
|
m_rfHeader.getYourCall(your);
|
|
|
|
if (m_rssi != 0U)
|
|
LogMessage("D-Star, transmission lost from %8.8s/%4.4s to %8.8s, %.1f seconds, BER: %.1f%%, RSSI: -%u/-%u/-%u dBm", my1, my2, your, float(m_rfFrames) / 50.0F, float(m_rfErrs * 100U) / float(m_rfBits), m_minRSSI, m_maxRSSI, m_aveRSSI / m_rssiCount);
|
|
else
|
|
LogMessage("D-Star, transmission lost from %8.8s/%4.4s to %8.8s, %.1f seconds, BER: %.1f%%", my1, my2, your, float(m_rfFrames) / 50.0F, float(m_rfErrs * 100U) / float(m_rfBits));
|
|
writeEndRF();
|
|
return false;
|
|
}
|
|
|
|
if (type == TAG_LOST && m_rfState == RS_RF_INVALID) {
|
|
m_rfState = RS_RF_LISTENING;
|
|
|
|
if (m_netState == RS_NET_IDLE) {
|
|
if (m_errorReply)
|
|
m_errTimer.start();
|
|
|
|
if (m_network != NULL)
|
|
m_network->reset();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if (type == TAG_LOST) {
|
|
m_rfState = RS_RF_LISTENING;
|
|
return false;
|
|
}
|
|
|
|
// Have we got RSSI bytes on the end of a D-Star header?
|
|
if (len == (DSTAR_HEADER_LENGTH_BYTES + 3U)) {
|
|
uint16_t raw = 0U;
|
|
raw |= (data[42U] << 8) & 0xFF00U;
|
|
raw |= (data[43U] << 0) & 0x00FFU;
|
|
|
|
// Convert the raw RSSI to dBm
|
|
int rssi = m_rssiMapper->interpolate(raw);
|
|
if (rssi != 0)
|
|
LogDebug("D-Star, raw RSSI: %u, reported RSSI: %d dBm", raw, rssi);
|
|
|
|
// RSSI is always reported as positive
|
|
m_rssi = (rssi >= 0) ? rssi : -rssi;
|
|
|
|
if (m_rssi > m_minRSSI)
|
|
m_minRSSI = m_rssi;
|
|
if (m_rssi < m_maxRSSI)
|
|
m_maxRSSI = m_rssi;
|
|
|
|
m_aveRSSI += m_rssi;
|
|
m_rssiCount++;
|
|
}
|
|
|
|
// Have we got RSSI bytes on the end of D-Star data?
|
|
if (len == (DSTAR_FRAME_LENGTH_BYTES + 3U)) {
|
|
uint16_t raw = 0U;
|
|
raw |= (data[13U] << 8) & 0xFF00U;
|
|
raw |= (data[14U] << 0) & 0x00FFU;
|
|
|
|
// Convert the raw RSSI to dBm
|
|
int rssi = m_rssiMapper->interpolate(raw);
|
|
if (rssi != 0)
|
|
LogDebug("D-Star, raw RSSI: %u, reported RSSI: %d dBm", raw, rssi);
|
|
|
|
// RSSI is always reported as positive
|
|
m_rssi = (rssi >= 0) ? rssi : -rssi;
|
|
|
|
if (m_rssi > m_minRSSI)
|
|
m_minRSSI = m_rssi;
|
|
if (m_rssi < m_maxRSSI)
|
|
m_maxRSSI = m_rssi;
|
|
|
|
m_aveRSSI += m_rssi;
|
|
m_rssiCount++;
|
|
}
|
|
|
|
if (type == TAG_HEADER) {
|
|
CDStarHeader header(data + 1U);
|
|
m_rfHeader = header;
|
|
|
|
unsigned char my1[DSTAR_LONG_CALLSIGN_LENGTH];
|
|
header.getMyCall1(my1);
|
|
|
|
// Is this a transmission destined for a repeater?
|
|
if (!header.isRepeater()) {
|
|
LogMessage("D-Star, non repeater RF header received from %8.8s", my1);
|
|
m_rfState = RS_RF_INVALID;
|
|
return false;
|
|
}
|
|
|
|
unsigned char callsign[DSTAR_LONG_CALLSIGN_LENGTH];
|
|
header.getRPTCall1(callsign);
|
|
|
|
// Is it for us?
|
|
if (::memcmp(callsign, m_callsign, DSTAR_LONG_CALLSIGN_LENGTH) != 0) {
|
|
LogMessage("D-Star, received RF header for wrong repeater (%8.8s) from %8.8s", callsign, my1);
|
|
m_rfState = RS_RF_INVALID;
|
|
return false;
|
|
}
|
|
|
|
if (m_selfOnly && ::memcmp(my1, m_callsign, DSTAR_LONG_CALLSIGN_LENGTH - 1U) != 0 && !(std::find_if(m_whiteList.begin(), m_whiteList.end(), std::bind(CallsignCompare, std::placeholders::_1, my1)) != m_whiteList.end())) {
|
|
LogMessage("D-Star, invalid access attempt from %8.8s", my1);
|
|
m_rfState = RS_RF_REJECTED;
|
|
return false;
|
|
}
|
|
|
|
if (!m_selfOnly && std::find_if(m_blackList.begin(), m_blackList.end(), std::bind(CallsignCompare, std::placeholders::_1, my1)) != m_blackList.end()) {
|
|
LogMessage("D-Star, invalid access attempt from %8.8s", my1);
|
|
m_rfState = RS_RF_REJECTED;
|
|
return false;
|
|
}
|
|
|
|
unsigned char gateway[DSTAR_LONG_CALLSIGN_LENGTH];
|
|
header.getRPTCall2(gateway);
|
|
|
|
unsigned char my2[DSTAR_SHORT_CALLSIGN_LENGTH];
|
|
header.getMyCall2(my2);
|
|
|
|
unsigned char your[DSTAR_LONG_CALLSIGN_LENGTH];
|
|
header.getYourCall(your);
|
|
|
|
m_net = ::memcmp(gateway, m_gateway, DSTAR_LONG_CALLSIGN_LENGTH) == 0;
|
|
|
|
// Only start the timeout if not already running
|
|
if (!m_rfTimeoutTimer.isRunning())
|
|
m_rfTimeoutTimer.start();
|
|
|
|
m_ackTimer.stop();
|
|
m_errTimer.stop();
|
|
|
|
m_rfBits = 1U;
|
|
m_rfErrs = 0U;
|
|
|
|
m_rfFrames = 1U;
|
|
m_rfN = 0U;
|
|
|
|
m_minRSSI = m_rssi;
|
|
m_maxRSSI = m_rssi;
|
|
m_aveRSSI = m_rssi;
|
|
m_rssiCount = 1U;
|
|
|
|
if (m_duplex) {
|
|
// Modify the header
|
|
header.setRepeater(false);
|
|
header.setRPTCall1(m_callsign);
|
|
header.setRPTCall2(m_callsign);
|
|
header.get(data + 1U);
|
|
|
|
writeQueueHeaderRF(data);
|
|
}
|
|
|
|
if (m_net) {
|
|
// Modify the header
|
|
header.setRepeater(false);
|
|
header.setRPTCall1(m_callsign);
|
|
header.setRPTCall2(m_gateway);
|
|
header.get(data + 1U);
|
|
|
|
writeNetworkHeaderRF(data);
|
|
}
|
|
|
|
m_rfState = RS_RF_AUDIO;
|
|
|
|
if (m_netState == RS_NET_IDLE) {
|
|
m_display->writeDStar((char*)my1, (char*)my2, (char*)your, "R", " ");
|
|
m_display->writeDStarRSSI(m_rssi);
|
|
}
|
|
|
|
LogMessage("D-Star, received RF header from %8.8s/%4.4s to %8.8s", my1, my2, your);
|
|
} else if (type == TAG_EOT) {
|
|
if (m_rfState == RS_RF_REJECTED) {
|
|
m_rfState = RS_RF_LISTENING;
|
|
} else if (m_rfState == RS_RF_INVALID) {
|
|
m_rfState = RS_RF_LISTENING;
|
|
|
|
if (m_netState == RS_NET_IDLE) {
|
|
if (m_errorReply)
|
|
m_errTimer.start();
|
|
|
|
if (m_network != NULL)
|
|
m_network->reset();
|
|
}
|
|
|
|
return false;
|
|
} else if (m_rfState == RS_RF_AUDIO) {
|
|
if (m_net)
|
|
writeNetworkDataRF(DSTAR_END_PATTERN_BYTES, 0U, true);
|
|
|
|
if (m_duplex)
|
|
writeQueueEOTRF();
|
|
|
|
m_rfNextFrameIsFastData = false;
|
|
m_rfSkipDTMFBlankingFrames = 0U;
|
|
|
|
unsigned char my1[DSTAR_LONG_CALLSIGN_LENGTH];
|
|
unsigned char my2[DSTAR_SHORT_CALLSIGN_LENGTH];
|
|
unsigned char your[DSTAR_LONG_CALLSIGN_LENGTH];
|
|
m_rfHeader.getMyCall1(my1);
|
|
m_rfHeader.getMyCall2(my2);
|
|
m_rfHeader.getYourCall(your);
|
|
|
|
if (m_rssi != 0U)
|
|
LogMessage("D-Star, received RF end of transmission from %8.8s/%4.4s to %8.8s, %.1f seconds, BER: %.1f%%, RSSI: -%u/-%u/-%u dBm", my1, my2, your, float(m_rfFrames) / 50.0F, float(m_rfErrs * 100U) / float(m_rfBits), m_minRSSI, m_maxRSSI, m_aveRSSI / m_rssiCount);
|
|
else
|
|
LogMessage("D-Star, received RF end of transmission from %8.8s/%4.4s to %8.8s, %.1f seconds, BER: %.1f%%", my1, my2, your, float(m_rfFrames) / 50.0F, float(m_rfErrs * 100U) / float(m_rfBits));
|
|
|
|
writeEndRF();
|
|
}
|
|
|
|
return false;
|
|
} else if (type == TAG_DATA) {
|
|
if (m_rfState == RS_RF_REJECTED) {
|
|
return false;
|
|
} else if (m_rfState == RS_RF_INVALID) {
|
|
return false;
|
|
} else if (m_rfState == RS_RF_LISTENING) {
|
|
// The sync is regenerated by the modem so can do exact match
|
|
if (::memcmp(data + 1U + DSTAR_VOICE_FRAME_LENGTH_BYTES, DSTAR_SYNC_BYTES, DSTAR_DATA_FRAME_LENGTH_BYTES) == 0) {
|
|
m_slowData.start();
|
|
m_rfState = RS_RF_LATE_ENTRY;
|
|
}
|
|
|
|
return false;
|
|
} else if (m_rfState == RS_RF_AUDIO) {
|
|
// The sync is regenerated by the modem so can do exact match
|
|
if (::memcmp(data + 1U + DSTAR_VOICE_FRAME_LENGTH_BYTES, DSTAR_SYNC_BYTES, DSTAR_DATA_FRAME_LENGTH_BYTES) == 0)
|
|
m_rfN = 0U;
|
|
|
|
// Regenerate the sync and send the RSSI data to the display
|
|
if (m_rfN == 0U) {
|
|
CSync::addDStarSync(data + 1U);
|
|
m_display->writeDStarRSSI(m_rssi);
|
|
}
|
|
|
|
unsigned int errors = 0U;
|
|
if (!m_rfHeader.isDataPacket()) {
|
|
errors = maybeFixupVoiceFrame(data, len, 1U, "RF", m_rfN, m_duplex, m_rfVoiceSyncData, &m_rfVoiceSyncDataLen,
|
|
&m_rfNextFrameIsFastData, &m_rfSkipDTMFBlankingFrames);
|
|
m_display->writeDStarBER(float(errors) / 0.48F);
|
|
LogDebug("D-Star, audio sequence no. %u, errs: %u/48 (%.1f%%)", m_rfN, errors, float(errors) / 0.48F);
|
|
m_rfErrs += errors;
|
|
}
|
|
|
|
m_rfBits += 48U;
|
|
m_rfFrames++;
|
|
|
|
if (m_net) {
|
|
if (m_rfN == 1U)
|
|
writeNetworkDataRF(m_rfVoiceSyncData, 0U, false);
|
|
if (m_rfN >= 1U)
|
|
writeNetworkDataRF(data, errors, false);
|
|
}
|
|
|
|
if (m_duplex) {
|
|
if (m_rfN == 1U)
|
|
writeQueueDataRF(m_rfVoiceSyncData);
|
|
if (m_rfN >= 1U)
|
|
writeQueueDataRF(data);
|
|
}
|
|
|
|
m_rfN = (m_rfN + 1U) % 21U;
|
|
} else if (m_rfState == RS_RF_LATE_ENTRY) {
|
|
// The sync is regenerated by the modem so can do exact match
|
|
if (::memcmp(data + 1U + DSTAR_VOICE_FRAME_LENGTH_BYTES, DSTAR_SYNC_BYTES, DSTAR_DATA_FRAME_LENGTH_BYTES) == 0) {
|
|
m_slowData.reset();
|
|
return false;
|
|
}
|
|
|
|
CDStarHeader* header = m_slowData.add(data + 1U);
|
|
if (header == NULL)
|
|
return false;
|
|
|
|
m_rfHeader = *header;
|
|
|
|
unsigned char my1[DSTAR_LONG_CALLSIGN_LENGTH];
|
|
header->getMyCall1(my1);
|
|
|
|
// Is this a transmission destined for a repeater?
|
|
if (!header->isRepeater()) {
|
|
LogMessage("D-Star, non repeater RF header received from %8.8s", my1);
|
|
m_rfState = RS_RF_INVALID;
|
|
delete header;
|
|
return false;
|
|
}
|
|
|
|
unsigned char callsign[DSTAR_LONG_CALLSIGN_LENGTH];
|
|
header->getRPTCall1(callsign);
|
|
|
|
// Is it for us?
|
|
if (::memcmp(callsign, m_callsign, DSTAR_LONG_CALLSIGN_LENGTH) != 0) {
|
|
LogMessage("D-Star, received RF header for wrong repeater (%8.8s) from %8.8s", callsign, my1);
|
|
m_rfState = RS_RF_INVALID;
|
|
delete header;
|
|
return false;
|
|
}
|
|
|
|
if (m_selfOnly && ::memcmp(my1, m_callsign, DSTAR_LONG_CALLSIGN_LENGTH - 1U) != 0 && !(std::find_if(m_whiteList.begin(), m_whiteList.end(), std::bind(CallsignCompare, std::placeholders::_1, my1)) != m_whiteList.end())) {
|
|
LogMessage("D-Star, invalid access attempt from %8.8s", my1);
|
|
m_rfState = RS_RF_REJECTED;
|
|
delete header;
|
|
return false;
|
|
}
|
|
|
|
if (!m_selfOnly && std::find_if(m_blackList.begin(), m_blackList.end(), std::bind(CallsignCompare, std::placeholders::_1, my1)) != m_blackList.end()) {
|
|
LogMessage("D-Star, invalid access attempt from %8.8s", my1);
|
|
m_rfState = RS_RF_REJECTED;
|
|
delete header;
|
|
return false;
|
|
}
|
|
|
|
unsigned char gateway[DSTAR_LONG_CALLSIGN_LENGTH];
|
|
header->getRPTCall2(gateway);
|
|
|
|
unsigned char my2[DSTAR_SHORT_CALLSIGN_LENGTH];
|
|
header->getMyCall2(my2);
|
|
|
|
unsigned char your[DSTAR_LONG_CALLSIGN_LENGTH];
|
|
header->getYourCall(your);
|
|
|
|
m_net = ::memcmp(gateway, m_gateway, DSTAR_LONG_CALLSIGN_LENGTH) == 0;
|
|
|
|
// Only reset the timeout if the timeout is not running
|
|
if (!m_rfTimeoutTimer.isRunning())
|
|
m_rfTimeoutTimer.start();
|
|
|
|
// Create a dummy start frame to replace the received frame
|
|
m_ackTimer.stop();
|
|
m_errTimer.stop();
|
|
|
|
m_rfBits = 1U;
|
|
m_rfErrs = 0U;
|
|
|
|
m_rfN = 0U;
|
|
m_rfFrames = 1U;
|
|
|
|
m_minRSSI = m_rssi;
|
|
m_maxRSSI = m_rssi;
|
|
m_aveRSSI = m_rssi;
|
|
m_rssiCount = 1U;
|
|
|
|
if (m_duplex) {
|
|
unsigned char start[DSTAR_HEADER_LENGTH_BYTES + 1U];
|
|
start[0U] = TAG_HEADER;
|
|
|
|
// Modify the header
|
|
header->setRepeater(false);
|
|
header->setRPTCall1(m_callsign);
|
|
header->setRPTCall2(m_callsign);
|
|
header->get(start + 1U);
|
|
|
|
writeQueueHeaderRF(start);
|
|
}
|
|
|
|
if (m_net) {
|
|
unsigned char start[DSTAR_HEADER_LENGTH_BYTES + 1U];
|
|
start[0U] = TAG_HEADER;
|
|
|
|
// Modify the header
|
|
header->setRepeater(false);
|
|
header->setRPTCall1(m_callsign);
|
|
header->setRPTCall2(m_gateway);
|
|
header->get(start + 1U);
|
|
|
|
writeNetworkHeaderRF(start);
|
|
}
|
|
|
|
delete header;
|
|
|
|
unsigned int errors = 0U;
|
|
if (!m_rfHeader.isDataPacket()) {
|
|
errors = maybeFixupVoiceFrame(data, len, 1U, "RF", m_rfN, m_duplex, m_rfVoiceSyncData, &m_rfVoiceSyncDataLen,
|
|
&m_rfNextFrameIsFastData, &m_rfSkipDTMFBlankingFrames);
|
|
LogDebug("D-Star, audio sequence no. %u, errs: %u/48 (%.1f%%)", m_rfN, errors, float(errors) / 0.48F);
|
|
m_rfErrs += errors;
|
|
}
|
|
|
|
m_rfBits += 48U;
|
|
|
|
if (m_net)
|
|
writeNetworkDataRF(data, errors, false);
|
|
|
|
if (m_duplex)
|
|
writeQueueDataRF(data);
|
|
|
|
m_rfState = RS_RF_AUDIO;
|
|
|
|
m_rfN = (m_rfN + 1U) % 21U;
|
|
|
|
if (m_netState == RS_NET_IDLE) {
|
|
m_display->writeDStar((char*)my1, (char*)my2, (char*)your, "R", " ");
|
|
m_display->writeDStarRSSI(m_rssi);
|
|
m_display->writeDStarBER(float(errors) / 0.48F);
|
|
}
|
|
|
|
LogMessage("D-Star, received RF late entry from %8.8s/%4.4s to %8.8s", my1, my2, your);
|
|
}
|
|
} else {
|
|
CUtils::dump("D-Star, unknown data from modem", data, DSTAR_FRAME_LENGTH_BYTES + 1U);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
unsigned int CDStarControl::readModem(unsigned char* data)
|
|
{
|
|
assert(data != NULL);
|
|
|
|
if (m_queue.isEmpty())
|
|
return 0U;
|
|
|
|
unsigned char len = 0U;
|
|
m_queue.getData(&len, 1U);
|
|
|
|
m_queue.getData(data, len);
|
|
|
|
return len;
|
|
}
|
|
|
|
void CDStarControl::writeEndRF()
|
|
{
|
|
m_rfState = RS_RF_LISTENING;
|
|
|
|
if (m_netState == RS_NET_IDLE) {
|
|
m_display->clearDStar();
|
|
|
|
m_ackTimer.start();
|
|
|
|
if (m_network != NULL)
|
|
m_network->reset();
|
|
} else {
|
|
m_rfTimeoutTimer.stop();
|
|
}
|
|
}
|
|
|
|
void CDStarControl::writeEndNet()
|
|
{
|
|
m_netState = RS_NET_IDLE;
|
|
|
|
m_lastFrameValid = false;
|
|
|
|
m_display->clearDStar();
|
|
|
|
m_netTimeoutTimer.stop();
|
|
m_networkWatchdog.stop();
|
|
m_packetTimer.stop();
|
|
|
|
if (m_network != NULL)
|
|
m_network->reset();
|
|
|
|
#if defined(DUMP_DSTAR)
|
|
closeFile();
|
|
#endif
|
|
}
|
|
|
|
void CDStarControl::writeNetwork()
|
|
{
|
|
assert(m_network != NULL);
|
|
|
|
unsigned char data[DSTAR_HEADER_LENGTH_BYTES + 2U];
|
|
unsigned int length = m_network->read(data, DSTAR_HEADER_LENGTH_BYTES + 2U);
|
|
if (length == 0U)
|
|
return;
|
|
|
|
if (!m_enabled)
|
|
return;
|
|
|
|
if (m_rfState == RS_RF_AUDIO && m_netState == RS_NET_IDLE)
|
|
return;
|
|
|
|
m_networkWatchdog.start();
|
|
|
|
unsigned char type = data[0U];
|
|
|
|
if (type == TAG_HEADER) {
|
|
if (m_netState != RS_NET_IDLE)
|
|
return;
|
|
|
|
CDStarHeader header(data + 1U);
|
|
|
|
unsigned char my1[DSTAR_LONG_CALLSIGN_LENGTH];
|
|
header.getMyCall1(my1);
|
|
|
|
unsigned char my2[DSTAR_SHORT_CALLSIGN_LENGTH];
|
|
header.getMyCall2(my2);
|
|
|
|
unsigned char your[DSTAR_LONG_CALLSIGN_LENGTH];
|
|
header.getYourCall(your);
|
|
|
|
m_netHeader = header;
|
|
|
|
m_netTimeoutTimer.start();
|
|
m_packetTimer.start();
|
|
m_ackTimer.stop();
|
|
m_errTimer.stop();
|
|
|
|
m_lastFrameValid = false;
|
|
|
|
m_netFrames = 0U;
|
|
m_netLost = 0U;
|
|
|
|
m_netN = 20U;
|
|
|
|
m_netBits = 1U;
|
|
m_netErrs = 0U;
|
|
|
|
if (m_remoteGateway) {
|
|
header.setRepeater(true);
|
|
header.setRPTCall1(m_callsign);
|
|
header.setRPTCall2(m_callsign);
|
|
header.get(data + 1U);
|
|
}
|
|
|
|
writeQueueHeaderNet(data);
|
|
|
|
#if defined(DUMP_DSTAR)
|
|
openFile();
|
|
writeFile(data + 1U, length - 1U);
|
|
#endif
|
|
m_netState = RS_NET_AUDIO;
|
|
|
|
LINK_STATUS status = LS_NONE;
|
|
unsigned char reflector[DSTAR_LONG_CALLSIGN_LENGTH];
|
|
m_network->getStatus(status, reflector);
|
|
if (status == LS_LINKED_DEXTRA || status == LS_LINKED_DPLUS || status == LS_LINKED_DCS || status == LS_LINKED_CCS || status == LS_LINKED_LOOPBACK) {
|
|
m_display->writeDStar((char*)my1, (char*)my2, (char*)your, "N", (char*) reflector);
|
|
LogMessage("D-Star, received network header from %8.8s/%4.4s to %8.8s via %8.8s", my1, my2, your, reflector);
|
|
} else {
|
|
m_display->writeDStar((char*)my1, (char*)my2, (char*)your, "N", (char*) " ");
|
|
LogMessage("D-Star, received network header from %8.8s/%4.4s to %8.8s", my1, my2, your);
|
|
}
|
|
|
|
// Something just above here introduces a large delay forcing erroneous(?) insertion of silence packets.
|
|
// Starting the elapsed timer here instead of the commented out position above solves that.
|
|
m_elapsed.start();
|
|
|
|
} else if (type == TAG_EOT) {
|
|
if (m_netState != RS_NET_AUDIO)
|
|
return;
|
|
|
|
writeQueueEOTNet();
|
|
|
|
data[1U] = TAG_EOT;
|
|
|
|
#if defined(DUMP_DSTAR)
|
|
writeFile(data + 1U, length - 1U);
|
|
closeFile();
|
|
#endif
|
|
m_netNextFrameIsFastData = false;
|
|
m_netSkipDTMFBlankingFrames = 0U;
|
|
|
|
unsigned char my1[DSTAR_LONG_CALLSIGN_LENGTH];
|
|
unsigned char my2[DSTAR_SHORT_CALLSIGN_LENGTH];
|
|
unsigned char your[DSTAR_LONG_CALLSIGN_LENGTH];
|
|
m_netHeader.getMyCall1(my1);
|
|
m_netHeader.getMyCall2(my2);
|
|
m_netHeader.getYourCall(your);
|
|
|
|
// We've received the header and EOT haven't we?
|
|
m_netFrames += 2U;
|
|
LogMessage("D-Star, received network end of transmission from %8.8s/%4.4s to %8.8s, %.1f seconds, %u%% packet loss, BER: %.1f%%", my1, my2, your, float(m_netFrames) / 50.0F, (m_netLost * 100U) / m_netFrames, float(m_netErrs * 100U) / float(m_netBits));
|
|
|
|
writeEndNet();
|
|
} else if (type == TAG_DATA) {
|
|
if (m_netState != RS_NET_AUDIO)
|
|
return;
|
|
|
|
unsigned char n = data[1U];
|
|
|
|
data[1U] = TAG_DATA;
|
|
|
|
unsigned int errors = 0U;
|
|
if (!m_netHeader.isDataPacket())
|
|
errors = maybeFixupVoiceFrame(data, length, 2U, "Net", n, true, m_netVoiceSyncData, &m_netVoiceSyncDataLen,
|
|
&m_netNextFrameIsFastData, &m_netSkipDTMFBlankingFrames);
|
|
|
|
// Insert silence and reject if in the past
|
|
bool ret = insertSilence(data + 1U, n);
|
|
if (!ret)
|
|
return;
|
|
|
|
m_netErrs += errors;
|
|
m_netBits += 48U;
|
|
|
|
m_netN = n;
|
|
|
|
// Regenerate the sync
|
|
if (n == 0U)
|
|
CSync::addDStarSync(data + 2U);
|
|
|
|
m_packetTimer.start();
|
|
m_netFrames++;
|
|
|
|
#if defined(DUMP_DSTAR)
|
|
if (n == 1U)
|
|
writeFile(m_netVoiceSyncData + 1U, m_netVoiceSyncDataLen - 1U);
|
|
if (n >= 1U)
|
|
writeFile(data + 1U, length - 1U);
|
|
#endif
|
|
if (n == 1U)
|
|
writeQueueDataNet(m_netVoiceSyncData + 1U);
|
|
if (n >= 1U)
|
|
writeQueueDataNet(data + 1U);
|
|
} else {
|
|
CUtils::dump("D-Star, unknown data from network", data, DSTAR_FRAME_LENGTH_BYTES + 1U);
|
|
}
|
|
}
|
|
|
|
void CDStarControl::clock()
|
|
{
|
|
unsigned int ms = m_interval.elapsed();
|
|
m_interval.start();
|
|
|
|
if (m_network != NULL)
|
|
writeNetwork();
|
|
|
|
m_ackTimer.clock(ms);
|
|
if (m_ackTimer.isRunning() && m_ackTimer.hasExpired()) {
|
|
sendAck();
|
|
m_ackTimer.stop();
|
|
}
|
|
|
|
m_errTimer.clock(ms);
|
|
if (m_errTimer.isRunning() && m_errTimer.hasExpired()) {
|
|
sendError();
|
|
m_errTimer.stop();
|
|
}
|
|
|
|
m_rfTimeoutTimer.clock(ms);
|
|
m_netTimeoutTimer.clock(ms);
|
|
|
|
if (m_netState == RS_NET_AUDIO) {
|
|
m_networkWatchdog.clock(ms);
|
|
|
|
if (m_networkWatchdog.hasExpired()) {
|
|
// We're received the header haven't we?
|
|
m_netFrames += 1U;
|
|
LogMessage("D-Star, network watchdog has expired, %.1f seconds, %u%% packet loss, BER: %.1f%%", float(m_netFrames) / 50.0F, (m_netLost * 100U) / m_netFrames, float(m_netErrs * 100U) / float(m_netBits));
|
|
writeEndNet();
|
|
#if defined(DUMP_DSTAR)
|
|
closeFile();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// Only insert silence on audio data
|
|
if (m_netState == RS_NET_AUDIO) {
|
|
m_packetTimer.clock(ms);
|
|
|
|
if (m_packetTimer.isRunning() && m_packetTimer.hasExpired()) {
|
|
unsigned int elapsed = m_elapsed.elapsed();
|
|
unsigned int frames = elapsed / DSTAR_FRAME_TIME;
|
|
|
|
if (frames > m_netFrames) {
|
|
unsigned int count = frames - m_netFrames;
|
|
if (count > 15U) {
|
|
LogDebug("D-Star, lost audio for 300ms filling in, elapsed: %ums, expected: %u, received: %u", elapsed, frames, m_netFrames);
|
|
insertSilence(count - 2U);
|
|
}
|
|
}
|
|
|
|
m_packetTimer.start();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CDStarControl::writeQueueHeaderRF(const unsigned char *data)
|
|
{
|
|
assert(data != NULL);
|
|
|
|
if (m_netState != RS_NET_IDLE)
|
|
return;
|
|
|
|
if (m_rfTimeoutTimer.isRunning() && m_rfTimeoutTimer.hasExpired())
|
|
return;
|
|
|
|
unsigned char len = DSTAR_HEADER_LENGTH_BYTES + 1U;
|
|
|
|
unsigned int space = m_queue.freeSpace();
|
|
if (space < (len + 1U)) {
|
|
LogError("D-Star, overflow in the D-Star RF queue");
|
|
return;
|
|
}
|
|
|
|
m_queue.addData(&len, 1U);
|
|
|
|
m_queue.addData(data, len);
|
|
}
|
|
|
|
void CDStarControl::writeQueueDataRF(const unsigned char *data)
|
|
{
|
|
assert(data != NULL);
|
|
|
|
if (m_netState != RS_NET_IDLE)
|
|
return;
|
|
|
|
if (m_rfTimeoutTimer.isRunning() && m_rfTimeoutTimer.hasExpired())
|
|
return;
|
|
|
|
unsigned char len = DSTAR_FRAME_LENGTH_BYTES + 1U;
|
|
|
|
unsigned int space = m_queue.freeSpace();
|
|
if (space < (len + 1U)) {
|
|
LogError("D-Star, overflow in the D-Star RF queue");
|
|
return;
|
|
}
|
|
|
|
m_queue.addData(&len, 1U);
|
|
|
|
m_queue.addData(data, len);
|
|
}
|
|
|
|
void CDStarControl::writeQueueEOTRF()
|
|
{
|
|
if (m_netState != RS_NET_IDLE)
|
|
return;
|
|
|
|
if (m_rfTimeoutTimer.isRunning() && m_rfTimeoutTimer.hasExpired())
|
|
return;
|
|
|
|
unsigned char len = 1U;
|
|
|
|
unsigned int space = m_queue.freeSpace();
|
|
if (space < (len + 1U)) {
|
|
LogError("D-Star, overflow in the D-Star RF queue");
|
|
return;
|
|
}
|
|
|
|
m_queue.addData(&len, 1U);
|
|
|
|
unsigned char data = TAG_EOT;
|
|
m_queue.addData(&data, len);
|
|
}
|
|
|
|
void CDStarControl::writeQueueHeaderNet(const unsigned char *data)
|
|
{
|
|
assert(data != NULL);
|
|
|
|
if (m_netTimeoutTimer.isRunning() && m_netTimeoutTimer.hasExpired())
|
|
return;
|
|
|
|
unsigned char len = DSTAR_HEADER_LENGTH_BYTES + 1U;
|
|
|
|
unsigned int space = m_queue.freeSpace();
|
|
if (space < (len + 1U)) {
|
|
LogError("D-Star, overflow in the D-Star RF queue");
|
|
return;
|
|
}
|
|
|
|
m_queue.addData(&len, 1U);
|
|
|
|
m_queue.addData(data, len);
|
|
}
|
|
|
|
void CDStarControl::writeQueueDataNet(const unsigned char *data)
|
|
{
|
|
assert(data != NULL);
|
|
|
|
if (m_netTimeoutTimer.isRunning() && m_netTimeoutTimer.hasExpired())
|
|
return;
|
|
|
|
unsigned char len = DSTAR_FRAME_LENGTH_BYTES + 1U;
|
|
|
|
unsigned int space = m_queue.freeSpace();
|
|
if (space < (len + 1U)) {
|
|
LogError("D-Star, overflow in the D-Star RF queue");
|
|
return;
|
|
}
|
|
|
|
m_queue.addData(&len, 1U);
|
|
|
|
m_queue.addData(data, len);
|
|
}
|
|
|
|
void CDStarControl::writeQueueEOTNet()
|
|
{
|
|
if (m_netTimeoutTimer.isRunning() && m_netTimeoutTimer.hasExpired())
|
|
return;
|
|
|
|
unsigned char len = 1U;
|
|
|
|
unsigned int space = m_queue.freeSpace();
|
|
if (space < (len + 1U)) {
|
|
LogError("D-Star, overflow in the D-Star RF queue");
|
|
return;
|
|
}
|
|
|
|
m_queue.addData(&len, 1U);
|
|
|
|
unsigned char data = TAG_EOT;
|
|
m_queue.addData(&data, len);
|
|
}
|
|
|
|
void CDStarControl::writeNetworkHeaderRF(const unsigned char* data)
|
|
{
|
|
assert(data != NULL);
|
|
|
|
if (m_network == NULL)
|
|
return;
|
|
|
|
// Don't send to the network if the timeout has expired
|
|
if (m_rfTimeoutTimer.isRunning() && m_rfTimeoutTimer.hasExpired())
|
|
return;
|
|
|
|
m_network->writeHeader(data + 1U, DSTAR_HEADER_LENGTH_BYTES, m_netState != RS_NET_IDLE);
|
|
}
|
|
|
|
void CDStarControl::writeNetworkDataRF(const unsigned char* data, unsigned int errors, bool end)
|
|
{
|
|
assert(data != NULL);
|
|
|
|
if (m_network == NULL)
|
|
return;
|
|
|
|
// Don't send to the network if the timeout has expired
|
|
if (m_rfTimeoutTimer.isRunning() && m_rfTimeoutTimer.hasExpired())
|
|
return;
|
|
|
|
m_network->writeData(data + 1U, DSTAR_FRAME_LENGTH_BYTES, errors, end, m_netState != RS_NET_IDLE);
|
|
}
|
|
|
|
bool CDStarControl::openFile()
|
|
{
|
|
if (m_fp != NULL)
|
|
return true;
|
|
|
|
time_t t;
|
|
::time(&t);
|
|
|
|
struct tm* tm = ::localtime(&t);
|
|
|
|
char name[100U];
|
|
::sprintf(name, "DStar_%04d%02d%02d_%02d%02d%02d.ambe", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
|
|
|
|
m_fp = ::fopen(name, "wb");
|
|
if (m_fp == NULL)
|
|
return false;
|
|
|
|
::fwrite("DSTAR", 1U, 4U, m_fp);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CDStarControl::writeFile(const unsigned char* data, unsigned int length)
|
|
{
|
|
if (m_fp == NULL)
|
|
return false;
|
|
|
|
::fwrite(data, 1U, length, m_fp);
|
|
|
|
return true;
|
|
}
|
|
|
|
void CDStarControl::closeFile()
|
|
{
|
|
if (m_fp != NULL) {
|
|
::fclose(m_fp);
|
|
m_fp = NULL;
|
|
}
|
|
}
|
|
|
|
bool CDStarControl::insertSilence(const unsigned char* data, unsigned char seqNo)
|
|
{
|
|
assert(data != NULL);
|
|
|
|
// Check to see if we have any spaces to fill?
|
|
unsigned int oldSeqNo = (m_netN + 1U) % 21U;
|
|
if (oldSeqNo == seqNo) {
|
|
// Just copy the data, nothing else to do here
|
|
::memcpy(m_lastFrame, data, DSTAR_FRAME_LENGTH_BYTES + 1U);
|
|
m_lastFrameValid = true;
|
|
return true;
|
|
}
|
|
|
|
unsigned int count;
|
|
if (seqNo > oldSeqNo)
|
|
count = seqNo - oldSeqNo;
|
|
else
|
|
count = (21U + seqNo) - oldSeqNo;
|
|
|
|
if (count >= 10U)
|
|
return false;
|
|
|
|
insertSilence(count);
|
|
|
|
::memcpy(m_lastFrame, data, DSTAR_FRAME_LENGTH_BYTES + 1U);
|
|
m_lastFrameValid = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
void CDStarControl::insertSilence(unsigned int count)
|
|
{
|
|
unsigned char n = (m_netN + 1U) % 21U;
|
|
|
|
for (unsigned int i = 0U; i < count; i++) {
|
|
if (i < 3U && m_lastFrameValid) {
|
|
if (n == 0U) {
|
|
::memcpy(m_lastFrame + DSTAR_VOICE_FRAME_LENGTH_BYTES + 1U, DSTAR_NULL_SLOW_SYNC_BYTES, DSTAR_DATA_FRAME_LENGTH_BYTES);
|
|
writeQueueDataNet(m_lastFrame);
|
|
} else {
|
|
::memcpy(m_lastFrame + DSTAR_VOICE_FRAME_LENGTH_BYTES + 1U, DSTAR_NULL_SLOW_DATA_BYTES, DSTAR_DATA_FRAME_LENGTH_BYTES);
|
|
writeQueueDataNet(m_lastFrame);
|
|
}
|
|
} else {
|
|
m_lastFrameValid = false;
|
|
|
|
if (n == 0U)
|
|
writeQueueDataNet(DSTAR_NULL_FRAME_SYNC_BYTES);
|
|
else
|
|
writeQueueDataNet(DSTAR_NULL_FRAME_DATA_BYTES);
|
|
}
|
|
|
|
m_netN = n;
|
|
|
|
m_netFrames++;
|
|
m_netLost++;
|
|
|
|
n = (n + 1U) % 21U;
|
|
}
|
|
}
|
|
|
|
void CDStarControl::blankDTMF(unsigned char* data) const
|
|
{
|
|
assert(data != NULL);
|
|
|
|
// DTMF begins with these byte values
|
|
if ((data[0] & DSTAR_DTMF_MASK[0]) == DSTAR_DTMF_SIG[0] && (data[1] & DSTAR_DTMF_MASK[1]) == DSTAR_DTMF_SIG[1] &&
|
|
(data[2] & DSTAR_DTMF_MASK[2]) == DSTAR_DTMF_SIG[2] && (data[3] & DSTAR_DTMF_MASK[3]) == DSTAR_DTMF_SIG[3] &&
|
|
(data[4] & DSTAR_DTMF_MASK[4]) == DSTAR_DTMF_SIG[4] && (data[5] & DSTAR_DTMF_MASK[5]) == DSTAR_DTMF_SIG[5] &&
|
|
(data[6] & DSTAR_DTMF_MASK[6]) == DSTAR_DTMF_SIG[6] && (data[7] & DSTAR_DTMF_MASK[7]) == DSTAR_DTMF_SIG[7] &&
|
|
(data[8] & DSTAR_DTMF_MASK[8]) == DSTAR_DTMF_SIG[8])
|
|
::memcpy(data, DSTAR_NULL_AMBE_DATA_BYTES, DSTAR_VOICE_FRAME_LENGTH_BYTES);
|
|
}
|
|
|
|
void CDStarControl::sendAck()
|
|
{
|
|
m_rfTimeoutTimer.stop();
|
|
|
|
if (!m_ackReply)
|
|
return;
|
|
|
|
unsigned char user[DSTAR_LONG_CALLSIGN_LENGTH];
|
|
m_rfHeader.getMyCall1(user);
|
|
|
|
CDStarHeader header;
|
|
header.setUnavailable(true);
|
|
header.setMyCall1(m_callsign);
|
|
header.setYourCall(user);
|
|
header.setRPTCall1(m_gateway);
|
|
header.setRPTCall2(m_callsign);
|
|
|
|
unsigned char data[DSTAR_HEADER_LENGTH_BYTES + 1U];
|
|
header.get(data + 1U);
|
|
data[0U] = TAG_HEADER;
|
|
|
|
writeQueueHeaderRF(data);
|
|
|
|
writeQueueDataRF(DSTAR_NULL_FRAME_SYNC_BYTES);
|
|
|
|
LINK_STATUS status = LS_NONE;
|
|
unsigned char reflector[DSTAR_LONG_CALLSIGN_LENGTH];
|
|
if (m_network != NULL)
|
|
m_network->getStatus(status, reflector);
|
|
|
|
char text[40U];
|
|
if (m_ackMessage && m_rssi != 0) {
|
|
if (status == LS_LINKED_DEXTRA || status == LS_LINKED_DPLUS || status == LS_LINKED_DCS || status == LS_LINKED_CCS || status == LS_LINKED_LOOPBACK)
|
|
::sprintf(text, "%-8.8s -%udBm ", reflector, m_aveRSSI / m_rssiCount);
|
|
else
|
|
::sprintf(text, "BER:%.1f%% -%udBm ", float(m_rfErrs * 100U) / float(m_rfBits), m_aveRSSI / m_rssiCount);
|
|
}
|
|
else {
|
|
if (status == LS_LINKED_DEXTRA || status == LS_LINKED_DPLUS || status == LS_LINKED_DCS || status == LS_LINKED_CCS || status == LS_LINKED_LOOPBACK)
|
|
::sprintf(text, "%-8.8s BER: %.1f%% ", reflector, float(m_rfErrs * 100U) / float(m_rfBits));
|
|
else
|
|
::sprintf(text, "BER: %.1f%% ", float(m_rfErrs * 100U) / float(m_rfBits));
|
|
}
|
|
m_slowData.setText(text);
|
|
|
|
::memcpy(data, DSTAR_NULL_FRAME_DATA_BYTES, DSTAR_FRAME_LENGTH_BYTES + 1U);
|
|
|
|
for (unsigned int i = 0U; i < 19U; i++) {
|
|
m_slowData.get(data + 1U + DSTAR_VOICE_FRAME_LENGTH_BYTES);
|
|
writeQueueDataRF(data);
|
|
}
|
|
|
|
writeQueueEOTRF();
|
|
}
|
|
|
|
void CDStarControl::sendError()
|
|
{
|
|
unsigned char user[DSTAR_LONG_CALLSIGN_LENGTH];
|
|
m_rfHeader.getMyCall1(user);
|
|
|
|
CDStarHeader header;
|
|
header.setUnavailable(true);
|
|
header.setMyCall1(m_callsign);
|
|
header.setYourCall(user);
|
|
header.setRPTCall1(m_callsign);
|
|
header.setRPTCall2(m_callsign);
|
|
|
|
unsigned char data[DSTAR_HEADER_LENGTH_BYTES + 1U];
|
|
header.get(data + 1U);
|
|
data[0U] = TAG_HEADER;
|
|
|
|
writeQueueHeaderRF(data);
|
|
|
|
writeQueueDataRF(DSTAR_NULL_FRAME_SYNC_BYTES);
|
|
|
|
LINK_STATUS status = LS_NONE;
|
|
unsigned char reflector[DSTAR_LONG_CALLSIGN_LENGTH];
|
|
if (m_network != NULL)
|
|
m_network->getStatus(status, reflector);
|
|
|
|
char text[40U];
|
|
if (m_ackMessage && m_rssi != 0) {
|
|
if (status == LS_LINKED_DEXTRA || status == LS_LINKED_DPLUS || status == LS_LINKED_DCS || status == LS_LINKED_CCS || status == LS_LINKED_LOOPBACK)
|
|
::sprintf(text, "%-8.8s -%udBm ", reflector, m_aveRSSI / m_rssiCount);
|
|
else
|
|
::sprintf(text, "BER:%.1f%% -%udBm ", float(m_rfErrs * 100U) / float(m_rfBits), m_aveRSSI / m_rssiCount);
|
|
}
|
|
else {
|
|
if (status == LS_LINKED_DEXTRA || status == LS_LINKED_DPLUS || status == LS_LINKED_DCS || status == LS_LINKED_CCS || status == LS_LINKED_LOOPBACK)
|
|
::sprintf(text, "%-8.8s BER: %.1f%% ", reflector, float(m_rfErrs * 100U) / float(m_rfBits));
|
|
else
|
|
::sprintf(text, "BER: %.1f%% ", float(m_rfErrs * 100U) / float(m_rfBits));
|
|
}
|
|
m_slowData.setText(text);
|
|
|
|
::memcpy(data, DSTAR_NULL_FRAME_DATA_BYTES, DSTAR_FRAME_LENGTH_BYTES + 1U);
|
|
|
|
for (unsigned int i = 0U; i < 19U; i++) {
|
|
m_slowData.get(data + 1U + DSTAR_VOICE_FRAME_LENGTH_BYTES);
|
|
writeQueueDataRF(data);
|
|
}
|
|
|
|
writeQueueEOTRF();
|
|
}
|
|
|
|
bool CDStarControl::isBusy() const
|
|
{
|
|
return m_rfState != RS_RF_LISTENING || m_netState != RS_NET_IDLE;
|
|
}
|
|
|
|
void CDStarControl::enable(bool enabled)
|
|
{
|
|
if (!enabled && m_enabled) {
|
|
m_queue.clear();
|
|
|
|
// Reset the RF section
|
|
m_rfState = RS_RF_LISTENING;
|
|
|
|
m_rfTimeoutTimer.stop();
|
|
|
|
// Reset the networking section
|
|
m_netState = RS_NET_IDLE;
|
|
|
|
m_lastFrameValid = false;
|
|
|
|
m_netTimeoutTimer.stop();
|
|
m_networkWatchdog.stop();
|
|
m_packetTimer.stop();
|
|
}
|
|
|
|
m_enabled = enabled;
|
|
}
|