diff --git a/NXDNGateway/APRSWriter.cpp b/NXDNGateway/APRSWriter.cpp new file mode 100644 index 0000000..52fab1c --- /dev/null +++ b/NXDNGateway/APRSWriter.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2010-2014,2016,2017,2018 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 "APRSWriter.h" + +#include +#include +#include +#include + +CAPRSWriter::CAPRSWriter(const std::string& callsign, const std::string& suffix, const std::string& password, const std::string& address, unsigned int port) : +m_thread(NULL), +m_enabled(false), +m_idTimer(1000U, 20U * 60U), // 20 minutes +m_callsign(callsign), +m_txFrequency(0U), +m_rxFrequency(0U), +m_latitude(0.0F), +m_longitude(0.0F), +m_height(0), +m_desc() +{ + assert(!callsign.empty()); + assert(!password.empty()); + assert(!address.empty()); + assert(port > 0U); + + if (!suffix.empty()) { + m_callsign.append("-"); + m_callsign.append(suffix.substr(0U, 1U)); + } + + m_thread = new CAPRSWriterThread(m_callsign, password, address, port); +} + +CAPRSWriter::~CAPRSWriter() +{ +} + +void CAPRSWriter::setInfo(unsigned int txFrequency, unsigned int rxFrequency, float latitude, float longitude, int height, const std::string& desc) +{ + m_txFrequency = txFrequency; + m_rxFrequency = rxFrequency; + m_latitude = latitude; + m_longitude = longitude; + m_height = height; + m_desc = desc; +} + +bool CAPRSWriter::open() +{ + m_idTimer.start(); + return m_thread->start(); +} + +void CAPRSWriter::write(const char* data) +{ + assert(data != NULL); + + m_thread->write(data); +} + +void CAPRSWriter::clock(unsigned int ms) +{ + m_idTimer.clock(ms); + + if (m_idTimer.hasExpired()) { + sendIdFrames(); + m_idTimer.start(); + } +} + +void CAPRSWriter::close() +{ + m_thread->stop(); +} + +void CAPRSWriter::sendIdFrames() +{ + if (!m_thread->isConnected()) + return; + + // Default values aren't passed on + if (m_latitude == 0.0F && m_longitude == 0.0F) + return; + + char desc[200U]; + if (m_txFrequency != 0U) { + float offset = float(int(m_rxFrequency) - int(m_txFrequency)) / 1000000.0F; + ::sprintf(desc, "MMDVM Voice %.5LfMHz %c%.4lfMHz%s%s", + (long double)(m_txFrequency) / 1000000.0F, + offset < 0.0F ? '-' : '+', + ::fabs(offset), m_desc.empty() ? "" : ", ", m_desc.c_str()); + } else { + ::sprintf(desc, "MMDVM Voice%s%s", m_desc.empty() ? "" : ", ", m_desc.c_str()); + } + + const char* band = "4m"; + if (m_txFrequency >= 1200000000U) + band = "1.2"; + else if (m_txFrequency >= 420000000U) + band = "440"; + else if (m_txFrequency >= 144000000U) + band = "2m"; + else if (m_txFrequency >= 50000000U) + band = "6m"; + else if (m_txFrequency >= 28000000U) + band = "10m"; + + double tempLat = ::fabs(m_latitude); + double tempLong = ::fabs(m_longitude); + + double latitude = ::floor(tempLat); + double longitude = ::floor(tempLong); + + latitude = (tempLat - latitude) * 60.0 + latitude * 100.0; + longitude = (tempLong - longitude) * 60.0 + longitude * 100.0; + + char lat[20U]; + ::sprintf(lat, "%07.2lf", latitude); + + char lon[20U]; + ::sprintf(lon, "%08.2lf", longitude); + + std::string server = m_callsign; + size_t pos = server.find_first_of('-'); + if (pos == std::string::npos) + server.append("-S"); + else + server.append("S"); + + char output[500U]; + ::sprintf(output, "%s>APDG04,TCPIP*,qAC,%s:!%s%cD%s%c&/A=%06.0f%s %s", + m_callsign.c_str(), server.c_str(), + lat, (m_latitude < 0.0F) ? 'S' : 'N', + lon, (m_longitude < 0.0F) ? 'W' : 'E', + float(m_height) * 3.28F, band, desc); + + m_thread->write(output); + + m_idTimer.start(); +} diff --git a/NXDNGateway/APRSWriter.h b/NXDNGateway/APRSWriter.h new file mode 100644 index 0000000..baa9141 --- /dev/null +++ b/NXDNGateway/APRSWriter.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2010,2011,2012,2016,2017,2018 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 APRSWriter_H +#define APRSWriter_H + +#include "APRSWriterThread.h" +#include "Timer.h" + +#include + +class CAPRSWriter { +public: + CAPRSWriter(const std::string& callsign, const std::string& suffix, const std::string& password, const std::string& address, unsigned int port); + ~CAPRSWriter(); + + bool open(); + + void setInfo(unsigned int txFrequency, unsigned int rxFrequency, float latitude, float longitude, int height, const std::string& desc); + + void write(const char* data); + + void clock(unsigned int ms); + + void close(); + +private: + CAPRSWriterThread* m_thread; + bool m_enabled; + CTimer m_idTimer; + std::string m_callsign; + unsigned int m_txFrequency; + unsigned int m_rxFrequency; + float m_latitude; + float m_longitude; + int m_height; + std::string m_desc; + + void sendIdFrames(); +}; + +#endif diff --git a/NXDNGateway/APRSWriterThread.cpp b/NXDNGateway/APRSWriterThread.cpp new file mode 100644 index 0000000..93e2e48 --- /dev/null +++ b/NXDNGateway/APRSWriterThread.cpp @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2010-2014,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 "APRSWriterThread.h" +#include "Utils.h" +#include "Log.h" + +#include +#include +#include +#include +#include +#include + +// #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_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(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_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(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(); + + try { + while (!m_exit) { + if (!m_connected) { + m_connected = connect(); + + if (!m_connected){ + LogError("Reconnect attempt to the APRS server has failed"); + sleep(10000UL); // 10 secs + } + } + + if (m_connected) { + 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, ::strlen(p)); + if (!ret) { + m_connected = false; + m_socket.close(); + LogError("Connection to the APRS thread has failed"); + } + + 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"); + } + + 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 = ::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(); +} + +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; +} diff --git a/NXDNGateway/APRSWriterThread.h b/NXDNGateway/APRSWriterThread.h new file mode 100644 index 0000000..42bfd67 --- /dev/null +++ b/NXDNGateway/APRSWriterThread.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2010,2011,2012,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 APRSWriterThread_H +#define APRSWriterThread_H + +#include "TCPSocket.h" +#include "RingBuffer.h" +#include "Thread.h" + +#include + +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); + +private: + std::string m_username; + std::string m_password; + CTCPSocket m_socket; + CRingBuffer m_queue; + bool m_exit; + bool m_connected; + ReadAPRSFrameCallback m_APRSReadCallback; + std::string m_filter; + std::string m_clientName; + + bool connect(); +}; + +#endif diff --git a/NXDNGateway/Conf.cpp b/NXDNGateway/Conf.cpp index 33bc8b1..c1b7cd0 100644 --- a/NXDNGateway/Conf.cpp +++ b/NXDNGateway/Conf.cpp @@ -29,20 +29,31 @@ const int BUFFER_SIZE = 500; enum SECTION { SECTION_NONE, SECTION_GENERAL, + SECTION_INFO, SECTION_ID_LOOKUP, SECTION_VOICE, SECTION_LOG, + SECTION_APRS_FI, SECTION_NETWORK }; CConf::CConf(const std::string& file) : m_file(file), m_callsign(), +m_suffix(), m_rptAddress(), m_rptPort(0U), m_myPort(0U), m_rptDebug(false), m_daemon(false), +m_rxFrequency(0U), +m_txFrequency(0U), +m_power(0U), +m_latitude(0.0F), +m_longitude(0.0F), +m_height(0), +m_name(), +m_description(), m_lookupName(), m_lookupTime(0U), m_voiceEnabled(true), @@ -50,6 +61,11 @@ m_voiceLanguage("en_GB"), m_voiceDirectory(), m_logFilePath(), m_logFileRoot(), +m_aprsEnabled(false), +m_aprsServer(), +m_aprsPort(0U), +m_aprsPassword(), +m_aprsDescription(), m_networkPort(0U), m_networkHosts(), m_networkReloadTime(0U), @@ -85,12 +101,16 @@ bool CConf::read() if (buffer[0U] == '[') { if (::strncmp(buffer, "[General]", 9U) == 0) section = SECTION_GENERAL; + else if (::strncmp(buffer, "[Info]", 6U) == 0) + section = SECTION_INFO; else if (::strncmp(buffer, "[Id Lookup]", 11U) == 0) section = SECTION_ID_LOOKUP; else if (::strncmp(buffer, "[Voice]", 7U) == 0) section = SECTION_VOICE; else if (::strncmp(buffer, "[Log]", 5U) == 0) section = SECTION_LOG; + else if (::strncmp(buffer, "[aprs.fi]", 9U) == 0) + section = SECTION_APRS_FI; else if (::strncmp(buffer, "[Network]", 9U) == 0) section = SECTION_NETWORK; else @@ -110,6 +130,11 @@ bool CConf::read() for (unsigned int i = 0U; value[i] != 0; i++) value[i] = ::toupper(value[i]); m_callsign = value; + } else if (::strcmp(key, "Suffix") == 0) { + // Convert the callsign to upper case + for (unsigned int i = 0U; value[i] != 0; i++) + value[i] = ::toupper(value[i]); + m_suffix = value; } else if (::strcmp(key, "RptAddress") == 0) m_rptAddress = value; else if (::strcmp(key, "RptPort") == 0) @@ -120,6 +145,23 @@ bool CConf::read() m_rptDebug = ::atoi(value) == 1; else if (::strcmp(key, "Daemon") == 0) m_daemon = ::atoi(value) == 1; + } else if (section == SECTION_INFO) { + if (::strcmp(key, "TXFrequency") == 0) + m_txFrequency = (unsigned int)::atoi(value); + else if (::strcmp(key, "RXFrequency") == 0) + m_rxFrequency = (unsigned int)::atoi(value); + else if (::strcmp(key, "Power") == 0) + m_power = (unsigned int)::atoi(value); + else if (::strcmp(key, "Latitude") == 0) + m_latitude = float(::atof(value)); + else if (::strcmp(key, "Longitude") == 0) + m_longitude = float(::atof(value)); + else if (::strcmp(key, "Height") == 0) + m_height = ::atoi(value); + else if (::strcmp(key, "Name") == 0) + m_name = value; + else if (::strcmp(key, "Description") == 0) + m_description = value; } else if (section == SECTION_ID_LOOKUP) { if (::strcmp(key, "Name") == 0) m_lookupName = value; @@ -137,6 +179,17 @@ bool CConf::read() m_logFilePath = value; else if (::strcmp(key, "FileRoot") == 0) m_logFileRoot = value; + } else if (section == SECTION_APRS_FI) { + if (::strcmp(key, "Enable") == 0) + m_aprsEnabled = ::atoi(value) == 1; + else if (::strcmp(key, "Server") == 0) + m_aprsServer = value; + else if (::strcmp(key, "Port") == 0) + m_aprsPort = (unsigned int)::atoi(value); + else if (::strcmp(key, "Password") == 0) + m_aprsPassword = value; + else if (::strcmp(key, "Description") == 0) + m_aprsDescription = value; } else if (section == SECTION_NETWORK) { if (::strcmp(key, "Port") == 0) m_networkPort = (unsigned int)::atoi(value); @@ -171,6 +224,11 @@ std::string CConf::getCallsign() const return m_callsign; } +std::string CConf::getSuffix() const +{ + return m_suffix; +} + std::string CConf::getRptAddress() const { return m_rptAddress; @@ -196,6 +254,46 @@ bool CConf::getDaemon() const return m_daemon; } +unsigned int CConf::getRxFrequency() const +{ + return m_rxFrequency; +} + +unsigned int CConf::getTxFrequency() const +{ + return m_txFrequency; +} + +unsigned int CConf::getPower() const +{ + return m_power; +} + +float CConf::getLatitude() const +{ + return m_latitude; +} + +float CConf::getLongitude() const +{ + return m_longitude; +} + +int CConf::getHeight() const +{ + return m_height; +} + +std::string CConf::getName() const +{ + return m_name; +} + +std::string CConf::getDescription() const +{ + return m_description; +} + std::string CConf::getLookupName() const { return m_lookupName; @@ -226,6 +324,31 @@ std::string CConf::getLogFilePath() const return m_logFilePath; } +bool CConf::getAPRSEnabled() const +{ + return m_aprsEnabled; +} + +std::string CConf::getAPRSServer() const +{ + return m_aprsServer; +} + +unsigned int CConf::getAPRSPort() const +{ + return m_aprsPort; +} + +std::string CConf::getAPRSPassword() const +{ + return m_aprsPassword; +} + +std::string CConf::getAPRSDescription() const +{ + return m_aprsDescription; +} + std::string CConf::getLogFileRoot() const { return m_logFileRoot; diff --git a/NXDNGateway/Conf.h b/NXDNGateway/Conf.h index 2ec54bc..4e9d44e 100644 --- a/NXDNGateway/Conf.h +++ b/NXDNGateway/Conf.h @@ -32,12 +32,23 @@ public: // The General section std::string getCallsign() const; + std::string getSuffix() const; std::string getRptAddress() const; unsigned int getRptPort() const; unsigned int getMyPort() const; bool getRptDebug() const; bool getDaemon() const; + // The Info section + unsigned int getRxFrequency() const; + unsigned int getTxFrequency() const; + unsigned int getPower() const; + float getLatitude() const; + float getLongitude() const; + int getHeight() const; + std::string getName() const; + std::string getDescription() const; + // The Id Lookup section std::string getLookupName() const; unsigned int getLookupTime() const; @@ -47,6 +58,13 @@ public: std::string getVoiceLanguage() const; std::string getVoiceDirectory() const; + // The aprs.fi section + bool getAPRSEnabled() const; + std::string getAPRSServer() const; + unsigned int getAPRSPort() const; + std::string getAPRSPassword() const; + std::string getAPRSDescription() const; + // The Log section std::string getLogFilePath() const; std::string getLogFileRoot() const; @@ -66,12 +84,22 @@ public: private: std::string m_file; std::string m_callsign; + std::string m_suffix; std::string m_rptAddress; unsigned int m_rptPort; unsigned int m_myPort; bool m_rptDebug; bool m_daemon; + unsigned int m_rxFrequency; + unsigned int m_txFrequency; + unsigned int m_power; + float m_latitude; + float m_longitude; + int m_height; + std::string m_name; + std::string m_description; + std::string m_lookupName; unsigned int m_lookupTime; @@ -82,6 +110,12 @@ private: std::string m_logFilePath; std::string m_logFileRoot; + bool m_aprsEnabled; + std::string m_aprsServer; + unsigned int m_aprsPort; + std::string m_aprsPassword; + std::string m_aprsDescription; + unsigned int m_networkPort; std::string m_networkHosts; unsigned int m_networkReloadTime; diff --git a/NXDNGateway/GPSHandler.cpp b/NXDNGateway/GPSHandler.cpp new file mode 100644 index 0000000..b488ba9 --- /dev/null +++ b/NXDNGateway/GPSHandler.cpp @@ -0,0 +1,171 @@ +/* +* Copyright (C) 2018 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 "GPSHandler.h" +#include "Utils.h" + +#include +#include +#include +#include + +const unsigned char NXDN_DATA_TYPE_GPS = 0x06U; + +const unsigned int NXDN_DATA_LENGTH = 20U; +const unsigned int NXDN_DATA_MAX_LENGTH = 15U * NXDN_DATA_LENGTH; + +CGPSHandler::CGPSHandler(const std::string& callsign, const std::string& suffix, const std::string& password, const std::string& address, unsigned int port) : +m_callsign(callsign), +m_writer(callsign, suffix, password, address, port), +m_data(NULL), +m_length(0U), +m_source() +{ + assert(!callsign.empty()); + assert(!password.empty()); + assert(!address.empty()); + assert(port > 0U); + + m_data = new char[NXDN_DATA_MAX_LENGTH]; + + reset(); +} + +CGPSHandler::~CGPSHandler() +{ + delete[] m_data; +} + +bool CGPSHandler::open() +{ + return m_writer.open(); +} + +void CGPSHandler::setInfo(unsigned int txFrequency, unsigned int rxFrequency, float latitude, float longitude, int height, const std::string& desc) +{ + m_writer.setInfo(txFrequency, rxFrequency, latitude, longitude, height, desc); +} + +void CGPSHandler::processHeader(const std::string& source) +{ + m_source = source; +} + +void CGPSHandler::processData(const unsigned char* data) +{ + assert(data != NULL); + + ::memcpy(m_data + m_length, data + 1U, NXDN_DATA_LENGTH); + m_length += NXDN_DATA_LENGTH; + + if (data[0U] == 0x00U) { + processNMEA(); + reset(); + } +} + +void CGPSHandler::processEnd() +{ + reset(); +} + +void CGPSHandler::clock(unsigned int ms) +{ + m_writer.clock(ms); +} + +void CGPSHandler::close() +{ + m_writer.close(); +} + +void CGPSHandler::reset() +{ + ::memset(m_data, 0x00U, NXDN_DATA_MAX_LENGTH); + m_length = 0U; + m_source.clear(); +} + +void CGPSHandler::processNMEA() +{ + if (m_data[0U] != NXDN_DATA_TYPE_GPS) + return; + + if (::memcmp(m_data + 1U, "$G", 2U) != 0) + return; + + if (::strchr(m_data + 1U, '*') == NULL) + return; + + if (!checkXOR()) + return; + + if (::memcmp(m_data + 4U, "RMC", 3U) != 0) { + CUtils::dump("Unhandled NMEA sentence", (unsigned char*)(m_data + 1U), m_length - 1U); + return; + } + + // Parse the $GxRMC string into tokens + char* pRMC[20U]; + ::memset(pRMC, 0x00U, 20U * sizeof(char*)); + unsigned int nRMC = 0U; + + char* p = NULL; + char* d = m_data + 1U; + while ((p = ::strtok(d, ",\r\n")) != NULL) { + pRMC[nRMC++] = p; + d = NULL; + } + + // Is there any position data? + if (pRMC[3U] == NULL || pRMC[4U] == NULL || pRMC[5U] == NULL || pRMC[6U] == NULL || ::strlen(pRMC[3U]) == 0U || ::strlen(pRMC[4U]) == 0U || ::strlen(pRMC[5U]) == 0 || ::strlen(pRMC[6U]) == 0) + return; + + // Is it a valid GPS fix? + if (::strcmp(pRMC[2U], "A") != 0) + return; + + char output[300U]; + if (pRMC[7U] != NULL && pRMC[8U] != NULL && ::strlen(pRMC[7U]) > 0U && ::strlen(pRMC[8U]) > 0U) { + int bearing = ::atoi(pRMC[8U]); + int speed = ::atoi(pRMC[7U]); + + ::sprintf(output, "%s-Y>APDPRS,NXDN*,qAR,%s:!%s%s/%s%sr%03d/%03d via MMDVM", + m_source.c_str(), m_callsign.c_str(), pRMC[3U], pRMC[4U], pRMC[5U], pRMC[6U], bearing, speed); + } else { + ::sprintf(output, "%s-Y>APDPRS,NXDN*,qAR,%s:!%s%s/%s%sr via MMDVM", + m_source.c_str(), m_callsign.c_str(), pRMC[3U], pRMC[4U], pRMC[5U], pRMC[6U]); + } + + m_writer.write(output); +} + +bool CGPSHandler::checkXOR() const +{ + char* p1 = ::strchr(m_data, '$'); + char* p2 = ::strchr(m_data, '*'); + + unsigned char res = 0U; + for (char* q = p1; q < p2; q++) + res ^= *q; + + char buffer[10U]; + ::sprintf(buffer, "%02X", res); + + return ::memcmp(buffer, p2 + 1U, 2U) == 0; +} diff --git a/NXDNGateway/GPSHandler.h b/NXDNGateway/GPSHandler.h new file mode 100644 index 0000000..13d43d0 --- /dev/null +++ b/NXDNGateway/GPSHandler.h @@ -0,0 +1,57 @@ +/* +* Copyright (C) 2018 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 GPSHandler_H +#define GPSHandler_H + +#include "APRSWriter.h" + +#include + +class CGPSHandler { +public: + CGPSHandler(const std::string& callsign, const std::string& suffix, const std::string& password, const std::string& address, unsigned int port); + ~CGPSHandler(); + + bool open(); + + void setInfo(unsigned int txFrequency, unsigned int rxFrequency, float latitude, float longitude, int height, const std::string& desc); + + void processHeader(const std::string& source); + + void processData(const unsigned char* data); + + void processEnd(); + + void clock(unsigned int ms); + + void close(); + +private: + std::string m_callsign; + CAPRSWriter m_writer; + char* m_data; + unsigned int m_length; + std::string m_source; + + void processNMEA(); + bool checkXOR() const; + void reset(); +}; + +#endif diff --git a/NXDNGateway/Makefile b/NXDNGateway/Makefile index 2f5aeea..881ee6d 100644 --- a/NXDNGateway/Makefile +++ b/NXDNGateway/Makefile @@ -4,7 +4,8 @@ CFLAGS = -g -O3 -Wall -std=c++0x -pthread LIBS = -lpthread LDFLAGS = -g -OBJECTS = Conf.o IcomNetwork.o Log.o Mutex.o NXDNCRC.o NXDNGateway.o NXDNLookup.o NXDNNetwork.o Reflectors.o StopWatch.o Thread.o Timer.o UDPSocket.o Utils.o Voice.o +OBJECTS = APRSWriter.o APRSWriterThread.o Conf.o GPSHandler.o IcomNetwork.o Log.o Mutex.o NXDNCRC.o NXDNGateway.o NXDNLookup.o NXDNNetwork.o Reflectors.o \ + StopWatch.o TCPSocket.o Thread.o Timer.o UDPSocket.o Utils.o Voice.o all: NXDNGateway diff --git a/NXDNGateway/NXDNGateway.cpp b/NXDNGateway/NXDNGateway.cpp index f21ed15..f3f1173 100644 --- a/NXDNGateway/NXDNGateway.cpp +++ b/NXDNGateway/NXDNGateway.cpp @@ -21,6 +21,7 @@ #include "NXDNGateway.h" #include "NXDNLookup.h" #include "Reflectors.h" +#include "GPSHandler.h" #include "StopWatch.h" #include "Version.h" #include "Thread.h" @@ -54,6 +55,10 @@ const char* DEFAULT_INI_FILE = "/etc/NXDNGateway.ini"; #include #include +const unsigned char NXDN_TYPE_DCALL_HDR = 0x09U; +const unsigned char NXDN_TYPE_DCALL = 0x0BU; +const unsigned char NXDN_TYPE_TX_REL = 0x08U; + const unsigned short NXDN_VOICE_ID = 9999U; int main(int argc, char** argv) @@ -166,6 +171,8 @@ void CNXDNGateway::run() in_addr rptAddr = CUDPSocket::lookup(m_conf.getRptAddress()); unsigned int rptPort = m_conf.getRptPort(); + createGPS(); + CIcomNetwork localNetwork(m_conf.getMyPort(), m_conf.getRptDebug()); ret = localNetwork.open(); if (!ret) { @@ -236,7 +243,7 @@ void CNXDNGateway::run() LogMessage("Linked at startup to reflector %u", currentId); } else { - startupId = 9999U; +startupId = 9999U; } } @@ -274,10 +281,10 @@ void CNXDNGateway::run() if ((buffer[0U] == 0x81U || buffer[0U] == 0x83U) && (buffer[5U] == 0x01U || buffer[5U] == 0x08U)) { grp = (buffer[7U] & 0x20U) == 0x20U; - srcId = (buffer[8U] << 8) & 0xFF00U; + srcId = (buffer[8U] << 8) & 0xFF00U; srcId |= (buffer[9U] << 0) & 0x00FFU; - dstId = (buffer[10U] << 8) & 0xFF00U; + dstId = (buffer[10U] << 8) & 0xFF00U; dstId |= (buffer[11U] << 0) & 0x00FFU; if (dstId != currentId) { @@ -308,7 +315,7 @@ void CNXDNGateway::run() // Link to the new reflector if (reflector != NULL) { - currentId = dstId; + currentId = dstId; currentAddr = reflector->m_address; currentPort = reflector->m_port; @@ -335,6 +342,29 @@ void CNXDNGateway::run() } } + if (m_gps != NULL) { + if ((buffer[0U] & 0xF0U) == 0x90U) { + switch (buffer[2U] & 0x3FU) { + case NXDN_TYPE_DCALL_HDR: { + unsigned short srcId = 0U; + srcId |= (buffer[5U] << 8) & 0xFF00U; + srcId |= (buffer[6U] << 0) & 0x00FFU; + std::string callsign = lookup->find(srcId); + m_gps->processHeader(callsign); + } + break; + case NXDN_TYPE_DCALL: + m_gps->processData(buffer + 3U); + break; + case NXDN_TYPE_TX_REL: + m_gps->processEnd(); + break; + default: + break; + } + } + } + // If we're linked and we have a network, send it on if (currentId != 9999U) { remoteNetwork.writeData(buffer, len, srcId, dstId, grp, currentAddr, currentPort); @@ -424,6 +454,9 @@ void CNXDNGateway::run() lostTimer.stop(); } + if (m_gps != NULL) + m_gps->clock(ms); + if (ms < 5U) CThread::sleep(5U); } @@ -436,5 +469,39 @@ void CNXDNGateway::run() lookup->stop(); + if (m_gps != NULL) { + m_gps->close(); + delete m_gps; + } + ::LogFinalise(); } + +void CNXDNGateway::createGPS() +{ + if (!m_conf.getAPRSEnabled()) + return; + + std::string callsign = m_conf.getCallsign(); + std::string suffix = m_conf.getSuffix(); + std::string hostname = m_conf.getAPRSServer(); + unsigned int port = m_conf.getAPRSPort(); + std::string password = m_conf.getAPRSPassword(); + std::string desc = m_conf.getAPRSDescription(); + + m_gps = new CGPSHandler(callsign, suffix, password, hostname, port); + + unsigned int txFrequency = m_conf.getTxFrequency(); + unsigned int rxFrequency = m_conf.getRxFrequency(); + float latitude = m_conf.getLatitude(); + float longitude = m_conf.getLongitude(); + int height = m_conf.getHeight(); + + m_gps->setInfo(txFrequency, rxFrequency, latitude, longitude, height, desc); + + bool ret = m_gps->open(); + if (!ret) { + delete m_gps; + m_gps = NULL; + } +} diff --git a/NXDNGateway/NXDNGateway.h b/NXDNGateway/NXDNGateway.h index 2dbc607..e01b74a 100644 --- a/NXDNGateway/NXDNGateway.h +++ b/NXDNGateway/NXDNGateway.h @@ -19,6 +19,7 @@ #if !defined(NXDNGateway_H) #define NXDNGateway_H +#include "GPSHandler.h" #include "Timer.h" #include "Conf.h" @@ -47,7 +48,10 @@ public: void run(); private: - CConf m_conf; + CConf m_conf; + CGPSHandler* m_gps; + + void createGPS(); }; #endif diff --git a/NXDNGateway/NXDNGateway.ini b/NXDNGateway/NXDNGateway.ini index 1d2c544..7eb5c90 100644 --- a/NXDNGateway/NXDNGateway.ini +++ b/NXDNGateway/NXDNGateway.ini @@ -1,16 +1,35 @@ [General] Callsign=G4KLX +Suffix=N RptAddress=127.0.0.1 RptPort=14021 LocalPort=14020 Debug=0 Daemon=1 +[Info] +RXFrequency=430475000 +TXFrequency=439475000 +Power=1 +Latitude=0.0 +Longitude=0.0 +Height=0 +Name=Nowhere +Description=Multi-Mode Repeater + [Voice] Enabled=1 Language=en_GB Directory=./Audio +[aprs.fi] +Enable=0 +# Server=noam.aprs2.net +Server=euro.aprs2.net +Port=14580 +Password=9999 +Description=APRS Description + [Id Lookup] Name=NXDN.csv Time=24 diff --git a/NXDNGateway/NXDNGateway.vcxproj b/NXDNGateway/NXDNGateway.vcxproj index 335fe6d..38995e9 100644 --- a/NXDNGateway/NXDNGateway.vcxproj +++ b/NXDNGateway/NXDNGateway.vcxproj @@ -146,7 +146,10 @@ + + + @@ -155,7 +158,9 @@ + + @@ -164,7 +169,10 @@ + + + @@ -174,6 +182,7 @@ + diff --git a/NXDNGateway/NXDNGateway.vcxproj.filters b/NXDNGateway/NXDNGateway.vcxproj.filters index 989c392..5013a7b 100644 --- a/NXDNGateway/NXDNGateway.vcxproj.filters +++ b/NXDNGateway/NXDNGateway.vcxproj.filters @@ -59,6 +59,21 @@ Header Files + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + @@ -106,5 +121,17 @@ Source Files + + Source Files + + + Source Files + + + Source Files + + + Source Files + \ No newline at end of file diff --git a/NXDNGateway/NXDNNetwork.cpp b/NXDNGateway/NXDNNetwork.cpp index 75a1681..96720ad 100644 --- a/NXDNGateway/NXDNNetwork.cpp +++ b/NXDNGateway/NXDNNetwork.cpp @@ -71,7 +71,7 @@ bool CNXDNNetwork::writeData(const unsigned char* data, unsigned int length, uns buffer[9U] |= data[5U] == 0x01U ? 0x04U : 0x00U; buffer[9U] |= data[5U] == 0x08U ? 0x08U : 0x00U; } else if ((data[0U] & 0xF0U) == 0x90U) { - // This if data. + // This is data. buffer[9U] |= 0x02U; if (data[0U] == 0x90U || data[0U] == 0x92U || data[0U] == 0x9CU || data[0U] == 0x9EU) { // This is data header or trailer. diff --git a/NXDNGateway/RingBuffer.h b/NXDNGateway/RingBuffer.h new file mode 100644 index 0000000..7805005 --- /dev/null +++ b/NXDNGateway/RingBuffer.h @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2006-2009,2012,2013,2015,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 RingBuffer_H +#define RingBuffer_H + +#include "Log.h" + +#include +#include +#include + +template class CRingBuffer { +public: + CRingBuffer(unsigned int length, const char* name) : + m_length(length), + m_name(name), + m_buffer(NULL), + m_iPtr(0U), + m_oPtr(0U) + { + assert(length > 0U); + assert(name != NULL); + + m_buffer = new T[length]; + + ::memset(m_buffer, 0x00, m_length * sizeof(T)); + } + + ~CRingBuffer() + { + delete[] m_buffer; + } + + bool addData(const T* buffer, unsigned int nSamples) + { + if (nSamples >= freeSpace()) { + LogError("**** Overflow in %s ring buffer, %u >= %u", m_name, nSamples, freeSpace()); + return false; + } + + for (unsigned int i = 0U; i < nSamples; i++) { + m_buffer[m_iPtr++] = buffer[i]; + + if (m_iPtr == m_length) + m_iPtr = 0U; + } + + return true; + } + + bool getData(T* buffer, unsigned int nSamples) + { + if (dataSize() < nSamples) { + LogError("**** Underflow in %s ring buffer, %u < %u", m_name, dataSize(), nSamples); + return false; + } + + for (unsigned int i = 0U; i < nSamples; i++) { + buffer[i] = m_buffer[m_oPtr++]; + + if (m_oPtr == m_length) + m_oPtr = 0U; + } + + return true; + } + + bool peek(T* buffer, unsigned int nSamples) + { + if (dataSize() < nSamples) { + LogError("**** Underflow peek in %s ring buffer, %u < %u", m_name, dataSize(), nSamples); + return false; + } + + unsigned int ptr = m_oPtr; + for (unsigned int i = 0U; i < nSamples; i++) { + buffer[i] = m_buffer[ptr++]; + + if (ptr == m_length) + ptr = 0U; + } + + return true; + } + + void clear() + { + m_iPtr = 0U; + m_oPtr = 0U; + + ::memset(m_buffer, 0x00, m_length * sizeof(T)); + } + + unsigned int freeSpace() const + { + if (m_oPtr == m_iPtr) + return m_length; + + if (m_oPtr > m_iPtr) + return m_oPtr - m_iPtr; + + return (m_length + m_oPtr) - m_iPtr; + } + + unsigned int dataSize() const + { + return m_length - freeSpace(); + } + + bool hasSpace(unsigned int length) const + { + return freeSpace() > length; + } + + bool hasData() const + { + return m_oPtr != m_iPtr; + } + + bool isEmpty() const + { + return m_oPtr == m_iPtr; + } + +private: + unsigned int m_length; + const char* m_name; + T* m_buffer; + unsigned int m_iPtr; + unsigned int m_oPtr; +}; + +#endif diff --git a/NXDNGateway/TCPSocket.cpp b/NXDNGateway/TCPSocket.cpp new file mode 100644 index 0000000..9bfc3c0 --- /dev/null +++ b/NXDNGateway/TCPSocket.cpp @@ -0,0 +1,232 @@ +/* + * 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 +#include +#include + +#if defined(_WIN32) || defined(_WIN64) +typedef int ssize_t; +#else +#include +#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; + } +} diff --git a/NXDNGateway/TCPSocket.h b/NXDNGateway/TCPSocket.h new file mode 100644 index 0000000..f3321ed --- /dev/null +++ b/NXDNGateway/TCPSocket.h @@ -0,0 +1,58 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#else +#include +#endif + +#include + +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