diff --git a/APRSWriter.cpp b/APRSWriter.cpp index 3ef0d99..89f58df 100644 --- a/APRSWriter.cpp +++ b/APRSWriter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2014,2016,2017,2018,2020 by Jonathan Naylor G4KLX + * Copyright (C) 2010-2014,2016,2017,2018,2020,2023 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 @@ -16,6 +16,7 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include "MQTTConnection.h" #include "APRSWriter.h" #include "DMRDefines.h" #include "Log.h" @@ -25,7 +26,10 @@ #include #include -CAPRSWriter::CAPRSWriter(const std::string& callsign, const std::string& suffix, const std::string& address, unsigned short port, bool debug) : +// In Log.cpp +extern CMQTTConnection* m_mqtt; + +CAPRSWriter::CAPRSWriter(const std::string& callsign, const std::string& suffix, bool debug) : m_idTimer(1000U), m_callsign(callsign), m_debug(debug), @@ -35,22 +39,14 @@ m_latitude(0.0F), m_longitude(0.0F), m_height(0), m_desc(), -m_symbol(), -m_aprsAddr(), -m_aprsLen(0U), -m_aprsSocket() +m_symbol() { assert(!callsign.empty()); - assert(!address.empty()); - assert(port > 0U); if (!suffix.empty()) { m_callsign.append("-"); m_callsign.append(suffix.substr(0U, 1U)); } - - if (CUDPSocket::lookup(address, port, m_aprsAddr, m_aprsLen) != 0) - m_aprsLen = 0U; } CAPRSWriter::~CAPRSWriter() @@ -75,17 +71,6 @@ void CAPRSWriter::setLocation(float latitude, float longitude, int height) bool CAPRSWriter::open() { - if (m_aprsLen == 0U) { - LogError("Could not lookup the address of the APRS-IS server"); - return false; - } - - bool ret = m_aprsSocket.open(m_aprsAddr); - if (!ret) - return false; - - LogMessage("Opened connection to the APRS Gateway"); - m_idTimer.setTimeout(60U); m_idTimer.start(); @@ -104,7 +89,6 @@ void CAPRSWriter::clock(unsigned int ms) void CAPRSWriter::close() { - m_aprsSocket.close(); } void CAPRSWriter::sendIdFrame() @@ -172,5 +156,6 @@ void CAPRSWriter::sendIdFrame() if (m_debug) LogDebug("APRS ==> %s", output); - m_aprsSocket.write((unsigned char*)output, (unsigned int)::strlen(output), m_aprsAddr, m_aprsLen); + m_mqtt->publish("aprs-gateway/aprs", output); } + diff --git a/APRSWriter.h b/APRSWriter.h index 164e571..842af6c 100644 --- a/APRSWriter.h +++ b/APRSWriter.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010,2011,2012,2016,2017,2018,2020 by Jonathan Naylor G4KLX + * Copyright (C) 2010,2011,2012,2016,2017,2018,2020,2023 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 @@ -19,7 +19,6 @@ #ifndef APRSWriter_H #define APRSWriter_H -#include "UDPSocket.h" #include "Timer.h" #include @@ -39,7 +38,7 @@ class CAPRSWriter { public: - CAPRSWriter(const std::string& callsign, const std::string& suffix, const std::string& address, unsigned short port, bool debug); + CAPRSWriter(const std::string& callsign, const std::string& suffix, bool debug); ~CAPRSWriter(); bool open(); @@ -53,19 +52,16 @@ public: 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; - std::string m_symbol; - sockaddr_storage m_aprsAddr; - unsigned int m_aprsLen; - CUDPSocket m_aprsSocket; + 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; + std::string m_symbol; void sendIdFrame(); }; diff --git a/Conf.cpp b/Conf.cpp index ef3550f..9ac1a30 100644 --- a/Conf.cpp +++ b/Conf.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2020 by Jonathan Naylor G4KLX + * Copyright (C) 2015-2020,2023 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 @@ -40,8 +40,9 @@ enum SECTION { SECTION_XLX_NETWORK, SECTION_GPSD, SECTION_APRS, + SECTION_MQTT, SECTION_DYNAMIC_TG_CONTROL, - SECTION_REMOTE_CONTROL + SECTION_REMOTE_COMMANDS }; CConf::CConf(const std::string& file) : @@ -59,10 +60,7 @@ m_voiceEnabled(true), m_voiceLanguage("en_GB"), m_voiceDirectory(), m_logDisplayLevel(0U), -m_logFileLevel(0U), -m_logFilePath(), -m_logFileRoot(), -m_logFileRotate(true), +m_logMQTTLevel(0U), m_infoLatitude(0.0F), m_infoLongitude(0.0F), m_infoHeight(0), @@ -178,16 +176,15 @@ m_gpsdEnabled(false), m_gpsdAddress(), m_gpsdPort(), m_aprsEnabled(false), -m_aprsAddress(), -m_aprsPort(0U), m_aprsSuffix(), m_aprsDescription(), m_aprsSymbol(), +m_mqttAddress("127.0.0.1"), +m_mqttPort(1883U), +m_mqttKeepalive(60U), +m_mqttName("dmr-gateway"), m_dynamicTGControlEnabled(false), -m_dynamicTGControlPort(3769U), -m_remoteControlEnabled(false), -m_remoteControlAddress("127.0.0.1"), -m_remoteControlPort(0U) +m_remoteCommandsEnabled(false) { } @@ -197,80 +194,82 @@ CConf::~CConf() bool CConf::read() { - FILE* fp = ::fopen(m_file.c_str(), "rt"); - if (fp == NULL) { - ::fprintf(stderr, "Couldn't open the .ini file - %s\n", m_file.c_str()); - return false; - } - - SECTION section = SECTION_NONE; - - char buffer[BUFFER_SIZE]; - while (::fgets(buffer, BUFFER_SIZE, fp) != NULL) { - if (buffer[0U] == '#') - continue; - - if (buffer[0U] == '[') { - if (::strncmp(buffer, "[General]", 9U) == 0) - section = SECTION_GENERAL; - else if (::strncmp(buffer, "[Log]", 5U) == 0) - section = SECTION_LOG; - else if (::strncmp(buffer, "[Voice]", 7U) == 0) - section = SECTION_VOICE; - else if (::strncmp(buffer, "[Info]", 6U) == 0) - section = SECTION_INFO; - else if (::strncmp(buffer, "[XLX Network]", 13U) == 0) - section = SECTION_XLX_NETWORK; - else if (::strncmp(buffer, "[DMR Network 1]", 15U) == 0) - section = SECTION_DMR_NETWORK_1; - else if (::strncmp(buffer, "[DMR Network 2]", 15U) == 0) - section = SECTION_DMR_NETWORK_2; - else if (::strncmp(buffer, "[DMR Network 3]", 15U) == 0) - section = SECTION_DMR_NETWORK_3; - else if (::strncmp(buffer, "[DMR Network 4]", 15U) == 0) - section = SECTION_DMR_NETWORK_4; - else if (::strncmp(buffer, "[DMR Network 5]", 15U) == 0) - section = SECTION_DMR_NETWORK_5; - 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 if (::strncmp(buffer, "[Remote Control]", 16U) == 0) - section = SECTION_REMOTE_CONTROL; - else - section = SECTION_NONE; - - continue; + FILE* fp = ::fopen(m_file.c_str(), "rt"); + if (fp == NULL) { + ::fprintf(stderr, "Couldn't open the .ini file - %s\n", m_file.c_str()); + return false; } - char* key = ::strtok(buffer, " \t=\r\n"); - if (key == NULL) - continue; + SECTION section = SECTION_NONE; - char* value = ::strtok(NULL, "\r\n"); - if (value == NULL) - continue; + char buffer[BUFFER_SIZE]; + while (::fgets(buffer, BUFFER_SIZE, fp) != NULL) { + if (buffer[0U] == '#') + continue; - // Remove quotes from the value - size_t len = ::strlen(value); - if (len > 1U && *value == '"' && value[len - 1U] == '"') { - value[len - 1U] = '\0'; - value++; - } else { - char *p; + if (buffer[0U] == '[') { + if (::strncmp(buffer, "[General]", 9U) == 0) + section = SECTION_GENERAL; + else if (::strncmp(buffer, "[Log]", 5U) == 0) + section = SECTION_LOG; + else if (::strncmp(buffer, "[Voice]", 7U) == 0) + section = SECTION_VOICE; + else if (::strncmp(buffer, "[Info]", 6U) == 0) + section = SECTION_INFO; + else if (::strncmp(buffer, "[XLX Network]", 13U) == 0) + section = SECTION_XLX_NETWORK; + else if (::strncmp(buffer, "[DMR Network 1]", 15U) == 0) + section = SECTION_DMR_NETWORK_1; + else if (::strncmp(buffer, "[DMR Network 2]", 15U) == 0) + section = SECTION_DMR_NETWORK_2; + else if (::strncmp(buffer, "[DMR Network 3]", 15U) == 0) + section = SECTION_DMR_NETWORK_3; + else if (::strncmp(buffer, "[DMR Network 4]", 15U) == 0) + section = SECTION_DMR_NETWORK_4; + else if (::strncmp(buffer, "[DMR Network 5]", 15U) == 0) + section = SECTION_DMR_NETWORK_5; + else if (::strncmp(buffer, "[GPSD]", 6U) == 0) + section = SECTION_GPSD; + else if (::strncmp(buffer, "[APRS]", 6U) == 0) + section = SECTION_APRS; + else if (::strncmp(buffer, "[MQTT]", 6U) == 0) + section = SECTION_MQTT; + else if (::strncmp(buffer, "[Dynamic TG Control]", 20U) == 0) + section = SECTION_DYNAMIC_TG_CONTROL; + else if (::strncmp(buffer, "[Remote Commands]", 17U) == 0) + section = SECTION_REMOTE_COMMANDS; + else + section = SECTION_NONE; - // if value is not quoted, remove after # (to make comment) - if ((p = strchr(value, '#')) != NULL) - *p = '\0'; + continue; + } - // remove trailing tab/space - for (p = value + strlen(value) - 1U; p >= value && (*p == '\t' || *p == ' '); p--) - *p = '\0'; - } + char* key = ::strtok(buffer, " \t=\r\n"); + if (key == NULL) + continue; - if (section == SECTION_GENERAL) { + char* value = ::strtok(NULL, "\r\n"); + if (value == NULL) + continue; + + // Remove quotes from the value + size_t len = ::strlen(value); + if (len > 1U && *value == '"' && value[len - 1U] == '"') { + value[len - 1U] = '\0'; + value++; + } else { + char *p; + + // if value is not quoted, remove after # (to make comment) + if ((p = strchr(value, '#')) != NULL) + *p = '\0'; + + // remove trailing tab/space + for (p = value + strlen(value) - 1U; p >= value && (*p == '\t' || *p == ' '); p--) + *p = '\0'; + } + + if (section == SECTION_GENERAL) { if (::strcmp(key, "Daemon") == 0) m_daemon = ::atoi(value) == 1; else if (::strcmp(key, "Timeout") == 0) @@ -292,16 +291,10 @@ bool CConf::read() else if (::strcmp(key, "Debug") == 0) m_debug = ::atoi(value) == 1; } else if (section == SECTION_LOG) { - if (::strcmp(key, "FilePath") == 0) - m_logFilePath = value; - else if (::strcmp(key, "FileRoot") == 0) - m_logFileRoot = value; - else if (::strcmp(key, "FileLevel") == 0) - m_logFileLevel = (unsigned int)::atoi(value); + if (::strcmp(key, "MQTTLevel") == 0) + m_logMQTTLevel = (unsigned int)::atoi(value); else if (::strcmp(key, "DisplayLevel") == 0) m_logDisplayLevel = (unsigned int)::atoi(value); - else if (::strcmp(key, "FileRotate") == 0) - m_logFileRotate = ::atoi(value) == 1; } else if (section == SECTION_VOICE) { if (::strcmp(key, "Enabled") == 0) m_voiceEnabled = ::atoi(value) == 1; @@ -353,15 +346,14 @@ bool CConf::read() *p++ = '0'; m_xlxNetworkStartup = std::string(buffer); - } - else if (::strcmp(key, "Relink") == 0) + } else if (::strcmp(key, "Relink") == 0) m_xlxNetworkRelink = (unsigned int)::atoi(value); else if (::strcmp(key, "Debug") == 0) m_xlxNetworkDebug = ::atoi(value) == 1; - else if (::strcmp(key, "UserControl") == 0) - m_xlxNetworkUserControl = ::atoi(value) ==1; - else if (::strcmp(key, "Module") == 0) - m_xlxNetworkModule = ::toupper(value[0]); + else if (::strcmp(key, "UserControl") == 0) + m_xlxNetworkUserControl = ::atoi(value) ==1; + else if (::strcmp(key, "Module") == 0) + m_xlxNetworkModule = ::toupper(value[0]); } else if (section == SECTION_DMR_NETWORK_1) { if (::strcmp(key, "Enabled") == 0) m_dmrNetwork1Enabled = ::atoi(value) == 1; @@ -982,28 +974,27 @@ bool CConf::read() } 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 short)::atoi(value); else if (::strcmp(key, "Suffix") == 0) m_aprsSuffix = value; else if (::strcmp(key, "Description") == 0) m_aprsDescription = value; - else if (::strcmp(key, "Symbol") == 0) - m_aprsSymbol = value; + else if (::strcmp(key, "Symbol") == 0) + m_aprsSymbol = value; + } else if (section == SECTION_MQTT) { + if (::strcmp(key, "Address") == 0) + m_mqttAddress = value; + else if (::strcmp(key, "Port") == 0) + m_mqttPort = (unsigned short)::atoi(value); + else if (::strcmp(key, "Keepalive") == 0) + m_mqttKeepalive = (unsigned int)::atoi(value); + else if (::strcmp(key, "Name") == 0) + m_mqttName = 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 short)::atoi(value); - } else if (section == SECTION_REMOTE_CONTROL) { if (::strcmp(key, "Enable") == 0) - m_remoteControlEnabled = ::atoi(value) == 1; - else if (::strcmp(key, "Address") == 0) - m_remoteControlAddress = value; - else if (::strcmp(key, "Port") == 0) - m_remoteControlPort = (unsigned short)::atoi(value); + m_dynamicTGControlEnabled = ::atoi(value) == 1; + } else if (section == SECTION_REMOTE_COMMANDS) { + if (::strcmp(key, "Enable") == 0) + m_remoteCommandsEnabled = ::atoi(value) == 1; } } @@ -1062,24 +1053,9 @@ unsigned int CConf::getLogDisplayLevel() const return m_logDisplayLevel; } -unsigned int CConf::getLogFileLevel() const +unsigned int CConf::getLogMQTTLevel() const { - return m_logFileLevel; -} - -std::string CConf::getLogFilePath() const -{ - return m_logFilePath; -} - -std::string CConf::getLogFileRoot() const -{ - return m_logFileRoot; -} - -bool CConf::getLogFileRotate() const -{ - return m_logFileRotate; + return m_logMQTTLevel; } bool CConf::getVoiceEnabled() const @@ -1144,17 +1120,17 @@ std::string CConf::getXLXNetworkFile() const unsigned int CConf::getXLXNetworkReloadTime() const { - return m_xlxNetworkReloadTime; + return m_xlxNetworkReloadTime; } unsigned short CConf::getXLXNetworkPort() const { - return m_xlxNetworkPort; + return m_xlxNetworkPort; } std::string CConf::getXLXNetworkPassword() const { - return m_xlxNetworkPassword; + return m_xlxNetworkPassword; } unsigned short CConf::getXLXNetworkLocal() const @@ -1685,16 +1661,6 @@ bool CConf::getAPRSEnabled() const return m_aprsEnabled; } -std::string CConf::getAPRSAddress() const -{ - return m_aprsAddress; -} - -unsigned short CConf::getAPRSPort() const -{ - return m_aprsPort; -} - std::string CConf::getAPRSSuffix() const { return m_aprsSuffix; @@ -1710,27 +1676,33 @@ std::string CConf::getAPRSSymbol() const return m_aprsSymbol; } +std::string CConf::getMQTTAddress() const +{ + return m_mqttAddress; +} + +unsigned short CConf::getMQTTPort() const +{ + return m_mqttPort; +} + +unsigned int CConf::getMQTTKeepalive() const +{ + return m_mqttKeepalive; +} + +std::string CConf::getMQTTName() const +{ + return m_mqttName; +} + bool CConf::getDynamicTGControlEnabled() const { return m_dynamicTGControlEnabled; } -unsigned short CConf::getDynamicTGControlPort() const +bool CConf::getRemoteCommandsEnabled() const { - return m_dynamicTGControlPort; + return m_remoteCommandsEnabled; } -bool CConf::getRemoteControlEnabled() const -{ - return m_remoteControlEnabled; -} - -std::string CConf::getRemoteControlAddress() const -{ - return m_remoteControlAddress; -} - -unsigned short CConf::getRemoteControlPort() const -{ - return m_remoteControlPort; -} diff --git a/Conf.h b/Conf.h index 40a9c9e..12d6b77 100644 --- a/Conf.h +++ b/Conf.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017,2019,2020 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2017,2019,2020,2023 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 @@ -90,10 +90,7 @@ public: // The Log section unsigned int getLogDisplayLevel() const; - unsigned int getLogFileLevel() const; - std::string getLogFilePath() const; - std::string getLogFileRoot() const; - bool getLogFileRotate() const; + unsigned int getLogMQTTLevel() const; // The Voice section bool getVoiceEnabled() const; @@ -232,20 +229,21 @@ public: // The APRS section bool getAPRSEnabled() const; - std::string getAPRSAddress() const; - unsigned short getAPRSPort() const; std::string getAPRSSuffix() const; std::string getAPRSDescription() const; std::string getAPRSSymbol() const; + // The MQTT section + std::string getMQTTAddress() const; + unsigned short getMQTTPort() const; + unsigned int getMQTTKeepalive() const; + std::string getMQTTName() const; + // The Dynamic TG Control section bool getDynamicTGControlEnabled() const; - unsigned short getDynamicTGControlPort() const; - // The Remote Control section - bool getRemoteControlEnabled() const; - std::string getRemoteControlAddress() const; - unsigned short getRemoteControlPort() const; + // The Remote Commands section + bool getRemoteCommandsEnabled() const; private: std::string m_file; @@ -264,10 +262,7 @@ private: std::string m_voiceDirectory; unsigned int m_logDisplayLevel; - unsigned int m_logFileLevel; - std::string m_logFilePath; - std::string m_logFileRoot; - bool m_logFileRotate; + unsigned int m_logMQTTLevel; float m_infoLatitude; float m_infoLongitude; @@ -392,18 +387,18 @@ private: std::string m_gpsdPort; bool m_aprsEnabled; - std::string m_aprsAddress; - unsigned short m_aprsPort; std::string m_aprsSuffix; std::string m_aprsDescription; std::string m_aprsSymbol; - bool m_dynamicTGControlEnabled; - unsigned short m_dynamicTGControlPort; + std::string m_mqttAddress; + unsigned short m_mqttPort; + unsigned int m_mqttKeepalive; + std::string m_mqttName; - bool m_remoteControlEnabled; - std::string m_remoteControlAddress; - unsigned short m_remoteControlPort; + bool m_dynamicTGControlEnabled; + + bool m_remoteCommandsEnabled; }; #endif diff --git a/DMRGateway.cpp b/DMRGateway.cpp index 701ad82..976e46f 100644 --- a/DMRGateway.cpp +++ b/DMRGateway.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2021 by Jonathan Naylor G4KLX + * Copyright (C) 2015-2021,2023 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 @@ -16,6 +16,7 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include "MQTTConnection.h" #include "RewriteType.h" #include "DMRSlotType.h" #include "RewriteSrc.h" @@ -67,10 +68,15 @@ static void sigHandler(int signum) } #endif +// In Log.cpp +extern CMQTTConnection* m_mqtt; + +static CDMRGateway* gateway = NULL; + const char* HEADER1 = "This software is for use on amateur radio networks only,"; const char* HEADER2 = "it is to be used for educational purposes only. Its use on"; const char* HEADER3 = "commercial networks is strictly prohibited."; -const char* HEADER4 = "Copyright(C) 2017-2022 by Jonathan Naylor, G4KLX and others"; +const char* HEADER4 = "Copyright(C) 2017-2023 by Jonathan Naylor, G4KLX and others"; int main(int argc, char** argv) { @@ -102,10 +108,9 @@ int main(int argc, char** argv) do { m_signal = 0; - CDMRGateway* host = new CDMRGateway(std::string(iniFile)); - ret = host->run(); - - delete host; + gateway = new CDMRGateway(std::string(iniFile)); + ret = gateway->run(); + delete gateway; if (m_signal == 2) ::LogInfo("DMRGateway-%s exited on receipt of SIGINT", VERSION); @@ -181,7 +186,6 @@ m_dmr4Passalls(), m_dmr5Passalls(), m_dynVoices(), m_dynRF(), -m_socket(NULL), m_writer(NULL), m_callsign(), m_txFrequency(0U), @@ -343,16 +347,6 @@ int CDMRGateway::run() } #endif -#if !defined(_WIN32) && !defined(_WIN64) - ret = ::LogInitialise(m_daemon, m_conf.getLogFilePath(), m_conf.getLogFileRoot(), m_conf.getLogFileLevel(), m_conf.getLogDisplayLevel(), m_conf.getLogFileRotate()); -#else - ret = ::LogInitialise(false, m_conf.getLogFilePath(), m_conf.getLogFileRoot(), m_conf.getLogFileLevel(), m_conf.getLogDisplayLevel(), m_conf.getLogFileRotate()); -#endif - if (!ret) { - ::fprintf(stderr, "DMRGateway: unable to open the log file\n"); - return 1; - } - #if !defined(_WIN32) && !defined(_WIN64) if (m_daemon) { ::close(STDIN_FILENO); @@ -360,6 +354,20 @@ int CDMRGateway::run() ::close(STDERR_FILENO); } #endif + ::LogInitialise(m_conf.getLogDisplayLevel(), m_conf.getLogMQTTLevel()); + + std::vector> subscriptions; + if (m_conf.getDynamicTGControlEnabled()) + subscriptions.push_back(std::make_pair("dynamic", CDMRGateway::onDynamic)); + if (m_conf.getRemoteCommandsEnabled()) + subscriptions.push_back(std::make_pair("command", CDMRGateway::onCommand)); + + m_mqtt = new CMQTTConnection(m_conf.getMQTTAddress(), m_conf.getMQTTPort(), m_conf.getMQTTName(), subscriptions, m_conf.getMQTTKeepalive()); + ret = m_mqtt->open(); + if (!ret) { + delete m_mqtt; + return 1; + } m_network1Enabled = m_conf.getDMRNetwork1Enabled(); m_network2Enabled = m_conf.getDMRNetwork2Enabled(); @@ -448,24 +456,9 @@ int CDMRGateway::run() } } - bool remoteControlEnabled = m_conf.getRemoteControlEnabled(); - if (remoteControlEnabled) { - std::string address = m_conf.getRemoteControlAddress(); - unsigned short port = m_conf.getRemoteControlPort(); - - LogInfo("Remote Control Parameters"); - LogInfo(" Address: %s", address.c_str()); - LogInfo(" Port: %hu", port); - - m_remoteControl = new CRemoteControl(this, address, port); - - ret = m_remoteControl->open(); - if (!ret) { - LogInfo("Failed to open Remove Control Socket"); - delete m_remoteControl; - m_remoteControl = NULL; - } - } + bool remoteCommandsEnabled = m_conf.getRemoteCommandsEnabled(); + if (remoteCommandsEnabled) + m_remoteControl = new CRemoteControl(this); if (m_network1Enabled && m_conf.getDMRNetwork1Enabled()) { ret = createDMRNetwork1(); @@ -497,12 +490,6 @@ int CDMRGateway::run() return 1; } - if (m_conf.getDynamicTGControlEnabled()) { - bool ret = createDynamicTGControl(); - if (!ret) - return 1; - } - createAPRS(); unsigned int rfTimeout = m_conf.getRFTimeout(); @@ -1222,11 +1209,6 @@ int CDMRGateway::run() m_repeater->write(data); } - if (m_socket != NULL) - processDynamicTGControl(); - - remoteControl(); - unsigned int ms = stopWatch.elapsed(); stopWatch.start(); @@ -1286,10 +1268,7 @@ int CDMRGateway::run() m_repeater->close(); delete m_repeater; - if (m_remoteControl != NULL) { - m_remoteControl->close(); - delete m_remoteControl; - } + delete m_remoteControl; #if defined(USE_GPSD) if (m_gpsd != NULL) { @@ -1333,11 +1312,6 @@ int CDMRGateway::run() delete m_xlxNetwork; } - if (m_socket != NULL) { - m_socket->close(); - delete m_socket; - } - delete timer[1U]; delete timer[2U]; @@ -2288,22 +2262,6 @@ bool CDMRGateway::createXLXNetwork() return true; } -bool CDMRGateway::createDynamicTGControl() -{ - unsigned short port = m_conf.getDynamicTGControlPort(); - - m_socket = new CUDPSocket(port); - - bool ret = m_socket->open(); - if (!ret) { - delete m_socket; - m_socket = NULL; - return false; - } - - return true; -} - bool CDMRGateway::linkXLX(const std::string &number) { CReflector* reflector = m_xlxReflectors->find(number); @@ -2520,15 +2478,13 @@ void CDMRGateway::createAPRS() if (!m_conf.getAPRSEnabled()) return; - std::string address = m_conf.getAPRSAddress(); - unsigned short port = m_conf.getAPRSPort(); - std::string suffix = m_conf.getAPRSSuffix(); - bool debug = m_conf.getDebug(); + std::string suffix = m_conf.getAPRSSuffix(); + bool debug = m_conf.getDebug(); - m_writer = new CAPRSWriter(m_callsign, suffix, address, port, debug); + m_writer = new CAPRSWriter(m_callsign, suffix, debug); - std::string desc = m_conf.getAPRSDescription(); - std::string symbol = m_conf.getAPRSSymbol(); + std::string desc = m_conf.getAPRSDescription(); + std::string symbol = m_conf.getAPRSSymbol(); m_writer->setInfo(m_txFrequency, m_rxFrequency, desc, symbol); @@ -2551,42 +2507,38 @@ void CDMRGateway::createAPRS() #endif } -void CDMRGateway::processDynamicTGControl() +void CDMRGateway::processDynamicTGControl(const std::string& command) { - unsigned char buffer[100U]; - sockaddr_storage address; - unsigned int addrlen; - int len = m_socket->read(buffer, 100U, address, addrlen); - if (len <= 0) - return; + std::vector args; - buffer[len] = '\0'; + std::stringstream tokeniser(command); - if (::memcmp(buffer + 0U, "DynTG", 5U) == 0) { - char* pSlot = ::strtok((char*)(buffer + 5U), ", \r\n"); - char* pTG = ::strtok(NULL, ", \r\n"); + // Parse the original command into a vector of strings. + std::string token; + while (std::getline(tokeniser, token, ' ')) + args.push_back(token); - if (pSlot == NULL || pTG == NULL) { + if (args.at(0U) == "DynTG") { + if (args.size() < 3) { LogWarning("Malformed dynamic TG control message"); return; } - unsigned int slot = (unsigned int)::atoi(pSlot); - unsigned int tg = (unsigned int)::atoi(pTG); + unsigned int slot = (unsigned int)std::stoi(args.at(1U)); + unsigned int tg = (unsigned int)std::stoi(args.at(2U)); for (std::vector::iterator it = m_dynRF.begin(); it != m_dynRF.end(); ++it) (*it)->tgChange(slot, tg); } else { - LogWarning("Unknown dynamic TG control message: %s", buffer); + LogWarning("Unknown dynamic TG control message: %s", command.c_str()); } } -void CDMRGateway::remoteControl() +void CDMRGateway::remoteControl(const std::string& commandStr) { - if (m_remoteControl == NULL) - return; + assert(m_remoteControl != NULL); - REMOTE_COMMAND command = m_remoteControl->getCommand(); + REMOTE_COMMAND command = m_remoteControl->processCommand(commandStr); switch (command) { case RCD_ENABLE_NETWORK1: processEnableCommand(m_dmrNetwork1, "DMR Network 1", m_network1Enabled, true); @@ -2689,3 +2641,18 @@ void CDMRGateway::buildNetworkHostNetworkString(std::string &str, const std::str str += name + ":\""+ ((network == NULL) ? "NONE" : ((host.length() > 0) ? host : "NONE")) + "\""; } } + +void CDMRGateway::onDynamic(const unsigned char* message, unsigned int length) +{ + assert(gateway != NULL); + + gateway->processDynamicTGControl(std::string((char*)message, length)); +} + +void CDMRGateway::onCommand(const unsigned char* message, unsigned int length) +{ + assert(gateway != NULL); + + gateway->remoteControl(std::string((char*)message, length)); +} + diff --git a/DMRGateway.h b/DMRGateway.h index 93bd330..0f97728 100644 --- a/DMRGateway.h +++ b/DMRGateway.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2017,2019,2020 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2017,2019,2020,2023 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 @@ -117,7 +117,6 @@ private: std::vector m_dmr5Passalls; std::vector m_dynVoices; std::vector m_dynRF; - CUDPSocket* m_socket; CAPRSWriter* m_writer; std::string m_callsign; unsigned int m_txFrequency; @@ -140,7 +139,6 @@ private: bool createDMRNetwork4(); bool createDMRNetwork5(); bool createXLXNetwork(); - bool createDynamicTGControl(); bool linkXLX(const std::string &number); void unlinkXLX(); @@ -153,11 +151,14 @@ private: void processRadioPosition(); void processTalkerAlias(); void createAPRS(); - void processDynamicTGControl(); - void remoteControl(); + void processDynamicTGControl(const std::string& command); + void remoteControl(const std::string& command); void processEnableCommand(CDMRNetwork* network, const std::string& name, bool& mode, bool enabled); void buildNetworkStatusNetworkString(std::string &str, const std::string& name, CDMRNetwork* network, bool enabled); void buildNetworkHostNetworkString(std::string &str, const std::string& name, CDMRNetwork* network); + + static void onCommand(const unsigned char* message, unsigned int length); + static void onDynamic(const unsigned char* message, unsigned int length); }; #endif diff --git a/DMRGateway.ini b/DMRGateway.ini index fd66c3a..37ffbff 100644 --- a/DMRGateway.ini +++ b/DMRGateway.ini @@ -13,10 +13,7 @@ Debug=0 [Log] # Logging levels, 0=No logging DisplayLevel=1 -FileLevel=1 -FilePath=. -FileRoot=DMRGateway -FileRotate=1 +MQTTLevel=1 [Voice] Enabled=1 @@ -145,17 +142,19 @@ Port=2947 [APRS] Enable=0 -Address=127.0.0.1 -Port=8673 Description=APRS Description Suffix=3 # Symbol="/r" -[Dynamic TG Control] -Enabled=1 -Port=3769 - -[Remote Control] -Enable=0 +[MQTT] Address=127.0.0.1 -Port=7643 +Port=1883 +Keepalive=60 +Name=dmr-gateway + +[Dynamic TG Control] +Enable=1 + +[Remote Commands] +Enable=0 + diff --git a/Log.cpp b/Log.cpp index 752601e..4537544 100644 --- a/Log.cpp +++ b/Log.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2020 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2020,2022,2023 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 @@ -17,6 +17,7 @@ */ #include "Log.h" +#include "MQTTConnection.h" #if defined(_WIN32) || defined(_WIN64) #include @@ -32,117 +33,27 @@ #include #include -static unsigned int m_fileLevel = 2U; -static std::string m_filePath; -static std::string m_fileRoot; -static bool m_fileRotate = true; +CMQTTConnection* m_mqtt = NULL; -static FILE* m_fpLog = NULL; -static bool m_daemon = false; +static unsigned int m_mqttLevel = 2U; static unsigned int m_displayLevel = 2U; -static struct tm m_tm; - static char LEVELS[] = " DMIWEF"; -static bool logOpenRotate() +void LogInitialise(unsigned int displayLevel, unsigned int mqttLevel) { - bool status = false; - - if (m_fileLevel == 0U) - return true; - - time_t now; - ::time(&now); - - struct tm* tm = ::gmtime(&now); - - if (tm->tm_mday == m_tm.tm_mday && tm->tm_mon == m_tm.tm_mon && tm->tm_year == m_tm.tm_year) { - if (m_fpLog != NULL) - return true; - } else { - if (m_fpLog != NULL) - ::fclose(m_fpLog); - } - - char filename[200U]; -#if defined(_WIN32) || defined(_WIN64) - ::sprintf(filename, "%s\\%s-%04d-%02d-%02d.log", m_filePath.c_str(), m_fileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); -#else - ::sprintf(filename, "%s/%s-%04d-%02d-%02d.log", m_filePath.c_str(), m_fileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); -#endif - - if ((m_fpLog = ::fopen(filename, "a+t")) != NULL) { - status = true; - -#if !defined(_WIN32) && !defined(_WIN64) - if (m_daemon) - dup2(fileno(m_fpLog), fileno(stderr)); -#endif - } - - m_tm = *tm; - - return status; -} - -static bool logOpenNoRotate() -{ - bool status = false; - - if (m_fileLevel == 0U) - return true; - - if (m_fpLog != NULL) - return true; - - char filename[200U]; -#if defined(_WIN32) || defined(_WIN64) - ::sprintf(filename, "%s\\%s.log", m_filePath.c_str(), m_fileRoot.c_str()); -#else - ::sprintf(filename, "%s/%s.log", m_filePath.c_str(), m_fileRoot.c_str()); -#endif - - if ((m_fpLog = ::fopen(filename, "a+t")) != NULL) { - status = true; - -#if !defined(_WIN32) && !defined(_WIN64) - if (m_daemon) - dup2(fileno(m_fpLog), fileno(stderr)); -#endif - } - - return status; -} - -bool LogOpen() -{ - if (m_fileRotate) - return logOpenRotate(); - else - return logOpenNoRotate(); -} - -bool LogInitialise(bool daemon, const std::string& filePath, const std::string& fileRoot, unsigned int fileLevel, unsigned int displayLevel, bool rotate) -{ - m_filePath = filePath; - m_fileRoot = fileRoot; - m_fileLevel = fileLevel; + m_mqttLevel = mqttLevel; m_displayLevel = displayLevel; - m_daemon = daemon; - m_fileRotate = rotate; - - if (m_daemon) - m_displayLevel = 0U; - - return ::LogOpen(); } void LogFinalise() { - if (m_fpLog != NULL) - ::fclose(m_fpLog); + if (m_mqtt != NULL) { + m_mqtt->close(); + delete m_mqtt; + m_mqtt = NULL; + } } void Log(unsigned int level, const char* fmt, ...) @@ -171,22 +82,26 @@ void Log(unsigned int level, const char* fmt, ...) va_end(vl); - if (level >= m_fileLevel && m_fileLevel != 0U) { - bool ret = ::LogOpen(); - if (!ret) - return; - - ::fprintf(m_fpLog, "%s\n", buffer); - ::fflush(m_fpLog); - } + if (m_mqtt != NULL && level >= m_mqttLevel && m_mqttLevel != 0U) + m_mqtt->publish("log", buffer); if (level >= m_displayLevel && m_displayLevel != 0U) { ::fprintf(stdout, "%s\n", buffer); ::fflush(stdout); } - if (level == 6U) { // Fatal - ::fclose(m_fpLog); + if (level == 6U) // Fatal exit(1); +} + +void WriteJSON(const std::string& topLevel, nlohmann::json& json) +{ + if (m_mqtt != NULL) { + nlohmann::json top; + + top[topLevel] = json; + + m_mqtt->publish("json", top.dump()); } } + diff --git a/Log.h b/Log.h index ae95b60..7392164 100644 --- a/Log.h +++ b/Log.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015,2016,2020 by Jonathan Naylor G4KLX + * Copyright (C) 2015,2016,2020,2022,2023 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 @@ -21,6 +21,8 @@ #include +#include + #define LogDebug(fmt, ...) Log(1U, fmt, ##__VA_ARGS__) #define LogMessage(fmt, ...) Log(2U, fmt, ##__VA_ARGS__) #define LogInfo(fmt, ...) Log(3U, fmt, ##__VA_ARGS__) @@ -30,7 +32,9 @@ extern void Log(unsigned int level, const char* fmt, ...); -extern bool LogInitialise(bool daemon, const std::string& filePath, const std::string& fileRoot, unsigned int fileLevel, unsigned int displayLevel, bool rotate); +extern void LogInitialise(unsigned int displayLevel, unsigned int mqttLevel); extern void LogFinalise(); +extern void WriteJSON(const std::string& topLevel, nlohmann::json& json); + #endif diff --git a/MQTTConnection.cpp b/MQTTConnection.cpp new file mode 100644 index 0000000..fa951e5 --- /dev/null +++ b/MQTTConnection.cpp @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2022,2023 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 "MQTTConnection.h" + +#include +#include +#include + + +CMQTTConnection::CMQTTConnection(const std::string& host, unsigned short port, const std::string& name, const std::vector>& subs, unsigned int keepalive, MQTT_QOS qos) : +m_host(host), +m_port(port), +m_name(name), +m_subs(subs), +m_keepalive(keepalive), +m_qos(qos), +m_mosq(NULL), +m_connected(false) +{ + assert(!host.empty()); + assert(port > 0U); + assert(!name.empty()); + assert(keepalive >= 5U); + + ::mosquitto_lib_init(); +} + +CMQTTConnection::~CMQTTConnection() +{ + ::mosquitto_lib_cleanup(); +} + +bool CMQTTConnection::open() +{ + m_mosq = ::mosquitto_new(m_name.c_str(), true, this); + if (m_mosq == NULL){ + ::fprintf(stderr, "MQTT Error newing: Out of memory.\n"); + return false; + } + + ::mosquitto_connect_callback_set(m_mosq, onConnect); + ::mosquitto_subscribe_callback_set(m_mosq, onSubscribe); + ::mosquitto_message_callback_set(m_mosq, onMessage); + ::mosquitto_disconnect_callback_set(m_mosq, onDisconnect); + + int rc = ::mosquitto_connect(m_mosq, m_host.c_str(), m_port, m_keepalive); + if (rc != MOSQ_ERR_SUCCESS) { + ::mosquitto_destroy(m_mosq); + m_mosq = NULL; + ::fprintf(stderr, "MQTT Error connecting: %s\n", ::mosquitto_strerror(rc)); + return false; + } + + rc = ::mosquitto_loop_start(m_mosq); + if (rc != MOSQ_ERR_SUCCESS) { + ::mosquitto_disconnect(m_mosq); + ::mosquitto_destroy(m_mosq); + m_mosq = NULL; + ::fprintf(stderr, "MQTT Error loop starting: %s\n", ::mosquitto_strerror(rc)); + return false; + } + + return true; +} + +bool CMQTTConnection::publish(const char* topic, const char* text) +{ + assert(topic != NULL); + assert(text != NULL); + + return publish(topic, (unsigned char*)text, ::strlen(text)); +} + +bool CMQTTConnection::publish(const char* topic, const std::string& text) +{ + assert(topic != NULL); + + return publish(topic, (unsigned char*)text.c_str(), text.size()); +} + +bool CMQTTConnection::publish(const char* topic, const unsigned char* data, unsigned int len) +{ + assert(topic != NULL); + assert(data != NULL); + + if (!m_connected) + return false; + + if (::strchr(topic, '/') == NULL) { + char topicEx[100U]; + ::sprintf(topicEx, "%s/%s", m_name.c_str(), topic); + + int rc = ::mosquitto_publish(m_mosq, NULL, topicEx, len, data, static_cast(m_qos), false); + if (rc != MOSQ_ERR_SUCCESS) { + ::fprintf(stderr, "MQTT Error publishing: %s\n", ::mosquitto_strerror(rc)); + return false; + } + } else { + int rc = ::mosquitto_publish(m_mosq, NULL, topic, len, data, static_cast(m_qos), false); + if (rc != MOSQ_ERR_SUCCESS) { + ::fprintf(stderr, "MQTT Error publishing: %s\n", ::mosquitto_strerror(rc)); + return false; + } + } + + return true; +} + +void CMQTTConnection::close() +{ + if (m_mosq != NULL) { + ::mosquitto_disconnect(m_mosq); + ::mosquitto_destroy(m_mosq); + m_mosq = NULL; + } +} + +void CMQTTConnection::onConnect(mosquitto* mosq, void* obj, int rc) +{ + assert(mosq != NULL); + assert(obj != NULL); + + ::fprintf(stdout, "MQTT: on_connect: %s\n", ::mosquitto_connack_string(rc)); + if (rc != 0) { + ::mosquitto_disconnect(mosq); + return; + } + + CMQTTConnection* p = static_cast(obj); + p->m_connected = true; + + for (std::vector>::const_iterator it = p->m_subs.cbegin(); it != p->m_subs.cend(); ++it) { + std::string topic = (*it).first; + + if (topic.find_first_of('/') == std::string::npos) { + char topicEx[100U]; + ::sprintf(topicEx, "%s/%s", p->m_name.c_str(), topic.c_str()); + + rc = ::mosquitto_subscribe(mosq, NULL, topicEx, static_cast(p->m_qos)); + if (rc != MOSQ_ERR_SUCCESS) { + ::fprintf(stderr, "MQTT: error subscribing to %s - %s\n", topicEx, ::mosquitto_strerror(rc)); + ::mosquitto_disconnect(mosq); + } + } else { + rc = ::mosquitto_subscribe(mosq, NULL, topic.c_str(), static_cast(p->m_qos)); + if (rc != MOSQ_ERR_SUCCESS) { + ::fprintf(stderr, "MQTT: error subscribing to %s - %s\n", topic.c_str(), ::mosquitto_strerror(rc)); + ::mosquitto_disconnect(mosq); + } + } + } +} + +void CMQTTConnection::onSubscribe(mosquitto* mosq, void* obj, int mid, int qosCount, const int* grantedQOS) +{ + assert(mosq != NULL); + assert(obj != NULL); + assert(grantedQOS != NULL); + + for (int i = 0; i < qosCount; i++) + ::fprintf(stdout, "MQTT: on_subscribe: %d:%d\n", i, grantedQOS[i]); +} + +void CMQTTConnection::onMessage(mosquitto* mosq, void* obj, const mosquitto_message* message) +{ + assert(mosq != NULL); + assert(obj != NULL); + assert(message != NULL); + + CMQTTConnection* p = static_cast(obj); + + for (std::vector>::const_iterator it = p->m_subs.cbegin(); it != p->m_subs.cend(); ++it) { + std::string topic = (*it).first; + + char topicEx[100U]; + ::sprintf(topicEx, "%s/%s", p->m_name.c_str(), topic.c_str()); + + if (::strcmp(topicEx, message->topic) == 0) { + (*it).second((unsigned char*)message->payload, message->payloadlen); + break; + } + } +} + +void CMQTTConnection::onDisconnect(mosquitto* mosq, void* obj, int rc) +{ + assert(mosq != NULL); + assert(obj != NULL); + + ::fprintf(stdout, "MQTT: on_disconnect: %s\n", ::mosquitto_reason_string(rc)); + + CMQTTConnection* p = static_cast(obj); + p->m_connected = false; +} + diff --git a/MQTTConnection.h b/MQTTConnection.h new file mode 100644 index 0000000..8fc98ce --- /dev/null +++ b/MQTTConnection.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2022,2023 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. + */ + +#if !defined(MQTTPUBLISHER_H) +#define MQTTPUBLISHER_H + +#include + +#include +#include + +enum MQTT_QOS { + MQTT_QOS_AT_MODE_ONCE = 0U, + MQTT_QOS_AT_LEAST_ONCE = 1U, + MQTT_QOS_EXACTLY_ONCE = 2U +}; + +class CMQTTConnection { +public: + CMQTTConnection(const std::string& host, unsigned short port, const std::string& name, const std::vector>& subs, unsigned int keepalive, MQTT_QOS qos = MQTT_QOS_EXACTLY_ONCE); + ~CMQTTConnection(); + + bool open(); + + bool publish(const char* topic, const char* text); + bool publish(const char* topic, const std::string& text); + bool publish(const char* topic, const unsigned char* data, unsigned int len); + + void close(); + +private: + std::string m_host; + unsigned short m_port; + std::string m_name; + std::vector> m_subs; + unsigned int m_keepalive; + MQTT_QOS m_qos; + mosquitto* m_mosq; + bool m_connected; + + static void onConnect(mosquitto* mosq, void* obj, int rc); + static void onSubscribe(mosquitto* mosq, void* obj, int mid, int qosCount, const int* grantedQOS); + static void onMessage(mosquitto* mosq, void* obj, const mosquitto_message* message); + static void onDisconnect(mosquitto* mosq, void* obj, int rc); +}; + +#endif + diff --git a/Makefile b/Makefile index f3b7b2c..3393386 100644 --- a/Makefile +++ b/Makefile @@ -2,19 +2,19 @@ CC = cc CXX = c++ # Use the following CFLAGS and LIBS if you don't want to use gpsd. -CFLAGS = -g -O3 -Wall -DHAVE_LOG_H -std=c++0x -pthread -LIBS = -lpthread +CFLAGS = -g -O3 -Wall -std=c++0x -pthread +LIBS = -lpthread -lmosquitto # Use the following CFLAGS and LIBS if you do want to use gpsd. -#CFLAGS = -g -O3 -Wall -DHAVE_LOG_H -DUSE_GPSD -std=c++0x -pthread -#LIBS = -lpthread -lgps +#CFLAGS = -g -O3 -Wall -DUSE_GPSD -std=c++0x -pthread +#LIBS = -lpthread -lgps -lmosquitto LDFLAGS = -g 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 RemoteControl.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 + DMRLC.o DMRNetwork.o DMRSlotType.o DynVoice.o Golay2087.o GPSD.o Hamming.o Log.o MMDVMNetwork.o MQTTConnection.o PassAllPC.o \ + PassAllTG.o QR1676.o Reflectors.o RemoteControl.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 diff --git a/RemoteControl.cpp b/RemoteControl.cpp index f84f11f..f56e038 100644 --- a/RemoteControl.cpp +++ b/RemoteControl.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019,2021 by Jonathan Naylor G4KLX + * Copyright (C) 2019,2021,2023 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 @@ -16,6 +16,7 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include "MQTTConnection.h" #include "RemoteControl.h" #include "Log.h" #include "DMRGateway.h" @@ -30,112 +31,95 @@ const unsigned int DISABLE_ARGS = 2U; const unsigned int BUFFER_LENGTH = 100U; -CRemoteControl::CRemoteControl(CDMRGateway* host, const std::string address, unsigned short port) : +// In Log.cpp +extern CMQTTConnection* m_mqtt; + +CRemoteControl::CRemoteControl(CDMRGateway* host) : m_host(host), -m_socket(address, port), m_command(RCD_NONE), m_args() { - assert(port > 0U); } CRemoteControl::~CRemoteControl() { } -bool CRemoteControl::open() -{ - return m_socket.open(); -} - -REMOTE_COMMAND CRemoteControl::getCommand() +REMOTE_COMMAND CRemoteControl::processCommand(const std::string& command) { m_command = RCD_NONE; m_args.clear(); - char command[BUFFER_LENGTH]; - char buffer[BUFFER_LENGTH * 2]; std::string replyStr = "OK"; - sockaddr_storage address; - unsigned int addrlen; - int ret = m_socket.read((unsigned char*)buffer, BUFFER_LENGTH, address, addrlen); - if (ret > 0) { - buffer[ret] = '\0'; - // Make a copy of the original command for logging. - ::strcpy(command, buffer); + std::stringstream tokeniser(command); - // Parse the original command into a vector of strings. - char* b = buffer; - char* p = NULL; - while ((p = ::strtok(b, " ")) != NULL) { - b = NULL; - m_args.push_back(std::string(p)); - } - if (m_args.at(0U) == "enable" && m_args.size() >= ENABLE_ARGS) { - if (m_args.at(1U) == "net1") - m_command = RCD_ENABLE_NETWORK1; - else if (m_args.at(1U) == "net2") - m_command = RCD_ENABLE_NETWORK2; - else if (m_args.at(1U) == "net3") - m_command = RCD_ENABLE_NETWORK3; - else if (m_args.at(1U) == "net4") - m_command = RCD_ENABLE_NETWORK4; - else if (m_args.at(1U) == "net5") - m_command = RCD_ENABLE_NETWORK5; - else if (m_args.at(1U) == "xlx") - m_command = RCD_ENABLE_XLX; - else - replyStr = "KO"; - } else if (m_args.at(0U) == "disable" && m_args.size() >= DISABLE_ARGS) { - if (m_args.at(1U) == "net1") - m_command = RCD_DISABLE_NETWORK1; - else if (m_args.at(1U) == "net2") - m_command = RCD_DISABLE_NETWORK2; - else if (m_args.at(1U) == "net3") - m_command = RCD_DISABLE_NETWORK3; - else if (m_args.at(1U) == "net4") - m_command = RCD_DISABLE_NETWORK4; - else if (m_args.at(1U) == "net5") - m_command = RCD_DISABLE_NETWORK5; - else if (m_args.at(1U) == "xlx") - m_command = RCD_DISABLE_XLX; - else - replyStr = "KO"; - } else if (m_args.at(0U) == "status") { - if (m_host != NULL) { - m_host->buildNetworkStatusString(replyStr); - } - else { - replyStr = "KO"; - } + // Parse the original command into a vector of strings. + std::string token; + while (std::getline(tokeniser, token, ' ')) + m_args.push_back(token); - m_command = RCD_CONNECTION_STATUS; - } else if (m_args.at(0U) == "hosts") { - if (m_host != NULL) { - m_host->buildNetworkHostsString(replyStr); - } - else { - replyStr = "KO"; - } - - m_command = RCD_CONFIG_HOSTS; - } else { + if (m_args.at(0U) == "enable" && m_args.size() >= ENABLE_ARGS) { + if (m_args.at(1U) == "net1") + m_command = RCD_ENABLE_NETWORK1; + else if (m_args.at(1U) == "net2") + m_command = RCD_ENABLE_NETWORK2; + else if (m_args.at(1U) == "net3") + m_command = RCD_ENABLE_NETWORK3; + else if (m_args.at(1U) == "net4") + m_command = RCD_ENABLE_NETWORK4; + else if (m_args.at(1U) == "net5") + m_command = RCD_ENABLE_NETWORK5; + else if (m_args.at(1U) == "xlx") + m_command = RCD_ENABLE_XLX; + else + replyStr = "KO"; + } else if (m_args.at(0U) == "disable" && m_args.size() >= DISABLE_ARGS) { + if (m_args.at(1U) == "net1") + m_command = RCD_DISABLE_NETWORK1; + else if (m_args.at(1U) == "net2") + m_command = RCD_DISABLE_NETWORK2; + else if (m_args.at(1U) == "net3") + m_command = RCD_DISABLE_NETWORK3; + else if (m_args.at(1U) == "net4") + m_command = RCD_DISABLE_NETWORK4; + else if (m_args.at(1U) == "net5") + m_command = RCD_DISABLE_NETWORK5; + else if (m_args.at(1U) == "xlx") + m_command = RCD_DISABLE_XLX; + else + replyStr = "KO"; + } else if (m_args.at(0U) == "status") { + if (m_host != NULL) + m_host->buildNetworkStatusString(replyStr); + else replyStr = "KO"; - } - ::snprintf(buffer, BUFFER_LENGTH * 2, "%s remote command of \"%s\" received", ((m_command == RCD_NONE) ? "Invalid" : "Valid"), command); - if (m_command == RCD_NONE) { - m_args.clear(); - LogWarning(buffer); - } else { -#if !defined(REMOTE_COMMAND_NO_LOG) - LogMessage(buffer); -#endif - } + m_command = RCD_CONNECTION_STATUS; + } else if (m_args.at(0U) == "hosts") { + if (m_host != NULL) + m_host->buildNetworkHostsString(replyStr); + else + replyStr = "KO"; - m_socket.write((unsigned char*)replyStr.c_str(), (unsigned int)replyStr.length(), address, addrlen); + m_command = RCD_CONFIG_HOSTS; + } else { + replyStr = "KO"; } + + char buffer[200U]; + ::snprintf(buffer, 200, "%s remote command of \"%s\" received", ((m_command == RCD_NONE) ? "Invalid" : "Valid"), command.c_str()); + + if (m_command == RCD_NONE) { + m_args.clear(); + LogWarning(buffer); + } else { +#if !defined(REMOTE_COMMAND_NO_LOG) + LogMessage(buffer); +#endif + } + + m_mqtt->publish("response", replyStr); return m_command; } @@ -171,7 +155,3 @@ int CRemoteControl::getArgInt(unsigned int n) const return ::atoi(getArgString(n).c_str()); } -void CRemoteControl::close() -{ - m_socket.close(); -} diff --git a/RemoteControl.h b/RemoteControl.h index 260cdc8..b5eec4f 100644 --- a/RemoteControl.h +++ b/RemoteControl.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019,2021 by Jonathan Naylor G4KLX + * Copyright (C) 2019,2021,2023 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 @@ -19,8 +19,6 @@ #ifndef RemoteControl_H #define RemoteControl_H -#include "UDPSocket.h" - #include #include @@ -47,12 +45,10 @@ class CDMRNetwork; class CRemoteControl { public: - CRemoteControl(CDMRGateway* host, const std::string address, unsigned short port); + CRemoteControl(CDMRGateway* host); ~CRemoteControl(); - bool open(); - - REMOTE_COMMAND getCommand(); + REMOTE_COMMAND processCommand(const std::string& command); unsigned int getArgCount() const; @@ -60,11 +56,8 @@ public: unsigned int getArgUInt(unsigned int n) const; signed int getArgInt(unsigned int n) const; - void close(); - private: CDMRGateway* m_host; - CUDPSocket m_socket; REMOTE_COMMAND m_command; std::vector m_args; }; diff --git a/Version.h b/Version.h index 44e8fb3..a971c36 100644 --- a/Version.h +++ b/Version.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015-2021 by Jonathan Naylor G4KLX + * Copyright (C) 2015-2023 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 @@ -19,6 +19,6 @@ #if !defined(VERSION_H) #define VERSION_H -const char* VERSION = "20230212"; +const char* VERSION = "20230707"; #endif