2018-09-19 16:09:28 +02:00
|
|
|
/*
|
2023-07-23 18:36:45 +02:00
|
|
|
* Copyright (C) 2013,2014,2023 by Jonathan Naylor G4KLX
|
2018-09-19 16:09:28 +02:00
|
|
|
*
|
|
|
|
|
* 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 "DStarDefines.h"
|
|
|
|
|
#include "CCSHandler.h"
|
2023-07-23 18:36:45 +02:00
|
|
|
#include "MQTTLog.h"
|
2018-09-19 16:09:28 +02:00
|
|
|
#include "Utils.h"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CCCSHandler** CCCSHandler::m_handlers = NULL;
|
|
|
|
|
|
|
|
|
|
unsigned int CCCSHandler::m_count = 0U;
|
|
|
|
|
|
|
|
|
|
wxString CCCSHandler::m_localAddress;
|
|
|
|
|
CHeaderLogger* CCCSHandler::m_headerLogger = NULL;
|
|
|
|
|
|
|
|
|
|
wxString CCCSHandler::m_ccsHost;
|
|
|
|
|
|
|
|
|
|
CCCSCache_t CCCSHandler::m_cache;
|
|
|
|
|
wxMutex CCCSHandler::m_mutex;
|
|
|
|
|
|
|
|
|
|
bool CCCSHandler::m_stateChange = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void CCCSHandler::initialise(unsigned int count)
|
|
|
|
|
{
|
|
|
|
|
wxASSERT(count > 0U);
|
|
|
|
|
|
|
|
|
|
m_count = count;
|
|
|
|
|
m_handlers = new CCCSHandler*[m_count];
|
|
|
|
|
|
|
|
|
|
for (unsigned int i = 0U; i < m_count; i++)
|
|
|
|
|
m_handlers[i] = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CCCSHandler::setLocalAddress(const wxString& address)
|
|
|
|
|
{
|
|
|
|
|
m_localAddress = address;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CCCSHandler::setHeaderLogger(CHeaderLogger* logger)
|
|
|
|
|
{
|
|
|
|
|
m_headerLogger = logger;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CCCSHandler::setHost(const wxString& host)
|
|
|
|
|
{
|
|
|
|
|
m_ccsHost = host;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CCCSHandler::process()
|
|
|
|
|
{
|
|
|
|
|
for (unsigned int i = 0U; i < m_count; i++) {
|
|
|
|
|
if (m_handlers[i] != NULL)
|
|
|
|
|
m_handlers[i]->processInt();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CCCSHandler::disconnect()
|
|
|
|
|
{
|
|
|
|
|
for (unsigned int i = 0U; i < m_count; i++) {
|
|
|
|
|
if (m_handlers[i] != NULL)
|
|
|
|
|
m_handlers[i]->disconnectInt();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CCCSHandler::clock(unsigned int ms)
|
|
|
|
|
{
|
|
|
|
|
for (unsigned int i = 0U; i < m_count; i++) {
|
|
|
|
|
if (m_handlers[i] != NULL)
|
|
|
|
|
m_handlers[i]->clockInt(ms);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CCCSHandler::getInfo(ICCSCallback* handler, CRemoteRepeaterData& data)
|
|
|
|
|
{
|
|
|
|
|
wxASSERT(handler != NULL);
|
|
|
|
|
|
|
|
|
|
for (unsigned int i = 0U; i < m_count; i++) {
|
|
|
|
|
CCCSHandler* ccs = m_handlers[i];
|
|
|
|
|
if (ccs != NULL && ccs->m_handler == handler && ccs->m_state == CS_ACTIVE)
|
|
|
|
|
data.addLink(ccs->m_yourCall, PROTO_CCS, true, ccs->m_direction, false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wxString CCCSHandler::getIncoming(const wxString& callsign)
|
|
|
|
|
{
|
|
|
|
|
wxString incoming;
|
|
|
|
|
|
|
|
|
|
for (unsigned int i = 0U; i < m_count; i++) {
|
|
|
|
|
CCCSHandler* handler = m_handlers[i];
|
|
|
|
|
if (handler != NULL && handler->m_direction == DIR_INCOMING && handler->m_state == CS_ACTIVE && handler->m_callsign.IsSameAs(callsign)) {
|
|
|
|
|
incoming.Append(handler->m_yourCall);
|
|
|
|
|
incoming.Append(wxT(" "));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return incoming;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CCCSHandler::finalise()
|
|
|
|
|
{
|
|
|
|
|
for (unsigned int i = 0U; i < m_count; i++)
|
|
|
|
|
delete m_handlers[i];
|
|
|
|
|
|
|
|
|
|
delete[] m_handlers;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CCCSHandler::CCCSHandler(ICCSCallback* handler, const wxString& callsign, unsigned int delay, double latitude, double longitude, double frequency, double offset, const wxString& description1, const wxString& description2, const wxString& url, unsigned int localPort) :
|
|
|
|
|
m_handler(handler),
|
|
|
|
|
m_callsign(callsign),
|
|
|
|
|
m_reflector(),
|
|
|
|
|
m_latitude(latitude),
|
|
|
|
|
m_longitude(longitude),
|
|
|
|
|
m_frequency(frequency),
|
|
|
|
|
m_offset(offset),
|
|
|
|
|
m_description1(description1),
|
|
|
|
|
m_description2(description2),
|
|
|
|
|
m_url(url),
|
|
|
|
|
m_ccsAddress(),
|
|
|
|
|
m_protocol(localPort, m_localAddress),
|
|
|
|
|
m_state(CS_DISABLED),
|
|
|
|
|
m_local(),
|
|
|
|
|
m_announceTimer(1000U, 20U), // 20 seconds
|
|
|
|
|
m_inactivityTimer(1000U, 300U), // 5 minutes
|
|
|
|
|
m_pollInactivityTimer(1000U, 60U), // 60 seconds
|
|
|
|
|
m_pollTimer(1000U, 10U), // 10 seconds
|
|
|
|
|
m_waitTimer(1000U, delay),
|
|
|
|
|
m_tryTimer(1000U, 1U), // 1 second
|
|
|
|
|
m_tryCount(0U),
|
|
|
|
|
m_id(0x00U),
|
|
|
|
|
m_seqNo(0U),
|
|
|
|
|
m_time(),
|
|
|
|
|
m_direction(DIR_OUTGOING),
|
|
|
|
|
m_yourCall(),
|
|
|
|
|
m_myCall1(),
|
|
|
|
|
m_myCall2(),
|
|
|
|
|
m_rptCall1()
|
|
|
|
|
{
|
|
|
|
|
wxASSERT(handler != NULL);
|
|
|
|
|
|
|
|
|
|
// Add to the global list
|
|
|
|
|
for (unsigned int i = 0U; i < m_count; i++) {
|
|
|
|
|
if (m_handlers[i] == NULL) {
|
|
|
|
|
m_handlers[i] = this;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CCCSHandler::~CCCSHandler()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CCCSHandler::setReflector(const wxString& callsign)
|
|
|
|
|
{
|
|
|
|
|
m_reflector = callsign;
|
|
|
|
|
|
|
|
|
|
if (m_reflector.IsEmpty())
|
|
|
|
|
m_reflector = wxT(" ");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CCCSHandler::processInt()
|
|
|
|
|
{
|
|
|
|
|
if (m_state == CS_DISABLED)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
|
CCS_TYPE type = m_protocol.read();
|
|
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
|
case CT_DATA: {
|
|
|
|
|
CAMBEData* data = m_protocol.readData();
|
|
|
|
|
if (data != NULL) {
|
|
|
|
|
process(*data);
|
|
|
|
|
delete data;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case CT_POLL: {
|
|
|
|
|
CPollData* poll = m_protocol.readPoll();
|
|
|
|
|
if (poll != NULL) {
|
|
|
|
|
process(*poll);
|
|
|
|
|
delete poll;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case CT_CONNECT: {
|
|
|
|
|
CConnectData* connect = m_protocol.readConnect();
|
|
|
|
|
if (connect != NULL) {
|
|
|
|
|
process(*connect);
|
|
|
|
|
delete connect;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case CT_MISC: {
|
|
|
|
|
CCCSData* data = m_protocol.readMisc();
|
|
|
|
|
if (data != NULL) {
|
|
|
|
|
process(*data);
|
|
|
|
|
delete data;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CCCSHandler::process(CAMBEData& data)
|
|
|
|
|
{
|
|
|
|
|
CHeaderData& header = data.getHeader();
|
|
|
|
|
wxString myCall1 = header.getMyCall1();
|
|
|
|
|
wxString rptCall1 = header.getRptCall1();
|
|
|
|
|
wxString yourCall = header.getYourCall();
|
|
|
|
|
unsigned int seqNo = data.getSeq();
|
|
|
|
|
unsigned int id = data.getId();
|
|
|
|
|
|
|
|
|
|
if (m_state != CS_CONNECTED && m_state != CS_ACTIVE)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// This is a new incoming CCS call
|
|
|
|
|
if (m_state == CS_CONNECTED) {
|
|
|
|
|
m_yourCall = myCall1;
|
|
|
|
|
m_local = yourCall;
|
|
|
|
|
m_rptCall1 = rptCall1;
|
|
|
|
|
m_direction = DIR_INCOMING;
|
|
|
|
|
m_time = ::time(NULL);
|
|
|
|
|
m_state = CS_ACTIVE;
|
|
|
|
|
m_stateChange = true;
|
|
|
|
|
m_inactivityTimer.start();
|
|
|
|
|
|
|
|
|
|
m_handler->ccsLinkMade(m_yourCall, m_direction);
|
|
|
|
|
|
|
|
|
|
wxLogMessage(wxT("CCS: New incoming link to %s from %s @ %s"), m_local.c_str(), m_yourCall.c_str(), m_rptCall1.c_str());
|
2023-07-23 18:36:45 +02:00
|
|
|
WriteJSONLinking("ccs", "in", "network", m_local, m_yourCall);
|
2018-09-19 16:09:28 +02:00
|
|
|
} else {
|
|
|
|
|
if (!m_yourCall.IsSameAs(myCall1) && !m_rptCall1.IsSameAs(rptCall1)) {
|
|
|
|
|
wxLogMessage(wxT("CCS: Rejecting new incoming CCS link from %s @ %s to %s"), myCall1.c_str(), rptCall1.c_str(), yourCall.c_str());
|
|
|
|
|
|
|
|
|
|
CCCSData data(yourCall, myCall1, CT_TERMINATE);
|
|
|
|
|
data.setDestination(m_ccsAddress, CCS_PORT);
|
|
|
|
|
|
|
|
|
|
m_protocol.writeMisc(data);
|
|
|
|
|
m_protocol.writeMisc(data);
|
|
|
|
|
m_protocol.writeMisc(data);
|
|
|
|
|
m_protocol.writeMisc(data);
|
|
|
|
|
m_protocol.writeMisc(data);
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Allow for the fact that the distant repeater may change during the QSO
|
|
|
|
|
if (m_yourCall.IsSameAs(myCall1) && !m_rptCall1.IsSameAs(rptCall1)) {
|
|
|
|
|
wxLogMessage(wxT("CCS: %s has moved from repeater %s to %s"), m_yourCall.c_str(), m_rptCall1.c_str(), rptCall1.c_str());
|
|
|
|
|
m_rptCall1 = rptCall1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_pollInactivityTimer.start();
|
|
|
|
|
m_inactivityTimer.start();
|
|
|
|
|
|
|
|
|
|
if (m_id != id) {
|
|
|
|
|
// Write to Header.log if it's enabled
|
|
|
|
|
if (m_headerLogger != NULL)
|
|
|
|
|
m_headerLogger->write(wxT("CCS"), header);
|
|
|
|
|
|
|
|
|
|
header.setCQCQCQ();
|
|
|
|
|
m_handler->process(header, DIR_INCOMING, AS_CCS);
|
|
|
|
|
|
|
|
|
|
m_id = id;
|
|
|
|
|
} else if (seqNo == 0U) {
|
|
|
|
|
header.setCQCQCQ();
|
|
|
|
|
m_handler->process(header, DIR_INCOMING, AS_DUP);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_handler->process(data, DIR_INCOMING, AS_CCS);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CCCSHandler::process(CCCSData& data)
|
|
|
|
|
{
|
|
|
|
|
CC_TYPE type = data.getType();
|
|
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
|
case CT_TERMINATE:
|
|
|
|
|
if (m_state == CS_ACTIVE) {
|
|
|
|
|
wxLogMessage(wxT("CCS: Link between %s and %s has been terminated"), data.getLocal().c_str(), data.getRemote().c_str());
|
2023-07-23 18:36:45 +02:00
|
|
|
WriteJSONUnlinked("ccs", "user", m_local, m_yourCall);
|
2018-09-19 16:09:28 +02:00
|
|
|
m_stateChange = true;
|
|
|
|
|
m_state = CS_CONNECTED;
|
|
|
|
|
m_inactivityTimer.stop();
|
|
|
|
|
m_handler->ccsLinkEnded(data.getRemote(), m_direction);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case CT_DTMFNOTFOUND:
|
|
|
|
|
wxLogMessage(wxT("CCS: Cannot map %s to a callsign"), m_yourCall.c_str());
|
|
|
|
|
m_stateChange = true;
|
|
|
|
|
m_state = CS_CONNECTED;
|
|
|
|
|
m_inactivityTimer.stop();
|
|
|
|
|
m_handler->ccsLinkFailed(m_yourCall, m_direction);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case CT_DTMFFOUND:
|
|
|
|
|
wxLogMessage(wxT("CCS: Mapped %s to %s, added to the cache"), m_yourCall.c_str(), data.getRemote().c_str());
|
|
|
|
|
addToCache(m_yourCall, data.getRemote());
|
|
|
|
|
m_stateChange = true;
|
|
|
|
|
m_yourCall = data.getRemote();
|
|
|
|
|
m_rptCall1 = data.getRemote();
|
|
|
|
|
m_handler->ccsLinkMade(m_yourCall, m_direction);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CCCSHandler::process(CPollData&)
|
|
|
|
|
{
|
|
|
|
|
m_pollInactivityTimer.start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CCCSHandler::process(CConnectData& connect)
|
|
|
|
|
{
|
|
|
|
|
CD_TYPE type = connect.getType();
|
|
|
|
|
|
|
|
|
|
if (type == CT_ACK && m_state == CS_CONNECTING) {
|
|
|
|
|
wxLogMessage(wxT("CCS: %s connected to server %s"), m_callsign.c_str(), m_ccsHost.c_str());
|
|
|
|
|
|
|
|
|
|
m_announceTimer.start();
|
|
|
|
|
m_pollInactivityTimer.start();
|
|
|
|
|
m_pollTimer.start();
|
|
|
|
|
m_tryTimer.stop();
|
|
|
|
|
|
|
|
|
|
// Give our location, frequency, etc
|
|
|
|
|
CCCSData data(m_callsign, m_latitude, m_longitude, m_frequency, m_offset, m_description1, m_description2, m_url, CT_INFO);
|
|
|
|
|
data.setDestination(m_ccsAddress, CCS_PORT);
|
|
|
|
|
m_protocol.writeMisc(data);
|
|
|
|
|
|
|
|
|
|
m_state = CS_CONNECTED;
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (type == CT_NAK && m_state == CS_CONNECTING) {
|
|
|
|
|
wxLogMessage(wxT("CCS: Connection refused for %s"), m_callsign.c_str());
|
|
|
|
|
m_tryTimer.stop();
|
|
|
|
|
m_state = CS_DISABLED;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CCCSHandler::connect()
|
|
|
|
|
{
|
|
|
|
|
// Is CCS disabled?
|
|
|
|
|
if (m_localAddress.IsSameAs(wxT("127.0.0.1")))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// Can we resolve the CCS server address?
|
|
|
|
|
m_ccsAddress = CUDPReaderWriter::lookup(m_ccsHost);
|
|
|
|
|
if (m_ccsAddress.s_addr == INADDR_NONE) {
|
|
|
|
|
wxLogError(wxT("CCS: Unable to find the IP address for %s"), m_ccsHost.c_str());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool res = m_protocol.open();
|
|
|
|
|
if (!res)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
wxLogMessage(wxT("CCS: Opening UDP port %u for %s"), m_protocol.getPort(), m_callsign.c_str());
|
|
|
|
|
|
|
|
|
|
m_waitTimer.start();
|
|
|
|
|
|
|
|
|
|
m_state = CS_CONNECTING;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CCCSHandler::disconnectInt()
|
|
|
|
|
{
|
|
|
|
|
if (m_state == CS_CONNECTED || m_state == CS_ACTIVE) {
|
|
|
|
|
CConnectData connect(m_callsign, CT_UNLINK, m_ccsAddress, CCS_PORT);
|
|
|
|
|
m_protocol.writeConnect(connect);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_announceTimer.stop();
|
|
|
|
|
m_pollInactivityTimer.stop();
|
|
|
|
|
m_inactivityTimer.stop();
|
|
|
|
|
m_pollTimer.stop();
|
|
|
|
|
m_tryTimer.stop();
|
|
|
|
|
|
|
|
|
|
if (m_state != CS_DISABLED)
|
|
|
|
|
m_protocol.close();
|
|
|
|
|
|
|
|
|
|
m_state = CS_DISABLED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CCCSHandler::startLink(const wxString& dtmf, const wxString& user, const wxString& type)
|
|
|
|
|
{
|
|
|
|
|
if (m_state != CS_CONNECTED)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
wxString callsign = findInCache(dtmf);
|
|
|
|
|
if (!callsign.IsEmpty()) {
|
|
|
|
|
wxLogMessage(wxT("CCS: New outgoing link to %s/%s via %s by %s"), dtmf.c_str(), callsign.c_str(), type.c_str(), user.c_str());
|
2023-07-23 18:36:45 +02:00
|
|
|
WriteJSONLinking("ccs", "out", "user", user, callsign);
|
2018-09-19 16:09:28 +02:00
|
|
|
m_handler->ccsLinkMade(callsign, m_direction);
|
|
|
|
|
m_yourCall = callsign;
|
|
|
|
|
m_rptCall1 = callsign;
|
|
|
|
|
} else {
|
|
|
|
|
wxLogMessage(wxT("CCS: New outgoing link to %s via %s by %s"), dtmf.c_str(), type.c_str(), user.c_str());
|
2023-07-23 18:36:45 +02:00
|
|
|
WriteJSONLinking("ccs", "out", "user", user, dtmf);
|
2018-09-19 16:09:28 +02:00
|
|
|
m_yourCall = dtmf;
|
|
|
|
|
m_yourCall.resize(LONG_CALLSIGN_LENGTH, wxT(' '));
|
|
|
|
|
m_rptCall1.Clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_local = user;
|
|
|
|
|
m_seqNo = 0U;
|
|
|
|
|
|
|
|
|
|
m_time = ::time(NULL);
|
|
|
|
|
m_stateChange = true;
|
|
|
|
|
m_state = CS_ACTIVE;
|
|
|
|
|
m_direction = DIR_OUTGOING;
|
|
|
|
|
m_inactivityTimer.start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CCCSHandler::stopLink(const wxString& user, const wxString& type)
|
|
|
|
|
{
|
|
|
|
|
if (m_state != CS_ACTIVE)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (!user.IsEmpty() && !type.IsEmpty())
|
|
|
|
|
wxLogMessage(wxT("CCS: Link to %s from %s has been terminated via %s by %s"), m_yourCall.c_str(), m_local.c_str(), type.c_str(), user.c_str());
|
2023-07-23 18:36:45 +02:00
|
|
|
WriteJSONUnlinked("ccs", "user", m_local, m_yourCall);
|
2018-09-19 16:09:28 +02:00
|
|
|
|
|
|
|
|
CCCSData data(m_local, m_yourCall, CT_TERMINATE);
|
|
|
|
|
data.setDestination(m_ccsAddress, CCS_PORT);
|
|
|
|
|
|
|
|
|
|
m_protocol.writeMisc(data);
|
|
|
|
|
m_protocol.writeMisc(data);
|
|
|
|
|
m_protocol.writeMisc(data);
|
|
|
|
|
m_protocol.writeMisc(data);
|
|
|
|
|
m_protocol.writeMisc(data);
|
|
|
|
|
|
|
|
|
|
m_stateChange = true;
|
|
|
|
|
m_state = CS_CONNECTED;
|
|
|
|
|
m_inactivityTimer.stop();
|
|
|
|
|
|
|
|
|
|
m_handler->ccsLinkEnded(m_yourCall, m_direction);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CCCSHandler::unlink(const wxString& callsign)
|
|
|
|
|
{
|
|
|
|
|
if (m_state != CS_ACTIVE)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (!m_yourCall.IsSameAs(callsign))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
wxLogMessage(wxT("CCS: Link to %s from %s has been terminated by command"), m_yourCall.c_str(), m_local.c_str());
|
2023-07-23 18:36:45 +02:00
|
|
|
WriteJSONUnlinked("ccs", "user", m_local, m_yourCall);
|
2018-09-19 16:09:28 +02:00
|
|
|
|
|
|
|
|
CCCSData data(m_local, m_yourCall, CT_TERMINATE);
|
|
|
|
|
data.setDestination(m_ccsAddress, CCS_PORT);
|
|
|
|
|
|
|
|
|
|
m_protocol.writeMisc(data);
|
|
|
|
|
m_protocol.writeMisc(data);
|
|
|
|
|
m_protocol.writeMisc(data);
|
|
|
|
|
m_protocol.writeMisc(data);
|
|
|
|
|
m_protocol.writeMisc(data);
|
|
|
|
|
|
|
|
|
|
m_stateChange = true;
|
|
|
|
|
m_state = CS_CONNECTED;
|
|
|
|
|
m_inactivityTimer.stop();
|
|
|
|
|
|
|
|
|
|
m_handler->ccsLinkEnded(m_yourCall, m_direction);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CCCSHandler::writeHeard(CHeaderData& header)
|
|
|
|
|
{
|
|
|
|
|
if (m_state != CS_CONNECTED && m_state != CS_ACTIVE)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
CHeardData heard(header, m_callsign, m_reflector);
|
|
|
|
|
heard.setDestination(m_ccsAddress, CCS_PORT);
|
|
|
|
|
m_protocol.writeHeard(heard);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CCCSHandler::writeHeader(CHeaderData& header)
|
|
|
|
|
{
|
|
|
|
|
m_myCall1 = header.getMyCall1();
|
|
|
|
|
m_myCall2 = header.getMyCall2();
|
|
|
|
|
|
|
|
|
|
m_seqNo = 0U;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CCCSHandler::writeAMBE(CAMBEData& data)
|
|
|
|
|
{
|
|
|
|
|
if (m_state != CS_ACTIVE)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
CAMBEData temp(data);
|
|
|
|
|
|
|
|
|
|
CHeaderData& header = temp.getHeader();
|
|
|
|
|
header.setMyCall1(m_myCall1);
|
|
|
|
|
header.setMyCall2(m_myCall2);
|
|
|
|
|
header.setYourCall(m_yourCall);
|
|
|
|
|
header.setRptCall1(m_callsign);
|
|
|
|
|
header.setRptCall2(m_reflector);
|
|
|
|
|
|
|
|
|
|
temp.setRptSeq(m_seqNo++);
|
|
|
|
|
temp.setDestination(m_ccsAddress, CCS_PORT);
|
|
|
|
|
m_protocol.writeData(temp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CCS_STATUS CCCSHandler::getStatus() const
|
|
|
|
|
{
|
|
|
|
|
return m_state;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CCCSHandler::clockInt(unsigned int ms)
|
|
|
|
|
{
|
|
|
|
|
m_announceTimer.clock(ms);
|
|
|
|
|
m_pollInactivityTimer.clock(ms);
|
|
|
|
|
m_inactivityTimer.clock(ms);
|
|
|
|
|
m_pollTimer.clock(ms);
|
|
|
|
|
m_waitTimer.clock(ms);
|
|
|
|
|
m_tryTimer.clock(ms);
|
|
|
|
|
|
|
|
|
|
if (m_pollInactivityTimer.isRunning() && m_pollInactivityTimer.hasExpired()) {
|
|
|
|
|
wxLogMessage(wxT("CCS: Connection has failed (poll inactivity) for %s, reconnecting"), m_callsign.c_str());
|
2023-07-23 18:36:45 +02:00
|
|
|
WriteJSONRelinking("ccs", m_local, m_yourCall);
|
2018-09-19 16:09:28 +02:00
|
|
|
|
|
|
|
|
m_announceTimer.stop();
|
|
|
|
|
m_pollInactivityTimer.stop();
|
|
|
|
|
m_inactivityTimer.stop();
|
|
|
|
|
m_pollTimer.stop();
|
|
|
|
|
|
|
|
|
|
if (m_state == CS_ACTIVE) {
|
|
|
|
|
m_stateChange = true;
|
|
|
|
|
m_handler->ccsLinkEnded(m_yourCall, m_direction);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_waitTimer.start();
|
|
|
|
|
|
|
|
|
|
m_state = CS_CONNECTING;
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_tryTimer.isRunning() && m_tryTimer.hasExpired()) {
|
|
|
|
|
CConnectData connect(m_callsign, CT_LINK1, m_ccsAddress, CCS_PORT);
|
|
|
|
|
if (m_latitude != 0.0 && m_longitude != 0.0) {
|
|
|
|
|
wxString locator = CUtils::latLonToLoc(m_latitude, m_longitude);
|
|
|
|
|
connect.setLocator(locator);
|
|
|
|
|
}
|
|
|
|
|
m_protocol.writeConnect(connect);
|
|
|
|
|
|
|
|
|
|
unsigned int t = calcBackoff();
|
|
|
|
|
m_tryTimer.start(t);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_pollTimer.isRunning() && m_pollTimer.hasExpired()) {
|
|
|
|
|
CPollData poll(m_callsign, m_ccsAddress, CCS_PORT);
|
|
|
|
|
m_protocol.writePoll(poll);
|
|
|
|
|
|
|
|
|
|
m_pollTimer.start();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_inactivityTimer.isRunning() && m_inactivityTimer.hasExpired()) {
|
|
|
|
|
wxLogMessage(wxT("CCS: Activity timeout on link for %s"), m_callsign.c_str(), m_callsign.c_str());
|
2023-07-23 18:36:45 +02:00
|
|
|
WriteJSONUnlinked("ccs", "timer", m_local, m_yourCall);
|
2018-09-19 16:09:28 +02:00
|
|
|
|
|
|
|
|
CCCSData data(m_local, m_yourCall, CT_TERMINATE);
|
|
|
|
|
data.setDestination(m_ccsAddress, CCS_PORT);
|
|
|
|
|
|
|
|
|
|
m_protocol.writeMisc(data);
|
|
|
|
|
m_protocol.writeMisc(data);
|
|
|
|
|
m_protocol.writeMisc(data);
|
|
|
|
|
m_protocol.writeMisc(data);
|
|
|
|
|
m_protocol.writeMisc(data);
|
|
|
|
|
|
|
|
|
|
m_stateChange = true;
|
|
|
|
|
m_state = CS_CONNECTED;
|
|
|
|
|
m_inactivityTimer.stop();
|
|
|
|
|
|
|
|
|
|
m_handler->ccsLinkEnded(m_yourCall, m_direction);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_waitTimer.isRunning() && m_waitTimer.hasExpired()) {
|
|
|
|
|
CConnectData connect(m_callsign, CT_LINK1, m_ccsAddress, CCS_PORT);
|
|
|
|
|
if (m_latitude != 0.0 && m_longitude != 0.0) {
|
|
|
|
|
wxString locator = CUtils::latLonToLoc(m_latitude, m_longitude);
|
|
|
|
|
connect.setLocator(locator);
|
|
|
|
|
}
|
|
|
|
|
m_protocol.writeConnect(connect);
|
|
|
|
|
|
|
|
|
|
m_tryTimer.start(1U);
|
|
|
|
|
m_tryCount = 1U;
|
|
|
|
|
|
|
|
|
|
m_waitTimer.stop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_announceTimer.isRunning() && m_announceTimer.hasExpired()) {
|
|
|
|
|
CHeaderData header;
|
|
|
|
|
header.setMyCall1(m_callsign.Left(LONG_CALLSIGN_LENGTH - 1U));
|
|
|
|
|
CHeardData heard(header, m_callsign, wxEmptyString);
|
|
|
|
|
heard.setDestination(m_ccsAddress, CCS_PORT);
|
|
|
|
|
m_protocol.writeHeard(heard);
|
|
|
|
|
|
|
|
|
|
m_announceTimer.start(3600U);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsigned int CCCSHandler::calcBackoff()
|
|
|
|
|
{
|
|
|
|
|
if (m_tryCount >= 7U) {
|
|
|
|
|
m_tryCount++;
|
|
|
|
|
return 60U;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsigned int timeout = 1U;
|
|
|
|
|
|
|
|
|
|
for (unsigned int i = 0U; i < m_tryCount; i++)
|
|
|
|
|
timeout *= 2U;
|
|
|
|
|
|
|
|
|
|
m_tryCount++;
|
|
|
|
|
|
|
|
|
|
if (timeout > 60U)
|
|
|
|
|
return 60U;
|
|
|
|
|
else
|
|
|
|
|
return timeout;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CCCSHandler::stateChange()
|
|
|
|
|
{
|
|
|
|
|
bool stateChange = m_stateChange;
|
|
|
|
|
|
|
|
|
|
m_stateChange = false;
|
|
|
|
|
|
|
|
|
|
return stateChange;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CCCSHandler::writeStatus(wxFFile& file)
|
|
|
|
|
{
|
|
|
|
|
for (unsigned int i = 0U; i < m_count; i++) {
|
|
|
|
|
CCCSHandler* handler = m_handlers[i];
|
|
|
|
|
if (handler != NULL) {
|
|
|
|
|
struct tm* tm = ::gmtime(&handler->m_time);
|
|
|
|
|
|
|
|
|
|
switch (handler->m_direction) {
|
|
|
|
|
case DIR_OUTGOING:
|
|
|
|
|
if (handler->m_state == CS_ACTIVE) {
|
|
|
|
|
wxString text;
|
|
|
|
|
text.Printf(wxT("%04d-%02d-%02d %02d:%02d:%02d: CCS link - Rptr: %s Remote: %s Dir: Outgoing\n"),
|
|
|
|
|
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec,
|
|
|
|
|
handler->m_callsign.c_str(), handler->m_yourCall.c_str());
|
|
|
|
|
file.Write(text);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case DIR_INCOMING:
|
|
|
|
|
if (handler->m_state == CS_ACTIVE) {
|
|
|
|
|
wxString text;
|
|
|
|
|
text.Printf(wxT("%04d-%02d-%02d %02d:%02d:%02d: CCS link - Rptr: %s Remote: %s Dir: Incoming\n"),
|
|
|
|
|
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec,
|
|
|
|
|
handler->m_callsign.c_str(), handler->m_yourCall.c_str());
|
|
|
|
|
file.Write(text);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CCCSHandler::addToCache(const wxString& dtmf, const wxString& callsign)
|
|
|
|
|
{
|
|
|
|
|
wxMutexLocker locker(m_mutex);
|
|
|
|
|
|
|
|
|
|
m_cache[dtmf] = callsign;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wxString CCCSHandler::findInCache(const wxString& dtmf)
|
|
|
|
|
{
|
|
|
|
|
wxMutexLocker locker(m_mutex);
|
|
|
|
|
|
|
|
|
|
return m_cache[dtmf];
|
|
|
|
|
}
|