mirror of
https://github.com/ShaYmez/NXDNClients.git
synced 2025-07-19 08:15:19 -04:00
Merge branch 'master' of https://github.com/g4klx/NXDNCLients
This commit is contained in:
commit
48d233e74c
@ -17,29 +17,34 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "APRSWriter.h"
|
#include "APRSWriter.h"
|
||||||
|
#include "Log.h"
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
CAPRSWriter::CAPRSWriter(const std::string& callsign, const std::string& suffix, const std::string& password, const std::string& address, unsigned int port) :
|
CAPRSWriter::CAPRSWriter(const std::string& callsign, const std::string& suffix, const std::string& address, unsigned int port, bool debug) :
|
||||||
m_thread(NULL),
|
|
||||||
m_enabled(false),
|
|
||||||
m_idTimer(1000U),
|
m_idTimer(1000U),
|
||||||
m_callsign(callsign),
|
m_callsign(callsign),
|
||||||
|
m_debug(debug),
|
||||||
m_txFrequency(0U),
|
m_txFrequency(0U),
|
||||||
m_rxFrequency(0U),
|
m_rxFrequency(0U),
|
||||||
m_latitude(0.0F),
|
m_latitude(0.0F),
|
||||||
m_longitude(0.0F),
|
m_longitude(0.0F),
|
||||||
m_height(0),
|
m_height(0),
|
||||||
m_desc(),
|
m_desc(),
|
||||||
m_mobileGPSAddress(),
|
m_aprsAddress(),
|
||||||
m_mobileGPSPort(0U),
|
m_aprsPort(port),
|
||||||
m_socket(NULL)
|
m_aprsSocket()
|
||||||
|
#if defined(USE_GPSD)
|
||||||
|
,m_gpsdEnabled(false),
|
||||||
|
m_gpsdAddress(),
|
||||||
|
m_gpsdPort(),
|
||||||
|
m_gpsdData()
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
assert(!callsign.empty());
|
assert(!callsign.empty());
|
||||||
assert(!password.empty());
|
|
||||||
assert(!address.empty());
|
assert(!address.empty());
|
||||||
assert(port > 0U);
|
assert(port > 0U);
|
||||||
|
|
||||||
@ -48,7 +53,7 @@ m_socket(NULL)
|
|||||||
m_callsign.append(suffix.substr(0U, 1U));
|
m_callsign.append(suffix.substr(0U, 1U));
|
||||||
}
|
}
|
||||||
|
|
||||||
m_thread = new CAPRSWriterThread(m_callsign, password, address, port);
|
m_aprsAddress = CUDPSocket::lookup(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
CAPRSWriter::~CAPRSWriter()
|
CAPRSWriter::~CAPRSWriter()
|
||||||
@ -69,88 +74,92 @@ void CAPRSWriter::setStaticLocation(float latitude, float longitude, int height)
|
|||||||
m_height = height;
|
m_height = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CAPRSWriter::setMobileLocation(const std::string& address, unsigned int port)
|
void CAPRSWriter::setGPSDLocation(const std::string& address, const std::string& port)
|
||||||
{
|
{
|
||||||
|
#if defined(USE_GPSD)
|
||||||
assert(!address.empty());
|
assert(!address.empty());
|
||||||
assert(port > 0U);
|
assert(!port.empty());
|
||||||
|
|
||||||
m_mobileGPSAddress = CUDPSocket::lookup(address);
|
m_gpsdEnabled = true;
|
||||||
m_mobileGPSPort = port;
|
m_gpsdAddress = address;
|
||||||
|
m_gpsdPort = port;
|
||||||
m_socket = new CUDPSocket;
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CAPRSWriter::open()
|
bool CAPRSWriter::open()
|
||||||
{
|
{
|
||||||
if (m_socket != NULL) {
|
#if defined(USE_GPSD)
|
||||||
bool ret = m_socket->open();
|
if (m_gpsdEnabled) {
|
||||||
if (!ret) {
|
int ret = ::gps_open(m_gpsdAddress.c_str(), m_gpsdPort.c_str(), &m_gpsdData);
|
||||||
delete m_socket;
|
if (ret != 0) {
|
||||||
m_socket = NULL;
|
LogError("Error when opening access to gpsd - %d - %s", errno, ::gps_errstr(errno));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Poll the GPS every minute
|
::gps_stream(&m_gpsdData, WATCH_ENABLE | WATCH_JSON, NULL);
|
||||||
m_idTimer.setTimeout(60U);
|
|
||||||
} else {
|
|
||||||
m_idTimer.setTimeout(20U * 60U);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
LogMessage("Connected to GPSD");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
bool ret = m_aprsSocket.open();
|
||||||
|
if (!ret)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
LogMessage("Opened connection to the APRS Gateway");
|
||||||
|
|
||||||
|
m_idTimer.setTimeout(60U);
|
||||||
m_idTimer.start();
|
m_idTimer.start();
|
||||||
|
|
||||||
return m_thread->start();
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CAPRSWriter::write(const char* data)
|
void CAPRSWriter::write(const char* data)
|
||||||
{
|
{
|
||||||
assert(data != NULL);
|
assert(data != NULL);
|
||||||
|
|
||||||
m_thread->write(data);
|
if (m_debug)
|
||||||
|
LogDebug("APRS ==> %s", data);
|
||||||
|
|
||||||
|
m_aprsSocket.write((unsigned char*)data, (unsigned int)::strlen(data), m_aprsAddress, m_aprsPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CAPRSWriter::clock(unsigned int ms)
|
void CAPRSWriter::clock(unsigned int ms)
|
||||||
{
|
{
|
||||||
m_idTimer.clock(ms);
|
m_idTimer.clock(ms);
|
||||||
|
|
||||||
m_thread->clock(ms);
|
#if defined(USE_GPSD)
|
||||||
|
if (m_gpsdEnabled) {
|
||||||
if (m_socket != NULL) {
|
|
||||||
if (m_idTimer.hasExpired()) {
|
if (m_idTimer.hasExpired()) {
|
||||||
pollGPS();
|
sendIdFrameMobile();
|
||||||
m_idTimer.start();
|
m_idTimer.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
sendIdFrameMobile();
|
|
||||||
} else {
|
} else {
|
||||||
|
#endif
|
||||||
if (m_idTimer.hasExpired()) {
|
if (m_idTimer.hasExpired()) {
|
||||||
sendIdFrameFixed();
|
sendIdFrameFixed();
|
||||||
|
m_idTimer.setTimeout(20U * 60U);
|
||||||
m_idTimer.start();
|
m_idTimer.start();
|
||||||
}
|
}
|
||||||
|
#if defined(USE_GPSD)
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void CAPRSWriter::close()
|
void CAPRSWriter::close()
|
||||||
{
|
{
|
||||||
if (m_socket != NULL) {
|
m_aprsSocket.close();
|
||||||
m_socket->close();
|
|
||||||
delete m_socket;
|
#if defined(USE_GPSD)
|
||||||
|
if (m_gpsdEnabled) {
|
||||||
|
::gps_stream(&m_gpsdData, WATCH_DISABLE, NULL);
|
||||||
|
::gps_close(&m_gpsdData);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
m_thread->stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CAPRSWriter::pollGPS()
|
|
||||||
{
|
|
||||||
assert(m_socket != NULL);
|
|
||||||
|
|
||||||
return m_socket->write((unsigned char*)"NXDNGateway", 11U, m_mobileGPSAddress, m_mobileGPSPort);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CAPRSWriter::sendIdFrameFixed()
|
void CAPRSWriter::sendIdFrameFixed()
|
||||||
{
|
{
|
||||||
if (!m_thread->isConnected())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Default values aren't passed on
|
// Default values aren't passed on
|
||||||
if (m_latitude == 0.0F && m_longitude == 0.0F)
|
if (m_latitude == 0.0F && m_longitude == 0.0F)
|
||||||
return;
|
return;
|
||||||
@ -201,43 +210,52 @@ void CAPRSWriter::sendIdFrameFixed()
|
|||||||
server.append("S");
|
server.append("S");
|
||||||
|
|
||||||
char output[500U];
|
char output[500U];
|
||||||
::sprintf(output, "%s>APDG04,TCPIP*,qAC,%s:!%s%cD%s%c&/A=%06.0f%s %s",
|
::sprintf(output, "%s>APDG04,TCPIP*,qAC,%s:!%s%cD%s%c&/A=%06.0f%s %s\r\n",
|
||||||
m_callsign.c_str(), server.c_str(),
|
m_callsign.c_str(), server.c_str(),
|
||||||
lat, (m_latitude < 0.0F) ? 'S' : 'N',
|
lat, (m_latitude < 0.0F) ? 'S' : 'N',
|
||||||
lon, (m_longitude < 0.0F) ? 'W' : 'E',
|
lon, (m_longitude < 0.0F) ? 'W' : 'E',
|
||||||
float(m_height) * 3.28F, band, desc);
|
float(m_height) * 3.28F, band, desc);
|
||||||
|
|
||||||
m_thread->write(output);
|
if (m_debug)
|
||||||
|
LogDebug("APRS ==> %s", output);
|
||||||
|
|
||||||
|
write(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(USE_GPSD)
|
||||||
void CAPRSWriter::sendIdFrameMobile()
|
void CAPRSWriter::sendIdFrameMobile()
|
||||||
{
|
{
|
||||||
// Grab GPS data if it's available
|
if (!::gps_waiting(&m_gpsdData, 0))
|
||||||
unsigned char buffer[200U];
|
|
||||||
in_addr address;
|
|
||||||
unsigned int port;
|
|
||||||
int ret = m_socket->read(buffer, 200U, address, port);
|
|
||||||
if (ret <= 0)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!m_thread->isConnected())
|
#if GPSD_API_MAJOR_VERSION >= 7
|
||||||
|
if (::gps_read(&m_gpsdData, NULL, 0) <= 0)
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
if (::gps_read(&m_gpsdData) <= 0)
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (m_gpsdData.status != STATUS_FIX)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
buffer[ret] = '\0';
|
bool latlonSet = (m_gpsdData.set & LATLON_SET) == LATLON_SET;
|
||||||
|
bool altitudeSet = (m_gpsdData.set & ALTITUDE_SET) == ALTITUDE_SET;
|
||||||
|
bool velocitySet = (m_gpsdData.set & SPEED_SET) == SPEED_SET;
|
||||||
|
bool bearingSet = (m_gpsdData.set & TRACK_SET) == TRACK_SET;
|
||||||
|
|
||||||
// Parse the GPS data
|
if (!latlonSet)
|
||||||
char* pLatitude = ::strtok((char*)buffer, ",\n"); // Latitude
|
|
||||||
char* pLongitude = ::strtok(NULL, ",\n"); // Longitude
|
|
||||||
char* pAltitude = ::strtok(NULL, ",\n"); // Altitude (m)
|
|
||||||
char* pVelocity = ::strtok(NULL, ",\n"); // Velocity (kms/h)
|
|
||||||
char* pBearing = ::strtok(NULL, "\n"); // Bearing
|
|
||||||
|
|
||||||
if (pLatitude == NULL || pLongitude == NULL || pAltitude == NULL)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
float rawLatitude = float(::atof(pLatitude));
|
float rawLatitude = float(m_gpsdData.fix.latitude);
|
||||||
float rawLongitude = float(::atof(pLongitude));
|
float rawLongitude = float(m_gpsdData.fix.longitude);
|
||||||
float rawAltitude = float(::atof(pAltitude));
|
#if GPSD_API_MAJOR_VERSION >= 9
|
||||||
|
float rawAltitude = float(m_gpsdData.fix.altMSL);
|
||||||
|
#else
|
||||||
|
float rawAltitude = float(m_gpsdData.fix.altitude);
|
||||||
|
#endif
|
||||||
|
float rawVelocity = float(m_gpsdData.fix.speed);
|
||||||
|
float rawBearing = float(m_gpsdData.fix.track);
|
||||||
|
|
||||||
char desc[200U];
|
char desc[200U];
|
||||||
if (m_txFrequency != 0U) {
|
if (m_txFrequency != 0U) {
|
||||||
@ -290,15 +308,17 @@ void CAPRSWriter::sendIdFrameMobile()
|
|||||||
lat, (rawLatitude < 0.0F) ? 'S' : 'N',
|
lat, (rawLatitude < 0.0F) ? 'S' : 'N',
|
||||||
lon, (rawLongitude < 0.0F) ? 'W' : 'E');
|
lon, (rawLongitude < 0.0F) ? 'W' : 'E');
|
||||||
|
|
||||||
if (pBearing != NULL && pVelocity != NULL) {
|
if (bearingSet && velocitySet)
|
||||||
float rawBearing = float(::atof(pBearing));
|
|
||||||
float rawVelocity = float(::atof(pVelocity));
|
|
||||||
|
|
||||||
::sprintf(output + ::strlen(output), "%03.0f/%03.0f", rawBearing, rawVelocity * 0.539957F);
|
::sprintf(output + ::strlen(output), "%03.0f/%03.0f", rawBearing, rawVelocity * 0.539957F);
|
||||||
}
|
|
||||||
|
|
||||||
::sprintf(output + ::strlen(output), "/A=%06.0f%s %s", float(rawAltitude) * 3.28F, band, desc);
|
if (altitudeSet)
|
||||||
|
::sprintf(output + ::strlen(output), "/A=%06.0f", float(rawAltitude) * 3.28F);
|
||||||
|
|
||||||
m_thread->write(output);
|
::sprintf(output + ::strlen(output), "%s %s\r\n", band, desc);
|
||||||
|
|
||||||
|
if (m_debug)
|
||||||
|
LogDebug("APRS ==> %s", output);
|
||||||
|
|
||||||
|
write(output);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2010,2011,2012,2016,2017,2018 by Jonathan Naylor G4KLX
|
* Copyright (C) 2010,2011,2012,2016,2017,2018,2020 by Jonathan Naylor G4KLX
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -19,7 +19,6 @@
|
|||||||
#ifndef APRSWriter_H
|
#ifndef APRSWriter_H
|
||||||
#define APRSWriter_H
|
#define APRSWriter_H
|
||||||
|
|
||||||
#include "APRSWriterThread.h"
|
|
||||||
#include "UDPSocket.h"
|
#include "UDPSocket.h"
|
||||||
#include "Timer.h"
|
#include "Timer.h"
|
||||||
|
|
||||||
@ -34,13 +33,16 @@
|
|||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#if defined(USE_GPSD)
|
||||||
|
#include <gps.h>
|
||||||
|
#endif
|
||||||
#else
|
#else
|
||||||
#include <winsock.h>
|
#include <winsock.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class CAPRSWriter {
|
class CAPRSWriter {
|
||||||
public:
|
public:
|
||||||
CAPRSWriter(const std::string& callsign, const std::string& suffix, const std::string& password, const std::string& address, unsigned int port);
|
CAPRSWriter(const std::string& callsign, const std::string& suffix, const std::string& address, unsigned int port, bool debug);
|
||||||
~CAPRSWriter();
|
~CAPRSWriter();
|
||||||
|
|
||||||
bool open();
|
bool open();
|
||||||
@ -49,7 +51,7 @@ public:
|
|||||||
|
|
||||||
void setStaticLocation(float latitude, float longitude, int height);
|
void setStaticLocation(float latitude, float longitude, int height);
|
||||||
|
|
||||||
void setMobileLocation(const std::string& address, unsigned int port);
|
void setGPSDLocation(const std::string& address, const std::string& port);
|
||||||
|
|
||||||
void write(const char* data);
|
void write(const char* data);
|
||||||
|
|
||||||
@ -58,21 +60,25 @@ public:
|
|||||||
void close();
|
void close();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CAPRSWriterThread* m_thread;
|
CTimer m_idTimer;
|
||||||
bool m_enabled;
|
std::string m_callsign;
|
||||||
CTimer m_idTimer;
|
bool m_debug;
|
||||||
std::string m_callsign;
|
unsigned int m_txFrequency;
|
||||||
unsigned int m_txFrequency;
|
unsigned int m_rxFrequency;
|
||||||
unsigned int m_rxFrequency;
|
float m_latitude;
|
||||||
float m_latitude;
|
float m_longitude;
|
||||||
float m_longitude;
|
int m_height;
|
||||||
int m_height;
|
std::string m_desc;
|
||||||
std::string m_desc;
|
in_addr m_aprsAddress;
|
||||||
in_addr m_mobileGPSAddress;
|
unsigned int m_aprsPort;
|
||||||
unsigned int m_mobileGPSPort;
|
CUDPSocket m_aprsSocket;
|
||||||
CUDPSocket* m_socket;
|
#if defined(USE_GPSD)
|
||||||
|
bool m_gpsdEnabled;
|
||||||
|
std::string m_gpsdAddress;
|
||||||
|
std::string m_gpsdPort;
|
||||||
|
struct gps_data_t m_gpsdData;
|
||||||
|
#endif
|
||||||
|
|
||||||
bool pollGPS();
|
|
||||||
void sendIdFrameFixed();
|
void sendIdFrameFixed();
|
||||||
void sendIdFrameMobile();
|
void sendIdFrameMobile();
|
||||||
};
|
};
|
||||||
|
@ -1,279 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2010-2014,2016,2020 by Jonathan Naylor G4KLX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "APRSWriterThread.h"
|
|
||||||
#include "Utils.h"
|
|
||||||
#include "Log.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <functional>
|
|
||||||
#include <cctype>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
// #define DUMP_TX
|
|
||||||
|
|
||||||
const unsigned int CALLSIGN_LENGTH = 8U;
|
|
||||||
|
|
||||||
const unsigned int APRS_TIMEOUT = 10U;
|
|
||||||
|
|
||||||
CAPRSWriterThread::CAPRSWriterThread(const std::string& callsign, const std::string& password, const std::string& address, unsigned int port) :
|
|
||||||
CThread(),
|
|
||||||
m_username(callsign),
|
|
||||||
m_password(password),
|
|
||||||
m_socket(address, port),
|
|
||||||
m_queue(20U, "APRS Queue"),
|
|
||||||
m_exit(false),
|
|
||||||
m_connected(false),
|
|
||||||
m_reconnectTimer(1000U),
|
|
||||||
m_tries(1U),
|
|
||||||
m_APRSReadCallback(NULL),
|
|
||||||
m_filter(),
|
|
||||||
m_clientName("YSFGateway")
|
|
||||||
{
|
|
||||||
assert(!callsign.empty());
|
|
||||||
assert(!password.empty());
|
|
||||||
assert(!address.empty());
|
|
||||||
assert(port > 0U);
|
|
||||||
|
|
||||||
m_username.resize(CALLSIGN_LENGTH, ' ');
|
|
||||||
m_username.erase(std::find_if(m_username.rbegin(), m_username.rend(), std::not1(std::ptr_fun<int, int>(std::isspace))).base(), m_username.end());
|
|
||||||
std::transform(m_username.begin(), m_username.end(), m_username.begin(), ::toupper);
|
|
||||||
}
|
|
||||||
|
|
||||||
CAPRSWriterThread::CAPRSWriterThread(const std::string& callsign, const std::string& password, const std::string& address, unsigned int port, const std::string& filter, const std::string& clientName) :
|
|
||||||
CThread(),
|
|
||||||
m_username(callsign),
|
|
||||||
m_password(password),
|
|
||||||
m_socket(address, port),
|
|
||||||
m_queue(20U, "APRS Queue"),
|
|
||||||
m_exit(false),
|
|
||||||
m_connected(false),
|
|
||||||
m_reconnectTimer(1000U),
|
|
||||||
m_tries(1U),
|
|
||||||
m_APRSReadCallback(NULL),
|
|
||||||
m_filter(filter),
|
|
||||||
m_clientName(clientName)
|
|
||||||
{
|
|
||||||
assert(!callsign.empty());
|
|
||||||
assert(!password.empty());
|
|
||||||
assert(!address.empty());
|
|
||||||
assert(port > 0U);
|
|
||||||
|
|
||||||
m_username.resize(CALLSIGN_LENGTH, ' ');
|
|
||||||
m_username.erase(std::find_if(m_username.rbegin(), m_username.rend(), std::not1(std::ptr_fun<int, int>(std::isspace))).base(), m_username.end());
|
|
||||||
std::transform(m_username.begin(), m_username.end(), m_username.begin(), ::toupper);
|
|
||||||
}
|
|
||||||
|
|
||||||
CAPRSWriterThread::~CAPRSWriterThread()
|
|
||||||
{
|
|
||||||
m_username.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CAPRSWriterThread::start()
|
|
||||||
{
|
|
||||||
run();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CAPRSWriterThread::entry()
|
|
||||||
{
|
|
||||||
LogMessage("Starting the APRS Writer thread");
|
|
||||||
|
|
||||||
m_connected = connect();
|
|
||||||
if (!m_connected) {
|
|
||||||
LogError("Connect attempt to the APRS server has failed");
|
|
||||||
startReconnectionTimer();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
while (!m_exit) {
|
|
||||||
if (!m_connected) {
|
|
||||||
if (m_reconnectTimer.isRunning() && m_reconnectTimer.hasExpired()) {
|
|
||||||
m_reconnectTimer.stop();
|
|
||||||
|
|
||||||
m_connected = connect();
|
|
||||||
if (!m_connected) {
|
|
||||||
LogError("Reconnect attempt to the APRS server has failed");
|
|
||||||
startReconnectionTimer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_connected) {
|
|
||||||
m_tries = 0U;
|
|
||||||
|
|
||||||
if (!m_queue.isEmpty()){
|
|
||||||
char* p = NULL;
|
|
||||||
m_queue.getData(&p, 1U);
|
|
||||||
|
|
||||||
LogMessage("APRS ==> %s", p);
|
|
||||||
|
|
||||||
::strcat(p, "\r\n");
|
|
||||||
|
|
||||||
bool ret = m_socket.write((unsigned char*)p, (unsigned int)::strlen(p));
|
|
||||||
if (!ret) {
|
|
||||||
m_connected = false;
|
|
||||||
m_socket.close();
|
|
||||||
LogError("Connection to the APRS thread has failed");
|
|
||||||
startReconnectionTimer();
|
|
||||||
}
|
|
||||||
|
|
||||||
delete[] p;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
std::string line;
|
|
||||||
int length = m_socket.readLine(line, APRS_TIMEOUT);
|
|
||||||
|
|
||||||
if (length < 0) {
|
|
||||||
m_connected = false;
|
|
||||||
m_socket.close();
|
|
||||||
LogError("Error when reading from the APRS server");
|
|
||||||
startReconnectionTimer();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(length > 0 && line.at(0U) != '#'//check if we have something and if that something is an APRS frame
|
|
||||||
&& m_APRSReadCallback != NULL)//do we have someone wanting an APRS Frame?
|
|
||||||
{
|
|
||||||
//wxLogMessage(wxT("Received APRS Frame : ") + line);
|
|
||||||
m_APRSReadCallback(std::string(line));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_connected)
|
|
||||||
m_socket.close();
|
|
||||||
|
|
||||||
while (!m_queue.isEmpty()) {
|
|
||||||
char* p = NULL;
|
|
||||||
m_queue.getData(&p, 1U);
|
|
||||||
delete[] p;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (std::exception& e) {
|
|
||||||
LogError("Exception raised in the APRS Writer thread - \"%s\"", e.what());
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
LogError("Unknown exception raised in the APRS Writer thread");
|
|
||||||
}
|
|
||||||
|
|
||||||
LogMessage("Stopping the APRS Writer thread");
|
|
||||||
}
|
|
||||||
|
|
||||||
void CAPRSWriterThread::setReadAPRSCallback(ReadAPRSFrameCallback cb)
|
|
||||||
{
|
|
||||||
m_APRSReadCallback = cb;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CAPRSWriterThread::write(const char* data)
|
|
||||||
{
|
|
||||||
assert(data != NULL);
|
|
||||||
|
|
||||||
if (!m_connected)
|
|
||||||
return;
|
|
||||||
|
|
||||||
unsigned int len = (unsigned int)::strlen(data);
|
|
||||||
|
|
||||||
char* p = new char[len + 5U];
|
|
||||||
::strcpy(p, data);
|
|
||||||
|
|
||||||
m_queue.addData(&p, 1U);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CAPRSWriterThread::isConnected() const
|
|
||||||
{
|
|
||||||
return m_connected;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CAPRSWriterThread::stop()
|
|
||||||
{
|
|
||||||
m_exit = true;
|
|
||||||
|
|
||||||
wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CAPRSWriterThread::clock(unsigned int ms)
|
|
||||||
{
|
|
||||||
m_reconnectTimer.clock(ms);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CAPRSWriterThread::connect()
|
|
||||||
{
|
|
||||||
bool ret = m_socket.open();
|
|
||||||
if (!ret)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
//wait for lgin banner
|
|
||||||
int length;
|
|
||||||
std::string serverResponse;
|
|
||||||
length = m_socket.readLine(serverResponse, APRS_TIMEOUT);
|
|
||||||
if (length == 0) {
|
|
||||||
LogError("No reply from the APRS server after %u seconds", APRS_TIMEOUT);
|
|
||||||
m_socket.close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LogMessage("Received login banner : %s", serverResponse.c_str());
|
|
||||||
|
|
||||||
std::string filter(m_filter);
|
|
||||||
if (filter.length() > 0)
|
|
||||||
filter.insert(0U, " filter ");
|
|
||||||
|
|
||||||
char connectString[200U];
|
|
||||||
::sprintf(connectString, "user %s pass %s vers %s%s\n", m_username.c_str(), m_password.c_str(), (m_clientName.length() ? m_clientName : "YSFGateway").c_str(), filter.c_str());
|
|
||||||
|
|
||||||
ret = m_socket.writeLine(std::string(connectString));
|
|
||||||
if (!ret) {
|
|
||||||
m_socket.close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
length = m_socket.readLine(serverResponse, APRS_TIMEOUT);
|
|
||||||
if (length == 0) {
|
|
||||||
LogError("No reply from the APRS server after %u seconds", APRS_TIMEOUT);
|
|
||||||
m_socket.close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (length < 0) {
|
|
||||||
LogError("Error when reading from the APRS server");
|
|
||||||
m_socket.close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LogMessage("Response from APRS server: %s", serverResponse.c_str());
|
|
||||||
|
|
||||||
LogMessage("Connected to the APRS server");
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CAPRSWriterThread::startReconnectionTimer()
|
|
||||||
{
|
|
||||||
// Clamp at a ten minutes reconnect time
|
|
||||||
m_tries++;
|
|
||||||
if (m_tries > 10U)
|
|
||||||
m_tries = 10U;
|
|
||||||
|
|
||||||
m_reconnectTimer.setTimeout(m_tries * 60U);
|
|
||||||
m_reconnectTimer.start();
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2010,2011,2012,2016,2020 by Jonathan Naylor G4KLX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef APRSWriterThread_H
|
|
||||||
#define APRSWriterThread_H
|
|
||||||
|
|
||||||
#include "TCPSocket.h"
|
|
||||||
#include "RingBuffer.h"
|
|
||||||
#include "Timer.h"
|
|
||||||
#include "Thread.h"
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
typedef void (*ReadAPRSFrameCallback)(const std::string&);
|
|
||||||
|
|
||||||
class CAPRSWriterThread : public CThread {
|
|
||||||
public:
|
|
||||||
CAPRSWriterThread(const std::string& callsign, const std::string& password, const std::string& address, unsigned int port);
|
|
||||||
CAPRSWriterThread(const std::string& callsign, const std::string& password, const std::string& address, unsigned int port, const std::string& filter, const std::string& clientName);
|
|
||||||
virtual ~CAPRSWriterThread();
|
|
||||||
|
|
||||||
virtual bool start();
|
|
||||||
|
|
||||||
virtual bool isConnected() const;
|
|
||||||
|
|
||||||
virtual void write(const char* data);
|
|
||||||
|
|
||||||
virtual void entry();
|
|
||||||
|
|
||||||
virtual void stop();
|
|
||||||
|
|
||||||
void setReadAPRSCallback(ReadAPRSFrameCallback cb);
|
|
||||||
|
|
||||||
void clock(unsigned int ms);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string m_username;
|
|
||||||
std::string m_password;
|
|
||||||
CTCPSocket m_socket;
|
|
||||||
CRingBuffer<char*> m_queue;
|
|
||||||
bool m_exit;
|
|
||||||
bool m_connected;
|
|
||||||
CTimer m_reconnectTimer;
|
|
||||||
unsigned int m_tries;
|
|
||||||
ReadAPRSFrameCallback m_APRSReadCallback;
|
|
||||||
std::string m_filter;
|
|
||||||
std::string m_clientName;
|
|
||||||
|
|
||||||
bool connect();
|
|
||||||
void startReconnectionTimer();
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
@ -33,9 +33,9 @@ enum SECTION {
|
|||||||
SECTION_ID_LOOKUP,
|
SECTION_ID_LOOKUP,
|
||||||
SECTION_VOICE,
|
SECTION_VOICE,
|
||||||
SECTION_LOG,
|
SECTION_LOG,
|
||||||
SECTION_APRS_FI,
|
SECTION_APRS,
|
||||||
SECTION_NETWORK,
|
SECTION_NETWORK,
|
||||||
SECTION_MOBILE_GPS,
|
SECTION_GPSD,
|
||||||
SECTION_REMOTE_COMMANDS
|
SECTION_REMOTE_COMMANDS
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ m_rptProtocol("Icom"),
|
|||||||
m_rptAddress(),
|
m_rptAddress(),
|
||||||
m_rptPort(0U),
|
m_rptPort(0U),
|
||||||
m_myPort(0U),
|
m_myPort(0U),
|
||||||
m_rptDebug(false),
|
m_debug(false),
|
||||||
m_daemon(false),
|
m_daemon(false),
|
||||||
m_rxFrequency(0U),
|
m_rxFrequency(0U),
|
||||||
m_txFrequency(0U),
|
m_txFrequency(0U),
|
||||||
@ -65,9 +65,8 @@ m_voiceDirectory(),
|
|||||||
m_logFilePath(),
|
m_logFilePath(),
|
||||||
m_logFileRoot(),
|
m_logFileRoot(),
|
||||||
m_aprsEnabled(false),
|
m_aprsEnabled(false),
|
||||||
m_aprsServer(),
|
m_aprsAddress("127.0.0.1"),
|
||||||
m_aprsPort(0U),
|
m_aprsPort(8673U),
|
||||||
m_aprsPassword(),
|
|
||||||
m_aprsSuffix(),
|
m_aprsSuffix(),
|
||||||
m_aprsDescription(),
|
m_aprsDescription(),
|
||||||
m_networkPort(0U),
|
m_networkPort(0U),
|
||||||
@ -81,9 +80,9 @@ m_networkNXDN2DMRPort(0U),
|
|||||||
m_networkStartup(9999U),
|
m_networkStartup(9999U),
|
||||||
m_networkInactivityTimeout(0U),
|
m_networkInactivityTimeout(0U),
|
||||||
m_networkDebug(false),
|
m_networkDebug(false),
|
||||||
m_mobileGPSEnabled(false),
|
m_gpsdEnabled(false),
|
||||||
m_mobileGPSAddress(),
|
m_gpsdAddress(),
|
||||||
m_mobileGPSPort(0U),
|
m_gpsdPort(),
|
||||||
m_remoteCommandsEnabled(false),
|
m_remoteCommandsEnabled(false),
|
||||||
m_remoteCommandsPort(6075U)
|
m_remoteCommandsPort(6075U)
|
||||||
{
|
{
|
||||||
@ -119,12 +118,12 @@ bool CConf::read()
|
|||||||
section = SECTION_VOICE;
|
section = SECTION_VOICE;
|
||||||
else if (::strncmp(buffer, "[Log]", 5U) == 0)
|
else if (::strncmp(buffer, "[Log]", 5U) == 0)
|
||||||
section = SECTION_LOG;
|
section = SECTION_LOG;
|
||||||
else if (::strncmp(buffer, "[aprs.fi]", 9U) == 0)
|
else if (::strncmp(buffer, "[APRS]", 6U) == 0)
|
||||||
section = SECTION_APRS_FI;
|
section = SECTION_APRS;
|
||||||
else if (::strncmp(buffer, "[Network]", 9U) == 0)
|
else if (::strncmp(buffer, "[Network]", 9U) == 0)
|
||||||
section = SECTION_NETWORK;
|
section = SECTION_NETWORK;
|
||||||
else if (::strncmp(buffer, "[Mobile GPS]", 12U) == 0)
|
else if (::strncmp(buffer, "[GPSD]", 6U) == 0)
|
||||||
section = SECTION_MOBILE_GPS;
|
section = SECTION_GPSD;
|
||||||
else if (::strncmp(buffer, "[Remote Commands]", 17U) == 0)
|
else if (::strncmp(buffer, "[Remote Commands]", 17U) == 0)
|
||||||
section = SECTION_REMOTE_COMMANDS;
|
section = SECTION_REMOTE_COMMANDS;
|
||||||
else
|
else
|
||||||
@ -158,7 +157,7 @@ bool CConf::read()
|
|||||||
else if (::strcmp(key, "LocalPort") == 0)
|
else if (::strcmp(key, "LocalPort") == 0)
|
||||||
m_myPort = (unsigned int)::atoi(value);
|
m_myPort = (unsigned int)::atoi(value);
|
||||||
else if (::strcmp(key, "Debug") == 0)
|
else if (::strcmp(key, "Debug") == 0)
|
||||||
m_rptDebug = ::atoi(value) == 1;
|
m_debug = ::atoi(value) == 1;
|
||||||
else if (::strcmp(key, "Daemon") == 0)
|
else if (::strcmp(key, "Daemon") == 0)
|
||||||
m_daemon = ::atoi(value) == 1;
|
m_daemon = ::atoi(value) == 1;
|
||||||
} else if (section == SECTION_INFO) {
|
} else if (section == SECTION_INFO) {
|
||||||
@ -195,15 +194,13 @@ bool CConf::read()
|
|||||||
m_logFilePath = value;
|
m_logFilePath = value;
|
||||||
else if (::strcmp(key, "FileRoot") == 0)
|
else if (::strcmp(key, "FileRoot") == 0)
|
||||||
m_logFileRoot = value;
|
m_logFileRoot = value;
|
||||||
} else if (section == SECTION_APRS_FI) {
|
} else if (section == SECTION_APRS) {
|
||||||
if (::strcmp(key, "Enable") == 0)
|
if (::strcmp(key, "Enable") == 0)
|
||||||
m_aprsEnabled = ::atoi(value) == 1;
|
m_aprsEnabled = ::atoi(value) == 1;
|
||||||
else if (::strcmp(key, "Server") == 0)
|
else if (::strcmp(key, "Address") == 0)
|
||||||
m_aprsServer = value;
|
m_aprsAddress = value;
|
||||||
else if (::strcmp(key, "Port") == 0)
|
else if (::strcmp(key, "Port") == 0)
|
||||||
m_aprsPort = (unsigned int)::atoi(value);
|
m_aprsPort = (unsigned int)::atoi(value);
|
||||||
else if (::strcmp(key, "Password") == 0)
|
|
||||||
m_aprsPassword = value;
|
|
||||||
else if (::strcmp(key, "Suffix") == 0)
|
else if (::strcmp(key, "Suffix") == 0)
|
||||||
m_aprsSuffix = value;
|
m_aprsSuffix = value;
|
||||||
else if (::strcmp(key, "Description") == 0)
|
else if (::strcmp(key, "Description") == 0)
|
||||||
@ -231,13 +228,13 @@ bool CConf::read()
|
|||||||
m_networkInactivityTimeout = (unsigned int)::atoi(value);
|
m_networkInactivityTimeout = (unsigned int)::atoi(value);
|
||||||
else if (::strcmp(key, "Debug") == 0)
|
else if (::strcmp(key, "Debug") == 0)
|
||||||
m_networkDebug = ::atoi(value) == 1;
|
m_networkDebug = ::atoi(value) == 1;
|
||||||
} else if (section == SECTION_MOBILE_GPS) {
|
} else if (section == SECTION_GPSD) {
|
||||||
if (::strcmp(key, "Enable") == 0)
|
if (::strcmp(key, "Enable") == 0)
|
||||||
m_mobileGPSEnabled = ::atoi(value) == 1;
|
m_gpsdEnabled = ::atoi(value) == 1;
|
||||||
else if (::strcmp(key, "Address") == 0)
|
else if (::strcmp(key, "Address") == 0)
|
||||||
m_mobileGPSAddress = value;
|
m_gpsdAddress = value;
|
||||||
else if (::strcmp(key, "Port") == 0)
|
else if (::strcmp(key, "Port") == 0)
|
||||||
m_mobileGPSPort = (unsigned int)::atoi(value);
|
m_gpsdPort = value;
|
||||||
} else if (section == SECTION_REMOTE_COMMANDS) {
|
} else if (section == SECTION_REMOTE_COMMANDS) {
|
||||||
if (::strcmp(key, "Enable") == 0)
|
if (::strcmp(key, "Enable") == 0)
|
||||||
m_remoteCommandsEnabled = ::atoi(value) == 1;
|
m_remoteCommandsEnabled = ::atoi(value) == 1;
|
||||||
@ -281,9 +278,9 @@ unsigned int CConf::getMyPort() const
|
|||||||
return m_myPort;
|
return m_myPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CConf::getRptDebug() const
|
bool CConf::getDebug() const
|
||||||
{
|
{
|
||||||
return m_rptDebug;
|
return m_debug;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CConf::getDaemon() const
|
bool CConf::getDaemon() const
|
||||||
@ -366,9 +363,9 @@ bool CConf::getAPRSEnabled() const
|
|||||||
return m_aprsEnabled;
|
return m_aprsEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string CConf::getAPRSServer() const
|
std::string CConf::getAPRSAddress() const
|
||||||
{
|
{
|
||||||
return m_aprsServer;
|
return m_aprsAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int CConf::getAPRSPort() const
|
unsigned int CConf::getAPRSPort() const
|
||||||
@ -376,11 +373,6 @@ unsigned int CConf::getAPRSPort() const
|
|||||||
return m_aprsPort;
|
return m_aprsPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string CConf::getAPRSPassword() const
|
|
||||||
{
|
|
||||||
return m_aprsPassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string CConf::getAPRSSuffix() const
|
std::string CConf::getAPRSSuffix() const
|
||||||
{
|
{
|
||||||
return m_aprsSuffix;
|
return m_aprsSuffix;
|
||||||
@ -451,19 +443,19 @@ bool CConf::getNetworkDebug() const
|
|||||||
return m_networkDebug;
|
return m_networkDebug;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CConf::getMobileGPSEnabled() const
|
bool CConf::getGPSDEnabled() const
|
||||||
{
|
{
|
||||||
return m_mobileGPSEnabled;
|
return m_gpsdEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string CConf::getMobileGPSAddress() const
|
std::string CConf::getGPSDAddress() const
|
||||||
{
|
{
|
||||||
return m_mobileGPSAddress;
|
return m_gpsdAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int CConf::getMobileGPSPort() const
|
std::string CConf::getGPSDPort() const
|
||||||
{
|
{
|
||||||
return m_mobileGPSPort;
|
return m_gpsdPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CConf::getRemoteCommandsEnabled() const
|
bool CConf::getRemoteCommandsEnabled() const
|
||||||
|
@ -37,7 +37,7 @@ public:
|
|||||||
std::string getRptAddress() const;
|
std::string getRptAddress() const;
|
||||||
unsigned int getRptPort() const;
|
unsigned int getRptPort() const;
|
||||||
unsigned int getMyPort() const;
|
unsigned int getMyPort() const;
|
||||||
bool getRptDebug() const;
|
bool getDebug() const;
|
||||||
bool getDaemon() const;
|
bool getDaemon() const;
|
||||||
|
|
||||||
// The Info section
|
// The Info section
|
||||||
@ -59,11 +59,10 @@ public:
|
|||||||
std::string getVoiceLanguage() const;
|
std::string getVoiceLanguage() const;
|
||||||
std::string getVoiceDirectory() const;
|
std::string getVoiceDirectory() const;
|
||||||
|
|
||||||
// The aprs.fi section
|
// The APRS section
|
||||||
bool getAPRSEnabled() const;
|
bool getAPRSEnabled() const;
|
||||||
std::string getAPRSServer() const;
|
std::string getAPRSAddress() const;
|
||||||
unsigned int getAPRSPort() const;
|
unsigned int getAPRSPort() const;
|
||||||
std::string getAPRSPassword() const;
|
|
||||||
std::string getAPRSSuffix() const;
|
std::string getAPRSSuffix() const;
|
||||||
std::string getAPRSDescription() const;
|
std::string getAPRSDescription() const;
|
||||||
|
|
||||||
@ -84,10 +83,10 @@ public:
|
|||||||
unsigned int getNetworkInactivityTimeout() const;
|
unsigned int getNetworkInactivityTimeout() const;
|
||||||
bool getNetworkDebug() const;
|
bool getNetworkDebug() const;
|
||||||
|
|
||||||
// The Mobile GPS section
|
// The GPSD section
|
||||||
bool getMobileGPSEnabled() const;
|
bool getGPSDEnabled() const;
|
||||||
std::string getMobileGPSAddress() const;
|
std::string getGPSDAddress() const;
|
||||||
unsigned int getMobileGPSPort() const;
|
std::string getGPSDPort() const;
|
||||||
|
|
||||||
// The Remote Commands section
|
// The Remote Commands section
|
||||||
bool getRemoteCommandsEnabled() const;
|
bool getRemoteCommandsEnabled() const;
|
||||||
@ -101,7 +100,7 @@ private:
|
|||||||
std::string m_rptAddress;
|
std::string m_rptAddress;
|
||||||
unsigned int m_rptPort;
|
unsigned int m_rptPort;
|
||||||
unsigned int m_myPort;
|
unsigned int m_myPort;
|
||||||
bool m_rptDebug;
|
bool m_debug;
|
||||||
bool m_daemon;
|
bool m_daemon;
|
||||||
|
|
||||||
unsigned int m_rxFrequency;
|
unsigned int m_rxFrequency;
|
||||||
@ -124,9 +123,8 @@ private:
|
|||||||
std::string m_logFileRoot;
|
std::string m_logFileRoot;
|
||||||
|
|
||||||
bool m_aprsEnabled;
|
bool m_aprsEnabled;
|
||||||
std::string m_aprsServer;
|
std::string m_aprsAddress;
|
||||||
unsigned int m_aprsPort;
|
unsigned int m_aprsPort;
|
||||||
std::string m_aprsPassword;
|
|
||||||
std::string m_aprsSuffix;
|
std::string m_aprsSuffix;
|
||||||
std::string m_aprsDescription;
|
std::string m_aprsDescription;
|
||||||
|
|
||||||
@ -142,9 +140,9 @@ private:
|
|||||||
unsigned int m_networkInactivityTimeout;
|
unsigned int m_networkInactivityTimeout;
|
||||||
bool m_networkDebug;
|
bool m_networkDebug;
|
||||||
|
|
||||||
bool m_mobileGPSEnabled;
|
bool m_gpsdEnabled;
|
||||||
std::string m_mobileGPSAddress;
|
std::string m_gpsdAddress;
|
||||||
unsigned int m_mobileGPSPort;
|
std::string m_gpsdPort;
|
||||||
|
|
||||||
bool m_remoteCommandsEnabled;
|
bool m_remoteCommandsEnabled;
|
||||||
unsigned int m_remoteCommandsPort;
|
unsigned int m_remoteCommandsPort;
|
||||||
|
@ -140,10 +140,10 @@ bool CGPSHandler::processIcom()
|
|||||||
int bearing = ::atoi(pRMC[8U]);
|
int bearing = ::atoi(pRMC[8U]);
|
||||||
int speed = ::atoi(pRMC[7U]);
|
int speed = ::atoi(pRMC[7U]);
|
||||||
|
|
||||||
::sprintf(output, "%s>APDPRS,NXDN*,qAR,%s:!%07.2lf%s/%08.2lf%sr%03d/%03d Icom IDAS via MMDVM",
|
::sprintf(output, "%s>APDPRS,NXDN*,qAR,%s:!%07.2lf%s/%08.2lf%sr%03d/%03d Icom IDAS via MMDVM\r\n",
|
||||||
source.c_str(), m_callsign.c_str(), latitude, pRMC[4U], longitude, pRMC[6U], bearing, speed);
|
source.c_str(), m_callsign.c_str(), latitude, pRMC[4U], longitude, pRMC[6U], bearing, speed);
|
||||||
} else {
|
} else {
|
||||||
::sprintf(output, "%s>APDPRS,NXDN*,qAR,%s:!%07.2lf%s/%08.2lf%sr Icom IDAS via MMDVM",
|
::sprintf(output, "%s>APDPRS,NXDN*,qAR,%s:!%07.2lf%s/%08.2lf%sr Icom IDAS via MMDVM\r\n",
|
||||||
source.c_str(), m_callsign.c_str(), latitude, pRMC[4U], longitude, pRMC[6U]);
|
source.c_str(), m_callsign.c_str(), latitude, pRMC[4U], longitude, pRMC[6U]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,10 +239,10 @@ bool CGPSHandler::processKenwood()
|
|||||||
|
|
||||||
char output[300U];
|
char output[300U];
|
||||||
if (course != 0xFFFFU && speedBefore != 0x3FFU && speedAfter != 0x0FU) {
|
if (course != 0xFFFFU && speedBefore != 0x3FFU && speedAfter != 0x0FU) {
|
||||||
::sprintf(output, "%s>APDPRS,NXDN*,qAR,%s:!%04u.%02u%c/%05u.%02u%cr%03d/%03d Kenwood NEXEDGE via MMDVM",
|
::sprintf(output, "%s>APDPRS,NXDN*,qAR,%s:!%04u.%02u%c/%05u.%02u%cr%03d/%03d Kenwood NEXEDGE via MMDVM\r\n",
|
||||||
source.c_str(), m_callsign.c_str(), latBefore, latAfter / 100U, north, lonBefore, lonAfter / 100U, east, course / 10U, speedBefore);
|
source.c_str(), m_callsign.c_str(), latBefore, latAfter / 100U, north, lonBefore, lonAfter / 100U, east, course / 10U, speedBefore);
|
||||||
} else {
|
} else {
|
||||||
::sprintf(output, "%s>APDPRS,NXDN*,qAR,%s:!%04u.%02u%c/%05u.%02u%cr Kenwood NEXEDGE via MMDVM",
|
::sprintf(output, "%s>APDPRS,NXDN*,qAR,%s:!%04u.%02u%c/%05u.%02u%cr Kenwood NEXEDGE via MMDVM\r\n",
|
||||||
source.c_str(), m_callsign.c_str(), latBefore, latAfter / 100U, north, lonBefore, lonAfter / 100U, east);
|
source.c_str(), m_callsign.c_str(), latBefore, latAfter / 100U, north, lonBefore, lonAfter / 100U, east);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,8 @@ m_rtcpTimer(1000U, 0U, 200U),
|
|||||||
m_hangTimer(1000U, 5U),
|
m_hangTimer(1000U, 5U),
|
||||||
m_hangType(0U),
|
m_hangType(0U),
|
||||||
m_hangSrc(0U),
|
m_hangSrc(0U),
|
||||||
m_hangDst(0U)
|
m_hangDst(0U),
|
||||||
|
m_random()
|
||||||
{
|
{
|
||||||
assert(localPort > 0U);
|
assert(localPort > 0U);
|
||||||
assert(!rptAddress.empty());
|
assert(!rptAddress.empty());
|
||||||
@ -64,6 +65,10 @@ m_hangDst(0U)
|
|||||||
m_sacch = new unsigned char[10U];
|
m_sacch = new unsigned char[10U];
|
||||||
|
|
||||||
m_address = CUDPSocket::lookup(rptAddress);
|
m_address = CUDPSocket::lookup(rptAddress);
|
||||||
|
|
||||||
|
std::random_device rd;
|
||||||
|
std::mt19937 mt(rd());
|
||||||
|
m_random = mt;
|
||||||
}
|
}
|
||||||
|
|
||||||
CKenwoodNetwork::~CKenwoodNetwork()
|
CKenwoodNetwork::~CKenwoodNetwork()
|
||||||
@ -86,7 +91,8 @@ bool CKenwoodNetwork::open()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_ssrc = m_rtpSocket.getLocalAddress();
|
std::uniform_int_distribution<unsigned int> dist(0x00000001, 0xfffffffe);
|
||||||
|
m_ssrc = dist(m_random);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
class CKenwoodNetwork : public IRptNetwork {
|
class CKenwoodNetwork : public IRptNetwork {
|
||||||
public:
|
public:
|
||||||
@ -64,6 +65,7 @@ private:
|
|||||||
unsigned char m_hangType;
|
unsigned char m_hangType;
|
||||||
unsigned short m_hangSrc;
|
unsigned short m_hangSrc;
|
||||||
unsigned short m_hangDst;
|
unsigned short m_hangDst;
|
||||||
|
std::mt19937 m_random;
|
||||||
|
|
||||||
bool processIcomVoiceHeader(const unsigned char* data);
|
bool processIcomVoiceHeader(const unsigned char* data);
|
||||||
bool processIcomVoiceData(const unsigned char* data);
|
bool processIcomVoiceData(const unsigned char* data);
|
||||||
|
@ -1,11 +1,18 @@
|
|||||||
CC = gcc
|
CC = gcc
|
||||||
CXX = g++
|
CXX = g++
|
||||||
|
|
||||||
|
# Use the following CFLAGS and LIBS if you don't want to use gpsd.
|
||||||
CFLAGS = -g -O3 -Wall -std=c++0x -pthread
|
CFLAGS = -g -O3 -Wall -std=c++0x -pthread
|
||||||
LIBS = -lpthread
|
LIBS = -lpthread
|
||||||
|
|
||||||
|
# Use the following CFLAGS and LIBS if you do want to use gpsd.
|
||||||
|
#CFLAGS = -g -O3 -Wall -DUSE_GPSD -std=c++0x -pthread
|
||||||
|
#LIBS = -lpthread -lgps
|
||||||
|
|
||||||
LDFLAGS = -g
|
LDFLAGS = -g
|
||||||
|
|
||||||
OBJECTS = APRSWriter.o APRSWriterThread.o Conf.o GPSHandler.o IcomNetwork.o KenwoodNetwork.o Log.o Mutex.o NXDNCRC.o NXDNGateway.o NXDNLookup.o NXDNNetwork.o \
|
OBJECTS = APRSWriter.o Conf.o GPSHandler.o IcomNetwork.o KenwoodNetwork.o Log.o Mutex.o NXDNCRC.o NXDNGateway.o NXDNLookup.o NXDNNetwork.o \
|
||||||
Reflectors.o RptNetwork.o StopWatch.o TCPSocket.o Thread.o Timer.o UDPSocket.o Utils.o Voice.o
|
Reflectors.o RptNetwork.o StopWatch.o Thread.o Timer.o UDPSocket.o Utils.o Voice.o
|
||||||
|
|
||||||
all: NXDNGateway
|
all: NXDNGateway
|
||||||
|
|
||||||
@ -15,6 +22,9 @@ NXDNGateway: $(OBJECTS)
|
|||||||
%.o: %.cpp
|
%.o: %.cpp
|
||||||
$(CXX) $(CFLAGS) -c -o $@ $<
|
$(CXX) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
|
install:
|
||||||
|
install -m 755 NXDNGateway /usr/local/bin/
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
$(RM) NXDNGateway *.o *.d *.bak *~
|
$(RM) NXDNGateway *.o *.d *.bak *~
|
||||||
|
|
||||||
|
@ -184,9 +184,9 @@ void CNXDNGateway::run()
|
|||||||
std::string protocol = m_conf.getRptProtocol();
|
std::string protocol = m_conf.getRptProtocol();
|
||||||
|
|
||||||
if (protocol == "Kenwood")
|
if (protocol == "Kenwood")
|
||||||
localNetwork = new CKenwoodNetwork(m_conf.getMyPort(), m_conf.getRptAddress(), m_conf.getRptPort(), m_conf.getRptDebug());
|
localNetwork = new CKenwoodNetwork(m_conf.getMyPort(), m_conf.getRptAddress(), m_conf.getRptPort(), m_conf.getDebug());
|
||||||
else
|
else
|
||||||
localNetwork = new CIcomNetwork(m_conf.getMyPort(), m_conf.getRptAddress(), m_conf.getRptPort(), m_conf.getRptDebug());
|
localNetwork = new CIcomNetwork(m_conf.getMyPort(), m_conf.getRptAddress(), m_conf.getRptPort(), m_conf.getDebug());
|
||||||
|
|
||||||
ret = localNetwork->open();
|
ret = localNetwork->open();
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
@ -591,12 +591,12 @@ void CNXDNGateway::createGPS()
|
|||||||
|
|
||||||
std::string callsign = m_conf.getCallsign();
|
std::string callsign = m_conf.getCallsign();
|
||||||
std::string rptSuffix = m_conf.getSuffix();
|
std::string rptSuffix = m_conf.getSuffix();
|
||||||
std::string hostname = m_conf.getAPRSServer();
|
std::string address = m_conf.getAPRSAddress();
|
||||||
unsigned int port = m_conf.getAPRSPort();
|
unsigned int port = m_conf.getAPRSPort();
|
||||||
std::string password = m_conf.getAPRSPassword();
|
|
||||||
std::string suffix = m_conf.getAPRSSuffix();
|
std::string suffix = m_conf.getAPRSSuffix();
|
||||||
|
bool debug = m_conf.getDebug();
|
||||||
|
|
||||||
m_writer = new CAPRSWriter(callsign, rptSuffix, password, hostname, port);
|
m_writer = new CAPRSWriter(callsign, rptSuffix, address, port, debug);
|
||||||
|
|
||||||
unsigned int txFrequency = m_conf.getTxFrequency();
|
unsigned int txFrequency = m_conf.getTxFrequency();
|
||||||
unsigned int rxFrequency = m_conf.getRxFrequency();
|
unsigned int rxFrequency = m_conf.getRxFrequency();
|
||||||
@ -604,12 +604,12 @@ void CNXDNGateway::createGPS()
|
|||||||
|
|
||||||
m_writer->setInfo(txFrequency, rxFrequency, desc);
|
m_writer->setInfo(txFrequency, rxFrequency, desc);
|
||||||
|
|
||||||
bool enabled = m_conf.getMobileGPSEnabled();
|
bool enabled = m_conf.getGPSDEnabled();
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
std::string address = m_conf.getMobileGPSAddress();
|
std::string address = m_conf.getGPSDAddress();
|
||||||
unsigned int port = m_conf.getMobileGPSPort();
|
std::string port = m_conf.getGPSDPort();
|
||||||
|
|
||||||
m_writer->setMobileLocation(address, port);
|
m_writer->setGPSDLocation(address, port);
|
||||||
} else {
|
} else {
|
||||||
float latitude = m_conf.getLatitude();
|
float latitude = m_conf.getLatitude();
|
||||||
float longitude = m_conf.getLongitude();
|
float longitude = m_conf.getLongitude();
|
||||||
|
@ -34,14 +34,12 @@ Enabled=1
|
|||||||
Language=en_GB
|
Language=en_GB
|
||||||
Directory=./Audio
|
Directory=./Audio
|
||||||
|
|
||||||
[aprs.fi]
|
[APRS]
|
||||||
Enable=0
|
Enable=0
|
||||||
# Server=noam.aprs2.net
|
Address=127.0.0.1
|
||||||
Server=euro.aprs2.net
|
Port=8673
|
||||||
Port=14580
|
|
||||||
Password=9999
|
|
||||||
Description=APRS Description
|
|
||||||
Suffix=N
|
Suffix=N
|
||||||
|
Description=APRS Description
|
||||||
|
|
||||||
[Id Lookup]
|
[Id Lookup]
|
||||||
Name=NXDN.csv
|
Name=NXDN.csv
|
||||||
@ -64,10 +62,10 @@ Startup=10200
|
|||||||
InactivityTimeout=10
|
InactivityTimeout=10
|
||||||
Debug=0
|
Debug=0
|
||||||
|
|
||||||
[Mobile GPS]
|
[GPSD]
|
||||||
Enable=0
|
Enable=0
|
||||||
Address=127.0.0.1
|
Address=127.0.0.1
|
||||||
Port=7834
|
Port=2947
|
||||||
|
|
||||||
[Remote Commands]
|
[Remote Commands]
|
||||||
Enable=0
|
Enable=0
|
||||||
|
@ -147,7 +147,6 @@
|
|||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="APRSWriter.h" />
|
<ClInclude Include="APRSWriter.h" />
|
||||||
<ClInclude Include="APRSWriterThread.h" />
|
|
||||||
<ClInclude Include="Conf.h" />
|
<ClInclude Include="Conf.h" />
|
||||||
<ClInclude Include="GPSHandler.h" />
|
<ClInclude Include="GPSHandler.h" />
|
||||||
<ClInclude Include="IcomNetwork.h" />
|
<ClInclude Include="IcomNetwork.h" />
|
||||||
@ -162,7 +161,6 @@
|
|||||||
<ClInclude Include="RingBuffer.h" />
|
<ClInclude Include="RingBuffer.h" />
|
||||||
<ClInclude Include="RptNetwork.h" />
|
<ClInclude Include="RptNetwork.h" />
|
||||||
<ClInclude Include="StopWatch.h" />
|
<ClInclude Include="StopWatch.h" />
|
||||||
<ClInclude Include="TCPSocket.h" />
|
|
||||||
<ClInclude Include="Thread.h" />
|
<ClInclude Include="Thread.h" />
|
||||||
<ClInclude Include="Timer.h" />
|
<ClInclude Include="Timer.h" />
|
||||||
<ClInclude Include="UDPSocket.h" />
|
<ClInclude Include="UDPSocket.h" />
|
||||||
@ -172,7 +170,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="APRSWriter.cpp" />
|
<ClCompile Include="APRSWriter.cpp" />
|
||||||
<ClCompile Include="APRSWriterThread.cpp" />
|
|
||||||
<ClCompile Include="Conf.cpp" />
|
<ClCompile Include="Conf.cpp" />
|
||||||
<ClCompile Include="GPSHandler.cpp" />
|
<ClCompile Include="GPSHandler.cpp" />
|
||||||
<ClCompile Include="IcomNetwork.cpp" />
|
<ClCompile Include="IcomNetwork.cpp" />
|
||||||
@ -186,7 +183,6 @@
|
|||||||
<ClCompile Include="Reflectors.cpp" />
|
<ClCompile Include="Reflectors.cpp" />
|
||||||
<ClCompile Include="RptNetwork.cpp" />
|
<ClCompile Include="RptNetwork.cpp" />
|
||||||
<ClCompile Include="StopWatch.cpp" />
|
<ClCompile Include="StopWatch.cpp" />
|
||||||
<ClCompile Include="TCPSocket.cpp" />
|
|
||||||
<ClCompile Include="Thread.cpp" />
|
<ClCompile Include="Thread.cpp" />
|
||||||
<ClCompile Include="Timer.cpp" />
|
<ClCompile Include="Timer.cpp" />
|
||||||
<ClCompile Include="UDPSocket.cpp" />
|
<ClCompile Include="UDPSocket.cpp" />
|
||||||
|
@ -62,12 +62,6 @@
|
|||||||
<ClInclude Include="APRSWriter.h">
|
<ClInclude Include="APRSWriter.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="APRSWriterThread.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="TCPSocket.h">
|
|
||||||
<Filter>Header Files</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="RingBuffer.h">
|
<ClInclude Include="RingBuffer.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
@ -130,12 +124,6 @@
|
|||||||
<ClCompile Include="APRSWriter.cpp">
|
<ClCompile Include="APRSWriter.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="APRSWriterThread.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="TCPSocket.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="GPSHandler.cpp">
|
<ClCompile Include="GPSHandler.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
@ -2,28 +2,32 @@
|
|||||||
#
|
#
|
||||||
# The format of this file is the number of the Talk Group followed by the host name or address and port
|
# The format of this file is the number of the Talk Group followed by the host name or address and port
|
||||||
#
|
#
|
||||||
# HELLAS Zone TG202
|
|
||||||
|
# 202 HELLAS Zone
|
||||||
202 hellaszone.com 41400
|
202 hellaszone.com 41400
|
||||||
|
|
||||||
# RED NXDN SPAIN
|
# 214 RED SPAIN
|
||||||
214 nxdn214.xreflector.es 41400
|
214 nxdn214.xreflector.es 41400
|
||||||
|
|
||||||
# 302, P25 Canada
|
# 260 HBLink Poland
|
||||||
|
260 nxdn.hblink.pl 41400
|
||||||
|
|
||||||
|
# 302 P25 Canada
|
||||||
302 p25canada.hopto.org 41400
|
302 p25canada.hopto.org 41400
|
||||||
|
|
||||||
# Super Freq 420
|
# 420 Super Freq
|
||||||
420 hb.superfreqdigital.com 41400
|
420 hb.superfreqdigital.com 41400
|
||||||
|
|
||||||
# VKCore, 505
|
# 505 VKCore
|
||||||
505 43.229.63.42 41450
|
505 43.229.63.42 41450
|
||||||
|
|
||||||
# New Zealand, 530
|
# 530 New Zealand
|
||||||
530 zldigitalreflectors.hopto.org 41400
|
530 zldigitalreflectors.hopto.org 41400
|
||||||
|
|
||||||
# XLX545E PA NXDN
|
# XLX545E PA
|
||||||
545 70.44.20.24 41401
|
545 70.44.20.24 41401
|
||||||
|
|
||||||
# RuralMN Reflector 707
|
# 707 RuralMN Reflector
|
||||||
707 707nxdn.kd0ioe.com 41400
|
707 707nxdn.kd0ioe.com 41400
|
||||||
|
|
||||||
# 911 Cop Talk, Multimode TG for Police and First Responders
|
# 911 Cop Talk, Multimode TG for Police and First Responders
|
||||||
@ -32,7 +36,7 @@
|
|||||||
# 914 Latin America
|
# 914 Latin America
|
||||||
914 164.132.96.157 41400
|
914 164.132.96.157 41400
|
||||||
|
|
||||||
# Florida, 1200
|
# 1200 Florida
|
||||||
1200 florida.nxref.org 41400
|
1200 florida.nxref.org 41400
|
||||||
|
|
||||||
# 2140 DMRplus BM Multi
|
# 2140 DMRplus BM Multi
|
||||||
@ -41,10 +45,13 @@
|
|||||||
# 2225 IT Tuscany
|
# 2225 IT Tuscany
|
||||||
2225 80.211.99.134 41400
|
2225 80.211.99.134 41400
|
||||||
|
|
||||||
|
# 2231 IT Multiprotocollo Sardegna
|
||||||
|
2231 nxdn.is0.org 41400
|
||||||
|
|
||||||
# 2249 IT SICILIA
|
# 2249 IT SICILIA
|
||||||
2249 nxdn.digitalsicilia.it 41400
|
2249 nxdn.digitalsicilia.it 41400
|
||||||
|
|
||||||
# RU DMR TG2503
|
# 2503 RU DMR
|
||||||
2503 nxdn.r1ik.ru 41400
|
2503 nxdn.r1ik.ru 41400
|
||||||
|
|
||||||
# 3023 Ontario Crosslink
|
# 3023 Ontario Crosslink
|
||||||
@ -53,31 +60,31 @@
|
|||||||
# 3142 Pennsylvania
|
# 3142 Pennsylvania
|
||||||
3142 3.215.215.169 41402
|
3142 3.215.215.169 41402
|
||||||
|
|
||||||
# VK7 TAS, 5057
|
# 5057 VK7 TAS
|
||||||
5057 45.248.50.37 41400
|
5057 45.248.50.37 41400
|
||||||
|
|
||||||
# Multiprotocolo Argentina
|
# 7225 Multiprotocolo Argentina
|
||||||
7225 ysfarg.ddns.net 41400
|
7225 ysfarg.ddns.net 41400
|
||||||
|
|
||||||
# North America, 10200
|
# 10200 North America
|
||||||
10200 dvswitch.org 42400
|
10200 dvswitch.org 42400
|
||||||
|
|
||||||
# HBLINK Espana, 10301
|
# 10301 HBLINK Espana
|
||||||
10301 ea5gvk.duckdns.org 41400
|
10301 ea5gvk.duckdns.org 41400
|
||||||
|
|
||||||
# NXDN 10302 Multimode BM 21461 EA Spain
|
# 10302 Multimode BM 21461 EA Spain
|
||||||
10302 93.186.254.219 41400
|
10302 93.186.254.219 41400
|
||||||
|
|
||||||
# Italian speaking, 10303
|
# 10303 Italian speaking
|
||||||
10303 185.203.118.8 41400
|
10303 185.203.118.8 41400
|
||||||
|
|
||||||
# REM-ADER Spain Group, 10304
|
# 10304 REM-ADER Spain Group
|
||||||
10304 176.31.161.9 41400
|
10304 176.31.161.9 41400
|
||||||
|
|
||||||
# Pacific, 10400
|
# Pacific, 10400
|
||||||
10400 pacific.nxref.org 41400
|
10400 pacific.nxref.org 41400
|
||||||
|
|
||||||
# Europe, German speaking, 20000
|
# 20000 Europe, German speaking
|
||||||
20000 89.185.97.38 41400
|
20000 89.185.97.38 41400
|
||||||
|
|
||||||
# 20945 Deutschland DL1BH
|
# 20945 Deutschland DL1BH
|
||||||
@ -92,118 +99,125 @@
|
|||||||
# 22202 IT NXDN Sardegna
|
# 22202 IT NXDN Sardegna
|
||||||
22202 nxdn.is0hha.hblink.it 41400
|
22202 nxdn.is0hha.hblink.it 41400
|
||||||
|
|
||||||
# 22220 IT ECHOLINK ITALY
|
# 22221 HBLINK IT-DMR REGIONALE LOMBARDIA
|
||||||
22220 dagobah.hblink.it 41400
|
22221 dagobah.hblink.it 41400
|
||||||
|
|
||||||
# 22245 IT PIEDMONT GDO
|
# 22245 IT PIEDMONT GDO
|
||||||
22245 nxdngdo.duckdns.org 41400
|
22245 nxdngdo.duckdns.org 41400
|
||||||
|
|
||||||
# 22825 Swiss NXDN Reflerctor
|
# 22825 Swiss NXDN Reflerctor
|
||||||
22825 212.237.33.114 41400
|
22825 212.237.33.114 41400
|
||||||
|
|
||||||
# NXDN Scotland, 23551
|
# 23531 RayNet UK
|
||||||
|
23531 51.91.78.118 41400
|
||||||
|
|
||||||
|
# 23551 NXDN Scotland
|
||||||
23551 nxdnscotland.ddns.net 41400
|
23551 nxdnscotland.ddns.net 41400
|
||||||
|
|
||||||
# NX Core, 25000
|
# 25000 NX Core
|
||||||
25000 173.166.94.77 41400
|
25000 173.166.94.77 41400
|
||||||
|
|
||||||
# Russia NXDN Net, 25641
|
# 25641 Russia NXDN Net
|
||||||
25641 194.182.85.217 41400
|
25641 194.182.85.217 41400
|
||||||
|
|
||||||
# Poland, 26000
|
# 26000 Poland
|
||||||
26000 31.0.161.238 41400
|
26000 80.211.249.221 41400
|
||||||
|
|
||||||
# Deutschland, 26200
|
# 26200 Deutschland
|
||||||
26200 26200.ham-nxdn.de 41400
|
26200 26200.ham-nxdn.de 41400
|
||||||
|
|
||||||
# Portuguese speaking test, 10268
|
# 26810 Portuguese speaking test
|
||||||
26810 84.90.7.110 41400
|
26810 84.90.7.110 41400
|
||||||
|
|
||||||
# America-Ragchew, 28299
|
# 28299 America-Ragchew
|
||||||
28299 65.101.7.52 41400
|
28299 65.101.7.52 41400
|
||||||
|
|
||||||
# NorCal-Bridge / Multimode-NXDN, 30639
|
# 30639 NorCal-Bridge / Multimode-NXDN
|
||||||
30639 wiresxdigi.dyndns.org 41400
|
30639 wiresxdigi.dyndns.org 41400
|
||||||
|
|
||||||
# Alabama-Link, 31010
|
# 31010 Alabama-Link
|
||||||
31010 nxdn.alabamalink.info 41400
|
31010 nxdn.alabamalink.info 41400
|
||||||
|
|
||||||
# Colorado Fun Machine WE0FUN Bridge to C4FM, DMR, DStar, P25 and AllStarLink (Analog) http://www.we0fun.com
|
# 31081 Colorado Fun Machine WE0FUN Bridge to C4FM, DMR, DStar, P25 and AllStarLink (Analog) http://www.we0fun.com
|
||||||
31081 nxdn.we0fun.com 41400
|
31081 nxdn.we0fun.com 41400
|
||||||
|
|
||||||
# Colorado HD, 31088
|
# 31088 Colorado HD
|
||||||
31088 54.191.50.212 41400
|
31088 54.191.50.212 41400
|
||||||
|
|
||||||
# Connecticut Chat, 31092
|
# 31092 Connecticut Chat
|
||||||
31092 nxdn.alecwasserman.com 41400
|
31092 nxdn.alecwasserman.com 41400
|
||||||
|
|
||||||
# Kingsland Digital Link
|
# 31137 Kingsland Digital Link
|
||||||
31137 74.91.119.94 41400
|
31137 74.91.119.94 41400
|
||||||
|
|
||||||
# Illinois, 31171
|
# 31171 Illinois
|
||||||
31171 74.208.235.115 41400
|
31171 74.208.235.115 41400
|
||||||
|
|
||||||
# Southern Indiana, 31188
|
# 31188 Southern Indiana
|
||||||
31188 w9windigital.org 41400
|
31188 w9windigital.org 41400
|
||||||
|
|
||||||
# 31264 XLX625 The BROniverse www.wa8bro.com
|
# 31264 XLX625 The BROniverse www.wa8bro.com
|
||||||
31264 nxdn.dudetronics.com 41400
|
31264 nxdn.dudetronics.com 41400
|
||||||
|
|
||||||
# Central New Jersey, 31340
|
# 31340 Central New Jersey
|
||||||
31340 cnjham.msmts.com 41400
|
31340 cnjham.msmts.com 41400
|
||||||
|
|
||||||
# Oklahoma Link, 31403
|
# 31403 Oklahoma Link
|
||||||
31403 3.208.70.29 41400
|
31403 3.208.70.29 41400
|
||||||
|
|
||||||
# XLX545A Pennsylvania Cross Mode, 31425
|
# 31425 XLX545A Pennsylvania Cross Mode
|
||||||
31425 70.44.20.24 41400
|
31425 70.44.20.24 41400
|
||||||
|
|
||||||
# XLX545A Pennsylvania Cross Mode (alt), 31426
|
# 31426 XLX545A Pennsylvania Cross Mode (alt)
|
||||||
31426 3.215.215.169 41400
|
31426 3.215.215.169 41400
|
||||||
|
|
||||||
# Rhode Island Digital Link, 31444
|
# 31444 Rhode Island Digital Link
|
||||||
31444 18.219.32.21 41400
|
31444 18.219.32.21 41400
|
||||||
|
|
||||||
# TGIF Network, 31665
|
# 31665 TGIF Network
|
||||||
31665 tgif.network 41400
|
31665 tgif.network 41400
|
||||||
|
|
||||||
# Pi-Star NXDN Reflector, 31672
|
# 31672 Pi-Star
|
||||||
31672 nxdn-31672.pistar.uk 41400
|
31672 nxdn-31672.pistar.uk 41400
|
||||||
|
|
||||||
# DX-LINK SYSTEM, 31777
|
# 31777 DX-LINK SYSTEM,
|
||||||
31777 8.9.4.102 41400
|
31777 8.9.4.102 41400
|
||||||
|
|
||||||
# CW-Ops Academy, NXDN Reflector 32103
|
# 31983 K8JTK Hub Multimode ILS/DVMIS (K8JTK.org)
|
||||||
|
31983 NXDNReflector31983.K8JTK.org 41400
|
||||||
|
|
||||||
|
# 32103 CW-Ops Academy
|
||||||
32103 cwops.dyndns.org 41400
|
32103 cwops.dyndns.org 41400
|
||||||
|
|
||||||
# OMISS Group, NXDN Reflector 33581
|
# 33581 OMISS Group
|
||||||
33581 omiss.dyndns.org 41400
|
33581 omiss.dyndns.org 41400
|
||||||
|
|
||||||
# Fusion Canada Fr /Wires-x /Ysf /Nxdn Network
|
# 40721 Fusion Canada Fr /Wires-x /Ysf /Nxdn Network
|
||||||
40721 38.110.97.161 41400
|
40721 38.110.97.161 41400
|
||||||
|
|
||||||
# China, 46000
|
# 46000 China
|
||||||
46000 120.234.41.144 41400
|
46000 120.234.41.144 41400
|
||||||
|
|
||||||
# DMR TG50525 bridge, 50525
|
# 50525 DMR TG50525 bridge
|
||||||
50525 50525.nxref.org 41400
|
50525 50525.nxref.org 41400
|
||||||
|
|
||||||
# BM NXCore bridge, 50599
|
# 50599 BM NXCore bridge
|
||||||
50599 nxdn.duckdns.org 41490
|
50599 nxdn.duckdns.org 41490
|
||||||
|
|
||||||
# Thailand DTDXA XLX520N, 52000
|
# 52000 Thailand DTDXA XLX520N
|
||||||
52000 nxdn.dtdxa.com 41400
|
52000 nxdn.dtdxa.com 41400
|
||||||
|
|
||||||
# New Zealand, 53099 linked to BM TG 53099
|
# 53099 New Zealand, 53099 linked to BM TG 53099
|
||||||
53099 203.86.206.49 41400
|
53099 203.86.206.49 41400
|
||||||
|
|
||||||
# World Wide, 65000
|
# 65000 World Wide
|
||||||
65000 176.9.1.168 41400
|
65000 176.9.1.168 41400
|
||||||
|
|
||||||
# French-Test, 65208
|
# 65208 French-Test
|
||||||
65208 m55.evxonline.net 41400
|
65208 m55.evxonline.net 41400
|
||||||
|
|
||||||
# NXDN - 2007DXGROUP
|
# 65100 NXDN - 2007DXGROUP
|
||||||
65100 89.46.75.115 41400
|
65100 89.46.75.115 41400
|
||||||
|
|
||||||
# NXDN SPAIN 21410
|
# 21410 NXDN SPAIN
|
||||||
21410 nxdn21410.nxdn.es 41400
|
21410 nxdn21410.nxdn.es 41400
|
||||||
|
@ -1,232 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2010-2013,2016 by Jonathan Naylor G4KLX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "TCPSocket.h"
|
|
||||||
#include "UDPSocket.h"
|
|
||||||
#include "Log.h"
|
|
||||||
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cassert>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
#if defined(_WIN32) || defined(_WIN64)
|
|
||||||
typedef int ssize_t;
|
|
||||||
#else
|
|
||||||
#include <cerrno>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
CTCPSocket::CTCPSocket(const std::string& address, unsigned int port) :
|
|
||||||
m_address(address),
|
|
||||||
m_port(port),
|
|
||||||
m_fd(-1)
|
|
||||||
{
|
|
||||||
assert(!address.empty());
|
|
||||||
assert(port > 0U);
|
|
||||||
|
|
||||||
#if defined(_WIN32) || defined(_WIN64)
|
|
||||||
WSAData data;
|
|
||||||
int wsaRet = ::WSAStartup(MAKEWORD(2, 2), &data);
|
|
||||||
if (wsaRet != 0)
|
|
||||||
LogError("Error from WSAStartup");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
CTCPSocket::~CTCPSocket()
|
|
||||||
{
|
|
||||||
#if defined(_WIN32) || defined(_WIN64)
|
|
||||||
::WSACleanup();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CTCPSocket::open()
|
|
||||||
{
|
|
||||||
if (m_fd != -1)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (m_address.empty() || m_port == 0U)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
m_fd = ::socket(PF_INET, SOCK_STREAM, 0);
|
|
||||||
if (m_fd < 0) {
|
|
||||||
#if defined(_WIN32) || defined(_WIN64)
|
|
||||||
LogError("Cannot create the TCP client socket, err=%d", ::GetLastError());
|
|
||||||
#else
|
|
||||||
LogError("Cannot create the TCP client socket, err=%d", errno);
|
|
||||||
#endif
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct sockaddr_in addr;
|
|
||||||
::memset(&addr, 0x00, sizeof(struct sockaddr_in));
|
|
||||||
addr.sin_family = AF_INET;
|
|
||||||
addr.sin_port = htons(m_port);
|
|
||||||
addr.sin_addr = CUDPSocket::lookup(m_address);
|
|
||||||
|
|
||||||
if (addr.sin_addr.s_addr == INADDR_NONE) {
|
|
||||||
close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (::connect(m_fd, (sockaddr*)&addr, sizeof(struct sockaddr_in)) == -1) {
|
|
||||||
#if defined(_WIN32) || defined(_WIN64)
|
|
||||||
LogError("Cannot connect the TCP client socket, err=%d", ::GetLastError());
|
|
||||||
#else
|
|
||||||
LogError("Cannot connect the TCP client socket, err=%d", errno);
|
|
||||||
#endif
|
|
||||||
close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int noDelay = 1;
|
|
||||||
if (::setsockopt(m_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&noDelay, sizeof(noDelay)) == -1) {
|
|
||||||
#if defined(_WIN32) || defined(_WIN64)
|
|
||||||
LogError("Cannot set the TCP client socket option, err=%d", ::GetLastError());
|
|
||||||
#else
|
|
||||||
LogError("Cannot set the TCP client socket option, err=%d", errno);
|
|
||||||
#endif
|
|
||||||
close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int CTCPSocket::read(unsigned char* buffer, unsigned int length, unsigned int secs, unsigned int msecs)
|
|
||||||
{
|
|
||||||
assert(buffer != NULL);
|
|
||||||
assert(length > 0U);
|
|
||||||
assert(m_fd != -1);
|
|
||||||
|
|
||||||
// Check that the recv() won't block
|
|
||||||
fd_set readFds;
|
|
||||||
FD_ZERO(&readFds);
|
|
||||||
#if defined(_WIN32) || defined(_WIN64)
|
|
||||||
FD_SET((unsigned int)m_fd, &readFds);
|
|
||||||
#else
|
|
||||||
FD_SET(m_fd, &readFds);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Return after timeout
|
|
||||||
timeval tv;
|
|
||||||
tv.tv_sec = secs;
|
|
||||||
tv.tv_usec = msecs * 1000;
|
|
||||||
|
|
||||||
int ret = ::select(m_fd + 1, &readFds, NULL, NULL, &tv);
|
|
||||||
if (ret < 0) {
|
|
||||||
#if defined(_WIN32) || defined(_WIN64)
|
|
||||||
LogError("Error returned from TCP client select, err=%d", ::GetLastError());
|
|
||||||
#else
|
|
||||||
LogError("Error returned from TCP client select, err=%d", errno);
|
|
||||||
#endif
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(_WIN32) || defined(_WIN64)
|
|
||||||
if (!FD_ISSET((unsigned int)m_fd, &readFds))
|
|
||||||
return 0;
|
|
||||||
#else
|
|
||||||
if (!FD_ISSET(m_fd, &readFds))
|
|
||||||
return 0;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
ssize_t len = ::recv(m_fd, (char*)buffer, length, 0);
|
|
||||||
if (len == 0) {
|
|
||||||
return -2;
|
|
||||||
} else if (len < 0) {
|
|
||||||
#if defined(_WIN32) || defined(_WIN64)
|
|
||||||
LogError("Error returned from recv, err=%d", ::GetLastError());
|
|
||||||
#else
|
|
||||||
LogError("Error returned from recv, err=%d", errno);
|
|
||||||
#endif
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
int CTCPSocket::readLine(std::string& line, unsigned int secs)
|
|
||||||
{
|
|
||||||
// Maybe there is a better way to do this like reading blocks, pushing them for later calls
|
|
||||||
// Nevermind, we'll read one char at a time for the time being.
|
|
||||||
int resultCode;
|
|
||||||
int len = 0;
|
|
||||||
|
|
||||||
line.clear();
|
|
||||||
|
|
||||||
char c[2U];
|
|
||||||
c[1U] = 0x00U;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
resultCode = read((unsigned char*)c, 1U, secs);
|
|
||||||
if (resultCode == 1){
|
|
||||||
line.append(c);
|
|
||||||
len++;
|
|
||||||
}
|
|
||||||
} while (c[0U] != '\n' && resultCode == 1);
|
|
||||||
|
|
||||||
return resultCode <= 0 ? resultCode : len;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CTCPSocket::write(const unsigned char* buffer, unsigned int length)
|
|
||||||
{
|
|
||||||
assert(buffer != NULL);
|
|
||||||
assert(length > 0U);
|
|
||||||
assert(m_fd != -1);
|
|
||||||
|
|
||||||
ssize_t ret = ::send(m_fd, (char *)buffer, length, 0);
|
|
||||||
if (ret != ssize_t(length)) {
|
|
||||||
#if defined(_WIN32) || defined(_WIN64)
|
|
||||||
LogError("Error returned from send, err=%d", ::GetLastError());
|
|
||||||
#else
|
|
||||||
LogError("Error returned from send, err=%d", errno);
|
|
||||||
#endif
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CTCPSocket::writeLine(const std::string& line)
|
|
||||||
{
|
|
||||||
std::string lineCopy(line);
|
|
||||||
if (lineCopy.length() > 0 && lineCopy.at(lineCopy.length() - 1) != '\n')
|
|
||||||
lineCopy.append("\n");
|
|
||||||
|
|
||||||
//stupidly write one char after the other
|
|
||||||
size_t len = lineCopy.length();
|
|
||||||
bool result = true;
|
|
||||||
for (size_t i = 0U; i < len && result; i++){
|
|
||||||
unsigned char c = lineCopy.at(i);
|
|
||||||
result = write(&c , 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CTCPSocket::close()
|
|
||||||
{
|
|
||||||
if (m_fd != -1) {
|
|
||||||
#if defined(_WIN32) || defined(_WIN64)
|
|
||||||
::closesocket(m_fd);
|
|
||||||
#else
|
|
||||||
::close(m_fd);
|
|
||||||
#endif
|
|
||||||
m_fd = -1;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2010,2011,2012,2013,2016 by Jonathan Naylor G4KLX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, write to the Free Software
|
|
||||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef TCPSocket_H
|
|
||||||
#define TCPSocket_H
|
|
||||||
|
|
||||||
#if !defined(_WIN32) && !defined(_WIN64)
|
|
||||||
#include <netdb.h>
|
|
||||||
#include <sys/time.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <netinet/in.h>
|
|
||||||
#include <netinet/tcp.h>
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#else
|
|
||||||
#include <winsock.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
class CTCPSocket {
|
|
||||||
public:
|
|
||||||
CTCPSocket(const std::string& address, unsigned int port);
|
|
||||||
~CTCPSocket();
|
|
||||||
|
|
||||||
bool open();
|
|
||||||
|
|
||||||
int read(unsigned char* buffer, unsigned int length, unsigned int secs, unsigned int msecs = 0U);
|
|
||||||
int readLine(std::string& line, unsigned int secs);
|
|
||||||
bool write(const unsigned char* buffer, unsigned int length);
|
|
||||||
bool writeLine(const std::string& line);
|
|
||||||
|
|
||||||
void close();
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string m_address;
|
|
||||||
unsigned short m_port;
|
|
||||||
int m_fd;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2006-2016,2020 by Jonathan Naylor G4KLX
|
* Copyright (C) 2006-2016 by Jonathan Naylor G4KLX
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -261,31 +261,3 @@ void CUDPSocket::close()
|
|||||||
::close(m_fd);
|
::close(m_fd);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned long CUDPSocket::getLocalAddress() const
|
|
||||||
{
|
|
||||||
unsigned long address = 0UL;
|
|
||||||
|
|
||||||
char hostname[80U];
|
|
||||||
int ret = ::gethostname(hostname, 80);
|
|
||||||
if (ret == -1)
|
|
||||||
return 0UL;
|
|
||||||
|
|
||||||
struct hostent* phe = ::gethostbyname(hostname);
|
|
||||||
if (phe == NULL)
|
|
||||||
return 0UL;
|
|
||||||
|
|
||||||
if (phe->h_addrtype != AF_INET)
|
|
||||||
return 0UL;
|
|
||||||
|
|
||||||
for (unsigned int i = 0U; phe->h_addr_list[i] != NULL; i++) {
|
|
||||||
struct in_addr addr;
|
|
||||||
::memcpy(&addr, phe->h_addr_list[i], sizeof(struct in_addr));
|
|
||||||
if (addr.s_addr != INADDR_LOOPBACK) {
|
|
||||||
address = addr.s_addr;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2009-2011,2013,2015,2016,2020 by Jonathan Naylor G4KLX
|
* Copyright (C) 2009-2011,2013,2015,2016 by Jonathan Naylor G4KLX
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -47,8 +47,6 @@ public:
|
|||||||
|
|
||||||
void close();
|
void close();
|
||||||
|
|
||||||
unsigned long getLocalAddress() const;
|
|
||||||
|
|
||||||
static in_addr lookup(const std::string& hostName);
|
static in_addr lookup(const std::string& hostName);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -19,6 +19,6 @@
|
|||||||
#if !defined(VERSION_H)
|
#if !defined(VERSION_H)
|
||||||
#define VERSION_H
|
#define VERSION_H
|
||||||
|
|
||||||
const char* VERSION = "20200519";
|
const char* VERSION = "20200701";
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -14,5 +14,8 @@ NXDNParrot: $(OBJECTS)
|
|||||||
%.o: %.cpp
|
%.o: %.cpp
|
||||||
$(CXX) $(CFLAGS) -c -o $@ $<
|
$(CXX) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
|
install:
|
||||||
|
install -m 755 NXDNParrot /usr/local/bin/
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
$(RM) NXDNParrot *.o *.d *.bak *~
|
$(RM) NXDNParrot *.o *.d *.bak *~
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <ctime>
|
||||||
|
|
||||||
const unsigned char BIT_MASK_TABLE[] = { 0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U };
|
const unsigned char BIT_MASK_TABLE[] = { 0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U };
|
||||||
|
|
||||||
@ -36,25 +37,42 @@ const unsigned int RTP_PORT = 64000U;
|
|||||||
const unsigned int RTCP_PORT = 64001U;
|
const unsigned int RTCP_PORT = 64001U;
|
||||||
|
|
||||||
CKenwoodNetwork::CKenwoodNetwork(const std::string& address, bool debug) :
|
CKenwoodNetwork::CKenwoodNetwork(const std::string& address, bool debug) :
|
||||||
m_rtcpSocket(RTCP_PORT),
|
|
||||||
m_rtpSocket(RTP_PORT),
|
m_rtpSocket(RTP_PORT),
|
||||||
m_stopWatch(),
|
m_rtcpSocket(RTCP_PORT),
|
||||||
m_address(),
|
m_address(),
|
||||||
|
m_headerSeen(false),
|
||||||
|
m_seen1(false),
|
||||||
|
m_seen2(false),
|
||||||
|
m_seen3(false),
|
||||||
|
m_seen4(false),
|
||||||
|
m_sacch(NULL),
|
||||||
|
m_sessionId(1U),
|
||||||
m_seqNo(0U),
|
m_seqNo(0U),
|
||||||
m_timeStamp(0U),
|
|
||||||
m_ssrc(0U),
|
m_ssrc(0U),
|
||||||
m_debug(debug),
|
m_debug(debug),
|
||||||
m_timer(1000U, 0U, 200U)
|
m_startSecs(0U),
|
||||||
|
m_startUSecs(0U),
|
||||||
|
m_rtcpTimer(1000U, 0U, 200U),
|
||||||
|
m_hangTimer(1000U, 5U),
|
||||||
|
m_hangType(0U),
|
||||||
|
m_hangSrc(0U),
|
||||||
|
m_hangDst(0U),
|
||||||
|
m_random()
|
||||||
{
|
{
|
||||||
assert(!address.empty());
|
assert(!address.empty());
|
||||||
|
|
||||||
|
m_sacch = new unsigned char[10U];
|
||||||
|
|
||||||
m_address = CUDPSocket::lookup(address);
|
m_address = CUDPSocket::lookup(address);
|
||||||
|
|
||||||
::srand((unsigned int)m_stopWatch.time());
|
std::random_device rd;
|
||||||
|
std::mt19937 mt(rd());
|
||||||
|
m_random = mt;
|
||||||
}
|
}
|
||||||
|
|
||||||
CKenwoodNetwork::~CKenwoodNetwork()
|
CKenwoodNetwork::~CKenwoodNetwork()
|
||||||
{
|
{
|
||||||
|
delete[] m_sacch;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CKenwoodNetwork::open()
|
bool CKenwoodNetwork::open()
|
||||||
@ -72,7 +90,8 @@ bool CKenwoodNetwork::open()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_ssrc = ::rand();
|
std::uniform_int_distribution<unsigned int> dist(0x00000001, 0xfffffffe);
|
||||||
|
m_ssrc = dist(m_random);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -107,12 +126,12 @@ bool CKenwoodNetwork::processIcomVoiceHeader(const unsigned char* inData)
|
|||||||
outData[3U] = inData[3U];
|
outData[3U] = inData[3U];
|
||||||
|
|
||||||
// FACCH 1+2
|
// FACCH 1+2
|
||||||
outData[4U] = outData[14U] = inData[6U];
|
outData[4U] = outData[14U] = inData[6U];
|
||||||
outData[5U] = outData[15U] = inData[5U];
|
outData[5U] = outData[15U] = inData[5U];
|
||||||
outData[6U] = outData[16U] = inData[8U];
|
outData[6U] = outData[16U] = inData[8U];
|
||||||
outData[7U] = outData[17U] = inData[7U];
|
outData[7U] = outData[17U] = inData[7U];
|
||||||
outData[8U] = outData[18U] = inData[10U];
|
outData[8U] = outData[18U] = inData[10U];
|
||||||
outData[9U] = outData[19U] = inData[9U];
|
outData[9U] = outData[19U] = inData[9U];
|
||||||
outData[10U] = outData[20U] = inData[12U];
|
outData[10U] = outData[20U] = inData[12U];
|
||||||
outData[11U] = outData[21U] = inData[11U];
|
outData[11U] = outData[21U] = inData[11U];
|
||||||
|
|
||||||
@ -122,13 +141,16 @@ bool CKenwoodNetwork::processIcomVoiceHeader(const unsigned char* inData)
|
|||||||
|
|
||||||
switch (inData[5U] & 0x3FU) {
|
switch (inData[5U] & 0x3FU) {
|
||||||
case 0x01U:
|
case 0x01U:
|
||||||
m_timer.start();
|
m_hangTimer.stop();
|
||||||
writeRTCPData(type, src, dst);
|
m_rtcpTimer.start();
|
||||||
|
writeRTCPStart();
|
||||||
return writeRTPVoiceHeader(outData);
|
return writeRTPVoiceHeader(outData);
|
||||||
case 0x08U:
|
case 0x08U: {
|
||||||
m_timer.stop();
|
m_hangTimer.start();
|
||||||
writeRTCPData(type, src, dst);
|
bool ret = writeRTPVoiceTrailer(outData);
|
||||||
return writeRTPVoiceTrailer(outData);
|
writeRTCPHang(type, src, dst);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -224,23 +246,27 @@ bool CKenwoodNetwork::writeRTPVoiceHeader(const unsigned char* data)
|
|||||||
buffer[0U] = 0x80U;
|
buffer[0U] = 0x80U;
|
||||||
buffer[1U] = 0x66U;
|
buffer[1U] = 0x66U;
|
||||||
|
|
||||||
|
m_seqNo++;
|
||||||
buffer[2U] = (m_seqNo >> 8) & 0xFFU;
|
buffer[2U] = (m_seqNo >> 8) & 0xFFU;
|
||||||
buffer[3U] = (m_seqNo >> 0) & 0xFFU;
|
buffer[3U] = (m_seqNo >> 0) & 0xFFU;
|
||||||
m_seqNo++;
|
|
||||||
|
|
||||||
m_timeStamp = (unsigned long)m_stopWatch.time();
|
unsigned long timeStamp = getTimeStamp();
|
||||||
|
buffer[4U] = (timeStamp >> 24) & 0xFFU;
|
||||||
buffer[4U] = (m_timeStamp >> 24) & 0xFFU;
|
buffer[5U] = (timeStamp >> 16) & 0xFFU;
|
||||||
buffer[5U] = (m_timeStamp >> 16) & 0xFFU;
|
buffer[6U] = (timeStamp >> 8) & 0xFFU;
|
||||||
buffer[6U] = (m_timeStamp >> 8) & 0xFFU;
|
buffer[7U] = (timeStamp >> 0) & 0xFFU;
|
||||||
buffer[7U] = (m_timeStamp >> 0) & 0xFFU;
|
|
||||||
m_timeStamp += 640U;
|
|
||||||
|
|
||||||
buffer[8U] = (m_ssrc >> 24) & 0xFFU;
|
buffer[8U] = (m_ssrc >> 24) & 0xFFU;
|
||||||
buffer[9U] = (m_ssrc >> 16) & 0xFFU;
|
buffer[9U] = (m_ssrc >> 16) & 0xFFU;
|
||||||
buffer[10U] = (m_ssrc >> 8) & 0xFFU;
|
buffer[10U] = (m_ssrc >> 8) & 0xFFU;
|
||||||
buffer[11U] = (m_ssrc >> 0) & 0xFFU;
|
buffer[11U] = (m_ssrc >> 0) & 0xFFU;
|
||||||
|
|
||||||
|
m_sessionId++;
|
||||||
|
buffer[12U] = m_sessionId;
|
||||||
|
|
||||||
|
buffer[13U] = 0x00U;
|
||||||
|
buffer[14U] = 0x00U;
|
||||||
|
buffer[15U] = 0x00U;
|
||||||
buffer[16U] = 0x03U;
|
buffer[16U] = 0x03U;
|
||||||
buffer[17U] = 0x03U;
|
buffer[17U] = 0x03U;
|
||||||
buffer[18U] = 0x04U;
|
buffer[18U] = 0x04U;
|
||||||
@ -267,19 +293,26 @@ bool CKenwoodNetwork::writeRTPVoiceTrailer(const unsigned char* data)
|
|||||||
buffer[0U] = 0x80U;
|
buffer[0U] = 0x80U;
|
||||||
buffer[1U] = 0x66U;
|
buffer[1U] = 0x66U;
|
||||||
|
|
||||||
|
m_seqNo++;
|
||||||
buffer[2U] = (m_seqNo >> 8) & 0xFFU;
|
buffer[2U] = (m_seqNo >> 8) & 0xFFU;
|
||||||
buffer[3U] = (m_seqNo >> 0) & 0xFFU;
|
buffer[3U] = (m_seqNo >> 0) & 0xFFU;
|
||||||
|
|
||||||
buffer[4U] = (m_timeStamp >> 24) & 0xFFU;
|
unsigned long timeStamp = getTimeStamp();
|
||||||
buffer[5U] = (m_timeStamp >> 16) & 0xFFU;
|
buffer[4U] = (timeStamp >> 24) & 0xFFU;
|
||||||
buffer[6U] = (m_timeStamp >> 8) & 0xFFU;
|
buffer[5U] = (timeStamp >> 16) & 0xFFU;
|
||||||
buffer[7U] = (m_timeStamp >> 0) & 0xFFU;
|
buffer[6U] = (timeStamp >> 8) & 0xFFU;
|
||||||
|
buffer[7U] = (timeStamp >> 0) & 0xFFU;
|
||||||
|
|
||||||
buffer[8U] = (m_ssrc >> 24) & 0xFFU;
|
buffer[8U] = (m_ssrc >> 24) & 0xFFU;
|
||||||
buffer[9U] = (m_ssrc >> 16) & 0xFFU;
|
buffer[9U] = (m_ssrc >> 16) & 0xFFU;
|
||||||
buffer[10U] = (m_ssrc >> 8) & 0xFFU;
|
buffer[10U] = (m_ssrc >> 8) & 0xFFU;
|
||||||
buffer[11U] = (m_ssrc >> 0) & 0xFFU;
|
buffer[11U] = (m_ssrc >> 0) & 0xFFU;
|
||||||
|
|
||||||
|
buffer[12U] = m_sessionId;
|
||||||
|
|
||||||
|
buffer[13U] = 0x00U;
|
||||||
|
buffer[14U] = 0x00U;
|
||||||
|
buffer[15U] = 0x00U;
|
||||||
buffer[16U] = 0x03U;
|
buffer[16U] = 0x03U;
|
||||||
buffer[17U] = 0x03U;
|
buffer[17U] = 0x03U;
|
||||||
buffer[18U] = 0x04U;
|
buffer[18U] = 0x04U;
|
||||||
@ -306,21 +339,26 @@ bool CKenwoodNetwork::writeRTPVoiceData(const unsigned char* data)
|
|||||||
buffer[0U] = 0x80U;
|
buffer[0U] = 0x80U;
|
||||||
buffer[1U] = 0x66U;
|
buffer[1U] = 0x66U;
|
||||||
|
|
||||||
|
m_seqNo++;
|
||||||
buffer[2U] = (m_seqNo >> 8) & 0xFFU;
|
buffer[2U] = (m_seqNo >> 8) & 0xFFU;
|
||||||
buffer[3U] = (m_seqNo >> 0) & 0xFFU;
|
buffer[3U] = (m_seqNo >> 0) & 0xFFU;
|
||||||
m_seqNo++;
|
|
||||||
|
|
||||||
buffer[4U] = (m_timeStamp >> 24) & 0xFFU;
|
unsigned long timeStamp = getTimeStamp();
|
||||||
buffer[5U] = (m_timeStamp >> 16) & 0xFFU;
|
buffer[4U] = (timeStamp >> 24) & 0xFFU;
|
||||||
buffer[6U] = (m_timeStamp >> 8) & 0xFFU;
|
buffer[5U] = (timeStamp >> 16) & 0xFFU;
|
||||||
buffer[7U] = (m_timeStamp >> 0) & 0xFFU;
|
buffer[6U] = (timeStamp >> 8) & 0xFFU;
|
||||||
m_timeStamp += 640U;
|
buffer[7U] = (timeStamp >> 0) & 0xFFU;
|
||||||
|
|
||||||
buffer[8U] = (m_ssrc >> 24) & 0xFFU;
|
buffer[8U] = (m_ssrc >> 24) & 0xFFU;
|
||||||
buffer[9U] = (m_ssrc >> 16) & 0xFFU;
|
buffer[9U] = (m_ssrc >> 16) & 0xFFU;
|
||||||
buffer[10U] = (m_ssrc >> 8) & 0xFFU;
|
buffer[10U] = (m_ssrc >> 8) & 0xFFU;
|
||||||
buffer[11U] = (m_ssrc >> 0) & 0xFFU;
|
buffer[11U] = (m_ssrc >> 0) & 0xFFU;
|
||||||
|
|
||||||
|
buffer[12U] = m_sessionId;
|
||||||
|
|
||||||
|
buffer[13U] = 0x00U;
|
||||||
|
buffer[14U] = 0x00U;
|
||||||
|
buffer[15U] = 0x00U;
|
||||||
buffer[16U] = 0x03U;
|
buffer[16U] = 0x03U;
|
||||||
buffer[17U] = 0x02U;
|
buffer[17U] = 0x02U;
|
||||||
buffer[18U] = 0x04U;
|
buffer[18U] = 0x04U;
|
||||||
@ -337,30 +375,59 @@ bool CKenwoodNetwork::writeRTPVoiceData(const unsigned char* data)
|
|||||||
return m_rtpSocket.write(buffer, 59U, m_address, RTP_PORT);
|
return m_rtpSocket.write(buffer, 59U, m_address, RTP_PORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CKenwoodNetwork::writeRTCPPing()
|
bool CKenwoodNetwork::writeRTCPStart()
|
||||||
{
|
{
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
time_t now;
|
||||||
|
::time(&now);
|
||||||
|
|
||||||
|
m_startSecs = uint32_t(now);
|
||||||
|
|
||||||
|
SYSTEMTIME st;
|
||||||
|
::GetSystemTime(&st);
|
||||||
|
|
||||||
|
m_startUSecs = st.wMilliseconds * 1000U;
|
||||||
|
#else
|
||||||
|
struct timeval tod;
|
||||||
|
::gettimeofday(&tod, NULL);
|
||||||
|
|
||||||
|
m_startSecs = tod.tv_sec;
|
||||||
|
m_startUSecs = tod.tv_usec;
|
||||||
|
#endif
|
||||||
|
|
||||||
unsigned char buffer[30U];
|
unsigned char buffer[30U];
|
||||||
::memset(buffer, 0x00U, 30U);
|
::memset(buffer, 0x00U, 30U);
|
||||||
|
|
||||||
buffer[0U] = 0x8AU;
|
buffer[0U] = 0x8AU;
|
||||||
buffer[1U] = 0xCCU;
|
buffer[1U] = 0xCCU;
|
||||||
|
buffer[2U] = 0x00U;
|
||||||
buffer[3U] = 0x06U;
|
buffer[3U] = 0x06U;
|
||||||
|
|
||||||
buffer[4U] = (m_ssrc >> 24) & 0xFFU;
|
buffer[4U] = (m_ssrc >> 24) & 0xFFU;
|
||||||
buffer[5U] = (m_ssrc >> 16) & 0xFFU;
|
buffer[5U] = (m_ssrc >> 16) & 0xFFU;
|
||||||
buffer[6U] = (m_ssrc >> 8) & 0xFFU;
|
buffer[6U] = (m_ssrc >> 8) & 0xFFU;
|
||||||
buffer[7U] = (m_ssrc >> 0) & 0xFFU;
|
buffer[7U] = (m_ssrc >> 0) & 0xFFU;
|
||||||
|
|
||||||
buffer[8U] = 'K';
|
buffer[8U] = 'K';
|
||||||
buffer[9U] = 'W';
|
buffer[9U] = 'W';
|
||||||
buffer[10U] = 'N';
|
buffer[10U] = 'N';
|
||||||
buffer[11U] = 'E';
|
buffer[11U] = 'E';
|
||||||
|
|
||||||
|
buffer[12U] = (m_startSecs >> 24) & 0xFFU;
|
||||||
|
buffer[13U] = (m_startSecs >> 16) & 0xFFU;
|
||||||
|
buffer[14U] = (m_startSecs >> 8) & 0xFFU;
|
||||||
|
buffer[15U] = (m_startSecs >> 0) & 0xFFU;
|
||||||
|
|
||||||
|
buffer[16U] = (m_startUSecs >> 24) & 0xFFU;
|
||||||
|
buffer[17U] = (m_startUSecs >> 16) & 0xFFU;
|
||||||
|
buffer[18U] = (m_startUSecs >> 8) & 0xFFU;
|
||||||
|
buffer[19U] = (m_startUSecs >> 0) & 0xFFU;
|
||||||
|
|
||||||
buffer[22U] = 0x02U;
|
buffer[22U] = 0x02U;
|
||||||
|
|
||||||
buffer[24U] = 0x01U;
|
buffer[24U] = 0x01U;
|
||||||
buffer[25U] = 0x01U;
|
|
||||||
|
buffer[27U] = 0x0AU;
|
||||||
|
|
||||||
if (m_debug)
|
if (m_debug)
|
||||||
CUtils::dump(1U, "Kenwood Network RTCP Data Sent", buffer, 28U);
|
CUtils::dump(1U, "Kenwood Network RTCP Data Sent", buffer, 28U);
|
||||||
@ -368,33 +435,84 @@ bool CKenwoodNetwork::writeRTCPPing()
|
|||||||
return m_rtcpSocket.write(buffer, 28U, m_address, RTCP_PORT);
|
return m_rtcpSocket.write(buffer, 28U, m_address, RTCP_PORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CKenwoodNetwork::writeRTCPData(unsigned char type, unsigned short src, unsigned short dst)
|
bool CKenwoodNetwork::writeRTCPPing()
|
||||||
{
|
{
|
||||||
unsigned char buffer[20U];
|
unsigned char buffer[30U];
|
||||||
::memset(buffer, 0x00U, 20U);
|
::memset(buffer, 0x00U, 30U);
|
||||||
|
|
||||||
buffer[0U] = 0x8BU;
|
buffer[0U] = 0x8AU;
|
||||||
buffer[1U] = 0xCCU;
|
buffer[1U] = 0xCCU;
|
||||||
|
buffer[2U] = 0x00U;
|
||||||
buffer[3U] = 0x04U;
|
buffer[3U] = 0x06U;
|
||||||
|
|
||||||
buffer[4U] = (m_ssrc >> 24) & 0xFFU;
|
buffer[4U] = (m_ssrc >> 24) & 0xFFU;
|
||||||
buffer[5U] = (m_ssrc >> 16) & 0xFFU;
|
buffer[5U] = (m_ssrc >> 16) & 0xFFU;
|
||||||
buffer[6U] = (m_ssrc >> 8) & 0xFFU;
|
buffer[6U] = (m_ssrc >> 8) & 0xFFU;
|
||||||
buffer[7U] = (m_ssrc >> 0) & 0xFFU;
|
buffer[7U] = (m_ssrc >> 0) & 0xFFU;
|
||||||
|
|
||||||
buffer[8U] = 'K';
|
buffer[8U] = 'K';
|
||||||
buffer[9U] = 'W';
|
buffer[9U] = 'W';
|
||||||
buffer[10U] = 'N';
|
buffer[10U] = 'N';
|
||||||
buffer[11U] = 'E';
|
buffer[11U] = 'E';
|
||||||
|
|
||||||
buffer[12U] = (src >> 8) & 0xFFU;
|
buffer[12U] = (m_startSecs >> 24) & 0xFFU;
|
||||||
buffer[13U] = (src >> 0) & 0xFFU;
|
buffer[13U] = (m_startSecs >> 16) & 0xFFU;
|
||||||
|
buffer[14U] = (m_startSecs >> 8) & 0xFFU;
|
||||||
|
buffer[15U] = (m_startSecs >> 0) & 0xFFU;
|
||||||
|
|
||||||
buffer[14U] = (dst >> 8) & 0xFFU;
|
buffer[16U] = (m_startUSecs >> 24) & 0xFFU;
|
||||||
buffer[15U] = (dst >> 0) & 0xFFU;
|
buffer[17U] = (m_startUSecs >> 16) & 0xFFU;
|
||||||
|
buffer[18U] = (m_startUSecs >> 8) & 0xFFU;
|
||||||
|
buffer[19U] = (m_startUSecs >> 0) & 0xFFU;
|
||||||
|
|
||||||
buffer[16U] = type;
|
buffer[22U] = 0x02U;
|
||||||
|
|
||||||
|
buffer[24U] = 0x01U;
|
||||||
|
|
||||||
|
buffer[27U] = 0x7BU;
|
||||||
|
|
||||||
|
if (m_debug)
|
||||||
|
CUtils::dump(1U, "Kenwood Network RTCP Data Sent", buffer, 28U);
|
||||||
|
|
||||||
|
return m_rtcpSocket.write(buffer, 28U, m_address, RTCP_PORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CKenwoodNetwork::writeRTCPHang(unsigned char type, unsigned short src, unsigned short dst)
|
||||||
|
{
|
||||||
|
m_hangType = type;
|
||||||
|
m_hangSrc = src;
|
||||||
|
m_hangDst = dst;
|
||||||
|
|
||||||
|
return writeRTCPHang();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CKenwoodNetwork::writeRTCPHang()
|
||||||
|
{
|
||||||
|
unsigned char buffer[30U];
|
||||||
|
::memset(buffer, 0x00U, 30U);
|
||||||
|
|
||||||
|
buffer[0U] = 0x8BU;
|
||||||
|
buffer[1U] = 0xCCU;
|
||||||
|
buffer[2U] = 0x00U;
|
||||||
|
buffer[3U] = 0x04U;
|
||||||
|
|
||||||
|
buffer[4U] = (m_ssrc >> 24) & 0xFFU;
|
||||||
|
buffer[5U] = (m_ssrc >> 16) & 0xFFU;
|
||||||
|
buffer[6U] = (m_ssrc >> 8) & 0xFFU;
|
||||||
|
buffer[7U] = (m_ssrc >> 0) & 0xFFU;
|
||||||
|
|
||||||
|
buffer[8U] = 'K';
|
||||||
|
buffer[9U] = 'W';
|
||||||
|
buffer[10U] = 'N';
|
||||||
|
buffer[11U] = 'E';
|
||||||
|
|
||||||
|
buffer[12U] = (m_hangSrc >> 8) & 0xFFU;
|
||||||
|
buffer[13U] = (m_hangSrc >> 0) & 0xFFU;
|
||||||
|
|
||||||
|
buffer[14U] = (m_hangDst >> 8) & 0xFFU;
|
||||||
|
buffer[15U] = (m_hangDst >> 0) & 0xFFU;
|
||||||
|
|
||||||
|
buffer[16U] = m_hangType;
|
||||||
|
|
||||||
if (m_debug)
|
if (m_debug)
|
||||||
CUtils::dump(1U, "Kenwood Network RTCP Data Sent", buffer, 20U);
|
CUtils::dump(1U, "Kenwood Network RTCP Data Sent", buffer, 20U);
|
||||||
@ -409,26 +527,23 @@ unsigned int CKenwoodNetwork::read(unsigned char* data)
|
|||||||
unsigned char dummy[BUFFER_LENGTH];
|
unsigned char dummy[BUFFER_LENGTH];
|
||||||
readRTCP(dummy);
|
readRTCP(dummy);
|
||||||
|
|
||||||
bool ret;
|
|
||||||
|
|
||||||
unsigned int len = readRTP(data);
|
unsigned int len = readRTP(data);
|
||||||
if (len > 0U) {
|
switch (len) {
|
||||||
switch (data[9U]) {
|
case 0U: // Nothing received
|
||||||
case 0x05U: // Voice header or trailer
|
return 0U;
|
||||||
ret = processKenwoodVoiceHeader(data);
|
case 35U: // Voice header or trailer
|
||||||
if (!ret)
|
return processKenwoodVoiceHeader(data);
|
||||||
return 0U;
|
case 47U: // Voice data
|
||||||
return 33U;
|
if (m_headerSeen)
|
||||||
case 0x08U: // Voice data
|
return processKenwoodVoiceData(data);
|
||||||
processKenwoodVoiceData(data);
|
else
|
||||||
return 33U;
|
return processKenwoodVoiceLateEntry(data);
|
||||||
default:
|
case 31U: // Data
|
||||||
break;
|
return processKenwoodData(data);
|
||||||
}
|
default:
|
||||||
|
CUtils::dump(5U, "Unknown data received from the Kenwood network", data, len);
|
||||||
|
return 0U;
|
||||||
}
|
}
|
||||||
|
|
||||||
CUtils::dump(5U, "Unknown data received from the Kenwood network", data, len);
|
|
||||||
return 0U;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int CKenwoodNetwork::readRTP(unsigned char* data)
|
unsigned int CKenwoodNetwork::readRTP(unsigned char* data)
|
||||||
@ -444,19 +559,14 @@ unsigned int CKenwoodNetwork::readRTP(unsigned char* data)
|
|||||||
return 0U;
|
return 0U;
|
||||||
|
|
||||||
// Check if the data is for us
|
// Check if the data is for us
|
||||||
if (m_address.s_addr != address.s_addr || port != RTP_PORT) {
|
if (m_address.s_addr != address.s_addr) {
|
||||||
LogMessage("Kenwood RTP packet received from an invalid source, %08X != %08X and/or %u != %u", m_address.s_addr, address.s_addr, RTP_PORT, port);
|
LogMessage("Kenwood RTP packet received from an invalid source, %08X != %08X", m_address.s_addr, address.s_addr);
|
||||||
return 0U;
|
return 0U;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_debug)
|
if (m_debug)
|
||||||
CUtils::dump(1U, "Kenwood Network RTP Data Received", buffer, length);
|
CUtils::dump(1U, "Kenwood Network RTP Data Received", buffer, length);
|
||||||
|
|
||||||
if (length != 47 && length != 59) {
|
|
||||||
LogError("Invalid RTP length of %d", length);
|
|
||||||
return 0U;
|
|
||||||
}
|
|
||||||
|
|
||||||
::memcpy(data, buffer + 12U, length - 12U);
|
::memcpy(data, buffer + 12U, length - 12U);
|
||||||
|
|
||||||
return length - 12U;
|
return length - 12U;
|
||||||
@ -475,19 +585,14 @@ unsigned int CKenwoodNetwork::readRTCP(unsigned char* data)
|
|||||||
return 0U;
|
return 0U;
|
||||||
|
|
||||||
// Check if the data is for us
|
// Check if the data is for us
|
||||||
if (m_address.s_addr != address.s_addr || port != RTCP_PORT) {
|
if (m_address.s_addr != address.s_addr) {
|
||||||
LogMessage("Kenwood RTCP packet received from an invalid source, %08X != %08X and/or %u != %u", m_address.s_addr, address.s_addr, RTCP_PORT, port);
|
LogMessage("Kenwood RTCP packet received from an invalid source, %08X != %08X", m_address.s_addr, address.s_addr);
|
||||||
return 0U;
|
return 0U;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_debug)
|
if (m_debug)
|
||||||
CUtils::dump(1U, "Kenwood Network RTCP Data Received", buffer, length);
|
CUtils::dump(1U, "Kenwood Network RTCP Data Received", buffer, length);
|
||||||
|
|
||||||
if (length != 20 && length != 28) {
|
|
||||||
LogError("Invalid RTCP length of %d", length);
|
|
||||||
return 0U;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (::memcmp(buffer + 8U, "KWNE", 4U) != 0) {
|
if (::memcmp(buffer + 8U, "KWNE", 4U) != 0) {
|
||||||
LogError("Missing RTCP KWNE signature");
|
LogError("Missing RTCP KWNE signature");
|
||||||
return 0U;
|
return 0U;
|
||||||
@ -508,14 +613,23 @@ void CKenwoodNetwork::close()
|
|||||||
|
|
||||||
void CKenwoodNetwork::clock(unsigned int ms)
|
void CKenwoodNetwork::clock(unsigned int ms)
|
||||||
{
|
{
|
||||||
m_timer.clock(ms);
|
m_rtcpTimer.clock(ms);
|
||||||
if (m_timer.isRunning() && m_timer.hasExpired()) {
|
if (m_rtcpTimer.isRunning() && m_rtcpTimer.hasExpired()) {
|
||||||
writeRTCPPing();
|
if (m_hangTimer.isRunning())
|
||||||
m_timer.start();
|
writeRTCPHang();
|
||||||
|
else
|
||||||
|
writeRTCPPing();
|
||||||
|
m_rtcpTimer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_hangTimer.clock(ms);
|
||||||
|
if (m_hangTimer.isRunning() && m_hangTimer.hasExpired()) {
|
||||||
|
m_rtcpTimer.stop();
|
||||||
|
m_hangTimer.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CKenwoodNetwork::processKenwoodVoiceHeader(unsigned char* inData)
|
unsigned int CKenwoodNetwork::processKenwoodVoiceHeader(unsigned char* inData)
|
||||||
{
|
{
|
||||||
assert(inData != NULL);
|
assert(inData != NULL);
|
||||||
|
|
||||||
@ -552,15 +666,27 @@ bool CKenwoodNetwork::processKenwoodVoiceHeader(unsigned char* inData)
|
|||||||
|
|
||||||
switch (outData[5U] & 0x3FU) {
|
switch (outData[5U] & 0x3FU) {
|
||||||
case 0x01U:
|
case 0x01U:
|
||||||
|
::memcpy(inData, outData, 33U);
|
||||||
|
m_headerSeen = true;
|
||||||
|
m_seen1 = false;
|
||||||
|
m_seen2 = false;
|
||||||
|
m_seen3 = false;
|
||||||
|
m_seen4 = false;
|
||||||
|
return 33U;
|
||||||
case 0x08U:
|
case 0x08U:
|
||||||
::memcpy(inData, outData, 33U);
|
::memcpy(inData, outData, 33U);
|
||||||
return true;
|
m_headerSeen = false;
|
||||||
|
m_seen1 = false;
|
||||||
|
m_seen2 = false;
|
||||||
|
m_seen3 = false;
|
||||||
|
m_seen4 = false;
|
||||||
|
return 33U;
|
||||||
default:
|
default:
|
||||||
return false;
|
return 0U;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CKenwoodNetwork::processKenwoodVoiceData(unsigned char* inData)
|
unsigned int CKenwoodNetwork::processKenwoodVoiceData(unsigned char* inData)
|
||||||
{
|
{
|
||||||
assert(inData != NULL);
|
assert(inData != NULL);
|
||||||
|
|
||||||
@ -642,4 +768,162 @@ void CKenwoodNetwork::processKenwoodVoiceData(unsigned char* inData)
|
|||||||
}
|
}
|
||||||
|
|
||||||
::memcpy(inData, outData, 33U);
|
::memcpy(inData, outData, 33U);
|
||||||
|
|
||||||
|
return 33U;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int CKenwoodNetwork::processKenwoodData(unsigned char* inData)
|
||||||
|
{
|
||||||
|
if (inData[7U] != 0x09U && inData[7U] != 0x0BU && inData[7U] != 0x08U)
|
||||||
|
return 0U;
|
||||||
|
|
||||||
|
unsigned char outData[50U];
|
||||||
|
|
||||||
|
if (inData[7U] == 0x09U || inData[7U] == 0x08U) {
|
||||||
|
outData[0U] = 0x90U;
|
||||||
|
outData[1U] = inData[8U];
|
||||||
|
outData[2U] = inData[7U];
|
||||||
|
outData[3U] = inData[10U];
|
||||||
|
outData[4U] = inData[9U];
|
||||||
|
outData[5U] = inData[12U];
|
||||||
|
outData[6U] = inData[11U];
|
||||||
|
::memcpy(inData, outData, 7U);
|
||||||
|
return 7U;
|
||||||
|
} else {
|
||||||
|
outData[0U] = 0x90U;
|
||||||
|
outData[1U] = inData[8U];
|
||||||
|
outData[2U] = inData[7U];
|
||||||
|
outData[3U] = inData[10U];
|
||||||
|
outData[4U] = inData[9U];
|
||||||
|
outData[5U] = inData[12U];
|
||||||
|
outData[6U] = inData[11U];
|
||||||
|
outData[7U] = inData[14U];
|
||||||
|
outData[8U] = inData[13U];
|
||||||
|
outData[9U] = inData[16U];
|
||||||
|
outData[10U] = inData[15U];
|
||||||
|
outData[11U] = inData[18U];
|
||||||
|
outData[12U] = inData[17U];
|
||||||
|
outData[13U] = inData[20U];
|
||||||
|
outData[14U] = inData[19U];
|
||||||
|
outData[15U] = inData[22U];
|
||||||
|
outData[16U] = inData[21U];
|
||||||
|
outData[17U] = inData[24U];
|
||||||
|
outData[18U] = inData[23U];
|
||||||
|
outData[19U] = inData[26U];
|
||||||
|
outData[20U] = inData[25U];
|
||||||
|
outData[21U] = inData[28U];
|
||||||
|
outData[22U] = inData[27U];
|
||||||
|
outData[23U] = inData[29U];
|
||||||
|
::memcpy(inData, outData, 24U);
|
||||||
|
return 24U;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long CKenwoodNetwork::getTimeStamp() const
|
||||||
|
{
|
||||||
|
unsigned long timeStamp = 0UL;
|
||||||
|
|
||||||
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
|
SYSTEMTIME st;
|
||||||
|
::GetSystemTime(&st);
|
||||||
|
|
||||||
|
unsigned int hh = st.wHour;
|
||||||
|
unsigned int mm = st.wMinute;
|
||||||
|
unsigned int ss = st.wSecond;
|
||||||
|
unsigned int ms = st.wMilliseconds;
|
||||||
|
|
||||||
|
timeStamp += hh * 3600U * 1000U * 80U;
|
||||||
|
timeStamp += mm * 60U * 1000U * 80U;
|
||||||
|
timeStamp += ss * 1000U * 80U;
|
||||||
|
timeStamp += ms * 80U;
|
||||||
|
#else
|
||||||
|
struct timeval tod;
|
||||||
|
::gettimeofday(&tod, NULL);
|
||||||
|
|
||||||
|
unsigned int ss = tod.tv_sec;
|
||||||
|
unsigned int ms = tod.tv_usec / 1000U;
|
||||||
|
|
||||||
|
timeStamp += ss * 1000U * 80U;
|
||||||
|
timeStamp += ms * 80U;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return timeStamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int CKenwoodNetwork::processKenwoodVoiceLateEntry(unsigned char* inData)
|
||||||
|
{
|
||||||
|
assert(inData != NULL);
|
||||||
|
|
||||||
|
unsigned char sacch[4U];
|
||||||
|
sacch[0U] = inData[12U];
|
||||||
|
sacch[1U] = inData[11U];
|
||||||
|
sacch[2U] = inData[14U];
|
||||||
|
sacch[3U] = inData[13U];
|
||||||
|
|
||||||
|
switch (sacch[0U] & 0xC0U) {
|
||||||
|
case 0xC0U:
|
||||||
|
if (!m_seen1) {
|
||||||
|
unsigned int offset = 0U;
|
||||||
|
for (unsigned int i = 8U; i < 26U; i++, offset++) {
|
||||||
|
bool b = READ_BIT(sacch, i) != 0U;
|
||||||
|
WRITE_BIT(m_sacch, offset, b);
|
||||||
|
}
|
||||||
|
m_seen1 = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x80U:
|
||||||
|
if (!m_seen2) {
|
||||||
|
unsigned int offset = 18U;
|
||||||
|
for (unsigned int i = 8U; i < 26U; i++, offset++) {
|
||||||
|
bool b = READ_BIT(sacch, i) != 0U;
|
||||||
|
WRITE_BIT(m_sacch, offset, b);
|
||||||
|
}
|
||||||
|
m_seen2 = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x40U:
|
||||||
|
if (!m_seen3) {
|
||||||
|
unsigned int offset = 36U;
|
||||||
|
for (unsigned int i = 8U; i < 26U; i++, offset++) {
|
||||||
|
bool b = READ_BIT(sacch, i) != 0U;
|
||||||
|
WRITE_BIT(m_sacch, offset, b);
|
||||||
|
}
|
||||||
|
m_seen3 = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x00U:
|
||||||
|
if (!m_seen4) {
|
||||||
|
unsigned int offset = 54U;
|
||||||
|
for (unsigned int i = 8U; i < 26U; i++, offset++) {
|
||||||
|
bool b = READ_BIT(sacch, i) != 0U;
|
||||||
|
WRITE_BIT(m_sacch, offset, b);
|
||||||
|
}
|
||||||
|
m_seen4 = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_seen1 || !m_seen2 || !m_seen3 || !m_seen4)
|
||||||
|
return 0U;
|
||||||
|
|
||||||
|
// Create a dummy header
|
||||||
|
// Header SACCH
|
||||||
|
inData[11U] = 0x10U;
|
||||||
|
inData[12U] = 0x01U;
|
||||||
|
inData[13U] = 0x00U;
|
||||||
|
inData[14U] = 0x00U;
|
||||||
|
|
||||||
|
// Header FACCH
|
||||||
|
inData[15U] = m_sacch[1U];
|
||||||
|
inData[16U] = m_sacch[0U];
|
||||||
|
inData[17U] = m_sacch[3U];
|
||||||
|
inData[18U] = m_sacch[2U];
|
||||||
|
inData[19U] = m_sacch[5U];
|
||||||
|
inData[20U] = m_sacch[4U];
|
||||||
|
inData[21U] = m_sacch[7U];
|
||||||
|
inData[22U] = m_sacch[6U];
|
||||||
|
inData[23U] = 0x00U;
|
||||||
|
inData[24U] = m_sacch[8U];
|
||||||
|
|
||||||
|
return processKenwoodVoiceHeader(inData);
|
||||||
}
|
}
|
||||||
|
@ -20,12 +20,12 @@
|
|||||||
#define KenwoodNetwork_H
|
#define KenwoodNetwork_H
|
||||||
|
|
||||||
#include "CoreNetwork.h"
|
#include "CoreNetwork.h"
|
||||||
#include "StopWatch.h"
|
|
||||||
#include "UDPSocket.h"
|
#include "UDPSocket.h"
|
||||||
#include "Timer.h"
|
#include "Timer.h"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
class CKenwoodNetwork : public ICoreNetwork {
|
class CKenwoodNetwork : public ICoreNetwork {
|
||||||
public:
|
public:
|
||||||
@ -45,25 +45,42 @@ public:
|
|||||||
private:
|
private:
|
||||||
CUDPSocket m_rtpSocket;
|
CUDPSocket m_rtpSocket;
|
||||||
CUDPSocket m_rtcpSocket;
|
CUDPSocket m_rtcpSocket;
|
||||||
CStopWatch m_stopWatch;
|
|
||||||
in_addr m_address;
|
in_addr m_address;
|
||||||
unsigned short m_seqNo;
|
bool m_headerSeen;
|
||||||
unsigned long m_timeStamp;
|
bool m_seen1;
|
||||||
|
bool m_seen2;
|
||||||
|
bool m_seen3;
|
||||||
|
bool m_seen4;
|
||||||
|
unsigned char* m_sacch;
|
||||||
|
uint8_t m_sessionId;
|
||||||
|
uint16_t m_seqNo;
|
||||||
unsigned int m_ssrc;
|
unsigned int m_ssrc;
|
||||||
bool m_debug;
|
bool m_debug;
|
||||||
CTimer m_timer;
|
uint32_t m_startSecs;
|
||||||
|
uint32_t m_startUSecs;
|
||||||
|
CTimer m_rtcpTimer;
|
||||||
|
CTimer m_hangTimer;
|
||||||
|
unsigned char m_hangType;
|
||||||
|
unsigned short m_hangSrc;
|
||||||
|
unsigned short m_hangDst;
|
||||||
|
std::mt19937 m_random;
|
||||||
|
|
||||||
bool processIcomVoiceHeader(const unsigned char* data);
|
bool processIcomVoiceHeader(const unsigned char* data);
|
||||||
bool processIcomVoiceData(const unsigned char* data);
|
bool processIcomVoiceData(const unsigned char* data);
|
||||||
bool processKenwoodVoiceHeader(unsigned char* data);
|
unsigned int processKenwoodVoiceHeader(unsigned char* data);
|
||||||
void processKenwoodVoiceData(unsigned char* data);
|
unsigned int processKenwoodVoiceData(unsigned char* data);
|
||||||
|
unsigned int processKenwoodVoiceLateEntry(unsigned char* data);
|
||||||
|
unsigned int processKenwoodData(unsigned char* data);
|
||||||
bool writeRTPVoiceHeader(const unsigned char* data);
|
bool writeRTPVoiceHeader(const unsigned char* data);
|
||||||
bool writeRTPVoiceData(const unsigned char* data);
|
bool writeRTPVoiceData(const unsigned char* data);
|
||||||
bool writeRTPVoiceTrailer(const unsigned char* data);
|
bool writeRTPVoiceTrailer(const unsigned char* data);
|
||||||
|
bool writeRTCPStart();
|
||||||
bool writeRTCPPing();
|
bool writeRTCPPing();
|
||||||
bool writeRTCPData(unsigned char type, unsigned short src, unsigned short dst);
|
bool writeRTCPHang(unsigned char type, unsigned short src, unsigned short dst);
|
||||||
|
bool writeRTCPHang();
|
||||||
unsigned int readRTP(unsigned char* data);
|
unsigned int readRTP(unsigned char* data);
|
||||||
unsigned int readRTCP(unsigned char* data);
|
unsigned int readRTCP(unsigned char* data);
|
||||||
|
unsigned long getTimeStamp() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -14,6 +14,9 @@ NXDNReflector: $(OBJECTS)
|
|||||||
%.o: %.cpp
|
%.o: %.cpp
|
||||||
$(CXX) $(CFLAGS) -c -o $@ $<
|
$(CXX) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
|
install:
|
||||||
|
install -m 755 NXDNReflector /usr/local/bin/
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
$(RM) NXDNReflector *.o *.d *.bak *~
|
$(RM) NXDNReflector *.o *.d *.bak *~
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2006-2016 by Jonathan Naylor G4KLX
|
* Copyright (C) 2006-2016,2020 by Jonathan Naylor G4KLX
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -47,7 +47,7 @@ public:
|
|||||||
|
|
||||||
void close();
|
void close();
|
||||||
|
|
||||||
static in_addr lookup(const std::string& hostName);
|
static in_addr lookup(const std::string& hostName);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string m_address;
|
std::string m_address;
|
||||||
|
@ -19,6 +19,6 @@
|
|||||||
#if !defined(VERSION_H)
|
#if !defined(VERSION_H)
|
||||||
#define VERSION_H
|
#define VERSION_H
|
||||||
|
|
||||||
const char* VERSION = "20200420";
|
const char* VERSION = "20200701";
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
x
Reference in New Issue
Block a user