diff --git a/APRSEntry.cpp b/APRSEntry.cpp new file mode 100644 index 0000000..2d1c3d3 --- /dev/null +++ b/APRSEntry.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2010,2011,2012,2018 by Jonathan Naylor G4KLX + * Copyright (C) 2021 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 "APRSEntry.h" + +CAPRSEntry::CAPRSEntry(const std::string& callsign, const std::string& band, double frequency, double offset, double range, double latitude, double longitude, double agl) : +m_callsign(callsign), +m_band(band), +m_frequency(frequency), +m_offset(offset), +m_range(range), +m_latitude(latitude), +m_longitude(longitude), +m_agl(agl), +m_timer(1000U, 10U), +m_first(true), +m_collector(NULL), +m_linkStatus() +{ + boost::trim(m_callsign); + + m_collector = new CAPRSCollector; +} + +CAPRSEntry::~CAPRSEntry() +{ + delete m_collector; +} + +std::string CAPRSEntry::getCallsign() const +{ + return m_callsign; +} + +std::string CAPRSEntry::getBand() const +{ + return m_band; +} + +double CAPRSEntry::getFrequency() const +{ + return m_frequency; +} + +double CAPRSEntry::getOffset() const +{ + return m_offset; +} + +double CAPRSEntry::getRange() const +{ + return m_range; +} + +double CAPRSEntry::getLatitude() const +{ + return m_latitude; +} + +double CAPRSEntry::getLongitude() const +{ + return m_longitude; +} + +double CAPRSEntry::getAGL() const +{ + return m_agl; +} + +CAPRSCollector* CAPRSEntry::getCollector() const +{ + return m_collector; +} + +CAPRSEntryStatus& CAPRSEntry::getStatus() +{ + return m_linkStatus; +} + +void CAPRSEntry::reset() +{ + m_first = true; + m_timer.stop(); + m_collector->reset(); +} + +void CAPRSEntry::clock(unsigned int ms) +{ + m_linkStatus.clock(ms); + m_timer.clock(ms); +} + +bool CAPRSEntry::isOK() +{ + if (m_first) { + m_first = false; + m_timer.start(); + return true; + } + + if (m_timer.hasExpired()) { + m_timer.start(); + return true; + } else { + m_timer.start(); + return false; + } +} \ No newline at end of file diff --git a/APRSEntry.h b/APRSEntry.h new file mode 100644 index 0000000..e9bc5af --- /dev/null +++ b/APRSEntry.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2010,2011,2012,2018 by Jonathan Naylor G4KLX + * Copyright (C) 2021 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 "APRSCollector.h" +#include "APRSEntryStatus.h" +#include "Timer.h" +#include "Defs.h" + +class CAPRSEntry { +public: + CAPRSEntry(const std::string& callsign, const std::string& band, double frequency, double offset, double range, double latitude, double longitude, double agl); + ~CAPRSEntry(); + + std::string getCallsign() const; + std::string getBand() const; + double getFrequency() const; + double getOffset() const; + double getRange() const; + double getLatitude() const; + double getLongitude() const; + double getAGL() const; + CAPRSCollector* getCollector() const; + CAPRSEntryStatus& getStatus(); + + // Transmission timer + void reset(); + void clock(unsigned int ms); + bool isOK(); + +private: + std::string m_callsign; + std::string m_band; + double m_frequency; + double m_offset; + double m_range; + double m_latitude; + double m_longitude; + double m_agl; + 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 new file mode 100644 index 0000000..c75cbe9 --- /dev/null +++ b/APRSFixedIdFrameProvider.cpp @@ -0,0 +1,133 @@ +/* + * 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 +#include +#include + +#include "APRSFixedIdFrameProvider.h" +#include "StringUtils.h" + +CAPRSFixedIdFrameProvider::CAPRSFixedIdFrameProvider() : +CAPRSIdFrameProvider(20U) // Initial timeout of 20 seconds +{ + +} + +bool CAPRSFixedIdFrameProvider::buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * entry, std::vector& frames) +{ + if (entry == nullptr) + return false; + + // Default values aren't passed on + if (entry->getLatitude() == 0.0 && entry->getLongitude() == 0.0) + return false; + + frames.clear(); + + time_t now; + ::time(&now); + struct tm* tm = ::gmtime(&now); + + std::string desc; + if (entry->getBand().length() > 1U) { + if (entry->getFrequency() != 0.0) + desc = CStringUtils::string_format("Data %.5lfMHz", entry->getFrequency()); + else + desc = "Data"; + } else { + if (entry->getFrequency() != 0.0) + desc = CStringUtils::string_format("Voice %.5lfMHz %c%.4lfMHz", + entry->getFrequency(), + entry->getOffset() < 0.0 ? '-' : '+', + std::fabs(entry->getOffset())); + else + desc = "Voice"; + } + + std::string band; + if (entry->getFrequency() >= 1200.0) + band = "1.2"; + else if (entry->getFrequency() >= 420.0) + band = "440"; + else if (entry->getFrequency() >= 144.0) + band = "2m"; + else if (entry->getFrequency() >= 50.0) + band = "6m"; + else if (entry->getFrequency() >= 28.0) + band = "10m"; + + double tempLat = ::fabs(entry->getLatitude()); + double tempLong = ::fabs(entry->getLongitude()); + + double latitude = ::floor(tempLat); + double longitude = ::floor(tempLong); + + latitude = (tempLat - latitude) * 60.0 + latitude * 100.0; + longitude = (tempLong - longitude) * 60.0 + longitude * 100.0; + + std::string lat; + if (latitude >= 1000.0F) + lat = CStringUtils::string_format("%.2lf", latitude); + else if (latitude >= 100.0F) + lat = CStringUtils::string_format("0%.2lf", latitude); + else if (latitude >= 10.0F) + lat = CStringUtils::string_format("00%.2lf", latitude); + else + lat = CStringUtils::string_format("000%.2lf", latitude); + + std::string lon; + if (longitude >= 10000.0F) + lon = CStringUtils::string_format("%.2lf", longitude); + else if (longitude >= 1000.0F) + lon = CStringUtils::string_format("0%.2lf", longitude); + else if (longitude >= 100.0F) + lon = CStringUtils::string_format("00%.2lf", longitude); + else if (longitude >= 10.0F) + lon = CStringUtils::string_format("000%.2lf", longitude); + else + lon = CStringUtils::string_format("0000%.2lf", longitude); + + // Convert commas to periods in the latitude and longitude + boost::replace_all(lat, ",", "."); + boost::replace_all(lon, ",", "."); + + std::string output = CStringUtils::string_format("%s-S>APD5T1,TCPIP*,qAC,%s-GS:;%-7s%-2s*%02d%02d%02dz%s%cD%s%caRNG%04.0lf/A=%06.0lf %s %s\r\n", + gateway.c_str(), gateway.c_str(), entry->getCallsign().c_str(), entry->getBand().c_str(), + tm->tm_mday, tm->tm_hour, tm->tm_min, + lat.c_str(), (entry->getLatitude() < 0.0F) ? 'S' : 'N', + lon.c_str(), (entry->getLongitude() < 0.0F) ? 'W' : 'E', + entry->getRange() * 0.6214, entry->getAGL() * 3.28, band.c_str(), desc.c_str()); + + + frames.push_back(output); + + if (entry->getBand().length() == 1U) { + output = CStringUtils::string_format("%s-%s>APD5T2,TCPIP*,qAC,%s-%sS:!%s%cD%s%c&RNG%04.0lf/A=%06.0lf %s %s\r\n", + entry->getCallsign().c_str(), entry->getBand().c_str(), entry->getCallsign().c_str(), entry->getBand().c_str(), + lat.c_str(), (entry->getLatitude() < 0.0F) ? 'S' : 'N', + lon.c_str(), (entry->getLongitude() < 0.0F) ? 'W' : 'E', + entry->getRange() * 0.6214, entry->getAGL() * 3.28, band.c_str(), desc.c_str()); + + frames.push_back(output); + } + + setTimeout(20U * 60U);//20 minutes, plenty enough for fixed + + return true; +} \ No newline at end of file diff --git a/APRSFixedIdFrameProvider.h b/APRSFixedIdFrameProvider.h new file mode 100644 index 0000000..d3bbc3d --- /dev/null +++ b/APRSFixedIdFrameProvider.h @@ -0,0 +1,30 @@ +/* + * 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 "APRSIdFrameProvider.h" + +class CAPRSFixedIdFrameProvider : public CAPRSIdFrameProvider +{ +public: + CAPRSFixedIdFrameProvider(); + +protected: + virtual bool buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * aprsEntry, std::vector& frames); +}; diff --git a/APRSGPSDIdFrameProvider.cpp b/APRSGPSDIdFrameProvider.cpp new file mode 100644 index 0000000..3a02916 --- /dev/null +++ b/APRSGPSDIdFrameProvider.cpp @@ -0,0 +1,212 @@ +/* + * 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. + */ + +#ifdef USE_DGPS +#include +#include + +#include "APRSGPSDIdFrameProvider.h" +#include "StringUtils.h" +#include "Log.h" + +CAPRSGPSDIdFrameProvider::CAPRSGPSDIdFrameProvider(std::string address, std::string port) : +CAPRSIdFrameProvider(20U), +m_gpsdAddress(address), +m_gpsdPort(port), +m_gpsdData(), +m_hasConnection(false) +{ + +} + +void CAPRSGPSDIdFrameProvider::start() +{ + int ret = ::gps_open(m_gpsdAddress.c_str(), m_gpsdPort.c_str(), &m_gpsdData); + if (ret != 0) { + CLog::logError("Error when opening access to gpsd - %d - %s", errno, ::gps_errstr(errno)); + m_hasConnection = false; + } + else { + ::gps_stream(&m_gpsdData, WATCH_ENABLE | WATCH_JSON, NULL); + CLog::logError("Connected to GPSD"); + m_hasConnection = true; + } +} + +void CAPRSGPSDIdFrameProvider::close() +{ + if(m_hasConnection) { + ::gps_stream(&m_gpsdData, WATCH_DISABLE, NULL); + ::gps_close(&m_gpsdData); + } +} + +bool CAPRSGPSDIdFrameProvider::buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * entry, std::vector& frames) +{ + if(!m_hasConnection) { + this->start(); + } + + if(!m_hasConnection || !::gps_waiting(&m_gpsdData, 0)) + return false; + +#if GPSD_API_MAJOR_VERSION >= 7 + char message[1024]; + if (::gps_read(&m_gpsdData, message, 1024) <= 0) + return false; +#else + if (::gps_read(&m_gpsdData) <= 0) + return false; +#endif + +#if GPSD_API_MAJOR_VERSION >= 10 + if(m_gpsdData.fix.status == STATUS_NO_FIX) + return false; +#else + if (m_gpsdData.status != STATUS_FIX) + return false; +#endif + + bool latlonSet = (m_gpsdData.set & LATLON_SET) == LATLON_SET; + bool altitudeSet = (m_gpsdData.set & ALTITUDE_SET) == ALTITUDE_SET; + bool velocitySet = (m_gpsdData.set & SPEED_SET) == SPEED_SET; + bool bearingSet = (m_gpsdData.set & TRACK_SET) == TRACK_SET; + + if (!latlonSet) + return false; + + float rawLatitude = float(m_gpsdData.fix.latitude); + float rawLongitude = float(m_gpsdData.fix.longitude); +#if GPSD_API_MAJOR_VERSION >= 9 + float rawAltitude = float(m_gpsdData.fix.altMSL); +#else + float rawAltitude = float(m_gpsdData.fix.altitude); +#endif + float rawVelocity = float(m_gpsdData.fix.speed); + float rawBearing = float(m_gpsdData.fix.track); + + time_t now; + ::time(&now); + struct tm* tm = ::gmtime(&now); + if (entry == NULL) + return false; + + std::string desc; + if (entry->getBand().length() > 1U) { + if (entry->getFrequency() != 0.0) + desc = CStringUtils::string_format("Data %.5lfMHz", entry->getFrequency()); + else + desc = "Data"; + } else { + if (entry->getFrequency() != 0.0) + desc = CStringUtils::string_format("Voice %.5lfMHz %c%.4lfMHz", + entry->getFrequency(), + entry->getOffset() < 0.0 ? '-' : '+', + ::fabs(entry->getOffset())); + else + desc = "Voice"; + } + + std::string band; + if (entry->getFrequency() >= 1200.0) + band = "1.2"; + else if (entry->getFrequency() >= 420.0) + band = "440"; + else if (entry->getFrequency() >= 144.0) + band = "2m"; + else if (entry->getFrequency() >= 50.0) + band = "6m"; + else if (entry->getFrequency() >= 28.0) + band = "10m"; + + double tempLat = ::fabs(rawLatitude); + double tempLong = ::fabs(rawLongitude); + + double latitude = ::floor(tempLat); + double longitude = ::floor(tempLong); + + latitude = (tempLat - latitude) * 60.0 + latitude * 100.0; + longitude = (tempLong - longitude) * 60.0 + longitude * 100.0; + + std::string lat; + if (latitude >= 1000.0F) + lat = CStringUtils::string_format("%.2lf", latitude); + else if (latitude >= 100.0F) + lat = CStringUtils::string_format("0%.2lf", latitude); + else if (latitude >= 10.0F) + lat = CStringUtils::string_format("00%.2lf", latitude); + else + lat = CStringUtils::string_format("000%.2lf", latitude); + + std::string lon; + if (longitude >= 10000.0F) + lon = CStringUtils::string_format("%.2lf", longitude); + else if (longitude >= 1000.0F) + lon = CStringUtils::string_format("0%.2lf", longitude); + else if (longitude >= 100.0F) + lon = CStringUtils::string_format("00%.2lf", longitude); + else if (longitude >= 10.0F) + lon = CStringUtils::string_format("000%.2lf", longitude); + else + lon = CStringUtils::string_format("0000%.2lf", longitude); + + // Convert commas to periods in the latitude and longitude + boost::replace_all(lat, ",", "."); + boost::replace_all(lon, ",", "."); + + std::string output1 = CStringUtils::string_format("%s-S>APD5T1,TCPIP*,qAC,%s-GS:;%-7s%-2s*%02d%02d%02dz%s%cD%s%ca/A=%06.0lf", + gateway.c_str(), gateway.c_str(), entry->getCallsign().c_str(), entry->getBand().c_str(), + tm->tm_mday, tm->tm_hour, tm->tm_min, + lat.c_str(), (rawLatitude < 0.0) ? 'S' : 'N', + lon.c_str(), (rawLongitude < 0.0) ? 'W' : 'E', + rawAltitude * 3.28); + + std::string output2; + if (bearingSet && velocitySet) + output2 = CStringUtils::string_format("%03.0lf/%03.0lf", rawBearing, rawVelocity * 0.539957F); + + std::string output3; + output3 = CStringUtils::string_format("RNG%04.0lf %s %s\r\n", entry->getRange() * 0.6214, band.c_str(), desc.c_str()); + + CLog::logDebug("APRS ==> %s%s%s", output1.c_str(), output2.c_str(), output3.c_str()); + + frames.push_back(output1.append(output2).append(output3)); + + if (entry->getBand().length() == 1U) { + if (altitudeSet) + output1 = CStringUtils::string_format("%s-%s>APD5T2,TCPIP*,qAC,%s-%sS:!%s%cD%s%c&/A=%06.0lf", + entry->getCallsign().c_str(), entry->getBand().c_str(), entry->getCallsign().c_str(), entry->getBand().c_str(), + lat.c_str(), (rawLatitude < 0.0) ? 'S' : 'N', + lon.c_str(), (rawLongitude < 0.0) ? 'W' : 'E', + rawAltitude * 3.28); + else + output1 = CStringUtils::string_format("%s-%s>APD5T2,TCPIP*,qAC,%s-%sS:!%s%cD%s%c&", + entry->getCallsign().c_str(), entry->getBand().c_str(), entry->getCallsign().c_str(), entry->getBand().c_str(), + lat.c_str(), (rawLatitude < 0.0) ? 'S' : 'N', + lon.c_str(), (rawLongitude < 0.0) ? 'W' : 'E'); + + CLog::logDebug("APRS ==> %s%s%s", output1.c_str(), output2.c_str(), output3.c_str()); + + frames.push_back(output1.append(output2).append(output3)); + } + + setTimeout(60U * 5U);//5 Minutes is plenty enough we aint an APRS tracker ! + + return true; +} +#endif \ No newline at end of file diff --git a/APRSGPSDIdFrameProvider.h b/APRSGPSDIdFrameProvider.h new file mode 100644 index 0000000..77ca4b9 --- /dev/null +++ b/APRSGPSDIdFrameProvider.h @@ -0,0 +1,46 @@ +/* + * 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 "Defs.h" + +#ifdef USE_GPSD +#include +#include + +#include "APRSIdFrameProvider.h" + +class CAPRSGPSDIdFrameProvider : public CAPRSIdFrameProvider +{ +public: + CAPRSGPSDIdFrameProvider(std::string address, std::string port); + + virtual void start(); + virtual void close(); + +protected: + virtual bool buildAPRSFramesInt(const std::string& gateway, const CAPRSEntry * aprsEntry, std::vector& frames); + +private: + std::string m_gpsdAddress; + std::string m_gpsdPort; + struct gps_data_t m_gpsdData; + bool m_hasConnection; +}; +#endif diff --git a/APRSIdFrameProvider.cpp b/APRSIdFrameProvider.cpp new file mode 100644 index 0000000..503720a --- /dev/null +++ b/APRSIdFrameProvider.cpp @@ -0,0 +1,50 @@ +/* + * 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 + +#include "APRSIdFrameProvider.h" + +CAPRSIdFrameProvider::CAPRSIdFrameProvider(unsigned int timeout) : +m_timer(1000U) +{ + m_timer.start(timeout); +} + +CAPRSIdFrameProvider::~CAPRSIdFrameProvider() +{ + +} + +bool CAPRSIdFrameProvider::buildAPRSFrames(const std::string& gateway, const CAPRSEntry * entry, std::vector & frames) +{ + assert(entry != nullptr); + + return buildAPRSFramesInt(gateway, entry, frames); +} + +bool CAPRSIdFrameProvider::wantsToSend() +{ + if(m_timer.hasExpired()) + { + m_timer.start(); + return true; + } + + return false; +} diff --git a/APRSIdFrameProvider.h b/APRSIdFrameProvider.h new file mode 100644 index 0000000..f83deb7 --- /dev/null +++ b/APRSIdFrameProvider.h @@ -0,0 +1,48 @@ +/* + * 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" +#include "APRSEntry.h" + +class CAPRSIdFrameProvider +{ +public: + CAPRSIdFrameProvider(unsigned int timeOut); + virtual ~CAPRSIdFrameProvider(); + + bool buildAPRSFrames(const std::string& gateway, 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; + + void setTimeout(unsigned int timeout) + { + m_timer.start(timeout); + } + +private: + CTimer m_timer; +}; \ No newline at end of file diff --git a/APRSWriter.cpp b/APRSWriter.cpp index 63df80d..57ea6e8 100644 --- a/APRSWriter.cpp +++ b/APRSWriter.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include "StringUtils.h" #include "Log.h" @@ -28,111 +29,13 @@ #include "Defs.h" #include "Log.h" -CAPRSEntry::CAPRSEntry(const std::string& callsign, const std::string& band, double frequency, double offset, double range, double latitude, double longitude, double agl) : -m_callsign(callsign), -m_band(band), -m_frequency(frequency), -m_offset(offset), -m_range(range), -m_latitude(latitude), -m_longitude(longitude), -m_agl(agl), -m_timer(1000U, 10U), -m_first(true), -m_collector(NULL) -{ - boost::trim(m_callsign); - - m_collector = new CAPRSCollector; -} - -CAPRSEntry::~CAPRSEntry() -{ - delete m_collector; -} - -std::string CAPRSEntry::getCallsign() const -{ - return m_callsign; -} - -std::string CAPRSEntry::getBand() const -{ - return m_band; -} - -double CAPRSEntry::getFrequency() const -{ - return m_frequency; -} - -double CAPRSEntry::getOffset() const -{ - return m_offset; -} - -double CAPRSEntry::getRange() const -{ - return m_range; -} - -double CAPRSEntry::getLatitude() const -{ - return m_latitude; -} - -double CAPRSEntry::getLongitude() const -{ - return m_longitude; -} - -double CAPRSEntry::getAGL() const -{ - return m_agl; -} - -CAPRSCollector* CAPRSEntry::getCollector() const -{ - return m_collector; -} - -void CAPRSEntry::reset() -{ - m_first = true; - m_timer.stop(); - m_collector->reset(); -} - -void CAPRSEntry::clock(unsigned int ms) -{ - m_timer.clock(ms); -} - -bool CAPRSEntry::isOK() -{ - if (m_first) { - m_first = false; - m_timer.start(); - return true; - } - - if (m_timer.hasExpired()) { - m_timer.start(); - return true; - } else { - m_timer.start(); - return false; - } -} - CAPRSWriter::CAPRSWriter(const std::string& hostname, unsigned int port, const std::string& gateway, const std::string& password, const std::string& address) : m_thread(NULL), -m_idTimer(1000U), m_gateway(), m_address(), m_port(0U), -m_socket(NULL), -m_array() +m_array(), +m_idFrameProvider(nullptr) { assert(!hostname.empty()); assert(port > 0U); @@ -155,7 +58,7 @@ CAPRSWriter::~CAPRSWriter() m_array.clear(); } -void CAPRSWriter::setPortFixed(const std::string& callsign, const std::string& band, double frequency, double offset, double range, double latitude, double longitude, double agl) +void CAPRSWriter::setPort(const std::string& callsign, const std::string& band, double frequency, double offset, double range, double latitude, double longitude, double agl) { std::string temp = callsign; temp.resize(LONG_CALLSIGN_LENGTH - 1U, ' '); @@ -164,40 +67,8 @@ void CAPRSWriter::setPortFixed(const std::string& callsign, const std::string& b m_array[temp] = new CAPRSEntry(callsign, band, frequency, offset, range, latitude, longitude, agl); } -void CAPRSWriter::setPortMobile(const std::string& callsign, const std::string& band, double frequency, double offset, double range, const std::string& address, unsigned int port) -{ - std::string temp = callsign; - temp.resize(LONG_CALLSIGN_LENGTH - 1U, ' '); - temp += band; - - m_array[temp] = new CAPRSEntry(callsign, band, frequency, offset, range, 0.0, 0.0, 0.0); - - if (m_socket == NULL) { - m_address = CUDPReaderWriter::lookup(address); - m_port = port; - - m_socket = new CUDPReaderWriter(); - } -} - bool CAPRSWriter::open() { - if (m_socket != NULL) { - bool ret = m_socket->open(); - if (!ret) { - delete m_socket; - m_socket = NULL; - return false; - } - - // Poll the GPS every minute - m_idTimer.setTimeout(60U); - } else { - m_idTimer.setTimeout(20U * 60U); - } - - m_idTimer.start(); - return m_thread->start(); } @@ -287,316 +158,85 @@ void CAPRSWriter::writeData(const std::string& callsign, const CAMBEData& data) collector->reset(); } -void CAPRSWriter::clock(unsigned int ms) -{ - m_idTimer.clock(ms); - - m_thread->clock(ms); - - if (m_socket != NULL) { - if (m_idTimer.hasExpired()) { - pollGPS(); - m_idTimer.start(); - } - - sendIdFramesMobile(); - } else { - if (m_idTimer.hasExpired()) { - sendIdFramesFixed(); - m_idTimer.start(); - } - } - - for (auto it = m_array.begin(); it != m_array.end(); ++it) { - if(it->second != NULL) - it->second->clock(ms); - } -} - -bool CAPRSWriter::isConnected() const -{ - return m_thread->isConnected(); -} - -void CAPRSWriter::close() +void CAPRSWriter::writeStatus(const std::string& callsign, const std::string status) { - if (m_socket != NULL) { - m_socket->close(); - delete m_socket; + CAPRSEntry* entry = m_array[callsign]; + if (entry == NULL) { + CLog::logError("Cannot find the callsign \"%s\" in the APRS array", callsign.c_str()); + return; } - m_thread->stop(); + entry->getStatus().setStatus(status); } -bool CAPRSWriter::pollGPS() -{ - assert(m_socket != NULL); - - return m_socket->write((unsigned char*)"ircDDBGateway", 13U, m_address, m_port); -} - -void CAPRSWriter::sendIdFramesFixed() +void CAPRSWriter::clock(unsigned int ms) { - if (!m_thread->isConnected()) - return; + m_thread->clock(ms); - time_t now; - ::time(&now); - struct tm* tm = ::gmtime(&now); - - for (auto it = m_array.begin(); it != m_array.end(); ++it) { - CAPRSEntry* entry = it->second; - if (entry == NULL) - continue; - - // Default values aren't passed on - if (entry->getLatitude() == 0.0 && entry->getLongitude() == 0.0) - continue; - - std::string desc; - if (entry->getBand().length() > 1U) { - if (entry->getFrequency() != 0.0) - desc = CStringUtils::string_format("Data %.5lfMHz", entry->getFrequency()); - else - desc = "Data"; - } else { - if (entry->getFrequency() != 0.0) - desc = CStringUtils::string_format("Voice %.5lfMHz %c%.4lfMHz", - entry->getFrequency(), - entry->getOffset() < 0.0 ? '-' : '+', - ::fabs(entry->getOffset())); - else - desc = "Voice"; + if(m_idFrameProvider != nullptr) { + m_idFrameProvider->clock(ms); + if(m_idFrameProvider->wantsToSend()) { + sendIdFrames(); } + } - std::string band; - if (entry->getFrequency() >= 1200.0) - band = "1.2"; - else if (entry->getFrequency() >= 420.0) - band = "440"; - else if (entry->getFrequency() >= 144.0) - band = "2m"; - else if (entry->getFrequency() >= 50.0) - band = "6m"; - else if (entry->getFrequency() >= 28.0) - band = "10m"; - - double tempLat = ::fabs(entry->getLatitude()); - double tempLong = ::fabs(entry->getLongitude()); - - double latitude = ::floor(tempLat); - double longitude = ::floor(tempLong); - - latitude = (tempLat - latitude) * 60.0 + latitude * 100.0; - longitude = (tempLong - longitude) * 60.0 + longitude * 100.0; - - std::string lat; - if (latitude >= 1000.0F) - lat = CStringUtils::string_format("%.2lf", latitude); - else if (latitude >= 100.0F) - lat = CStringUtils::string_format("0%.2lf", latitude); - else if (latitude >= 10.0F) - lat = CStringUtils::string_format("00%.2lf", latitude); - else - lat = CStringUtils::string_format("000%.2lf", latitude); - - std::string lon; - if (longitude >= 10000.0F) - lon = CStringUtils::string_format("%.2lf", longitude); - else if (longitude >= 1000.0F) - lon = CStringUtils::string_format("0%.2lf", longitude); - else if (longitude >= 100.0F) - lon = CStringUtils::string_format("00%.2lf", longitude); - else if (longitude >= 10.0F) - lon = CStringUtils::string_format("000%.2lf", longitude); - else - lon = CStringUtils::string_format("0000%.2lf", longitude); - - // Convert commas to periods in the latitude and longitude - boost::replace_all(lat, ",", "."); - boost::replace_all(lon, ",", "."); - - std::string output; - output = CStringUtils::string_format("%s-S>APD5T1,TCPIP*,qAC,%s-GS:;%-7s%-2s*%02d%02d%02dz%s%cD%s%caRNG%04.0lf/A=%06.0lf %s %s", - m_gateway.c_str(), m_gateway.c_str(), entry->getCallsign().c_str(), entry->getBand().c_str(), - tm->tm_mday, tm->tm_hour, tm->tm_min, - lat.c_str(), (entry->getLatitude() < 0.0F) ? 'S' : 'N', - lon.c_str(), (entry->getLongitude() < 0.0F) ? 'W' : 'E', - entry->getRange() * 0.6214, entry->getAGL() * 3.28, band.c_str(), desc.c_str()); - - char ascii[300U]; - ::memset(ascii, 0x00, 300U); - for (unsigned int i = 0U; i < output.length(); i++) - ascii[i] = output[i]; - - m_thread->write(ascii); - - if (entry->getBand().length() == 1U) { - output = CStringUtils::string_format("%s-%s>APD5T2,TCPIP*,qAC,%s-%sS:!%s%cD%s%c&RNG%04.0lf/A=%06.0lf %s %s", - entry->getCallsign().c_str(), entry->getBand().c_str(), entry->getCallsign().c_str(), entry->getBand().c_str(), - lat.c_str(), (entry->getLatitude() < 0.0F) ? 'S' : 'N', - lon.c_str(), (entry->getLongitude() < 0.0F) ? 'W' : 'E', - entry->getRange() * 0.6214, entry->getAGL() * 3.28, band.c_str(), desc.c_str()); - - ::memset(ascii, 0x00, 300U); - for (unsigned int i = 0U; i < output.length(); i++) - ascii[i] = output[i]; - - m_thread->write(ascii); + for (auto it : m_array) { + if(it.second != NULL) { + it.second->clock(ms); + if(it.second->getStatus().isOutOfDate()) + sendStatusFrame(it.second); } } } -void CAPRSWriter::sendIdFramesMobile() +void CAPRSWriter::sendStatusFrame(CAPRSEntry * entry) { - // Grab GPS data if it's available - unsigned char buffer[200U]; - in_addr address; - unsigned int port; - int ret = m_socket->read(buffer, 200U, address, port); - if (ret <= 0) - return; + assert(entry != nullptr); - if (!m_thread->isConnected()) + if(!m_thread->isConnected()) return; - buffer[ret] = '\0'; + auto linkStatus = entry->getStatus(); + std::string body = boost::trim_copy(linkStatus.getStatus()); - // Parse the GPS data - char* pLatitude = ::strtok((char*)buffer, ",\n"); // Latitude - char* pLongitude = ::strtok(NULL, ",\n"); // Longitude - char* pAltitude = ::strtok(NULL, ",\n"); // Altitude (m) - char* pVelocity = ::strtok(NULL, ",\n"); // Velocity (kms/h) - char* pBearing = ::strtok(NULL, "\n"); // Bearing + if(body[0] != '>') + body = '>' + body; - if (pLatitude == NULL || pLongitude == NULL || pAltitude == NULL) - return; + 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()); - double rawLatitude = ::atof(pLatitude); - double rawLongitude = ::atof(pLongitude); - double rawAltitude = ::atof(pAltitude); - - time_t now; - ::time(&now); - struct tm* tm = ::gmtime(&now); - - for (auto it = m_array.begin(); it != m_array.end(); ++it) { - CAPRSEntry* entry = it->second; - if (entry == NULL) - continue; - - std::string desc; - if (entry->getBand().length() > 1U) { - if (entry->getFrequency() != 0.0) - desc = CStringUtils::string_format("Data %.5lfMHz", entry->getFrequency()); - else - desc = "Data"; - } else { - if (entry->getFrequency() != 0.0) - desc = CStringUtils::string_format("Voice %.5lfMHz %c%.4lfMHz", - entry->getFrequency(), - entry->getOffset() < 0.0 ? '-' : '+', - ::fabs(entry->getOffset())); - else - desc = "Voice"; - } + m_thread->write(output.c_str()); - std::string band; - if (entry->getFrequency() >= 1200.0) - band = "1.2"; - else if (entry->getFrequency() >= 420.0) - band = "440"; - else if (entry->getFrequency() >= 144.0) - band = "2m"; - else if (entry->getFrequency() >= 50.0) - band = "6m"; - else if (entry->getFrequency() >= 28.0) - band = "10m"; - - double tempLat = ::fabs(rawLatitude); - double tempLong = ::fabs(rawLongitude); - - double latitude = ::floor(tempLat); - double longitude = ::floor(tempLong); - - latitude = (tempLat - latitude) * 60.0 + latitude * 100.0; - longitude = (tempLong - longitude) * 60.0 + longitude * 100.0; - - std::string lat; - if (latitude >= 1000.0F) - lat = CStringUtils::string_format("%.2lf", latitude); - else if (latitude >= 100.0F) - lat = CStringUtils::string_format("0%.2lf", latitude); - else if (latitude >= 10.0F) - lat = CStringUtils::string_format("00%.2lf", latitude); - else - lat = CStringUtils::string_format("000%.2lf", latitude); - - std::string lon; - if (longitude >= 10000.0F) - lon = CStringUtils::string_format("%.2lf", longitude); - else if (longitude >= 1000.0F) - lon = CStringUtils::string_format("0%.2lf", longitude); - else if (longitude >= 100.0F) - lon = CStringUtils::string_format("00%.2lf", longitude); - else if (longitude >= 10.0F) - lon = CStringUtils::string_format("000%.2lf", longitude); - else - lon = CStringUtils::string_format("0000%.2lf", longitude); - - // Convert commas to periods in the latitude and longitude - boost::replace_all(lat, ",", "."); - boost::replace_all(lon, ",", "."); - - std::string output1; - output1 = CStringUtils::string_format("%s-S>APD5T1,TCPIP*,qAC,%s-GS:;%-7s%-2s*%02d%02d%02dz%s%cD%s%ca/A=%06.0lf", - m_gateway.c_str(), m_gateway.c_str(), entry->getCallsign().c_str(), entry->getBand().c_str(), - tm->tm_mday, tm->tm_hour, tm->tm_min, - lat.c_str(), (rawLatitude < 0.0) ? 'S' : 'N', - lon.c_str(), (rawLongitude < 0.0) ? 'W' : 'E', - rawAltitude * 3.28); - - std::string output2; - if (pBearing != NULL && pVelocity != NULL) { - double rawBearing = ::atof(pBearing); - double rawVelocity = ::atof(pVelocity); - - output2 = CStringUtils::string_format("%03.0lf/%03.0lf", rawBearing, rawVelocity * 0.539957F); - } +} - std::string output3; - output3 = CStringUtils::string_format("RNG%04.0lf %s %s", entry->getRange() * 0.6214, band.c_str(), desc.c_str()); - - char ascii[300U]; - ::memset(ascii, 0x00, 300U); - unsigned int n = 0U; - for (unsigned int i = 0U; i < output1.length(); i++, n++) - ascii[n] = output1[i]; - for (unsigned int i = 0U; i < output2.length(); i++, n++) - ascii[n] = output2[i]; - for (unsigned int i = 0U; i < output3.length(); i++, n++) - ascii[n] = output3[i]; - - m_thread->write(ascii); - - if (entry->getBand().length() == 1U) { - output1 = CStringUtils::string_format("%s-%s>APD5T2,TCPIP*,qAC,%s-%sS:!%s%cD%s%c&/A=%06.0lf", - entry->getCallsign().c_str(), entry->getBand().c_str(), entry->getCallsign().c_str(), entry->getBand().c_str(), - lat.c_str(), (rawLatitude < 0.0) ? 'S' : 'N', - lon.c_str(), (rawLongitude < 0.0) ? 'W' : 'E', - rawAltitude * 3.28); - - ::memset(ascii, 0x00, 300U); - unsigned int n = 0U; - for (unsigned int i = 0U; i < output1.length(); i++, n++) - ascii[n] = output1[i]; - for (unsigned int i = 0U; i < output2.length(); i++, n++) - ascii[n] = output2[i]; - for (unsigned int i = 0U; i < output3.length(); i++, n++) - ascii[n] = output3[i]; - - m_thread->write(ascii); +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()); + } + } } } } +bool CAPRSWriter::isConnected() const +{ + return m_thread->isConnected(); +} + +void CAPRSWriter::close() +{ + if(m_idFrameProvider != nullptr) { + m_idFrameProvider->close(); + delete m_idFrameProvider; + m_idFrameProvider = nullptr; + } + + m_thread->stop(); +} diff --git a/APRSWriter.h b/APRSWriter.h index 54c672c..3543930 100644 --- a/APRSWriter.h +++ b/APRSWriter.h @@ -20,9 +20,13 @@ #ifndef APRSWriter_H #define APRSWriter_H +#include "Defs.h" + #include #include + +#include "APRSEntry.h" #include "APRSWriterThread.h" #include "UDPReaderWriter.h" #include "APRSCollector.h" @@ -30,41 +34,7 @@ #include "HeaderData.h" #include "AMBEData.h" #include "Timer.h" -#include "Defs.h" - -class CAPRSEntry { -public: - CAPRSEntry(const std::string& callsign, const std::string& band, double frequency, double offset, double range, double latitude, double longitude, double agl); - ~CAPRSEntry(); - - std::string getCallsign() const; - std::string getBand() const; - double getFrequency() const; - double getOffset() const; - double getRange() const; - double getLatitude() const; - double getLongitude() const; - double getAGL() const; - CAPRSCollector* getCollector() const; - - // Transmission timer - void reset(); - void clock(unsigned int ms); - bool isOK(); - -private: - std::string m_callsign; - std::string m_band; - double m_frequency; - double m_offset; - double m_range; - double m_latitude; - double m_longitude; - double m_agl; - CTimer m_timer; - bool m_first; - CAPRSCollector* m_collector; -}; +#include "APRSIdFrameProvider.h" class CAPRSWriter { public: @@ -73,14 +43,16 @@ public: bool open(); - void setPortFixed(const std::string& callsign, const std::string& band, double frequency, double offset, double range, double latitude, double longitude, double agl); + void setIdFrameProvider(CAPRSIdFrameProvider * idFrameProvider) { m_idFrameProvider = idFrameProvider; } - void setPortMobile(const std::string& callsign, const std::string& band, double frequency, double offset, double range, const std::string& address, unsigned int port); + void setPort(const std::string& callsign, const std::string& band, double frequency, double offset, double range, double latitude, double longitude, double agl); void writeHeader(const std::string& callsign, const CHeaderData& header); 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); @@ -89,16 +61,14 @@ public: private: CAPRSWriterThread* m_thread; - CTimer m_idTimer; std::string m_gateway; in_addr m_address; unsigned int m_port; - CUDPReaderWriter* m_socket; std::unordered_map m_array; + CAPRSIdFrameProvider * m_idFrameProvider; - bool pollGPS(); - void sendIdFramesFixed(); - void sendIdFramesMobile(); + void sendIdFrames(); + void sendStatusFrame(CAPRSEntry * entrry); }; #endif diff --git a/APRSWriterThread.cpp b/APRSWriterThread.cpp index 2e2c137..fe27a9b 100644 --- a/APRSWriterThread.cpp +++ b/APRSWriterThread.cpp @@ -26,6 +26,7 @@ #include "Utils.h" #include "Defs.h" #include "Log.h" +#include "Version.h" // #define DUMP_TX @@ -44,7 +45,7 @@ m_reconnectTimer(1000U), m_tries(0U), m_APRSReadCallback(NULL), m_filter(""), -m_clientName("DStarGateway") +m_clientName(FULL_PRODUCT_NAME) { assert(!callsign.empty()); assert(!password.empty()); @@ -154,7 +155,7 @@ void* CAPRSWriterThread::Entry() if (length < 0) { m_connected = false; m_socket.close(); - CLog::logInfo("Error when reading from the APRS server"); + CLog::logError("Error when reading from the APRS server"); startReconnectionTimer(); } @@ -249,7 +250,7 @@ bool CAPRSWriterThread::connect() std::stringstream connectString; connectString << "User " << m_username << "-" << m_ssid << " pass " << m_password - << " vers " << (m_clientName.length() ? m_clientName : "DStarGateway") + << " vers " << (!m_clientName.empty() ? m_clientName : FULL_PRODUCT_NAME) << filter; //CLog::logInfo("Connect String : ") + connectString); ret = m_socket.writeLine(connectString.str()); diff --git a/DStarGatewayApp.cpp b/DStarGatewayApp.cpp index bfe9e31..c8fd520 100644 --- a/DStarGatewayApp.cpp +++ b/DStarGatewayApp.cpp @@ -36,6 +36,10 @@ #include "RepeaterProtocolHandlerFactory.h" #include "XLXHostsFileDownloader.h" #include "Log.h" +#include "LogFileTarget.h" +#include "LogConsoleTarget.h" +#include "APRSGPSDIdFrameProvider.h" +#include "APRSFixedIdFrameProvider.h" int main(int argc, char *argv[]) { @@ -47,7 +51,7 @@ int main(int argc, char *argv[]) } if ('-' == argv[1][0]) { - printf("\nDStarGateway Version %s (GitID #%.7s) Copyright (C) %s\n", VERSION.c_str(), gitversion, VENDOR_NAME.c_str()); + printf("\n%s Copyright (C) %s\n", FULL_PRODUCT_NAME.c_str(), VENDOR_NAME.c_str()); printf("DStarGateway comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n"); printf("This is free software, and you are welcome to distribute it\nunder certain conditions that are discussed in the LICENSE file.\n\n"); return 0; @@ -84,11 +88,12 @@ void CDStarGatewayApp::run() m_thread->Run(); m_thread->Wait(); CLog::logInfo("exiting\n"); + CLog::finalise(); } bool CDStarGatewayApp::createThread() { - printf("\nDStarGateway Version %s (GitID #%.7s) Copyright (C) %s\n", VERSION.c_str(), gitversion, VENDOR_NAME.c_str()); + printf("\n%s Copyright (C) %s\n", FULL_PRODUCT_NAME.c_str(), VENDOR_NAME.c_str()); printf("DStarGateway comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n"); printf("This is free software, and you are welcome to distribute it\nunder certain conditions that are discussed in the LICENSE file.\n\n"); @@ -98,9 +103,12 @@ bool CDStarGatewayApp::createThread() return false; } + // Setup Log TLog log; config.getLog(log); - CLog::initialize(log.logDir + "dstargateway.log", LS_INFO, true); + CLog::finalise(); + if(log.m_displayLevel != LOG_NONE) CLog::addTarget(new CLogConsoleTarget(log.m_displayLevel)); + if(log.m_fileLevel != LOG_NONE) CLog::addTarget(new CLogFileTarget(log.m_fileLevel, log.logDir, log.m_fileRotate)); Tpaths paths; config.getPaths(paths); @@ -113,6 +121,12 @@ bool CDStarGatewayApp::createThread() m_thread->setLanguage(gatewayConfig.language); m_thread->setLocation(gatewayConfig.latitude, gatewayConfig.longitude); +#ifdef USE_GPSD + // Setup GPSD + TGPSD gpsdConfig; + config.getGPSD(gpsdConfig); +#endif + // Setup APRS TAPRS aprsConfig; config.getAPRS(aprsConfig); @@ -120,6 +134,14 @@ bool CDStarGatewayApp::createThread() if(aprsConfig.enabled && !aprsConfig.password.empty()) { aprsWriter = new CAPRSWriter(aprsConfig.hostname, aprsConfig.port, gatewayConfig.callsign, aprsConfig.password, gatewayConfig.address); if(aprsWriter->open()) { +#ifdef USE_GPSD + CAPRSIdFrameProvider * idFrameProvider = aprsConfig.m_positionSource == POSSRC_GPSD ? (CAPRSIdFrameProvider *)new CAPRSGPSDIdFrameProvider(gpsdConfig.m_address, gpsdConfig.m_port) + : new CAPRSFixedIdFrameProvider(); +#else + CAPRSIdFrameProvider * idFrameProvider = new CAPRSFixedIdFrameProvider(); +#endif + idFrameProvider->start(); + aprsWriter->setIdFrameProvider(idFrameProvider); m_thread->setAPRSWriter(aprsWriter); } else { @@ -159,8 +181,9 @@ bool CDStarGatewayApp::createThread() rptrConfig.band1, rptrConfig.band2, rptrConfig.band3); - - if(aprsWriter != NULL) aprsWriter->setPortFixed(rptrConfig.callsign, rptrConfig.band, rptrConfig.frequency, rptrConfig.offset, rptrConfig.range, rptrConfig.latitude, rptrConfig.longitude, rptrConfig.agl); + + aprsWriter->setPort(rptrConfig.callsign, rptrConfig.band, rptrConfig.frequency, rptrConfig.offset, rptrConfig.range, rptrConfig.latitude, rptrConfig.longitude, rptrConfig.agl); + if(!ddEnabled) ddEnabled = rptrConfig.band.length() > 1U; } m_thread->setDDModeEnabled(ddEnabled); @@ -172,7 +195,7 @@ bool CDStarGatewayApp::createThread() TircDDB ircDDBConfig; config.getIrcDDB(i, ircDDBConfig); CLog::logInfo("ircDDB Network %d set to %s user: %s, Quadnet %d", i + 1,ircDDBConfig.hostname.c_str(), ircDDBConfig.username.c_str(), ircDDBConfig.isQuadNet); - CIRCDDB * ircDDB = new CIRCDDBClient(ircDDBConfig.hostname, 9007U, ircDDBConfig.username, ircDDBConfig.password, std::string("DStarGateway") + std::string("-") + VERSION, gatewayConfig.address, ircDDBConfig.isQuadNet); + CIRCDDB * ircDDB = new CIRCDDBClient(ircDDBConfig.hostname, 9007U, ircDDBConfig.username, ircDDBConfig.password, FULL_PRODUCT_NAME, gatewayConfig.address, ircDDBConfig.isQuadNet); clients.push_back(ircDDB); } CIRCDDBMultiClient* multiClient = new CIRCDDBMultiClient(clients); diff --git a/DStarGatewayConfig.cpp b/DStarGatewayConfig.cpp index 6629bd7..a976c1f 100644 --- a/DStarGatewayConfig.cpp +++ b/DStarGatewayConfig.cpp @@ -52,6 +52,9 @@ bool CDStarGatewayConfig::load() ret = loadDPlus(cfg) && ret; ret = loadRemote(cfg) && ret; ret = loadXLX(cfg) && ret; +#ifdef USE_GPSD + ret = loadGPSD(cfg) && ret; +#endif } if(ret) { @@ -118,6 +121,16 @@ bool CDStarGatewayConfig::loadAPRS(const CConfig & cfg) ret = cfg.getValue("aprs", "port", m_aprs.port, 1U, 65535U, 14580U) && ret; ret = cfg.getValue("aprs", "hostname", m_aprs.hostname, 0, 1024, "rotate.aprs2.net") && ret; ret = cfg.getValue("aprs", "password", m_aprs.password, 0U, 30U, "") && ret; +#ifdef USE_GPSD + std::string positionSource; + ret = cfg.getValue("aprs", "positionSource", positionSource, "fixed", {"fixed", "gpsd"}) && ret; + if(ret) { + if(positionSource == "fixed") m_aprs.m_positionSource = POSSRC_FIXED; + else if(positionSource == "gpsd") m_aprs.m_positionSource = POSSRC_GPSD; + } +#else + m_aprs.m_positionSource = POSSRC_FIXED; +#endif m_aprs.enabled = m_aprs.enabled && !m_aprs.password.empty(); @@ -126,13 +139,38 @@ bool CDStarGatewayConfig::loadAPRS(const CConfig & cfg) bool CDStarGatewayConfig::loadLog(const CConfig & cfg) { - bool ret =cfg.getValue("log", "path", m_log.logDir, 0, 2048, "/var/log/dstargateway/"); - + bool ret = cfg.getValue("log", "path", m_log.logDir, 0, 2048, "/var/log/dstargateway/"); if(ret && m_log.logDir[m_log.logDir.length() - 1] != '/') { m_log.logDir.push_back('/'); } - //TODO 20211226 check if directory are accessible + ret = cfg.getValue("log", "fileRoot", m_log.m_fileRoot, 0, 64, "dstargateway") && ret; + ret = cfg.getValue("log", "fileRotate", m_log.m_fileRotate, true) && ret; + + std::string levelStr; + ret = cfg.getValue("log", "fileLevel", levelStr, "info", {"trace", "debug", "info", "warning", "error", "fatal", "none"}) && ret; + if(ret) { + if(levelStr == "trace") m_log.m_fileLevel = LOG_TRACE; + else if(levelStr == "debug") m_log.m_fileLevel = LOG_DEBUG; + else if(levelStr == "info") m_log.m_fileLevel = LOG_INFO; + else if(levelStr == "warning") m_log.m_fileLevel = LOG_WARNING; + else if(levelStr == "error") m_log.m_fileLevel = LOG_ERROR; + else if(levelStr == "fatal") m_log.m_fileLevel = LOG_FATAL; + else if(levelStr == "none") m_log.m_fileLevel = LOG_NONE; + } + + ret = cfg.getValue("log", "displayLevel", levelStr, "info", {"trace", "debug", "info", "warning", "error", "fatal", "none"}) && ret; + if(ret) { + if(levelStr == "trace") m_log.m_displayLevel = LOG_TRACE; + else if(levelStr == "debug") m_log.m_displayLevel = LOG_DEBUG; + else if(levelStr == "info") m_log.m_displayLevel = LOG_INFO; + else if(levelStr == "warning") m_log.m_displayLevel = LOG_WARNING; + else if(levelStr == "error") m_log.m_displayLevel = LOG_ERROR; + else if(levelStr == "fatal") m_log.m_displayLevel = LOG_FATAL; + else if(levelStr == "none") m_log.m_displayLevel = LOG_NONE; + } + + //TODO 20211226 check if directories are accessible return ret; } @@ -294,6 +332,16 @@ bool CDStarGatewayConfig::loadGateway(const CConfig & cfg) return ret; } +#ifdef USE_GPSD +bool CDStarGatewayConfig::loadGPSD(const CConfig & cfg) +{ + bool ret = cfg.getValue("gpsd", "address", m_gpsd.m_address, 0U, 15U, "127.0.0.1"); + ret = cfg.getValue("gpsd", "port", m_gpsd.m_port, 0U, 5U, "2947") && ret; + + return ret; +} +#endif + bool CDStarGatewayConfig::open(CConfig & cfg) { try { @@ -383,3 +431,10 @@ void CDStarGatewayConfig::getXLX(TXLX & xlx) const { xlx = m_xlx; } + +#ifdef USE_GPSD +void CDStarGatewayConfig::getGPSD(TGPSD & gpsd) const +{ + gpsd = m_gpsd; +} +#endif diff --git a/DStarGatewayConfig.h b/DStarGatewayConfig.h index 56197bf..b27ca44 100644 --- a/DStarGatewayConfig.h +++ b/DStarGatewayConfig.h @@ -23,6 +23,7 @@ #include "Defs.h" #include "Config.h" +#include "LogSeverity.h" typedef struct { GATEWAY_TYPE type; @@ -79,6 +80,10 @@ typedef struct { typedef struct { std::string logDir; + LOG_SEVERITY m_displayLevel; + LOG_SEVERITY m_fileLevel; + std::string m_fileRoot; + bool m_fileRotate; } TLog; typedef struct { @@ -86,6 +91,7 @@ typedef struct { std::string hostname; unsigned int port; std::string password; + POSITION_SOURCE m_positionSource; } TAPRS; typedef struct { @@ -115,6 +121,12 @@ typedef struct { std::string password; } TRemote; +#ifdef USE_GPSD +typedef struct { + std::string m_address; + std::string m_port; +} TGPSD; +#endif class CDStarGatewayConfig { public: @@ -135,6 +147,9 @@ public: void getDCS(TDCS & dcs) const; void getRemote(TRemote & remote) const; void getXLX(TXLX & xlx) const; +#ifdef USE_GPSD + void getGPSD(TGPSD & gpsd) const; +#endif private: bool open(CConfig & cfg); @@ -149,6 +164,9 @@ private: bool loadDCS(const CConfig & cfg); bool loadRemote(const CConfig & cfg); bool loadXLX(const CConfig & cfg); +#ifdef USE_GPSD + bool loadGPSD(const CConfig & cfg); +#endif std::string m_fileName; TGateway m_gateway; @@ -160,6 +178,9 @@ private: TRemote m_remote; TXLX m_xlx; TLog m_log; +#ifdef USE_GPSD + TGPSD m_gpsd; +#endif std::vector m_repeaters; std::vector m_ircDDB; }; diff --git a/DStarGatewayThread.cpp b/DStarGatewayThread.cpp index 8aff02d..23aa7a2 100644 --- a/DStarGatewayThread.cpp +++ b/DStarGatewayThread.cpp @@ -745,13 +745,13 @@ void CDStarGatewayThread::processIrcDDB() break; if (!address.empty()) { - CLog::logInfo("USER: %s %s %s %s", user.c_str(), repeater.c_str(), gateway.c_str(), address.c_str()); + CLog::logDebug("USER: %s %s %s %s", user.c_str(), repeater.c_str(), gateway.c_str(), address.c_str()); m_cache.updateUser(user, repeater, gateway, address, timestamp, DP_DEXTRA, false, false); #if defined(ENABLE_NAT_TRAVERSAL) m_natTraversal->traverseNatG2(address); #endif } else { - CLog::logInfo("USER: %s NOT FOUND", user.c_str()); + CLog::logDebug("USER: %s NOT FOUND", user.c_str()); } } break; @@ -764,13 +764,13 @@ void CDStarGatewayThread::processIrcDDB() CRepeaterHandler::resolveRepeater(repeater, gateway, address, DP_DEXTRA); if (!address.empty()) { - CLog::logInfo("REPEATER: %s %s %s", repeater.c_str(), gateway.c_str(), address.c_str()); + CLog::logDebug("REPEATER: %s %s %s", repeater.c_str(), gateway.c_str(), address.c_str()); m_cache.updateRepeater(repeater, gateway, address, DP_DEXTRA, false, false); #if defined(ENABLE_NAT_TRAVERSAL) m_natTraversal->traverseNatG2(address); #endif } else { - CLog::logInfo("REPEATER: %s NOT FOUND", repeater.c_str()); + CLog::logDebug("REPEATER: %s NOT FOUND", repeater.c_str()); } } break; @@ -784,13 +784,13 @@ void CDStarGatewayThread::processIrcDDB() CDExtraHandler::gatewayUpdate(gateway, address); CDPlusHandler::gatewayUpdate(gateway, address); if (!address.empty()) { - CLog::logInfo("GATEWAY: %s %s", gateway.c_str(), address.c_str()); + CLog::logDebug("GATEWAY: %s %s", gateway.c_str(), address.c_str()); m_cache.updateGateway(gateway, address, DP_DEXTRA, false, false); #if defined(ENABLE_NAT_TRAVERSAL) m_natTraversal->traverseNatG2(address); #endif } else { - CLog::logInfo("GATEWAY: %s NOT FOUND", gateway.c_str()); + CLog::logDebug("GATEWAY: %s NOT FOUND", gateway.c_str()); } } break; diff --git a/Defs.h b/Defs.h index 273ed25..2f12568 100644 --- a/Defs.h +++ b/Defs.h @@ -137,5 +137,10 @@ enum GATEWAY_TYPE { GT_SMARTGROUP }; +enum POSITION_SOURCE { + POSSRC_FIXED, + POSSRC_GPSD +}; + const unsigned int TIME_PER_TIC_MS = 5U; diff --git a/IRCDDBApp.cpp b/IRCDDBApp.cpp index 190ddd5..2731fb0 100644 --- a/IRCDDBApp.cpp +++ b/IRCDDBApp.cpp @@ -255,7 +255,7 @@ IRCDDB_RESPONSE_TYPE IRCDDBApp::getReplyMessageType() if (0 == msgType.compare("IDRT_GATEWAY")) return IDRT_GATEWAY; - CLog::logInfo("IRCDDBApp::getMessageType: unknown msg type: %s\n", msgType.c_str()); + CLog::logWarning("IRCDDBApp::getMessageType: unknown msg type: %s\n", msgType.c_str()); return IDRT_NONE; } diff --git a/IRCDDBClient.cpp b/IRCDDBClient.cpp index 45d4ba1..bf5130a 100644 --- a/IRCDDBClient.cpp +++ b/IRCDDBClient.cpp @@ -88,27 +88,27 @@ bool CIRCDDBClient::sendHeard( const std::string& myCall, const std::string& myC const std::string& rpt2, unsigned char flag1, unsigned char flag2, unsigned char flag3 ) { if (myCall.size() != 8) { - CLog::logInfo("CIRCDDBClient::sendHeard:myCall='%s' len != 8\n", myCall.c_str()); + CLog::logDebug("CIRCDDBClient::sendHeard:myCall='%s' len != 8\n", myCall.c_str()); return false; } if (myCallExt.size() != 4) { - CLog::logInfo("CIRCDDBClient::sendHeard:myCallExt='%s' len != 4\n", myCallExt.c_str()); + CLog::logDebug("CIRCDDBClient::sendHeard:myCallExt='%s' len != 4\n", myCallExt.c_str()); return false; } if (yourCall.size() != 8) { - CLog::logInfo("CIRCDDBClient::sendHeard:yourCall='%s' len != 8\n", yourCall.c_str()); + CLog::logDebug("CIRCDDBClient::sendHeard:yourCall='%s' len != 8\n", yourCall.c_str()); return false; } if (rpt1.size() != 8) { - CLog::logInfo("CIRCDDBClient::sendHeard:rpt1='%s' len != 8\n", rpt1.c_str()); + CLog::logDebug("CIRCDDBClient::sendHeard:rpt1='%s' len != 8\n", rpt1.c_str()); return false; } if (rpt2.size() != 8) { - CLog::logInfo("CIRCDDBClient::sendHeard:rpt2='%s' len != 8\n", rpt2.c_str()); + CLog::logDebug("CIRCDDBClient::sendHeard:rpt2='%s' len != 8\n", rpt2.c_str()); return false; } @@ -117,7 +117,7 @@ bool CIRCDDBClient::sendHeard( const std::string& myCall, const std::string& myC void CIRCDDBClient::sendDStarGatewayInfo(const std::string subcommand, const std::vector parms) { - CLog::logInfo("CIRCDDBClient::sendDStarGatewayInfo subcommand %s parms", subcommand.c_str()); + CLog::logDebug("CIRCDDBClient::sendDStarGatewayInfo subcommand %s parms", subcommand.c_str()); for(unsigned int i=0; i < parms.size();i++) CLog::logInfo(" %s", parms[i].c_str()); CLog::logInfo("\n"); @@ -132,27 +132,27 @@ bool CIRCDDBClient::sendHeardWithTXMsg(const std::string& myCall, const std::str const std::string& rpt2, unsigned char flag1, unsigned char flag2, unsigned char flag3, const std::string& network_destination, const std::string& tx_message) { if (myCall.size() != 8) { - CLog::logInfo("CIRCDDBClient::sendHeardWithTXMsg:myCall='%s' len != 8\n", myCall.c_str()); + CLog::logDebug("CIRCDDBClient::sendHeardWithTXMsg:myCall='%s' len != 8\n", myCall.c_str()); return false; } if (myCallExt.size() != 4) { - CLog::logInfo("CIRCDDBClient::sendHeardWithTXMsg:myCallExt='%s' len != 4\n", myCallExt.c_str()); + CLog::logDebug("CIRCDDBClient::sendHeardWithTXMsg:myCallExt='%s' len != 4\n", myCallExt.c_str()); return false; } if (yourCall.size() != 8) { - CLog::logInfo("CIRCDDBClient::sendHeardWithTXMsg:yourCall='%s' len != 8\n", yourCall.c_str()); + CLog::logDebug("CIRCDDBClient::sendHeardWithTXMsg:yourCall='%s' len != 8\n", yourCall.c_str()); return false; } if (rpt1.size() != 8) { - CLog::logInfo("CIRCDDBClient::sendHeardWithTXMsg:rpt1='%s' len != 8\n", rpt1.c_str()); + CLog::logDebug("CIRCDDBClient::sendHeardWithTXMsg:rpt1='%s' len != 8\n", rpt1.c_str()); return false; } if (rpt2.size() != 8) { - CLog::logInfo("CIRCDDBClient::sendHeardWithTXMsg:rpt2='%s' len != 8\n", rpt2.c_str()); + CLog::logDebug("CIRCDDBClient::sendHeardWithTXMsg:rpt2='%s' len != 8\n", rpt2.c_str()); return false; } @@ -161,7 +161,7 @@ bool CIRCDDBClient::sendHeardWithTXMsg(const std::string& myCall, const std::str dest = std::string(" "); if (8 != dest.size()) { - CLog::logInfo("CIRCDDBClient::sendHeardWithTXMsg:network_destination='%s' len != 8\n", dest.c_str()); + CLog::logDebug("CIRCDDBClient::sendHeardWithTXMsg:network_destination='%s' len != 8\n", dest.c_str()); return false; } @@ -184,42 +184,42 @@ bool CIRCDDBClient::sendHeardWithTXStats( const std::string& myCall, const std:: const std::string& rpt2, unsigned char flag1, unsigned char flag2, unsigned char flag3, int num_dv_frames, int num_dv_silent_frames, int num_bit_errors) { if ((num_dv_frames <= 0) || (num_dv_frames > 65535)) { - CLog::logInfo("CIRCDDBClient::sendHeardWithTXStats:num_dv_frames=%d not in range 1-65535\n", num_dv_frames); + CLog::logDebug("CIRCDDBClient::sendHeardWithTXStats:num_dv_frames=%d not in range 1-65535\n", num_dv_frames); return false; } if (num_dv_silent_frames > num_dv_frames) { - CLog::logInfo("CIRCDDBClient::sendHeardWithTXStats:num_dv_silent_frames=%d > num_dv_frames=%d\n", num_dv_silent_frames, num_dv_frames); + CLog::logDebug("CIRCDDBClient::sendHeardWithTXStats:num_dv_silent_frames=%d > num_dv_frames=%d\n", num_dv_silent_frames, num_dv_frames); return false; } if (num_bit_errors > (4*num_dv_frames)) { // max 4 bit errors per frame - CLog::logInfo("CIRCDDBClient::sendHeardWithTXStats:num_bit_errors > (4*num_dv_frames), %d > 4*%d\n", num_bit_errors, num_dv_frames); + CLog::logDebug("CIRCDDBClient::sendHeardWithTXStats:num_bit_errors > (4*num_dv_frames), %d > 4*%d\n", num_bit_errors, num_dv_frames); return false; } if (myCall.size() != 8) { - CLog::logInfo("CIRCDDBClient::sendHeardWithTXStats:myCall='%s' len != 8\n", myCall.c_str()); + CLog::logDebug("CIRCDDBClient::sendHeardWithTXStats:myCall='%s' len != 8\n", myCall.c_str()); return false; } if (myCallExt.size() != 4) { - CLog::logInfo("CIRCDDBClient::sendHeardWithTXStats:myCallExt='%s' len != 4\n", myCallExt.c_str()); + CLog::logDebug("CIRCDDBClient::sendHeardWithTXStats:myCallExt='%s' len != 4\n", myCallExt.c_str()); return false; } if (yourCall.size() != 8) { - CLog::logInfo("CIRCDDBClient::sendHeardWithTXStats:yourCall='%s' len != 8\n", yourCall.c_str()); + CLog::logDebug("CIRCDDBClient::sendHeardWithTXStats:yourCall='%s' len != 8\n", yourCall.c_str()); return false; } if (rpt1.size() != 8) { - CLog::logInfo("CIRCDDBClient::sendHeardWithTXStats:rpt1='%s' len != 8\n", rpt1.c_str()); + CLog::logDebug("CIRCDDBClient::sendHeardWithTXStats:rpt1='%s' len != 8\n", rpt1.c_str()); return false; } if (rpt2.size() != 8) { - CLog::logInfo("CIRCDDBClient::sendHeardWithTXStats:rpt2='%s' len != 8\n", rpt2.c_str()); + CLog::logDebug("CIRCDDBClient::sendHeardWithTXStats:rpt2='%s' len != 8\n", rpt2.c_str()); return false; } @@ -245,7 +245,7 @@ bool CIRCDDBClient::sendHeardWithTXStats( const std::string& myCall, const std:: bool CIRCDDBClient::findGateway(const std::string& gatewayCallsign) { if (8 != gatewayCallsign.size()) { - CLog::logInfo("CIRCDDBClient::findGateway:gatewayCallsign='%s' len != 8\n", gatewayCallsign.c_str()); + CLog::logDebug("CIRCDDBClient::findGateway:gatewayCallsign='%s' len != 8\n", gatewayCallsign.c_str()); return false; } std::string gw(gatewayCallsign); @@ -257,7 +257,7 @@ bool CIRCDDBClient::findGateway(const std::string& gatewayCallsign) bool CIRCDDBClient::findRepeater(const std::string& repeaterCallsign) { if (8 != repeaterCallsign.size()) { - CLog::logInfo("CIRCDDBClient::findRepeater:repeaterCallsign='%s' len != 8\n", repeaterCallsign.c_str()); + CLog::logDebug("CIRCDDBClient::findRepeater:repeaterCallsign='%s' len != 8\n", repeaterCallsign.c_str()); return false; } std::string rptr(repeaterCallsign); @@ -269,7 +269,7 @@ bool CIRCDDBClient::findRepeater(const std::string& repeaterCallsign) bool CIRCDDBClient::findUser(const std::string& userCallsign) { if (8 != userCallsign.size()) { - CLog::logInfo("CIRCDDBClient::findUser:userCall='%s' len != 8\n", userCallsign.c_str()); + CLog::logDebug("CIRCDDBClient::findUser:userCall='%s' len != 8\n", userCallsign.c_str()); return false; } std::string usr(userCallsign); @@ -292,24 +292,24 @@ bool CIRCDDBClient::receiveRepeater(std::string& repeaterCallsign, std::string& IRCDDB_RESPONSE_TYPE rt = d->app->getReplyMessageType(); if (rt != IDRT_REPEATER) { - CLog::logInfo("CIRCDDBClient::receiveRepeater: unexpected response type=%d\n", rt); + CLog::logDebug("CIRCDDBClient::receiveRepeater: unexpected response type=%d\n", rt); return false; } IRCMessage *m = d->app->getReplyMessage(); if (m == NULL) { - CLog::logInfo("CIRCDDBClient::receiveRepeater: no message\n"); + CLog::logDebug("CIRCDDBClient::receiveRepeater: no message\n"); return false; } if (m->getCommand().compare("IDRT_REPEATER")) { - CLog::logInfo("CIRCDDBClient::receiveRepeater: wrong message type, expected 'IDRT_REPEATER, got '%s'\n", m->getCommand().c_str()); + CLog::logDebug("CIRCDDBClient::receiveRepeater: wrong message type, expected 'IDRT_REPEATER, got '%s'\n", m->getCommand().c_str()); delete m; return false; } if (3 != m->getParamCount()) { - CLog::logInfo("CIRCDDBClient::receiveRepeater: unexpected number of message parameters, expected 3, got %d\n", m->getParamCount()); + CLog::logDebug("CIRCDDBClient::receiveRepeater: unexpected number of message parameters, expected 3, got %d\n", m->getParamCount()); delete m; return false; } @@ -328,25 +328,25 @@ bool CIRCDDBClient::receiveGateway(std::string& gatewayCallsign, std::string& ad IRCDDB_RESPONSE_TYPE rt = d->app->getReplyMessageType(); if (rt != IDRT_GATEWAY) { - CLog::logInfo("CIRCDDBClient::receiveGateway: unexpected response type=%d\n", rt); + CLog::logDebug("CIRCDDBClient::receiveGateway: unexpected response type=%d\n", rt); return false; } IRCMessage *m = d->app->getReplyMessage(); if (m == NULL) { - CLog::logInfo("CIRCDDBClient::receiveGateway: no message\n"); + CLog::logDebug("CIRCDDBClient::receiveGateway: no message\n"); return false; } if (m->getCommand().compare("IDRT_GATEWAY")) { - CLog::logInfo("CIRCDDBClient::receiveGateway: wrong message type, expected 'IDRT_GATEWAY' got '%s'\n", m->getCommand().c_str()); + CLog::logDebug("CIRCDDBClient::receiveGateway: wrong message type, expected 'IDRT_GATEWAY' got '%s'\n", m->getCommand().c_str()); delete m; return false; } if (2 != m->getParamCount()) { - CLog::logInfo("CIRCDDBClient::receiveGateway: unexpected number of message parameters, expected 2, got %d\n", m->getParamCount()); + CLog::logDebug("CIRCDDBClient::receiveGateway: unexpected number of message parameters, expected 2, got %d\n", m->getParamCount()); delete m; return false; } @@ -370,25 +370,25 @@ bool CIRCDDBClient::receiveUser(std::string& userCallsign, std::string& repeater IRCDDB_RESPONSE_TYPE rt = d->app->getReplyMessageType(); if (rt != IDRT_USER) { - CLog::logInfo("CIRCDDBClient::receiveUser: unexpected response type=%d\n", rt); + CLog::logDebug("CIRCDDBClient::receiveUser: unexpected response type=%d\n", rt); return false; } IRCMessage * m = d->app->getReplyMessage(); if (m == NULL) { - CLog::logInfo("CIRCDDBClient::receiveUser: no message\n"); + CLog::logDebug("CIRCDDBClient::receiveUser: no message\n"); return false; } if (m->getCommand().compare("IDRT_USER")) { - CLog::logInfo("CIRCDDBClient::receiveUser: wrong message type, expected 'IDRT_USER', got '%s'\n", m->getCommand().c_str()); + CLog::logDebug("CIRCDDBClient::receiveUser: wrong message type, expected 'IDRT_USER', got '%s'\n", m->getCommand().c_str()); delete m; return false; } if (5 != m->getParamCount()) { - CLog::logInfo("CIRCDDBClient::receiveUser: unexpected number of message parameters, expected 5, got %d\n", m->getParamCount()); + CLog::logDebug("CIRCDDBClient::receiveUser: unexpected number of message parameters, expected 5, got %d\n", m->getParamCount()); delete m; return false; } diff --git a/Log.cpp b/Log.cpp index c0404fb..689e69f 100644 --- a/Log.cpp +++ b/Log.cpp @@ -18,18 +18,39 @@ */ #include +#include +#include #include "Log.h" +#include "LogConsoleTarget.h" -LOG_SEVERITY CLog::m_level = LS_INFO; -std::string CLog::m_file = ""; -bool CLog::m_logToConsole = true; +bool CLog::m_addedTargets(false); +std::recursive_mutex CLog::m_targetsMutex; +std::vector CLog::m_targets = { new CLogConsoleTarget(LOG_DEBUG) }; -void CLog::initialize(const std::string& logfile, LOG_SEVERITY logLevel, bool logToConsole) +void CLog::addTarget(CLogTarget* target) { - m_file = logfile; - m_level = logLevel; - m_logToConsole = logToConsole; + assert(target != nullptr); + + std::lock_guard lockTargets(m_targetsMutex); + + if(!m_addedTargets) { + // It is the first time we add an external target, clear the default one(s) + m_addedTargets = true; + finalise(); + } + + m_targets.push_back(target); +} + +void CLog::finalise() +{ + std::lock_guard lockTargets(m_targetsMutex); + for(auto target : m_targets) { + delete target; + } + + m_targets.clear(); } void CLog::getTimeStamp(std::string & s) @@ -39,4 +60,4 @@ void CLog::getTimeStamp(std::string & s) char buf[64]; std::strftime(buf, 42, "%Y-%m-%d %T", now_tm); s = std::string(buf); -} \ No newline at end of file +} diff --git a/Log.h b/Log.h index 0e99426..26414c8 100644 --- a/Log.h +++ b/Log.h @@ -20,104 +20,105 @@ #pragma once #include -#include -#include -#include -#include +#include #include +#include +#include +#include #include "StringUtils.h" - -enum LOG_SEVERITY { - LS_TRACE = 1, - LS_DEBUG, - LS_INFO, - LS_WARNING, - LS_ERROR, - LS_FATAL -}; +#include "LogTarget.h" class CLog { private: - static LOG_SEVERITY m_level; - static std::string m_file; - static bool m_logToConsole; + static std::vector m_targets; + static bool m_addedTargets; + static std::recursive_mutex m_targetsMutex; static void getTimeStamp(std::string & s); + template static void formatLogMessage(std::string& output, LOG_SEVERITY severity, const std::string & f, Args... args) + { + assert(severity != LOG_NONE); + + std::string severityStr; + switch (severity) + { + case LOG_DEBUG: + severityStr = "DEBUG "; + break; + case LOG_ERROR: + severityStr = "ERROR "; + break; + case LOG_FATAL: + severityStr = "FATAL "; + break; + case LOG_INFO : + severityStr = "INFO "; + break; + case LOG_WARNING: + severityStr = "WARNING"; + break; + case LOG_TRACE: + severityStr = "TRACE "; + break; + default: + break; + } + + std::string message = CStringUtils::string_format(f, args...); + boost::trim(message); + std::string timeUtc; + getTimeStamp(timeUtc); + std::stringstream s; + s << "[" << timeUtc << "] [" << severityStr << "] " << message << std::endl; + + output = s.str(); + } + public: - static void initialize(const std::string& logfile, LOG_SEVERITY logLevel, bool logToConsole); + static void addTarget(CLogTarget * target); + static void finalise(); template static void logDebug(const std::string & f, Args... args) { - log(LS_DEBUG, f, args...); + log(LOG_DEBUG, f, args...); } template static void logInfo(const std::string & f, Args... args) { - log(LS_INFO, f, args...); + log(LOG_INFO, f, args...); } template static void logWarning(const std::string & f, Args... args) { - log(LS_WARNING, f, args...); + log(LOG_WARNING, f, args...); } template static void logError(const std::string & f, Args... args) { - log(LS_ERROR, f, args...); + log(LOG_ERROR, f, args...); } template static void logFatal(const std::string & f, Args... args) { - log(LS_FATAL, f, args...); + log(LOG_FATAL, f, args...); } template static void log(LOG_SEVERITY severity, const std::string & f, Args... args) { - if(severity >= CLog::m_level || CLog::m_file.empty()) { - std::string severityStr; - switch (severity) - { - case LS_DEBUG: - severityStr = "DEBUG "; - break; - case LS_ERROR: - severityStr = "ERROR "; - break; - case LS_FATAL: - severityStr = "FATAL "; - break; - case LS_INFO : - severityStr = "INFO "; - break; - case LS_WARNING: - severityStr = "WARNING"; - break; - case LS_TRACE: - severityStr = "TRACE "; - break; - default: - break; - } + std::lock_guard lockTarget(m_targetsMutex); + + std::string msg; + for(auto target : m_targets) { + if(severity >= target->getLevel()) { + + if(msg.empty()) + formatLogMessage(msg, severity, f, args...); - std::string message = CStringUtils::string_format(f, args...); - boost::trim(message); - std::string timeUtc; - getTimeStamp(timeUtc); - std::stringstream s; - s << "[" << timeUtc << "] [" << severityStr << "] " << message << std::endl; - - if(CLog::m_logToConsole || CLog::m_file.empty()) - std::cout << s.str(); - - std::ofstream file; - file.open(CLog::m_file, std::ios::app); - if(file.is_open()) { - file << s.str(); - file.close(); + target->printLog(msg); } } } diff --git a/LogConsoleTarget.cpp b/LogConsoleTarget.cpp new file mode 100644 index 0000000..78cdabc --- /dev/null +++ b/LogConsoleTarget.cpp @@ -0,0 +1,33 @@ +/* + * 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 + +#include "LogConsoleTarget.h" + +CLogConsoleTarget::CLogConsoleTarget(LOG_SEVERITY logLevel) : +CLogTarget(logLevel) +{ + +} + +void CLogConsoleTarget::printLogInt(const std::string& msg) +{ + std::cout << msg; +} \ No newline at end of file diff --git a/LogConsoleTarget.h b/LogConsoleTarget.h new file mode 100644 index 0000000..7ef2da8 --- /dev/null +++ b/LogConsoleTarget.h @@ -0,0 +1,30 @@ +/* + * 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 "LogTarget.h" + +class CLogConsoleTarget : public CLogTarget +{ +public: + CLogConsoleTarget(LOG_SEVERITY logLevel); + +protected: + virtual void printLogInt(const std::string& msg); +}; \ No newline at end of file diff --git a/LogFileTarget.cpp b/LogFileTarget.cpp new file mode 100644 index 0000000..a44dd35 --- /dev/null +++ b/LogFileTarget.cpp @@ -0,0 +1,57 @@ +/* + * 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 +#include +#include +#include + +#include "LogFileTarget.h" + +CLogFileTarget::CLogFileTarget(LOG_SEVERITY logLevel, const std::string & dir, bool rotate) : +CLogTarget(logLevel), +m_dir(dir), +m_rotate(rotate) +{ + +} + +void CLogFileTarget::printLogInt(const std::string& msg) +{ + // construct filename + std::string fileName(m_dir); + if(fileName[fileName.length() - 1U] != '/') fileName.push_back('/'); + fileName.append("dstargateway"); + + if(m_rotate) { + std::time_t now = std::time(0); + std::tm* now_tm = std::gmtime(&now); + char buf[64]; + std::strftime(buf, 42, "-%Y-%m-%d", now_tm); + fileName.append(std::string(buf)); + } + fileName.append(".log"); + + std::ofstream file; + file.open(fileName, std::ios::app); + if(file.is_open()) { + file << msg; + file.close(); + } +} \ No newline at end of file diff --git a/LogFileTarget.h b/LogFileTarget.h new file mode 100644 index 0000000..2a8ad1d --- /dev/null +++ b/LogFileTarget.h @@ -0,0 +1,36 @@ +/* + * 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 "LogTarget.h" + +class CLogFileTarget : public CLogTarget +{ +public: + CLogFileTarget(LOG_SEVERITY logLevel, const std::string& directory, bool rotate); + +protected: + virtual void printLogInt(const std::string& msg); + +private: + std::string m_dir; + bool m_rotate; +}; \ No newline at end of file diff --git a/LogSeverity.h b/LogSeverity.h new file mode 100644 index 0000000..fe1144f --- /dev/null +++ b/LogSeverity.h @@ -0,0 +1,29 @@ +/* + * 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 + +enum LOG_SEVERITY : unsigned int { + LOG_TRACE = 1, + LOG_DEBUG, + LOG_INFO, + LOG_WARNING, + LOG_ERROR, + LOG_FATAL, + LOG_NONE = 0xFFFFFFFF +}; \ No newline at end of file diff --git a/LogTarget.cpp b/LogTarget.cpp new file mode 100644 index 0000000..ea60270 --- /dev/null +++ b/LogTarget.cpp @@ -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. + */ + +#include "LogTarget.h" + +CLogTarget::CLogTarget(LOG_SEVERITY logLevel) : +m_logLevel(logLevel) +{ + +} + +CLogTarget::~CLogTarget() +{ + +} + +void CLogTarget::printLog(const std::string& msg) +{ + printLogInt(msg); +} \ No newline at end of file diff --git a/LogTarget.h b/LogTarget.h new file mode 100644 index 0000000..1f372e1 --- /dev/null +++ b/LogTarget.h @@ -0,0 +1,38 @@ +/* + * 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 "LogSeverity.h" + +class CLogTarget +{ +public: + CLogTarget(LOG_SEVERITY logLevel); + virtual ~CLogTarget(); + void printLog(const std::string& msg); + LOG_SEVERITY getLevel() { return m_logLevel; } + +protected: + virtual void printLogInt(const std::string& msg) = 0; + +private: + LOG_SEVERITY m_logLevel; +}; \ No newline at end of file diff --git a/Makefile b/Makefile index 3e1e2e4..d959810 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,13 @@ CPPFLAGS=-g -ggdb -W -Wall -std=c++17 # or, you can choose this for a much smaller executable without debugging help #CPPFLAGS=-W -Wall -std=c++17 +LDFLAGS:=-lcurl -pthread + +ifeq ($(USE_GPSD), 1) +CPPFLAGS+= -DUSE_GPSD +LDFLAGS+= -lgps +endif + SRCS = $(wildcard *.cpp) OBJS = $(SRCS:.cpp=.o) DEPS = $(SRCS:.cpp=.d) @@ -34,16 +41,20 @@ DEPS = $(SRCS:.cpp=.d) all: dstargateway dstargateway : GitVersion.h $(OBJS) - g++ $(CPPFLAGS) -o dstargateway $(OBJS) -lcurl -pthread + g++ $(CPPFLAGS) -o dstargateway $(OBJS) $(LDFLAGS) %.o : %.cpp g++ $(CPPFLAGS) -MMD -MD -c $< -o $@ GitVersion.h : FORCE ifneq ("$(wildcard .git/index)","") - @echo "#pragma once\nconst char *gitversion = \"$(shell git rev-parse HEAD)\";" > /tmp/$@ + @echo "#pragma once" > /tmp/$@ + @echo "#include " >> /tmp/$@ + @echo "const std::string gitversion(\"$(shell git rev-parse --short HEAD)\");" >> /tmp/$@ else - @echo "#pragma once\nconst char *gitversion = \"0000000000000000000000000000000000000000\";" > /tmp/$@ + @echo "#pragma once" > /tmp/$@ + @echo "#include " >> /tmp/$@ + @echo "const std::string gitversion(\"0000000\");" >> /tmp/$@ endif @cmp -s /tmp/$@ $@; \ RETVAL=$$?; \ diff --git a/README.md b/README.md index 75bd64e..b04a1a7 100644 --- a/README.md +++ b/README.md @@ -4,29 +4,34 @@ - [2.2. Code Credit](#22-code-credit) - [2.3. Features](#23-features) - [3. Building and installing](#3-building-and-installing) - - [3.1. Get latest stable version](#31-get-latest-stable-version) - - [3.2. Prerequisites and dependencies](#32-prerequisites-and-dependencies) - - [3.3. Building](#33-building) - - [3.4. Installing](#34-installing) - - [3.5. Configuring](#35-configuring) + - [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.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) + - [3.6. Installing](#36-installing) + - [3.7. Configuring](#37-configuring) - [4. Contributing](#4-contributing) - [4.1. Work Flow](#41-work-flow) - [5. Version History](#5-version-history) - - [5.1. Version 0.3](#51-version-03) - - [5.2. Version 0.2](#52-version-02) - - [5.3. Version 0.1](#53-version-01) + - [5.1. Version 0.4](#51-version-04) + - [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), libconfig++ 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) @@ -37,17 +42,19 @@ 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. -- 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. -- Mobile APRS: Code has been ported, yet I am targeting repeaters so low priority. +- 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 : 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. Get latest stable version +## 3.1. Initial setup Clone the repository (only required initally) ``` git clone https://github.com/F4FXL/DStarGateway.git cd DStarGateway ``` +## 3.2. Get latest stable version (recommended) From inside the cloned repository run following commands to get the latest stable version ``` git pull -p @@ -55,22 +62,35 @@ git fetch --tags latestTag=$(git describe --tags `git rev-list --tags --max-count=1`) git checkout $latestTag ``` +## 3.3. Get latest development version version +``` +git checkout develop +``` -## 3.2. Prerequisites and dependencies +## 3.4. Prerequisites and dependencies Before first time building you need to install dependencies and prerequisites ``` -apt install build-essential libconfig++-dev libcurl4-openssl-dev libboost-dev +apt install build-essential libcurl4-openssl-dev libboost-dev +``` +If you are going to build with gpsd support, also install libgps-dev +``` +apt install libgps-dev ``` -## 3.3. Building +## 3.5. Building +Regular building ``` make ``` -## 3.4. Installing +#### 3.5.0.1. Build With GPSD Support +``` +make USE_GPS=1 +``` +## 3.6. Installing The program is meant to run as a systemd service. All bits an pieces are provided. ``` sudo make install ``` -## 3.5. Configuring +## 3.7. Configuring After installing you have to edit the configuration file. If you went with default paths, the config file is located in `/usr/local/etc/dstargateway.cfg` The configuration format is quite straight forward. It is organised in sections and key/value pairs. @@ -85,13 +105,32 @@ 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.3 +## 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) +## 5.2. Version 0.3 - [Improvement] Get ride of libcongif++ dependency. When upgrading from earlier version you need to manualy delete the config file before reinstalling. -## 5.2. Version 0.2 -- [bugfix] ircDDBFreeze when repeater not found ([#1](https://github.com/F4FXL/DStarGateway/issues/1)) +## 5.3. Version 0.2 +- [Bugfix] ircDDBFreeze when repeater not found ([#1](https://github.com/F4FXL/DStarGateway/issues/1)) - Code sanitization -## 5.3. Version 0.1 -First working version \ No newline at end of file +## 5.4. 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 : +- ☒ 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 to APRS-IS 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 :) 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 diff --git a/Version.h b/Version.h index 1065953..e888470 100644 --- a/Version.h +++ b/Version.h @@ -22,5 +22,10 @@ #include -const std::string VENDOR_NAME("Geoffrey Merck F4FXL / KC3FRA"); -const std::string VERSION("0.3"); +#include "GitVersion.h" + +const std::string PRODUCT_NAME("DStarGateway"); +const std::string VENDOR_NAME("Geoffrey Merck F4FXL / KC3FRA and Contributors"); +const std::string VERSION("0.4"); +const std::string FULL_PRODUCT_NAME = PRODUCT_NAME + " v" + VERSION + "-" + gitversion; +const std::string SHORT_PRODUCT_NAME = "DStarGW v" + VERSION + "-" + gitversion; diff --git a/VersionUnit.cpp b/VersionUnit.cpp index 36ea096..a382039 100644 --- a/VersionUnit.cpp +++ b/VersionUnit.cpp @@ -45,13 +45,13 @@ m_out(0U) m_data = new CAMBEData*[NUM_FRAMES]; - char vstr[32]; - snprintf(vstr, 32, "DStar GW - %s", VERSION.substr(0, 8).c_str()); + auto vstr = SHORT_PRODUCT_NAME; + vstr.resize(NUM_FRAMES, ' '); CLog::logInfo("Version text set to \"%s\"\n", vstr); CSlowDataEncoder encoder; - encoder.setTextData(std::string(vstr)); + encoder.setTextData(vstr); // Seq No and end for (unsigned int i = 0U; i < NUM_FRAMES; i++) { diff --git a/example.cfg b/example.cfg index d84358c..81c1103 100644 --- a/example.cfg +++ b/example.cfg @@ -139,9 +139,18 @@ enabled=true hostname=rotate.aprs2.net # Defaults to rotate.aprs2.net port=14580 # Defaults to 14580, there is no reason to change this password=12345 +positionSource= # Sets how the position is determined fixed or gpsd. this is ignored if DStargateway was built without GPSD support + +[GPSD] +address= # GPSD address, defaults to 127.0.0.1 +port= # GPSD port, defaults to 2947 [Log] path=/var/log/dstargateway/ +fileRoot= # defaults to dstarGateway +fileRotate= # rotate log files daily, defaults to true +fileLevel= # defaults to info, valid values are trace, debug, info, warning, error, fatal, none +displayLevel= # defaults to info, valid values are trace, debug, info, warning, error, fatal, none [Paths] data=/usr/local/share/dstargateway.d/ #Path where the data (hostfiles, audio files etc) can be found