Merge branch 'release/0.4'

master v0.4
Geoffrey Merck 4 years ago
commit 277e8766ff

@ -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 <boost/algorithm/string.hpp>
#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;
}
}

@ -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 <string>
#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;
};

@ -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);
}

@ -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 <string>
#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;
};

@ -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 <string>
#include <cmath>
#include <boost/algorithm/string.hpp>
#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<std::string>& 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;
}

@ -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<std::string>& frames);
};

@ -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 <cmath>
#include <boost/algorithm/string.hpp>
#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<std::string>& 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

@ -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 <gps.h>
#include <string>
#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<std::string>& frames);
private:
std::string m_gpsdAddress;
std::string m_gpsdPort;
struct gps_data_t m_gpsdData;
bool m_hasConnection;
};
#endif

@ -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 <cassert>
#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<std::string> & frames)
{
assert(entry != nullptr);
return buildAPRSFramesInt(gateway, entry, frames);
}
bool CAPRSIdFrameProvider::wantsToSend()
{
if(m_timer.hasExpired())
{
m_timer.start();
return true;
}
return false;
}

@ -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 <vector>
#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<std::string>& 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<std::string>& frames) = 0;
void setTimeout(unsigned int timeout)
{
m_timer.start(timeout);
}
private:
CTimer m_timer;
};

@ -20,6 +20,7 @@
#include <cassert> #include <cassert>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <cmath> #include <cmath>
#include <cassert>
#include "StringUtils.h" #include "StringUtils.h"
#include "Log.h" #include "Log.h"
@ -28,111 +29,13 @@
#include "Defs.h" #include "Defs.h"
#include "Log.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) : 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_thread(NULL),
m_idTimer(1000U),
m_gateway(), m_gateway(),
m_address(), m_address(),
m_port(0U), m_port(0U),
m_socket(NULL), m_array(),
m_array() m_idFrameProvider(nullptr)
{ {
assert(!hostname.empty()); assert(!hostname.empty());
assert(port > 0U); assert(port > 0U);
@ -155,7 +58,7 @@ CAPRSWriter::~CAPRSWriter()
m_array.clear(); 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; std::string temp = callsign;
temp.resize(LONG_CALLSIGN_LENGTH - 1U, ' '); 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); 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() 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(); return m_thread->start();
} }
@ -287,316 +158,85 @@ void CAPRSWriter::writeData(const std::string& callsign, const CAMBEData& data)
collector->reset(); collector->reset();
} }
void CAPRSWriter::clock(unsigned int ms) void CAPRSWriter::writeStatus(const std::string& callsign, const std::string status)
{
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()
{ {
if (m_socket != NULL) { CAPRSEntry* entry = m_array[callsign];
m_socket->close(); if (entry == NULL) {
delete m_socket; 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() void CAPRSWriter::clock(unsigned int ms)
{
assert(m_socket != NULL);
return m_socket->write((unsigned char*)"ircDDBGateway", 13U, m_address, m_port);
}
void CAPRSWriter::sendIdFramesFixed()
{ {
if (!m_thread->isConnected()) m_thread->clock(ms);
return;
time_t now; if(m_idFrameProvider != nullptr) {
::time(&now); m_idFrameProvider->clock(ms);
struct tm* tm = ::gmtime(&now); if(m_idFrameProvider->wantsToSend()) {
sendIdFrames();
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";
} }
}
std::string band; for (auto it : m_array) {
if (entry->getFrequency() >= 1200.0) if(it.second != NULL) {
band = "1.2"; it.second->clock(ms);
else if (entry->getFrequency() >= 420.0) if(it.second->getStatus().isOutOfDate())
band = "440"; sendStatusFrame(it.second);
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);
} }
} }
} }
void CAPRSWriter::sendIdFramesMobile() void CAPRSWriter::sendStatusFrame(CAPRSEntry * entry)
{ {
// Grab GPS data if it's available assert(entry != nullptr);
unsigned char buffer[200U];
in_addr address;
unsigned int port;
int ret = m_socket->read(buffer, 200U, address, port);
if (ret <= 0)
return;
if (!m_thread->isConnected()) if(!m_thread->isConnected())
return; return;
buffer[ret] = '\0'; auto linkStatus = entry->getStatus();
std::string body = boost::trim_copy(linkStatus.getStatus());
// Parse the GPS data if(body[0] != '>')
char* pLatitude = ::strtok((char*)buffer, ",\n"); // Latitude body = '>' + body;
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 (pLatitude == NULL || pLongitude == NULL || pAltitude == NULL) std::string output = CStringUtils::string_format("%s-%s>APD5T3,TCPIP*,qAC,%s-%sS:%s\r\n",
return; entry->getCallsign().c_str(), entry->getBand().c_str(), entry->getCallsign().c_str(), entry->getBand().c_str(),
body.c_str());
double rawLatitude = ::atof(pLatitude); m_thread->write(output.c_str());
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";
}
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; void CAPRSWriter::sendIdFrames()
output3 = CStringUtils::string_format("RNG%04.0lf %s %s", entry->getRange() * 0.6214, band.c_str(), desc.c_str()); {
if(m_thread->isConnected())
char ascii[300U]; {
::memset(ascii, 0x00, 300U); for(auto entry : m_array) {
unsigned int n = 0U; std::vector<std::string> frames;
for (unsigned int i = 0U; i < output1.length(); i++, n++) if(m_idFrameProvider->buildAPRSFrames(m_gateway, entry.second, frames)) {
ascii[n] = output1[i]; for(auto frame : frames) {
for (unsigned int i = 0U; i < output2.length(); i++, n++) m_thread->write(frame.c_str());
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);
} }
} }
} }
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();
}

@ -20,9 +20,13 @@
#ifndef APRSWriter_H #ifndef APRSWriter_H
#define APRSWriter_H #define APRSWriter_H
#include "Defs.h"
#include <unordered_map> #include <unordered_map>
#include <string> #include <string>
#include "APRSEntry.h"
#include "APRSWriterThread.h" #include "APRSWriterThread.h"
#include "UDPReaderWriter.h" #include "UDPReaderWriter.h"
#include "APRSCollector.h" #include "APRSCollector.h"
@ -30,41 +34,7 @@
#include "HeaderData.h" #include "HeaderData.h"
#include "AMBEData.h" #include "AMBEData.h"
#include "Timer.h" #include "Timer.h"
#include "Defs.h" #include "APRSIdFrameProvider.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;
};
class CAPRSWriter { class CAPRSWriter {
public: public:
@ -73,14 +43,16 @@ public:
bool open(); 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 writeHeader(const std::string& callsign, const CHeaderData& header);
void writeData(const std::string& callsign, const CAMBEData& data); void writeData(const std::string& callsign, const CAMBEData& data);
void writeStatus(const std::string& callsign, const std::string status);
bool isConnected() const; bool isConnected() const;
void clock(unsigned int ms); void clock(unsigned int ms);
@ -89,16 +61,14 @@ public:
private: private:
CAPRSWriterThread* m_thread; CAPRSWriterThread* m_thread;
CTimer m_idTimer;
std::string m_gateway; std::string m_gateway;
in_addr m_address; in_addr m_address;
unsigned int m_port; unsigned int m_port;
CUDPReaderWriter* m_socket;
std::unordered_map<std::string,CAPRSEntry *> m_array; std::unordered_map<std::string,CAPRSEntry *> m_array;
CAPRSIdFrameProvider * m_idFrameProvider;
bool pollGPS(); void sendIdFrames();
void sendIdFramesFixed(); void sendStatusFrame(CAPRSEntry * entrry);
void sendIdFramesMobile();
}; };
#endif #endif

@ -26,6 +26,7 @@
#include "Utils.h" #include "Utils.h"
#include "Defs.h" #include "Defs.h"
#include "Log.h" #include "Log.h"
#include "Version.h"
// #define DUMP_TX // #define DUMP_TX
@ -44,7 +45,7 @@ m_reconnectTimer(1000U),
m_tries(0U), m_tries(0U),
m_APRSReadCallback(NULL), m_APRSReadCallback(NULL),
m_filter(""), m_filter(""),
m_clientName("DStarGateway") m_clientName(FULL_PRODUCT_NAME)
{ {
assert(!callsign.empty()); assert(!callsign.empty());
assert(!password.empty()); assert(!password.empty());
@ -154,7 +155,7 @@ void* CAPRSWriterThread::Entry()
if (length < 0) { if (length < 0) {
m_connected = false; m_connected = false;
m_socket.close(); m_socket.close();
CLog::logInfo("Error when reading from the APRS server"); CLog::logError("Error when reading from the APRS server");
startReconnectionTimer(); startReconnectionTimer();
} }
@ -249,7 +250,7 @@ bool CAPRSWriterThread::connect()
std::stringstream connectString; std::stringstream connectString;
connectString << "User " << m_username << "-" << m_ssid connectString << "User " << m_username << "-" << m_ssid
<< " pass " << m_password << " pass " << m_password
<< " vers " << (m_clientName.length() ? m_clientName : "DStarGateway") << " vers " << (!m_clientName.empty() ? m_clientName : FULL_PRODUCT_NAME)
<< filter; << filter;
//CLog::logInfo("Connect String : ") + connectString); //CLog::logInfo("Connect String : ") + connectString);
ret = m_socket.writeLine(connectString.str()); ret = m_socket.writeLine(connectString.str());

@ -36,6 +36,10 @@
#include "RepeaterProtocolHandlerFactory.h" #include "RepeaterProtocolHandlerFactory.h"
#include "XLXHostsFileDownloader.h" #include "XLXHostsFileDownloader.h"
#include "Log.h" #include "Log.h"
#include "LogFileTarget.h"
#include "LogConsoleTarget.h"
#include "APRSGPSDIdFrameProvider.h"
#include "APRSFixedIdFrameProvider.h"
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
@ -47,7 +51,7 @@ int main(int argc, char *argv[])
} }
if ('-' == argv[1][0]) { 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("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"); 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; return 0;
@ -84,11 +88,12 @@ void CDStarGatewayApp::run()
m_thread->Run(); m_thread->Run();
m_thread->Wait(); m_thread->Wait();
CLog::logInfo("exiting\n"); CLog::logInfo("exiting\n");
CLog::finalise();
} }
bool CDStarGatewayApp::createThread() 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("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"); 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; return false;
} }
// Setup Log
TLog log; TLog log;
config.getLog(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; Tpaths paths;
config.getPaths(paths); config.getPaths(paths);
@ -113,6 +121,12 @@ bool CDStarGatewayApp::createThread()
m_thread->setLanguage(gatewayConfig.language); m_thread->setLanguage(gatewayConfig.language);
m_thread->setLocation(gatewayConfig.latitude, gatewayConfig.longitude); m_thread->setLocation(gatewayConfig.latitude, gatewayConfig.longitude);
#ifdef USE_GPSD
// Setup GPSD
TGPSD gpsdConfig;
config.getGPSD(gpsdConfig);
#endif
// Setup APRS // Setup APRS
TAPRS aprsConfig; TAPRS aprsConfig;
config.getAPRS(aprsConfig); config.getAPRS(aprsConfig);
@ -120,6 +134,14 @@ bool CDStarGatewayApp::createThread()
if(aprsConfig.enabled && !aprsConfig.password.empty()) { if(aprsConfig.enabled && !aprsConfig.password.empty()) {
aprsWriter = new CAPRSWriter(aprsConfig.hostname, aprsConfig.port, gatewayConfig.callsign, aprsConfig.password, gatewayConfig.address); aprsWriter = new CAPRSWriter(aprsConfig.hostname, aprsConfig.port, gatewayConfig.callsign, aprsConfig.password, gatewayConfig.address);
if(aprsWriter->open()) { 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); m_thread->setAPRSWriter(aprsWriter);
} }
else { else {
@ -159,8 +181,9 @@ bool CDStarGatewayApp::createThread()
rptrConfig.band1, rptrConfig.band1,
rptrConfig.band2, rptrConfig.band2,
rptrConfig.band3); 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; if(!ddEnabled) ddEnabled = rptrConfig.band.length() > 1U;
} }
m_thread->setDDModeEnabled(ddEnabled); m_thread->setDDModeEnabled(ddEnabled);
@ -172,7 +195,7 @@ bool CDStarGatewayApp::createThread()
TircDDB ircDDBConfig; TircDDB ircDDBConfig;
config.getIrcDDB(i, 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); 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); clients.push_back(ircDDB);
} }
CIRCDDBMultiClient* multiClient = new CIRCDDBMultiClient(clients); CIRCDDBMultiClient* multiClient = new CIRCDDBMultiClient(clients);

@ -52,6 +52,9 @@ bool CDStarGatewayConfig::load()
ret = loadDPlus(cfg) && ret; ret = loadDPlus(cfg) && ret;
ret = loadRemote(cfg) && ret; ret = loadRemote(cfg) && ret;
ret = loadXLX(cfg) && ret; ret = loadXLX(cfg) && ret;
#ifdef USE_GPSD
ret = loadGPSD(cfg) && ret;
#endif
} }
if(ret) { 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", "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", "hostname", m_aprs.hostname, 0, 1024, "rotate.aprs2.net") && ret;
ret = cfg.getValue("aprs", "password", m_aprs.password, 0U, 30U, "") && 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(); 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 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] != '/') { if(ret && m_log.logDir[m_log.logDir.length() - 1] != '/') {
m_log.logDir.push_back('/'); 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; return ret;
} }
@ -294,6 +332,16 @@ bool CDStarGatewayConfig::loadGateway(const CConfig & cfg)
return ret; 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) bool CDStarGatewayConfig::open(CConfig & cfg)
{ {
try { try {
@ -383,3 +431,10 @@ void CDStarGatewayConfig::getXLX(TXLX & xlx) const
{ {
xlx = m_xlx; xlx = m_xlx;
} }
#ifdef USE_GPSD
void CDStarGatewayConfig::getGPSD(TGPSD & gpsd) const
{
gpsd = m_gpsd;
}
#endif

@ -23,6 +23,7 @@
#include "Defs.h" #include "Defs.h"
#include "Config.h" #include "Config.h"
#include "LogSeverity.h"
typedef struct { typedef struct {
GATEWAY_TYPE type; GATEWAY_TYPE type;
@ -79,6 +80,10 @@ typedef struct {
typedef struct { typedef struct {
std::string logDir; std::string logDir;
LOG_SEVERITY m_displayLevel;
LOG_SEVERITY m_fileLevel;
std::string m_fileRoot;
bool m_fileRotate;
} TLog; } TLog;
typedef struct { typedef struct {
@ -86,6 +91,7 @@ typedef struct {
std::string hostname; std::string hostname;
unsigned int port; unsigned int port;
std::string password; std::string password;
POSITION_SOURCE m_positionSource;
} TAPRS; } TAPRS;
typedef struct { typedef struct {
@ -115,6 +121,12 @@ typedef struct {
std::string password; std::string password;
} TRemote; } TRemote;
#ifdef USE_GPSD
typedef struct {
std::string m_address;
std::string m_port;
} TGPSD;
#endif
class CDStarGatewayConfig { class CDStarGatewayConfig {
public: public:
@ -135,6 +147,9 @@ public:
void getDCS(TDCS & dcs) const; void getDCS(TDCS & dcs) const;
void getRemote(TRemote & remote) const; void getRemote(TRemote & remote) const;
void getXLX(TXLX & xlx) const; void getXLX(TXLX & xlx) const;
#ifdef USE_GPSD
void getGPSD(TGPSD & gpsd) const;
#endif
private: private:
bool open(CConfig & cfg); bool open(CConfig & cfg);
@ -149,6 +164,9 @@ private:
bool loadDCS(const CConfig & cfg); bool loadDCS(const CConfig & cfg);
bool loadRemote(const CConfig & cfg); bool loadRemote(const CConfig & cfg);
bool loadXLX(const CConfig & cfg); bool loadXLX(const CConfig & cfg);
#ifdef USE_GPSD
bool loadGPSD(const CConfig & cfg);
#endif
std::string m_fileName; std::string m_fileName;
TGateway m_gateway; TGateway m_gateway;
@ -160,6 +178,9 @@ private:
TRemote m_remote; TRemote m_remote;
TXLX m_xlx; TXLX m_xlx;
TLog m_log; TLog m_log;
#ifdef USE_GPSD
TGPSD m_gpsd;
#endif
std::vector<TRepeater *> m_repeaters; std::vector<TRepeater *> m_repeaters;
std::vector<TircDDB *> m_ircDDB; std::vector<TircDDB *> m_ircDDB;
}; };

@ -745,13 +745,13 @@ void CDStarGatewayThread::processIrcDDB()
break; break;
if (!address.empty()) { 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); m_cache.updateUser(user, repeater, gateway, address, timestamp, DP_DEXTRA, false, false);
#if defined(ENABLE_NAT_TRAVERSAL) #if defined(ENABLE_NAT_TRAVERSAL)
m_natTraversal->traverseNatG2(address); m_natTraversal->traverseNatG2(address);
#endif #endif
} else { } else {
CLog::logInfo("USER: %s NOT FOUND", user.c_str()); CLog::logDebug("USER: %s NOT FOUND", user.c_str());
} }
} }
break; break;
@ -764,13 +764,13 @@ void CDStarGatewayThread::processIrcDDB()
CRepeaterHandler::resolveRepeater(repeater, gateway, address, DP_DEXTRA); CRepeaterHandler::resolveRepeater(repeater, gateway, address, DP_DEXTRA);
if (!address.empty()) { 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); m_cache.updateRepeater(repeater, gateway, address, DP_DEXTRA, false, false);
#if defined(ENABLE_NAT_TRAVERSAL) #if defined(ENABLE_NAT_TRAVERSAL)
m_natTraversal->traverseNatG2(address); m_natTraversal->traverseNatG2(address);
#endif #endif
} else { } else {
CLog::logInfo("REPEATER: %s NOT FOUND", repeater.c_str()); CLog::logDebug("REPEATER: %s NOT FOUND", repeater.c_str());
} }
} }
break; break;
@ -784,13 +784,13 @@ void CDStarGatewayThread::processIrcDDB()
CDExtraHandler::gatewayUpdate(gateway, address); CDExtraHandler::gatewayUpdate(gateway, address);
CDPlusHandler::gatewayUpdate(gateway, address); CDPlusHandler::gatewayUpdate(gateway, address);
if (!address.empty()) { 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); m_cache.updateGateway(gateway, address, DP_DEXTRA, false, false);
#if defined(ENABLE_NAT_TRAVERSAL) #if defined(ENABLE_NAT_TRAVERSAL)
m_natTraversal->traverseNatG2(address); m_natTraversal->traverseNatG2(address);
#endif #endif
} else { } else {
CLog::logInfo("GATEWAY: %s NOT FOUND", gateway.c_str()); CLog::logDebug("GATEWAY: %s NOT FOUND", gateway.c_str());
} }
} }
break; break;

@ -137,5 +137,10 @@ enum GATEWAY_TYPE {
GT_SMARTGROUP GT_SMARTGROUP
}; };
enum POSITION_SOURCE {
POSSRC_FIXED,
POSSRC_GPSD
};
const unsigned int TIME_PER_TIC_MS = 5U; const unsigned int TIME_PER_TIC_MS = 5U;

@ -255,7 +255,7 @@ IRCDDB_RESPONSE_TYPE IRCDDBApp::getReplyMessageType()
if (0 == msgType.compare("IDRT_GATEWAY")) if (0 == msgType.compare("IDRT_GATEWAY"))
return 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; return IDRT_NONE;
} }

@ -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 ) const std::string& rpt2, unsigned char flag1, unsigned char flag2, unsigned char flag3 )
{ {
if (myCall.size() != 8) { 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; return false;
} }
if (myCallExt.size() != 4) { 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; return false;
} }
if (yourCall.size() != 8) { 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; return false;
} }
if (rpt1.size() != 8) { 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; return false;
} }
if (rpt2.size() != 8) { 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; 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<std::string> parms) void CIRCDDBClient::sendDStarGatewayInfo(const std::string subcommand, const std::vector<std::string> 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++) for(unsigned int i=0; i < parms.size();i++)
CLog::logInfo(" %s", parms[i].c_str()); CLog::logInfo(" %s", parms[i].c_str());
CLog::logInfo("\n"); 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) 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) { 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; return false;
} }
if (myCallExt.size() != 4) { 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; return false;
} }
if (yourCall.size() != 8) { 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; return false;
} }
if (rpt1.size() != 8) { 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; return false;
} }
if (rpt2.size() != 8) { 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; return false;
} }
@ -161,7 +161,7 @@ bool CIRCDDBClient::sendHeardWithTXMsg(const std::string& myCall, const std::str
dest = std::string(" "); dest = std::string(" ");
if (8 != dest.size()) { 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; 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) 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)) { 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; return false;
} }
if (num_dv_silent_frames > num_dv_frames) { 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; return false;
} }
if (num_bit_errors > (4*num_dv_frames)) { // max 4 bit errors per frame 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; return false;
} }
if (myCall.size() != 8) { 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; return false;
} }
if (myCallExt.size() != 4) { 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; return false;
} }
if (yourCall.size() != 8) { 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; return false;
} }
if (rpt1.size() != 8) { 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; return false;
} }
if (rpt2.size() != 8) { 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; return false;
} }
@ -245,7 +245,7 @@ bool CIRCDDBClient::sendHeardWithTXStats( const std::string& myCall, const std::
bool CIRCDDBClient::findGateway(const std::string& gatewayCallsign) bool CIRCDDBClient::findGateway(const std::string& gatewayCallsign)
{ {
if (8 != gatewayCallsign.size()) { 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; return false;
} }
std::string gw(gatewayCallsign); std::string gw(gatewayCallsign);
@ -257,7 +257,7 @@ bool CIRCDDBClient::findGateway(const std::string& gatewayCallsign)
bool CIRCDDBClient::findRepeater(const std::string& repeaterCallsign) bool CIRCDDBClient::findRepeater(const std::string& repeaterCallsign)
{ {
if (8 != repeaterCallsign.size()) { 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; return false;
} }
std::string rptr(repeaterCallsign); std::string rptr(repeaterCallsign);
@ -269,7 +269,7 @@ bool CIRCDDBClient::findRepeater(const std::string& repeaterCallsign)
bool CIRCDDBClient::findUser(const std::string& userCallsign) bool CIRCDDBClient::findUser(const std::string& userCallsign)
{ {
if (8 != userCallsign.size()) { 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; return false;
} }
std::string usr(userCallsign); std::string usr(userCallsign);
@ -292,24 +292,24 @@ bool CIRCDDBClient::receiveRepeater(std::string& repeaterCallsign, std::string&
IRCDDB_RESPONSE_TYPE rt = d->app->getReplyMessageType(); IRCDDB_RESPONSE_TYPE rt = d->app->getReplyMessageType();
if (rt != IDRT_REPEATER) { 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; return false;
} }
IRCMessage *m = d->app->getReplyMessage(); IRCMessage *m = d->app->getReplyMessage();
if (m == NULL) { if (m == NULL) {
CLog::logInfo("CIRCDDBClient::receiveRepeater: no message\n"); CLog::logDebug("CIRCDDBClient::receiveRepeater: no message\n");
return false; return false;
} }
if (m->getCommand().compare("IDRT_REPEATER")) { 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; delete m;
return false; return false;
} }
if (3 != m->getParamCount()) { 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; delete m;
return false; return false;
} }
@ -328,25 +328,25 @@ bool CIRCDDBClient::receiveGateway(std::string& gatewayCallsign, std::string& ad
IRCDDB_RESPONSE_TYPE rt = d->app->getReplyMessageType(); IRCDDB_RESPONSE_TYPE rt = d->app->getReplyMessageType();
if (rt != IDRT_GATEWAY) { 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; return false;
} }
IRCMessage *m = d->app->getReplyMessage(); IRCMessage *m = d->app->getReplyMessage();
if (m == NULL) { if (m == NULL) {
CLog::logInfo("CIRCDDBClient::receiveGateway: no message\n"); CLog::logDebug("CIRCDDBClient::receiveGateway: no message\n");
return false; return false;
} }
if (m->getCommand().compare("IDRT_GATEWAY")) { 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; delete m;
return false; return false;
} }
if (2 != m->getParamCount()) { 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; delete m;
return false; return false;
} }
@ -370,25 +370,25 @@ bool CIRCDDBClient::receiveUser(std::string& userCallsign, std::string& repeater
IRCDDB_RESPONSE_TYPE rt = d->app->getReplyMessageType(); IRCDDB_RESPONSE_TYPE rt = d->app->getReplyMessageType();
if (rt != IDRT_USER) { 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; return false;
} }
IRCMessage * m = d->app->getReplyMessage(); IRCMessage * m = d->app->getReplyMessage();
if (m == NULL) { if (m == NULL) {
CLog::logInfo("CIRCDDBClient::receiveUser: no message\n"); CLog::logDebug("CIRCDDBClient::receiveUser: no message\n");
return false; return false;
} }
if (m->getCommand().compare("IDRT_USER")) { 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; delete m;
return false; return false;
} }
if (5 != m->getParamCount()) { 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; delete m;
return false; return false;
} }

@ -18,18 +18,39 @@
*/ */
#include <ctime> #include <ctime>
#include <sstream>
#include <cassert>
#include "Log.h" #include "Log.h"
#include "LogConsoleTarget.h"
LOG_SEVERITY CLog::m_level = LS_INFO; bool CLog::m_addedTargets(false);
std::string CLog::m_file = ""; std::recursive_mutex CLog::m_targetsMutex;
bool CLog::m_logToConsole = true; std::vector<CLogTarget *> 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; assert(target != nullptr);
m_level = logLevel;
m_logToConsole = logToConsole; 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) void CLog::getTimeStamp(std::string & s)
@ -39,4 +60,4 @@ void CLog::getTimeStamp(std::string & s)
char buf[64]; char buf[64];
std::strftime(buf, 42, "%Y-%m-%d %T", now_tm); std::strftime(buf, 42, "%Y-%m-%d %T", now_tm);
s = std::string(buf); s = std::string(buf);
} }

125
Log.h

@ -20,104 +20,105 @@
#pragma once #pragma once
#include <string> #include <string>
#include <iostream> #include <vector>
#include <fstream>
#include <chrono>
#include <sstream>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <mutex>
#include <sstream>
#include <cassert>
#include "StringUtils.h" #include "StringUtils.h"
#include "LogTarget.h"
enum LOG_SEVERITY {
LS_TRACE = 1,
LS_DEBUG,
LS_INFO,
LS_WARNING,
LS_ERROR,
LS_FATAL
};
class CLog class CLog
{ {
private: private:
static LOG_SEVERITY m_level; static std::vector<CLogTarget *> m_targets;
static std::string m_file; static bool m_addedTargets;
static bool m_logToConsole; static std::recursive_mutex m_targetsMutex;
static void getTimeStamp(std::string & s); static void getTimeStamp(std::string & s);
template<typename... Args> 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: public:
static void initialize(const std::string& logfile, LOG_SEVERITY logLevel, bool logToConsole); static void addTarget(CLogTarget * target);
static void finalise();
template<typename... Args> static void logDebug(const std::string & f, Args... args) template<typename... Args> static void logDebug(const std::string & f, Args... args)
{ {
log(LS_DEBUG, f, args...); log(LOG_DEBUG, f, args...);
} }
template<typename... Args> static void logInfo(const std::string & f, Args... args) template<typename... Args> static void logInfo(const std::string & f, Args... args)
{ {
log(LS_INFO, f, args...); log(LOG_INFO, f, args...);
} }
template<typename... Args> static void logWarning(const std::string & f, Args... args) template<typename... Args> static void logWarning(const std::string & f, Args... args)
{ {
log(LS_WARNING, f, args...); log(LOG_WARNING, f, args...);
} }
template<typename... Args> static void logError(const std::string & f, Args... args) template<typename... Args> static void logError(const std::string & f, Args... args)
{ {
log(LS_ERROR, f, args...); log(LOG_ERROR, f, args...);
} }
template<typename... Args> static void logFatal(const std::string & f, Args... args) template<typename... Args> static void logFatal(const std::string & f, Args... args)
{ {
log(LS_FATAL, f, args...); log(LOG_FATAL, f, args...);
} }
template<typename... Args> static void log(LOG_SEVERITY severity, const std::string & f, Args... args) template<typename... Args> static void log(LOG_SEVERITY severity, const std::string & f, Args... args)
{ {
if(severity >= CLog::m_level || CLog::m_file.empty()) { std::lock_guard lockTarget(m_targetsMutex);
std::string severityStr;
switch (severity) std::string msg;
{ for(auto target : m_targets) {
case LS_DEBUG: if(severity >= target->getLevel()) {
severityStr = "DEBUG ";
break; if(msg.empty())
case LS_ERROR: formatLogMessage(msg, severity, f, args...);
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::string message = CStringUtils::string_format(f, args...); target->printLog(msg);
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();
} }
} }
} }

@ -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 <iostream>
#include "LogConsoleTarget.h"
CLogConsoleTarget::CLogConsoleTarget(LOG_SEVERITY logLevel) :
CLogTarget(logLevel)
{
}
void CLogConsoleTarget::printLogInt(const std::string& msg)
{
std::cout << msg;
}

@ -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);
};

@ -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 <iostream>
#include <fstream>
#include <chrono>
#include <ctime>
#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();
}
}

@ -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 <string>
#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;
};

@ -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
};

@ -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);
}

@ -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 <string>
#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;
};

@ -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 # or, you can choose this for a much smaller executable without debugging help
#CPPFLAGS=-W -Wall -std=c++17 #CPPFLAGS=-W -Wall -std=c++17
LDFLAGS:=-lcurl -pthread
ifeq ($(USE_GPSD), 1)
CPPFLAGS+= -DUSE_GPSD
LDFLAGS+= -lgps
endif
SRCS = $(wildcard *.cpp) SRCS = $(wildcard *.cpp)
OBJS = $(SRCS:.cpp=.o) OBJS = $(SRCS:.cpp=.o)
DEPS = $(SRCS:.cpp=.d) DEPS = $(SRCS:.cpp=.d)
@ -34,16 +41,20 @@ DEPS = $(SRCS:.cpp=.d)
all: dstargateway all: dstargateway
dstargateway : GitVersion.h $(OBJS) dstargateway : GitVersion.h $(OBJS)
g++ $(CPPFLAGS) -o dstargateway $(OBJS) -lcurl -pthread g++ $(CPPFLAGS) -o dstargateway $(OBJS) $(LDFLAGS)
%.o : %.cpp %.o : %.cpp
g++ $(CPPFLAGS) -MMD -MD -c $< -o $@ g++ $(CPPFLAGS) -MMD -MD -c $< -o $@
GitVersion.h : FORCE GitVersion.h : FORCE
ifneq ("$(wildcard .git/index)","") ifneq ("$(wildcard .git/index)","")
@echo "#pragma once\nconst char *gitversion = \"$(shell git rev-parse HEAD)\";" > /tmp/$@ @echo "#pragma once" > /tmp/$@
@echo "#include <string>" >> /tmp/$@
@echo "const std::string gitversion(\"$(shell git rev-parse --short HEAD)\");" >> /tmp/$@
else else
@echo "#pragma once\nconst char *gitversion = \"0000000000000000000000000000000000000000\";" > /tmp/$@ @echo "#pragma once" > /tmp/$@
@echo "#include <string>" >> /tmp/$@
@echo "const std::string gitversion(\"0000000\");" >> /tmp/$@
endif endif
@cmp -s /tmp/$@ $@; \ @cmp -s /tmp/$@ $@; \
RETVAL=$$?; \ RETVAL=$$?; \

@ -4,29 +4,34 @@
- [2.2. Code Credit](#22-code-credit) - [2.2. Code Credit](#22-code-credit)
- [2.3. Features](#23-features) - [2.3. Features](#23-features)
- [3. Building and installing](#3-building-and-installing) - [3. Building and installing](#3-building-and-installing)
- [3.1. Get latest stable version](#31-get-latest-stable-version) - [3.1. Initial setup](#31-initial-setup)
- [3.2. Prerequisites and dependencies](#32-prerequisites-and-dependencies) - [3.2. Get latest stable version (recommended)](#32-get-latest-stable-version-recommended)
- [3.3. Building](#33-building) - [3.3. Get latest development version version](#33-get-latest-development-version-version)
- [3.4. Installing](#34-installing) - [3.4. Prerequisites and dependencies](#34-prerequisites-and-dependencies)
- [3.5. Configuring](#35-configuring) - [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. Contributing](#4-contributing)
- [4.1. Work Flow](#41-work-flow) - [4.1. Work Flow](#41-work-flow)
- [5. Version History](#5-version-history) - [5. Version History](#5-version-history)
- [5.1. Version 0.3](#51-version-03) - [5.1. Version 0.4](#51-version-04)
- [5.2. Version 0.2](#52-version-02) - [5.2. Version 0.3](#52-version-03)
- [5.3. Version 0.1](#53-version-01) - [5.3. Version 0.2](#53-version-02)
- [5.4. Version 0.1](#54-version-01)
- [6. Future](#6-future)
# 1. Introduction # 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. Current State
## 2.1. Code sanity ## 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. 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 ## 2.2. Code Credit
- Jonathan Naylor G4KLX (The original author of [ircddbGateway](https://github.com/g4klx/ircDDBGateway)) - 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) - 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 : 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. - 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. - Starnet: You might consider running [Smart Group Server XL](https://github.com/F4FXL/smart-group-server-xl) from a dedicated computer instead.
- Announcement: same can be achieved using transmitd. - Announcement: same can be achieved using VoiceTransmit.
- APRSGateway capability: I would prefer to have some sort of TCP "APRS-IS proxy" program sitting between the program and the APRS server, thus keeping the ability to directly connect to APRS-IS or not. - 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.
- Mobile APRS: Code has been ported, yet I am targeting repeaters so low priority. - 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. Building and installing
## 3.1. Get latest stable version ## 3.1. Initial setup
Clone the repository (only required initally) Clone the repository (only required initally)
``` ```
git clone https://github.com/F4FXL/DStarGateway.git git clone https://github.com/F4FXL/DStarGateway.git
cd DStarGateway cd DStarGateway
``` ```
## 3.2. Get latest stable version (recommended)
From inside the cloned repository run following commands to get the latest stable version From inside the cloned repository run following commands to get the latest stable version
``` ```
git pull -p git pull -p
@ -55,22 +62,35 @@ git fetch --tags
latestTag=$(git describe --tags `git rev-list --tags --max-count=1`) latestTag=$(git describe --tags `git rev-list --tags --max-count=1`)
git checkout $latestTag 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 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 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. The program is meant to run as a systemd service. All bits an pieces are provided.
``` ```
sudo make install 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` 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. 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. Contributing
## 4.1. Work Flow ## 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. 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. - [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 ## 5.3. Version 0.2
- [bugfix] ircDDBFreeze when repeater not found ([#1](https://github.com/F4FXL/DStarGateway/issues/1)) - [Bugfix] ircDDBFreeze when repeater not found ([#1](https://github.com/F4FXL/DStarGateway/issues/1))
- Code sanitization - Code sanitization
## 5.3. Version 0.1 ## 5.4. Version 0.1
First working version 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 :
- &#9746; 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
- &#9745; Send the connection status to APRS-IS as a status frame
- &#9746; Reinstantiate DRATS
- &#9746; Migrate all the "accessories" (VoiceTransmit, RemoteControl ...)
- &#9746; Automatic refresh of host files
- &#9746; 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 :)

@ -1594,7 +1594,6 @@ void CRepeaterHandler::clockInt(unsigned int ms)
m_starNet = NULL; m_starNet = NULL;
break; break;
#endif #endif
case G2_ECHO: case G2_ECHO:
m_echo->end(); m_echo->end();
break; break;
@ -2564,6 +2563,9 @@ void CRepeaterHandler::writeLinkingTo(const std::string &callsign)
m_infoAudio->setStatus(m_linkStatus, m_linkRepeater, text); m_infoAudio->setStatus(m_linkStatus, m_linkRepeater, text);
triggerInfo(); triggerInfo();
if(m_aprsWriter != nullptr)
m_aprsWriter->writeStatus(m_rptCallsign, text);
#ifdef USE_CCS #ifdef USE_CCS
m_ccsHandler->setReflector(); m_ccsHandler->setReflector();
#endif #endif
@ -2616,6 +2618,9 @@ void CRepeaterHandler::writeLinkedTo(const std::string &callsign)
m_infoAudio->setStatus(m_linkStatus, m_linkRepeater, text); m_infoAudio->setStatus(m_linkStatus, m_linkRepeater, text);
triggerInfo(); triggerInfo();
if(m_aprsWriter != nullptr)
m_aprsWriter->writeStatus(m_rptCallsign, text);
#ifdef USE_CCS #ifdef USE_CCS
m_ccsHandler->setReflector(callsign); m_ccsHandler->setReflector(callsign);
#endif #endif
@ -2668,6 +2673,9 @@ void CRepeaterHandler::writeNotLinked()
m_infoAudio->setStatus(m_linkStatus, m_linkRepeater, text); m_infoAudio->setStatus(m_linkStatus, m_linkRepeater, text);
triggerInfo(); triggerInfo();
if(m_aprsWriter != nullptr)
m_aprsWriter->writeStatus(m_rptCallsign, text);
#ifdef USE_CCS #ifdef USE_CCS
m_ccsHandler->setReflector(); m_ccsHandler->setReflector();
#endif #endif
@ -2736,6 +2744,9 @@ void CRepeaterHandler::writeIsBusy(const std::string& callsign)
m_infoAudio->setTempStatus(m_linkStatus, m_linkRepeater, tempText); m_infoAudio->setTempStatus(m_linkStatus, m_linkRepeater, tempText);
triggerInfo(); triggerInfo();
if(m_aprsWriter != nullptr)
m_aprsWriter->writeStatus(m_rptCallsign, text);
#ifdef USE_CCS #ifdef USE_CCS
m_ccsHandler->setReflector(); m_ccsHandler->setReflector();
#endif #endif

@ -22,5 +22,10 @@
#include <string> #include <string>
const std::string VENDOR_NAME("Geoffrey Merck F4FXL / KC3FRA"); #include "GitVersion.h"
const std::string VERSION("0.3");
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;

@ -45,13 +45,13 @@ m_out(0U)
m_data = new CAMBEData*[NUM_FRAMES]; m_data = new CAMBEData*[NUM_FRAMES];
char vstr[32]; auto vstr = SHORT_PRODUCT_NAME;
snprintf(vstr, 32, "DStar GW - %s", VERSION.substr(0, 8).c_str()); vstr.resize(NUM_FRAMES, ' ');
CLog::logInfo("Version text set to \"%s\"\n", vstr); CLog::logInfo("Version text set to \"%s\"\n", vstr);
CSlowDataEncoder encoder; CSlowDataEncoder encoder;
encoder.setTextData(std::string(vstr)); encoder.setTextData(vstr);
// Seq No and end // Seq No and end
for (unsigned int i = 0U; i < NUM_FRAMES; i++) { for (unsigned int i = 0U; i < NUM_FRAMES; i++) {

@ -139,9 +139,18 @@ enabled=true
hostname=rotate.aprs2.net # Defaults to rotate.aprs2.net hostname=rotate.aprs2.net # Defaults to rotate.aprs2.net
port=14580 # Defaults to 14580, there is no reason to change this port=14580 # Defaults to 14580, there is no reason to change this
password=12345 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] [Log]
path=/var/log/dstargateway/ 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] [Paths]
data=/usr/local/share/dstargateway.d/ #Path where the data (hostfiles, audio files etc) can be found data=/usr/local/share/dstargateway.d/ #Path where the data (hostfiles, audio files etc) can be found

Loading…
Cancel
Save

Powered by TurnKey Linux.