diff --git a/APRSWriter.cpp b/APRSWriter.cpp new file mode 100644 index 0000000..cc9cfaf --- /dev/null +++ b/APRSWriter.cpp @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2010-2014,2016,2017,2018,2020 by Jonathan Naylor G4KLX + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "APRSWriter.h" +#include "DMRDefines.h" +#include "Log.h" + +#include +#include +#include +#include + +CAPRSWriter::CAPRSWriter(const std::string& callsign, const std::string& suffix, const std::string& address, unsigned int port, bool debug) : +m_idTimer(1000U), +m_callsign(callsign), +m_debug(debug), +m_txFrequency(0U), +m_rxFrequency(0U), +m_latitude(0.0F), +m_longitude(0.0F), +m_height(0), +m_desc(), +m_aprsAddress(), +m_aprsPort(port), +m_aprsSocket() +{ + assert(!callsign.empty()); + assert(!address.empty()); + assert(port > 0U); + + if (!suffix.empty()) { + m_callsign.append("-"); + m_callsign.append(suffix.substr(0U, 1U)); + } + + m_aprsAddress = CUDPSocket::lookup(address); +} + +CAPRSWriter::~CAPRSWriter() +{ +} + +void CAPRSWriter::setInfo(unsigned int txFrequency, unsigned int rxFrequency, const std::string& desc) +{ + m_txFrequency = txFrequency; + m_rxFrequency = rxFrequency; + m_desc = desc; +} + +void CAPRSWriter::setLocation(float latitude, float longitude, int height) +{ + m_latitude = latitude; + m_longitude = longitude; + m_height = height; +} + +bool CAPRSWriter::open() +{ + bool ret = m_aprsSocket.open(); + if (!ret) + return false; + + LogMessage("Opened connection to the APRS Gateway"); + + m_idTimer.setTimeout(60U); + m_idTimer.start(); + + return true; +} + +void CAPRSWriter::clock(unsigned int ms) +{ + m_idTimer.clock(ms); + if (m_idTimer.hasExpired()) { + sendIdFrame(); + m_idTimer.setTimeout(20U * 60U); + m_idTimer.start(); + } +} + +void CAPRSWriter::close() +{ + m_aprsSocket.close(); +} + +void CAPRSWriter::sendIdFrame() +{ + // Default values aren't passed on + if (m_latitude == 0.0F && m_longitude == 0.0F) + return; + + char desc[200U]; + if (m_txFrequency != 0U) { + float offset = float(int(m_rxFrequency) - int(m_txFrequency)) / 1000000.0F; + ::sprintf(desc, "MMDVM Voice %.5LfMHz %c%.4lfMHz%s%s", + (long double)(m_txFrequency) / 1000000.0F, + offset < 0.0F ? '-' : '+', + ::fabs(offset), m_desc.empty() ? "" : ", ", m_desc.c_str()); + } else { + ::sprintf(desc, "MMDVM Voice%s%s", m_desc.empty() ? "" : ", ", m_desc.c_str()); + } + + const char* band = "4m"; + if (m_txFrequency >= 1200000000U) + band = "1.2"; + else if (m_txFrequency >= 420000000U) + band = "440"; + else if (m_txFrequency >= 144000000U) + band = "2m"; + else if (m_txFrequency >= 50000000U) + band = "6m"; + else if (m_txFrequency >= 28000000U) + band = "10m"; + + double tempLat = ::fabs(m_latitude); + double tempLong = ::fabs(m_longitude); + + double latitude = ::floor(tempLat); + double longitude = ::floor(tempLong); + + latitude = (tempLat - latitude) * 60.0 + latitude * 100.0; + longitude = (tempLong - longitude) * 60.0 + longitude * 100.0; + + char lat[20U]; + ::sprintf(lat, "%07.2lf", latitude); + + char lon[20U]; + ::sprintf(lon, "%08.2lf", longitude); + + std::string server = m_callsign; + size_t pos = server.find_first_of('-'); + if (pos == std::string::npos) + server.append("-S"); + else + server.append("S"); + + char output[500U]; + ::sprintf(output, "%s>APDG03,TCPIP*,qAC,%s:!%s%cD%s%c&/A=%06.0f%s %s\r\n", + m_callsign.c_str(), server.c_str(), + lat, (m_latitude < 0.0F) ? 'S' : 'N', + lon, (m_longitude < 0.0F) ? 'W' : 'E', + float(m_height) * 3.28F, band, desc); + + if (m_debug) + LogDebug("APRS ==> %s", output); + + m_aprsSocket.write((unsigned char*)output, (unsigned int)::strlen(output), m_aprsAddress, m_aprsPort); +} diff --git a/APRSWriter.h b/APRSWriter.h new file mode 100644 index 0000000..b1b32c6 --- /dev/null +++ b/APRSWriter.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2010,2011,2012,2016,2017,2018,2020 by Jonathan Naylor G4KLX + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef APRSWriter_H +#define APRSWriter_H + +#include "UDPSocket.h" +#include "Timer.h" + +#include + +#if !defined(_WIN32) && !defined(_WIN64) +#include +#include +#include +#include +#include +#include +#include +#include +#else +#include +#endif + +class CAPRSWriter { +public: + CAPRSWriter(const std::string& callsign, const std::string& suffix, const std::string& address, unsigned int port, bool debug); + ~CAPRSWriter(); + + bool open(); + + void setInfo(unsigned int txFrequency, unsigned int rxFrequency, const std::string& desc); + + void setLocation(float latitude, float longitude, int height); + + void clock(unsigned int ms); + + void close(); + +private: + CTimer m_idTimer; + std::string m_callsign; + bool m_debug; + unsigned int m_txFrequency; + unsigned int m_rxFrequency; + float m_latitude; + float m_longitude; + int m_height; + std::string m_desc; + in_addr m_aprsAddress; + unsigned int m_aprsPort; + CUDPSocket m_aprsSocket; + + void sendIdFrame(); +}; + +#endif diff --git a/Conf.cpp b/Conf.cpp index 6768326..85d243d 100644 --- a/Conf.cpp +++ b/Conf.cpp @@ -38,8 +38,9 @@ enum SECTION { SECTION_DMR_NETWORK_4, SECTION_DMR_NETWORK_5, SECTION_XLX_NETWORK, - SECTION_DYNAMIC_TG_CONTROL, SECTION_GPSD, + SECTION_APRS, + SECTION_DYNAMIC_TG_CONTROL }; CConf::CConf(const std::string& file) : @@ -171,11 +172,16 @@ m_xlxNetworkRelink(0U), m_xlxNetworkDebug(false), m_xlxNetworkUserControl(true), m_xlxNetworkModule(), -m_dynamicTGControlEnabled(false), -m_dynamicTGControlPort(3769U), m_gpsdEnabled(false), m_gpsdAddress(), -m_gpsdPort() +m_gpsdPort(), +m_aprsEnabled(false), +m_aprsAddress(), +m_aprsPort(0U), +m_aprsSuffix(), +m_aprsDescription(), +m_dynamicTGControlEnabled(false), +m_dynamicTGControlPort(3769U) { } @@ -219,10 +225,12 @@ bool CConf::read() section = SECTION_DMR_NETWORK_4; else if (::strncmp(buffer, "[DMR Network 5]", 15U) == 0) section = SECTION_DMR_NETWORK_5; - else if (::strncmp(buffer, "[Dynamic TG Control]", 20U) == 0) - section = SECTION_DYNAMIC_TG_CONTROL; else if (::strncmp(buffer, "[GPSD]", 6U) == 0) section = SECTION_GPSD; + else if (::strncmp(buffer, "[APRS]", 6U) == 0) + section = SECTION_APRS; + else if (::strncmp(buffer, "[Dynamic TG Control]", 20U) == 0) + section = SECTION_DYNAMIC_TG_CONTROL; else section = SECTION_NONE; @@ -935,11 +943,6 @@ bool CConf::read() unsigned int slotNo = (unsigned int)::atoi(value); m_dmrNetwork5PassAllTG.push_back(slotNo); } - } else if (section == SECTION_DYNAMIC_TG_CONTROL) { - if (::strcmp(key, "Enabled") == 0) - m_dynamicTGControlEnabled = ::atoi(value) == 1; - else if (::strcmp(key, "Port") == 0) - m_dynamicTGControlPort = (unsigned int)::atoi(value); } else if (section == SECTION_GPSD) { if (::strcmp(key, "Enable") == 0) m_gpsdEnabled = ::atoi(value) == 1; @@ -947,6 +950,20 @@ bool CConf::read() m_gpsdAddress = value; else if (::strcmp(key, "Port") == 0) m_gpsdPort = value; + } else if (section == SECTION_APRS) { + if (::strcmp(key, "Enable") == 0) + m_aprsEnabled = ::atoi(value) == 1; + else if (::strcmp(key, "Address") == 0) + m_aprsAddress = value; + else if (::strcmp(key, "Port") == 0) + m_aprsPort = (unsigned int)::atoi(value); + else if (::strcmp(key, "Suffix") == 0) + m_aprsSuffix = value; + } else if (section == SECTION_DYNAMIC_TG_CONTROL) { + if (::strcmp(key, "Enabled") == 0) + m_dynamicTGControlEnabled = ::atoi(value) == 1; + else if (::strcmp(key, "Port") == 0) + m_dynamicTGControlPort = (unsigned int)::atoi(value); } } @@ -1603,16 +1620,6 @@ std::vector CConf::getDMRNetwork5PassAllTG() const return m_dmrNetwork5PassAllTG; } -bool CConf::getDynamicTGControlEnabled() const -{ - return m_dynamicTGControlEnabled; -} - -unsigned int CConf::getDynamicTGControlPort() const -{ - return m_dynamicTGControlPort; -} - bool CConf::getGPSDEnabled() const { return m_gpsdEnabled; @@ -1627,3 +1634,38 @@ std::string CConf::getGPSDPort() const { return m_gpsdPort; } + +bool CConf::getAPRSEnabled() const +{ + return m_aprsEnabled; +} + +std::string CConf::getAPRSAddress() const +{ + return m_aprsAddress; +} + +unsigned int CConf::getAPRSPort() const +{ + return m_aprsPort; +} + +std::string CConf::getAPRSSuffix() const +{ + return m_aprsSuffix; +} + +std::string CConf::getAPRSDescription() const +{ + return m_aprsDescription; +} + +bool CConf::getDynamicTGControlEnabled() const +{ + return m_dynamicTGControlEnabled; +} + +unsigned int CConf::getDynamicTGControlPort() const +{ + return m_dynamicTGControlPort; +} diff --git a/Conf.h b/Conf.h index 1a337b8..18c3a7a 100644 --- a/Conf.h +++ b/Conf.h @@ -224,15 +224,22 @@ public: bool getXLXNetworkUserControl() const; char getXLXNetworkModule() const; - // The Dynamic TG Control section - bool getDynamicTGControlEnabled() const; - unsigned int getDynamicTGControlPort() const; - // The GPSD section bool getGPSDEnabled() const; std::string getGPSDAddress() const; std::string getGPSDPort() const; + // The APRS section + bool getAPRSEnabled() const; + std::string getAPRSAddress() const; + unsigned int getAPRSPort() const; + std::string getAPRSSuffix() const; + std::string getAPRSDescription() const; + + // The Dynamic TG Control section + bool getDynamicTGControlEnabled() const; + unsigned int getDynamicTGControlPort() const; + private: std::string m_file; bool m_daemon; @@ -372,12 +379,18 @@ private: bool m_xlxNetworkUserControl; char m_xlxNetworkModule; - bool m_dynamicTGControlEnabled; - unsigned int m_dynamicTGControlPort; - bool m_gpsdEnabled; std::string m_gpsdAddress; std::string m_gpsdPort; + + bool m_aprsEnabled; + std::string m_aprsAddress; + unsigned int m_aprsPort; + std::string m_aprsSuffix; + std::string m_aprsDescription; + + bool m_dynamicTGControlEnabled; + unsigned int m_dynamicTGControlPort; }; #endif diff --git a/DMRGateway.cpp b/DMRGateway.cpp index 4330d24..bb40ec8 100644 --- a/DMRGateway.cpp +++ b/DMRGateway.cpp @@ -181,7 +181,11 @@ m_dmr4Passalls(), m_dmr5Passalls(), m_dynVoices(), m_dynRF(), -m_socket(NULL) +m_socket(NULL), +m_writer(NULL), +m_callsign(), +m_txFrequency(0U), +m_rxFrequency(0U) #if defined(USE_GPSD) ,m_gpsd(NULL) #endif @@ -459,6 +463,8 @@ int CDMRGateway::run() return 1; } + createAPRS(); + unsigned int rfTimeout = m_conf.getRFTimeout(); unsigned int netTimeout = m_conf.getNetTimeout(); @@ -1209,6 +1215,9 @@ int CDMRGateway::run() m_gpsd->clock(ms); #endif + if (m_writer != NULL) + m_writer->clock(ms); + for (std::vector::iterator it = m_dynVoices.begin(); it != m_dynVoices.end(); ++it) (*it)->clock(ms); @@ -1236,6 +1245,11 @@ int CDMRGateway::run() } #endif + if (m_writer != NULL) { + m_writer->close(); + delete m_writer; + } + if (m_dmrNetwork1 != NULL) { m_dmrNetwork1->close(); delete m_dmrNetwork1; @@ -2367,6 +2381,20 @@ unsigned int CDMRGateway::getConfig(const std::string& name, unsigned char* buff latitude, longitude, height, location.c_str(), description.c_str(), m_config[30U], url.c_str(), m_config + 31U, m_config + 71U); + m_callsign = std::string((char*)m_config + 0U, 8U); + size_t n = m_callsign.find_first_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"); + if (n != std::string::npos) + m_callsign.erase(n); + + char frequency[10U]; + ::memset(frequency, 0x00U, 10U); + ::memcpy(frequency, m_config + 8U, 9U); + m_rxFrequency = (unsigned int)::atoi(frequency); + + ::memset(frequency, 0x00U, 10U); + ::memcpy(frequency, m_config + 17U, 9U); + m_txFrequency = (unsigned int)::atoi(frequency); + LogInfo("%s: configuration message: %s", name.c_str(), buffer); return (unsigned int)::strlen((char*)buffer); @@ -2423,6 +2451,41 @@ void CDMRGateway::processTalkerAlias() m_dmrNetwork5->writeTalkerAlias(buffer, length); } +void CDMRGateway::createAPRS() +{ + if (!m_conf.getAPRSEnabled()) + return; + + std::string address = m_conf.getAPRSAddress(); + unsigned int port = m_conf.getAPRSPort(); + std::string suffix = m_conf.getAPRSSuffix(); + bool debug = m_conf.getDebug(); + + m_writer = new CAPRSWriter(m_callsign, suffix, address, port, debug); + + std::string desc = m_conf.getAPRSDescription(); + + m_writer->setInfo(m_txFrequency, m_rxFrequency, desc); + + float latitude = m_conf.getInfoLatitude(); + float longitude = m_conf.getInfoLongitude(); + int height = m_conf.getInfoHeight(); + + m_writer->setLocation(latitude, longitude, height); + + bool ret = m_writer->open(); + if (!ret) { + delete m_writer; + m_writer = NULL; + return; + } + +#if defined(USE_GPSD) + if (m_gpsd != NULL) + m_gpsd->setAPRS(m_writer); +#endif +} + void CDMRGateway::processDynamicTGControl() { unsigned char buffer[100U]; diff --git a/DMRGateway.h b/DMRGateway.h index 0898cba..d872389 100644 --- a/DMRGateway.h +++ b/DMRGateway.h @@ -23,6 +23,7 @@ #include "RewriteDynTGRF.h" #include "MMDVMNetwork.h" #include "DMRNetwork.h" +#include "APRSWriter.h" #include "Reflectors.h" #include "UDPSocket.h" #include "RewriteTG.h" @@ -111,6 +112,10 @@ private: std::vector m_dynVoices; std::vector m_dynRF; CUDPSocket* m_socket; + CAPRSWriter* m_writer; + std::string m_callsign; + unsigned int m_txFrequency; + unsigned int m_rxFrequency; #if defined(USE_GPSD) CGPSD* m_gpsd; #endif @@ -134,6 +139,7 @@ private: void processRadioPosition(); void processTalkerAlias(); + void createAPRS(); void processDynamicTGControl(); }; diff --git a/DMRGateway.ini b/DMRGateway.ini index 67a8d4c..3aa42b1 100644 --- a/DMRGateway.ini +++ b/DMRGateway.ini @@ -138,6 +138,13 @@ Enable=0 Address=127.0.0.1 Port=2947 +[APRS] +Enable=0 +Address=127.0.0.1 +Port=8673 +Description=APRS Description +Suffix=3 + [Dynamic TG Control] Enabled=1 Port=3769 diff --git a/DMRGateway.vcxproj b/DMRGateway.vcxproj index 59a1d26..10be0b4 100644 --- a/DMRGateway.vcxproj +++ b/DMRGateway.vcxproj @@ -152,6 +152,7 @@ + @@ -198,6 +199,7 @@ + diff --git a/DMRGateway.vcxproj.filters b/DMRGateway.vcxproj.filters index 2913d8d..3f9fdc3 100644 --- a/DMRGateway.vcxproj.filters +++ b/DMRGateway.vcxproj.filters @@ -143,6 +143,9 @@ Header Files + + Header Files + @@ -268,5 +271,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/GPSD.cpp b/GPSD.cpp index 16acc00..17d1f1a 100644 --- a/GPSD.cpp +++ b/GPSD.cpp @@ -30,7 +30,8 @@ m_gpsdAddress(address), m_gpsdPort(port), m_gpsdData(), m_idTimer(1000U, 60U), -m_networks() +m_networks(), +m_aprs(NULL) { assert(!address.empty()); assert(!port.empty()); @@ -47,6 +48,13 @@ void CGPSD::addNetwork(CDMRNetwork* network) m_networks.push_back(network); } +void CGPSD::addAPRS(CAPRSWriter* aprs) +{ + assert(aprs != NULL); + + m_aprs = aprs; +} + bool CGPSD::open() { int ret = ::gps_open(m_gpsdAddress.c_str(), m_gpsdPort.c_str(), &m_gpsdData); @@ -100,8 +108,18 @@ void CGPSD::sendReport() if (!latlonSet) return; + bool altitudeSet = (m_gpsdData.set & ALTITUDE_SET) == ALTITUDE_SET; + float latitude = float(m_gpsdData.fix.latitude); float longitude = float(m_gpsdData.fix.longitude); +#if GPSD_API_MAJOR_VERSION >= 9 + float altitude = float(m_gpsdData.fix.altMSL); +#else + float altitude = float(m_gpsdData.fix.altitude); +#endif + + if (m_aprs != NULL) + m_aprs->setLocation(;latitude, longitude, altitudeSet ? altitude : 0.0F); for (std::vector::const_iterator it = m_networks.begin(); it != m_networks.end(); ++it) (*it)->writeHomePosition(latitude, longitude); diff --git a/GPSD.h b/GPSD.h index 7be3df8..182b695 100644 --- a/GPSD.h +++ b/GPSD.h @@ -21,6 +21,7 @@ #if defined(USE_GPSD) +#include "APRSWriter.h" #include "DMRNetwork.h" #include "Timer.h" @@ -36,6 +37,8 @@ public: void addNetwork(CDMRNetwork* network); + void setAPRS(CAPRSWriter* aprs); + bool open(); void clock(unsigned int ms); @@ -48,6 +51,7 @@ private: struct gps_data_t m_gpsdData; CTimer m_idTimer; std::vector m_networks; + CAPRSWriter* m_aprs; void sendReport(); }; diff --git a/Makefile b/Makefile index 1ff39f3..a13f510 100644 --- a/Makefile +++ b/Makefile @@ -11,10 +11,10 @@ LIBS = -lpthread LDFLAGS = -g -OBJECTS = BPTC19696.o Conf.o CRC.o DMRCSBK.o DMRData.o DMRDataHeader.o DMREmbeddedData.o DMREMB.o DMRFullLC.o DMRGateway.o DMRLC.o DMRNetwork.o DMRSlotType.o \ - DynVoice.o Golay2087.o GPSD.o Hamming.o Log.o MMDVMNetwork.o PassAllPC.o PassAllTG.o QR1676.o Reflectors.o Rewrite.o \ - RewriteDstId.o RewriteDynTGNet.o RewriteDynTGRF.o RewritePC.o RewriteSrc.o RewriteSrcId.o RewriteTG.o RewriteType.o RS129.o SHA256.o StopWatch.o Sync.o \ - Thread.o Timer.o UDPSocket.o Utils.o XLXVoice.o +OBJECTS = APRSWriter.o BPTC19696.o Conf.o CRC.o DMRCSBK.o DMRData.o DMRDataHeader.o DMREmbeddedData.o DMREMB.o DMRFullLC.o DMRGateway.o DMRLC.o DMRNetwork.o \ + DMRSlotType.o DynVoice.o Golay2087.o GPSD.o Hamming.o Log.o MMDVMNetwork.o PassAllPC.o PassAllTG.o QR1676.o Reflectors.o Rewrite.o RewriteDstId.o \ + RewriteDynTGNet.o RewriteDynTGRF.o RewritePC.o RewriteSrc.o RewriteSrcId.o RewriteTG.o RewriteType.o RS129.o SHA256.o StopWatch.o Sync.o Thread.o \ + Timer.o UDPSocket.o Utils.o XLXVoice.o all: DMRGateway