diff --git a/.vscode/settings.json b/.vscode/settings.json index 42e5956..df80dfe 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -73,7 +73,25 @@ "typeindex": "cpp", "variant": "cpp", "iostream": "cpp", - "fstream": "cpp" + "fstream": "cpp", + "csignal": "cpp", + "cstdarg": "cpp", + "strstream": "cpp", + "codecvt": "cpp", + "numeric": "cpp", + "regex": "cpp", + "cfenv": "cpp", + "cinttypes": "cpp" + }, + "files.exclude": { + "**/.DS_Store": true, + "**/.git": true, + "**/.hg": true, + "**/.svn": true, + "**/*.d": true, + "**/*.o": true, + "**/CVS": true, + "**/Thumbs.db": true }, "editor.tokenColorCustomizations": { "textMateRules": [ diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 6d6532b..9f00615 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -13,10 +13,7 @@ "USE_GPSD=1", "all" ], - "group": { - "kind": "build", - "isDefault": true - }, + "group": "build", "problemMatcher": [] }, { @@ -94,7 +91,10 @@ "ENABLE_DEBUG=1", "USE_GPSD=1" ], - "group": "build", + "group": { + "kind": "build", + "isDefault": true + }, "problemMatcher": [] } ] diff --git a/APRS/APRSParser.cpp b/APRS/APRSParser.cpp index 0eeaabd..b5e60a2 100644 --- a/APRS/APRSParser.cpp +++ b/APRS/APRSParser.cpp @@ -138,6 +138,11 @@ bool CAPRSParser::parseInt(CAPRSFrame& frame) type = APFT_TELEMETRY; } break; + case ';' : + if( body.length() >= 10 && (body[9] == '*' || body[9] == '_')) { + type = APFT_OBJECT; + } + break; default: type = APFT_UNKNOWN; break; diff --git a/BaseCommon/Utils.cpp b/BaseCommon/Utils.cpp index ebb1c57..c5d6746 100644 --- a/BaseCommon/Utils.cpp +++ b/BaseCommon/Utils.cpp @@ -70,7 +70,7 @@ void CUtils::dump(const char* title, const bool* data, unsigned int length) output += "*'"; - CLog::logInfo("%04X: %s\n", offset / 8U, output.c_str()); + CLog::logDebug("%04X: %s\n", offset / 8U, output.c_str()); offset += 128U; } @@ -115,7 +115,7 @@ void CUtils::dumpRev(const char* title, const bool* data, unsigned int length) output += "*"; - CLog::logInfo("%04X: %s\n", offset / 8U, output.c_str()); + CLog::logDebug("%04X: %s\n", offset / 8U, output.c_str()); offset += 128U; } @@ -157,7 +157,7 @@ void CUtils::dump(const char* title, const unsigned char* data, unsigned int len output += "*"; - CLog::logInfo("%04X: %s\n", offset, output.c_str()); + CLog::logDebug("%04X: %s\n", offset, output.c_str()); offset += 16U; diff --git a/Common/APRSCollector.cpp b/Common/APRSCollector.cpp index afba4d0..83bd8af 100644 --- a/Common/APRSCollector.cpp +++ b/Common/APRSCollector.cpp @@ -51,10 +51,14 @@ CAPRSCollector::~CAPRSCollector() m_collectors.clear(); } -void CAPRSCollector::writeHeader(const std::string& callsign) +void CAPRSCollector::writeHeader(const CHeaderData& header) { + std::string mycall1 = header.getMyCall1(); + std::string mycall2 = header.getMyCall2(); + for(auto collector : m_collectors) { - collector->setMyCall(callsign); + collector->setMyCall1(mycall1); + collector->setMyCall2(mycall2); } } @@ -95,12 +99,12 @@ unsigned int CAPRSCollector::getData(unsigned char dataType, unsigned char* data return 0U; } -void CAPRSCollector::getData(std::function dataHandler) +void CAPRSCollector::getData(std::function dataHandler) { for(auto collector : m_collectors) { std::string data; if(collector->getData(data)) { - dataHandler(data); + dataHandler(data, collector->getMyCall1().append("/").append(collector->getMyCall2())); collector->reset(); } } diff --git a/Common/APRSCollector.h b/Common/APRSCollector.h index 2f955bf..8e9ae3a 100644 --- a/Common/APRSCollector.h +++ b/Common/APRSCollector.h @@ -24,6 +24,7 @@ #include #include "SlowDataCollector.h" +#include "HeaderData.h" #include "Defs.h" enum APRS_STATE { @@ -39,7 +40,7 @@ public: CAPRSCollector(); ~CAPRSCollector(); - void writeHeader(const std::string& callsign); + void writeHeader(const CHeaderData& callsign); bool writeData(const unsigned char* data); @@ -49,7 +50,7 @@ public: unsigned int getData(unsigned char dataType, unsigned char* data, unsigned int length); - void getData(std::function dataHandler); + void getData(std::function dataHandler); void clock(unsigned int ms); diff --git a/Common/APRSFixedIdFrameProvider.cpp b/Common/APRSFixedIdFrameProvider.cpp index 9434175..b09ae9e 100644 --- a/Common/APRSFixedIdFrameProvider.cpp +++ b/Common/APRSFixedIdFrameProvider.cpp @@ -23,13 +23,13 @@ #include "APRSFixedIdFrameProvider.h" #include "StringUtils.h" -CAPRSFixedIdFrameProvider::CAPRSFixedIdFrameProvider() : -CAPRSIdFrameProvider(20U) // Initial timeout of 20 seconds +CAPRSFixedIdFrameProvider::CAPRSFixedIdFrameProvider(const std::string& gateway) : +CAPRSIdFrameProvider(gateway, 20U) // Initial timeout of 20 seconds { } -bool CAPRSFixedIdFrameProvider::buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * entry, std::vector& frames) +bool CAPRSFixedIdFrameProvider::buildAPRSFramesInt(const CAPRSEntry * entry, std::vector& frames) { if (entry == nullptr) return false; @@ -114,9 +114,9 @@ bool CAPRSFixedIdFrameProvider::buildAPRSFramesInt(const std::string& gateway, c lon.c_str(), (entry->getLongitude() < 0.0F) ? 'W' : 'E', entry->getRange() * 0.6214, entry->getAGL() * 3.28, band.c_str(), desc.c_str()); - CAPRSFrame * frame = new CAPRSFrame(gateway + "-S", + CAPRSFrame * frame = new CAPRSFrame(m_gateway + "-S", "APD5T1", - { "TCPIP*", "qAC" , gateway + "-GS" }, + { "TCPIP*", "qAC" , m_gateway + "-GS" }, body, APFT_OBJECT); frames.push_back(frame); diff --git a/Common/APRSFixedIdFrameProvider.h b/Common/APRSFixedIdFrameProvider.h index 8fa7032..c073a37 100644 --- a/Common/APRSFixedIdFrameProvider.h +++ b/Common/APRSFixedIdFrameProvider.h @@ -23,8 +23,8 @@ class CAPRSFixedIdFrameProvider : public CAPRSIdFrameProvider { public: - CAPRSFixedIdFrameProvider(); + CAPRSFixedIdFrameProvider(const std::string& gateway); protected: - virtual bool buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * aprsEntry, std::vector& frames); + virtual bool buildAPRSFramesInt(const CAPRSEntry * aprsEntry, std::vector& frames); }; diff --git a/Common/APRSGPSDIdFrameProvider.cpp b/Common/APRSGPSDIdFrameProvider.cpp index f67f634..7f621be 100644 --- a/Common/APRSGPSDIdFrameProvider.cpp +++ b/Common/APRSGPSDIdFrameProvider.cpp @@ -24,8 +24,8 @@ #include "StringUtils.h" #include "Log.h" -CAPRSGPSDIdFrameProvider::CAPRSGPSDIdFrameProvider(std::string address, std::string port) : -CAPRSIdFrameProvider(20U), +CAPRSGPSDIdFrameProvider::CAPRSGPSDIdFrameProvider(const std::string& gateway, const std::string& address, const std::string& port) : +CAPRSIdFrameProvider(gateway, 20U), m_gpsdAddress(address), m_gpsdPort(port), m_gpsdData(), @@ -56,7 +56,7 @@ void CAPRSGPSDIdFrameProvider::close() } } -bool CAPRSGPSDIdFrameProvider::buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * entry, std::vector& frames) +bool CAPRSGPSDIdFrameProvider::buildAPRSFramesInt(const CAPRSEntry * entry, std::vector& frames) { if(!m_hasConnection) { this->start(); @@ -182,9 +182,9 @@ bool CAPRSGPSDIdFrameProvider::buildAPRSFramesInt(const std::string& gateway, co body.append(CStringUtils::string_format("RNG%04.0lf %s %s\r\n", entry->getRange() * 0.6214, band.c_str(), desc.c_str())); - CAPRSFrame * frame = new CAPRSFrame(gateway + "-S", + CAPRSFrame * frame = new CAPRSFrame(m_gateway + "-S", "APD5T1", - { "TCPIP*", "qAC" , gateway + "-GS" }, + { "TCPIP*", "qAC" , m_gateway + "-GS" }, body, APFT_OBJECT); @@ -201,9 +201,9 @@ bool CAPRSGPSDIdFrameProvider::buildAPRSFramesInt(const std::string& gateway, co lat.c_str(), (rawLatitude < 0.0) ? 'S' : 'N', lon.c_str(), (rawLongitude < 0.0) ? 'W' : 'E'); - frame = new CAPRSFrame(gateway, + frame = new CAPRSFrame(m_gateway, "APD5T2", - { "TCPIP*", "qAC" , gateway + "-GS" }, + { "TCPIP*", "qAC" , m_gateway + "-GS" }, body, APFT_POSITION); frames.push_back(frame); diff --git a/Common/APRSGPSDIdFrameProvider.h b/Common/APRSGPSDIdFrameProvider.h index e982f9b..eca659e 100644 --- a/Common/APRSGPSDIdFrameProvider.h +++ b/Common/APRSGPSDIdFrameProvider.h @@ -29,13 +29,13 @@ class CAPRSGPSDIdFrameProvider : public CAPRSIdFrameProvider { public: - CAPRSGPSDIdFrameProvider(std::string address, std::string port); + CAPRSGPSDIdFrameProvider(const std::string& gateway, const std::string& address, const std::string& port); virtual void start(); virtual void close(); protected: - virtual bool buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * aprsEntry, std::vector& frames); + virtual bool buildAPRSFramesInt(const CAPRSEntry * aprsEntry, std::vector& frames); private: std::string m_gpsdAddress; diff --git a/Common/APRSHandler.cpp b/Common/APRSHandler.cpp index c81a745..e39df77 100644 --- a/Common/APRSHandler.cpp +++ b/Common/APRSHandler.cpp @@ -18,7 +18,6 @@ */ #include -#include #include #include #include @@ -34,24 +33,12 @@ #include "APRSFormater.h" #include "APRSUtils.h" -CAPRSHandler::CAPRSHandler(const std::string& hostname, unsigned int port, const std::string& gateway, const std::string& password, const std::string& address) : -m_thread(NULL), -m_gateway(), -m_address(), -m_port(0U), +CAPRSHandler::CAPRSHandler(IAPRSHandlerBackend* thread) : +m_backend(thread), m_array(), m_idFrameProvider(nullptr) { - assert(!hostname.empty()); - assert(port > 0U); - assert(!gateway.empty()); - assert(!password.empty()); - - m_thread = new CAPRSHandlerThread(gateway, password, address, hostname, port); - - m_gateway = gateway; - m_gateway = m_gateway.substr(0, LONG_CALLSIGN_LENGTH - 1U); - boost::trim(m_gateway); + assert(thread != nullptr); } CAPRSHandler::~CAPRSHandler() @@ -61,6 +48,7 @@ CAPRSHandler::~CAPRSHandler() } m_array.clear(); + delete m_backend; } void CAPRSHandler::setPort(const std::string& callsign, const std::string& band, double frequency, double offset, double range, double latitude, double longitude, double agl) @@ -74,7 +62,7 @@ void CAPRSHandler::setPort(const std::string& callsign, const std::string& band, bool CAPRSHandler::open() { - return m_thread->start(); + return m_backend->start(); } void CAPRSHandler::writeHeader(const std::string& callsign, const CHeaderData& header) @@ -89,7 +77,7 @@ void CAPRSHandler::writeHeader(const std::string& callsign, const CHeaderData& h CAPRSCollector* collector = entry->getCollector(); - collector->writeHeader(header.getMyCall1()); + collector->writeHeader(header); } void CAPRSHandler::writeData(const std::string& callsign, const CAMBEData& data) @@ -117,22 +105,22 @@ void CAPRSHandler::writeData(const std::string& callsign, const CAMBEData& data) if (!complete) return; - if (!m_thread->isConnected()) { + if (!m_backend->isConnected()) { collector->reset(); return; } - collector->getData([=](const std::string& text) + collector->getData([=](const std::string& rawFrame, const std::string& dstarCall) { CAPRSFrame frame; - if(!CAPRSParser::parseFrame(text, frame)) { - CLog::logWarning("Failed to parse DPRS Frame : %s", text.c_str()); + if(!CAPRSParser::parseFrame(rawFrame, frame)) { + CLog::logWarning("Failed to parse DPRS Frame : %s", rawFrame.c_str()); return; } // If we already have a q-construct, don't send it on if(std::any_of(frame.getPath().begin(), frame.getPath().end(), [] (std::string s) { return !s.empty() && s[0] == 'q'; })) { - CLog::logWarning("DPRS Frame already has q construct, not forwarding to APRS-IS: %s", text.c_str()); + CLog::logWarning("DPRS Frame already has q construct, not forwarding to APRS-IS: %s", rawFrame.c_str()); return; } @@ -142,7 +130,9 @@ void CAPRSHandler::writeData(const std::string& callsign, const CAMBEData& data) std::string output ; CAPRSFormater::frameToString(output, frame); - m_thread->write(frame); + CLog::logInfo("DPRS\t%s\t%s\t%s", dstarCall.c_str(), frame.getSource().c_str(), rawFrame.c_str()); + + m_backend->write(frame); }); } @@ -159,7 +149,7 @@ void CAPRSHandler::writeStatus(const std::string& callsign, const std::string st void CAPRSHandler::clock(unsigned int ms) { - m_thread->clock(ms); + m_backend->clock(ms); if(m_idFrameProvider != nullptr) { m_idFrameProvider->clock(ms); @@ -181,7 +171,7 @@ void CAPRSHandler::sendStatusFrame(CAPRSEntry * entry) { assert(entry != nullptr); - if(!m_thread->isConnected()) + if(!m_backend->isConnected()) return; @@ -199,19 +189,19 @@ void CAPRSHandler::sendStatusFrame(CAPRSEntry * entry) body, APFT_STATUS); - m_thread->write(frame); + m_backend->write(frame); } void CAPRSHandler::sendIdFrames() { - if(m_thread->isConnected()) + if(m_backend->isConnected()) { for(auto entry : m_array) { std::vector frames; - if(m_idFrameProvider->buildAPRSFrames(m_gateway, entry.second, frames)) { + if(m_idFrameProvider->buildAPRSFrames(entry.second, frames)) { for(auto frame : frames) { - m_thread->write(*frame); + m_backend->write(*frame); delete frame; } } @@ -221,12 +211,12 @@ void CAPRSHandler::sendIdFrames() bool CAPRSHandler::isConnected() const { - return m_thread->isConnected(); + return m_backend->isConnected(); } void CAPRSHandler::close() { - m_thread->stop(); + m_backend->stop(); if(m_idFrameProvider != nullptr) { m_idFrameProvider->close(); @@ -237,5 +227,5 @@ void CAPRSHandler::close() void CAPRSHandler::addReadAPRSCallback(IReadAPRSFrameCallback* cb) { - m_thread->addReadAPRSCallback(cb); + m_backend->addReadAPRSCallback(cb); } diff --git a/Common/APRSHandler.h b/Common/APRSHandler.h index 743ad37..dea2046 100644 --- a/Common/APRSHandler.h +++ b/Common/APRSHandler.h @@ -26,7 +26,6 @@ #include "APRSEntry.h" -#include "APRSHandlerThread.h" #include "UDPReaderWriter.h" #include "APRSCollector.h" #include "DStarDefines.h" @@ -34,10 +33,11 @@ #include "AMBEData.h" #include "Timer.h" #include "APRSIdFrameProvider.h" +#include "IAPRSHandlerBackend.h" class CAPRSHandler { public: - CAPRSHandler(const std::string& hostname, unsigned int port, const std::string& gateway, const std::string& password, const std::string& address); + CAPRSHandler(IAPRSHandlerBackend * thread); ~CAPRSHandler(); bool open(); @@ -61,10 +61,7 @@ public: void addReadAPRSCallback(IReadAPRSFrameCallback* cb); private: - CAPRSHandlerThread* m_thread; - std::string m_gateway; - in_addr m_address; - unsigned int m_port; + IAPRSHandlerBackend* m_backend; std::unordered_map m_array; CAPRSIdFrameProvider * m_idFrameProvider; diff --git a/Common/APRSHandlerThread.cpp b/Common/APRSISHandlerThread.cpp similarity index 87% rename from Common/APRSHandlerThread.cpp rename to Common/APRSISHandlerThread.cpp index e3098c6..a060fe1 100644 --- a/Common/APRSHandlerThread.cpp +++ b/Common/APRSISHandlerThread.cpp @@ -21,7 +21,7 @@ #include #include -#include "APRSHandlerThread.h" +#include "APRSISHandlerThread.h" #include "DStarDefines.h" #include "Utils.h" #include "Defs.h" @@ -36,7 +36,7 @@ const unsigned int APRS_TIMEOUT = 10U; const unsigned int APRS_READ_TIMEOUT = 1U; const unsigned int APRS_KEEP_ALIVE_TIMEOUT = 60U; -CAPRSHandlerThread::CAPRSHandlerThread(const std::string& callsign, const std::string& password, const std::string& address, const std::string& hostname, unsigned int port) : +CAPRSISHandlerThread::CAPRSISHandlerThread(const std::string& callsign, const std::string& password, const std::string& address, const std::string& hostname, unsigned int port) : CThread("APRS"), m_username(callsign), m_password(password), @@ -64,7 +64,7 @@ m_clientName(FULL_PRODUCT_NAME) m_ssid = m_ssid.substr(LONG_CALLSIGN_LENGTH - 1U, 1); } -CAPRSHandlerThread::CAPRSHandlerThread(const std::string& callsign, const std::string& password, const std::string& address, const std::string& hostname, unsigned int port, const std::string& filter) : +CAPRSISHandlerThread::CAPRSISHandlerThread(const std::string& callsign, const std::string& password, const std::string& address, const std::string& hostname, unsigned int port, const std::string& filter) : CThread("APRS"), m_username(callsign), m_password(password), @@ -92,7 +92,7 @@ m_clientName(FULL_PRODUCT_NAME) m_ssid = m_ssid.substr(LONG_CALLSIGN_LENGTH - 1U, 1); } -CAPRSHandlerThread::~CAPRSHandlerThread() +CAPRSISHandlerThread::~CAPRSISHandlerThread() { std::vector callBacksCopy; callBacksCopy.assign(m_APRSReadCallbacks.begin(), m_APRSReadCallbacks.end()); @@ -105,7 +105,7 @@ CAPRSHandlerThread::~CAPRSHandlerThread() m_password.clear(); } -bool CAPRSHandlerThread::start() +bool CAPRSISHandlerThread::start() { Create(); Run(); @@ -113,7 +113,7 @@ bool CAPRSHandlerThread::start() return true; } -void* CAPRSHandlerThread::Entry() +void* CAPRSISHandlerThread::Entry() { CLog::logInfo("Starting the APRS Writer thread"); @@ -151,7 +151,7 @@ void* CAPRSHandlerThread::Entry() if(!m_queue.empty()){ auto frameStr = m_queue.getData(); - CLog::logInfo("APRS ==> %s", frameStr.c_str()); + CLog::logInfo("APRS Frame sent to IS ==> %s", frameStr.c_str()); bool ret = m_socket.writeLine(frameStr); if (!ret) { @@ -176,7 +176,7 @@ void* CAPRSHandlerThread::Entry() } else if(line.length() > 0 && line[0] != '#') { m_keepAliveTimer.start(); - CLog::logDebug("APRS <== %s", line.c_str()); + CLog::logDebug("APRS Frame received from IS <== %s", line.c_str()); CAPRSFrame readFrame; if(CAPRSParser::parseFrame(line, readFrame)) { for(auto cb : m_APRSReadCallbacks) { @@ -214,13 +214,13 @@ void* CAPRSHandlerThread::Entry() return NULL; } -void CAPRSHandlerThread::addReadAPRSCallback(IReadAPRSFrameCallback * cb) +void CAPRSISHandlerThread::addReadAPRSCallback(IReadAPRSFrameCallback * cb) { assert(cb != nullptr); m_APRSReadCallbacks.push_back(cb); } -void CAPRSHandlerThread::write(CAPRSFrame& frame) +void CAPRSISHandlerThread::write(CAPRSFrame& frame) { if (!m_connected) return; @@ -235,25 +235,25 @@ void CAPRSHandlerThread::write(CAPRSFrame& frame) } } -bool CAPRSHandlerThread::isConnected() const +bool CAPRSISHandlerThread::isConnected() const { return m_connected; } -void CAPRSHandlerThread::stop() +void CAPRSISHandlerThread::stop() { m_exit = true; Wait(); } -void CAPRSHandlerThread::clock(unsigned int ms) +void CAPRSISHandlerThread::clock(unsigned int ms) { m_reconnectTimer.clock(ms); m_keepAliveTimer.clock(ms); } -bool CAPRSHandlerThread::connect() +bool CAPRSISHandlerThread::connect() { m_socket.close(); bool ret = m_socket.open(); @@ -304,7 +304,7 @@ bool CAPRSHandlerThread::connect() return true; } -void CAPRSHandlerThread::startReconnectionTimer() +void CAPRSISHandlerThread::startReconnectionTimer() { // Clamp at a ten minutes reconnect time m_tries++; diff --git a/Common/APRSHandlerThread.h b/Common/APRSISHandlerThread.h similarity index 78% rename from Common/APRSHandlerThread.h rename to Common/APRSISHandlerThread.h index 01410de..4be743e 100644 --- a/Common/APRSHandlerThread.h +++ b/Common/APRSISHandlerThread.h @@ -25,15 +25,15 @@ #include "RingBuffer.h" #include "Timer.h" #include "Thread.h" -#include "ReadAPRSFrameCallback.h" +#include "IAPRSHandlerBackend.h" #include "APRSFrame.h" -class CAPRSHandlerThread : public CThread { +class CAPRSISHandlerThread : public CThread, IAPRSHandlerBackend { public: - CAPRSHandlerThread(const std::string& callsign, const std::string& password, const std::string& address, const std::string& hostname, unsigned int port); - CAPRSHandlerThread(const std::string& callsign, const std::string& password, const std::string& address, const std::string& hostname, unsigned int port, const std::string& filter); - virtual ~CAPRSHandlerThread(); + CAPRSISHandlerThread(const std::string& callsign, const std::string& password, const std::string& address, const std::string& hostname, unsigned int port); + CAPRSISHandlerThread(const std::string& callsign, const std::string& password, const std::string& address, const std::string& hostname, unsigned int port, const std::string& filter); + virtual ~CAPRSISHandlerThread(); bool start(); diff --git a/Common/APRSIdFrameProvider.cpp b/Common/APRSIdFrameProvider.cpp index 3637f9d..fc8b6e0 100644 --- a/Common/APRSIdFrameProvider.cpp +++ b/Common/APRSIdFrameProvider.cpp @@ -17,13 +17,21 @@ */ #include +#include #include "APRSIdFrameProvider.h" -CAPRSIdFrameProvider::CAPRSIdFrameProvider(unsigned int timeout) : +CAPRSIdFrameProvider::CAPRSIdFrameProvider(const std::string& gateway, unsigned int timeout) : +m_gateway(), m_timer(1000U) { + assert(!gateway.empty()); + m_timer.start(timeout); + + m_gateway = gateway; + m_gateway = m_gateway.substr(0, LONG_CALLSIGN_LENGTH - 1U); + boost::trim(m_gateway); } CAPRSIdFrameProvider::~CAPRSIdFrameProvider() @@ -31,11 +39,11 @@ CAPRSIdFrameProvider::~CAPRSIdFrameProvider() } -bool CAPRSIdFrameProvider::buildAPRSFrames(const std::string& gateway, const CAPRSEntry * entry, std::vector & frames) +bool CAPRSIdFrameProvider::buildAPRSFrames(const CAPRSEntry * entry, std::vector & frames) { assert(entry != nullptr); - return buildAPRSFramesInt(gateway, entry, frames); + return buildAPRSFramesInt(entry, frames); } bool CAPRSIdFrameProvider::wantsToSend() diff --git a/Common/APRSIdFrameProvider.h b/Common/APRSIdFrameProvider.h index 48917ee..f123118 100644 --- a/Common/APRSIdFrameProvider.h +++ b/Common/APRSIdFrameProvider.h @@ -27,23 +27,25 @@ class CAPRSIdFrameProvider { public: - CAPRSIdFrameProvider(unsigned int timeOut); + CAPRSIdFrameProvider(const std::string& gateway, unsigned int timeOut); virtual ~CAPRSIdFrameProvider(); - bool buildAPRSFrames(const std::string& gateway, const CAPRSEntry * aprsEntry, std::vector& frames); + bool buildAPRSFrames(const CAPRSEntry * aprsEntry, std::vector& frames); void clock(unsigned int ms) { m_timer.clock(ms); } bool wantsToSend(); virtual void start() { }; virtual void close() { }; protected: - virtual bool buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * aprsEntry, std::vector& frames) = 0; + virtual bool buildAPRSFramesInt(const CAPRSEntry * aprsEntry, std::vector& frames) = 0; void setTimeout(unsigned int timeout) { m_timer.start(timeout); } +protected: + std::string m_gateway; private: CTimer m_timer; }; \ No newline at end of file diff --git a/Common/DummyAPRSHandlerThread.cpp b/Common/DummyAPRSHandlerThread.cpp new file mode 100644 index 0000000..c404fe0 --- /dev/null +++ b/Common/DummyAPRSHandlerThread.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021, 2022 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DummyAPRSHandlerThread.h" + +CDummyAPRSHandlerBackend::CDummyAPRSHandlerBackend() +{ + +} + +CDummyAPRSHandlerBackend::~CDummyAPRSHandlerBackend() +{ + +} + +bool CDummyAPRSHandlerBackend::start() +{ + return true; +} + +bool CDummyAPRSHandlerBackend::isConnected() const +{ + return true; +} + +void CDummyAPRSHandlerBackend::write(CAPRSFrame &) +{ +} + +void CDummyAPRSHandlerBackend::clock(unsigned int) +{ +} + +void CDummyAPRSHandlerBackend::stop() +{ +} + +void CDummyAPRSHandlerBackend::addReadAPRSCallback(IReadAPRSFrameCallback *) +{ +} \ No newline at end of file diff --git a/Common/DummyAPRSHandlerThread.h b/Common/DummyAPRSHandlerThread.h new file mode 100644 index 0000000..3401813 --- /dev/null +++ b/Common/DummyAPRSHandlerThread.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2021, 2022 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#pragma once + +#include "IAPRSHandlerBackend.h" + +class CDummyAPRSHandlerBackend : public IAPRSHandlerBackend +{ +public: + CDummyAPRSHandlerBackend(); + ~CDummyAPRSHandlerBackend(); + + bool start(); + bool isConnected() const; + void write(CAPRSFrame& frame); + void clock(unsigned int ms); + void stop(); + void addReadAPRSCallback(IReadAPRSFrameCallback* cb); +}; \ No newline at end of file diff --git a/Common/HeardData.cpp b/Common/HeardData.cpp index e16d997..1b1ac8d 100644 --- a/Common/HeardData.cpp +++ b/Common/HeardData.cpp @@ -64,10 +64,11 @@ bool CHeardData::setIcomRepeaterData(const unsigned char *data, unsigned int len assert(data != NULL); assert(length >= 26U); - std::string sdata((const char *)data); + std::string suser((const char *)data + 10U); + std::string srptr((const char *)data + 18U); - m_user = sdata.substr(10, LONG_CALLSIGN_LENGTH); - m_repeater = sdata.substr(18, LONG_CALLSIGN_LENGTH); + m_user = suser.substr(0, LONG_CALLSIGN_LENGTH); + m_repeater = srptr.substr(0, LONG_CALLSIGN_LENGTH); m_address = address; m_port = port; diff --git a/Common/IAPRSHandlerBackend.h b/Common/IAPRSHandlerBackend.h new file mode 100644 index 0000000..e06f10f --- /dev/null +++ b/Common/IAPRSHandlerBackend.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2021, 2022 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#pragma once + +#include "APRSFrame.h" +#include "ReadAPRSFrameCallback.h" + +class IAPRSHandlerBackend +{ +public: + virtual ~IAPRSHandlerBackend() { } ; + + virtual bool start() = 0; + virtual bool isConnected() const = 0; + virtual void write(CAPRSFrame& frame) = 0; + virtual void clock(unsigned int ms) = 0; + virtual void stop() = 0; + virtual void addReadAPRSCallback(IReadAPRSFrameCallback* cb) = 0; +}; \ No newline at end of file diff --git a/Common/NMEASentenceCollector.cpp b/Common/NMEASentenceCollector.cpp index 9caa630..4ad660c 100644 --- a/Common/NMEASentenceCollector.cpp +++ b/Common/NMEASentenceCollector.cpp @@ -58,7 +58,7 @@ unsigned char CNMEASentenceCollector::calcXOR(const std::string& nmea) unsigned char res = 0U; if(!nmea.empty()) { - unsigned int i = nmea[0] == '$' ? 1U : 0U; //skip $ it it is there + unsigned int i = nmea[0] == '$' ? 1U : 0U; //skip $ if it is there while(i < nmea.length()) { if(nmea[i] != '*') { @@ -98,15 +98,22 @@ unsigned int CNMEASentenceCollector::getDataInt(unsigned char * data, unsigned i bool CNMEASentenceCollector::getDataInt(std::string& data) { - if(getMyCall().empty() || getSentence().empty()) + if(getMyCall1().empty() || getSentence().empty()) return false; data.clear(); auto nmea = getSentence(); fixUpNMEATimeStamp(nmea); - std::string fromCall = getMyCall(); + std::string fromCall = getMyCall1(); CAPRSUtils::dstarCallsignToAPRS(fromCall); + + // 20230425 Fix for https://github.com/F4FXL/DStarGateway/issues/33 + size_t hyphenIndex = fromCall.find('-'); + if(hyphenIndex != std::string::npos) { + fromCall = fromCall.substr(0, hyphenIndex); + } + std::string aprsFrame(fromCall); aprsFrame.append("-5>GPS30,DSTAR*:") .append(nmea); diff --git a/Common/RepeaterHandler.cpp b/Common/RepeaterHandler.cpp index c66e95f..3fbb913 100644 --- a/Common/RepeaterHandler.cpp +++ b/Common/RepeaterHandler.cpp @@ -59,7 +59,8 @@ bool CRepeaterHandler::m_dtmfEnabled = true; CHeaderLogger* CRepeaterHandler::m_headerLogger = NULL; -CAPRSHandler* CRepeaterHandler::m_aprsWriter = NULL; +CAPRSHandler* CRepeaterHandler::m_outgoingAprsHandler = NULL; //handles APRS/DPRS frames coming from radio to network +CAPRSHandler* CRepeaterHandler::m_incomingAprsHandler = NULL; //handles APRS/DPRS frames coming from network to radio CCallsignList* CRepeaterHandler::m_restrictList = NULL; @@ -352,9 +353,10 @@ void CRepeaterHandler::setHeaderLogger(CHeaderLogger* logger) m_headerLogger = logger; } -void CRepeaterHandler::setAPRSWriter(CAPRSHandler* writer) +void CRepeaterHandler::setAPRSHandlers(CAPRSHandler* outgoingAprsHandler, CAPRSHandler* incomingAprsHandler) { - m_aprsWriter = writer; + m_outgoingAprsHandler = outgoingAprsHandler; + m_incomingAprsHandler = incomingAprsHandler; } void CRepeaterHandler::setLocalAddress(const std::string& address) @@ -613,8 +615,8 @@ void CRepeaterHandler::processRepeater(CHeaderData& header) m_text.clear(); // Reset the APRS Writer if it's enabled - if (m_aprsWriter != NULL) - m_aprsWriter->writeHeader(m_rptCallsign, header); + if (m_outgoingAprsHandler != NULL) + m_outgoingAprsHandler->writeHeader(m_rptCallsign, header); // Write to Header.log if it's enabled if (m_headerLogger != NULL) @@ -823,8 +825,8 @@ void CRepeaterHandler::processRepeater(CAMBEData& data) if (m_drats != NULL) m_drats->writeData(data); - if (m_aprsWriter != NULL) - m_aprsWriter->writeData(m_rptCallsign, data); + if (m_outgoingAprsHandler != NULL) + m_outgoingAprsHandler->writeData(m_rptCallsign, data); if (m_text.empty() && !data.isEnd()) { m_textCollector.writeData(data); @@ -1173,6 +1175,9 @@ bool CRepeaterHandler::process(CHeaderData& header, DIRECTION, AUDIO_SOURCE sour if (source == AS_DUP) return true; + if(m_incomingAprsHandler != nullptr) + m_incomingAprsHandler->writeHeader(m_rptCallsign, header); + sendToIncoming(header); #ifdef USE_CCS @@ -1212,6 +1217,9 @@ bool CRepeaterHandler::process(CAMBEData& data, DIRECTION, AUDIO_SOURCE source) m_repeaterHandler->writeAMBE(data); + if(m_incomingAprsHandler != nullptr) + m_incomingAprsHandler->writeData(m_rptCallsign, data); + sendToIncoming(data); #ifdef USE_CCS @@ -2413,8 +2421,8 @@ void CRepeaterHandler::startupInt() m_irc->rptrQTH(callsign, m_latitude, m_longitude, m_description1, m_description2, m_url); } - if(m_aprsWriter != nullptr) { - m_aprsWriter->addReadAPRSCallback(this); + if(m_outgoingAprsHandler != nullptr) { + m_outgoingAprsHandler->addReadAPRSCallback(this); } #ifdef USE_CCS @@ -2564,8 +2572,8 @@ void CRepeaterHandler::writeLinkingTo(const std::string &callsign) m_infoAudio->setStatus(m_linkStatus, m_linkRepeater, text); triggerInfo(); - if(m_aprsWriter != nullptr) - m_aprsWriter->writeStatus(m_rptCallsign, text); + if(m_outgoingAprsHandler != nullptr) + m_outgoingAprsHandler->writeStatus(m_rptCallsign, text); #ifdef USE_CCS m_ccsHandler->setReflector(); @@ -2619,8 +2627,8 @@ void CRepeaterHandler::writeLinkedTo(const std::string &callsign) m_infoAudio->setStatus(m_linkStatus, m_linkRepeater, text); triggerInfo(); - if(m_aprsWriter != nullptr) - m_aprsWriter->writeStatus(m_rptCallsign, text); + if(m_outgoingAprsHandler != nullptr) + m_outgoingAprsHandler->writeStatus(m_rptCallsign, text); #ifdef USE_CCS m_ccsHandler->setReflector(callsign); @@ -2674,8 +2682,8 @@ void CRepeaterHandler::writeNotLinked() m_infoAudio->setStatus(m_linkStatus, m_linkRepeater, text); triggerInfo(); - if(m_aprsWriter != nullptr) - m_aprsWriter->writeStatus(m_rptCallsign, text); + if(m_outgoingAprsHandler != nullptr) + m_outgoingAprsHandler->writeStatus(m_rptCallsign, text); #ifdef USE_CCS m_ccsHandler->setReflector(); @@ -2745,8 +2753,8 @@ void CRepeaterHandler::writeIsBusy(const std::string& callsign) m_infoAudio->setTempStatus(m_linkStatus, m_linkRepeater, tempText); triggerInfo(); - if(m_aprsWriter != nullptr) - m_aprsWriter->writeStatus(m_rptCallsign, text); + if(m_outgoingAprsHandler != nullptr) + m_outgoingAprsHandler->writeStatus(m_rptCallsign, text); #ifdef USE_CCS m_ccsHandler->setReflector(); diff --git a/Common/RepeaterHandler.h b/Common/RepeaterHandler.h index e0422c4..c5ed834 100644 --- a/Common/RepeaterHandler.h +++ b/Common/RepeaterHandler.h @@ -74,7 +74,7 @@ public: static void setDPlusEnabled(bool enabled); static void setDCSEnabled(bool enabled); static void setHeaderLogger(CHeaderLogger* logger); - static void setAPRSWriter(CAPRSHandler* writer); + static void setAPRSHandlers(CAPRSHandler* outgoingAprsHandler, CAPRSHandler* incomingAprsHandler); static void setInfoEnabled(bool enabled); static void setEchoEnabled(bool enabled); static void setDTMFEnabled(bool enabled); @@ -169,7 +169,8 @@ private: static CHeaderLogger* m_headerLogger; - static CAPRSHandler* m_aprsWriter; + static CAPRSHandler* m_outgoingAprsHandler; + static CAPRSHandler* m_incomingAprsHandler; static CCallsignList* m_whiteList; static CCallsignList* m_blackList; diff --git a/Common/SlowDataCollector.cpp b/Common/SlowDataCollector.cpp index b9caad0..55f38e7 100644 --- a/Common/SlowDataCollector.cpp +++ b/Common/SlowDataCollector.cpp @@ -27,7 +27,7 @@ const unsigned int SLOW_DATA_BLOCK_LENGTH = 6U; CSlowDataCollector::CSlowDataCollector(unsigned char slowDataType) : m_slowDataType(slowDataType), -m_myCall(), +m_myCall1(), m_state(SS_FIRST) { m_buffer = new unsigned char[SLOW_DATA_BLOCK_LENGTH]; @@ -39,14 +39,24 @@ CSlowDataCollector::~CSlowDataCollector() delete[] m_buffer; } -std::string CSlowDataCollector::getMyCall() const +std::string CSlowDataCollector::getMyCall1() const { - return m_myCall; + return m_myCall1; } -void CSlowDataCollector::setMyCall(const std::string& myCall) +void CSlowDataCollector::setMyCall1(const std::string& myCall) { - m_myCall = myCall; + m_myCall1 = myCall; +} + +std::string CSlowDataCollector::getMyCall2() const +{ + return m_myCall2; +} + +void CSlowDataCollector::setMyCall2(const std::string& myCall) +{ + m_myCall2 = myCall; } bool CSlowDataCollector::writeData(const unsigned char* data) diff --git a/Common/SlowDataCollector.h b/Common/SlowDataCollector.h index f2389f9..1f6c803 100644 --- a/Common/SlowDataCollector.h +++ b/Common/SlowDataCollector.h @@ -29,8 +29,10 @@ class ISlowDataCollector public: virtual ~ISlowDataCollector() { } ; - virtual std::string getMyCall() const = 0; - virtual void setMyCall(const std::string& mycall) = 0; + virtual std::string getMyCall1() const = 0; + virtual void setMyCall1(const std::string& mycall) = 0; + virtual std::string getMyCall2() const = 0; + virtual void setMyCall2(const std::string& mycall) = 0; virtual bool writeData(const unsigned char* data) = 0; virtual void sync() = 0; virtual unsigned int getData(unsigned char* data, unsigned int length) = 0; @@ -46,8 +48,10 @@ public: CSlowDataCollector(unsigned char slowDataType); virtual ~CSlowDataCollector(); - std::string getMyCall() const; - void setMyCall(const std::string& mycall); + std::string getMyCall1() const; + void setMyCall1(const std::string& mycall); + std::string getMyCall2() const; + void setMyCall2(const std::string& mycall); bool writeData(const unsigned char* data); void sync(); unsigned int getData(unsigned char* data, unsigned int length); @@ -64,7 +68,8 @@ protected: private: unsigned char m_slowDataType; - std::string m_myCall; + std::string m_myCall1; + std::string m_myCall2; SLOWDATA_STATE m_state; unsigned char * m_buffer; }; \ No newline at end of file diff --git a/Common/SlowDataCollectorThrottle.cpp b/Common/SlowDataCollectorThrottle.cpp index 8d97fe1..37e0f23 100644 --- a/Common/SlowDataCollectorThrottle.cpp +++ b/Common/SlowDataCollectorThrottle.cpp @@ -33,16 +33,27 @@ CSlowDataCollectorThrottle::~CSlowDataCollectorThrottle() delete m_collector; } -std::string CSlowDataCollectorThrottle::getMyCall() const +std::string CSlowDataCollectorThrottle::getMyCall1() const { - return m_collector->getMyCall(); + return m_collector->getMyCall1(); } -void CSlowDataCollectorThrottle::setMyCall(const std::string& mycall) +void CSlowDataCollectorThrottle::setMyCall1(const std::string& mycall) { m_isFirst = true; - m_collector->setMyCall(mycall); + m_collector->setMyCall1(mycall); } +std::string CSlowDataCollectorThrottle::getMyCall2() const +{ + return m_collector->getMyCall2(); +} + +void CSlowDataCollectorThrottle::setMyCall2(const std::string& mycall) +{ + m_isFirst = true; + m_collector->setMyCall2(mycall); +} + bool CSlowDataCollectorThrottle::writeData(const unsigned char* data) { m_isComplete = false; diff --git a/Common/SlowDataCollectorThrottle.h b/Common/SlowDataCollectorThrottle.h index f7fbd23..55ca7b4 100644 --- a/Common/SlowDataCollectorThrottle.h +++ b/Common/SlowDataCollectorThrottle.h @@ -28,8 +28,10 @@ class CSlowDataCollectorThrottle : public ISlowDataCollector public: CSlowDataCollectorThrottle(ISlowDataCollector* collector, unsigned int timeout); ~CSlowDataCollectorThrottle(); - std::string getMyCall() const; - void setMyCall(const std::string& mycall); + std::string getMyCall1() const; + void setMyCall1(const std::string& mycall); + std::string getMyCall2() const; + void setMyCall2(const std::string& mycall); bool writeData(const unsigned char* data); void sync(); unsigned int getData(unsigned char* data, unsigned int length); diff --git a/DStarGateway/DStarGatewayApp.cpp b/DStarGateway/DStarGatewayApp.cpp index 8ce9864..a62550b 100644 --- a/DStarGateway/DStarGatewayApp.cpp +++ b/DStarGateway/DStarGatewayApp.cpp @@ -1,6 +1,6 @@ /* * Copyright (C) 2010,2011 by Jonathan Naylor G4KLX - * Copyright (c) 2021-2022 by Geoffrey Merck F4FXL / KC3FRA + * Copyright (c) 2021,2022 by Geoffrey Merck F4FXL / KC3FRA * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -48,6 +48,8 @@ #include "APRSGPSDIdFrameProvider.h" #include "APRSFixedIdFrameProvider.h" #include "Daemon.h" +#include "APRSISHandlerThread.h" +#include "DummyAPRSHandlerThread.h" CDStarGatewayApp * CDStarGatewayApp::g_app = nullptr; const std::string BANNER_1 = CStringUtils::string_format("%s Copyright (C) %s\n", FULL_PRODUCT_NAME.c_str(), VENDOR_NAME.c_str()); @@ -192,23 +194,28 @@ bool CDStarGatewayApp::createThread() // Setup APRS TAPRS aprsConfig; m_config->getAPRS(aprsConfig); - CAPRSHandler * aprsWriter = NULL; + CAPRSHandler * outgoingAprsWriter = nullptr; + CAPRSHandler * incomingAprsWriter = nullptr; if(aprsConfig.enabled && !aprsConfig.password.empty()) { - aprsWriter = new CAPRSHandler(aprsConfig.hostname, aprsConfig.port, gatewayConfig.callsign, aprsConfig.password, gatewayConfig.address); - if(aprsWriter->open()) { + CAPRSISHandlerThread* aprsisthread = new CAPRSISHandlerThread(gatewayConfig.callsign, aprsConfig.password, gatewayConfig.address, aprsConfig.hostname, aprsConfig.port); + outgoingAprsWriter = new CAPRSHandler((IAPRSHandlerBackend *)aprsisthread); + + incomingAprsWriter = new CAPRSHandler((IAPRSHandlerBackend *)new CDummyAPRSHandlerBackend()); + + if(outgoingAprsWriter->open()) { #ifdef USE_GPSD - CAPRSIdFrameProvider * idFrameProvider = aprsConfig.m_positionSource == POSSRC_GPSD ? (CAPRSIdFrameProvider *)new CAPRSGPSDIdFrameProvider(gpsdConfig.m_address, gpsdConfig.m_port) - : new CAPRSFixedIdFrameProvider(); + CAPRSIdFrameProvider * idFrameProvider = aprsConfig.m_positionSource == POSSRC_GPSD ? (CAPRSIdFrameProvider *)new CAPRSGPSDIdFrameProvider(gatewayConfig.callsign, gpsdConfig.m_address, gpsdConfig.m_port) + : new CAPRSFixedIdFrameProvider(gatewayConfig.callsign); #else - CAPRSIdFrameProvider * idFrameProvider = new CAPRSFixedIdFrameProvider(); + CAPRSIdFrameProvider * idFrameProvider = new CAPRSFixedIdFrameProvider(gatewayConfig.callsign); #endif idFrameProvider->start(); - aprsWriter->setIdFrameProvider(idFrameProvider); - m_thread->setAPRSWriter(aprsWriter); + outgoingAprsWriter->setIdFrameProvider(idFrameProvider); + m_thread->setAPRSWriters(outgoingAprsWriter, incomingAprsWriter); } else { - delete aprsWriter; - aprsWriter = NULL; + delete outgoingAprsWriter; + outgoingAprsWriter = NULL; } } @@ -278,7 +285,10 @@ bool CDStarGatewayApp::createThread() rptrConfig.band2, rptrConfig.band3); - aprsWriter->setPort(rptrConfig.callsign, rptrConfig.band, rptrConfig.frequency, rptrConfig.offset, rptrConfig.range, rptrConfig.latitude, rptrConfig.longitude, rptrConfig.agl); + if(outgoingAprsWriter != nullptr) + outgoingAprsWriter->setPort(rptrConfig.callsign, rptrConfig.band, rptrConfig.frequency, rptrConfig.offset, rptrConfig.range, rptrConfig.latitude, rptrConfig.longitude, rptrConfig.agl); + if(incomingAprsWriter != nullptr) + incomingAprsWriter->setPort(rptrConfig.callsign, rptrConfig.band, rptrConfig.frequency, rptrConfig.offset, rptrConfig.range, rptrConfig.latitude, rptrConfig.longitude, rptrConfig.agl); if(!ddEnabled) ddEnabled = rptrConfig.band.length() > 1U; } diff --git a/DStarGateway/DStarGatewayThread.cpp b/DStarGateway/DStarGatewayThread.cpp index 1d7d074..3f4c218 100644 --- a/DStarGateway/DStarGatewayThread.cpp +++ b/DStarGateway/DStarGatewayThread.cpp @@ -69,15 +69,16 @@ m_stopped(true), m_gatewayType(GT_REPEATER), m_gatewayCallsign(), m_gatewayAddress(), -m_icomRepeaterHandler(NULL), -m_hbRepeaterHandler(NULL), -m_dummyRepeaterHandler(NULL), -m_dextraPool(NULL), -m_dplusPool(NULL), -m_dcsPool(NULL), -m_g2HandlerPool(NULL), -m_aprsWriter(NULL), -m_irc(NULL), +m_icomRepeaterHandler(nullptr), +m_hbRepeaterHandler(nullptr), +m_dummyRepeaterHandler(nullptr), +m_dextraPool(nullptr), +m_dplusPool(nullptr), +m_dcsPool(nullptr), +m_g2HandlerPool(nullptr), +m_outgoingAprsHandler(nullptr), +m_incomingAprsHandler(nullptr), +m_irc(nullptr), m_cache(), m_language(TL_ENGLISH_UK), m_dextraEnabled(true), @@ -110,9 +111,9 @@ m_status4(), m_status5(), m_latitude(0.0), m_longitude(0.0), -m_whiteList(NULL), -m_blackList(NULL), -m_restrictList(NULL) +m_whiteList(nullptr), +m_blackList(nullptr), +m_restrictList(nullptr) { CHeaderData::initialise(); CG2Handler::initialise(MAX_ROUTES); @@ -250,7 +251,7 @@ void* CDStarGatewayThread::Entry() CRepeaterHandler::setDPlusEnabled(m_dplusEnabled); CRepeaterHandler::setDCSEnabled(m_dcsEnabled); CRepeaterHandler::setHeaderLogger(headerLogger); - CRepeaterHandler::setAPRSWriter(m_aprsWriter); + CRepeaterHandler::setAPRSHandlers(m_outgoingAprsHandler, m_incomingAprsHandler); CRepeaterHandler::setInfoEnabled(m_infoEnabled); CRepeaterHandler::setEchoEnabled(m_echoEnabled); CRepeaterHandler::setDTMFEnabled(m_dtmfEnabled); @@ -377,8 +378,8 @@ void* CDStarGatewayThread::Entry() m_statusFileTimer.start(); } - if (m_aprsWriter != NULL) - m_aprsWriter->clock(ms); + if (m_outgoingAprsHandler != NULL) + m_outgoingAprsHandler->clock(ms); if (m_logEnabled) { m_statusTimer1.clock(ms); @@ -469,9 +470,9 @@ void* CDStarGatewayThread::Entry() delete m_remote; } - if(m_aprsWriter != nullptr) { - m_aprsWriter->close(); - delete m_aprsWriter; + if(m_outgoingAprsHandler != nullptr) { + m_outgoingAprsHandler->close(); + delete m_outgoingAprsHandler; } if (headerLogger != NULL) { @@ -636,9 +637,10 @@ void CDStarGatewayThread::setLog(bool enabled) m_logEnabled = enabled; } -void CDStarGatewayThread::setAPRSWriter(CAPRSHandler* writer) +void CDStarGatewayThread::setAPRSWriters(CAPRSHandler* outgoingWriter, CAPRSHandler* incomingWriter) { - m_aprsWriter = writer; + m_outgoingAprsHandler = outgoingWriter; + m_incomingAprsHandler = incomingWriter; } void CDStarGatewayThread::setInfoEnabled(bool enabled) @@ -1244,8 +1246,8 @@ void CDStarGatewayThread::writeStatus() CDStarGatewayStatusData* CDStarGatewayThread::getStatus() const { bool aprsStatus = false; - if (m_aprsWriter != NULL) - aprsStatus = m_aprsWriter->isConnected(); + if (m_outgoingAprsHandler != NULL) + aprsStatus = m_outgoingAprsHandler->isConnected(); CDStarGatewayStatusData* status = new CDStarGatewayStatusData(m_lastStatus, aprsStatus); diff --git a/DStarGateway/DStarGatewayThread.h b/DStarGateway/DStarGatewayThread.h index 767a81d..26a799b 100644 --- a/DStarGateway/DStarGatewayThread.h +++ b/DStarGateway/DStarGatewayThread.h @@ -66,7 +66,7 @@ public: virtual void setCCS(bool enabled, const std::string& host); #endif virtual void setLog(bool enabled); - virtual void setAPRSWriter(CAPRSHandler* writer); + virtual void setAPRSWriters(CAPRSHandler* outgoingAprsWriter, CAPRSHandler* incomingAPRSHandler); virtual void setInfoEnabled(bool enabled); virtual void setEchoEnabled(bool enabled); virtual void setDTMFEnabled(bool enabled); @@ -100,7 +100,8 @@ private: CDPlusProtocolHandlerPool* m_dplusPool; CDCSProtocolHandlerPool* m_dcsPool; CG2ProtocolHandlerPool* m_g2HandlerPool; - CAPRSHandler* m_aprsWriter; + CAPRSHandler* m_outgoingAprsHandler; + CAPRSHandler* m_incomingAprsHandler; CIRCDDB* m_irc; CCacheManager m_cache; TEXT_LANG m_language; diff --git a/README.md b/README.md index 5003389..e528206 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![F4FXL](https://circleci.com/gh/F4FXL/DStarGateway.svg?style=svg)](https://app.circleci.com/pipelines/github/F4FXL/DStarGateway?filter=all) - [1. Introduction](#1-introduction) - [2. Current State](#2-current-state) - [2.1. Code sanity](#21-code-sanity) @@ -9,7 +10,7 @@ - [3. Building and installing](#3-building-and-installing) - [3.1. Initial setup](#31-initial-setup) - [3.2. Get latest stable version (recommended)](#32-get-latest-stable-version-recommended) - - [3.3. Get latest development version version](#33-get-latest-development-version-version) + - [3.3. Get latest development version](#33-get-latest-development-version) - [3.4. Prerequisites and dependencies](#34-prerequisites-and-dependencies) - [3.5. Building](#35-building) - [3.5.0.1. Build With GPSD Support](#3501-build-with-gpsd-support) @@ -21,12 +22,13 @@ - [4.1. Work Flow](#41-work-flow) - [4.2. Continuous Integration](#42-continuous-integration) - [5. Version History](#5-version-history) - - [5.1. Version 0.6](#51-version-06) - - [5.2. Version 0.5](#52-version-05) - - [5.3. Version 0.4](#53-version-04) - - [5.4. Version 0.3](#54-version-03) - - [5.5. Version 0.2](#55-version-02) - - [5.6. Version 0.1](#56-version-01) + - [5.1. Version 0.7](#51-version-07) + - [5.2. Version 0.6](#52-version-06) + - [5.3. Version 0.5](#53-version-05) + - [5.4. Version 0.4](#54-version-04) + - [5.5. Version 0.3](#55-version-03) + - [5.6. Version 0.2](#56-version-02) + - [5.7. Version 0.1](#57-version-01) - [6. Future](#6-future) @@ -59,7 +61,7 @@ All the features found in ircddbGateway are supposed to be working. Except the o ### 2.4.2. Additional Features compared to ircddbGateway: - DPlus, DExtra and G2 NAT Traversal using ircddb network as rendez-vous server. I.e. it is not required to open firewall ports for Callsign Routing or Gateway calls. however it is still recommended to do so. But NAT Traversal will bring more flexibility when operating on CGNAT (Mobile) Networks. -- Forward RSMS1A app messages from/to APRS-IS Network, yes you can send/receive messages to and from aprs. Yes, you can send messages to APRS stations and Vice Versa. Additionnally part of the message is sent as Text Dat in the slow data. This allows you to read the message dirdclty on your radio screen. +- Forward RSMS1A app messages from/to APRS-IS Network, yes you can send/receive messages to and from aprs. Yes, you can send messages to APRS stations and Vice Versa. Additionnally, part of the message is sent as Text Dat in the slow data. This allows you to read the message directly on your radio screen. - Repeater Link status is sent to APRS-IS as a status frame # 3. Building and installing @@ -77,7 +79,7 @@ git fetch --tags latestTag=$(git describe --tags `git rev-list --tags --max-count=1`) git checkout $latestTag ``` -## 3.3. Get latest development version version +## 3.3. Get latest development version ``` git checkout develop ``` @@ -142,15 +144,21 @@ I have added some basic CI using CircleCI [![F4FXL](https://circleci.com/gh/F4FX The testing framwework used is Google Test. # 5. Version History -## 5.1. Version 0.6 -- [**Improvement**] Add DRats Support ([#22](https://github.com/F4FXL/DStarGateway/issues/22)) +## 5.1. Version 0.7 +- [**Bugfix**] Unknow repeater entries in log when using Icom Hardware ([#35](https://github.com/F4FXL/DStarGateway/issues/35)) +- [**Bugfix**] Malformed callsign in some cases when using DV-G (NMEA) ([#33](https://github.com/F4FXL/DStarGateway/issues/33)) +- [**Bugfix**] Crash on startup with Icom Hardware. Thanks to Josh AB9FT for reporting the issue.([#31](https://github.com/F4FXL/DStarGateway/issues/31)) +- [**Improvement**] Add/Fix DPRS Object support([#28](https://github.com/F4FXL/DStarGateway/issues/28)) +- [**Improvement**] Log incoming DPRS frames so they can be used in e.g. dashboards([#29](https://github.com/F4FXL/DStarGateway/issues/29)) +## 5.2. Version 0.6 +- [**Improvement**] Add DRats Support ([#24](https://github.com/F4FXL/DStarGateway/issues/24)) - [**Improvement**] Add call sign lists ([#22](https://github.com/F4FXL/DStarGateway/issues/22)) - [**Improvement**] Add a way to override Slow Data in VoiceTransmit ([#23](https://github.com/F4FXL/DStarGateway/issues/23)) - [**Improvement**] Add time server - [**Improvement**] Gracefully exit on SIGINT and SIGTERM ([#21](https://github.com/F4FXL/DStarGateway/issues/21)). DStarGateway can also be run as a "forking" daemon. This might be required for distros still using sysv. Systemd can live without it. - [**Improvement**] Add text transmit utility dgwtexttransmit ([#18](https://github.com/F4FXL/DStarGateway/issues/18)) - [**Improvement**] Add voice transmit utility dgwvoicetransmit ([#18](https://github.com/F4FXL/DStarGateway/issues/18)) -## 5.2. Version 0.5 +## 5.3. Version 0.5 - [**Improvement**] Add remote control utility dgwremotecontrol ([#17](https://github.com/F4FXL/DStarGateway/issues/17)) - [**Bugfix**] Two simultaneous incoming G2 streams would fail to be transmitted on dual band repeaters ([#16](https://github.com/F4FXL/DStarGateway/issues/16)) - [**Improvement**] Add NAT Traversal for G2 and DExtra, using IRCDDB as a Rendez Vous server ([#5](https://github.com/F4FXL/DStarGateway/issues/5)) @@ -158,17 +166,17 @@ The testing framwework used is Google Test. - [**Bugfix**] Failed to download XLX Hosts when URL contains a = sign ([#14](https://github.com/F4FXL/DStarGateway/issues/14)) - [**Bugfix**] Remote control connection failed ([#13](https://github.com/F4FXL/DStarGateway/issues/13)) - [**Bugfix**] Trying to connect to ghost ircDDB when no ircDDB is configured -## 5.3. Version 0.4 +## 5.4. Version 0.4 - [**Improvement**] Add APRS status link feature ([#8](https://github.com/F4FXL/DStarGateway/issues/8)) - [**Bugfix**] Posotions received over radio were not sent to APRS-IS when GPDS connection failed. ([#7](https://github.com/F4FXL/DStarGateway/issues/7)) - [**Improvement**] Bring back GPSD support ([#6](https://github.com/F4FXL/DStarGateway/issues/6)) - [**Improvement**] Log enhancements ([#4](https://github.com/F4FXL/DStarGateway/issues/4)) -## 5.4. Version 0.3 +## 5.5. Version 0.3 - [**Improvement**] Get ride of libcongig++ dependency. When upgrading from earlier version you need to manualy delete the config file before reinstalling. -## 5.5. Version 0.2 +## 5.6. Version 0.2 - [**Bugfix**] ircDDBFreeze when repeater not found ([#1](https://github.com/F4FXL/DStarGateway/issues/1)) - Code sanitization -## 5.6. Version 0.1 +## 5.7. Version 0.1 First working version # 6. Future I started this during my 2021 seasons holiday. It took me almost 8 days to get to a workable version. Here are a couple of stuff I'd like to do : diff --git a/Tests/APRSParser/parseAPRSFrame.cpp b/Tests/APRSParser/parseAPRSFrame.cpp index a2d1ae1..bf60a96 100644 --- a/Tests/APRSParser/parseAPRSFrame.cpp +++ b/Tests/APRSParser/parseAPRSFrame.cpp @@ -173,6 +173,50 @@ namespace APRSParserTests EXPECT_EQ(aprsFrame.getPath().size(), 0); } + TEST_F(APRSParser_parseAPRSFrame, ObjectAlive) + { + CAPRSFrame aprsFrame; + bool retVal = CAPRSParser::parseFrame("F8DSN-15>API510,DSTAR*:;F1ZBV *091510h4802.40N/00647.12ErPHG7430/A=003182R Vosges 145,6625@-0,6MHz", aprsFrame); + + EXPECT_TRUE(retVal); + EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), ";F1ZBV *091510h4802.40N/00647.12ErPHG7430/A=003182R Vosges 145,6625@-0,6MHz"); + EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "API510"); + EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "F8DSN-15"); + EXPECT_EQ(aprsFrame.getType(), APFT_OBJECT); + EXPECT_EQ(aprsFrame.getPath().size(), 1); + EXPECT_STRCASEEQ(aprsFrame.getPath().at(0).c_str(), "DSTAR*"); + } + + TEST_F(APRSParser_parseAPRSFrame, ObjectKilled) + { + CAPRSFrame aprsFrame; + bool retVal = CAPRSParser::parseFrame("F8DSN-15>API510,DSTAR*:;F1ZBV _091510h4802.40N/00647.12ErPHG7430/A=003182R Vosges 145,6625@-0,6MHz", aprsFrame); + + EXPECT_TRUE(retVal); + EXPECT_STRCASEEQ(aprsFrame.getBody().c_str(), ";F1ZBV _091510h4802.40N/00647.12ErPHG7430/A=003182R Vosges 145,6625@-0,6MHz"); + EXPECT_STRCASEEQ(aprsFrame.getDestination().c_str(), "API510"); + EXPECT_STRCASEEQ(aprsFrame.getSource().c_str(), "F8DSN-15"); + EXPECT_EQ(aprsFrame.getType(), APFT_OBJECT); + EXPECT_EQ(aprsFrame.getPath().size(), 1); + EXPECT_STRCASEEQ(aprsFrame.getPath().at(0).c_str(), "DSTAR*"); + } + + TEST_F(APRSParser_parseAPRSFrame, ObjectInvalid) + { + CAPRSFrame aprsFrame; + bool retVal = CAPRSParser::parseFrame("F8DSN-15>API510,DSTAR*:;F1ZBV ~091510h4802.40N/00647.12ErPHG7430/A=003182R Vosges 145,6625@-0,6MHz", aprsFrame); + + EXPECT_FALSE(retVal); + } + + TEST_F(APRSParser_parseAPRSFrame, ObjectTooShort) + { + CAPRSFrame aprsFrame; + bool retVal = CAPRSParser::parseFrame("F8DSN-15>API510,DSTAR*:;F1ZBV", aprsFrame); + + EXPECT_FALSE(retVal); + } + TEST_F(APRSParser_parseAPRSFrame, messageToSelf) { CAPRSFrame aprsFrame; diff --git a/Tests/APRSUtils/calcGPSAIcomCRC.cpp b/Tests/APRSUtils/calcGPSAIcomCRC.cpp index 9d5850c..d6a96d5 100644 --- a/Tests/APRSUtils/calcGPSAIcomCRC.cpp +++ b/Tests/APRSUtils/calcGPSAIcomCRC.cpp @@ -20,7 +20,7 @@ #include "APRSUtils.h" -namespace APRStoDPRSTests +namespace APRSUtilsTests { class APRSUtils_calcGPSAIcomCRC : public ::testing::Test { }; diff --git a/Tests/APRSUtils/dstarCallsignToAPRS.cpp b/Tests/APRSUtils/dstarCallsignToAPRS.cpp new file mode 100644 index 0000000..4f8cdcb --- /dev/null +++ b/Tests/APRSUtils/dstarCallsignToAPRS.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2021-2023 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include + +#include "APRSUtils.h" + +namespace APRSUtilsTests +{ + class APRSUtils_dstarCallsignToAPRS : public ::testing::Test { + }; + + TEST_F(APRSUtils_dstarCallsignToAPRS, noBlanks) + { + std::string call("N0CALL"); + CAPRSUtils::dstarCallsignToAPRS(call); + + EXPECT_STRCASEEQ(call.c_str(), "N0CALL") << "Call shall not have changed"; + } + + TEST_F(APRSUtils_dstarCallsignToAPRS, twoBlanks) + { + std::string call("F4ABC B"); + CAPRSUtils::dstarCallsignToAPRS(call); + + EXPECT_STRCASEEQ(call.c_str(), "F4ABC-B") << "-H shall have been removed"; + } + + TEST_F(APRSUtils_dstarCallsignToAPRS, threeBlanks) + { + std::string call("F4AB B"); + CAPRSUtils::dstarCallsignToAPRS(call); + + EXPECT_STRCASEEQ(call.c_str(), "F4AB-B") << "-H shall have been removed"; + } +} \ No newline at end of file diff --git a/Tests/HeardData/setIcomRepeaterData.cpp b/Tests/HeardData/setIcomRepeaterData.cpp new file mode 100644 index 0000000..ca8315b --- /dev/null +++ b/Tests/HeardData/setIcomRepeaterData.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2021-2023 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include + +#include "HeardData.h" + +namespace HeardDataTests +{ + class HeardData_setIcomRepeaterData: public ::testing::Test { + + }; + + TEST_F(HeardData_setIcomRepeaterData, setCorrectValues) + { + CHeardData hdata; + + in_addr addr; + addr.s_addr=123; + + std::string data("0123456789abcdefghijklmnopqrstuvwxyz"); + + hdata.setIcomRepeaterData((const unsigned char*)data.c_str(), data.length(), addr, 456U); + + EXPECT_STREQ(hdata.getUser().c_str(), "abcdefgh") << "User not correctly set"; + EXPECT_STREQ(hdata.getRepeater().c_str(), "ijklmnop") << "Repeater not correctly set"; + EXPECT_EQ(hdata.getAddress().s_addr, 123) << "Addr not correctly set"; + EXPECT_EQ(hdata.getPort(), 456) << "Port not correctly set"; + } +} \ No newline at end of file diff --git a/Tests/NMEASentenceCollector/getData.cpp b/Tests/NMEASentenceCollector/getData.cpp new file mode 100644 index 0000000..1b6eb32 --- /dev/null +++ b/Tests/NMEASentenceCollector/getData.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2021-2023 by Geoffrey Merck F4FXL / KC3FRA + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include + +#include "NMEASentenceCollector.h" + +namespace NMEASentenceCollectorTests +{ + class NMEASentenceCollectorTests_getData: public ::testing::Test + { + protected: + CNMEASentenceCollector * m_collector; + + void SetUp() override + { + m_collector = new CNMEASentenceCollector("$GPRMC"); + + std::string nmea("$GPRMC,092751.000,A,5321.6802,N,00630.3371,W,0.06,31.66,280511,,,A*45\x0A"); + + unsigned char data[3]; + unsigned int len = nmea.length() + (nmea.length() % 3); + bool first = true; + + for (unsigned int i = 0; i < len;) { + if(first) { + data[0] = SLOW_DATA_TYPE_GPS ^ SCRAMBLER_BYTE1; + first = false; + } + else { + data[0] = (i < nmea.length() ? nmea[i] : 'f') ^ SCRAMBLER_BYTE1; + i++; + first = true; + } + data[1] = (i < nmea.length() ? nmea[i] : 'f') ^ SCRAMBLER_BYTE2; + i++; + data[2] = (i < nmea.length() ? nmea[i] : 'f') ^ SCRAMBLER_BYTE3; + i++; + + m_collector->writeData(data); + } + + } + + void TearDown() override + { + delete m_collector; + } + }; + + TEST_F(NMEASentenceCollectorTests_getData, noSSIDinCallsign) + { + std::string data; + + m_collector->setMyCall1("N0CALL"); + m_collector->setMyCall2("5100"); + m_collector->getData(data); + + EXPECT_STREQ(data.c_str(), "N0CALL-5>GPS30,DSTAR*:$GPRMC,092751.000,A,5321.6802,N,00630.3371,W,0.06,31.66,280511,,,A*45"); + } + + TEST_F(NMEASentenceCollectorTests_getData, SSIDinCallsign) + { + std::string data; + + m_collector->setMyCall1("N0CALL H"); + m_collector->setMyCall2("5100"); + m_collector->getData(data); + + EXPECT_STREQ(data.c_str(), "N0CALL-5>GPS30,DSTAR*:$GPRMC,092751.000,A,5321.6802,N,00630.3371,W,0.06,31.66,280511,,,A*45"); + } +} \ No newline at end of file diff --git a/VersionInfo/Version.h b/VersionInfo/Version.h index 51e465d..87a9775 100644 --- a/VersionInfo/Version.h +++ b/VersionInfo/Version.h @@ -26,7 +26,7 @@ const std::string PRODUCT_NAME("DStarGateway"); const std::string VENDOR_NAME("Geoffrey Merck F4FXL / KC3FRA and Contributors"); -const std::string VERSION("0.6"); +const std::string VERSION("0.7"); const std::string LONG_VERSION = VERSION + "-" + gitversion; const std::string FULL_PRODUCT_NAME = PRODUCT_NAME + " v" + VERSION + "-" + gitversion; const std::string SHORT_PRODUCT_NAME = "DStarGW v" + VERSION + "-" + gitversion; diff --git a/debian/dstargateway.service b/debian/dstargateway.service index d3981ee..c853f44 100644 --- a/debian/dstargateway.service +++ b/debian/dstargateway.service @@ -6,6 +6,7 @@ Wants=network-online.target [Service] User=dstar Type=simple +ExecStartPre=/bin/bash -c 'until host google.com; do sleep 1; done' ExecStart=/usr/local/bin/dstargateway %CFG_DIR%/dstargateway.cfg Restart=on-failure RestartSec=5