mirror of
https://github.com/g4klx/DAPNETGateway.git
synced 2026-08-26 12:32:45 -04:00
Compare commits
20 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b869053a80 | ||
|
|
5aafb6c983 | ||
|
|
552754628f | ||
|
|
8abb83120c | ||
|
|
fbba1d449e | ||
|
|
c97ce090a2 | ||
|
|
edac3b873a | ||
|
|
3b99af8afc | ||
|
|
5cf7887c18 | ||
|
|
1cb140bc29 | ||
|
|
d1bd0b549a | ||
|
|
3aee1f0ed8 | ||
|
|
2a13d8b4d1 | ||
|
|
496c22f4c2 | ||
|
|
98aa2e877d | ||
|
|
c28edb9c4f | ||
|
|
0eda05e064 | ||
|
|
97db2e9b45 | ||
|
|
f0a271ae53 | ||
|
|
e677d6a954 |
20 changed files with 609 additions and 297 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -2,7 +2,9 @@ Debug
|
|||
Release
|
||||
x64
|
||||
DAPNETGateway
|
||||
GitVersion.h
|
||||
*.o
|
||||
*.d
|
||||
*.opendb
|
||||
*.bak
|
||||
*.obj
|
||||
|
|
|
|||
78
Conf.cpp
78
Conf.cpp
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2018,2020,2025 by Jonathan Naylor G4KLX
|
||||
* Copyright (C) 2018,2020,2023,2025 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
|
||||
|
|
@ -31,6 +31,7 @@ enum class SECTION {
|
|||
NONE,
|
||||
GENERAL,
|
||||
LOG,
|
||||
MQTT,
|
||||
DAPNET
|
||||
};
|
||||
|
||||
|
|
@ -46,10 +47,14 @@ m_myAddress(),
|
|||
m_myPort(0U),
|
||||
m_daemon(false),
|
||||
m_logDisplayLevel(0U),
|
||||
m_logFileLevel(0U),
|
||||
m_logFilePath(),
|
||||
m_logFileRoot(),
|
||||
m_logFileRotate(true),
|
||||
m_logMQTTLevel(0U),
|
||||
m_mqttAddress("127.0.0.1"),
|
||||
m_mqttPort(1883U),
|
||||
m_mqttKeepalive(60U),
|
||||
m_mqttName("dapnet-gateway"),
|
||||
m_mqttAuthEnabled(false),
|
||||
m_mqttUsername(),
|
||||
m_mqttPassword(),
|
||||
m_dapnetAddress(),
|
||||
m_dapnetPort(0U),
|
||||
m_dapnetAuthKey(),
|
||||
|
|
@ -81,6 +86,8 @@ bool CConf::read()
|
|||
section = SECTION::GENERAL;
|
||||
else if (::strncmp(buffer, "[Log]", 5U) == 0)
|
||||
section = SECTION::LOG;
|
||||
else if (::strncmp(buffer, "[MQTT]", 6U) == 0)
|
||||
section = SECTION::MQTT;
|
||||
else if (::strncmp(buffer, "[DAPNET]", 8U) == 0)
|
||||
section = SECTION::DAPNET;
|
||||
else
|
||||
|
|
@ -151,16 +158,25 @@ bool CConf::read()
|
|||
else if (::strcmp(key, "Daemon") == 0)
|
||||
m_daemon = ::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::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 (::strcmp(key, "Auth") == 0)
|
||||
m_mqttAuthEnabled = ::atoi(value) == 1;
|
||||
else if (::strcmp(key, "Username") == 0)
|
||||
m_mqttUsername = value;
|
||||
else if (::strcmp(key, "Password") == 0)
|
||||
m_mqttPassword = value;
|
||||
} else if (section == SECTION::DAPNET) {
|
||||
if (::strcmp(key, "Address") == 0)
|
||||
m_dapnetAddress = value;
|
||||
|
|
@ -236,24 +252,44 @@ unsigned int CConf::getLogDisplayLevel() const
|
|||
return m_logDisplayLevel;
|
||||
}
|
||||
|
||||
unsigned int CConf::getLogFileLevel() const
|
||||
unsigned int CConf::getLogMQTTLevel() const
|
||||
{
|
||||
return m_logFileLevel;
|
||||
return m_logMQTTLevel;
|
||||
}
|
||||
|
||||
std::string CConf::getLogFilePath() const
|
||||
std::string CConf::getMQTTAddress() const
|
||||
{
|
||||
return m_logFilePath;
|
||||
return m_mqttAddress;
|
||||
}
|
||||
|
||||
std::string CConf::getLogFileRoot() const
|
||||
unsigned short CConf::getMQTTPort() const
|
||||
{
|
||||
return m_logFileRoot;
|
||||
return m_mqttPort;
|
||||
}
|
||||
|
||||
bool CConf::getLogFileRotate() const
|
||||
unsigned int CConf::getMQTTKeepalive() const
|
||||
{
|
||||
return m_logFileRotate;
|
||||
return m_mqttKeepalive;
|
||||
}
|
||||
|
||||
std::string CConf::getMQTTName() const
|
||||
{
|
||||
return m_mqttName;
|
||||
}
|
||||
|
||||
bool CConf::getMQTTAuthEnabled() const
|
||||
{
|
||||
return m_mqttAuthEnabled;
|
||||
}
|
||||
|
||||
std::string CConf::getMQTTUsername() const
|
||||
{
|
||||
return m_mqttUsername;
|
||||
}
|
||||
|
||||
std::string CConf::getMQTTPassword() const
|
||||
{
|
||||
return m_mqttPassword;
|
||||
}
|
||||
|
||||
std::string CConf::getDAPNETAddress() const
|
||||
|
|
|
|||
103
Conf.h
103
Conf.h
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2018 by Jonathan Naylor G4KLX
|
||||
* Copyright (C) 2018,2023,2025 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
|
||||
|
|
@ -25,61 +25,72 @@
|
|||
class CConf
|
||||
{
|
||||
public:
|
||||
CConf(const std::string& file);
|
||||
~CConf();
|
||||
CConf(const std::string& file);
|
||||
~CConf();
|
||||
|
||||
bool read();
|
||||
bool read();
|
||||
|
||||
// The General section
|
||||
std::string getCallsign() const;
|
||||
std::vector<unsigned int> getWhiteList() const;
|
||||
std::vector<unsigned int> getBlackList() const;
|
||||
std::string getblacklistRegexfile() const;
|
||||
std::string getwhitelistRegexfile() const;
|
||||
std::string getRptAddress() const;
|
||||
unsigned short getRptPort() const;
|
||||
std::string getMyAddress() const;
|
||||
unsigned short getMyPort() const;
|
||||
bool getDaemon() const;
|
||||
// The General section
|
||||
std::string getCallsign() const;
|
||||
std::vector<unsigned int> getWhiteList() const;
|
||||
std::vector<unsigned int> getBlackList() const;
|
||||
std::string getblacklistRegexfile() const;
|
||||
std::string getwhitelistRegexfile() const;
|
||||
std::string getRptAddress() const;
|
||||
unsigned short getRptPort() const;
|
||||
std::string getMyAddress() const;
|
||||
unsigned short getMyPort() const;
|
||||
bool getDaemon() const;
|
||||
|
||||
// The Log section
|
||||
unsigned int getLogDisplayLevel() const;
|
||||
unsigned int getLogFileLevel() const;
|
||||
std::string getLogFilePath() const;
|
||||
std::string getLogFileRoot() const;
|
||||
bool getLogFileRotate() const;
|
||||
// The Log section
|
||||
unsigned int getLogDisplayLevel() const;
|
||||
unsigned int getLogMQTTLevel() const;
|
||||
|
||||
// The DAPNET section
|
||||
std::string getDAPNETAddress() const;
|
||||
unsigned short getDAPNETPort() const;
|
||||
std::string getDAPNETAuthKey() const;
|
||||
bool getDAPNETDebug() const;
|
||||
// The MQTT section
|
||||
std::string getMQTTAddress() const;
|
||||
unsigned short getMQTTPort() const;
|
||||
unsigned int getMQTTKeepalive() const;
|
||||
std::string getMQTTName() const;
|
||||
bool getMQTTAuthEnabled() const;
|
||||
std::string getMQTTUsername() const;
|
||||
std::string getMQTTPassword() const;
|
||||
|
||||
// The DAPNET section
|
||||
std::string getDAPNETAddress() const;
|
||||
unsigned short getDAPNETPort() const;
|
||||
std::string getDAPNETAuthKey() const;
|
||||
bool getDAPNETDebug() const;
|
||||
|
||||
private:
|
||||
std::string m_file;
|
||||
std::string m_file;
|
||||
|
||||
std::string m_callsign;
|
||||
std::vector<unsigned int> m_whiteList;
|
||||
std::vector<unsigned int> m_blackList;
|
||||
std::string m_callsign;
|
||||
std::vector<unsigned int> m_whiteList;
|
||||
std::vector<unsigned int> m_blackList;
|
||||
|
||||
std::string m_blacklistRegexfile;
|
||||
std::string m_whitelistRegexfile;
|
||||
std::string m_rptAddress;
|
||||
unsigned short m_rptPort;
|
||||
std::string m_myAddress;
|
||||
unsigned short m_myPort;
|
||||
bool m_daemon;
|
||||
std::string m_blacklistRegexfile;
|
||||
std::string m_whitelistRegexfile;
|
||||
std::string m_rptAddress;
|
||||
unsigned short m_rptPort;
|
||||
std::string m_myAddress;
|
||||
unsigned short m_myPort;
|
||||
bool m_daemon;
|
||||
|
||||
unsigned int m_logDisplayLevel;
|
||||
unsigned int m_logFileLevel;
|
||||
std::string m_logFilePath;
|
||||
std::string m_logFileRoot;
|
||||
bool m_logFileRotate;
|
||||
unsigned int m_logDisplayLevel;
|
||||
unsigned int m_logMQTTLevel;
|
||||
|
||||
std::string m_dapnetAddress;
|
||||
unsigned short m_dapnetPort;
|
||||
std::string m_dapnetAuthKey;
|
||||
bool m_dapnetDebug;
|
||||
std::string m_mqttAddress;
|
||||
unsigned short m_mqttPort;
|
||||
unsigned int m_mqttKeepalive;
|
||||
std::string m_mqttName;
|
||||
bool m_mqttAuthEnabled;
|
||||
std::string m_mqttUsername;
|
||||
std::string m_mqttPassword;
|
||||
|
||||
std::string m_dapnetAddress;
|
||||
unsigned short m_dapnetPort;
|
||||
std::string m_dapnetAuthKey;
|
||||
bool m_dapnetDebug;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2018,2020,2024,2025 by Jonathan Naylor G4KLX
|
||||
* Copyright (C) 2018,2020,2023,2024,2025 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,15 +16,18 @@
|
|||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "MQTTConnection.h"
|
||||
#include "DAPNETGateway.h"
|
||||
#include "StopWatch.h"
|
||||
#include "Version.h"
|
||||
#include "Thread.h"
|
||||
#include "Timer.h"
|
||||
|
||||
#include "REGEX.h"
|
||||
#include "Utils.h"
|
||||
#include "Log.h"
|
||||
#include "GitVersion.h"
|
||||
|
||||
#include "REGEX.h"
|
||||
#include <regex>
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
|
|
@ -43,6 +46,9 @@ const char* DEFAULT_INI_FILE = "DAPNETGateway.ini";
|
|||
const char* DEFAULT_INI_FILE = "/etc/DAPNETGateway.ini";
|
||||
#endif
|
||||
|
||||
// In Log.cpp
|
||||
extern CMQTTConnection* m_mqtt;
|
||||
|
||||
static bool m_killed = false;
|
||||
static int m_signal = 0;
|
||||
|
||||
|
|
@ -86,6 +92,7 @@ const unsigned char FUNCTIONAL_ALPHANUMERIC = 3U;
|
|||
|
||||
const unsigned int MAX_TIME_TO_HOLD_TIME_MESSAGES = 15000U; // 15s
|
||||
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
const char* iniFile = DEFAULT_INI_FILE;
|
||||
|
|
@ -236,16 +243,6 @@ int CDAPNETGateway::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, "DAPNETGateway: unable to open the log file\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
#if !defined(_WIN32) && !defined(_WIN64)
|
||||
if (m_daemon) {
|
||||
::close(STDIN_FILENO);
|
||||
|
|
@ -254,6 +251,16 @@ int CDAPNETGateway::run()
|
|||
}
|
||||
#endif
|
||||
|
||||
::LogInitialise(m_conf.getLogDisplayLevel(), m_conf.getLogMQTTLevel());
|
||||
|
||||
std::vector<std::pair<std::string, void (*)(const unsigned char*, unsigned int)>> subscriptions;
|
||||
m_mqtt = new CMQTTConnection(m_conf.getMQTTAddress(), m_conf.getMQTTPort(), m_conf.getMQTTName(), m_conf.getMQTTAuthEnabled(), m_conf.getMQTTUsername(), m_conf.getMQTTPassword(), subscriptions, m_conf.getMQTTKeepalive());
|
||||
ret = m_mqtt->open();
|
||||
if (!ret) {
|
||||
delete m_mqtt;
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool debug = m_conf.getDAPNETDebug();
|
||||
|
||||
std::string rptAddress = m_conf.getRptAddress();
|
||||
|
|
@ -282,24 +289,26 @@ int CDAPNETGateway::run()
|
|||
ret = m_dapnetNetwork->open();
|
||||
if (!ret) {
|
||||
m_pocsagNetwork->close();
|
||||
|
||||
delete m_pocsagNetwork;
|
||||
delete m_dapnetNetwork;
|
||||
|
||||
::LogError("Cannot open the DAPNET network port");
|
||||
writeJSONLink("failed", "socket");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
LogMessage("Starting DAPNETGateway-%s", VERSION);
|
||||
|
||||
ret = m_dapnetNetwork->login();
|
||||
if (!ret) {
|
||||
m_pocsagNetwork->close();
|
||||
m_dapnetNetwork->close();
|
||||
|
||||
delete m_pocsagNetwork;
|
||||
delete m_dapnetNetwork;
|
||||
|
||||
::LogError("Cannot login to the DAPNET network");
|
||||
writeJSONLink("failed", "socket");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -320,6 +329,10 @@ int CDAPNETGateway::run()
|
|||
if (m_regexWhitelist->load())
|
||||
regexWhitelist = m_regexWhitelist->get();
|
||||
|
||||
LogInfo("DAPNETGateway-%s is starting", VERSION);
|
||||
LogInfo("Built %s %s (GitID #%.7s)", __TIME__, __DATE__, gitversion);
|
||||
|
||||
writeJSONStatus("DAPNETGateway is starting");
|
||||
|
||||
while (!m_killed) {
|
||||
unsigned char buffer[200U];
|
||||
|
|
@ -347,8 +360,10 @@ int CDAPNETGateway::run()
|
|||
}
|
||||
|
||||
bool ok = m_dapnetNetwork->read();
|
||||
if (!ok)
|
||||
if (!ok) {
|
||||
writeJSONLink("unlinked", "lost");
|
||||
recover();
|
||||
}
|
||||
|
||||
CPOCSAGMessage* message = m_dapnetNetwork->readMessage();
|
||||
if (message != nullptr) {
|
||||
|
|
@ -430,6 +445,10 @@ int CDAPNETGateway::run()
|
|||
CThread::sleep(10U);
|
||||
}
|
||||
|
||||
LogInfo("DAPNETGateway is stopping");
|
||||
writeJSONStatus("DAPNETGateway is stopping");
|
||||
writeJSONLink("unlinked", "");
|
||||
|
||||
m_pocsagNetwork->close();
|
||||
delete m_pocsagNetwork;
|
||||
|
||||
|
|
@ -621,3 +640,14 @@ bool CDAPNETGateway::sendMessage(CPOCSAGMessage* message) const
|
|||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void CDAPNETGateway::writeJSONStatus(const std::string& status)
|
||||
{
|
||||
nlohmann::json json;
|
||||
|
||||
json["timestamp"] = CUtils::createTimestamp();
|
||||
json["message"] = status;
|
||||
|
||||
WriteJSON("status", json, false);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2018 by Jonathan Naylor G4KLX
|
||||
* Copyright (C) 2018,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
|
||||
|
|
@ -61,6 +61,7 @@ private:
|
|||
void loadSchedule();
|
||||
bool sendMessage(CPOCSAGMessage* message) const;
|
||||
|
||||
void writeJSONStatus(const std::string& status);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -13,10 +13,16 @@ Daemon=0
|
|||
[Log]
|
||||
# Logging levels, 0=No logging
|
||||
DisplayLevel=1
|
||||
FileLevel=1
|
||||
FilePath=.
|
||||
FileRoot=DAPNETGateway
|
||||
FileRotate=1
|
||||
MQTTLevel=1
|
||||
|
||||
[MQTT]
|
||||
Address=127.0.0.1
|
||||
Port=1883
|
||||
Keepalive=60
|
||||
Auth=0
|
||||
Username=mmdvm
|
||||
Password=mmdvm
|
||||
Name=dapnet-gateway
|
||||
|
||||
[DAPNET]
|
||||
Address=dapnet.afu.rwth-aachen.de
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
<ClInclude Include="DAPNETGateway.h" />
|
||||
<ClInclude Include="DAPNETNetwork.h" />
|
||||
<ClInclude Include="Log.h" />
|
||||
<ClInclude Include="MQTTConnection.h" />
|
||||
<ClInclude Include="POCSAGMessage.h" />
|
||||
<ClInclude Include="POCSAGNetwork.h" />
|
||||
<ClInclude Include="REGEX.h" />
|
||||
|
|
@ -39,6 +40,7 @@
|
|||
<ClCompile Include="DAPNETGateway.cpp" />
|
||||
<ClCompile Include="DAPNETNetwork.cpp" />
|
||||
<ClCompile Include="Log.cpp" />
|
||||
<ClCompile Include="MQTTConnection.cpp" />
|
||||
<ClCompile Include="POCSAGMessage.cpp" />
|
||||
<ClCompile Include="POCSAGNetwork.cpp" />
|
||||
<ClCompile Include="REGEX.cpp" />
|
||||
|
|
@ -60,26 +62,26 @@
|
|||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
|
|
@ -121,11 +123,13 @@
|
|||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>HAVE_LOG_H;_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>C:\Program Files\mosquitto\devel;C:\Program Files</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>ws2_32.lib;mosquitto.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>C:\Program Files\mosquitto\devel</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
<PreBuildEvent>
|
||||
<Command>prebuild.cmd</Command>
|
||||
|
|
@ -139,11 +143,13 @@
|
|||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>HAVE_LOG_H;_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>C:\Program Files\mosquitto\devel;C:\Program Files</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>ws2_32.lib;mosquitto.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>C:\Program Files\mosquitto\devel</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
<PreBuildEvent>
|
||||
<Command>prebuild.cmd</Command>
|
||||
|
|
@ -159,13 +165,15 @@
|
|||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>HAVE_LOG_H;_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>C:\Program Files\mosquitto\devel;C:\Program Files</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>ws2_32.lib;mosquitto.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>C:\Program Files\mosquitto\devel</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
<PreBuildEvent>
|
||||
<Command>prebuild.cmd</Command>
|
||||
|
|
@ -181,13 +189,15 @@
|
|||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>HAVE_LOG_H;_CRT_SECURE_NO_WARNINGS</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>C:\Program Files\mosquitto\devel;C:\Program Files</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>ws2_32.lib;mosquitto.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>C:\Program Files\mosquitto\devel</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
<PreBuildEvent>
|
||||
<Command>prebuild.cmd</Command>
|
||||
|
|
|
|||
|
|
@ -53,6 +53,9 @@
|
|||
<ClInclude Include="REGEX.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="MQTTConnection.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Conf.cpp">
|
||||
|
|
@ -94,5 +97,8 @@
|
|||
<ClCompile Include="REGEX.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MQTTConnection.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -100,6 +100,7 @@ bool CDAPNETNetwork::read()
|
|||
if (!m_loggedIn) {
|
||||
m_loggedIn = true;
|
||||
LogMessage("Logged into the DAPNET network");
|
||||
writeJSONLink("linking", "");
|
||||
}
|
||||
// Time synchronisation
|
||||
char* p = ::strchr((char*)buffer, '\n');
|
||||
|
|
@ -271,6 +272,7 @@ bool CDAPNETNetwork::parseFailedLogin(unsigned char* data)
|
|||
assert(p != nullptr);
|
||||
|
||||
LogMessage("Login failed: %s", p);
|
||||
writeJSONLink("failed", "auth");
|
||||
|
||||
CThread::sleep(BACKOFF[m_failCount]);
|
||||
if (m_failCount < 9)
|
||||
|
|
|
|||
148
Log.cpp
148
Log.cpp
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2015,2016,2020,2025 by Jonathan Naylor G4KLX
|
||||
* Copyright (C) 2015,2016,2020,2022,2023,2025 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,8 @@
|
|||
*/
|
||||
|
||||
#include "Log.h"
|
||||
#include "MQTTConnection.h"
|
||||
#include "Utils.h"
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#include <Windows.h>
|
||||
|
|
@ -32,117 +34,27 @@
|
|||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
static unsigned int m_fileLevel = 2U;
|
||||
static std::string m_filePath;
|
||||
static std::string m_fileRoot;
|
||||
static bool m_fileRotate = true;
|
||||
CMQTTConnection* m_mqtt = nullptr;
|
||||
|
||||
static FILE* m_fpLog = nullptr;
|
||||
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 != nullptr)
|
||||
return true;
|
||||
} else {
|
||||
if (m_fpLog != nullptr)
|
||||
::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")) != nullptr) {
|
||||
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 != nullptr)
|
||||
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")) != nullptr) {
|
||||
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 != nullptr)
|
||||
::fclose(m_fpLog);
|
||||
if (m_mqtt != nullptr) {
|
||||
m_mqtt->close();
|
||||
delete m_mqtt;
|
||||
m_mqtt = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Log(unsigned int level, const char* fmt, ...)
|
||||
|
|
@ -171,22 +83,38 @@ 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 != nullptr && 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, bool retain)
|
||||
{
|
||||
if (m_mqtt != nullptr) {
|
||||
nlohmann::json top;
|
||||
|
||||
top[topLevel] = json;
|
||||
|
||||
m_mqtt->publish("json", top.dump(), retain);
|
||||
}
|
||||
}
|
||||
|
||||
void writeJSONLink(const std::string& action, const std::string& reason)
|
||||
{
|
||||
nlohmann::json json;
|
||||
|
||||
json["timestamp"] = CUtils::createTimestamp();
|
||||
json["action"] = action;
|
||||
if (!reason.empty())
|
||||
json["reason"] = reason;
|
||||
|
||||
WriteJSON("link", json, true);
|
||||
}
|
||||
|
||||
|
|
|
|||
14
Log.h
14
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 <string>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#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,15 @@
|
|||
|
||||
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, bool retain);
|
||||
|
||||
// Publishes a "link" Kind directly, for connectivity events (DAPNET login
|
||||
// success/failure/loss) that CDAPNETNetwork detects but has no WriteJSON
|
||||
// access of its own (it isn't CDAPNETGateway) -- same free-function shape
|
||||
// as APRSGateway's writeJSONLink.
|
||||
extern void writeJSONLink(const std::string& action, const std::string& reason);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
232
MQTTConnection.cpp
Normal file
232
MQTTConnection.cpp
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
/*
|
||||
* Copyright (C) 2022,2023,2025 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 <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#include <process.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
CMQTTConnection::CMQTTConnection(const std::string& host, unsigned short port, const std::string& name, const bool authEnabled, const std::string& username, const std::string& password, const std::vector<std::pair<std::string, void (*)(const unsigned char*, unsigned int)>>& subs, unsigned int keepalive, MQTT_QOS qos) :
|
||||
m_host(host),
|
||||
m_port(port),
|
||||
m_name(name),
|
||||
m_authEnabled(authEnabled),
|
||||
m_username(username),
|
||||
m_password(password),
|
||||
m_subs(subs),
|
||||
m_keepalive(keepalive),
|
||||
m_qos(qos),
|
||||
m_mosq(nullptr),
|
||||
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()
|
||||
{
|
||||
char name[50U];
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
::sprintf(name, "DAPNETGateway.%u", (unsigned)::_getpid());
|
||||
#else
|
||||
::sprintf(name, "DAPNETGateway.%u", (unsigned)::getpid());
|
||||
#endif
|
||||
|
||||
::fprintf(stdout, "DAPNETGateway (%s) connecting to MQTT as %s\n", m_name.c_str(), name);
|
||||
|
||||
m_mosq = ::mosquitto_new(name, true, this);
|
||||
if (m_mosq == nullptr){
|
||||
::fprintf(stderr, "MQTT Error newing: Out of memory.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_authEnabled)
|
||||
::mosquitto_username_pw_set(m_mosq, m_username.c_str(), m_password.c_str());
|
||||
|
||||
::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 = nullptr;
|
||||
::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 = nullptr;
|
||||
::fprintf(stderr, "MQTT Error loop starting: %s\n", ::mosquitto_strerror(rc));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CMQTTConnection::publish(const char* topic, const char* text, bool retain)
|
||||
{
|
||||
assert(topic != nullptr);
|
||||
assert(text != nullptr);
|
||||
|
||||
return publish(topic, (unsigned char*)text, (unsigned int)::strlen(text), retain);
|
||||
}
|
||||
|
||||
bool CMQTTConnection::publish(const char* topic, const std::string& text, bool retain)
|
||||
{
|
||||
assert(topic != nullptr);
|
||||
|
||||
return publish(topic, (unsigned char*)text.c_str(), (unsigned int)text.size(), retain);
|
||||
}
|
||||
|
||||
bool CMQTTConnection::publish(const char* topic, const unsigned char* data, unsigned int len, bool retain)
|
||||
{
|
||||
assert(topic != nullptr);
|
||||
assert(data != nullptr);
|
||||
|
||||
if (!m_connected)
|
||||
return false;
|
||||
|
||||
if (::strchr(topic, '/') == nullptr) {
|
||||
char topicEx[100U];
|
||||
::sprintf(topicEx, "%s/%s", m_name.c_str(), topic);
|
||||
|
||||
int rc = ::mosquitto_publish(m_mosq, nullptr, topicEx, len, data, static_cast<int>(m_qos), retain);
|
||||
if (rc != MOSQ_ERR_SUCCESS) {
|
||||
::fprintf(stderr, "MQTT Error publishing: %s\n", ::mosquitto_strerror(rc));
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
int rc = ::mosquitto_publish(m_mosq, nullptr, topic, len, data, static_cast<int>(m_qos), retain);
|
||||
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 != nullptr) {
|
||||
::mosquitto_disconnect(m_mosq);
|
||||
::mosquitto_loop_stop(m_mosq, true);
|
||||
::mosquitto_destroy(m_mosq);
|
||||
m_mosq = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void CMQTTConnection::onConnect(mosquitto* mosq, void* obj, int rc)
|
||||
{
|
||||
assert(mosq != nullptr);
|
||||
assert(obj != nullptr);
|
||||
|
||||
::fprintf(stdout, "MQTT: on_connect: %s\n", ::mosquitto_connack_string(rc));
|
||||
if (rc != 0) {
|
||||
::mosquitto_disconnect(mosq);
|
||||
return;
|
||||
}
|
||||
|
||||
CMQTTConnection* p = static_cast<CMQTTConnection*>(obj);
|
||||
p->m_connected = true;
|
||||
|
||||
for (std::vector<std::pair<std::string, void (*)(const unsigned char*, unsigned int)>>::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, nullptr, topicEx, static_cast<int>(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, nullptr, topic.c_str(), static_cast<int>(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 != nullptr);
|
||||
assert(obj != nullptr);
|
||||
assert(grantedQOS != nullptr);
|
||||
|
||||
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 != nullptr);
|
||||
assert(obj != nullptr);
|
||||
assert(message != nullptr);
|
||||
|
||||
CMQTTConnection* p = static_cast<CMQTTConnection*>(obj);
|
||||
|
||||
for (std::vector<std::pair<std::string, void (*)(const unsigned char*, unsigned int)>>::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 != nullptr);
|
||||
assert(obj != nullptr);
|
||||
|
||||
::fprintf(stdout, "MQTT: on_disconnect: %s\n", ::mosquitto_reason_string(rc));
|
||||
|
||||
CMQTTConnection* p = static_cast<CMQTTConnection*>(obj);
|
||||
p->m_connected = false;
|
||||
}
|
||||
|
||||
66
MQTTConnection.h
Normal file
66
MQTTConnection.h
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright (C) 2022,2023,2025,2026 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 <mosquitto.h>
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
enum class MQTT_QOS : int {
|
||||
AT_MODE_ONCE = 0,
|
||||
AT_LEAST_ONCE = 1,
|
||||
EXACTLY_ONCE = 2
|
||||
};
|
||||
|
||||
class CMQTTConnection {
|
||||
public:
|
||||
CMQTTConnection(const std::string& host, unsigned short port, const std::string& name, const bool authEnabled, const std::string& username, const std::string& password, const std::vector<std::pair<std::string, void (*)(const unsigned char*, unsigned int)>>& subs, unsigned int keepalive, MQTT_QOS qos = MQTT_QOS::EXACTLY_ONCE);
|
||||
~CMQTTConnection();
|
||||
|
||||
bool open();
|
||||
|
||||
bool publish(const char* topic, const char* text, bool retain = false);
|
||||
bool publish(const char* topic, const std::string& text, bool retain = false);
|
||||
bool publish(const char* topic, const unsigned char* data, unsigned int len, bool retain = false);
|
||||
|
||||
void close();
|
||||
|
||||
private:
|
||||
std::string m_host;
|
||||
unsigned short m_port;
|
||||
std::string m_name;
|
||||
bool m_authEnabled;
|
||||
std::string m_username;
|
||||
std::string m_password;
|
||||
std::vector<std::pair<std::string, void (*)(const unsigned char*, unsigned int)>> 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
|
||||
|
||||
14
Makefile
14
Makefile
|
|
@ -1,18 +1,21 @@
|
|||
CC = cc
|
||||
CXX = c++
|
||||
CFLAGS = -g -O3 -Wall -DHAVE_LOG_H -std=c++0x -pthread
|
||||
LIBS = -lm -lpthread
|
||||
CFLAGS = -g -O3 -Wall -std=c++0x -MMD -MD -pthread
|
||||
LIBS = -lm -lpthread -lmosquitto
|
||||
LDFLAGS = -g
|
||||
|
||||
OBJECTS = Conf.o DAPNETGateway.o DAPNETNetwork.o Log.o POCSAGMessage.o POCSAGNetwork.o StopWatch.o TCPSocket.o Thread.o Timer.o UDPSocket.o Utils.o REGEX.o
|
||||
SRCS = $(wildcard *.cpp)
|
||||
OBJS = $(SRCS:.cpp=.o)
|
||||
DEPS = $(SRCS:.cpp=.d)
|
||||
|
||||
all: DAPNETGateway
|
||||
|
||||
DAPNETGateway: GitVersion.h $(OBJECTS)
|
||||
$(CXX) $(OBJECTS) $(CFLAGS) $(LIBS) -o DAPNETGateway
|
||||
DAPNETGateway: GitVersion.h $(OBJS)
|
||||
$(CXX) $(OBJS) $(CFLAGS) $(LIBS) -o DAPNETGateway
|
||||
|
||||
%.o: %.cpp
|
||||
$(CXX) $(CFLAGS) -c -o $@ $<
|
||||
-include $(DEPS)
|
||||
|
||||
DAPNETGateway.o: GitVersion.h FORCE
|
||||
|
||||
|
|
@ -20,7 +23,6 @@ DAPNETGateway.o: GitVersion.h FORCE
|
|||
|
||||
FORCE:
|
||||
|
||||
|
||||
install:
|
||||
install -m 755 DAPNETGateway /usr/local/bin/
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@ The DAPNET Gateway links the MMDVM Host to the DAPNET network to receive paging
|
|||
|
||||
The messages are buffered by the gateway when the host is busy with other modes, and then sends them to the host in order once the host becomes idle.
|
||||
|
||||
They build on 32-bit and 64-bit Linux as well as on Windows using Visual Studio 2017 on x86 and x64.
|
||||
They build on 32-bit and 64-bit Linux as well as on Windows using Visual Studio 2022 on x86 and x64.
|
||||
|
||||
This software is licenced under the GPL v2 and is primarily intended for amateur and educational use.
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ int CTCPSocket::read(unsigned char* buffer, unsigned int length, unsigned int se
|
|||
tv.tv_sec = secs;
|
||||
tv.tv_usec = msecs * 1000;
|
||||
|
||||
int ret = ::select(m_fd + 1, &readFds, nullptr, nullptr, &tv);
|
||||
int ret = ::select(int(m_fd) + 1, &readFds, nullptr, nullptr, &tv);
|
||||
if (ret < 0) {
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
LogError("Error returned from TCP client select, err=%d", ::GetLastError());
|
||||
|
|
|
|||
96
Utils.cpp
96
Utils.cpp
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2009,2014,2015,2016,2025 Jonathan Naylor, G4KLX
|
||||
* Copyright (C) 2009,2014,2015,2016,2023,2025 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,13 @@
|
|||
#include <cstdio>
|
||||
#include <cassert>
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#include <Windows.h>
|
||||
#else
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
void CUtils::dump(const std::string& title, const unsigned char* data, unsigned int length)
|
||||
{
|
||||
assert(data != nullptr);
|
||||
|
|
@ -70,77 +77,24 @@ void CUtils::dump(int level, const std::string& title, const unsigned char* data
|
|||
}
|
||||
}
|
||||
|
||||
void CUtils::dump(const std::string& title, const bool* bits, unsigned int length)
|
||||
std::string CUtils::createTimestamp()
|
||||
{
|
||||
assert(bits != nullptr);
|
||||
char buffer[100U];
|
||||
|
||||
dump(2U, title, bits, length);
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
SYSTEMTIME st;
|
||||
::GetSystemTime(&st);
|
||||
|
||||
::sprintf(buffer, "%04u-%02u-%02uT%02u:%02u:%02u.%03uZ", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
|
||||
#else
|
||||
struct timeval now;
|
||||
::gettimeofday(&now, nullptr);
|
||||
|
||||
struct tm* tm = ::gmtime(&now.tv_sec);
|
||||
|
||||
::sprintf(buffer, "%04d-%02d-%02dT%02d:%02d:%02d.%03lldZ", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, now.tv_usec / 1000LL);
|
||||
#endif
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void CUtils::dump(int level, const std::string& title, const bool* bits, unsigned int length)
|
||||
{
|
||||
assert(bits != nullptr);
|
||||
|
||||
unsigned char bytes[100U];
|
||||
unsigned int nBytes = 0U;
|
||||
for (unsigned int n = 0U; n < length; n += 8U, nBytes++)
|
||||
bitsToByteBE(bits + n, bytes[nBytes]);
|
||||
|
||||
dump(level, title, bytes, nBytes);
|
||||
}
|
||||
|
||||
void CUtils::byteToBitsBE(unsigned char byte, bool* bits)
|
||||
{
|
||||
assert(bits != nullptr);
|
||||
|
||||
bits[0U] = (byte & 0x80U) == 0x80U;
|
||||
bits[1U] = (byte & 0x40U) == 0x40U;
|
||||
bits[2U] = (byte & 0x20U) == 0x20U;
|
||||
bits[3U] = (byte & 0x10U) == 0x10U;
|
||||
bits[4U] = (byte & 0x08U) == 0x08U;
|
||||
bits[5U] = (byte & 0x04U) == 0x04U;
|
||||
bits[6U] = (byte & 0x02U) == 0x02U;
|
||||
bits[7U] = (byte & 0x01U) == 0x01U;
|
||||
}
|
||||
|
||||
void CUtils::byteToBitsLE(unsigned char byte, bool* bits)
|
||||
{
|
||||
assert(bits != nullptr);
|
||||
|
||||
bits[0U] = (byte & 0x01U) == 0x01U;
|
||||
bits[1U] = (byte & 0x02U) == 0x02U;
|
||||
bits[2U] = (byte & 0x04U) == 0x04U;
|
||||
bits[3U] = (byte & 0x08U) == 0x08U;
|
||||
bits[4U] = (byte & 0x10U) == 0x10U;
|
||||
bits[5U] = (byte & 0x20U) == 0x20U;
|
||||
bits[6U] = (byte & 0x40U) == 0x40U;
|
||||
bits[7U] = (byte & 0x80U) == 0x80U;
|
||||
}
|
||||
|
||||
void CUtils::bitsToByteBE(const bool* bits, unsigned char& byte)
|
||||
{
|
||||
assert(bits != nullptr);
|
||||
|
||||
byte = bits[0U] ? 0x80U : 0x00U;
|
||||
byte |= bits[1U] ? 0x40U : 0x00U;
|
||||
byte |= bits[2U] ? 0x20U : 0x00U;
|
||||
byte |= bits[3U] ? 0x10U : 0x00U;
|
||||
byte |= bits[4U] ? 0x08U : 0x00U;
|
||||
byte |= bits[5U] ? 0x04U : 0x00U;
|
||||
byte |= bits[6U] ? 0x02U : 0x00U;
|
||||
byte |= bits[7U] ? 0x01U : 0x00U;
|
||||
}
|
||||
|
||||
void CUtils::bitsToByteLE(const bool* bits, unsigned char& byte)
|
||||
{
|
||||
assert(bits != nullptr);
|
||||
|
||||
byte = bits[0U] ? 0x01U : 0x00U;
|
||||
byte |= bits[1U] ? 0x02U : 0x00U;
|
||||
byte |= bits[2U] ? 0x04U : 0x00U;
|
||||
byte |= bits[3U] ? 0x08U : 0x00U;
|
||||
byte |= bits[4U] ? 0x10U : 0x00U;
|
||||
byte |= bits[5U] ? 0x20U : 0x00U;
|
||||
byte |= bits[6U] ? 0x40U : 0x00U;
|
||||
byte |= bits[7U] ? 0x80U : 0x00U;
|
||||
}
|
||||
|
|
|
|||
11
Utils.h
11
Utils.h
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2009,2014,2015 by Jonathan Naylor, G4KLX
|
||||
* Copyright (C) 2009,2014,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
|
||||
|
|
@ -21,14 +21,7 @@ public:
|
|||
static void dump(const std::string& title, const unsigned char* data, unsigned int length);
|
||||
static void dump(int level, const std::string& title, const unsigned char* data, unsigned int length);
|
||||
|
||||
static void dump(const std::string& title, const bool* bits, unsigned int length);
|
||||
static void dump(int level, const std::string& title, const bool* bits, unsigned int length);
|
||||
|
||||
static void byteToBitsBE(unsigned char byte, bool* bits);
|
||||
static void byteToBitsLE(unsigned char byte, bool* bits);
|
||||
|
||||
static void bitsToByteBE(const bool* bits, unsigned char& byte);
|
||||
static void bitsToByteLE(const bool* bits, unsigned char& byte);
|
||||
static std::string createTimestamp();
|
||||
|
||||
private:
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2018,2020,2024,2025 by Jonathan Naylor G4KLX
|
||||
* Copyright (C) 2018,2020,2023,2024,2025,2026 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 = "20250607";
|
||||
const char* VERSION = "20260214";
|
||||
|
||||
#endif
|
||||
|
|
|
|||
23
schema.json
Normal file
23
schema.json
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"$defs": {
|
||||
"timestamp": {"type": "string"},
|
||||
"action": {"type": "string", "enum": ["linking", "unlinked", "failed"]},
|
||||
"reason": {"type": "string", "enum": ["auth", "socket", "lost"]}
|
||||
},
|
||||
|
||||
"status": {
|
||||
"type": "object",
|
||||
"timestamp": {"$ref": "#/$defs/timestamp"},
|
||||
"message": {"type": "string"},
|
||||
"required": ["timestamp", "message"]
|
||||
},
|
||||
|
||||
"link": {
|
||||
"type": "object",
|
||||
"timestamp": {"$ref": "#/$defs/timestamp"},
|
||||
"action": {"$ref": "#/$defs/action"},
|
||||
"reason": {"$ref": "#/$defs/reason"},
|
||||
"required": ["timestamp", "action"]
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue