/* * 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 "SMeter.h" #include #include #include #include #include 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, DSTAR_ACK_MESSAGE ackMessage, bool errorReply, const std::vector& blackList, const std::vector& 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 int voice_sync_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) { ::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; if (n == 1U) LogDebug("D-Star, %s fastdata sequence no. 0", log_prefix); LogDebug("D-Star, %s fastdata sequence no. %2u", log_prefix, n); } else if (next_frame_is_fast_data) { next_frame_is_fast_data = false; if (blank_dtmf) skip_dtmf_blanking_frames = FAST_DATA_BEEP_GRACE_FRAMES; LogDebug("D-Star, %s fastdata sequence no. %2u", 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)) voice_sync_data_is_null_ambe_data = true; if (::memcmp(data + offset, DSTAR_NULL_AMBE_DATA_BYTES_SCRAMBLED, DSTAR_VOICE_FRAME_LENGTH_BYTES) == 0) data_is_null_ambe_data = true; if ((n == 1U) && !voice_sync_data_is_null_ambe_data) voice_sync_errors += m_fec.regenerateDStar(voice_sync_data + offset); if (!data_is_null_ambe_data) errors += m_fec.regenerateDStar(data + offset); if (blank_dtmf && skip_dtmf_blanking_frames > 0U) { skip_dtmf_blanking_frames--; } else if (blank_dtmf && skip_dtmf_blanking_frames == 0U) { if ((n == 1U) && !voice_sync_data_is_null_ambe_data) blankDTMF(voice_sync_data + offset); if (!data_is_null_ambe_data) blankDTMF(data + offset); } if (n == 1U) { if (voice_sync_data_is_null_ambe_data) LogDebug("D-Star, %s nullaudio sequence no. 0", log_prefix); else LogDebug("D-Star, %s audio sequence no. 0, errs: %2u/48 (%5.1f%%)", log_prefix, voice_sync_errors, float(voice_sync_errors) / 0.48F); } if (data_is_null_ambe_data) LogDebug("D-Star, %s nullaudio sequence no. %2u", log_prefix, n); else LogDebug("D-Star, %s audio sequence no. %2u, errs: %2u/48 (%5.1f%%)", log_prefix, n, errors, float(errors) / 0.48F); } return voice_sync_errors + 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); 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); 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 == DSTAR_ACK_RSSI && m_rssi != 0) { if (status == LS_LINKED_DEXTRA || status == LS_LINKED_DPLUS || status == LS_LINKED_DCS || status == LS_LINKED_CCS || status == LS_LINKED_LOOPBACK) { CUtils::removeChar(reflector, ' ');//remove space from reflector so all nicely fits onto 20 chars in case rssi < 99dBm ::sprintf(text, "%-8.8s %.1f%% -%udBm ", reflector, float(m_rfErrs * 100U) / float(m_rfBits), m_aveRSSI / m_rssiCount); } else { ::sprintf(text, "BER:%.1f%% -%udBm ", float(m_rfErrs * 100U) / float(m_rfBits), m_aveRSSI / m_rssiCount); } } else if (m_ackMessage == DSTAR_ACK_SMETER && m_rssi != 0) { unsigned int signal, plus; char signalText[10U]; CSMeter::getSignal(m_aveRSSI / m_rssiCount, signal, plus); if (plus != 0U) ::sprintf(signalText, "S%u+%02u", signal, plus); else ::sprintf(signalText, "S%u", signal); 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 %.1f%% %s ", reflector, float(m_rfErrs * 100U) / float(m_rfBits), signalText); else ::sprintf(text, "BER:%.1f%% %s ", float(m_rfErrs * 100U) / float(m_rfBits), signalText); } 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 == DSTAR_ACK_RSSI && m_rssi != 0) { if (status == LS_LINKED_DEXTRA || status == LS_LINKED_DPLUS || status == LS_LINKED_DCS || status == LS_LINKED_CCS || status == LS_LINKED_LOOPBACK) { CUtils::removeChar(reflector, ' ');//remove space from reflector so all nicely fits onto 20 chars in case rssi < 99dBm ::sprintf(text, "%-8.8s %.1f%% -%udBm ", reflector, float(m_rfErrs * 100U) / float(m_rfBits), m_aveRSSI / m_rssiCount); } else { ::sprintf(text, "BER:%.1f%% -%udBm ", float(m_rfErrs * 100U) / float(m_rfBits), m_aveRSSI / m_rssiCount); } } else if (m_ackMessage == DSTAR_ACK_SMETER && m_rssi != 0) { unsigned int signal, plus; char signalText[10U]; CSMeter::getSignal(m_aveRSSI / m_rssiCount, signal, plus); if (plus != 0U) ::sprintf(signalText, "S%u+%02u", signal, plus); else ::sprintf(signalText, "S%u", signal); 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 %.1f%% %s ", reflector, float(m_rfErrs * 100U) / float(m_rfBits), signalText); else ::sprintf(text, "BER:%.1f%% %s ", float(m_rfErrs * 100U) / float(m_rfBits), signalText); } 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; }