/* * Copyright (C) 2010-2015,2018,2019 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 "RepeaterHandler.h" #include "DExtraHandler.h" #include "DPlusHandler.h" #include "DStarDefines.h" #include "DCSHandler.h" #include "CCSHandler.h" #include "HeaderData.h" #include "DDHandler.h" #include "AMBEData.h" #include "Utils.h" #include const unsigned int ETHERNET_ADDRESS_LENGTH = 6U; const unsigned char ETHERNET_BROADCAST_ADDRESS[] = {0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU}; // Multicast address '01:00:5E:00:00:01' - IP: '224.0.0.1' (to all) const unsigned char TOALL_MULTICAST_ADDRESS[] = {0x01U, 0x00U, 0x5EU, 0x00U, 0x00U, 0x01U}; // Multicast address '01:00:5E:00:00:23' - IP: '224.0.0.35' (DX-Cluster) const unsigned char DX_MULTICAST_ADDRESS[] = {0x01U, 0x00U, 0x5EU, 0x00U, 0x00U, 0x23U}; unsigned int CRepeaterHandler::m_maxRepeaters = 0U; CRepeaterHandler** CRepeaterHandler::m_repeaters = NULL; wxString CRepeaterHandler::m_localAddress; CG2ProtocolHandler* CRepeaterHandler::m_g2Handler = NULL; CIRCDDB* CRepeaterHandler::m_irc = NULL; CCacheManager* CRepeaterHandler::m_cache = NULL; wxString CRepeaterHandler::m_gateway; TEXT_LANG CRepeaterHandler::m_language = TL_ENGLISH_UK; bool CRepeaterHandler::m_dextraEnabled = true; bool CRepeaterHandler::m_dplusEnabled = false; bool CRepeaterHandler::m_dcsEnabled = true; bool CRepeaterHandler::m_infoEnabled = true; bool CRepeaterHandler::m_echoEnabled = true; bool CRepeaterHandler::m_dtmfEnabled = true; CHeaderLogger* CRepeaterHandler::m_headerLogger = NULL; CAPRSWriter* CRepeaterHandler::m_aprsWriter = NULL; CCallsignList* CRepeaterHandler::m_restrictList = NULL; CRepeaterHandler::CRepeaterHandler(const wxString& callsign, const wxString& band, const wxString& address, unsigned int port, HW_TYPE hwType, const wxString& reflector, bool atStartup, RECONNECT reconnect, bool dratsEnabled, double frequency, double offset, double range, double latitude, double longitude, double agl, const wxString& description1, const wxString& description2, const wxString& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3) : m_index(0x00U), m_rptCallsign(), m_gwyCallsign(), m_band(' '), m_address(), m_port(port), m_hwType(hwType), m_repeaterHandler(handler), m_frequency(frequency), m_offset(offset), m_range(range), m_latitude(latitude), m_longitude(longitude), m_agl(agl), m_description1(description1), m_description2(description2), m_url(url), m_band1(band1), m_band2(band2), m_band3(band3), m_repeaterId(0x00U), m_busyId(0x00U), m_watchdogTimer(1000U, REPEATER_TIMEOUT), m_ddMode(false), m_ddCallsign(), m_queryTimer(1000U, 5U), // 5 seconds m_myCall1(), m_myCall2(), m_yourCall(), m_rptCall1(), m_rptCall2(), m_flag1(0x00U), m_flag2(0x00U), m_flag3(0x00U), m_restricted(false), m_fastData(false), m_frames(0U), m_silence(0U), m_errors(0U), m_textCollector(), m_text(), m_xBandRptr(NULL), m_starNet(NULL), m_g2Status(G2_NONE), m_g2User(), m_g2Repeater(), m_g2Gateway(), m_g2Header(NULL), m_g2Address(), m_linkStatus(LS_NONE), m_linkRepeater(), m_linkGateway(), m_linkReconnect(reconnect), m_linkAtStartup(atStartup), m_linkStartup(reflector), m_linkReconnectTimer(1000U), m_linkRelink(false), m_echo(NULL), m_infoAudio(NULL), m_infoNeeded(false), m_msgAudio(NULL), m_msgNeeded(false), m_wxAudio(NULL), m_wxNeeded(false), m_version(NULL), m_drats(NULL), m_dtmf(), m_pollTimer(1000U, 900U), // 15 minutes m_ccsHandler(NULL), m_lastReflector(), m_heardUser(), m_heardRepeater(), m_heardTimer(1000U, 0U, 100U) // 100ms { wxASSERT(!callsign.IsEmpty()); wxASSERT(port > 0U); wxASSERT(handler != NULL); m_ddMode = band.Len() > 1U; m_band = band.GetChar(0U); m_rptCallsign = callsign; m_rptCallsign.Append(wxT(" ")); m_rptCallsign.Truncate(LONG_CALLSIGN_LENGTH - 1U); m_rptCallsign.Append(band); m_rptCallsign.Truncate(LONG_CALLSIGN_LENGTH); m_gwyCallsign = callsign; m_gwyCallsign.Append(wxT(" ")); m_gwyCallsign.Truncate(LONG_CALLSIGN_LENGTH - 1U); m_gwyCallsign.Append(wxT("G")); m_address.s_addr = ::inet_addr(address.mb_str()); m_pollTimer.start(); switch (m_linkReconnect) { case RECONNECT_5MINS: m_linkReconnectTimer.start(5U * 60U); break; case RECONNECT_10MINS: m_linkReconnectTimer.start(10U * 60U); break; case RECONNECT_15MINS: m_linkReconnectTimer.start(15U * 60U); break; case RECONNECT_20MINS: m_linkReconnectTimer.start(20U * 60U); break; case RECONNECT_25MINS: m_linkReconnectTimer.start(25U * 60U); break; case RECONNECT_30MINS: m_linkReconnectTimer.start(30U * 60U); break; case RECONNECT_60MINS: m_linkReconnectTimer.start(60U * 60U); break; case RECONNECT_90MINS: m_linkReconnectTimer.start(90U * 60U); break; case RECONNECT_120MINS: m_linkReconnectTimer.start(120U * 60U); break; case RECONNECT_180MINS: m_linkReconnectTimer.start(180U * 60U); break; default: break; } wxFileName messageFile; messageFile.SetPath(::wxGetHomeDir()); messageFile.SetName(wxT("message")); messageFile.SetExt(wxT("dvtool")); wxFileName weatherFile; weatherFile.SetPath(::wxGetHomeDir()); weatherFile.SetName(wxT("weather")); weatherFile.SetExt(wxT("dvtool")); m_echo = new CEchoUnit(this, callsign); m_infoAudio = new CAudioUnit(this, callsign); m_msgAudio = new CAnnouncementUnit(this, callsign, messageFile.GetFullPath(), wxT("MSG")); m_wxAudio = new CAnnouncementUnit(this, callsign, weatherFile.GetFullPath(), wxT("WX")); m_version = new CVersionUnit(this, callsign); if (dratsEnabled) { m_drats = new CDRATSServer(m_localAddress, port, callsign, this); bool ret = m_drats->open(); if (!ret) { delete m_drats; m_drats = NULL; } } } CRepeaterHandler::~CRepeaterHandler() { delete m_echo; delete m_infoAudio; delete m_msgAudio; delete m_wxAudio; delete m_version; if (m_drats != NULL) m_drats->close(); } void CRepeaterHandler::initialise(unsigned int maxRepeaters) { wxASSERT(maxRepeaters > 0U); m_maxRepeaters = maxRepeaters; m_repeaters = new CRepeaterHandler*[m_maxRepeaters]; for (unsigned int i = 0U; i < m_maxRepeaters; i++) m_repeaters[i] = NULL; } void CRepeaterHandler::setIndex(unsigned int index) { m_index = index; } void CRepeaterHandler::add(const wxString& callsign, const wxString& band, const wxString& address, unsigned int port, HW_TYPE hwType, const wxString& reflector, bool atStartup, RECONNECT reconnect, bool dratsEnabled, double frequency, double offset, double range, double latitude, double longitude, double agl, const wxString& description1, const wxString& description2, const wxString& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3) { wxASSERT(!callsign.IsEmpty()); wxASSERT(port > 0U); wxASSERT(handler != NULL); CRepeaterHandler* repeater = new CRepeaterHandler(callsign, band, address, port, hwType, reflector, atStartup, reconnect, dratsEnabled, frequency, offset, range, latitude, longitude, agl, description1, description2, url, handler, band1, band2, band3); for (unsigned int i = 0U; i < m_maxRepeaters; i++) { if (m_repeaters[i] == NULL) { repeater->setIndex(i); m_repeaters[i] = repeater; return; } } wxLogError(wxT("Cannot add repeater with callsign %s, no space"), callsign.c_str()); delete repeater; } void CRepeaterHandler::setG2Handler(CG2ProtocolHandler* handler) { wxASSERT(handler != NULL); m_g2Handler = handler; } void CRepeaterHandler::setCache(CCacheManager* cache) { wxASSERT(cache != NULL); m_cache = cache; } void CRepeaterHandler::setIRC(CIRCDDB* irc) { wxASSERT(irc != NULL); m_irc = irc; } void CRepeaterHandler::setGateway(const wxString& gateway) { m_gateway = gateway; } void CRepeaterHandler::setLanguage(TEXT_LANG language) { m_language = language; } void CRepeaterHandler::setDExtraEnabled(bool enabled) { m_dextraEnabled = enabled; } void CRepeaterHandler::setDPlusEnabled(bool enabled) { m_dplusEnabled = enabled; } void CRepeaterHandler::setDCSEnabled(bool enabled) { m_dcsEnabled = enabled; } void CRepeaterHandler::setInfoEnabled(bool enabled) { m_infoEnabled = enabled; } void CRepeaterHandler::setEchoEnabled(bool enabled) { m_echoEnabled = enabled; } void CRepeaterHandler::setDTMFEnabled(bool enabled) { m_dtmfEnabled = enabled; } void CRepeaterHandler::setHeaderLogger(CHeaderLogger* logger) { m_headerLogger = logger; } void CRepeaterHandler::setAPRSWriter(CAPRSWriter* writer) { m_aprsWriter = writer; } void CRepeaterHandler::setLocalAddress(const wxString& address) { m_localAddress = address; } void CRepeaterHandler::setRestrictList(CCallsignList* list) { wxASSERT(list != NULL); m_restrictList = list; } bool CRepeaterHandler::getRepeater(unsigned int n, wxString& callsign, LINK_STATUS& linkStatus, wxString& linkCallsign) { if (n >= m_maxRepeaters) return false; if (m_repeaters[n] == NULL) return false; callsign = m_repeaters[n]->m_rptCallsign; linkStatus = m_repeaters[n]->m_linkStatus; linkCallsign = m_repeaters[n]->m_linkRepeater; return true; } void CRepeaterHandler::resolveUser(const wxString &user, const wxString& repeater, const wxString& gateway, const wxString &address) { for (unsigned int i = 0U; i < m_maxRepeaters; i++) { if (m_repeaters[i] != NULL) m_repeaters[i]->resolveUserInt(user, repeater, gateway, address); } } void CRepeaterHandler::resolveRepeater(const wxString& repeater, const wxString& gateway, const wxString &address, DSTAR_PROTOCOL protocol) { for (unsigned int i = 0U; i < m_maxRepeaters; i++) { if (m_repeaters[i] != NULL) m_repeaters[i]->resolveRepeaterInt(repeater, gateway, address, protocol); } } void CRepeaterHandler::startup() { for (unsigned int i = 0U; i < m_maxRepeaters; i++) { if (m_repeaters[i] != NULL) m_repeaters[i]->startupInt(); } } void CRepeaterHandler::clock(unsigned int ms) { for (unsigned int i = 0U; i < m_maxRepeaters; i++) { if (m_repeaters[i] != NULL) m_repeaters[i]->clockInt(ms); } } void CRepeaterHandler::finalise() { for (unsigned int i = 0U; i < m_maxRepeaters; i++) { delete m_repeaters[i]; m_repeaters[i] = NULL; } if (m_aprsWriter != NULL) { m_aprsWriter->close(); delete m_aprsWriter; } delete[] m_repeaters; } CRepeaterHandler* CRepeaterHandler::findDVRepeater(const CHeaderData& header) { wxString rpt1 = header.getRptCall1(); in_addr address = header.getYourAddress(); for (unsigned int i = 0U; i < m_maxRepeaters; i++) { CRepeaterHandler* repeater = m_repeaters[i]; if (repeater != NULL) { if (!repeater->m_ddMode && repeater->m_address.s_addr == address.s_addr && repeater->m_rptCallsign.IsSameAs(rpt1)) return repeater; } } return NULL; } CRepeaterHandler* CRepeaterHandler::findDVRepeater(const CAMBEData& data, bool busy) { unsigned int id = data.getId(); for (unsigned int i = 0U; i < m_maxRepeaters; i++) { CRepeaterHandler* repeater = m_repeaters[i]; if (repeater != NULL) { if (!busy && !repeater->m_ddMode && repeater->m_repeaterId == id) return repeater; if (busy && !repeater->m_ddMode && repeater->m_busyId == id) return repeater; } } return NULL; } CRepeaterHandler* CRepeaterHandler::findDVRepeater(const wxString& callsign) { for (unsigned int i = 0U; i < m_maxRepeaters; i++) { CRepeaterHandler* repeater = m_repeaters[i]; if (repeater != NULL) { if (!repeater->m_ddMode && repeater->m_rptCallsign.IsSameAs(callsign)) return repeater; } } return NULL; } CRepeaterHandler* CRepeaterHandler::findRepeater(const CPollData& data) { in_addr address = data.getYourAddress(); unsigned int port = data.getYourPort(); for (unsigned int i = 0U; i < m_maxRepeaters; i++) { CRepeaterHandler* repeater = m_repeaters[i]; if (repeater != NULL) { if (repeater->m_address.s_addr == address.s_addr && repeater->m_port == port) return repeater; } } return NULL; } CRepeaterHandler* CRepeaterHandler::findDDRepeater(const CDDData& data) { wxString rpt1 = data.getRptCall1(); for (unsigned int i = 0U; i < m_maxRepeaters; i++) { CRepeaterHandler* repeater = m_repeaters[i]; if (repeater != NULL) { if (repeater->m_ddMode && repeater->m_rptCallsign.IsSameAs(rpt1)) return repeater; } } return NULL; } CRepeaterHandler* CRepeaterHandler::findDDRepeater() { for (unsigned int i = 0U; i < m_maxRepeaters; i++) { CRepeaterHandler* repeater = m_repeaters[i]; if (repeater != NULL) { if (repeater->m_ddMode) return repeater; } } return NULL; } wxArrayString CRepeaterHandler::listDVRepeaters() { wxArrayString repeaters; for (unsigned int i = 0U; i < m_maxRepeaters; i++) { CRepeaterHandler* repeater = m_repeaters[i]; if (repeater != NULL && !repeater->m_ddMode) repeaters.Add(repeater->m_rptCallsign); } return repeaters; } void CRepeaterHandler::pollAllIcom(CPollData& data) { for (unsigned int i = 0U; i < m_maxRepeaters; i++) { CRepeaterHandler* repeater = m_repeaters[i]; if (repeater != NULL && repeater->m_hwType == HW_ICOM) repeater->processRepeater(data); } } CRemoteRepeaterData* CRepeaterHandler::getInfo() const { return new CRemoteRepeaterData(m_rptCallsign, m_linkReconnect, m_linkStartup); } void CRepeaterHandler::processRepeater(CHeaderData& header) { unsigned int id = header.getId(); // Stop duplicate headers if (id == m_repeaterId) return; // Save the header fields m_myCall1 = header.getMyCall1(); m_myCall2 = header.getMyCall2(); m_yourCall = header.getYourCall(); m_rptCall1 = header.getRptCall1(); m_rptCall2 = header.getRptCall2(); m_flag1 = header.getFlag1(); m_flag2 = header.getFlag2(); m_flag3 = header.getFlag3(); if (m_hwType == HW_ICOM) { unsigned char band1 = header.getBand1(); unsigned char band2 = header.getBand2(); unsigned char band3 = header.getBand3(); if (m_band1 != band1 || m_band2 != band2 || m_band3 != band3) { m_band1 = band1; m_band2 = band2; m_band3 = band3; wxLogMessage(wxT("Repeater %s registered with bands %u %u %u"), m_rptCall1.c_str(), m_band1, m_band2, m_band3); } } if (m_flag1 == 0x01) { wxLogMessage(wxT("Received a busy message from repeater %s"), m_rptCall1.c_str()); return; } if (!m_heardUser.IsEmpty() && !m_myCall1.IsSameAs(m_heardUser) && m_irc != NULL) m_irc->sendHeard(m_heardUser, wxT(" "), wxT(" "), m_heardRepeater, wxT(" "), 0x00U, 0x00U, 0x00U); // Inform CCS m_ccsHandler->writeHeard(header); m_ccsHandler->writeHeader(header); // The Icom heard timer m_heardTimer.stop(); if (m_drats != NULL) m_drats->writeHeader(header); // Reset the statistics m_frames = 0U; m_silence = 0U; m_errors = 0U; // Assume voice mode m_fastData = false; // An RF header resets the reconnect timer m_linkReconnectTimer.start(); // Incoming links get everything sendToIncoming(header); // Reset the slow data text collector m_textCollector.reset(); m_text.Clear(); // Reset the APRS Writer if it's enabled if (m_aprsWriter != NULL) m_aprsWriter->writeHeader(m_rptCallsign, header); // Write to Header.log if it's enabled if (m_headerLogger != NULL) m_headerLogger->write(wxT("Repeater"), header); // Reset the DTMF decoder m_dtmf.reset(); // Reset the info, echo and version commands if they're running m_infoAudio->cancel(); m_msgAudio->cancel(); m_wxAudio->cancel(); m_echo->cancel(); m_version->cancel(); // A new header resets fields and G2 routing status m_repeaterId = id; m_busyId = 0x00U; m_watchdogTimer.start(); m_xBandRptr = NULL; m_starNet = NULL; // If we're querying for a user or repeater, kill the query timer if (m_g2Status == G2_USER || m_g2Status == G2_REPEATER) m_queryTimer.stop(); delete m_g2Header; m_g2Header = NULL; m_g2Status = G2_NONE; m_g2User.Clear(); m_g2Repeater.Clear(); m_g2Gateway.Clear(); // Check if this user is restricted m_restricted = false; if (m_restrictList != NULL) { bool res = m_restrictList->isInList(m_myCall1); if (res) m_restricted = true; } // Reject silly RPT2 values if (m_rptCall2.IsSameAs(m_rptCallsign) || m_rptCall2.IsSameAs(wxT(" "))) return; // Do cross-band routing if RPT2 is not one of the gateway callsigns if (!m_rptCall2.IsSameAs(m_gwyCallsign) && !m_rptCall2.IsSameAs(m_gateway)) { CRepeaterHandler* repeater = findDVRepeater(m_rptCall2); if (repeater != NULL) { wxLogMessage(wxT("Cross-band routing by %s from %s to %s"), m_myCall1.c_str(), m_rptCallsign.c_str(), m_rptCall2.c_str()); m_xBandRptr = repeater; m_xBandRptr->process(header, DIR_INCOMING, AS_XBAND); m_g2Status = G2_XBAND; } else { // Keep the transmission local wxLogMessage(wxT("Invalid cross-band route by %s from %s to %s"), m_myCall1.c_str(), m_rptCallsign.c_str(), m_rptCall2.c_str()); m_g2Status = G2_LOCAL; } return; } m_starNet = CStarNetHandler::findStarNet(header); if (m_starNet != NULL && !m_restricted) { wxLogMessage(wxT("StarNet routing by %s to %s"), m_myCall1.c_str(), m_yourCall.c_str()); m_starNet->process(header); m_g2Status = G2_STARNET; return; } // Reject simple cases if (m_yourCall.Left(4).IsSameAs(wxT("CQCQ"))) { sendToOutgoing(header); return; } // Handle the Echo command if (m_echoEnabled && m_yourCall.IsSameAs(wxT(" E"))) { m_g2Status = G2_ECHO; m_echo->writeHeader(header); return; } // Handle the Info command if (m_infoEnabled && m_yourCall.IsSameAs(wxT(" I"))) { m_g2Status = G2_LOCAL; m_infoNeeded = true; return; } // Handle the MSG command if (m_infoEnabled && m_yourCall.IsSameAs(wxT(" M"))) { m_g2Status = G2_LOCAL; m_msgNeeded = true; return; } // Handle the WX command if (m_infoEnabled && m_yourCall.IsSameAs(wxT(" W"))) { m_g2Status = G2_LOCAL; m_wxNeeded = true; return; } // Handle the Version command if (m_infoEnabled && m_yourCall.IsSameAs(wxT(" V"))) { m_g2Status = G2_VERSION; sendToOutgoing(header); return; } if (m_restricted) { sendToOutgoing(header); return; } if (isCCSCommand(m_yourCall)) { ccsCommandHandler(m_yourCall, m_myCall1, wxT("UR Call")); sendToOutgoing(header); } else { g2CommandHandler(m_yourCall, m_myCall1, header); if (m_g2Status == G2_NONE) { reflectorCommandHandler(m_yourCall, m_myCall1, wxT("UR Call")); sendToOutgoing(header); } } } void CRepeaterHandler::processRepeater(CAMBEData& data) { // AMBE data via RF resets the reconnect timer m_linkReconnectTimer.start(); m_watchdogTimer.start(); m_frames++; m_errors += data.getErrors(); unsigned char buffer[DV_FRAME_MAX_LENGTH_BYTES]; data.getData(buffer, DV_FRAME_MAX_LENGTH_BYTES); // Check for the fast data signature if (!m_fastData) { unsigned char slowDataType = (buffer[VOICE_FRAME_LENGTH_BYTES] ^ SCRAMBLER_BYTE1) & SLOW_DATA_TYPE_MASK; if (slowDataType == SLOW_DATA_TYPE_FAST_DATA1 || slowDataType == SLOW_DATA_TYPE_FAST_DATA2) m_fastData = true; } // Don't do AMBE processing when in Fast Data mode if (!m_fastData) { if (::memcmp(buffer, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES) == 0) m_silence++; // Don't do DTMF decoding or blanking if off and not on crossband either if (m_dtmfEnabled && m_g2Status != G2_XBAND) { bool pressed = m_dtmf.decode(buffer, data.isEnd()); if (pressed) { // Replace the DTMF with silence ::memcpy(buffer, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); data.setData(buffer, DV_FRAME_LENGTH_BYTES); } bool dtmfDone = m_dtmf.hasCommand(); if (dtmfDone) { wxString command = m_dtmf.translate(); // Only process the DTMF command if the your call is CQCQCQ and not a restricted user if (!m_restricted && m_yourCall.Left(4U).IsSameAs(wxT("CQCQ"))) { if (command.IsEmpty()) { // Do nothing } else if (isCCSCommand(command)) { ccsCommandHandler(command, m_myCall1, wxT("DTMF")); } else if (command.IsSameAs(wxT(" I"))) { m_infoNeeded = true; } else { reflectorCommandHandler(command, m_myCall1, wxT("DTMF")); } } } } } // Incoming links get everything sendToIncoming(data); // CCS gets everything m_ccsHandler->writeAMBE(data); if (m_drats != NULL) m_drats->writeData(data); if (m_aprsWriter != NULL) m_aprsWriter->writeData(m_rptCallsign, data); if (m_text.IsEmpty() && !data.isEnd()) { m_textCollector.writeData(data); bool hasText = m_textCollector.hasData(); if (hasText) { m_text = m_textCollector.getData(); sendHeard(m_text); } } data.setText(m_text); // If no slow data text has been received, send a heard with no text when the end of the // transmission arrives if (data.isEnd() && m_text.IsEmpty()) sendHeard(); // Send the statistics after the end of the data, any stats from the repeater should have // been received by now if (data.isEnd()) { m_watchdogTimer.stop(); sendStats(); } switch (m_g2Status) { case G2_LOCAL: if (data.isEnd()) { m_repeaterId = 0x00U; m_g2Status = G2_NONE; } break; case G2_OK: data.setDestination(m_g2Address, G2_DV_PORT); m_g2Handler->writeAMBE(data); if (data.isEnd()) { m_repeaterId = 0x00U; m_g2Status = G2_NONE; } break; case G2_USER: case G2_REPEATER: // Data ended before the callsign could be resolved if (data.isEnd()) { m_queryTimer.stop(); delete m_g2Header; m_repeaterId = 0x0U; m_g2Status = G2_NONE; m_g2Header = NULL; } break; case G2_NONE: if (data.isEnd()) m_repeaterId = 0x00U; sendToOutgoing(data); break; case G2_XBAND: m_xBandRptr->process(data, DIR_INCOMING, AS_XBAND); if (data.isEnd()) { m_repeaterId = 0x00U; m_g2Status = G2_NONE; m_xBandRptr = NULL; } break; case G2_STARNET: m_starNet->process(data); if (data.isEnd()) { m_repeaterId = 0x00U; m_g2Status = G2_NONE; m_starNet = NULL; } break; case G2_ECHO: m_echo->writeData(data); if (data.isEnd()) { m_repeaterId = 0x00U; m_g2Status = G2_NONE; } break; case G2_VERSION: sendToOutgoing(data); if (data.isEnd()) { m_version->sendVersion(); m_repeaterId = 0x00U; m_g2Status = G2_NONE; } break; } if (data.isEnd() && m_infoNeeded) { m_infoAudio->sendStatus(); m_infoNeeded = false; } if (data.isEnd() && m_msgNeeded) { m_msgAudio->sendAnnouncement(); m_msgNeeded = false; } if (data.isEnd() && m_wxNeeded) { m_wxAudio->sendAnnouncement(); m_wxNeeded = false; } } // Incoming headers when relaying network traffic, as detected by the repeater, will be used as a command // to the reflector command handler, probably to do an unlink. void CRepeaterHandler::processBusy(CHeaderData& header) { unsigned int id = header.getId(); // Ignore duplicate headers if (id == m_busyId) return; wxString rptCall1 = header.getRptCall1(); wxString rptCall2 = header.getRptCall2(); if (m_hwType == HW_ICOM) { unsigned char band1 = header.getBand1(); unsigned char band2 = header.getBand2(); unsigned char band3 = header.getBand3(); if (m_band1 != band1 || m_band2 != band2 || m_band3 != band3) { m_band1 = band1; m_band2 = band2; m_band3 = band3; wxLogMessage(wxT("Repeater %s registered with bands %u %u %u"), rptCall1.c_str(), m_band1, m_band2, m_band3); } } if (header.getFlag1() == 0x01) { wxLogMessage(wxT("Received a busy message from repeater %s"), rptCall1.c_str()); return; } // Reject the header if the RPT2 value is not one of the gateway callsigns if (!rptCall2.IsSameAs(m_gwyCallsign) && !rptCall2.IsSameAs(m_gateway)) return; m_myCall1 = header.getMyCall1(); m_yourCall = header.getYourCall(); m_rptCall1 = rptCall1; m_rptCall2 = rptCall2; m_dtmf.reset(); m_busyId = id; m_repeaterId = 0x00U; m_watchdogTimer.start(); // If restricted then don't send to the command handler m_restricted = false; if (m_restrictList != NULL) { bool res = m_restrictList->isInList(m_myCall1); if (res) { m_restricted = true; return; } } // Reject simple cases if (m_yourCall.Left(4).IsSameAs(wxT("CQCQ")) || m_yourCall.IsSameAs(wxT(" E")) || m_yourCall.IsSameAs(wxT(" I"))) return; if (isCCSCommand(m_yourCall)) ccsCommandHandler(m_yourCall, m_myCall1, wxT("background UR Call")); else reflectorCommandHandler(m_yourCall, m_myCall1, wxT("background UR Call")); } void CRepeaterHandler::processBusy(CAMBEData& data) { m_watchdogTimer.start(); unsigned char buffer[DV_FRAME_MAX_LENGTH_BYTES]; data.getData(buffer, DV_FRAME_MAX_LENGTH_BYTES); // Don't do DTMF decoding if off if (m_dtmfEnabled) { m_dtmf.decode(buffer, data.isEnd()); bool dtmfDone = m_dtmf.hasCommand(); if (dtmfDone) { wxString command = m_dtmf.translate(); // Only process the DTMF command if the your call is CQCQCQ and the user isn't restricted if (!m_restricted && m_yourCall.Left(4U).IsSameAs(wxT("CQCQ"))) { if (command.IsEmpty()) { // Do nothing } else if (isCCSCommand(command)) { ccsCommandHandler(command, m_myCall1, wxT("background DTMF")); } else if (command.IsSameAs(wxT(" I"))) { // Do nothing } else { reflectorCommandHandler(command, m_myCall1, wxT("background DTMF")); } } } } if (data.isEnd()) { if (m_infoNeeded) { m_infoAudio->sendStatus(); m_infoNeeded = false; } if (m_msgNeeded) { m_msgAudio->sendAnnouncement(); m_msgNeeded = false; } if (m_wxNeeded) { m_wxAudio->sendAnnouncement(); m_wxNeeded = false; } if (m_g2Status == G2_VERSION) m_version->sendVersion(); m_g2Status = G2_NONE; m_busyId = 0x00U; m_watchdogTimer.stop(); } } void CRepeaterHandler::processRepeater(CHeardData& heard) { if (m_irc == NULL) return; // A second heard has come in before the original has been sent or cancelled if (m_heardTimer.isRunning() && !m_heardTimer.hasExpired()) m_irc->sendHeard(m_heardUser, wxT(" "), wxT(" "), m_heardRepeater, wxT(" "), 0x00U, 0x00U, 0x00U); m_heardUser = heard.getUser(); m_heardRepeater = heard.getRepeater(); m_heardTimer.start(); } void CRepeaterHandler::processRepeater(CPollData& data) { if (!m_pollTimer.hasExpired()) return; if (m_irc == NULL) return; wxString callsign = m_rptCallsign; if (m_ddMode) callsign.Append(wxT("D")); wxString text = data.getData1(); m_irc->kickWatchdog(callsign, text); m_pollTimer.start(); } void CRepeaterHandler::processRepeater(CDDData& data) { if (!m_ddMode) return; if (m_ddCallsign.IsEmpty()) { m_ddCallsign = data.getYourCall(); wxLogMessage(wxT("Added DD callsign %s"), m_ddCallsign.c_str()); } CDDHandler::process(data); } bool CRepeaterHandler::process(CDDData& data) { unsigned char* address = data.getDestinationAddress(); if (::memcmp(address, ETHERNET_BROADCAST_ADDRESS, ETHERNET_ADDRESS_LENGTH) == 0) data.setRepeaters(m_gwyCallsign, wxT(" ")); else if (::memcmp(address, TOALL_MULTICAST_ADDRESS, ETHERNET_ADDRESS_LENGTH) == 0) data.setRepeaters(m_gwyCallsign, m_rptCallsign); else if (::memcmp(address, DX_MULTICAST_ADDRESS, ETHERNET_ADDRESS_LENGTH) == 0) data.setRepeaters(m_gwyCallsign, m_rptCallsign); else data.setRepeaters(m_gwyCallsign, m_rptCallsign); data.setDestination(m_address, m_port); data.setFlags(0xC0U, 0x00U, 0x00U); data.setMyCall1(m_ddCallsign); data.setMyCall2(wxT(" ")); m_repeaterHandler->writeDD(data); return true; } bool CRepeaterHandler::process(CHeaderData& header, DIRECTION, AUDIO_SOURCE source) { // If data is coming from the repeater then don't send if (m_repeaterId != 0x00U) return false; // Rewrite the ID if we're using Icom hardware if (m_hwType == HW_ICOM) { unsigned int id1 = header.getId(); unsigned int id2 = id1 + m_index; header.setId(id2); } // Send all original headers to all repeater types, and only send duplicate headers to homebrew repeaters if (source != AS_DUP || (source == AS_DUP && m_hwType == HW_HOMEBREW)) { header.setBand1(m_band1); header.setBand2(m_band2); header.setBand3(m_band3); header.setDestination(m_address, m_port); header.setRepeaters(m_gwyCallsign, m_rptCallsign); m_repeaterHandler->writeHeader(header); } // Don't send duplicate headers to anyone else if (source == AS_DUP) return true; sendToIncoming(header); if (source == AS_DPLUS || source == AS_DEXTRA || source == AS_DCS) m_ccsHandler->writeHeader(header); if (source == AS_G2 || source == AS_INFO || source == AS_VERSION || source == AS_XBAND || source == AS_ECHO) return true; // Reset the slow data text collector, used for DCS text passing m_textCollector.reset(); m_text.Clear(); sendToOutgoing(header); return true; } bool CRepeaterHandler::process(CAMBEData& data, DIRECTION, AUDIO_SOURCE source) { // If data is coming from the repeater then don't send if (m_repeaterId != 0x00U) return false; // Rewrite the ID if we're using Icom hardware if (m_hwType == HW_ICOM) { unsigned int id = data.getId(); id += m_index; data.setId(id); } data.setBand1(m_band1); data.setBand2(m_band2); data.setBand3(m_band3); data.setDestination(m_address, m_port); m_repeaterHandler->writeAMBE(data); sendToIncoming(data); if (source == AS_DPLUS || source == AS_DEXTRA || source == AS_DCS) m_ccsHandler->writeAMBE(data); if (source == AS_G2 || source == AS_INFO || source == AS_VERSION || source == AS_XBAND || source == AS_ECHO) return true; // Collect the text from the slow data for DCS if (m_text.IsEmpty() && !data.isEnd()) { m_textCollector.writeData(data); bool hasText = m_textCollector.hasData(); if (hasText) m_text = m_textCollector.getData(); } data.setText(m_text); sendToOutgoing(data); return true; } void CRepeaterHandler::resolveUserInt(const wxString& user, const wxString& repeater, const wxString& gateway, const wxString &address) { if (m_g2Status == G2_USER && m_g2User.IsSameAs(user)) { m_queryTimer.stop(); if (!address.IsEmpty()) { // No point routing to self if (repeater.IsSameAs(m_rptCallsign)) { m_g2Status = G2_LOCAL; delete m_g2Header; m_g2Header = NULL; return; } // User found, update the settings and send the header to the correct place m_g2Address.s_addr = ::inet_addr(address.mb_str()); m_g2Repeater = repeater; m_g2Gateway = gateway; m_g2Header->setDestination(m_g2Address, G2_DV_PORT); m_g2Header->setRepeaters(m_g2Gateway, m_g2Repeater); m_g2Handler->writeHeader(*m_g2Header); delete m_g2Header; m_g2Status = G2_OK; m_g2Header = NULL; } else { // User not found, remove G2 settings m_g2Status = G2_LOCAL; m_g2User.Clear(); m_g2Repeater.Clear(); m_g2Gateway.Clear(); delete m_g2Header; m_g2Header = NULL; } } } void CRepeaterHandler::resolveRepeaterInt(const wxString& repeater, const wxString& gateway, const wxString &address, DSTAR_PROTOCOL protocol) { if (m_g2Status == G2_REPEATER && m_g2Repeater.IsSameAs(repeater)) { m_queryTimer.stop(); if (!address.IsEmpty()) { // Repeater found, update the settings and send the header to the correct place m_g2Address.s_addr = ::inet_addr(address.mb_str()); m_g2Repeater = repeater; m_g2Gateway = gateway; m_g2Header->setDestination(m_g2Address, G2_DV_PORT); m_g2Header->setRepeaters(m_g2Gateway, m_g2Repeater); m_g2Handler->writeHeader(*m_g2Header); delete m_g2Header; m_g2Status = G2_OK; m_g2Header = NULL; } else { // Repeater not found, remove G2 settings m_g2Status = G2_LOCAL; m_g2User.Clear(); m_g2Repeater.Clear(); m_g2Gateway.Clear(); delete m_g2Header; m_g2Header = NULL; } } if (m_linkStatus == LS_PENDING_IRCDDB && m_linkRepeater.IsSameAs(repeater)) { m_queryTimer.stop(); if (!address.IsEmpty()) { // Repeater found in_addr addr; switch (protocol) { case DP_DPLUS: if (m_dplusEnabled) { m_linkGateway = gateway; addr.s_addr = ::inet_addr(address.mb_str()); CDPlusHandler::link(this, m_rptCallsign, m_linkRepeater, addr); m_linkStatus = LS_LINKING_DPLUS; } else { wxLogMessage(wxT("Require D-Plus for linking to %s, but D-Plus is disabled"), repeater.c_str()); m_linkStatus = LS_NONE; m_linkRepeater.Clear(); m_linkGateway.Clear(); writeNotLinked(); triggerInfo(); } break; case DP_DCS: if (m_dcsEnabled) { m_linkGateway = gateway; addr.s_addr = ::inet_addr(address.mb_str()); CDCSHandler::link(this, m_rptCallsign, m_linkRepeater, addr); m_linkStatus = LS_LINKING_DCS; } else { wxLogMessage(wxT("Require DCS for linking to %s, but DCS is disabled"), repeater.c_str()); m_linkStatus = LS_NONE; m_linkRepeater.Clear(); m_linkGateway.Clear(); writeNotLinked(); triggerInfo(); } break; case DP_LOOPBACK: m_linkGateway = gateway; addr.s_addr = ::inet_addr(address.mb_str()); CDCSHandler::link(this, m_rptCallsign, m_linkRepeater, addr); m_linkStatus = LS_LINKING_LOOPBACK; break; default: if (m_dextraEnabled) { m_linkGateway = gateway; addr.s_addr = ::inet_addr(address.mb_str()); CDExtraHandler::link(this, m_rptCallsign, m_linkRepeater, addr); m_linkStatus = LS_LINKING_DEXTRA; } else { wxLogMessage(wxT("Require DExtra for linking to %s, but DExtra is disabled"), repeater.c_str()); m_linkStatus = LS_NONE; m_linkRepeater.Clear(); m_linkGateway.Clear(); writeNotLinked(); triggerInfo(); } break; } } else { // Repeater not found m_linkStatus = LS_NONE; m_linkRepeater.Clear(); m_linkGateway.Clear(); writeNotLinked(); triggerInfo(); } } } void CRepeaterHandler::clockInt(unsigned int ms) { m_infoAudio->clock(ms); m_msgAudio->clock(ms); m_wxAudio->clock(ms); m_echo->clock(ms); m_version->clock(ms); m_linkReconnectTimer.clock(ms); m_watchdogTimer.clock(ms); m_queryTimer.clock(ms); m_heardTimer.clock(ms); m_pollTimer.clock(ms); // If the reconnect timer has expired if (m_linkReconnectTimer.isRunning() && m_linkReconnectTimer.hasExpired()) { if (m_linkStatus != LS_NONE && (m_linkStartup.IsEmpty() || m_linkStartup.IsSameAs(wxT(" ")))) { // Unlink if linked to something wxLogMessage(wxT("Reconnect timer has expired, unlinking %s from %s"), m_rptCallsign.c_str(), m_linkRepeater.c_str()); CDExtraHandler::unlink(this); CDPlusHandler::unlink(this); CDCSHandler::unlink(this); m_linkStatus = LS_NONE; m_linkRepeater.Clear(); // Tell the users writeNotLinked(); triggerInfo(); } else if ((m_linkStatus == LS_NONE && !m_linkStartup.IsEmpty() && !m_linkStartup.IsSameAs(wxT(" "))) || (m_linkStatus != LS_NONE && !m_linkRepeater.IsSameAs(m_linkStartup))) { // Relink if not linked or linked to the wrong reflector wxLogMessage(wxT("Reconnect timer has expired, relinking %s to %s"), m_rptCallsign.c_str(), m_linkStartup.c_str()); // Check for just a change of letter if (m_linkStatus != LS_NONE) { wxString oldCall = m_linkRepeater.Left(LONG_CALLSIGN_LENGTH - 1U); wxString newCall = m_linkStartup.Left(LONG_CALLSIGN_LENGTH - 1U); // Just a change of port? if (oldCall.IsSameAs(newCall)) { switch (m_linkStatus) { case LS_LINKING_DEXTRA: case LS_LINKED_DEXTRA: m_linkRelink = true; m_linkRepeater = m_linkStartup; CDExtraHandler::unlink(this, m_linkRepeater); m_linkStatus = LS_LINKING_DEXTRA; writeLinkingTo(m_linkRepeater); triggerInfo(); break; case LS_LINKING_DCS: case LS_LINKED_DCS: m_linkRelink = true; m_linkRepeater = m_linkStartup; CDCSHandler::unlink(this, m_linkRepeater); m_linkStatus = LS_LINKING_DCS; writeLinkingTo(m_linkRepeater); triggerInfo(); break; case LS_LINKING_LOOPBACK: case LS_LINKED_LOOPBACK: m_linkRelink = true; m_linkRepeater = m_linkStartup; CDCSHandler::unlink(this, m_linkRepeater); m_linkStatus = LS_LINKING_LOOPBACK; writeLinkingTo(m_linkRepeater); triggerInfo(); break; case LS_LINKING_DPLUS: m_linkRepeater = m_linkStartup; CDPlusHandler::relink(this, m_linkRepeater); writeLinkingTo(m_linkRepeater); triggerInfo(); break; case LS_LINKED_DPLUS: m_linkRepeater = m_linkStartup; CDPlusHandler::relink(this, m_linkRepeater); writeLinkedTo(m_linkRepeater); triggerInfo(); break; default: break; } return; } } CDExtraHandler::unlink(this); CDPlusHandler::unlink(this); CDCSHandler::unlink(this); linkInt(m_linkStartup); } m_linkReconnectTimer.start(); } // If the ircDDB query timer has expired if (m_queryTimer.isRunning() && m_queryTimer.hasExpired()) { m_queryTimer.stop(); if (m_g2Status == G2_USER || m_g2Status == G2_REPEATER) { // User or repeater not found in time, remove G2 settings wxLogMessage(wxT("ircDDB did not reply within five seconds")); m_g2Status = G2_LOCAL; m_g2User.Clear(); m_g2Repeater.Clear(); m_g2Gateway.Clear(); delete m_g2Header; m_g2Header = NULL; } else if (m_linkStatus == LS_PENDING_IRCDDB) { // Repeater not found in time wxLogMessage(wxT("ircDDB did not reply within five seconds")); m_linkStatus = LS_NONE; m_linkRepeater.Clear(); m_linkGateway.Clear(); writeNotLinked(); triggerInfo(); } else if (m_linkStatus == LS_LINKING_CCS) { // CCS didn't reply in time wxLogMessage(wxT("CCS did not reply within five seconds")); m_ccsHandler->stopLink(); m_linkStatus = LS_NONE; m_linkRepeater.Clear(); restoreLinks(); } } // Icom heard timer has expired if (m_heardTimer.isRunning() && m_heardTimer.hasExpired() && m_irc != NULL) { m_irc->sendHeard(m_heardUser, wxT(" "), wxT(" "), m_heardRepeater, wxT(" "), 0x00U, 0x00U, 0x00U); m_heardTimer.stop(); } // If the watchdog timer has expired, clean up if (m_watchdogTimer.isRunning() && m_watchdogTimer.hasExpired()) { wxLogMessage(wxT("Radio watchdog timer for %s has expired"), m_rptCallsign.c_str()); m_watchdogTimer.stop(); if (m_repeaterId != 0x00U) { if (m_text.IsEmpty()) sendHeard(); if (m_drats != NULL) m_drats->writeEnd(); sendStats(); switch (m_g2Status) { case G2_USER: case G2_REPEATER: m_queryTimer.stop(); delete m_g2Header; m_g2Header = NULL; break; case G2_XBAND: m_xBandRptr = NULL; break; case G2_STARNET: m_starNet = NULL; break; case G2_ECHO: m_echo->end(); break; case G2_VERSION: m_version->sendVersion(); break; default: break; } if (m_infoNeeded) { m_infoAudio->sendStatus(); m_infoNeeded = false; } if (m_msgNeeded) { m_msgAudio->sendAnnouncement(); m_msgNeeded = false; } if (m_wxNeeded) { m_wxAudio->sendAnnouncement(); m_wxNeeded = false; } m_repeaterId = 0x00U; m_g2Status = G2_NONE; } if (m_busyId != 0x00U) { if (m_infoNeeded) { m_infoAudio->sendStatus(); m_infoNeeded = false; } if (m_msgNeeded) { m_msgAudio->sendAnnouncement(); m_msgNeeded = false; } if (m_wxNeeded) { m_wxAudio->sendAnnouncement(); m_wxNeeded = false; } if (m_g2Status == G2_VERSION) m_version->sendVersion(); m_g2Status = G2_NONE; m_busyId = 0x00U; } } } void CRepeaterHandler::linkUp(DSTAR_PROTOCOL protocol, const wxString& callsign) { if (protocol == DP_DEXTRA && m_linkStatus == LS_LINKING_DEXTRA) { wxLogMessage(wxT("DExtra link to %s established"), callsign.c_str()); m_linkStatus = LS_LINKED_DEXTRA; writeLinkedTo(callsign); triggerInfo(); } if (protocol == DP_DPLUS && m_linkStatus == LS_LINKING_DPLUS) { wxLogMessage(wxT("D-Plus link to %s established"), callsign.c_str()); m_linkStatus = LS_LINKED_DPLUS; writeLinkedTo(callsign); triggerInfo(); } if (protocol == DP_DCS && m_linkStatus == LS_LINKING_DCS) { wxLogMessage(wxT("DCS link to %s established"), callsign.c_str()); m_linkStatus = LS_LINKED_DCS; writeLinkedTo(callsign); triggerInfo(); } if (protocol == DP_DCS && m_linkStatus == LS_LINKING_LOOPBACK) { wxLogMessage(wxT("Loopback link to %s established"), callsign.c_str()); m_linkStatus = LS_LINKED_LOOPBACK; writeLinkedTo(callsign); triggerInfo(); } } bool CRepeaterHandler::linkFailed(DSTAR_PROTOCOL protocol, const wxString& callsign, bool isRecoverable) { // Is relink to another module required? if (!isRecoverable && m_linkRelink) { m_linkRelink = false; wxLogMessage(wxT("Relinking %s from %s to %s"), m_rptCallsign.c_str(), callsign.c_str(), m_linkRepeater.c_str()); linkInt(m_linkRepeater); return false; } // Have we linked to something else in the meantime? if (m_linkStatus == LS_NONE || !m_linkRepeater.IsSameAs(callsign)) { switch (protocol) { case DP_DCS: wxLogMessage(wxT("DCS link to %s has failed"), callsign.c_str()); break; case DP_DEXTRA: wxLogMessage(wxT("DExtra link to %s has failed"), callsign.c_str()); break; case DP_DPLUS: wxLogMessage(wxT("D-Plus link to %s has failed"), callsign.c_str()); break; default: break; } return false; } if (!isRecoverable) { if (protocol == DP_DEXTRA && callsign.IsSameAs(m_linkRepeater)) { wxLogMessage(wxT("DExtra link to %s has failed"), m_linkRepeater.c_str()); m_linkRepeater.Clear(); m_linkStatus = LS_NONE; writeNotLinked(); triggerInfo(); } if (protocol == DP_DPLUS && callsign.IsSameAs(m_linkRepeater)) { wxLogMessage(wxT("D-Plus link to %s has failed"), m_linkRepeater.c_str()); m_linkRepeater.Clear(); m_linkStatus = LS_NONE; writeNotLinked(); triggerInfo(); } if (protocol == DP_DCS && callsign.IsSameAs(m_linkRepeater)) { if (m_linkStatus == LS_LINKED_DCS || m_linkStatus == LS_LINKING_DCS) wxLogMessage(wxT("DCS link to %s has failed"), m_linkRepeater.c_str()); else wxLogMessage(wxT("Loopback link to %s has failed"), m_linkRepeater.c_str()); m_linkRepeater.Clear(); m_linkStatus = LS_NONE; writeNotLinked(); triggerInfo(); } return false; } if (protocol == DP_DEXTRA) { switch (m_linkStatus) { case LS_LINKED_DEXTRA: wxLogMessage(wxT("DExtra link to %s has failed, relinking"), m_linkRepeater.c_str()); m_linkStatus = LS_LINKING_DEXTRA; writeLinkingTo(m_linkRepeater); triggerInfo(); return true; case LS_LINKING_DEXTRA: return true; default: return false; } } if (protocol == DP_DPLUS) { switch (m_linkStatus) { case LS_LINKED_DPLUS: wxLogMessage(wxT("D-Plus link to %s has failed, relinking"), m_linkRepeater.c_str()); m_linkStatus = LS_LINKING_DPLUS; writeLinkingTo(m_linkRepeater); triggerInfo(); return true; case LS_LINKING_DPLUS: return true; default: return false; } } if (protocol == DP_DCS) { switch (m_linkStatus) { case LS_LINKED_DCS: wxLogMessage(wxT("DCS link to %s has failed, relinking"), m_linkRepeater.c_str()); m_linkStatus = LS_LINKING_DCS; writeLinkingTo(m_linkRepeater); triggerInfo(); return true; case LS_LINKED_LOOPBACK: wxLogMessage(wxT("Loopback link to %s has failed, relinking"), m_linkRepeater.c_str()); m_linkStatus = LS_LINKING_LOOPBACK; writeLinkingTo(m_linkRepeater); triggerInfo(); return true; case LS_LINKING_DCS: case LS_LINKING_LOOPBACK: return true; default: return false; } } return false; } void CRepeaterHandler::linkRefused(DSTAR_PROTOCOL protocol, const wxString& callsign) { if (protocol == DP_DEXTRA && callsign.IsSameAs(m_linkRepeater)) { wxLogMessage(wxT("DExtra link to %s was refused"), m_linkRepeater.c_str()); m_linkRepeater.Clear(); m_linkStatus = LS_NONE; writeIsBusy(callsign); triggerInfo(); } if (protocol == DP_DPLUS && callsign.IsSameAs(m_linkRepeater)) { wxLogMessage(wxT("D-Plus link to %s was refused"), m_linkRepeater.c_str()); m_linkRepeater.Clear(); m_linkStatus = LS_NONE; writeIsBusy(callsign); triggerInfo(); } if (protocol == DP_DCS && callsign.IsSameAs(m_linkRepeater)) { if (m_linkStatus == LS_LINKED_DCS || m_linkStatus == LS_LINKING_DCS) wxLogMessage(wxT("DCS link to %s was refused"), m_linkRepeater.c_str()); else wxLogMessage(wxT("Loopback link to %s was refused"), m_linkRepeater.c_str()); m_linkRepeater.Clear(); m_linkStatus = LS_NONE; writeIsBusy(callsign); triggerInfo(); } } void CRepeaterHandler::link(RECONNECT reconnect, const wxString& reflector) { // CCS removal if (m_linkStatus == LS_LINKING_CCS || m_linkStatus == LS_LINKED_CCS) { wxLogMessage(wxT("Dropping CCS link to %s"), m_linkRepeater.c_str()); m_ccsHandler->stopLink(); m_linkStatus = LS_NONE; m_linkRepeater.Clear(); m_queryTimer.stop(); } m_linkStartup = reflector; m_linkReconnect = reconnect; m_linkReconnectTimer.stop(); switch (m_linkReconnect) { case RECONNECT_5MINS: m_linkReconnectTimer.start(5U * 60U); break; case RECONNECT_10MINS: m_linkReconnectTimer.start(10U * 60U); break; case RECONNECT_15MINS: m_linkReconnectTimer.start(15U * 60U); break; case RECONNECT_20MINS: m_linkReconnectTimer.start(20U * 60U); break; case RECONNECT_25MINS: m_linkReconnectTimer.start(25U * 60U); break; case RECONNECT_30MINS: m_linkReconnectTimer.start(30U * 60U); break; case RECONNECT_60MINS: m_linkReconnectTimer.start(60U * 60U); break; case RECONNECT_90MINS: m_linkReconnectTimer.start(90U * 60U); break; case RECONNECT_120MINS: m_linkReconnectTimer.start(120U * 60U); break; case RECONNECT_180MINS: m_linkReconnectTimer.start(180U * 60U); break; default: break; } // Nothing to do if ((m_linkStatus != LS_NONE && m_linkRepeater.IsSameAs(reflector)) || (m_linkStatus == LS_NONE && (reflector.IsEmpty() || reflector.IsSameAs(wxT(" "))))) return; // Handle unlinking if (m_linkStatus != LS_NONE && (reflector.IsEmpty() || reflector.IsSameAs(wxT(" ")))) { wxLogMessage(wxT("Unlinking %s from %s"), m_rptCallsign.c_str(), m_linkRepeater.c_str()); CDExtraHandler::unlink(this); CDPlusHandler::unlink(this); CDCSHandler::unlink(this); m_linkStatus = LS_NONE; m_linkRepeater.Clear(); writeNotLinked(); triggerInfo(); return; } wxLogMessage(wxT("Linking %s to %s"), m_rptCallsign.c_str(), reflector.c_str()); // Check for just a change of letter if (m_linkStatus != LS_NONE) { wxString oldCall = m_linkRepeater.Left(LONG_CALLSIGN_LENGTH - 1U); wxString newCall = reflector.Left(LONG_CALLSIGN_LENGTH - 1U); // Just a change of port? if (oldCall.IsSameAs(newCall)) { switch (m_linkStatus) { case LS_LINKING_DEXTRA: case LS_LINKED_DEXTRA: m_linkRelink = true; m_linkRepeater = reflector; CDExtraHandler::unlink(this, m_linkRepeater); m_linkStatus = LS_LINKING_DEXTRA; writeLinkingTo(m_linkRepeater); triggerInfo(); break; case LS_LINKING_DCS: case LS_LINKED_DCS: m_linkRelink = true; m_linkRepeater = reflector; CDCSHandler::unlink(this, m_linkRepeater); m_linkStatus = LS_LINKING_DCS; writeLinkingTo(m_linkRepeater); triggerInfo(); break; case LS_LINKING_LOOPBACK: case LS_LINKED_LOOPBACK: m_linkRelink = true; m_linkRepeater = reflector; CDCSHandler::unlink(this, m_linkRepeater); m_linkStatus = LS_LINKING_LOOPBACK; writeLinkingTo(m_linkRepeater); triggerInfo(); break; case LS_LINKING_DPLUS: m_linkRepeater = reflector; CDPlusHandler::relink(this, m_linkRepeater); writeLinkingTo(m_linkRepeater); triggerInfo(); break; case LS_LINKED_DPLUS: m_linkRepeater = reflector; CDPlusHandler::relink(this, m_linkRepeater); writeLinkedTo(m_linkRepeater); triggerInfo(); break; default: break; } return; } } CDExtraHandler::unlink(this); CDPlusHandler::unlink(this); CDCSHandler::unlink(this); linkInt(m_linkStartup); } void CRepeaterHandler::unlink(PROTOCOL protocol, const wxString& reflector) { if (protocol == PROTO_CCS) { m_ccsHandler->unlink(reflector); return; } if (m_linkReconnect == RECONNECT_FIXED && m_linkRepeater.IsSameAs(reflector)) { wxLogMessage(wxT("Cannot unlink %s because it is fixed"), reflector.c_str()); return; } switch (protocol) { case PROTO_DPLUS: CDPlusHandler::unlink(this, reflector, false); break; case PROTO_DEXTRA: CDExtraHandler::unlink(this, reflector, false); break; case PROTO_DCS: CDCSHandler::unlink(this, reflector, false); break; default: break; } } void CRepeaterHandler::g2CommandHandler(const wxString& callsign, const wxString& user, CHeaderData& header) { if (m_linkStatus == LS_LINKING_CCS || m_linkStatus == LS_LINKED_CCS) return; if (callsign.Left(1).IsSameAs(wxT("/"))) { if (m_irc == NULL) { wxLogMessage(wxT("%s is trying to G2 route with ircDDB disabled"), user.c_str()); m_g2Status = G2_LOCAL; return; } // This a repeater route // Convert "/1234567" to "123456 7" wxString repeater = callsign.Mid(1, LONG_CALLSIGN_LENGTH - 2U); repeater.Append(wxT(" ")); repeater.Append(callsign.Right(1)); if (repeater.IsSameAs(m_rptCallsign)) { wxLogMessage(wxT("%s is trying to G2 route to self, ignoring"), user.c_str()); m_g2Status = G2_LOCAL; return; } if (repeater.Left(3U).IsSameAs(wxT("REF")) || repeater.Left(3U).IsSameAs(wxT("XRF")) || repeater.Left(3U).IsSameAs(wxT("DCS"))) { wxLogMessage(wxT("%s is trying to G2 route to reflector %s, ignoring"), user.c_str(), repeater.c_str()); m_g2Status = G2_LOCAL; return; } wxLogMessage(wxT("%s is trying to G2 route to repeater %s"), user.c_str(), repeater.c_str()); m_g2Repeater = repeater; m_g2User = wxT("CQCQCQ "); CRepeaterData* data = m_cache->findRepeater(m_g2Repeater); if (data == NULL) { m_g2Status = G2_REPEATER; m_irc->findRepeater(m_g2Repeater); m_g2Header = new CHeaderData(header); m_queryTimer.start(); } else { m_g2Status = G2_OK; m_g2Address = data->getAddress(); m_g2Gateway = data->getGateway(); header.setDestination(m_g2Address, G2_DV_PORT); header.setRepeaters(m_g2Gateway, m_g2Repeater); m_g2Handler->writeHeader(header); delete data; } } else if (!callsign.Right(1).IsSameAs(wxT("L")) && !callsign.Right(1).IsSameAs(wxT("U"))) { if (m_irc == NULL) { wxLogMessage(wxT("%s is trying to G2 route with ircDDB disabled"), user.c_str()); m_g2Status = G2_LOCAL; return; } // This a callsign route if (callsign.Left(3U).IsSameAs(wxT("REF")) || callsign.Left(3U).IsSameAs(wxT("XRF")) || callsign.Left(3U).IsSameAs(wxT("DCS"))) { wxLogMessage(wxT("%s is trying to G2 route to reflector %s, ignoring"), user.c_str(), callsign.c_str()); m_g2Status = G2_LOCAL; return; } wxLogMessage(wxT("%s is trying to G2 route to callsign %s"), user.c_str(), callsign.c_str()); CUserData* data = m_cache->findUser(callsign); if (data == NULL) { m_g2User = callsign; m_g2Status = G2_USER; m_irc->findUser(m_g2User); m_g2Header = new CHeaderData(header); m_queryTimer.start(); } else { // No point G2 routing to yourself if (data->getRepeater().IsSameAs(m_rptCallsign)) { m_g2Status = G2_LOCAL; delete data; return; } m_g2Status = G2_OK; m_g2User = callsign; m_g2Address = data->getAddress(); m_g2Repeater = data->getRepeater(); m_g2Gateway = data->getGateway(); header.setDestination(m_g2Address, G2_DV_PORT); header.setRepeaters(m_g2Gateway, m_g2Repeater); m_g2Handler->writeHeader(header); delete data; } } } void CRepeaterHandler::ccsCommandHandler(const wxString& callsign, const wxString& user, const wxString& type) { if (callsign.IsSameAs(wxT("CA "))) { m_ccsHandler->stopLink(user, type); } else { CCS_STATUS status = m_ccsHandler->getStatus(); if (status == CS_CONNECTED) { suspendLinks(); m_queryTimer.start(); m_linkStatus = LS_LINKING_CCS; m_linkRepeater = callsign.Mid(1U); m_ccsHandler->startLink(m_linkRepeater, user, type); } } } void CRepeaterHandler::reflectorCommandHandler(const wxString& callsign, const wxString& user, const wxString& type) { if (m_linkStatus == LS_LINKING_CCS || m_linkStatus == LS_LINKED_CCS) return; if (m_linkReconnect == RECONNECT_FIXED) return; m_queryTimer.stop(); wxString letter = callsign.Right(1); if (letter.IsSameAs(wxT("U"))) { // Ignore duplicate unlink requests if (m_linkStatus == LS_NONE) return; wxLogMessage(wxT("Unlink command issued via %s by %s"), type.c_str(), user.c_str()); CDExtraHandler::unlink(this); CDPlusHandler::unlink(this); CDCSHandler::unlink(this); m_linkStatus = LS_NONE; m_linkRepeater.Clear(); writeNotLinked(); triggerInfo(); } else if (letter.IsSameAs(wxT("L"))) { wxString reflector; // Handle the special case of " L" if (callsign.IsSameAs(wxT(" L"))) { if (m_linkStartup.IsEmpty()) return; reflector = m_linkStartup; } else { // Extract the callsign "1234567L" -> "123456 7" reflector = callsign.Left(LONG_CALLSIGN_LENGTH - 2U); reflector.Append(wxT(" ")); reflector.Append(callsign.Mid(LONG_CALLSIGN_LENGTH - 2U, 1)); } // Ensure duplicate link requests aren't acted on if (m_linkStatus != LS_NONE && reflector.IsSameAs(m_linkRepeater)) return; // We can't link to ourself if (reflector.IsSameAs(m_rptCallsign)) { wxLogMessage(wxT("%s is trying to link with self via %s, ignoring"), user.c_str(), type.c_str()); triggerInfo(); return; } wxLogMessage(wxT("Link command from %s to %s issued via %s by %s"), m_rptCallsign.c_str(), reflector.c_str(), type.c_str(), user.c_str()); // Check for just a change of letter if (m_linkStatus != LS_NONE) { wxString oldCall = m_linkRepeater.Left(LONG_CALLSIGN_LENGTH - 1U); wxString newCall = reflector.Left(LONG_CALLSIGN_LENGTH - 1U); // Just a change of port? if (oldCall.IsSameAs(newCall)) { switch (m_linkStatus) { case LS_LINKING_DEXTRA: case LS_LINKED_DEXTRA: m_linkRelink = true; m_linkRepeater = reflector; CDExtraHandler::unlink(this, m_linkRepeater); m_linkStatus = LS_LINKING_DEXTRA; writeLinkingTo(m_linkRepeater); triggerInfo(); break; case LS_LINKING_DCS: case LS_LINKED_DCS: m_linkRelink = true; m_linkRepeater = reflector; CDCSHandler::unlink(this, m_linkRepeater); m_linkStatus = LS_LINKING_DCS; writeLinkingTo(m_linkRepeater); triggerInfo(); break; case LS_LINKING_LOOPBACK: case LS_LINKED_LOOPBACK: m_linkRelink = true; m_linkRepeater = reflector; CDCSHandler::unlink(this, m_linkRepeater); m_linkStatus = LS_LINKING_LOOPBACK; writeLinkingTo(m_linkRepeater); triggerInfo(); break; case LS_LINKING_DPLUS: m_linkRepeater = reflector; CDPlusHandler::relink(this, m_linkRepeater); writeLinkingTo(m_linkRepeater); triggerInfo(); break; case LS_LINKED_DPLUS: m_linkRepeater = reflector; CDPlusHandler::relink(this, m_linkRepeater); writeLinkedTo(m_linkRepeater); triggerInfo(); break; default: break; } return; } } CDExtraHandler::unlink(this); CDPlusHandler::unlink(this); CDCSHandler::unlink(this); linkInt(reflector); } } void CRepeaterHandler::linkInt(const wxString& callsign) { // Find the repeater to link to CRepeaterData* data = m_cache->findRepeater(callsign); // Are we trying to link to an unknown DExtra, D-Plus, or DCS reflector? if (data == NULL && (callsign.Left(3U).IsSameAs(wxT("REF")) || callsign.Left(3U).IsSameAs(wxT("XRF")) || callsign.Left(3U).IsSameAs(wxT("DCS")))) { wxLogMessage(wxT("%s is unknown, ignoring link request"), callsign.c_str()); triggerInfo(); return; } m_linkRepeater = callsign; if (data != NULL) { m_linkGateway = data->getGateway(); switch (data->getProtocol()) { case DP_DPLUS: if (m_dplusEnabled) { m_linkStatus = LS_LINKING_DPLUS; CDPlusHandler::link(this, m_rptCallsign, m_linkRepeater, data->getAddress()); writeLinkingTo(m_linkRepeater); triggerInfo(); } else { wxLogMessage(wxT("Require D-Plus for linking to %s, but D-Plus is disabled"), callsign.c_str()); m_linkStatus = LS_NONE; writeNotLinked(); triggerInfo(); } break; case DP_DCS: if (m_dcsEnabled) { m_linkStatus = LS_LINKING_DCS; CDCSHandler::link(this, m_rptCallsign, m_linkRepeater, data->getAddress()); writeLinkingTo(m_linkRepeater); triggerInfo(); } else { wxLogMessage(wxT("Require DCS for linking to %s, but DCS is disabled"), callsign.c_str()); m_linkStatus = LS_NONE; writeNotLinked(); triggerInfo(); } break; case DP_LOOPBACK: m_linkStatus = LS_LINKING_LOOPBACK; CDCSHandler::link(this, m_rptCallsign, m_linkRepeater, data->getAddress()); writeLinkingTo(m_linkRepeater); triggerInfo(); break; default: if (m_dextraEnabled) { m_linkStatus = LS_LINKING_DEXTRA; CDExtraHandler::link(this, m_rptCallsign, m_linkRepeater, data->getAddress()); writeLinkingTo(m_linkRepeater); triggerInfo(); } else { wxLogMessage(wxT("Require DExtra for linking to %s, but DExtra is disabled"), callsign.c_str()); m_linkStatus = LS_NONE; writeNotLinked(); triggerInfo(); } break; } delete data; } else { if (m_irc != NULL) { m_linkStatus = LS_PENDING_IRCDDB; m_irc->findRepeater(callsign); m_queryTimer.start(); writeLinkingTo(callsign); triggerInfo(); } else { m_linkStatus = LS_NONE; writeNotLinked(); triggerInfo(); } } } void CRepeaterHandler::sendToOutgoing(const CHeaderData& header) { CHeaderData temp(header); temp.setCQCQCQ(); temp.setFlags(0x00U, 0x00U, 0x00U); // Outgoing DPlus links change the RPT1 and RPT2 values in the DPlus handler CDPlusHandler::writeHeader(this, temp, DIR_OUTGOING); // Outgoing DExtra links have the currently linked repeater/gateway // as the RPT1 and RPT2 values temp.setRepeaters(m_linkGateway, m_linkRepeater); CDExtraHandler::writeHeader(this, temp, DIR_OUTGOING); // Outgoing DCS links have the currently linked repeater and repeater callsign // as the RPT1 and RPT2 values temp.setRepeaters(m_rptCallsign, m_linkRepeater); CDCSHandler::writeHeader(this, temp, DIR_OUTGOING); } void CRepeaterHandler::sendToOutgoing(const CAMBEData& data) { CAMBEData temp(data); CDExtraHandler::writeAMBE(this, temp, DIR_OUTGOING); CDPlusHandler::writeAMBE(this, temp, DIR_OUTGOING); CDCSHandler::writeAMBE(this, temp, DIR_OUTGOING); } void CRepeaterHandler::sendToIncoming(const CHeaderData& header) { CHeaderData temp(header); temp.setCQCQCQ(); temp.setFlags(0x00U, 0x00U, 0x00U); // Incoming DPlus links temp.setRepeaters(m_rptCallsign, m_gateway); CDPlusHandler::writeHeader(this, temp, DIR_INCOMING); // Incoming DExtra links have RPT1 and RPT2 swapped temp.setRepeaters(m_gwyCallsign, m_rptCallsign); CDExtraHandler::writeHeader(this, temp, DIR_INCOMING); // Incoming DCS links have RPT1 and RPT2 swapped temp.setRepeaters(m_gwyCallsign, m_rptCallsign); CDCSHandler::writeHeader(this, temp, DIR_INCOMING); } void CRepeaterHandler::sendToIncoming(const CAMBEData& data) { CAMBEData temp(data); CDExtraHandler::writeAMBE(this, temp, DIR_INCOMING); CDPlusHandler::writeAMBE(this, temp, DIR_INCOMING); CDCSHandler::writeAMBE(this, temp, DIR_INCOMING); } void CRepeaterHandler::startupInt() { // Report our existence to ircDDB if (m_irc != NULL) { wxString callsign = m_rptCallsign; if (m_ddMode) callsign.Append(wxT("D")); if (m_frequency > 0.0) m_irc->rptrQRG(callsign, m_frequency, m_offset, m_range * 1000.0, m_agl); if (m_latitude != 0.0 && m_longitude != 0.0) m_irc->rptrQTH(callsign, m_latitude, m_longitude, m_description1, m_description2, m_url); } m_ccsHandler = new CCCSHandler(this, m_rptCallsign, m_index + 1U, m_latitude, m_longitude, m_frequency, m_offset, m_description1, m_description2, m_url, CCS_PORT + m_index); // Start up our CCS link if we are DV mode if (!m_ddMode) m_ccsHandler->connect(); // Link to a startup reflector/repeater if (m_linkAtStartup && !m_linkStartup.IsEmpty()) { wxLogMessage(wxT("Linking %s at startup to %s"), m_rptCallsign.c_str(), m_linkStartup.c_str()); // Find the repeater to link to CRepeaterData* data = m_cache->findRepeater(m_linkStartup); m_linkRepeater = m_linkStartup; if (data != NULL) { m_linkGateway = data->getGateway(); DSTAR_PROTOCOL protocol = data->getProtocol(); switch (protocol) { case DP_DPLUS: if (m_dplusEnabled) { m_linkStatus = LS_LINKING_DPLUS; CDPlusHandler::link(this, m_rptCallsign, m_linkRepeater, data->getAddress()); writeLinkingTo(m_linkRepeater); triggerInfo(); } else { wxLogMessage(wxT("Require D-Plus for linking to %s, but D-Plus is disabled"), m_linkRepeater.c_str()); m_linkStatus = LS_NONE; writeNotLinked(); triggerInfo(); } break; case DP_DCS: if (m_dcsEnabled) { m_linkStatus = LS_LINKING_DCS; CDCSHandler::link(this, m_rptCallsign, m_linkRepeater, data->getAddress()); writeLinkingTo(m_linkRepeater); triggerInfo(); } else { wxLogMessage(wxT("Require DCS for linking to %s, but DCS is disabled"), m_linkRepeater.c_str()); m_linkStatus = LS_NONE; writeNotLinked(); triggerInfo(); } break; case DP_LOOPBACK: m_linkStatus = LS_LINKING_LOOPBACK; CDCSHandler::link(this, m_rptCallsign, m_linkRepeater, data->getAddress()); writeLinkingTo(m_linkRepeater); triggerInfo(); break; default: if (m_dextraEnabled) { m_linkStatus = LS_LINKING_DEXTRA; CDExtraHandler::link(this, m_rptCallsign, m_linkRepeater, data->getAddress()); writeLinkingTo(m_linkRepeater); triggerInfo(); } else { wxLogMessage(wxT("Require DExtra for linking to %s, but DExtra is disabled"), m_linkRepeater.c_str()); m_linkStatus = LS_NONE; writeNotLinked(); triggerInfo(); } break; } delete data; } else { if (m_irc != NULL) { m_linkStatus = LS_PENDING_IRCDDB; m_irc->findRepeater(m_linkStartup); m_queryTimer.start(); writeLinkingTo(m_linkStartup); triggerInfo(); } else { m_linkStatus = LS_NONE; writeNotLinked(); triggerInfo(); } } } else { writeNotLinked(); triggerInfo(); } } void CRepeaterHandler::writeLinkingTo(const wxString &callsign) { wxString text; switch (m_language) { case TL_DEUTSCH: text.Printf(wxT("Verbinde mit %s"), callsign.c_str()); break; case TL_DANSK: text.Printf(wxT("Linker til %s"), callsign.c_str()); break; case TL_FRANCAIS: text.Printf(wxT("Connexion a %s"), callsign.c_str()); break; case TL_ITALIANO: text.Printf(wxT("In conn con %s"), callsign.c_str()); break; case TL_POLSKI: text.Printf(wxT("Linkuje do %s"), callsign.c_str()); break; case TL_ESPANOL: text.Printf(wxT("Enlazando %s"), callsign.c_str()); break; case TL_SVENSKA: text.Printf(wxT("Lankar till %s"), callsign.c_str()); break; case TL_NEDERLANDS_NL: case TL_NEDERLANDS_BE: text.Printf(wxT("Linken naar %s"), callsign.c_str()); break; case TL_NORSK: text.Printf(wxT("Kobler til %s"), callsign.c_str()); break; case TL_PORTUGUES: text.Printf(wxT("A ligar a %s"), callsign.c_str()); break; default: text.Printf(wxT("Linking to %s"), callsign.c_str()); break; } CTextData textData(m_linkStatus, callsign, text, m_address, m_port); m_repeaterHandler->writeText(textData); m_infoAudio->setStatus(m_linkStatus, m_linkRepeater, text); triggerInfo(); m_ccsHandler->setReflector(); } void CRepeaterHandler::writeLinkedTo(const wxString &callsign) { wxString text; switch (m_language) { case TL_DEUTSCH: text.Printf(wxT("Verlinkt zu %s"), callsign.c_str()); break; case TL_DANSK: text.Printf(wxT("Linket til %s"), callsign.c_str()); break; case TL_FRANCAIS: text.Printf(wxT("Connecte a %s"), callsign.c_str()); break; case TL_ITALIANO: text.Printf(wxT("Connesso a %s"), callsign.c_str()); break; case TL_POLSKI: text.Printf(wxT("Polaczony z %s"), callsign.c_str()); break; case TL_ESPANOL: text.Printf(wxT("Enlazado %s"), callsign.c_str()); break; case TL_SVENSKA: text.Printf(wxT("Lankad till %s"), callsign.c_str()); break; case TL_NEDERLANDS_NL: case TL_NEDERLANDS_BE: text.Printf(wxT("Gelinkt met %s"), callsign.c_str()); break; case TL_NORSK: text.Printf(wxT("Tilkoblet %s"), callsign.c_str()); break; case TL_PORTUGUES: text.Printf(wxT("Ligado a %s"), callsign.c_str()); break; default: text.Printf(wxT("Linked to %s"), callsign.c_str()); break; } CTextData textData(m_linkStatus, callsign, text, m_address, m_port); m_repeaterHandler->writeText(textData); m_infoAudio->setStatus(m_linkStatus, m_linkRepeater, text); triggerInfo(); m_ccsHandler->setReflector(callsign); } void CRepeaterHandler::writeNotLinked() { wxString text; switch (m_language) { case TL_DEUTSCH: text = wxT("Nicht verbunden"); break; case TL_DANSK: text = wxT("Ikke forbundet"); break; case TL_FRANCAIS: text = wxT("Non connecte"); break; case TL_ITALIANO: text = wxT("Non connesso"); break; case TL_POLSKI: text = wxT("Nie polaczony"); break; case TL_ESPANOL: text = wxT("No enlazado"); break; case TL_SVENSKA: text = wxT("Ej lankad"); break; case TL_NEDERLANDS_NL: case TL_NEDERLANDS_BE: text = wxT("Niet gelinkt"); break; case TL_NORSK: text = wxT("Ikke linket"); break; case TL_PORTUGUES: text = wxT("Desligado"); break; default: text = wxT("Not linked"); break; } CTextData textData(LS_NONE, wxEmptyString, text, m_address, m_port); m_repeaterHandler->writeText(textData); m_infoAudio->setStatus(m_linkStatus, m_linkRepeater, text); triggerInfo(); m_ccsHandler->setReflector(); } void CRepeaterHandler::writeIsBusy(const wxString& callsign) { wxString tempText; wxString text; switch (m_language) { case TL_DEUTSCH: text = wxT("Nicht verbunden"); tempText.Printf(wxT("%s ist belegt"), callsign.c_str()); break; case TL_DANSK: text = wxT("Ikke forbundet"); tempText.Printf(wxT("Optaget fra %s"), callsign.c_str()); break; case TL_FRANCAIS: text = wxT("Non connecte"); tempText.Printf(wxT("Occupe par %s"), callsign.c_str()); break; case TL_ITALIANO: text = wxT("Non connesso"); tempText.Printf(wxT("Occupado da%s"), callsign.c_str()); break; case TL_POLSKI: text = wxT("Nie polaczony"); tempText.Printf(wxT("%s jest zajety"), callsign.c_str()); break; case TL_ESPANOL: text = wxT("No enlazado"); tempText.Printf(wxT("%s ocupado"), callsign.c_str()); break; case TL_SVENSKA: text = wxT("Ej lankad"); tempText.Printf(wxT("%s ar upptagen"), callsign.c_str()); break; case TL_NEDERLANDS_NL: case TL_NEDERLANDS_BE: text = wxT("Niet gelinkt"); tempText.Printf(wxT("%s is bezet"), callsign.c_str()); break; case TL_NORSK: text = wxT("Ikke linket"); tempText.Printf(wxT("%s er opptatt"), callsign.c_str()); break; case TL_PORTUGUES: text = wxT("Desligado"); tempText.Printf(wxT("%s ocupado"), callsign.c_str()); break; default: text = wxT("Not linked"); tempText.Printf(wxT("%s is busy"), callsign.c_str()); break; } CTextData textData1(m_linkStatus, m_linkRepeater, tempText, m_address, m_port, true); m_repeaterHandler->writeText(textData1); CTextData textData2(m_linkStatus, m_linkRepeater, text, m_address, m_port); m_repeaterHandler->writeText(textData2); m_infoAudio->setStatus(m_linkStatus, m_linkRepeater, text); m_infoAudio->setTempStatus(m_linkStatus, m_linkRepeater, tempText); triggerInfo(); m_ccsHandler->setReflector(); } void CRepeaterHandler::ccsLinkMade(const wxString& callsign, DIRECTION direction) { wxString text; switch (m_language) { case TL_DEUTSCH: text.Printf(wxT("Verlinkt zu %s"), callsign.c_str()); break; case TL_DANSK: text.Printf(wxT("Linket til %s"), callsign.c_str()); break; case TL_FRANCAIS: text.Printf(wxT("Connecte a %s"), callsign.c_str()); break; case TL_ITALIANO: text.Printf(wxT("Connesso a %s"), callsign.c_str()); break; case TL_POLSKI: text.Printf(wxT("Polaczony z %s"), callsign.c_str()); break; case TL_ESPANOL: text.Printf(wxT("Enlazado %s"), callsign.c_str()); break; case TL_SVENSKA: text.Printf(wxT("Lankad till %s"), callsign.c_str()); break; case TL_NEDERLANDS_NL: case TL_NEDERLANDS_BE: text.Printf(wxT("Gelinkt met %s"), callsign.c_str()); break; case TL_NORSK: text.Printf(wxT("Tilkoblet %s"), callsign.c_str()); break; case TL_PORTUGUES: text.Printf(wxT("Ligado a %s"), callsign.c_str()); break; default: text.Printf(wxT("Linked to %s"), callsign.c_str()); break; } if (direction == DIR_OUTGOING) { suspendLinks(); m_linkStatus = LS_LINKED_CCS; m_linkRepeater = callsign; m_queryTimer.stop(); CTextData textData(m_linkStatus, callsign, text, m_address, m_port); m_repeaterHandler->writeText(textData); m_infoAudio->setStatus(m_linkStatus, m_linkRepeater, text); triggerInfo(); } else { CTextData textData(m_linkStatus, m_linkRepeater, text, m_address, m_port, true); m_repeaterHandler->writeText(textData); m_infoAudio->setTempStatus(LS_LINKED_CCS, callsign, text); triggerInfo(); } } void CRepeaterHandler::ccsLinkEnded(const wxString&, DIRECTION direction) { wxString tempText; wxString text; switch (m_language) { case TL_DEUTSCH: text = wxT("Nicht verbunden"); tempText = wxT("CCS ist beendet"); break; case TL_DANSK: text = wxT("Ikke forbundet"); tempText = wxT("CCS er afsluttet"); break; case TL_FRANCAIS: text = wxT("Non connecte"); tempText = wxT("CCS a pris fin"); break; case TL_ITALIANO: text = wxT("Non connesso"); tempText = wxT("CCS e finita"); break; case TL_POLSKI: text = wxT("Nie polaczony"); tempText = wxT("CCS zakonczyl"); break; case TL_ESPANOL: text = wxT("No enlazado"); tempText = wxT("CCS ha terminado"); break; case TL_SVENSKA: text = wxT("Ej lankad"); tempText = wxT("CCS har upphort"); break; case TL_NEDERLANDS_NL: case TL_NEDERLANDS_BE: text = wxT("Niet gelinkt"); tempText = wxT("CCS is afgelopen"); break; case TL_NORSK: text = wxT("Ikke linket"); tempText = wxT("CCS er avsluttet"); break; case TL_PORTUGUES: text = wxT("Desligado"); tempText = wxT("CCS terminou"); break; default: text = wxT("Not linked"); tempText = wxT("CCS has ended"); break; } if (direction == DIR_OUTGOING) { m_linkStatus = LS_NONE; m_linkRepeater.Clear(); m_queryTimer.stop(); bool res = restoreLinks(); if (!res) { CTextData textData1(m_linkStatus, m_linkRepeater, tempText, m_address, m_port, true); m_repeaterHandler->writeText(textData1); CTextData textData2(m_linkStatus, m_linkRepeater, text, m_address, m_port); m_repeaterHandler->writeText(textData2); m_infoAudio->setStatus(m_linkStatus, m_linkRepeater, text); m_infoAudio->setTempStatus(m_linkStatus, m_linkRepeater, tempText); triggerInfo(); } } else { CTextData textData(m_linkStatus, m_linkRepeater, tempText, m_address, m_port, true); m_repeaterHandler->writeText(textData); m_infoAudio->setTempStatus(m_linkStatus, m_linkRepeater, tempText); triggerInfo(); } } void CRepeaterHandler::ccsLinkFailed(const wxString& dtmf, DIRECTION direction) { wxString tempText; wxString text; switch (m_language) { case TL_DEUTSCH: text = wxT("Nicht verbunden"); tempText.Printf(wxT("%s unbekannt"), dtmf.c_str()); break; case TL_DANSK: text = wxT("Ikke forbundet"); tempText.Printf(wxT("%s unknown"), dtmf.c_str()); break; case TL_FRANCAIS: text = wxT("Non connecte"); tempText.Printf(wxT("%s inconnu"), dtmf.c_str()); break; case TL_ITALIANO: text = wxT("Non connesso"); tempText.Printf(wxT("Sconosciuto %s"), dtmf.c_str()); break; case TL_POLSKI: text = wxT("Nie polaczony"); tempText.Printf(wxT("%s nieznany"), dtmf.c_str()); break; case TL_ESPANOL: text = wxT("No enlazado"); tempText.Printf(wxT("Desconocido %s"), dtmf.c_str()); break; case TL_SVENSKA: text = wxT("Ej lankad"); tempText.Printf(wxT("%s okand"), dtmf.c_str()); break; case TL_NEDERLANDS_NL: case TL_NEDERLANDS_BE: text = wxT("Niet gelinkt"); tempText.Printf(wxT("%s bekend"), dtmf.c_str()); break; case TL_NORSK: text = wxT("Ikke linket"); tempText.Printf(wxT("%s ukjent"), dtmf.c_str()); break; case TL_PORTUGUES: text = wxT("Desligado"); tempText.Printf(wxT("%s desconhecido"), dtmf.c_str()); break; default: text = wxT("Not linked"); tempText.Printf(wxT("%s unknown"), dtmf.c_str()); break; } if (direction == DIR_OUTGOING) { m_linkStatus = LS_NONE; m_linkRepeater.Clear(); m_queryTimer.stop(); bool res = restoreLinks(); if (!res) { CTextData textData1(m_linkStatus, m_linkRepeater, tempText, m_address, m_port, true); m_repeaterHandler->writeText(textData1); CTextData textData2(m_linkStatus, m_linkRepeater, text, m_address, m_port); m_repeaterHandler->writeText(textData2); m_infoAudio->setStatus(m_linkStatus, m_linkRepeater, text); m_infoAudio->setTempStatus(m_linkStatus, m_linkRepeater, tempText); triggerInfo(); } } else { CTextData textData(m_linkStatus, m_linkRepeater, tempText, m_address, m_port, true); m_repeaterHandler->writeText(textData); m_infoAudio->setTempStatus(m_linkStatus, m_linkRepeater, tempText); triggerInfo(); } } void CRepeaterHandler::writeStatus(CStatusData& statusData) { for (unsigned int i = 0U; i < m_maxRepeaters; i++) { if (m_repeaters[i] != NULL) { statusData.setDestination(m_repeaters[i]->m_address, m_repeaters[i]->m_port); m_repeaters[i]->m_repeaterHandler->writeStatus(statusData); } } } void CRepeaterHandler::sendHeard(const wxString& text) { if (m_irc == NULL) return; wxString destination; if (m_g2Status == G2_OK) { destination = m_g2Repeater; } else if (m_g2Status == G2_NONE && (m_linkStatus == LS_LINKED_DPLUS || m_linkStatus == LS_LINKED_DEXTRA || m_linkStatus == LS_LINKED_DCS)) { if (m_linkRepeater.Left(3U).IsSameAs(wxT("REF")) || m_linkRepeater.Left(3U).IsSameAs(wxT("XRF")) || m_linkRepeater.Left(3U).IsSameAs(wxT("DCS"))) destination = m_linkRepeater; } m_irc->sendHeardWithTXMsg(m_myCall1, m_myCall2, m_yourCall, m_rptCall1, m_rptCall2, m_flag1, m_flag2, m_flag3, destination, text); } void CRepeaterHandler::sendStats() { if (m_irc != NULL) m_irc->sendHeardWithTXStats(m_myCall1, m_myCall2, m_yourCall, m_rptCall1, m_rptCall2, m_flag1, m_flag2, m_flag3, m_frames, m_silence, m_errors / 2U); } void CRepeaterHandler::suspendLinks() { if (m_linkStatus == LS_LINKING_DCS || m_linkStatus == LS_LINKED_DCS || m_linkStatus == LS_LINKING_DEXTRA || m_linkStatus == LS_LINKED_DEXTRA || m_linkStatus == LS_LINKING_DPLUS || m_linkStatus == LS_LINKED_DPLUS || m_linkStatus == LS_LINKING_LOOPBACK || m_linkStatus == LS_LINKED_LOOPBACK) { m_lastReflector = m_linkRepeater; m_lastReflector.Trim(); } CDPlusHandler::unlink(this); CDExtraHandler::unlink(this); CDCSHandler::unlink(this); m_linkStatus = LS_NONE; m_linkRepeater.Clear(); m_linkReconnectTimer.stop(); m_ccsHandler->setReflector(); } bool CRepeaterHandler::restoreLinks() { if (m_linkReconnect == RECONNECT_FIXED) { if (!m_lastReflector.IsEmpty()) { linkInt(m_linkStartup); m_lastReflector.Clear(); return true; } } else if (m_linkReconnect == RECONNECT_NEVER) { if (!m_lastReflector.IsEmpty()) { linkInt(m_lastReflector); m_lastReflector.Clear(); return true; } } else { m_linkReconnectTimer.start(); if (!m_lastReflector.IsEmpty()) { linkInt(m_lastReflector); m_lastReflector.Clear(); return true; } } m_lastReflector.Clear(); return false; } void CRepeaterHandler::triggerInfo() { if (!m_infoEnabled) return; // Either send the audio now, or queue it until the end of the transmission if (m_repeaterId != 0x00U || m_busyId != 0x00U) { m_infoNeeded = true; } else { m_infoAudio->sendStatus(); m_infoNeeded = false; } } bool CRepeaterHandler::isCCSCommand(const wxString& command) const { if (command.IsSameAs(wxT("CA "))) return true; wxChar c = command.GetChar(0U); if (c != wxT('C')) return false; c = command.GetChar(1U); if (c < wxT('0') || c > wxT('9')) return false; c = command.GetChar(2U); if (c < wxT('0') || c > wxT('9')) return false; c = command.GetChar(3U); if (c < wxT('0') || c > wxT('9')) return false; return true; }