diff --git a/APRSEntry.cpp b/APRSEntry.cpp index a9fbdcf..2d1c3d3 100644 --- a/APRSEntry.cpp +++ b/APRSEntry.cpp @@ -32,7 +32,8 @@ m_longitude(longitude), m_agl(agl), m_timer(1000U, 10U), m_first(true), -m_collector(NULL) +m_collector(NULL), +m_linkStatus() { boost::trim(m_callsign); @@ -89,6 +90,11 @@ CAPRSCollector* CAPRSEntry::getCollector() const return m_collector; } +CAPRSEntryStatus& CAPRSEntry::getStatus() +{ + return m_linkStatus; +} + void CAPRSEntry::reset() { m_first = true; @@ -98,6 +104,7 @@ void CAPRSEntry::reset() void CAPRSEntry::clock(unsigned int ms) { + m_linkStatus.clock(ms); m_timer.clock(ms); } diff --git a/APRSEntry.h b/APRSEntry.h index 3ad5484..e9bc5af 100644 --- a/APRSEntry.h +++ b/APRSEntry.h @@ -22,7 +22,9 @@ #include #include "APRSCollector.h" +#include "APRSEntryStatus.h" #include "Timer.h" +#include "Defs.h" class CAPRSEntry { public: @@ -38,6 +40,7 @@ public: double getLongitude() const; double getAGL() const; CAPRSCollector* getCollector() const; + CAPRSEntryStatus& getStatus(); // Transmission timer void reset(); @@ -56,4 +59,5 @@ private: CTimer m_timer; bool m_first; CAPRSCollector* m_collector; + CAPRSEntryStatus m_linkStatus; }; diff --git a/APRSEntryStatus.cpp b/APRSEntryStatus.cpp new file mode 100644 index 0000000..e16200d --- /dev/null +++ b/APRSEntryStatus.cpp @@ -0,0 +1,54 @@ +/* + * 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 "APRSEntryStatus.h" + +CAPRSEntryStatus::CAPRSEntryStatus() : +m_status(), +m_statusChanged(false), +m_timer(1000U) +{ + m_timer.setTimeout(20U * 60U); //statuses go out every 20 minutes or every change +} + +bool CAPRSEntryStatus::isOutOfDate() +{ + bool ouOfDate = m_statusChanged || m_timer.hasExpired(); + m_statusChanged = false; + return ouOfDate; +} + + +std::string CAPRSEntryStatus::getStatus() const +{ + return m_status; +} + +void CAPRSEntryStatus::setStatus(const std::string& linkstatus) +{ + bool changed = m_status != linkstatus; + if(changed) { + m_status = linkstatus; + } + m_statusChanged = changed; +} + +void CAPRSEntryStatus::clock(unsigned int ms) +{ + m_timer.clock(ms); +} \ No newline at end of file diff --git a/APRSEntryStatus.h b/APRSEntryStatus.h new file mode 100644 index 0000000..da795a2 --- /dev/null +++ b/APRSEntryStatus.h @@ -0,0 +1,39 @@ +/* + * 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 + +#include "Timer.h" + +class CAPRSEntryStatus +{ +public: + CAPRSEntryStatus() ; + + std::string getStatus() const; + bool isOutOfDate(); + void setStatus(const std::string& destination); + void clock(unsigned int ms); + +private: + std::string m_status; + bool m_statusChanged; + CTimer m_timer; +}; \ No newline at end of file diff --git a/APRSFixedIdFrameProvider.cpp b/APRSFixedIdFrameProvider.cpp index 80079fb..c75cbe9 100644 --- a/APRSFixedIdFrameProvider.cpp +++ b/APRSFixedIdFrameProvider.cpp @@ -127,7 +127,7 @@ bool CAPRSFixedIdFrameProvider::buildAPRSFramesInt(const std::string& gateway, c frames.push_back(output); } - setTimeout(20U * 60);//20 minutes, plenty enough for fixed + setTimeout(20U * 60U);//20 minutes, plenty enough for fixed return true; } \ No newline at end of file diff --git a/APRSWriter.cpp b/APRSWriter.cpp index 12d0272..57ea6e8 100644 --- a/APRSWriter.cpp +++ b/APRSWriter.cpp @@ -34,7 +34,8 @@ m_thread(NULL), m_gateway(), m_address(), m_port(0U), -m_array() +m_array(), +m_idFrameProvider(nullptr) { assert(!hostname.empty()); assert(port > 0U); @@ -157,28 +158,70 @@ void CAPRSWriter::writeData(const std::string& callsign, const CAMBEData& data) collector->reset(); } +void CAPRSWriter::writeStatus(const std::string& callsign, const std::string status) +{ + CAPRSEntry* entry = m_array[callsign]; + if (entry == NULL) { + CLog::logError("Cannot find the callsign \"%s\" in the APRS array", callsign.c_str()); + return; + } + + entry->getStatus().setStatus(status); +} + void CAPRSWriter::clock(unsigned int ms) { m_thread->clock(ms); if(m_idFrameProvider != nullptr) { m_idFrameProvider->clock(ms); + if(m_idFrameProvider->wantsToSend()) { + sendIdFrames(); + } + } - if(m_idFrameProvider->wantsToSend() && m_thread->isConnected()) { - for(auto entry : m_array) { - std::vector frames; - if(m_idFrameProvider->buildAPRSFrames(m_gateway, entry.second, frames)) { - for(auto frame : frames) { - m_thread->write(frame.c_str()); - } - } - } + for (auto it : m_array) { + if(it.second != NULL) { + it.second->clock(ms); + if(it.second->getStatus().isOutOfDate()) + sendStatusFrame(it.second); } } +} - for (auto it = m_array.begin(); it != m_array.end(); ++it) { - if(it->second != NULL) - it->second->clock(ms); +void CAPRSWriter::sendStatusFrame(CAPRSEntry * entry) +{ + assert(entry != nullptr); + + if(!m_thread->isConnected()) + return; + + auto linkStatus = entry->getStatus(); + std::string body = boost::trim_copy(linkStatus.getStatus()); + + if(body[0] != '>') + body = '>' + body; + + std::string output = CStringUtils::string_format("%s-%s>APD5T3,TCPIP*,qAC,%s-%sS:%s\r\n", + entry->getCallsign().c_str(), entry->getBand().c_str(), entry->getCallsign().c_str(), entry->getBand().c_str(), + body.c_str()); + + m_thread->write(output.c_str()); + +} + +void CAPRSWriter::sendIdFrames() +{ + if(m_thread->isConnected()) + { + for(auto entry : m_array) { + std::vector frames; + if(m_idFrameProvider->buildAPRSFrames(m_gateway, entry.second, frames)) { + for(auto frame : frames) { + m_thread->write(frame.c_str()); + } + } + } } } diff --git a/APRSWriter.h b/APRSWriter.h index b49adcf..3543930 100644 --- a/APRSWriter.h +++ b/APRSWriter.h @@ -51,6 +51,8 @@ public: void writeData(const std::string& callsign, const CAMBEData& data); + void writeStatus(const std::string& callsign, const std::string status); + bool isConnected() const; void clock(unsigned int ms); @@ -65,7 +67,8 @@ private: std::unordered_map m_array; CAPRSIdFrameProvider * m_idFrameProvider; - void sendIdFramesMobile(); + void sendIdFrames(); + void sendStatusFrame(CAPRSEntry * entrry); }; #endif diff --git a/README.md b/README.md index f58ca15..e22f1a5 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ - [3.3. Get latest development version version](#33-get-latest-development-version-version) - [3.4. Prerequisites and dependencies](#34-prerequisites-and-dependencies) - [3.5. Building](#35-building) - - [Build With GPSD Support](#build-with-gpsd-support) + - [3.5.0.1. Build With GPSD Support](#3501-build-with-gpsd-support) - [3.6. Installing](#36-installing) - [3.7. Configuring](#37-configuring) - [4. Contributing](#4-contributing) @@ -19,18 +19,19 @@ - [5.2. Version 0.3](#52-version-03) - [5.3. Version 0.2](#53-version-02) - [5.4. Version 0.1](#54-version-01) +- [6. Future](#6-future) # 1. Introduction -This is a port of G4KLX Jonathan Naylor's [ircddbGateway](https://github.com/g4klx/ircDDBGateway). It is wxWidgets free and has minimal dependencies to boost (header libs only) and libcurl +This is a port of G4KLX Jonathan Naylor's [ircddbGateway](https://github.com/g4klx/ircDDBGateway). It is wxWidgets free and has minimal dependencies to boost (header libs only) and libcurl. I plan to ad some features in the future # 2. Current State ## 2.1. Code sanity -The current code is working, yet ugly IMHO as it is a mix of C and C++ of various ages. +The current code is working, yet ugly IMHO as it is a mix of C and C++ of various ages. I realised that G4KLx started programming this over a decade ago, when C++11 was not yet a thing ! The code has also been amended to no longer rely on compiler defines for paths like log or data. These can be set in configuration file. -Quite a few classes are more or less copy/paste from each other some sanitization by using base classes or template classes would greatly improve code maintainibility. +Quite a few classes are more or less copy/paste from each other some sanitization by using base classes or template classes would greatly improve code maintainibility. Maybe one day ;) ## 2.2. Code Credit - Jonathan Naylor G4KLX (The original author of [ircddbGateway](https://github.com/g4klx/ircDDBGateway)) - Thomas A. Early N7TAE (Code taken from his [smart-group](https://github.com/n7tae/smart-group-server) software) @@ -41,10 +42,10 @@ All the features found in ircddbGateway are supposed to be working. I have mixed Features that where left out : - CCS: is still being used? I always considered this as trojan horse to push some DMR Agenda into DStar an more or les a burdain to use. Call sign routing is by far more flexible and superior. - Starnet: You might consider running [Smart Group Server XL](https://github.com/F4FXL/smart-group-server-xl) from a dedicated computer instead. -- Announcement: same can be achieved using transmitd. +- Announcement: same can be achieved using VoiceTransmit. - APRSGateway capability: I would prefer to have some sort of TCP "APRS-IS proxy" program sitting between the program and the APRS server, thus keeping the ability to directly connect to APRS-IS or not, depending on the system owner wish. I run mostly DStar Only repeaters, having an additional program to maintain is unnecessary burden. -- DRats : No opinion on this one, I am not using it. -- CallSign Server : this is a legacy from the dead project xreflector.net, I will most probably drop it. +- DRats : Is on the to-do list see [#6](#6) +- CallSign Server : this is a legacy from the dead project xreflector.net, I will most probably drop it for good. # 3. Building and installing ## 3.1. Initial setup @@ -80,7 +81,7 @@ Regular building ``` make ``` -#### Build With GPSD Support +#### 3.5.0.1. Build With GPSD Support ``` make USE_GPS=1 ``` @@ -104,10 +105,13 @@ sudo systemctl stop dstargateway.service ``` # 4. Contributing ## 4.1. Work Flow -I Use [Git flow](https://danielkummer.github.io/git-flow-cheatsheet/) as my workflow. PR are welcome and shall be done against the develop branch and follow the Git Flow branch naming rules. - +I Use [Git flow](https://danielkummer.github.io/git-flow-cheatsheet/) as my workflow. PR are welcome but pleasee observe following rules : +- You have tested your code thoroughly +- Compilation produces no warnings +- Code formating rules are observed (these are very lousy though) # 5. Version History ## 5.1. 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 (https://github.com/F4FXL/DStarGateway/issues/6) - [Improvement] Log enhancements ([#4])(https://github.com/F4FXL/DStarGateway/issues/4) @@ -117,4 +121,16 @@ I Use [Git flow](https://danielkummer.github.io/git-flow-cheatsheet/) as my work - [Bugfix] ircDDBFreeze when repeater not found ([#1](https://github.com/F4FXL/DStarGateway/issues/1)) - Code sanitization ## 5.4. Version 0.1 -First working version \ No newline at end of file +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 : +- ☒ Better NatTraversal + - No banging on every gateway: use ircDDB (or something else) as mitigation server to notify peer + - Support for all protocols (G2, DExtra, DCS, REF) + - A [branch](https://github.com/F4FXL/DStarGateway/tree/feature/NatTraversal) already exists for this +- ☑ Send the connection status as to APRS as a status frame +- ☒ Reinstantiate DRATS +- ☒ Migrate all the "accessories" (VoiceTransmit, RemoteControl ...) +- ☒ Automatic refresh of host files +- ☒ Reduce ircDDB dependency, build something more P2P, maybe based on [Distributed Hashtable](https://github.com/DavidKeller/kademlia) ? +- Everything that might come handy to make dstar the most powerful system ever :) \ No newline at end of file diff --git a/RepeaterHandler.cpp b/RepeaterHandler.cpp index c64adb4..13635b3 100644 --- a/RepeaterHandler.cpp +++ b/RepeaterHandler.cpp @@ -1594,7 +1594,6 @@ void CRepeaterHandler::clockInt(unsigned int ms) m_starNet = NULL; break; #endif - case G2_ECHO: m_echo->end(); break; @@ -2564,6 +2563,9 @@ 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); + #ifdef USE_CCS m_ccsHandler->setReflector(); #endif @@ -2616,6 +2618,9 @@ 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); + #ifdef USE_CCS m_ccsHandler->setReflector(callsign); #endif @@ -2668,6 +2673,9 @@ void CRepeaterHandler::writeNotLinked() m_infoAudio->setStatus(m_linkStatus, m_linkRepeater, text); triggerInfo(); + if(m_aprsWriter != nullptr) + m_aprsWriter->writeStatus(m_rptCallsign, text); + #ifdef USE_CCS m_ccsHandler->setReflector(); #endif @@ -2736,6 +2744,9 @@ 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); + #ifdef USE_CCS m_ccsHandler->setReflector(); #endif