You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3062 lines
79 KiB
3062 lines
79 KiB
/*
|
|
* Copyright (C) 2010-2015,2018 by Jonathan Naylor G4KLX
|
|
* Copyright (c) 2021 by Geoffrey Merck F4FXL / KC3FRA
|
|
*
|
|
* 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 <cstring>
|
|
#include <boost/algorithm/string.hpp>
|
|
|
|
#include "RepeaterHandler.h"
|
|
#include "DExtraHandler.h"
|
|
#include "DPlusHandler.h"
|
|
#include "DStarDefines.h"
|
|
#include "DCSHandler.h"
|
|
#include "HeaderData.h"
|
|
#include "DDHandler.h"
|
|
#include "AMBEData.h"
|
|
#include "Utils.h"
|
|
#include "Log.h"
|
|
#include "StringUtils.h"
|
|
|
|
|
|
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;
|
|
|
|
std::string CRepeaterHandler::m_localAddress;
|
|
CG2ProtocolHandlerPool* CRepeaterHandler::m_g2HandlerPool = NULL;
|
|
CIRCDDB* CRepeaterHandler::m_irc = NULL;
|
|
CCacheManager* CRepeaterHandler::m_cache = NULL;
|
|
std::string 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;
|
|
|
|
CAPRSHandler* CRepeaterHandler::m_outgoingAprsHandler = NULL; //handles APRS/DPRS frames coming from radio to network
|
|
CAPRSHandler* CRepeaterHandler::m_incomingAprsHandler = NULL; //handles APRS/DPRS frames coming from network to radio
|
|
|
|
CCallsignList* CRepeaterHandler::m_restrictList = NULL;
|
|
|
|
CRepeaterHandler::CRepeaterHandler(const std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, bool dratsEnabled, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& 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),
|
|
#ifdef USE_STARNET
|
|
m_starNet(NULL),
|
|
#endif
|
|
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),
|
|
#ifdef USE_ANNOUNCE
|
|
m_msgAudio(NULL),
|
|
m_msgNeeded(false),
|
|
m_wxAudio(NULL),
|
|
m_wxNeeded(false),
|
|
#endif
|
|
m_version(NULL),
|
|
m_drats(NULL),
|
|
m_dtmf(),
|
|
m_pollTimer(1000U, 900U), // 15 minutes
|
|
#ifdef USE_CSS
|
|
m_ccsHandler(NULL),
|
|
#endif
|
|
m_lastReflector(),
|
|
m_heardUser(),
|
|
m_heardRepeater(),
|
|
m_heardTimer(1000U, 0U, 100U) // 100ms
|
|
{
|
|
assert(!callsign.empty());
|
|
assert(port > 0U);
|
|
assert(handler != NULL);
|
|
|
|
m_ddMode = band.length() > 1U;
|
|
|
|
m_band = band[0U];
|
|
|
|
m_rptCallsign = callsign;
|
|
m_rptCallsign += " ";
|
|
m_rptCallsign = m_rptCallsign.substr(0, LONG_CALLSIGN_LENGTH - 1U);
|
|
m_rptCallsign += band;
|
|
m_rptCallsign = m_rptCallsign.substr(0, LONG_CALLSIGN_LENGTH);
|
|
|
|
m_gwyCallsign = callsign;
|
|
m_gwyCallsign += " ";
|
|
m_gwyCallsign = m_gwyCallsign.substr(0, LONG_CALLSIGN_LENGTH - 1U);
|
|
m_gwyCallsign += "G";
|
|
|
|
m_address.s_addr = ::inet_addr(address.c_str());
|
|
|
|
m_pollTimer.start();
|
|
|
|
setReconnectTimer(m_linkReconnect);
|
|
|
|
#ifdef USE_ANNOUNCE
|
|
wxFileName messageFile;
|
|
messageFile.SetPath(::wxGetHomeDir());
|
|
messageFile.SetName("message"));
|
|
messageFile.SetExt("dvtool"));
|
|
|
|
wxFileName weatherFile;
|
|
weatherFile.SetPath(::wxGetHomeDir());
|
|
weatherFile.SetName("weather"));
|
|
weatherFile.SetExt("dvtool"));
|
|
|
|
m_msgAudio = new CAnnouncementUnit(this, callsign, messageFile.GetFullPath(), "MSG"));
|
|
m_wxAudio = new CAnnouncementUnit(this, callsign, weatherFile.GetFullPath(), "WX"));
|
|
#endif
|
|
|
|
m_echo = new CEchoUnit(this, callsign);
|
|
m_infoAudio = new CAudioUnit(this, callsign);
|
|
m_version = new CVersionUnit(this, callsign);
|
|
m_aprsUnit = new CAPRSUnit(this);
|
|
|
|
|
|
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;
|
|
#ifdef USE_ANNOUNCE
|
|
delete m_msgAudio;
|
|
delete m_wxAudio;
|
|
#endif
|
|
delete m_version;
|
|
|
|
if (m_drats != NULL)
|
|
m_drats->close();
|
|
}
|
|
|
|
void CRepeaterHandler::initialise(unsigned int maxRepeaters)
|
|
{
|
|
assert(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 std::string& callsign, const std::string& band, const std::string& address, unsigned int port, HW_TYPE hwType, const std::string& reflector, bool atStartup, RECONNECT reconnect, bool dratsEnabled, double frequency, double offset, double range, double latitude, double longitude, double agl, const std::string& description1, const std::string& description2, const std::string& url, IRepeaterProtocolHandler* handler, unsigned char band1, unsigned char band2, unsigned char band3)
|
|
{
|
|
assert(!callsign.empty());
|
|
assert(port > 0U);
|
|
assert(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;
|
|
}
|
|
}
|
|
|
|
CLog::logError("Cannot add repeater with callsign %s, no space", callsign.c_str());
|
|
|
|
delete repeater;
|
|
}
|
|
|
|
void CRepeaterHandler::setG2HandlerPool(CG2ProtocolHandlerPool* handler)
|
|
{
|
|
assert(handler != NULL);
|
|
|
|
m_g2HandlerPool = handler;
|
|
}
|
|
|
|
void CRepeaterHandler::setCache(CCacheManager* cache)
|
|
{
|
|
assert(cache != NULL);
|
|
|
|
m_cache = cache;
|
|
}
|
|
|
|
void CRepeaterHandler::setIRC(CIRCDDB* irc)
|
|
{
|
|
assert(irc != NULL);
|
|
|
|
m_irc = irc;
|
|
}
|
|
|
|
void CRepeaterHandler::setGateway(const std::string& 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::setAPRSHandlers(CAPRSHandler* outgoingAprsHandler, CAPRSHandler* incomingAprsHandler)
|
|
{
|
|
m_outgoingAprsHandler = outgoingAprsHandler;
|
|
m_incomingAprsHandler = incomingAprsHandler;
|
|
}
|
|
|
|
void CRepeaterHandler::setLocalAddress(const std::string& address)
|
|
{
|
|
m_localAddress = address;
|
|
}
|
|
|
|
void CRepeaterHandler::setRestrictList(CCallsignList* list)
|
|
{
|
|
assert(list != NULL);
|
|
|
|
m_restrictList = list;
|
|
}
|
|
|
|
bool CRepeaterHandler::getRepeater(unsigned int n, std::string& callsign, LINK_STATUS& linkStatus, std::string& 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 std::string &user, const std::string& repeater, const std::string& gateway, const std::string &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 std::string& repeater, const std::string& gateway, const std::string &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;
|
|
}
|
|
|
|
delete[] m_repeaters;
|
|
}
|
|
|
|
CRepeaterHandler* CRepeaterHandler::findDVRepeater(const CHeaderData& header)
|
|
{
|
|
std::string 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 == 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 std::string& 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 == 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)
|
|
{
|
|
std::string 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 == 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;
|
|
}
|
|
|
|
std::vector<std::string> CRepeaterHandler::listDVRepeaters()
|
|
{
|
|
std::vector<std::string> repeaters;
|
|
|
|
for (unsigned int i = 0U; i < m_maxRepeaters; i++) {
|
|
CRepeaterHandler* repeater = m_repeaters[i];
|
|
if (repeater != NULL && !repeater->m_ddMode)
|
|
repeaters.push_back(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;
|
|
CLog::logInfo("Repeater %s registered with bands %u %u %u", m_rptCall1.c_str(), m_band1, m_band2, m_band3);
|
|
}
|
|
}
|
|
|
|
if (m_flag1 == 0x01) {
|
|
CLog::logInfo("Received a busy message from repeater %s", m_rptCall1.c_str());
|
|
return;
|
|
}
|
|
|
|
if (!m_heardUser.empty() && m_myCall1 != m_heardUser && m_irc != NULL)
|
|
m_irc->sendHeard(m_heardUser, " ", " ", m_heardRepeater, " ", 0x00U, 0x00U, 0x00U);
|
|
|
|
#ifdef USE_CCS
|
|
// Inform CCS
|
|
m_ccsHandler->writeHeard(header);
|
|
m_ccsHandler->writeHeader(header);
|
|
#endif
|
|
|
|
// 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_outgoingAprsHandler != NULL)
|
|
m_outgoingAprsHandler->writeHeader(m_rptCallsign, header);
|
|
|
|
// Write to Header.log if it's enabled
|
|
if (m_headerLogger != NULL)
|
|
m_headerLogger->write("Repeater", header);
|
|
|
|
// Reset the DTMF decoder
|
|
m_dtmf.reset();
|
|
|
|
// Reset the info, echo and version commands if they're running
|
|
m_infoAudio->cancel();
|
|
#ifdef USE_ANNOUNCE
|
|
m_msgAudio->cancel();
|
|
m_wxAudio->cancel();
|
|
#endif
|
|
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;
|
|
#ifdef USE_STARNET
|
|
m_starNet = NULL;
|
|
#endif
|
|
|
|
// 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 == (m_rptCallsign) || m_rptCall2 == (" "))
|
|
return;
|
|
|
|
// Do cross-band routing if RPT2 is not one of the gateway callsigns
|
|
if (m_rptCall2 != (m_gwyCallsign) && m_rptCall2 != m_gateway) {
|
|
CRepeaterHandler* repeater = findDVRepeater(m_rptCall2);
|
|
if (repeater != NULL) {
|
|
CLog::logInfo("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
|
|
CLog::logInfo("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;
|
|
}
|
|
|
|
#ifdef USE_STARNET
|
|
m_starNet = CStarNetHandler::findStarNet(header);
|
|
if (m_starNet != NULL && !m_restricted) {
|
|
CLog::logInfo("StarNet routing by %s to %s", m_myCall1.c_str(), m_yourCall.c_str());
|
|
m_starNet->process(header);
|
|
m_g2Status = G2_STARNET;
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
// Reject simple cases
|
|
if (m_yourCall.substr(0,4) == ("CQCQ")) {
|
|
sendToOutgoing(header);
|
|
return;
|
|
}
|
|
|
|
// Handle the Echo command
|
|
if (m_echoEnabled && m_yourCall == (" E")) {
|
|
m_g2Status = G2_ECHO;
|
|
m_echo->writeHeader(header);
|
|
return;
|
|
}
|
|
|
|
// Handle the Info command
|
|
if (m_infoEnabled && m_yourCall == (" I")) {
|
|
m_g2Status = G2_LOCAL;
|
|
m_infoNeeded = true;
|
|
return;
|
|
}
|
|
|
|
#ifdef USE_ANNOUNCE
|
|
// Handle the MSG command
|
|
if (m_infoEnabled && m_yourCall == (" M")) {
|
|
m_g2Status = G2_LOCAL;
|
|
m_msgNeeded = true;
|
|
return;
|
|
}
|
|
|
|
// Handle the WX command
|
|
if (m_infoEnabled && m_yourCall == (" W")) {
|
|
m_g2Status = G2_LOCAL;
|
|
m_wxNeeded = true;
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
// Handle the Version command
|
|
if (m_infoEnabled && m_yourCall == (" V")) {
|
|
m_g2Status = G2_VERSION;
|
|
sendToOutgoing(header);
|
|
return;
|
|
}
|
|
|
|
if (m_restricted) {
|
|
sendToOutgoing(header);
|
|
return;
|
|
}
|
|
|
|
#ifdef USE_CCS
|
|
if (isCCSCommand(m_yourCall)) {
|
|
ccsCommandHandler(m_yourCall, m_myCall1, "UR Call"));
|
|
sendToOutgoing(header);
|
|
} else
|
|
#endif
|
|
{
|
|
g2CommandHandler(m_yourCall, m_myCall1, header);
|
|
|
|
if (m_g2Status == G2_NONE) {
|
|
reflectorCommandHandler(m_yourCall, m_myCall1, "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) {
|
|
std::string 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.substr(0, 4U) == "CQCQ") {
|
|
if (command.empty()) {
|
|
// Do nothing
|
|
}
|
|
#ifdef USE_CCS
|
|
else if (isCCSCommand(command)) {
|
|
ccsCommandHandler(command, m_myCall1, "DTMF"));
|
|
}
|
|
#endif
|
|
else if (command == " I") {
|
|
m_infoNeeded = true;
|
|
} else {
|
|
reflectorCommandHandler(command, m_myCall1, "DTMF");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Incoming links get everything
|
|
sendToIncoming(data);
|
|
|
|
#ifdef USE_CCS
|
|
// CCS gets everything
|
|
m_ccsHandler->writeAMBE(data);
|
|
#endif
|
|
|
|
if (m_drats != NULL)
|
|
m_drats->writeData(data);
|
|
|
|
if (m_outgoingAprsHandler != NULL)
|
|
m_outgoingAprsHandler->writeData(m_rptCallsign, data);
|
|
|
|
if (m_text.empty() && !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.empty())
|
|
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_g2HandlerPool->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;
|
|
|
|
#ifdef USE_STARNET
|
|
case G2_STARNET:
|
|
m_starNet->process(data);
|
|
|
|
if (data.isEnd()) {
|
|
m_repeaterId = 0x00U;
|
|
m_g2Status = G2_NONE;
|
|
m_starNet = NULL;
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
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;
|
|
}
|
|
|
|
#ifdef USE_ANNOUNCE
|
|
if (data.isEnd() && m_msgNeeded) {
|
|
m_msgAudio->sendAnnouncement();
|
|
m_msgNeeded = false;
|
|
}
|
|
|
|
if (data.isEnd() && m_wxNeeded) {
|
|
m_wxAudio->sendAnnouncement();
|
|
m_wxNeeded = false;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// 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;
|
|
|
|
std::string rptCall1 = header.getRptCall1();
|
|
std::string 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;
|
|
CLog::logInfo("Repeater %s registered with bands %u %u %u", rptCall1.c_str(), m_band1, m_band2, m_band3);
|
|
}
|
|
}
|
|
|
|
if (header.getFlag1() == 0x01) {
|
|
CLog::logInfo("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 != m_gwyCallsign && rptCall2 != 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.substr(0,4) == "CQCQ" || m_yourCall == " E" || m_yourCall == " I")
|
|
return;
|
|
|
|
#ifdef USE_CCS
|
|
if (isCCSCommand(m_yourCall))
|
|
ccsCommandHandler(m_yourCall, m_myCall1, "background UR Call");
|
|
else
|
|
#endif
|
|
reflectorCommandHandler(m_yourCall, m_myCall1, "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) {
|
|
std::string 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.substr(0,4U) == "CQCQ") {
|
|
if (command.empty()) {
|
|
// Do nothing
|
|
}
|
|
#ifdef USE_CCS
|
|
else if (isCCSCommand(command)) {
|
|
ccsCommandHandler(command, m_myCall1, "background DTMF");
|
|
}
|
|
#endif
|
|
else if (command == " I") {
|
|
// Do nothing
|
|
} else {
|
|
reflectorCommandHandler(command, m_myCall1, "background DTMF");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (data.isEnd()) {
|
|
if (m_infoNeeded) {
|
|
m_infoAudio->sendStatus();
|
|
m_infoNeeded = false;
|
|
}
|
|
#ifdef USE_ANNOUNCE
|
|
if (m_msgNeeded) {
|
|
m_msgAudio->sendAnnouncement();
|
|
m_msgNeeded = false;
|
|
}
|
|
|
|
if (m_wxNeeded) {
|
|
m_wxAudio->sendAnnouncement();
|
|
m_wxNeeded = false;
|
|
}
|
|
#endif
|
|
|
|
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, " ", " ", m_heardRepeater, " ", 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;
|
|
|
|
std::string callsign = m_rptCallsign;
|
|
if (m_ddMode)
|
|
callsign += "D";
|
|
|
|
std::string text = data.getData1();
|
|
|
|
m_irc->kickWatchdog(callsign, text);
|
|
|
|
m_pollTimer.start();
|
|
}
|
|
|
|
void CRepeaterHandler::processRepeater(CDDData& data)
|
|
{
|
|
if (!m_ddMode)
|
|
return;
|
|
|
|
if (m_ddCallsign.empty()) {
|
|
m_ddCallsign = data.getYourCall();
|
|
CLog::logInfo("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, " ");
|
|
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(" ");
|
|
|
|
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;
|
|
|
|
if(m_incomingAprsHandler != nullptr)
|
|
m_incomingAprsHandler->writeHeader(m_rptCallsign, header);
|
|
|
|
sendToIncoming(header);
|
|
|
|
#ifdef USE_CCS
|
|
if (source == AS_DPLUS || source == AS_DEXTRA || source == AS_DCS)
|
|
m_ccsHandler->writeHeader(header);
|
|
#endif
|
|
|
|
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);
|
|
|
|
if(m_incomingAprsHandler != nullptr)
|
|
m_incomingAprsHandler->writeData(m_rptCallsign, data);
|
|
|
|
sendToIncoming(data);
|
|
|
|
#ifdef USE_CCS
|
|
if (source == AS_DPLUS || source == AS_DEXTRA || source == AS_DCS)
|
|
m_ccsHandler->writeAMBE(data);
|
|
#endif
|
|
|
|
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.empty() && !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 std::string& user, const std::string& repeater, const std::string& gateway, const std::string &address)
|
|
{
|
|
if (m_g2Status == G2_USER && m_g2User == user) {
|
|
m_queryTimer.stop();
|
|
|
|
if (!address.empty()) {
|
|
// No point routing to self
|
|
if (repeater == 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.c_str());
|
|
|
|
m_g2Repeater = repeater;
|
|
m_g2Gateway = gateway;
|
|
|
|
m_g2Header->setDestination(m_g2Address, G2_DV_PORT);
|
|
m_g2Header->setRepeaters(m_g2Gateway, m_g2Repeater);
|
|
m_g2HandlerPool->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 std::string& repeater, const std::string& gateway, const std::string &address, DSTAR_PROTOCOL protocol)
|
|
{
|
|
if (m_g2Status == G2_REPEATER && m_g2Repeater == repeater) {
|
|
m_queryTimer.stop();
|
|
|
|
if (!address.empty()) {
|
|
// Repeater found, update the settings and send the header to the correct place
|
|
m_g2Address.s_addr = ::inet_addr(address.c_str());
|
|
|
|
m_g2Repeater = repeater;
|
|
m_g2Gateway = gateway;
|
|
|
|
m_g2Header->setDestination(m_g2Address, G2_DV_PORT);
|
|
m_g2Header->setRepeaters(m_g2Gateway, m_g2Repeater);
|
|
m_g2HandlerPool->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 == repeater) {
|
|
m_queryTimer.stop();
|
|
|
|
if (!address.empty()) {
|
|
// Repeater found
|
|
in_addr addr;
|
|
switch (protocol) {
|
|
case DP_DPLUS:
|
|
if (m_dplusEnabled) {
|
|
m_linkGateway = gateway;
|
|
unsigned int localPort = 0U;
|
|
addr.s_addr = ::inet_addr(address.c_str());
|
|
CDPlusHandler::link(this, m_rptCallsign, m_linkRepeater, addr, localPort);
|
|
m_linkStatus = LS_LINKING_DPLUS;
|
|
} else {
|
|
CLog::logInfo("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.c_str());
|
|
CDCSHandler::link(this, m_rptCallsign, m_linkRepeater, addr);
|
|
m_linkStatus = LS_LINKING_DCS;
|
|
} else {
|
|
CLog::logInfo("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.c_str());
|
|
CDCSHandler::link(this, m_rptCallsign, m_linkRepeater, addr);
|
|
m_linkStatus = LS_LINKING_LOOPBACK;
|
|
break;
|
|
|
|
default:
|
|
if (m_dextraEnabled) {
|
|
m_linkGateway = gateway;
|
|
unsigned int localPort = 0U;
|
|
addr.s_addr = ::inet_addr(address.c_str());
|
|
CDExtraHandler::link(this, m_rptCallsign, m_linkRepeater, addr, localPort);
|
|
m_linkStatus = LS_LINKING_DEXTRA;
|
|
} else {
|
|
CLog::logInfo("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);
|
|
#ifdef USE_ANNOUNCE
|
|
m_msgAudio->clock(ms);
|
|
m_wxAudio->clock(ms);
|
|
#endif
|
|
m_echo->clock(ms);
|
|
m_version->clock(ms);
|
|
|
|
if(m_aprsUnit != nullptr)
|
|
m_aprsUnit->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.empty() || m_linkStartup == " ")) {
|
|
// Unlink if linked to something
|
|
CLog::logInfo("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.empty() && m_linkStartup != " ") ||
|
|
(m_linkStatus != LS_NONE && m_linkRepeater != m_linkStartup)) {
|
|
// Relink if not linked or linked to the wrong reflector
|
|
CLog::logInfo("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) {
|
|
std::string oldCall = m_linkRepeater.substr(0,LONG_CALLSIGN_LENGTH - 1U);
|
|
std::string newCall = m_linkStartup.substr(0,LONG_CALLSIGN_LENGTH - 1U);
|
|
|
|
// Just a change of port?
|
|
if (oldCall == 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);
|
|
}
|
|
|
|
setReconnectTimer(m_linkReconnect);
|
|
}
|
|
|
|
// 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
|
|
CLog::logInfo("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
|
|
CLog::logInfo("ircDDB did not reply within five seconds");
|
|
|
|
m_linkStatus = LS_NONE;
|
|
m_linkRepeater.clear();
|
|
m_linkGateway.clear();
|
|
|
|
writeNotLinked();
|
|
triggerInfo();
|
|
}
|
|
#ifdef USE_CCS
|
|
else if (m_linkStatus == LS_LINKING_CCS) {
|
|
// CCS didn't reply in time
|
|
CLog::logInfo("CCS did not reply within five seconds"));
|
|
|
|
m_ccsHandler->stopLink();
|
|
|
|
m_linkStatus = LS_NONE;
|
|
m_linkRepeater.clear();
|
|
|
|
restoreLinks();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Icom heard timer has expired
|
|
if (m_heardTimer.isRunning() && m_heardTimer.hasExpired() && m_irc != NULL) {
|
|
m_irc->sendHeard(m_heardUser, " ", " ", m_heardRepeater, " ", 0x00U, 0x00U, 0x00U);
|
|
m_heardTimer.stop();
|
|
}
|
|
|
|
// If the watchdog timer has expired, clean up
|
|
if (m_watchdogTimer.isRunning() && m_watchdogTimer.hasExpired()) {
|
|
CLog::logInfo("Radio watchdog timer for %s has expired", m_rptCallsign.c_str());
|
|
m_watchdogTimer.stop();
|
|
|
|
if (m_repeaterId != 0x00U) {
|
|
if (m_text.empty())
|
|
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;
|
|
#ifdef USE_STARNET
|
|
case G2_STARNET:
|
|
m_starNet = NULL;
|
|
break;
|
|
#endif
|
|
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;
|
|
}
|
|
|
|
#ifdef USE_ANNOUNCE
|
|
if (m_msgNeeded) {
|
|
m_msgAudio->sendAnnouncement();
|
|
m_msgNeeded = false;
|
|
}
|
|
|
|
if (m_wxNeeded) {
|
|
m_wxAudio->sendAnnouncement();
|
|
m_wxNeeded = false;
|
|
}
|
|
#endif
|
|
|
|
m_repeaterId = 0x00U;
|
|
m_g2Status = G2_NONE;
|
|
}
|
|
|
|
if (m_busyId != 0x00U) {
|
|
if (m_infoNeeded) {
|
|
m_infoAudio->sendStatus();
|
|
m_infoNeeded = false;
|
|
}
|
|
#ifdef USE_ANNOUNCE
|
|
if (m_msgNeeded) {
|
|
m_msgAudio->sendAnnouncement();
|
|
m_msgNeeded = false;
|
|
}
|
|
|
|
if (m_wxNeeded) {
|
|
m_wxAudio->sendAnnouncement();
|
|
m_wxNeeded = false;
|
|
}
|
|
#endif
|
|
|
|
if (m_g2Status == G2_VERSION)
|
|
m_version->sendVersion();
|
|
|
|
m_g2Status = G2_NONE;
|
|
m_busyId = 0x00U;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CRepeaterHandler::linkUp(DSTAR_PROTOCOL protocol, const std::string& callsign)
|
|
{
|
|
if (protocol == DP_DEXTRA && m_linkStatus == LS_LINKING_DEXTRA) {
|
|
CLog::logInfo("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) {
|
|
CLog::logInfo("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) {
|
|
CLog::logInfo("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) {
|
|
CLog::logInfo("Loopback link to %s established", callsign.c_str());
|
|
m_linkStatus = LS_LINKED_LOOPBACK;
|
|
writeLinkedTo(callsign);
|
|
triggerInfo();
|
|
}
|
|
}
|
|
|
|
bool CRepeaterHandler::linkFailed(DSTAR_PROTOCOL protocol, const std::string& callsign, bool isRecoverable)
|
|
{
|
|
// Is relink to another module required?
|
|
if (!isRecoverable && m_linkRelink) {
|
|
m_linkRelink = false;
|
|
CLog::logInfo("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 != callsign) {
|
|
switch (protocol) {
|
|
case DP_DCS:
|
|
CLog::logInfo("DCS link to %s has failed", callsign.c_str());
|
|
break;
|
|
case DP_DEXTRA:
|
|
CLog::logInfo("DExtra link to %s has failed", callsign.c_str());
|
|
break;
|
|
case DP_DPLUS:
|
|
CLog::logInfo("D-Plus link to %s has failed", callsign.c_str());
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if (!isRecoverable) {
|
|
if (protocol == DP_DEXTRA && callsign == m_linkRepeater) {
|
|
CLog::logInfo("DExtra link to %s has failed", m_linkRepeater.c_str());
|
|
m_linkRepeater.clear();
|
|
m_linkStatus = LS_NONE;
|
|
writeNotLinked();
|
|
triggerInfo();
|
|
}
|
|
|
|
if (protocol == DP_DPLUS && callsign == m_linkRepeater) {
|
|
CLog::logInfo("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 == m_linkRepeater) {
|
|
if (m_linkStatus == LS_LINKED_DCS || m_linkStatus == LS_LINKING_DCS)
|
|
CLog::logInfo("DCS link to %s has failed", m_linkRepeater.c_str());
|
|
else
|
|
CLog::logInfo("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:
|
|
CLog::logInfo("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:
|
|
CLog::logInfo("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:
|
|
CLog::logInfo("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:
|
|
CLog::logInfo("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 std::string& callsign)
|
|
{
|
|
if (protocol == DP_DEXTRA && callsign == m_linkRepeater) {
|
|
CLog::logInfo("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 == m_linkRepeater) {
|
|
CLog::logInfo("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 == m_linkRepeater) {
|
|
if (m_linkStatus == LS_LINKED_DCS || m_linkStatus == LS_LINKING_DCS)
|
|
CLog::logInfo("DCS link to %s was refused", m_linkRepeater.c_str());
|
|
else
|
|
CLog::logInfo("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 std::string& reflector)
|
|
{
|
|
#ifdef USE_CCS
|
|
// CCS removal
|
|
if (m_linkStatus == LS_LINKING_CCS || m_linkStatus == LS_LINKED_CCS) {
|
|
CLog::logInfo("Dropping CCS link to %s", m_linkRepeater.c_str());
|
|
|
|
m_ccsHandler->stopLink();
|
|
|
|
m_linkStatus = LS_NONE;
|
|
m_linkRepeater.clear();
|
|
m_queryTimer.stop();
|
|
}
|
|
#endif
|
|
|
|
m_linkReconnectTimer.stop();
|
|
|
|
setReconnectTimer(reconnect);
|
|
|
|
// Nothing to do
|
|
if ((m_linkStatus != LS_NONE && m_linkRepeater == reflector) ||
|
|
(m_linkStatus == LS_NONE && (reflector.empty() || reflector == " ")))
|
|
return;
|
|
|
|
// Handle unlinking
|
|
if (m_linkStatus != LS_NONE && (reflector.empty() || reflector == " ")) {
|
|
CLog::logInfo("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;
|
|
}
|
|
|
|
CLog::logInfo("Linking %s to %s", m_rptCallsign.c_str(), reflector.c_str());
|
|
|
|
// Check for just a change of letter
|
|
if (m_linkStatus != LS_NONE) {
|
|
std::string oldCall = m_linkRepeater.substr(0,LONG_CALLSIGN_LENGTH - 1U);
|
|
std::string newCall = reflector.substr(0,LONG_CALLSIGN_LENGTH - 1U);
|
|
|
|
// Just a change of port?
|
|
if (oldCall == 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::unlink(PROTOCOL protocol, const std::string& reflector)
|
|
{
|
|
#ifdef USE_CCS
|
|
if (protocol == PROTO_CCS) {
|
|
m_ccsHandler->unlink(reflector);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (m_linkReconnect == RECONNECT_FIXED && m_linkRepeater == (reflector)) {
|
|
CLog::logInfo("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 std::string& callsign, const std::string& user, CHeaderData& header)
|
|
{
|
|
if (m_linkStatus == LS_LINKING_CCS || m_linkStatus == LS_LINKED_CCS)
|
|
return;
|
|
|
|
if (!callsign.empty() && callsign[0] == '/') {
|
|
if (m_irc == NULL) {
|
|
CLog::logInfo("%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"
|
|
std::string repeater = callsign.substr(1, LONG_CALLSIGN_LENGTH - 2U);
|
|
repeater += " ";
|
|
repeater += callsign.substr(callsign.length() - 1, 1);
|
|
|
|
if (repeater == m_rptCallsign) {
|
|
CLog::logInfo("%s is trying to G2 route to self, ignoring", user.c_str());
|
|
m_g2Status = G2_LOCAL;
|
|
return;
|
|
}
|
|
|
|
if (repeater.substr(0,3U) == "REF" || repeater.substr(0,3U) == "XRF" || repeater.substr(0,3U) == "DCS" || repeater.substr(0,3U) == "XLX") {
|
|
CLog::logInfo("%s is trying to G2 route to reflector %s, ignoring", user.c_str(), repeater.c_str());
|
|
m_g2Status = G2_LOCAL;
|
|
return;
|
|
}
|
|
|
|
CLog::logInfo("%s is trying to G2 route to repeater %s", user.c_str(), repeater.c_str());
|
|
|
|
m_g2Repeater = repeater;
|
|
m_g2User = "CQCQCQ ";
|
|
|
|
CRepeaterData* data = m_cache->findRepeater(m_g2Repeater);
|
|
m_irc->notifyRepeaterG2NatTraversal(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_g2HandlerPool->writeHeader(header);
|
|
delete data;
|
|
}
|
|
} else if (string_right(callsign, 1) != "L" && string_right(callsign, 1) != "U") {
|
|
if (m_irc == NULL) {
|
|
CLog::logInfo("%s is trying to G2 route with ircDDB disabled", user.c_str());
|
|
m_g2Status = G2_LOCAL;
|
|
return;
|
|
}
|
|
|
|
// This a callsign route
|
|
if (callsign.substr(0,3U) == "REF" || callsign.substr(0,3U) == "XRF" || callsign.substr(0,3U) == "DCS" || callsign.substr(0,3U) == "XLX") {
|
|
CLog::logInfo("%s is trying to G2 route to reflector %s, ignoring", user.c_str(), callsign.c_str());
|
|
m_g2Status = G2_LOCAL;
|
|
return;
|
|
}
|
|
|
|
CLog::logInfo("%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() == 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_irc->notifyRepeaterG2NatTraversal(m_g2Repeater);
|
|
m_g2Gateway = data->getGateway();
|
|
header.setDestination(m_g2Address, G2_DV_PORT);
|
|
header.setRepeaters(m_g2Gateway, m_g2Repeater);
|
|
m_g2HandlerPool->writeHeader(header);
|
|
|
|
delete data;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef USE_CCS
|
|
void CRepeaterHandler::ccsCommandHandler(const std::string& callsign, const std::string& user, const std::string& type)
|
|
{
|
|
if (callsign.IsSameAs("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);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void CRepeaterHandler::reflectorCommandHandler(const std::string& callsign, const std::string& user, const std::string& type)
|
|
{
|
|
if (m_linkStatus == LS_LINKING_CCS || m_linkStatus == LS_LINKED_CCS)
|
|
return;
|
|
|
|
if (m_linkReconnect == RECONNECT_FIXED)
|
|
return;
|
|
|
|
m_queryTimer.stop();
|
|
|
|
std::string letter = string_right(callsign,1);
|
|
|
|
if (letter == "U") {
|
|
// Ignore duplicate unlink requests
|
|
if (m_linkStatus == LS_NONE)
|
|
return;
|
|
|
|
CLog::logInfo("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 == "L") {
|
|
std::string reflector;
|
|
|
|
// Handle the special case of " L"
|
|
if (callsign == " L") {
|
|
if (m_linkStartup.empty())
|
|
return;
|
|
|
|
reflector = m_linkStartup;
|
|
} else {
|
|
// Extract the callsign "1234567L" -> "123456 7"
|
|
reflector = callsign.substr(0,LONG_CALLSIGN_LENGTH - 2U);
|
|
reflector += " ";
|
|
reflector += callsign.substr(LONG_CALLSIGN_LENGTH - 2U, 1);
|
|
}
|
|
|
|
// Ensure duplicate link requests aren't acted on
|
|
if (m_linkStatus != LS_NONE && reflector == m_linkRepeater)
|
|
return;
|
|
|
|
// We can't link to ourself
|
|
if (reflector == m_rptCallsign) {
|
|
CLog::logInfo("%s is trying to link with self via %s, ignoring", user.c_str(), type.c_str());
|
|
triggerInfo();
|
|
return;
|
|
}
|
|
|
|
CLog::logInfo("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) {
|
|
std::string oldCall = m_linkRepeater.substr(0,LONG_CALLSIGN_LENGTH - 1U);
|
|
std::string newCall = reflector.substr(0,LONG_CALLSIGN_LENGTH - 1U);
|
|
|
|
// Just a change of port?
|
|
if (oldCall == 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 std::string& 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.substr(0,3U) == "REF" || callsign.substr(0,3U) == "XRF" || callsign.substr(0,3U) == "DCS" || callsign.substr(0,3U) == "XLX")) {
|
|
CLog::logInfo("%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) {
|
|
unsigned int localPort = 0U;
|
|
m_linkStatus = LS_LINKING_DPLUS;
|
|
CDPlusHandler::link(this, m_rptCallsign, m_linkRepeater, data->getAddress(), localPort);
|
|
if(m_irc != nullptr && localPort > 0U)
|
|
m_irc->notifyRepeaterDPlusNatTraversal(m_linkRepeater, localPort);
|
|
writeLinkingTo(m_linkRepeater);
|
|
triggerInfo();
|
|
} else {
|
|
CLog::logInfo("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 {
|
|
CLog::logInfo("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) {
|
|
unsigned int localPort = 0U;
|
|
m_linkStatus = LS_LINKING_DEXTRA;
|
|
CDExtraHandler::link(this, m_rptCallsign, m_linkRepeater, data->getAddress(), localPort);
|
|
if(m_irc != nullptr && localPort > 0U)
|
|
m_irc->notifyRepeaterDextraNatTraversal(m_linkRepeater, localPort);
|
|
writeLinkingTo(m_linkRepeater);
|
|
triggerInfo();
|
|
} else {
|
|
CLog::logInfo("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) {
|
|
std::string callsign = m_rptCallsign;
|
|
if (m_ddMode)
|
|
callsign += "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);
|
|
}
|
|
|
|
if(m_outgoingAprsHandler != nullptr) {
|
|
m_outgoingAprsHandler->addReadAPRSCallback(this);
|
|
}
|
|
|
|
#ifdef USE_CCS
|
|
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);
|
|
#endif
|
|
|
|
#ifdef USE_CCS
|
|
// Start up our CCS link if we are DV mode
|
|
if (!m_ddMode)
|
|
m_ccsHandler->connect();
|
|
#endif
|
|
|
|
// Link to a startup reflector/repeater
|
|
if (m_linkAtStartup && !m_linkStartup.empty()) {
|
|
CLog::logInfo("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) {
|
|
unsigned int localPort = 0U;
|
|
m_linkStatus = LS_LINKING_DPLUS;
|
|
CDPlusHandler::link(this, m_rptCallsign, m_linkRepeater, data->getAddress(), localPort);
|
|
if(m_irc != nullptr && localPort > 0U)
|
|
m_irc->notifyRepeaterDPlusNatTraversal(m_linkRepeater, localPort);
|
|
writeLinkingTo(m_linkRepeater);
|
|
triggerInfo();
|
|
} else {
|
|
CLog::logInfo("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 {
|
|
CLog::logInfo("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) {
|
|
unsigned int localPort = 0U;
|
|
m_linkStatus = LS_LINKING_DEXTRA;
|
|
CDExtraHandler::link(this, m_rptCallsign, m_linkRepeater, data->getAddress(), localPort);
|
|
if(m_irc != nullptr && localPort > 0U)
|
|
m_irc->notifyRepeaterDextraNatTraversal(m_linkRepeater, localPort);
|
|
writeLinkingTo(m_linkRepeater);
|
|
triggerInfo();
|
|
} else {
|
|
CLog::logInfo("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 std::string &callsign)
|
|
{
|
|
std::string text;
|
|
|
|
switch (m_language) {
|
|
case TL_DEUTSCH:
|
|
text = CStringUtils::string_format("Verbinde mit %s", callsign.c_str());
|
|
break;
|
|
case TL_DANSK:
|
|
text = CStringUtils::string_format("Linker til %s", callsign.c_str());
|
|
break;
|
|
case TL_FRANCAIS:
|
|
text = CStringUtils::string_format("Connexion a %s", callsign.c_str());
|
|
break;
|
|
case TL_ITALIANO:
|
|
text = CStringUtils::string_format("In conn con %s", callsign.c_str());
|
|
break;
|
|
case TL_POLSKI:
|
|
text = CStringUtils::string_format("Linkuje do %s", callsign.c_str());
|
|
break;
|
|
case TL_ESPANOL:
|
|
text = CStringUtils::string_format("Enlazando %s", callsign.c_str());
|
|
break;
|
|
case TL_SVENSKA:
|
|
text = CStringUtils::string_format("Lankar till %s", callsign.c_str());
|
|
break;
|
|
case TL_NEDERLANDS_NL:
|
|
case TL_NEDERLANDS_BE:
|
|
text = CStringUtils::string_format("Linken naar %s", callsign.c_str());
|
|
break;
|
|
case TL_NORSK:
|
|
text = CStringUtils::string_format("Kobler til %s", callsign.c_str());
|
|
break;
|
|
case TL_PORTUGUES:
|
|
text = CStringUtils::string_format("Conectando, %s", callsign.c_str());
|
|
break;
|
|
default:
|
|
text = CStringUtils::string_format("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();
|
|
|
|
if(m_outgoingAprsHandler != nullptr)
|
|
m_outgoingAprsHandler->writeStatus(m_rptCallsign, text);
|
|
|
|
#ifdef USE_CCS
|
|
m_ccsHandler->setReflector();
|
|
#endif
|
|
}
|
|
|
|
void CRepeaterHandler::writeLinkedTo(const std::string &callsign)
|
|
{
|
|
std::string text;
|
|
|
|
switch (m_language) {
|
|
case TL_DEUTSCH:
|
|
text = CStringUtils::string_format("Verlinkt zu %s", callsign.c_str());
|
|
break;
|
|
case TL_DANSK:
|
|
text = CStringUtils::string_format("Linket til %s", callsign.c_str());
|
|
break;
|
|
case TL_FRANCAIS:
|
|
text = CStringUtils::string_format("Connecte a %s", callsign.c_str());
|
|
break;
|
|
case TL_ITALIANO:
|
|
text = CStringUtils::string_format("Connesso a %s", callsign.c_str());
|
|
break;
|
|
case TL_POLSKI:
|
|
text = CStringUtils::string_format("Polaczony z %s", callsign.c_str());
|
|
break;
|
|
case TL_ESPANOL:
|
|
text = CStringUtils::string_format("Enlazado %s", callsign.c_str());
|
|
break;
|
|
case TL_SVENSKA:
|
|
text = CStringUtils::string_format("Lankad till %s", callsign.c_str());
|
|
break;
|
|
case TL_NEDERLANDS_NL:
|
|
case TL_NEDERLANDS_BE:
|
|
text = CStringUtils::string_format("Gelinkt met %s", callsign.c_str());
|
|
break;
|
|
case TL_NORSK:
|
|
text = CStringUtils::string_format("Tilkoblet %s", callsign.c_str());
|
|
break;
|
|
case TL_PORTUGUES:
|
|
text = CStringUtils::string_format("Conectado a %s", callsign.c_str());
|
|
break;
|
|
default:
|
|
text = CStringUtils::string_format("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();
|
|
|
|
if(m_outgoingAprsHandler != nullptr)
|
|
m_outgoingAprsHandler->writeStatus(m_rptCallsign, text);
|
|
|
|
#ifdef USE_CCS
|
|
m_ccsHandler->setReflector(callsign);
|
|
#endif
|
|
}
|
|
|
|
void CRepeaterHandler::writeNotLinked()
|
|
{
|
|
std::string text;
|
|
|
|
switch (m_language) {
|
|
case TL_DEUTSCH:
|
|
text = "Nicht verbunden";
|
|
break;
|
|
case TL_DANSK:
|
|
text = "Ikke forbundet";
|
|
break;
|
|
case TL_FRANCAIS:
|
|
text = "Non connecte";
|
|
break;
|
|
case TL_ITALIANO:
|
|
text = "Non connesso";
|
|
break;
|
|
case TL_POLSKI:
|
|
text = "Nie polaczony";
|
|
break;
|
|
case TL_ESPANOL:
|
|
text = "No enlazado";
|
|
break;
|
|
case TL_SVENSKA:
|
|
text = "Ej lankad";
|
|
break;
|
|
case TL_NEDERLANDS_NL:
|
|
case TL_NEDERLANDS_BE:
|
|
text = "Niet gelinkt";
|
|
break;
|
|
case TL_NORSK:
|
|
text = "Ikke linket";
|
|
break;
|
|
case TL_PORTUGUES:
|
|
text = "Desconectado";
|
|
break;
|
|
default:
|
|
text = "Not linked";
|
|
break;
|
|
}
|
|
|
|
CTextData textData(LS_NONE, "", text, m_address, m_port);
|
|
m_repeaterHandler->writeText(textData);
|
|
|
|
m_infoAudio->setStatus(m_linkStatus, m_linkRepeater, text);
|
|
triggerInfo();
|
|
|
|
if(m_outgoingAprsHandler != nullptr)
|
|
m_outgoingAprsHandler->writeStatus(m_rptCallsign, text);
|
|
|
|
#ifdef USE_CCS
|
|
m_ccsHandler->setReflector();
|
|
#endif
|
|
}
|
|
|
|
void CRepeaterHandler::writeIsBusy(const std::string& callsign)
|
|
{
|
|
std::string tempText;
|
|
std::string text;
|
|
|
|
switch (m_language) {
|
|
case TL_DEUTSCH:
|
|
text = "Nicht verbunden";
|
|
tempText = CStringUtils::string_format("%s ist belegt", callsign.c_str());
|
|
break;
|
|
case TL_DANSK:
|
|
text = "Ikke forbundet";
|
|
tempText = CStringUtils::string_format("Optaget fra %s", callsign.c_str());
|
|
break;
|
|
case TL_FRANCAIS:
|
|
text = "Non connecte";
|
|
tempText = CStringUtils::string_format("Occupe par %s", callsign.c_str());
|
|
break;
|
|
case TL_ITALIANO:
|
|
text = "Non connesso";
|
|
tempText = CStringUtils::string_format("Occupado da%s", callsign.c_str());
|
|
break;
|
|
case TL_POLSKI:
|
|
text = "Nie polaczony";
|
|
tempText = CStringUtils::string_format("%s jest zajety", callsign.c_str());
|
|
break;
|
|
case TL_ESPANOL:
|
|
text = "No enlazado";
|
|
tempText = CStringUtils::string_format("%s ocupado", callsign.c_str());
|
|
break;
|
|
case TL_SVENSKA:
|
|
text = "Ej lankad";
|
|
tempText = CStringUtils::string_format("%s ar upptagen", callsign.c_str());
|
|
break;
|
|
case TL_NEDERLANDS_NL:
|
|
case TL_NEDERLANDS_BE:
|
|
text = "Niet gelinkt";
|
|
tempText = CStringUtils::string_format("%s is bezet", callsign.c_str());
|
|
break;
|
|
case TL_NORSK:
|
|
text = "Ikke linket";
|
|
tempText = CStringUtils::string_format("%s er opptatt", callsign.c_str());
|
|
break;
|
|
case TL_PORTUGUES:
|
|
text = "Desconectado";
|
|
tempText = CStringUtils::string_format("%s, ocupado", callsign.c_str());
|
|
break;
|
|
default:
|
|
text = "Not linked";
|
|
tempText = CStringUtils::string_format("%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();
|
|
|
|
if(m_outgoingAprsHandler != nullptr)
|
|
m_outgoingAprsHandler->writeStatus(m_rptCallsign, text);
|
|
|
|
#ifdef USE_CCS
|
|
m_ccsHandler->setReflector();
|
|
#endif
|
|
}
|
|
|
|
void CRepeaterHandler::ccsLinkMade(const std::string& callsign, DIRECTION direction)
|
|
{
|
|
std::string text;
|
|
|
|
switch (m_language) {
|
|
case TL_DEUTSCH:
|
|
text = CStringUtils::string_format("Verlinkt zu %s", callsign.c_str());
|
|
break;
|
|
case TL_DANSK:
|
|
text = CStringUtils::string_format("Linket til %s", callsign.c_str());
|
|
break;
|
|
case TL_FRANCAIS:
|
|
text = CStringUtils::string_format("Connecte a %s", callsign.c_str());
|
|
break;
|
|
case TL_ITALIANO:
|
|
text = CStringUtils::string_format("Connesso a %s", callsign.c_str());
|
|
break;
|
|
case TL_POLSKI:
|
|
text = CStringUtils::string_format("Polaczony z %s", callsign.c_str());
|
|
break;
|
|
case TL_ESPANOL:
|
|
text = CStringUtils::string_format("Enlazado %s", callsign.c_str());
|
|
break;
|
|
case TL_SVENSKA:
|
|
text = CStringUtils::string_format("Lankad till %s", callsign.c_str());
|
|
break;
|
|
case TL_NEDERLANDS_NL:
|
|
case TL_NEDERLANDS_BE:
|
|
text = CStringUtils::string_format("Gelinkt met %s", callsign.c_str());
|
|
break;
|
|
case TL_NORSK:
|
|
text = CStringUtils::string_format("Tilkoblet %s", callsign.c_str());
|
|
break;
|
|
case TL_PORTUGUES:
|
|
text = CStringUtils::string_format("Conectado a %s", callsign.c_str());
|
|
break;
|
|
default:
|
|
text = CStringUtils::string_format("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 std::string&, DIRECTION direction)
|
|
{
|
|
std::string tempText;
|
|
std::string text;
|
|
|
|
switch (m_language) {
|
|
case TL_DEUTSCH:
|
|
text = "Nicht verbunden";
|
|
tempText = "CCS ist beendet";
|
|
break;
|
|
case TL_DANSK:
|
|
text = "Ikke forbundet";
|
|
tempText = "CCS er afsluttet";
|
|
break;
|
|
case TL_FRANCAIS:
|
|
text = "Non connecte";
|
|
tempText = "CCS a pris fin";
|
|
break;
|
|
case TL_ITALIANO:
|
|
text = "Non connesso";
|
|
tempText = "CCS e finita";
|
|
break;
|
|
case TL_POLSKI:
|
|
text = "Nie polaczony";
|
|
tempText = "CCS zakonczyl";
|
|
break;
|
|
case TL_ESPANOL:
|
|
text = "No enlazado";
|
|
tempText = "CCS ha terminado";
|
|
break;
|
|
case TL_SVENSKA:
|
|
text = "Ej lankad";
|
|
tempText = "CCS har upphort";
|
|
break;
|
|
case TL_NEDERLANDS_NL:
|
|
case TL_NEDERLANDS_BE:
|
|
text = "Niet gelinkt";
|
|
tempText = "CCS is afgelopen";
|
|
break;
|
|
case TL_NORSK:
|
|
text = "Ikke linket";
|
|
tempText = "CCS er avsluttet";
|
|
break;
|
|
case TL_PORTUGUES:
|
|
text = "Desconectado";
|
|
tempText = "CCS terminou";
|
|
break;
|
|
default:
|
|
text = "Not linked";
|
|
tempText = "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 std::string& dtmf, DIRECTION direction)
|
|
{
|
|
std::string tempText;
|
|
std::string text;
|
|
|
|
switch (m_language) {
|
|
case TL_DEUTSCH:
|
|
text = "Nicht verbunden";
|
|
tempText = CStringUtils::string_format("%s unbekannt", dtmf.c_str());
|
|
break;
|
|
case TL_DANSK:
|
|
text = "Ikke forbundet";
|
|
tempText = CStringUtils::string_format("%s unknown", dtmf.c_str());
|
|
break;
|
|
case TL_FRANCAIS:
|
|
text = "Non connecte";
|
|
tempText = CStringUtils::string_format("%s inconnu", dtmf.c_str());
|
|
break;
|
|
case TL_ITALIANO:
|
|
text = "Non connesso";
|
|
tempText = CStringUtils::string_format("Sconosciuto %s", dtmf.c_str());
|
|
break;
|
|
case TL_POLSKI:
|
|
text = "Nie polaczony";
|
|
tempText = CStringUtils::string_format("%s nieznany", dtmf.c_str());
|
|
break;
|
|
case TL_ESPANOL:
|
|
text = "No enlazado";
|
|
tempText = CStringUtils::string_format("Desconocido %s", dtmf.c_str());
|
|
break;
|
|
case TL_SVENSKA:
|
|
text = "Ej lankad";
|
|
tempText = CStringUtils::string_format("%s okand", dtmf.c_str());
|
|
break;
|
|
case TL_NEDERLANDS_NL:
|
|
case TL_NEDERLANDS_BE:
|
|
text = "Niet gelinkt";
|
|
tempText = CStringUtils::string_format("%s bekend", dtmf.c_str());
|
|
break;
|
|
case TL_NORSK:
|
|
text = "Ikke linket";
|
|
tempText = CStringUtils::string_format("%s ukjent", dtmf.c_str());
|
|
break;
|
|
case TL_PORTUGUES:
|
|
text = "Desconectado";
|
|
tempText = CStringUtils::string_format("%s desconhecido", dtmf.c_str());
|
|
break;
|
|
default:
|
|
text = "Not linked";
|
|
tempText = CStringUtils::string_format("%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 std::string& text)
|
|
{
|
|
if (m_irc == NULL)
|
|
return;
|
|
|
|
std::string 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.substr(0,3U) == "REF" || m_linkRepeater.substr(0,3U) == "XRF" || m_linkRepeater.substr(0,3U) == "DCS" || m_linkRepeater.substr(0,3U) == "XLX")
|
|
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;
|
|
boost::trim(m_lastReflector);
|
|
}
|
|
|
|
CDPlusHandler::unlink(this);
|
|
CDExtraHandler::unlink(this);
|
|
CDCSHandler::unlink(this);
|
|
|
|
m_linkStatus = LS_NONE;
|
|
m_linkRepeater.clear();
|
|
m_linkReconnectTimer.stop();
|
|
|
|
#ifdef USE_CCS
|
|
m_ccsHandler->setReflector();
|
|
#endif
|
|
}
|
|
|
|
bool CRepeaterHandler::restoreLinks()
|
|
{
|
|
if (m_linkReconnect == RECONNECT_FIXED) {
|
|
if (!m_lastReflector.empty()) {
|
|
linkInt(m_linkStartup);
|
|
m_lastReflector.clear();
|
|
return true;
|
|
}
|
|
} else if (m_linkReconnect == RECONNECT_NEVER) {
|
|
if (!m_lastReflector.empty()) {
|
|
linkInt(m_lastReflector);
|
|
m_lastReflector.clear();
|
|
return true;
|
|
}
|
|
} else {
|
|
m_linkReconnectTimer.start();
|
|
if (!m_lastReflector.empty()) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
void CRepeaterHandler::readAPRSFrame(CAPRSFrame& frame)
|
|
{
|
|
if(m_aprsUnit != nullptr) {
|
|
m_aprsUnit->writeFrame(frame);
|
|
}
|
|
}
|
|
|
|
void CRepeaterHandler::setReconnectTimer(RECONNECT reconnect)
|
|
{
|
|
CLog::logDebug("Reconnect timer set to %s", reconnectToString(reconnect));
|
|
switch (reconnect) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
#ifdef USE_CCS
|
|
bool CRepeaterHandler::isCCSCommand(const std::string& command) const
|
|
{
|
|
if (command.IsSameAs("CA ")))
|
|
return true;
|
|
|
|
wxChar c = command.at(0U);
|
|
if (c != wxT('C'))
|
|
return false;
|
|
|
|
c = command.at(1U);
|
|
if (c < wxT('0') || c > wxT('9'))
|
|
return false;
|
|
|
|
c = command.at(2U);
|
|
if (c < wxT('0') || c > wxT('9'))
|
|
return false;
|
|
|
|
c = command.at(3U);
|
|
if (c < wxT('0') || c > wxT('9'))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
#endif
|