From d9c0bb27c905e4b932d763ff6412a8fb36f0b1bd Mon Sep 17 00:00:00 2001 From: Jonathan Naylor Date: Wed, 1 Jun 2016 15:49:21 +0100 Subject: [PATCH] Add the APRS writer thread and support class. --- YSFGateway/APRSWriterThread.cpp | 267 ++++++++++++++++++++++++++ YSFGateway/APRSWriterThread.h | 63 ++++++ YSFGateway/Thread.cpp | 99 ++++++++++ YSFGateway/Thread.h | 56 ++++++ YSFGateway/YSFGateway.cpp | 10 +- YSFGateway/YSFGateway.vcxproj | 4 + YSFGateway/YSFGateway.vcxproj.filters | 12 ++ 7 files changed, 504 insertions(+), 7 deletions(-) create mode 100644 YSFGateway/APRSWriterThread.cpp create mode 100644 YSFGateway/APRSWriterThread.h create mode 100644 YSFGateway/Thread.cpp create mode 100644 YSFGateway/Thread.h diff --git a/YSFGateway/APRSWriterThread.cpp b/YSFGateway/APRSWriterThread.cpp new file mode 100644 index 0000000..c38b1e2 --- /dev/null +++ b/YSFGateway/APRSWriterThread.cpp @@ -0,0 +1,267 @@ +/* + * 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& address, const std::string& hostname, unsigned int port) : +CThread(), +m_username(callsign), +m_ssid(callsign), +m_socket(hostname, port, address), +m_queue(20U, "APRS Queue"), +m_exit(false), +m_connected(false), +m_APRSReadCallback(NULL), +m_filter(), +m_clientName("YSFGateway") +{ + assert(!callsign.empty()); + assert(!hostname.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); + + m_ssid = m_ssid.substr(CALLSIGN_LENGTH - 1U, 1); +} + +CAPRSWriterThread::CAPRSWriterThread(const std::string& callsign, const std::string& address, const std::string& hostname, unsigned int port, const std::string& filter, const std::string& clientName) : +CThread(), +m_username(callsign), +m_ssid(callsign), +m_socket(hostname, port, address), +m_queue(20U, "APRS Queue"), +m_exit(false), +m_connected(false), +m_APRSReadCallback(NULL), +m_filter(filter), +m_clientName(clientName) +{ + assert(!callsign.empty()); + assert(!hostname.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); + + m_ssid = m_ssid.substr(CALLSIGN_LENGTH - 1U, 1); +} + +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; + 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; + 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() +{ + unsigned int password = getAPRSPassword(m_username); + + 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-%s pass %u vers %s%s\n", m_username.c_str(), m_ssid.c_str(), password, (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; +} + +unsigned int CAPRSWriterThread::getAPRSPassword(std::string callsign) const +{ + unsigned int len = callsign.length(); + + uint16_t hash = 0x73E2U; + + for (unsigned int i = 0U; i < len; i += 2U) { + hash ^= (char)callsign.at(i) << 8; + if (i + 1 < len) + hash ^= (char)callsign.at(i + 1); + } + + return hash & 0x7FFFU; +} diff --git a/YSFGateway/APRSWriterThread.h b/YSFGateway/APRSWriterThread.h new file mode 100644 index 0000000..03cab67 --- /dev/null +++ b/YSFGateway/APRSWriterThread.h @@ -0,0 +1,63 @@ +/* + * 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& address, const std::string& hostname, unsigned int port); + CAPRSWriterThread(const std::string& callsign, const std::string& address, const std::string& hostname, 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_ssid; + 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(); + unsigned int getAPRSPassword(std::string username) const; +}; + +#endif diff --git a/YSFGateway/Thread.cpp b/YSFGateway/Thread.cpp new file mode 100644 index 0000000..9f43374 --- /dev/null +++ b/YSFGateway/Thread.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (C) 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. + */ + +#include "Thread.h" + +#if defined(_WIN32) || defined(_WIN64) + +CThread::CThread() : +m_handle() +{ +} + +CThread::~CThread() +{ +} + +bool CThread::run() +{ + m_handle = ::CreateThread(NULL, 0, &helper, this, 0, NULL); + + return m_handle != NULL; +} + + +void CThread::wait() +{ + ::WaitForSingleObject(m_handle, INFINITE); + + ::CloseHandle(m_handle); +} + + +DWORD CThread::helper(LPVOID arg) +{ + CThread* p = (CThread*)arg; + + p->entry(); + + return 0UL; +} + +void CThread::sleep(unsigned int ms) +{ + ::Sleep(ms); +} + +#else + +CThread::CThread() : +m_thread() +{ +} + +CThread::~CThread() +{ +} + +bool CThread::run() +{ + return ::pthread_create(&m_thread, NULL, helper, this) == 0; +} + + +void CThread::wait() +{ + ::pthread_join(m_thread, NULL); +} + + +void* CThread::helper(void* arg) +{ + CThread* p = (CThread*)arg; + + p->entry(); + + return NULL; +} + +void CThread::sleep(unsigned int ms) +{ + ::usleep(ms * 1000); +} + +#endif diff --git a/YSFGateway/Thread.h b/YSFGateway/Thread.h new file mode 100644 index 0000000..8a6843f --- /dev/null +++ b/YSFGateway/Thread.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 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. + */ + +#if !defined(THREAD_H) +#define THREAD_H + +#if defined(_WIN32) || defined(_WIN64) +#include +#else +#include +#endif + +class CThread +{ +public: + CThread(); + virtual ~CThread(); + + virtual bool run(); + + virtual void entry() = 0; + + virtual void wait(); + + static void sleep(unsigned int ms); + +private: +#if defined(_WIN32) || defined(_WIN64) + HANDLE m_handle; +#else + pthread_t m_thread; +#endif + +#if defined(_WIN32) || defined(_WIN64) + static DWORD __stdcall helper(LPVOID arg); +#else + static void* helper(void* arg); +#endif +}; + +#endif diff --git a/YSFGateway/YSFGateway.cpp b/YSFGateway/YSFGateway.cpp index 16386e1..e5bdf35 100644 --- a/YSFGateway/YSFGateway.cpp +++ b/YSFGateway/YSFGateway.cpp @@ -20,6 +20,7 @@ #include "StopWatch.h" #include "Version.h" #include "YSFFICH.h" +#include "Thread.h" #include "Timer.h" #include "Log.h" @@ -261,13 +262,8 @@ int CYSFGateway::run() watchdogTimer.stop(); } - if (ms < 5U) { -#if defined(_WIN32) || defined(_WIN64) - ::Sleep(5UL); // 5ms -#else - ::usleep(5000); // 5ms -#endif - } + if (ms < 5U) + CThread::sleep(5U); } rptNetwork.close(); diff --git a/YSFGateway/YSFGateway.vcxproj b/YSFGateway/YSFGateway.vcxproj index 3db69df..095e9f8 100644 --- a/YSFGateway/YSFGateway.vcxproj +++ b/YSFGateway/YSFGateway.vcxproj @@ -146,6 +146,7 @@ + @@ -156,6 +157,7 @@ + @@ -168,6 +170,7 @@ + @@ -177,6 +180,7 @@ + diff --git a/YSFGateway/YSFGateway.vcxproj.filters b/YSFGateway/YSFGateway.vcxproj.filters index 0d3e5b9..2597eb5 100644 --- a/YSFGateway/YSFGateway.vcxproj.filters +++ b/YSFGateway/YSFGateway.vcxproj.filters @@ -71,6 +71,12 @@ Header Files + + Header Files + + + Header Files + @@ -124,5 +130,11 @@ Source Files + + Source Files + + + Source Files + \ No newline at end of file