You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
964 lines
26 KiB
964 lines
26 KiB
/*
|
|
* Copyright (C) 2011-2016 by 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; 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 "DStarRepeaterStatusData.h"
|
|
#include "DStarRepeaterTXRXThread.h"
|
|
#include "DVAPController.h"
|
|
#include "DStarDefines.h"
|
|
#include "HeaderData.h"
|
|
#include "Version.h"
|
|
#include "Utils.h"
|
|
|
|
const unsigned char DTMF_MASK[] = {0x82U, 0x08U, 0x20U, 0x82U, 0x00U, 0x00U, 0x82U, 0x00U, 0x00U};
|
|
const unsigned char DTMF_SIG[] = {0x82U, 0x08U, 0x20U, 0x82U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U};
|
|
|
|
const unsigned int MAX_DATA_SYNC_BIT_ERRS = 2U;
|
|
|
|
const unsigned int NETWORK_QUEUE_COUNT = 2U;
|
|
|
|
const unsigned int SILENCE_THRESHOLD = 2U;
|
|
|
|
const unsigned int CYCLE_TIME = 9U;
|
|
|
|
CDStarRepeaterTXRXThread::CDStarRepeaterTXRXThread(const wxString& type) :
|
|
m_type(type),
|
|
m_modem(NULL),
|
|
m_protocolHandler(NULL),
|
|
m_controller(NULL),
|
|
m_rptCallsign(),
|
|
m_rxHeader(NULL),
|
|
m_txHeader(NULL),
|
|
m_networkQueue(NULL),
|
|
m_writeNum(0U),
|
|
m_readNum(0U),
|
|
m_radioSeqNo(0U),
|
|
m_networkSeqNo(0U),
|
|
m_watchdogTimer(1000U, NETWORK_TIMEOUT),
|
|
m_registerTimer(1000U),
|
|
m_statusTimer(1000U, 0U, 100U), // 100ms
|
|
m_heartbeatTimer(1000U, 1U), // 1s
|
|
m_rptState(DSRS_LISTENING),
|
|
m_rxState(DSRXS_LISTENING),
|
|
m_slowDataDecoder(),
|
|
m_tx(false),
|
|
m_transmitting(false),
|
|
m_space(0U),
|
|
m_killed(false),
|
|
m_activeHangTimer(1000U),
|
|
m_disable(false),
|
|
m_lastData(NULL),
|
|
m_ambe(),
|
|
m_ambeFrames(0U),
|
|
m_ambeSilence(0U),
|
|
m_ambeBits(1U),
|
|
m_ambeErrors(0U),
|
|
m_lastAMBEBits(0U),
|
|
m_lastAMBEErrors(0U),
|
|
m_headerTime(),
|
|
m_packetTime(),
|
|
m_packetCount(0U),
|
|
m_packetSilence(0U)
|
|
#if defined(MQTT)
|
|
,m_mqttStatusTimer(1000U, 1U) // 1s
|
|
#endif
|
|
{
|
|
m_networkQueue = new COutputQueue*[NETWORK_QUEUE_COUNT];
|
|
for (unsigned int i = 0U; i < NETWORK_QUEUE_COUNT; i++)
|
|
m_networkQueue[i] = new COutputQueue((DV_FRAME_LENGTH_BYTES + 2U) * 200U, NETWORK_RUN_FRAME_COUNT); // 4s worth of data);
|
|
|
|
m_lastData = new unsigned char[DV_FRAME_MAX_LENGTH_BYTES];
|
|
|
|
setRepeaterState(DSRS_LISTENING);
|
|
setRadioState(DSRXS_LISTENING);
|
|
}
|
|
|
|
CDStarRepeaterTXRXThread::~CDStarRepeaterTXRXThread()
|
|
{
|
|
for (unsigned int i = 0U; i < NETWORK_QUEUE_COUNT; i++)
|
|
delete m_networkQueue[i];
|
|
delete[] m_networkQueue;
|
|
delete[] m_lastData;
|
|
delete m_rxHeader;
|
|
delete m_txHeader;
|
|
}
|
|
|
|
void *CDStarRepeaterTXRXThread::Entry()
|
|
{
|
|
// Wait here until we have the essentials to run
|
|
while (!m_killed && (m_modem == NULL || m_controller == NULL || m_protocolHandler == NULL || m_rptCallsign.IsEmpty() || m_rptCallsign.IsSameAs(wxT(" "))))
|
|
::wxMilliSleep(500UL); // 1/2 sec
|
|
|
|
if (m_killed)
|
|
return NULL;
|
|
|
|
m_controller->setActive(false);
|
|
m_controller->setRadioTransmit(false);
|
|
|
|
m_heartbeatTimer.start();
|
|
m_statusTimer.start();
|
|
m_registerTimer.start(10U);
|
|
#if defined(MQTT)
|
|
m_mqttStatusTimer.start();
|
|
#endif
|
|
|
|
wxString hardware = m_type;
|
|
int n = hardware.Find(wxT(' '));
|
|
if (n != wxNOT_FOUND)
|
|
hardware = m_type.Left(n);
|
|
|
|
wxLogMessage(wxT("Starting the D-Star transmitter and receiver thread"));
|
|
|
|
wxStopWatch stopWatch;
|
|
|
|
try {
|
|
while (!m_killed) {
|
|
stopWatch.Start();
|
|
|
|
if (m_statusTimer.hasExpired() || m_space == 0U) {
|
|
m_space = m_modem->getSpace();
|
|
m_tx = m_modem->isTX();
|
|
m_statusTimer.start();
|
|
}
|
|
|
|
receiveModem();
|
|
|
|
receiveNetwork();
|
|
|
|
repeaterStateMachine();
|
|
|
|
// Send the register packet if needed and restart the timer
|
|
if (m_registerTimer.hasExpired()) {
|
|
m_protocolHandler->writeRegister();
|
|
m_registerTimer.start(30U);
|
|
}
|
|
|
|
// Clock the heartbeat output every one second
|
|
if (m_heartbeatTimer.hasExpired()) {
|
|
m_controller->setHeartbeat();
|
|
m_heartbeatTimer.start();
|
|
}
|
|
|
|
#if defined(MQTT)
|
|
// Publish status to MQTT every second
|
|
if (m_mqttStatusTimer.hasExpired()) {
|
|
if (g_mqtt != NULL) {
|
|
CDStarRepeaterStatusData* status = getStatus();
|
|
std::string json = status->toJSON();
|
|
g_mqtt->publish("status", json.c_str());
|
|
delete status;
|
|
|
|
// Publish BER for Display-Driver during active RF
|
|
if (m_rptState == DSRS_VALID && m_ambeBits > 0U) {
|
|
float ber = float(m_ambeErrors * 100U) / float(m_ambeBits);
|
|
mqttPublishBER(ber);
|
|
}
|
|
}
|
|
m_mqttStatusTimer.start();
|
|
}
|
|
#endif
|
|
|
|
// Set the output state
|
|
if (m_tx || (m_activeHangTimer.isRunning() && !m_activeHangTimer.hasExpired())) {
|
|
m_controller->setActive(true);
|
|
} else {
|
|
m_controller->setActive(false);
|
|
m_activeHangTimer.stop();
|
|
}
|
|
|
|
// Check the shutdown state, state changes are done here to bypass the state machine which is
|
|
// frozen when m_disable is asserted
|
|
m_disable = m_controller->getDisable();
|
|
if (m_disable) {
|
|
if (m_rptState != DSRS_SHUTDOWN) {
|
|
m_watchdogTimer.stop();
|
|
m_activeHangTimer.stop();
|
|
for (unsigned int i = 0U; i < NETWORK_QUEUE_COUNT; i++)
|
|
m_networkQueue[i]->reset();
|
|
m_controller->setActive(false);
|
|
m_controller->setRadioTransmit(false);
|
|
m_rptState = DSRS_SHUTDOWN;
|
|
m_transmitting = false;
|
|
}
|
|
} else {
|
|
if (m_rptState == DSRS_SHUTDOWN) {
|
|
m_watchdogTimer.stop();
|
|
m_rptState = DSRS_LISTENING;
|
|
m_protocolHandler->reset();
|
|
m_transmitting = false;
|
|
}
|
|
}
|
|
|
|
if (m_networkQueue[m_readNum]->dataReady())
|
|
transmitNetworkData();
|
|
else if (m_networkQueue[m_readNum]->headerReady())
|
|
transmitNetworkHeader();
|
|
|
|
m_controller->setRadioTransmit(m_tx);
|
|
|
|
unsigned long ms = stopWatch.Time();
|
|
if (ms < CYCLE_TIME) {
|
|
::wxMilliSleep(CYCLE_TIME - ms);
|
|
clock(CYCLE_TIME);
|
|
} else {
|
|
clock(ms);
|
|
}
|
|
}
|
|
}
|
|
catch (std::exception& e) {
|
|
wxString message(e.what(), wxConvLocal);
|
|
wxLogError(wxT("Exception raised - \"%s\""), message.c_str());
|
|
}
|
|
catch (...) {
|
|
wxLogError(wxT("Unknown exception raised"));
|
|
}
|
|
|
|
wxLogMessage(wxT("Stopping the D-Star transmitter and receiver thread"));
|
|
|
|
m_modem->stop();
|
|
|
|
m_controller->setActive(false);
|
|
m_controller->setRadioTransmit(false);
|
|
m_controller->close();
|
|
delete m_controller;
|
|
|
|
m_protocolHandler->close();
|
|
delete m_protocolHandler;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::kill()
|
|
{
|
|
m_killed = true;
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::setCallsign(const wxString& callsign, const wxString&, DSTAR_MODE, ACK_TYPE, bool, bool, bool, bool)
|
|
{
|
|
// Pad the callsign up to eight characters
|
|
m_rptCallsign = callsign;
|
|
m_rptCallsign.resize(LONG_CALLSIGN_LENGTH, wxT(' '));
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::setProtocolHandler(CRepeaterProtocolHandler* handler, bool local)
|
|
{
|
|
wxASSERT(handler != NULL);
|
|
|
|
m_protocolHandler = handler;
|
|
|
|
if (local) {
|
|
wxLogInfo(wxT("Reducing transmit buffering because of local connection"));
|
|
|
|
for (unsigned int i = 0U; i < NETWORK_QUEUE_COUNT; i++)
|
|
m_networkQueue[i]->setThreshold(LOCAL_RUN_FRAME_COUNT);
|
|
}
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::setModem(CModem* modem)
|
|
{
|
|
wxASSERT(modem != NULL);
|
|
|
|
m_modem = modem;
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::setTimes(unsigned int, unsigned int)
|
|
{
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::setBeacon(unsigned int, const wxString&, bool, TEXT_LANG)
|
|
{
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::setAnnouncement(bool, unsigned int, const wxString&, const wxString&, const wxString&, const wxString&)
|
|
{
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::setController(CExternalController* controller, unsigned int activeHangTime)
|
|
{
|
|
wxASSERT(controller != NULL);
|
|
|
|
m_controller = controller;
|
|
m_activeHangTimer.setTimeout(activeHangTime);
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::setOutputs(bool, bool, bool, bool)
|
|
{
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::setLogging(bool, const wxString&)
|
|
{
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::setWhiteList(CCallsignList*)
|
|
{
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::setBlackList(CCallsignList*)
|
|
{
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::setGreyList(CCallsignList*)
|
|
{
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::receiveModem()
|
|
{
|
|
for (;;) {
|
|
DSMT_TYPE type = m_modem->read();
|
|
if (type == DSMTT_NONE)
|
|
return;
|
|
|
|
switch (m_rxState) {
|
|
case DSRXS_LISTENING:
|
|
if (type == DSMTT_HEADER) {
|
|
CHeaderData* header = m_modem->readHeader();
|
|
receiveHeader(header);
|
|
} else if (type == DSMTT_DATA) {
|
|
unsigned char data[20U];
|
|
unsigned int length = m_modem->readData(data, 20U);
|
|
setRadioState(DSRXS_PROCESS_SLOW_DATA);
|
|
receiveSlowData(data, length);
|
|
}
|
|
break;
|
|
|
|
case DSRXS_PROCESS_SLOW_DATA:
|
|
if (type == DSMTT_DATA) {
|
|
unsigned char data[20U];
|
|
unsigned int length = m_modem->readData(data, 20U);
|
|
receiveSlowData(data, length);
|
|
} else if (type == DSMTT_EOT || type == DSMTT_LOST) {
|
|
setRadioState(DSRXS_LISTENING);
|
|
}
|
|
break;
|
|
|
|
case DSRXS_PROCESS_DATA:
|
|
if (type == DSMTT_DATA) {
|
|
unsigned char data[20U];
|
|
unsigned int length = m_modem->readData(data, 20U);
|
|
receiveRadioData(data, length);
|
|
} else if (type == DSMTT_EOT || type == DSMTT_LOST) {
|
|
unsigned char data[20U];
|
|
::memcpy(data, END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES);
|
|
processRadioFrame(data, FRAME_END);
|
|
setRadioState(DSRXS_LISTENING);
|
|
#if defined(MQTT)
|
|
mqttPublishDStarEnd();
|
|
mqttPublishIdle();
|
|
#endif
|
|
endOfRadioData();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::receiveHeader(CHeaderData* header)
|
|
{
|
|
wxASSERT(header != NULL);
|
|
|
|
wxLogMessage(wxT("Radio header decoded - My: %s/%s Your: %s Rpt1: %s Rpt2: %s Flags: %02X %02X %02X"), header->getMyCall1().c_str(), header->getMyCall2().c_str(), header->getYourCall().c_str(), header->getRptCall1().c_str(), header->getRptCall2().c_str(), header->getFlag1(), header->getFlag2(), header->getFlag3());
|
|
|
|
bool res = processRadioHeader(header);
|
|
if (res) {
|
|
// A valid header and is a DV packet
|
|
m_radioSeqNo = 20U;
|
|
setRadioState(DSRXS_PROCESS_DATA);
|
|
} else {
|
|
// This is a DD packet or some other problem
|
|
// wxLogMessage(wxT("Invalid header"));
|
|
}
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::receiveSlowData(unsigned char* data, unsigned int)
|
|
{
|
|
unsigned int errs;
|
|
errs = countBits(data[VOICE_FRAME_LENGTH_BYTES + 0U] ^ DATA_SYNC_BYTES[0U]);
|
|
errs += countBits(data[VOICE_FRAME_LENGTH_BYTES + 1U] ^ DATA_SYNC_BYTES[1U]);
|
|
errs += countBits(data[VOICE_FRAME_LENGTH_BYTES + 2U] ^ DATA_SYNC_BYTES[2U]);
|
|
|
|
// The data sync has been seen, a fuzzy match is used, two bit errors or less
|
|
if (errs <= MAX_DATA_SYNC_BIT_ERRS) {
|
|
// wxLogMessage(wxT("Found data sync at frame %u, errs: %u"), m_radioSeqNo, errs);
|
|
m_radioSeqNo = 0U;
|
|
m_slowDataDecoder.sync();
|
|
} else if (m_radioSeqNo == 20U) {
|
|
// wxLogMessage(wxT("Assuming data sync"));
|
|
m_radioSeqNo = 0U;
|
|
m_slowDataDecoder.sync();
|
|
} else {
|
|
m_radioSeqNo++;
|
|
m_slowDataDecoder.addData(data + VOICE_FRAME_LENGTH_BYTES);
|
|
|
|
CHeaderData* header = m_slowDataDecoder.getHeaderData();
|
|
if (header == NULL)
|
|
return;
|
|
|
|
wxLogMessage(wxT("Radio header from slow data - My: %s/%s Your: %s Rpt1: %s Rpt2: %s Flags: %02X %02X %02X BER: 0%%"), header->getMyCall1().c_str(), header->getMyCall2().c_str(), header->getYourCall().c_str(), header->getRptCall1().c_str(), header->getRptCall2().c_str(), header->getFlag1(), header->getFlag2(), header->getFlag3());
|
|
|
|
if (header != NULL) {
|
|
bool res = processRadioHeader(header);
|
|
if (res) {
|
|
// A valid header and is a DV packet, go to normal data relaying
|
|
setRadioState(DSRXS_PROCESS_DATA);
|
|
} else {
|
|
// This is a DD packet or some other problem
|
|
// wxLogMessage(wxT("Invalid header"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::receiveRadioData(unsigned char* data, unsigned int)
|
|
{
|
|
unsigned int errs;
|
|
errs = countBits(data[VOICE_FRAME_LENGTH_BYTES + 0U] ^ DATA_SYNC_BYTES[0U]);
|
|
errs += countBits(data[VOICE_FRAME_LENGTH_BYTES + 1U] ^ DATA_SYNC_BYTES[1U]);
|
|
errs += countBits(data[VOICE_FRAME_LENGTH_BYTES + 2U] ^ DATA_SYNC_BYTES[2U]);
|
|
|
|
// The data sync has been seen, a fuzzy match is used, two bit errors or less
|
|
if (errs <= MAX_DATA_SYNC_BIT_ERRS) {
|
|
// wxLogMessage(wxT("Found data sync at frame %u, errs: %u"), m_radioSeqNo, errs);
|
|
m_radioSeqNo = 0U;
|
|
processRadioFrame(data, FRAME_SYNC);
|
|
} else if (m_radioSeqNo == 20U) {
|
|
// wxLogMessage(wxT("Regenerating data sync"));
|
|
m_radioSeqNo = 0U;
|
|
processRadioFrame(data, FRAME_SYNC);
|
|
} else {
|
|
m_radioSeqNo++;
|
|
processRadioFrame(data, FRAME_NORMAL);
|
|
}
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::receiveNetwork()
|
|
{
|
|
NETWORK_TYPE type;
|
|
|
|
for (;;) {
|
|
type = m_protocolHandler->read();
|
|
|
|
// Get the data from the network
|
|
if (type == NETWORK_NONE) { // Nothing received
|
|
break;
|
|
} else if (type == NETWORK_HEADER) { // A header
|
|
CHeaderData* header = m_protocolHandler->readHeader();
|
|
if (header != NULL) {
|
|
::memcpy(m_lastData, NULL_FRAME_DATA_BYTES, DV_FRAME_LENGTH_BYTES);
|
|
|
|
processNetworkHeader(header);
|
|
|
|
m_headerTime.Start();
|
|
m_packetTime.Start();
|
|
m_packetCount = 0U;
|
|
m_packetSilence = 0U;
|
|
}
|
|
} else if (type == NETWORK_DATA) { // AMBE data and slow data
|
|
unsigned char data[2U * DV_FRAME_MAX_LENGTH_BYTES];
|
|
::memset(data, 0x00U, 2U * DV_FRAME_MAX_LENGTH_BYTES);
|
|
|
|
unsigned char seqNo;
|
|
unsigned int length = m_protocolHandler->readData(data, DV_FRAME_MAX_LENGTH_BYTES, seqNo);
|
|
if (length != 0U && m_transmitting) {
|
|
::memcpy(m_lastData, data, length);
|
|
m_watchdogTimer.start();
|
|
m_packetCount += processNetworkFrame(data, length, seqNo);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Have we missed any data frames?
|
|
if (m_transmitting && m_packetTime.Time() > 200L) {
|
|
unsigned int packetsNeeded = m_headerTime.Time() / DSTAR_FRAME_TIME_MS;
|
|
|
|
// wxLogMessage(wxT("Time: %u ms, need %u packets and received %u packets"), ms - m_headerMS, packetsNeeded, m_packetCount);
|
|
|
|
if (packetsNeeded > m_packetCount) {
|
|
unsigned int count = packetsNeeded - m_packetCount;
|
|
|
|
if (count > 5U) {
|
|
count -= 2U;
|
|
|
|
// wxLogMessage(wxT("Inserting %u silence frames into the network data stream"), count);
|
|
|
|
// Create silence frames
|
|
for (unsigned int i = 0U; i < count; i++) {
|
|
unsigned char data[DV_FRAME_LENGTH_BYTES];
|
|
::memcpy(data, NULL_FRAME_DATA_BYTES, DV_FRAME_LENGTH_BYTES);
|
|
m_packetCount += processNetworkFrame(data, DV_FRAME_LENGTH_BYTES, m_networkSeqNo);
|
|
m_packetSilence++;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_packetTime.Start();
|
|
}
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::transmitNetworkHeader(const CHeaderData& header)
|
|
{
|
|
wxLogMessage(wxT("Transmitting to - My: %s/%s Your: %s Rpt1: %s Rpt2: %s Flags: %02X %02X %02X"), header.getMyCall1().c_str(), header.getMyCall2().c_str(), header.getYourCall().c_str(), header.getRptCall1().c_str(), header.getRptCall2().c_str(), header.getFlag1(), header.getFlag2(), header.getFlag3());
|
|
|
|
bool empty = m_networkQueue[m_readNum]->isEmpty();
|
|
if (!empty) {
|
|
bool headerReady = m_networkQueue[m_readNum]->headerReady();
|
|
if (headerReady) {
|
|
// Transmission has never started, so just purge the queue
|
|
m_networkQueue[m_readNum]->reset();
|
|
|
|
m_readNum++;
|
|
if (m_readNum >= NETWORK_QUEUE_COUNT)
|
|
m_readNum = 0U;
|
|
} else {
|
|
// Append an end of stream
|
|
m_networkQueue[m_readNum]->reset();
|
|
m_networkQueue[m_readNum]->addData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES, true);
|
|
}
|
|
}
|
|
|
|
m_networkQueue[m_writeNum]->reset();
|
|
m_networkQueue[m_writeNum]->setHeader(new CHeaderData(header));
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::transmitNetworkHeader()
|
|
{
|
|
// Don't send a header until the modem is ready
|
|
bool ready = m_modem->isTXReady();
|
|
if (!ready)
|
|
return;
|
|
|
|
CHeaderData* header = m_networkQueue[m_readNum]->getHeader();
|
|
if (header == NULL)
|
|
return;
|
|
|
|
m_modem->writeHeader(*header);
|
|
delete header;
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::transmitNetworkData()
|
|
{
|
|
if (m_space == 0U)
|
|
return;
|
|
|
|
unsigned char buffer[DV_FRAME_LENGTH_BYTES];
|
|
bool end;
|
|
unsigned int length = m_networkQueue[m_readNum]->getData(buffer, DV_FRAME_LENGTH_BYTES, end);
|
|
|
|
if (length == 0U)
|
|
return;
|
|
|
|
m_modem->writeData(buffer, length, end);
|
|
m_space--;
|
|
|
|
if (end) {
|
|
m_networkQueue[m_readNum]->reset();
|
|
|
|
m_readNum++;
|
|
if (m_readNum >= NETWORK_QUEUE_COUNT)
|
|
m_readNum = 0U;
|
|
}
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::repeaterStateMachine()
|
|
{
|
|
if (m_watchdogTimer.isRunning() && m_watchdogTimer.hasExpired()) {
|
|
wxLogMessage(wxT("Network watchdog has expired"));
|
|
// Send end of transmission data to the radio
|
|
m_networkQueue[m_writeNum]->addData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES, true);
|
|
#if defined(MQTT)
|
|
mqttPublishDStarLost();
|
|
mqttPublishIdle();
|
|
#endif
|
|
endOfNetworkData();
|
|
}
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::setRadioState(DSTAR_RX_STATE state)
|
|
{
|
|
// This is the too state
|
|
switch (state) {
|
|
case DSRXS_LISTENING:
|
|
m_rxState = DSRXS_LISTENING;
|
|
break;
|
|
|
|
case DSRXS_PROCESS_DATA:
|
|
m_ambeFrames = 0U;
|
|
m_ambeSilence = 0U;
|
|
m_ambeBits = 1U;
|
|
m_ambeErrors = 0U;
|
|
m_lastAMBEBits = 0U;
|
|
m_lastAMBEErrors = 0U;
|
|
m_rxState = DSRXS_PROCESS_DATA;
|
|
break;
|
|
|
|
case DSRXS_PROCESS_SLOW_DATA:
|
|
m_radioSeqNo = 0U;
|
|
m_slowDataDecoder.reset();
|
|
m_rxState = DSRXS_PROCESS_SLOW_DATA;
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool CDStarRepeaterTXRXThread::setRepeaterState(DSTAR_RPT_STATE state)
|
|
{
|
|
// Can't change state when shutdown
|
|
if (m_disable)
|
|
return false;
|
|
|
|
// The "to" state
|
|
switch (state) {
|
|
case DSRS_SHUTDOWN:
|
|
m_watchdogTimer.stop();
|
|
m_activeHangTimer.stop();
|
|
m_controller->setActive(false);
|
|
m_controller->setRadioTransmit(false);
|
|
m_rptState = DSRS_SHUTDOWN;
|
|
m_transmitting = false;
|
|
break;
|
|
|
|
case DSRS_LISTENING:
|
|
m_rptState = DSRS_LISTENING;
|
|
break;
|
|
|
|
case DSRS_VALID:
|
|
if (m_rptState != DSRS_LISTENING)
|
|
return false;
|
|
|
|
m_rptState = DSRS_VALID;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CDStarRepeaterTXRXThread::processRadioHeader(CHeaderData* header)
|
|
{
|
|
wxASSERT(header != NULL);
|
|
|
|
// We don't handle DD data packets
|
|
if (header->isDataPacket()) {
|
|
wxLogMessage(wxT("Received a DD packet, ignoring"));
|
|
delete header;
|
|
return false;
|
|
}
|
|
|
|
setRepeaterState(DSRS_VALID);
|
|
|
|
if (m_rptState == DSRS_VALID) {
|
|
// Send the valid header to the gateway if we are accepted
|
|
delete m_rxHeader;
|
|
m_rxHeader = header;
|
|
|
|
#if defined(MQTT)
|
|
mqttPublishDStarStart(m_rxHeader->getMyCall1(), m_rxHeader->getMyCall2(),
|
|
m_rxHeader->getYourCall(), m_rxHeader->getRptCall2(), "rf");
|
|
#endif
|
|
|
|
CHeaderData netHeader(*m_rxHeader);
|
|
netHeader.setRptCall1(m_rxHeader->getRptCall2());
|
|
netHeader.setRptCall2(m_rxHeader->getRptCall1());
|
|
|
|
m_protocolHandler->writeHeader(netHeader);
|
|
} else {
|
|
delete header;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::processNetworkHeader(CHeaderData* header)
|
|
{
|
|
wxASSERT(header != NULL);
|
|
|
|
// If shutdown we ignore incoming headers
|
|
if (m_rptState == DSRS_SHUTDOWN) {
|
|
delete header;
|
|
return;
|
|
}
|
|
|
|
wxLogMessage(wxT("Network header received - My: %s/%s Your: %s Rpt1: %s Rpt2: %s Flags: %02X %02X %02X"), header->getMyCall1().c_str(), header->getMyCall2().c_str(), header->getYourCall().c_str(), header->getRptCall1().c_str(), header->getRptCall2().c_str(), header->getFlag1(), header->getFlag2(), header->getFlag3());
|
|
|
|
// Is it for us?
|
|
if (!header->getRptCall2().IsSameAs(m_rptCallsign)) {
|
|
wxLogMessage(wxT("Invalid network RPT2 value, ignoring"));
|
|
delete header;
|
|
return;
|
|
}
|
|
|
|
delete m_txHeader;
|
|
m_txHeader = header;
|
|
|
|
#if defined(MQTT)
|
|
mqttPublishDStarStart(m_txHeader->getMyCall1(), m_txHeader->getMyCall2(),
|
|
m_txHeader->getYourCall(), m_txHeader->getRptCall2(), "net");
|
|
#endif
|
|
|
|
m_networkSeqNo = 0U;
|
|
m_transmitting = true;
|
|
m_watchdogTimer.start();
|
|
m_activeHangTimer.stop();
|
|
|
|
transmitNetworkHeader(*header);
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::processRadioFrame(unsigned char* data, FRAME_TYPE type)
|
|
{
|
|
m_ambeFrames++;
|
|
|
|
// If a sync frame, regenerate the sync bytes
|
|
if (type == FRAME_SYNC)
|
|
::memcpy(data + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES);
|
|
|
|
// Only regenerate the AMBE on received radio data
|
|
unsigned int errors = 0U;
|
|
if (type != FRAME_END) {
|
|
// Data packets have no AMBE FEC
|
|
if (!m_rxHeader->isDataPacket())
|
|
errors = m_ambe.count(data);
|
|
|
|
m_ambeErrors += errors;
|
|
m_ambeBits += 48U; // Only count the bits with FEC added
|
|
}
|
|
|
|
if (::memcmp(data, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES) == 0)
|
|
m_ambeSilence++;
|
|
|
|
// Don't pass through the frame of an invalid transmission
|
|
if (m_rptState != DSRS_VALID)
|
|
return;
|
|
|
|
if (type == FRAME_END) {
|
|
// Send null data and the end marker over the network, and the statistics
|
|
unsigned char bytes[DV_FRAME_MAX_LENGTH_BYTES];
|
|
::memcpy(bytes, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES);
|
|
::memcpy(bytes + VOICE_FRAME_LENGTH_BYTES, END_PATTERN_BYTES, END_PATTERN_LENGTH_BYTES);
|
|
m_protocolHandler->writeData(bytes, DV_FRAME_MAX_LENGTH_BYTES, 0U, true);
|
|
} else {
|
|
// Send the data to the network
|
|
m_protocolHandler->writeData(data, DV_FRAME_LENGTH_BYTES, errors, false);
|
|
}
|
|
}
|
|
|
|
unsigned int CDStarRepeaterTXRXThread::processNetworkFrame(unsigned char* data, unsigned int length, unsigned char seqNo)
|
|
{
|
|
wxASSERT(data != NULL);
|
|
wxASSERT(length > 0U);
|
|
|
|
// If shutdown we ignore incoming data
|
|
if (m_rptState == DSRS_SHUTDOWN)
|
|
return 1U;
|
|
|
|
bool end = (seqNo & 0x40U) == 0x40U;
|
|
if (end) {
|
|
m_networkQueue[m_writeNum]->addData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES, true);
|
|
#if defined(MQTT)
|
|
mqttPublishDStarEnd();
|
|
mqttPublishIdle();
|
|
#endif
|
|
endOfNetworkData();
|
|
return 1U;
|
|
}
|
|
|
|
// Mask out the control bits of the sequence number
|
|
seqNo &= 0x1FU;
|
|
|
|
// Count the number of silence frames to insert
|
|
unsigned int tempSeqNo = m_networkSeqNo;
|
|
unsigned int count = 0U;
|
|
while (seqNo != tempSeqNo) {
|
|
count++;
|
|
|
|
tempSeqNo++;
|
|
if (tempSeqNo >= 21U)
|
|
tempSeqNo = 0U;
|
|
}
|
|
|
|
// If the number is too high, then it probably means an old out-of-order frame, ignore it
|
|
if (count > 18U)
|
|
return 0U;
|
|
|
|
unsigned int packetCount = 0U;
|
|
|
|
// Insert missing frames
|
|
while (seqNo != m_networkSeqNo) {
|
|
unsigned char buffer[DV_FRAME_LENGTH_BYTES];
|
|
if (count > SILENCE_THRESHOLD) {
|
|
::memcpy(buffer, NULL_FRAME_DATA_BYTES, DV_FRAME_LENGTH_BYTES);
|
|
} else {
|
|
::memcpy(buffer, m_lastData, DV_FRAME_LENGTH_BYTES);
|
|
// Data packets have no AMBE FEC
|
|
if (!m_txHeader->isDataPacket())
|
|
m_ambe.regenerate(buffer);
|
|
}
|
|
|
|
if (m_networkSeqNo == 0U)
|
|
::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES);
|
|
|
|
m_networkQueue[m_writeNum]->addData(buffer, DV_FRAME_LENGTH_BYTES, false);
|
|
|
|
packetCount++;
|
|
m_networkSeqNo++;
|
|
m_packetSilence++;
|
|
if (m_networkSeqNo >= 21U)
|
|
m_networkSeqNo = 0U;
|
|
}
|
|
|
|
// Regenerate the sync bytes
|
|
if (m_networkSeqNo == 0U)
|
|
::memcpy(data + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES);
|
|
|
|
packetCount++;
|
|
m_networkSeqNo++;
|
|
if (m_networkSeqNo >= 21U)
|
|
m_networkSeqNo = 0U;
|
|
|
|
// Data packets have no AMBE FEC
|
|
if (!m_txHeader->isDataPacket())
|
|
m_ambe.regenerate(data);
|
|
|
|
m_networkQueue[m_writeNum]->addData(data, DV_FRAME_LENGTH_BYTES, false);
|
|
|
|
return packetCount;
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::endOfRadioData()
|
|
{
|
|
wxLogMessage(wxT("AMBE for %s Frames: %.1fs, Silence: %.1f%%, BER: %.1f%%"), m_rxHeader->getMyCall1().c_str(), float(m_ambeFrames) / 50.0F, float(m_ambeSilence * 100U) / float(m_ambeFrames), float(m_ambeErrors * 100U) / float(m_ambeBits));
|
|
|
|
setRepeaterState(DSRS_LISTENING);
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::endOfNetworkData()
|
|
{
|
|
float loss = 0.0F;
|
|
if (m_packetCount != 0U)
|
|
loss = float(m_packetSilence) / float(m_packetCount);
|
|
|
|
wxLogMessage(wxT("Stats for %s Frames: %.1fs, Loss: %.1f%%, Packets: %u/%u"), m_txHeader->getMyCall1().c_str(), float(m_packetCount) / 50.0F, loss * 100.0F, m_packetSilence, m_packetCount);
|
|
|
|
m_watchdogTimer.stop();
|
|
m_activeHangTimer.start();
|
|
m_protocolHandler->reset();
|
|
m_transmitting = false;
|
|
|
|
m_writeNum++;
|
|
if (m_writeNum >= NETWORK_QUEUE_COUNT)
|
|
m_writeNum = 0U;
|
|
}
|
|
|
|
CDStarRepeaterStatusData* CDStarRepeaterTXRXThread::getStatus()
|
|
{
|
|
float bits = float(m_ambeBits - m_lastAMBEBits);
|
|
float errors = float(m_ambeErrors - m_lastAMBEErrors);
|
|
if (bits == 0.0F)
|
|
bits = 1.0F;
|
|
|
|
m_lastAMBEBits = m_ambeBits;
|
|
m_lastAMBEErrors = m_ambeErrors;
|
|
|
|
CDStarRepeaterStatusData* status;
|
|
if (m_rptState == DSRS_SHUTDOWN || m_rptState == DSRS_LISTENING)
|
|
status = new CDStarRepeaterStatusData(wxEmptyString, wxEmptyString, wxEmptyString, wxEmptyString,
|
|
wxEmptyString, 0x00, 0x00, 0x00, m_tx, m_rxState, m_rptState, 0U, 0U, 0U, 0U, 0U, 0U, 0.0F,
|
|
wxEmptyString, wxEmptyString, wxEmptyString, wxEmptyString, wxEmptyString, wxEmptyString);
|
|
else
|
|
status = new CDStarRepeaterStatusData(m_rxHeader->getMyCall1(), m_rxHeader->getMyCall2(),
|
|
m_rxHeader->getYourCall(), m_rxHeader->getRptCall1(), m_rxHeader->getRptCall2(),
|
|
m_rxHeader->getFlag1(), m_rxHeader->getFlag2(), m_rxHeader->getFlag3(), m_tx, m_rxState,
|
|
m_rptState, 0U, 0U, 0U, 0U, 0U, 0U, (errors * 100.0F) / bits, wxEmptyString, wxEmptyString,
|
|
wxEmptyString, wxEmptyString, wxEmptyString, wxEmptyString);
|
|
|
|
if (m_type.IsSameAs(wxT("DVAP")) && m_modem != NULL) {
|
|
CDVAPController* dvap = static_cast<CDVAPController*>(m_modem);
|
|
bool squelch = dvap->getSquelch();
|
|
int signal = dvap->getSignal();
|
|
status->setDVAP(squelch, signal);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::clock(unsigned int ms)
|
|
{
|
|
m_registerTimer.clock(ms);
|
|
m_watchdogTimer.clock(ms);
|
|
m_activeHangTimer.clock(ms);
|
|
m_statusTimer.clock(ms);
|
|
m_heartbeatTimer.clock(ms);
|
|
#if defined(MQTT)
|
|
m_mqttStatusTimer.clock(ms);
|
|
#endif
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::shutdown()
|
|
{
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::startup()
|
|
{
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::command1()
|
|
{
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::command2()
|
|
{
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::command3()
|
|
{
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::command4()
|
|
{
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::command5()
|
|
{
|
|
}
|
|
|
|
void CDStarRepeaterTXRXThread::command6()
|
|
{
|
|
}
|
|
|
|
unsigned int CDStarRepeaterTXRXThread::countBits(unsigned char byte)
|
|
{
|
|
unsigned int bits = 0U;
|
|
|
|
if ((byte & 0x01U) == 0x01U)
|
|
bits++;
|
|
if ((byte & 0x02U) == 0x02U)
|
|
bits++;
|
|
if ((byte & 0x04U) == 0x04U)
|
|
bits++;
|
|
if ((byte & 0x08U) == 0x08U)
|
|
bits++;
|
|
if ((byte & 0x10U) == 0x10U)
|
|
bits++;
|
|
if ((byte & 0x20U) == 0x20U)
|
|
bits++;
|
|
if ((byte & 0x40U) == 0x40U)
|
|
bits++;
|
|
if ((byte & 0x80U) == 0x80U)
|
|
bits++;
|
|
|
|
return bits;
|
|
}
|