Merge branch 'feature/AddTimeServer_#20' into develop Closes #20
commit
a1cca3fbea
@ -0,0 +1,206 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 by Jonathan Naylor G4KLX
|
||||||
|
* Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
#include <cassert>
|
||||||
|
#include <sstream>
|
||||||
|
#include <csignal>
|
||||||
|
#ifdef DEBUG_DSTARGW
|
||||||
|
#include <boost/stacktrace.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "DGWTimeServerApp.h"
|
||||||
|
#include "Version.h"
|
||||||
|
#include "Log.h"
|
||||||
|
#include "Daemon.h"
|
||||||
|
#include "LogConsoleTarget.h"
|
||||||
|
#include "LogFileTarget.h"
|
||||||
|
|
||||||
|
CDGWTimeServerApp * CDGWTimeServerApp::g_app = nullptr;
|
||||||
|
const std::string BANNER_1 = CStringUtils::string_format("%s v%s Copyright (C) %s\n", APPLICATION_NAME.c_str(), LONG_VERSION.c_str(), VENDOR_NAME.c_str());
|
||||||
|
const std::string BANNER_2 = "DGWTimeServer comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n";
|
||||||
|
const std::string BANNER_3 = "This is free software, and you are welcome to distribute it under certain conditions that are discussed in the LICENSE file.\n\n";
|
||||||
|
|
||||||
|
int main(int argc, char * argv[])
|
||||||
|
{
|
||||||
|
std::set_terminate(CDGWTimeServerApp::terminateHandler);
|
||||||
|
|
||||||
|
signal(SIGSEGV, CDGWTimeServerApp::sigHandlerFatal);
|
||||||
|
signal(SIGILL, CDGWTimeServerApp::sigHandlerFatal);
|
||||||
|
signal(SIGFPE, CDGWTimeServerApp::sigHandlerFatal);
|
||||||
|
signal(SIGABRT, CDGWTimeServerApp::sigHandlerFatal);
|
||||||
|
signal(SIGTERM, CDGWTimeServerApp::sigHandler);
|
||||||
|
signal(SIGINT, CDGWTimeServerApp::sigHandler);
|
||||||
|
|
||||||
|
if (2 != argc) {
|
||||||
|
printf("usage: %s path_to_config_file\n", argv[0]);
|
||||||
|
printf(" %s --version\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << std::endl << BANNER_1 << BANNER_2 << BANNER_3;
|
||||||
|
if(argv[1][0] == '-') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load config
|
||||||
|
std::string configfile(argv[1]);
|
||||||
|
CTimeServerConfig config(configfile);
|
||||||
|
if(!config.load())
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
// Do daemon stuff
|
||||||
|
TDaemon daemon;
|
||||||
|
config.getDameon(daemon);
|
||||||
|
if (daemon.daemon) {
|
||||||
|
CLog::logInfo("Configured as a daemon, detaching ...");
|
||||||
|
auto res = CDaemon::daemonise(daemon.pidFile, daemon.user);
|
||||||
|
|
||||||
|
switch (res)
|
||||||
|
{
|
||||||
|
case DR_PARENT:
|
||||||
|
return 0;
|
||||||
|
case DR_CHILD:
|
||||||
|
break;
|
||||||
|
case DR_PIDFILE_FAILED:
|
||||||
|
case DR_FAILURE:
|
||||||
|
default:
|
||||||
|
CLog::logFatal("Failed to run as daemon");
|
||||||
|
CLog::finalise();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup Log
|
||||||
|
TLog logConf;
|
||||||
|
config.getLog(logConf);
|
||||||
|
CLog::finalise();
|
||||||
|
if(logConf.displayLevel != LOG_NONE && !daemon.daemon) CLog::addTarget(new CLogConsoleTarget(logConf.displayLevel));
|
||||||
|
if(logConf.fileLevel != LOG_NONE) CLog::addTarget(new CLogFileTarget(logConf.fileLevel, logConf.logDir, logConf.fileRoot, logConf.fileRotate));
|
||||||
|
|
||||||
|
// Start the app
|
||||||
|
CDGWTimeServerApp app(&config);
|
||||||
|
if(app.init()) {
|
||||||
|
app.run();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
CDGWTimeServerApp::CDGWTimeServerApp(const CTimeServerConfig * config) :
|
||||||
|
m_config(config)
|
||||||
|
{
|
||||||
|
g_app = this;
|
||||||
|
assert(config != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
CDGWTimeServerApp::~CDGWTimeServerApp()
|
||||||
|
{
|
||||||
|
delete m_thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CDGWTimeServerApp::init()
|
||||||
|
{
|
||||||
|
return createThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CDGWTimeServerApp::run()
|
||||||
|
{
|
||||||
|
m_thread->Run();
|
||||||
|
m_thread->Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CDGWTimeServerApp::createThread()
|
||||||
|
{
|
||||||
|
m_thread = new CTimeServerThread();
|
||||||
|
|
||||||
|
TTimeServer timeserver;
|
||||||
|
m_config->getTimeServer(timeserver);
|
||||||
|
|
||||||
|
std::vector<std::string> rptrs = { "", "", "", "" };
|
||||||
|
TRepeater repeater;
|
||||||
|
for(unsigned int i = 0u; i < m_config->getRepeaterCount(); i++) {
|
||||||
|
m_config->getRepeater(i, repeater);
|
||||||
|
rptrs[i].assign(repeater.band);
|
||||||
|
}
|
||||||
|
|
||||||
|
TPaths paths;
|
||||||
|
m_config->getPaths(paths);
|
||||||
|
|
||||||
|
m_thread = new CTimeServerThread();
|
||||||
|
bool ret = m_thread->setGateway(timeserver.callsign, rptrs[0], rptrs[1], rptrs[2], rptrs[3], timeserver.address, paths.data);
|
||||||
|
if(ret) {
|
||||||
|
m_thread->setAnnouncements(timeserver.language, timeserver.format, timeserver.interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CDGWTimeServerApp::sigHandler(int sig)
|
||||||
|
{
|
||||||
|
CLog::logInfo("Caught signal : %s, shutting down time server", strsignal(sig));
|
||||||
|
|
||||||
|
if(g_app != nullptr && g_app->m_thread != nullptr) {
|
||||||
|
g_app->m_thread->kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CDGWTimeServerApp::sigHandlerFatal(int sig)
|
||||||
|
{
|
||||||
|
CLog::logFatal("Caught signal : %s", strsignal(sig));
|
||||||
|
fprintf(stderr, "Caught signal : %s\n", strsignal(sig));
|
||||||
|
#ifdef DEBUG_DSTARGW
|
||||||
|
std::stringstream stackTrace;
|
||||||
|
stackTrace << boost::stacktrace::stacktrace();
|
||||||
|
CLog::logFatal("Stack Trace : \n%s", stackTrace.str().c_str());
|
||||||
|
fprintf(stderr, "Stack Trace : \n%s\n", stackTrace.str().c_str());
|
||||||
|
#endif
|
||||||
|
exit(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CDGWTimeServerApp::terminateHandler()
|
||||||
|
{
|
||||||
|
#ifdef DEBUG_DSTARGW
|
||||||
|
std::stringstream stackTrace;
|
||||||
|
stackTrace << boost::stacktrace::stacktrace();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::exception_ptr eptr;
|
||||||
|
eptr = std::current_exception();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (eptr != nullptr) {
|
||||||
|
std::rethrow_exception(eptr);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
CLog::logFatal("Unhandled unknown exception occured");
|
||||||
|
fprintf(stderr, "Unknown ex\n");
|
||||||
|
}
|
||||||
|
} catch(const std::exception& e) {
|
||||||
|
CLog::logFatal("Unhandled exception occured %s", e.what());
|
||||||
|
fprintf(stderr, "Unhandled ex %s\n", e.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG_DSTARGW
|
||||||
|
CLog::logFatal("Stack Trace : \n%s", stackTrace.str().c_str());
|
||||||
|
#endif
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 by Jonathan Naylor G4KLX
|
||||||
|
* Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "TimeServerDefs.h"
|
||||||
|
#include "TimeServerConfig.h"
|
||||||
|
#include "TimeServerThread.h"
|
||||||
|
|
||||||
|
class CDGWTimeServerApp
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CDGWTimeServerApp(const CTimeServerConfig * config);
|
||||||
|
~CDGWTimeServerApp();
|
||||||
|
|
||||||
|
bool init();
|
||||||
|
void run();
|
||||||
|
|
||||||
|
static void sigHandler(int sig);
|
||||||
|
static void sigHandlerFatal(int sig);
|
||||||
|
static void terminateHandler();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool createThread();
|
||||||
|
|
||||||
|
static CDGWTimeServerApp * g_app;
|
||||||
|
|
||||||
|
const CTimeServerConfig * m_config;
|
||||||
|
CTimeServerThread * m_thread;
|
||||||
|
};
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
SRCS = $(wildcard *.cpp)
|
||||||
|
OBJS = $(SRCS:.cpp=.o)
|
||||||
|
DEPS = $(SRCS:.cpp=.d)
|
||||||
|
|
||||||
|
dgwtimeserver: ../VersionInfo/GitVersion.h $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a
|
||||||
|
$(CC) $(CPPFLAGS) -o dgwtimeserver $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a $(LDFLAGS)
|
||||||
|
|
||||||
|
%.o : %.cpp
|
||||||
|
$(CC) -I../BaseCommon -I../DStarBase -I../VersionInfo $(CPPFLAGS) -MMD -MD -c $< -o $@
|
||||||
|
|
||||||
|
.PHONY clean:
|
||||||
|
clean:
|
||||||
|
$(RM) *.o *.d dgwtimeserver
|
||||||
|
|
||||||
|
.PHONY install:
|
||||||
|
install: dgwtimeserver
|
||||||
|
# copy executable
|
||||||
|
@cp -f dgwtimeserver $(BIN_DIR)
|
||||||
|
|
||||||
|
# copy and adjust config
|
||||||
|
@cp -fn example.cfg $(CFG_DIR)/dgwtimeserver.cfg
|
||||||
|
@sed -i "s|path=/var/log/dstargateway/|path=$(LOG_DIR)|g" $(CFG_DIR)/dgwtimeserver.cfg
|
||||||
|
@sed -i "s|data=/usr/local/share/dstargateway.d/|data=$(DATA_DIR)|g" $(CFG_DIR)/dgwtimeserver.cfg
|
||||||
|
|
||||||
|
# SystemD service install
|
||||||
|
@cp -f ../debian/dgwtimeserver.service /lib/systemd/system/
|
||||||
|
@sed -i "s|%CFG_DIR%|$(CFG_DIR)|g" /lib/systemd/system/dgwtimeserver.service
|
||||||
|
systemctl enable dgwtimeserver.service
|
||||||
|
@systemctl daemon-reload
|
||||||
|
@echo "\n"
|
||||||
|
@echo "DGWTimeserver Install complete, edit $(CFG_DIR)dgwtimeserver.cfg and start the daemon with 'systemctl start dgwtimeserver.service'"
|
||||||
|
@echo "\n"
|
||||||
|
|
||||||
|
../BaseCommon/BaseCommon.a:
|
||||||
|
../DStarBase/DStarBase.a:
|
||||||
|
../VersionInfo/GitVersion.h:
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
Time Server for DStarGateway. Installs as a systemd service.
|
||||||
|
|
||||||
|
If you did not change build settings, the configuration can be found in
|
||||||
|
```
|
||||||
|
/usr/local/etc/dgwtimeserver.cfg
|
||||||
|
```
|
||||||
|
After configuring enable and start the dgwtimeserver systemd service using :
|
||||||
|
```
|
||||||
|
sudo systemctl enable dgwtimeserver
|
||||||
|
sudo systemctl start dgwtimeserver
|
||||||
|
```
|
||||||
@ -0,0 +1,216 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <boost/algorithm/string.hpp>
|
||||||
|
|
||||||
|
#include "TimeServerConfig.h"
|
||||||
|
#include "StringUtils.h"
|
||||||
|
|
||||||
|
CTimeServerConfig::CTimeServerConfig(const std::string &pathname) :
|
||||||
|
m_fileName(pathname),
|
||||||
|
m_repeaters()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
CTimeServerConfig::~CTimeServerConfig()
|
||||||
|
{
|
||||||
|
for(auto repeater : m_repeaters) {
|
||||||
|
delete repeater;
|
||||||
|
}
|
||||||
|
m_repeaters.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CTimeServerConfig::load()
|
||||||
|
{
|
||||||
|
bool ret = false;
|
||||||
|
CLog::logInfo("Loading Configuration from %s", m_fileName.c_str());
|
||||||
|
CConfig cfg(m_fileName);
|
||||||
|
|
||||||
|
ret = open(cfg);
|
||||||
|
if(ret) {
|
||||||
|
ret = loadTimeServer(cfg) && ret;
|
||||||
|
ret = loadRepeaters(cfg) && ret;
|
||||||
|
ret = loadDaemon(cfg) && ret;
|
||||||
|
ret = loadPaths(cfg) && ret;
|
||||||
|
ret = loadLog(cfg) && ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CTimeServerConfig::open(CConfig & cfg)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return cfg.load();
|
||||||
|
}
|
||||||
|
catch(...) {
|
||||||
|
CLog::logError("Can't read %s\n", m_fileName.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CTimeServerConfig::loadRepeaters(const CConfig & cfg)
|
||||||
|
{
|
||||||
|
m_repeaters.clear();
|
||||||
|
|
||||||
|
for(unsigned int i = 0; i < 4; i++) {
|
||||||
|
std::string section = CStringUtils::string_format("repeater_%d", i+ 1);
|
||||||
|
bool repeaterEnabled;
|
||||||
|
|
||||||
|
bool ret = cfg.getValue(section, "enabled", repeaterEnabled, false);
|
||||||
|
if(!ret || !repeaterEnabled)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
TRepeater * repeater = new TRepeater;
|
||||||
|
ret = cfg.getValue(section, "band", repeater->band, 1, 2, "") && ret;
|
||||||
|
if(!ret) {
|
||||||
|
delete repeater;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
boost::to_upper(repeater->band);
|
||||||
|
|
||||||
|
bool alreadyConfigured = std::any_of(m_repeaters.begin(), m_repeaters.end(), [repeater](TRepeater * r) { return r->band == repeater->band;});
|
||||||
|
if(alreadyConfigured) {
|
||||||
|
CLog::logWarning("%s-%s repeater already configured, ignoring", m_timeServer.callsign.c_str(), repeater->band.c_str());
|
||||||
|
delete repeater;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_repeaters.push_back(repeater);
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_repeaters.size() > 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CTimeServerConfig::loadTimeServer(const CConfig & cfg)
|
||||||
|
{
|
||||||
|
bool ret = cfg.getValue("timeserver", "callsign", m_timeServer.callsign, 3, 8, "");
|
||||||
|
boost::to_upper(m_timeServer.callsign);
|
||||||
|
ret = cfg.getValue("timeserver", "address", m_timeServer.address, 0, 1024, "127.0.0.1") && ret;
|
||||||
|
|
||||||
|
std::string format;
|
||||||
|
ret = cfg.getValue("timeserver", "format", format, "voice", {"voice", "text" }) && ret;
|
||||||
|
if(format == "voice") m_timeServer.format = FORMAT_VOICE_TIME;
|
||||||
|
else if(format == "text") m_timeServer.format = FORMAT_TEXT_TIME;
|
||||||
|
|
||||||
|
std::string lang;
|
||||||
|
ret = cfg.getValue("timeserver", "language", lang, "english_uk_1", {"english_uk_1", "english_uk_2", "english_us_1", "english_us_2", "deutsch_1", "deutsch_2", "francais", "nederlands", "svenska", "espanol", "norsk", "portugues"}) && ret;;
|
||||||
|
if (lang == "english_uk_1") m_timeServer.language = LANG_ENGLISH_UK_1;
|
||||||
|
else if(lang == "english_uk_2") m_timeServer.language = LANG_ENGLISH_UK_2;
|
||||||
|
else if(lang == "english_us_1") m_timeServer.language = LANG_ENGLISH_US_1;
|
||||||
|
else if(lang == "english_us_2") m_timeServer.language = LANG_ENGLISH_US_2;
|
||||||
|
else if(lang == "deutsch_1" ) m_timeServer.language = LANG_DEUTSCH_1;
|
||||||
|
else if(lang == "detusch_2" ) m_timeServer.language = LANG_DEUTSCH_2;
|
||||||
|
else if(lang == "francais" ) m_timeServer.language = LANG_FRANCAIS;
|
||||||
|
else if(lang == "nederlands" ) m_timeServer.language = LANG_NEDERLANDS;
|
||||||
|
else if(lang == "svenska" ) m_timeServer.language = LANG_SVENSKA;
|
||||||
|
else if(lang == "espanol" ) m_timeServer.language = LANG_ESPANOL;
|
||||||
|
else if(lang == "norsk" ) m_timeServer.language = LANG_NORSK;
|
||||||
|
else if(lang == "portugues" ) m_timeServer.language = LANG_PORTUGUES;
|
||||||
|
|
||||||
|
std::string interval;
|
||||||
|
ret = cfg.getValue("timeserver", "interval", interval, "30", {"15", "30", "60"}) && ret;
|
||||||
|
if(interval == "15") m_timeServer.interval = INTERVAL_15MINS;
|
||||||
|
else if(interval == "30") m_timeServer.interval = INTERVAL_30MINS;
|
||||||
|
else if(interval == "60") m_timeServer.interval = INTERVAL_60MINS;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CTimeServerConfig::loadDaemon(const CConfig & cfg)
|
||||||
|
{
|
||||||
|
bool ret = cfg.getValue("daemon", "daemon", m_daemon.daemon, false);
|
||||||
|
ret = cfg.getValue("daemon", "pidfile", m_daemon.pidFile, 0, 1024, "") && ret;
|
||||||
|
ret = cfg.getValue("daemon", "user", m_daemon.user, 0, 1024, "") && ret;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CTimeServerConfig::loadPaths(const CConfig & cfg)
|
||||||
|
{
|
||||||
|
bool ret = cfg.getValue("paths", "data", m_paths.data, 1, 1024, "");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CTimeServerConfig::loadLog(const CConfig & cfg)
|
||||||
|
{
|
||||||
|
bool ret = cfg.getValue("log", "path", m_log.logDir, 0, 2048, "/var/log/dstargateway/");
|
||||||
|
if(ret && m_log.logDir[m_log.logDir.length() - 1] != '/') {
|
||||||
|
m_log.logDir.push_back('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = cfg.getValue("log", "fileRoot", m_log.fileRoot, 0, 64, "dgwtimeserver") && ret;
|
||||||
|
ret = cfg.getValue("log", "fileRotate", m_log.fileRotate, true) && ret;
|
||||||
|
|
||||||
|
std::string levelStr;
|
||||||
|
ret = cfg.getValue("log", "fileLevel", levelStr, "info", {"trace", "debug", "info", "warning", "error", "fatal", "none"}) && ret;
|
||||||
|
if(ret) {
|
||||||
|
if(levelStr == "trace") m_log.fileLevel = LOG_TRACE;
|
||||||
|
else if(levelStr == "debug") m_log.fileLevel = LOG_DEBUG;
|
||||||
|
else if(levelStr == "info") m_log.fileLevel = LOG_INFO;
|
||||||
|
else if(levelStr == "warning") m_log.fileLevel = LOG_WARNING;
|
||||||
|
else if(levelStr == "error") m_log.fileLevel = LOG_ERROR;
|
||||||
|
else if(levelStr == "fatal") m_log.fileLevel = LOG_FATAL;
|
||||||
|
else if(levelStr == "none") m_log.fileLevel = LOG_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = cfg.getValue("log", "displayLevel", levelStr, "info", {"trace", "debug", "info", "warning", "error", "fatal", "none"}) && ret;
|
||||||
|
if(ret) {
|
||||||
|
if(levelStr == "trace") m_log.displayLevel = LOG_TRACE;
|
||||||
|
else if(levelStr == "debug") m_log.displayLevel = LOG_DEBUG;
|
||||||
|
else if(levelStr == "info") m_log.displayLevel = LOG_INFO;
|
||||||
|
else if(levelStr == "warning") m_log.displayLevel = LOG_WARNING;
|
||||||
|
else if(levelStr == "error") m_log.displayLevel = LOG_ERROR;
|
||||||
|
else if(levelStr == "fatal") m_log.displayLevel = LOG_FATAL;
|
||||||
|
else if(levelStr == "none") m_log.displayLevel = LOG_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTimeServerConfig::getTimeServer(TTimeServer& timeserver) const
|
||||||
|
{
|
||||||
|
timeserver = m_timeServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTimeServerConfig::getDameon(TDaemon& daemon) const
|
||||||
|
{
|
||||||
|
daemon = m_daemon;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int CTimeServerConfig::getRepeaterCount() const
|
||||||
|
{
|
||||||
|
return m_repeaters.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTimeServerConfig::getRepeater(unsigned int idx, TRepeater& repeater) const
|
||||||
|
{
|
||||||
|
repeater = *(m_repeaters[idx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTimeServerConfig::getPaths(TPaths& paths) const
|
||||||
|
{
|
||||||
|
paths = m_paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CTimeServerConfig::getLog(TLog& log) const
|
||||||
|
{
|
||||||
|
log = m_log;
|
||||||
|
}
|
||||||
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "Config.h"
|
||||||
|
#include "TimeServerDefs.h"
|
||||||
|
#include "Log.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
std::string callsign;
|
||||||
|
std::string address;
|
||||||
|
FORMAT format;
|
||||||
|
LANGUAGE language;
|
||||||
|
INTERVAL interval;
|
||||||
|
} TTimeServer;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
std::string band;
|
||||||
|
} TRepeater;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool daemon;
|
||||||
|
std::string pidFile;
|
||||||
|
std::string user;
|
||||||
|
} TDaemon;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
std::string data;
|
||||||
|
} TPaths;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
std::string logDir;
|
||||||
|
LOG_SEVERITY displayLevel;
|
||||||
|
LOG_SEVERITY fileLevel;
|
||||||
|
std::string fileRoot;
|
||||||
|
bool fileRotate;
|
||||||
|
} TLog;
|
||||||
|
|
||||||
|
class CTimeServerConfig
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CTimeServerConfig(const std::string &pathname);
|
||||||
|
~CTimeServerConfig();
|
||||||
|
|
||||||
|
bool load();
|
||||||
|
void getTimeServer(TTimeServer& timeserver) const;
|
||||||
|
void getDameon(TDaemon& daemon) const;
|
||||||
|
unsigned int getRepeaterCount() const;
|
||||||
|
void getRepeater(unsigned int idx, TRepeater& repeater) const;
|
||||||
|
void getPaths(TPaths& paths) const;
|
||||||
|
void getLog(TLog& log) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool open(CConfig & cfg);
|
||||||
|
bool loadRepeaters(const CConfig & cfg);
|
||||||
|
bool loadTimeServer(const CConfig & cfg);
|
||||||
|
bool loadDaemon(const CConfig & cfg);
|
||||||
|
bool loadPaths(const CConfig & cfg);
|
||||||
|
bool loadLog(const CConfig & cfg);
|
||||||
|
|
||||||
|
std::string m_fileName;
|
||||||
|
std::vector<TRepeater *> m_repeaters;
|
||||||
|
TTimeServer m_timeServer;
|
||||||
|
TDaemon m_daemon;
|
||||||
|
TPaths m_paths;
|
||||||
|
TLog m_log;
|
||||||
|
};
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2012,2013,2014 by Jonathan Naylor G4KLX
|
||||||
|
* Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
const std::string APPLICATION_NAME("DStarGateway Time Server");
|
||||||
|
|
||||||
|
enum LANGUAGE {
|
||||||
|
LANG_ENGLISH_UK_1,
|
||||||
|
LANG_ENGLISH_UK_2,
|
||||||
|
LANG_ENGLISH_US_1,
|
||||||
|
LANG_ENGLISH_US_2,
|
||||||
|
LANG_DEUTSCH_1,
|
||||||
|
LANG_DEUTSCH_2,
|
||||||
|
LANG_FRANCAIS,
|
||||||
|
LANG_NEDERLANDS,
|
||||||
|
LANG_SVENSKA,
|
||||||
|
LANG_ESPANOL,
|
||||||
|
LANG_NORSK,
|
||||||
|
LANG_PORTUGUES
|
||||||
|
};
|
||||||
|
|
||||||
|
enum INTERVAL {
|
||||||
|
INTERVAL_15MINS,
|
||||||
|
INTERVAL_30MINS,
|
||||||
|
INTERVAL_60MINS
|
||||||
|
};
|
||||||
|
|
||||||
|
enum FORMAT {
|
||||||
|
FORMAT_VOICE_TIME,
|
||||||
|
FORMAT_TEXT_TIME
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2012,2013 by Jonathan Naylor G4KLX
|
||||||
|
* Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "SlowDataEncoder.h"
|
||||||
|
#include "UDPReaderWriter.h"
|
||||||
|
#include "TimeServerDefs.h"
|
||||||
|
#include "HeaderData.h"
|
||||||
|
#include "AMBEData.h"
|
||||||
|
#include "Thread.h"
|
||||||
|
#include "AMBEFileReader.h"
|
||||||
|
|
||||||
|
class CTimeServerThread : public CThread
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CTimeServerThread();
|
||||||
|
~CTimeServerThread();
|
||||||
|
|
||||||
|
bool setGateway(const std::string& callsign, const std::string& rpt1, const std::string& rpt2, const std::string& rpt3, const std::string& rpt4, const std::string& address, const std::string& dataPath);
|
||||||
|
void setAnnouncements(LANGUAGE language, FORMAT format, INTERVAL interval);
|
||||||
|
|
||||||
|
void * Entry();
|
||||||
|
void kill();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_callsign;
|
||||||
|
std::vector<std::string> m_repeaters;
|
||||||
|
std::string m_callsignG;
|
||||||
|
in_addr m_address;
|
||||||
|
std::string m_addressStr;
|
||||||
|
LANGUAGE m_language;
|
||||||
|
FORMAT m_format;
|
||||||
|
INTERVAL m_interval;
|
||||||
|
CSlowDataEncoder m_encoder;
|
||||||
|
std::vector<CAMBEData*> m_data;
|
||||||
|
bool m_killed;
|
||||||
|
std::string m_dataPath;
|
||||||
|
CAMBEFileReader * m_ambeFileReader;
|
||||||
|
|
||||||
|
void sendTime(unsigned int hour, unsigned int min);
|
||||||
|
|
||||||
|
std::vector<std::string> sendTimeEnGB1(unsigned int hour, unsigned int min);
|
||||||
|
std::vector<std::string> sendTimeEnGB2(unsigned int hour, unsigned int min);
|
||||||
|
std::vector<std::string> sendTimeEnUS1(unsigned int hour, unsigned int min);
|
||||||
|
std::vector<std::string> sendTimeEnUS2(unsigned int hour, unsigned int min);
|
||||||
|
std::vector<std::string> sendTimeDeDE1(unsigned int hour, unsigned int min);
|
||||||
|
std::vector<std::string> sendTimeDeDE2(unsigned int hour, unsigned int min);
|
||||||
|
std::vector<std::string> sendTimeFrFR(unsigned int hour, unsigned int min);
|
||||||
|
std::vector<std::string> sendTimeNlNL(unsigned int hour, unsigned int min);
|
||||||
|
std::vector<std::string> sendTimeSeSE(unsigned int hour, unsigned int min);
|
||||||
|
std::vector<std::string> sendTimeEsES(unsigned int hour, unsigned int min);
|
||||||
|
std::vector<std::string> sendTimeNoNO(unsigned int hour, unsigned int min);
|
||||||
|
std::vector<std::string> sendTimePtPT(unsigned int hour, unsigned int min);
|
||||||
|
|
||||||
|
bool send(const std::vector<std::string>& words, unsigned int hour, unsigned int min);
|
||||||
|
bool sendHeader(CUDPReaderWriter& socket, const CHeaderData& header);
|
||||||
|
bool sendData(CUDPReaderWriter& socket, const CAMBEData& data);
|
||||||
|
|
||||||
|
bool loadAMBE();
|
||||||
|
|
||||||
|
bool lookup(const std::string& id);
|
||||||
|
|
||||||
|
void buildAudio(const std::vector<std::string>& words, CSlowDataEncoder& slowDataEncoder);
|
||||||
|
};
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
[TimeServer]
|
||||||
|
callsign= # call of the gateway to send time beacons without G letter
|
||||||
|
address= # address of the gateway, defaults to 127.0.0.1
|
||||||
|
format= # possible values are voice, text, voiceandtext, defaults to voice and text
|
||||||
|
language= # valid values: english_uk_1, english_uk_2, english_us_1, english_us_2, deutsch_1, deutsch_2, francais, nederlands, svenska, espanol, norsk, portugues. Defaults to english_uk_1
|
||||||
|
interval= # valid values are 15, 30 and 60, defaults to 30
|
||||||
|
|
||||||
|
[Paths]
|
||||||
|
data=/usr/local/share/dstargateway.d/ #Path where the data (hostfiles, audio files etc) can be found
|
||||||
|
|
||||||
|
# Up to 4 repeaters can be enabled to transmit time beacons
|
||||||
|
[Repeater_1]
|
||||||
|
enabled=true # enable time beacons on this repeater
|
||||||
|
band=B # Module letter of the repeater
|
||||||
|
|
||||||
|
[Repeater_2]
|
||||||
|
enabled=false # enable time beacons on this repeater
|
||||||
|
band= # Module letter of the repeater
|
||||||
|
|
||||||
|
[Repeater_3]
|
||||||
|
enabled=false # enable time beacons on this repeater
|
||||||
|
band= # Module letter of the repeater
|
||||||
|
|
||||||
|
[Repeater_4]
|
||||||
|
enabled=false # enable time beacons on this repeater
|
||||||
|
band= # Module letter of the repeater
|
||||||
|
|
||||||
|
[Log]
|
||||||
|
path=/var/log/dstargateway/
|
||||||
|
fileRoot= # defaults to dgwtimeserver
|
||||||
|
fileRotate= # rotate log files daily, defaults to true
|
||||||
|
fileLevel= # defaults to info, valid values are trace, debug, info, warning, error, fatal, none
|
||||||
|
displayLevel= # defaults to info, valid values are trace, debug, info, warning, error, fatal, none
|
||||||
|
|
||||||
|
# Provided install routines install the program as a systemd unit. SystemD does not recommand "old-school" forking daemons nor does systemd
|
||||||
|
# require a pid file. Moreover systemd handles the user under which the program is started. This is provided as convenience for people who might
|
||||||
|
# run the program using sysv or any other old school init system.
|
||||||
|
[Daemon]
|
||||||
|
daemon=false
|
||||||
|
pidfile=/var/run/dstargateway/dstargateway.pid # pid file is in our case useless when running as a daemon using systemd as systemd takes care of the service not being started twice
|
||||||
|
user=dstar # user account the daemon will run under, ideally a user with low privileges. Switching to this user will only happen when the program is started as root
|
||||||
@ -0,0 +1,179 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 by Geoffrey Merck F4FXL / KC3FRA
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "AMBEFileReader.h"
|
||||||
|
#include "DStarDefines.h"
|
||||||
|
#include "Log.h"
|
||||||
|
|
||||||
|
const unsigned int SILENCE_LENGTH = 10U;
|
||||||
|
|
||||||
|
CAMBEFileReader::CAMBEFileReader(const std::string& indexFile, const std::string& ambeFile) :
|
||||||
|
m_indexFile(indexFile),
|
||||||
|
m_ambeFile(ambeFile),
|
||||||
|
m_ambe(nullptr),
|
||||||
|
m_ambeLength(0U),
|
||||||
|
m_index()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
CAMBEFileReader::~CAMBEFileReader()
|
||||||
|
{
|
||||||
|
if(m_ambe != nullptr) {
|
||||||
|
delete[] m_ambe;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CAMBEFileReader::read()
|
||||||
|
{
|
||||||
|
bool ret = readAmbe() && readIndex();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CAMBEFileReader::readAmbe()
|
||||||
|
{
|
||||||
|
struct stat sbuf;
|
||||||
|
if (stat(m_ambeFile.c_str(), &sbuf)) {
|
||||||
|
CLog::logWarning("File %s not readable\n", m_ambeFile.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int fsize = sbuf.st_size;
|
||||||
|
|
||||||
|
FILE *file = fopen(m_ambeFile.c_str(), "rb");
|
||||||
|
if (NULL == file) {
|
||||||
|
CLog::logError("Cannot open %s for reading\n", m_ambeFile.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CLog::logInfo("Reading %s\n", m_ambeFile.c_str());
|
||||||
|
|
||||||
|
unsigned char buffer[VOICE_FRAME_LENGTH_BYTES];
|
||||||
|
|
||||||
|
size_t n = fread(buffer, sizeof(unsigned char), 4, file);
|
||||||
|
if (n != 4) {
|
||||||
|
CLog::logError("Unable to read the header from %s\n", m_ambeFile.c_str());
|
||||||
|
fclose(file);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memcmp(buffer, "AMBE", 4)) {
|
||||||
|
CLog::logError("Invalid header from %s\n", m_ambeFile.c_str());
|
||||||
|
fclose(file);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Length of the file minus the header
|
||||||
|
unsigned int length = fsize - 4U;
|
||||||
|
|
||||||
|
// Hold the file data plus silence at the end
|
||||||
|
m_ambe = new unsigned char[length + SILENCE_LENGTH * VOICE_FRAME_LENGTH_BYTES];
|
||||||
|
m_ambeLength = length / VOICE_FRAME_LENGTH_BYTES;
|
||||||
|
|
||||||
|
// Add silence to the beginning of the buffer
|
||||||
|
unsigned char* p = m_ambe;
|
||||||
|
for (unsigned int i = 0U; i < SILENCE_LENGTH; i++, p += VOICE_FRAME_LENGTH_BYTES)
|
||||||
|
memcpy(p, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES);
|
||||||
|
|
||||||
|
n = fread(p, 1, length, file);
|
||||||
|
if (n != length) {
|
||||||
|
CLog::logError("Unable to read the AMBE data from %s\n", m_ambeFile.c_str());
|
||||||
|
fclose(file);
|
||||||
|
delete[] m_ambe;
|
||||||
|
m_ambeLength = 0U;
|
||||||
|
m_ambe = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(file);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CAMBEFileReader::readIndex()
|
||||||
|
{
|
||||||
|
struct stat sbuf;
|
||||||
|
|
||||||
|
if (stat(m_indexFile.c_str(), &sbuf)) {
|
||||||
|
CLog::logError("File %s not readable\n", m_indexFile.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *file = fopen(m_indexFile.c_str(), "r");
|
||||||
|
if (file == nullptr) {
|
||||||
|
CLog::logError("Cannot open %s for reading\n", m_indexFile.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a silence entry at the beginning
|
||||||
|
m_index[" "] = new CIndexRecord(" ", 0, SILENCE_LENGTH);
|
||||||
|
|
||||||
|
CLog::logInfo("Reading %s\n", m_indexFile.c_str());
|
||||||
|
|
||||||
|
char line[128];
|
||||||
|
while (fgets(line, 128, file)) {
|
||||||
|
if (strlen(line) && '#'!=line[0]) {
|
||||||
|
const std::string space(" \t\r\n");
|
||||||
|
std::string name(strtok(line, space.c_str()));
|
||||||
|
std::string strt(strtok(NULL, space.c_str()));
|
||||||
|
std::string leng(strtok(NULL, space.c_str()));
|
||||||
|
|
||||||
|
if (name.size() && strt.size() && leng.size()) {
|
||||||
|
unsigned long start = std::stoul(strt);
|
||||||
|
unsigned long length = std::stoul(leng);
|
||||||
|
|
||||||
|
if (start >= m_ambeLength || (start + length) >= m_ambeLength)
|
||||||
|
CLog::logInfo("The start or end for *%s* is out of range, start: %lu, end: %lu\n", name.c_str(), start, start + length);
|
||||||
|
else
|
||||||
|
m_index[name] = new CIndexRecord(name, start + SILENCE_LENGTH, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(file);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CAMBEFileReader::lookup(const std::string &id, std::vector<CAMBEData *>& data)
|
||||||
|
{
|
||||||
|
if(m_index.count(id) == 0U) {
|
||||||
|
CLog::logError("Cannot find the AMBE index for *%s*", id.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CIndexRecord* info = m_index[id];
|
||||||
|
unsigned int start = info->getStart();
|
||||||
|
unsigned int length = info->getLength();
|
||||||
|
|
||||||
|
for (unsigned int i = 0U; i < length; i++) {
|
||||||
|
unsigned char* dataIn = m_ambe + (start + i) * VOICE_FRAME_LENGTH_BYTES;
|
||||||
|
unsigned char buffer[DV_FRAME_LENGTH_BYTES];
|
||||||
|
::memcpy(buffer + 0U, dataIn, VOICE_FRAME_LENGTH_BYTES);
|
||||||
|
|
||||||
|
CAMBEData* dataOut = new CAMBEData;
|
||||||
|
dataOut->setData(buffer, DV_FRAME_LENGTH_BYTES);
|
||||||
|
data.push_back(dataOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 by Geoffrey Merck F4FXL / KC3FRA
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "AMBEData.h"
|
||||||
|
|
||||||
|
class CIndexRecord {
|
||||||
|
public:
|
||||||
|
CIndexRecord(const std::string& name, unsigned int start, unsigned int length) :
|
||||||
|
m_name(name),
|
||||||
|
m_start(start),
|
||||||
|
m_length(length)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string getName() const
|
||||||
|
{
|
||||||
|
return m_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int getStart() const
|
||||||
|
{
|
||||||
|
return m_start;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int getLength() const
|
||||||
|
{
|
||||||
|
return m_length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_name;
|
||||||
|
unsigned int m_start;
|
||||||
|
unsigned int m_length;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CAMBEFileReader
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CAMBEFileReader(const std::string& indexFile, const std::string& ambeFile);
|
||||||
|
~CAMBEFileReader();
|
||||||
|
bool read();
|
||||||
|
bool lookup(const std::string &id, std::vector<CAMBEData *>& data);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool readAmbe();
|
||||||
|
bool readIndex();
|
||||||
|
|
||||||
|
std::string m_indexFile;
|
||||||
|
std::string m_ambeFile;
|
||||||
|
unsigned char* m_ambe;
|
||||||
|
unsigned int m_ambeLength;
|
||||||
|
std::unordered_map<std::string, CIndexRecord*> m_index;
|
||||||
|
};
|
||||||
Binary file not shown.
@ -0,0 +1,44 @@
|
|||||||
|
0 94 29
|
||||||
|
1 140 16
|
||||||
|
2 173 20
|
||||||
|
3 215 19
|
||||||
|
4 255 24
|
||||||
|
5 298 29
|
||||||
|
6 344 34
|
||||||
|
7 398 25
|
||||||
|
8 440 24
|
||||||
|
9 481 23
|
||||||
|
alpha 521 29
|
||||||
|
bravo 568 27
|
||||||
|
charlie 615 30
|
||||||
|
delta 666 30
|
||||||
|
A 714 21
|
||||||
|
B 753 27
|
||||||
|
C 796 34
|
||||||
|
D 848 30
|
||||||
|
E 895 23
|
||||||
|
F 936 27
|
||||||
|
G 982 27
|
||||||
|
H 1026 30
|
||||||
|
I 1075 20
|
||||||
|
J 1113 29
|
||||||
|
K 1162 22
|
||||||
|
L 1201 28
|
||||||
|
M 1248 28
|
||||||
|
N 1295 28
|
||||||
|
O 1341 23
|
||||||
|
P 1386 21
|
||||||
|
Q 1426 23
|
||||||
|
R 1466 28
|
||||||
|
S 1511 31
|
||||||
|
T 1567 21
|
||||||
|
U 1606 22
|
||||||
|
V 1646 30
|
||||||
|
W 1693 46
|
||||||
|
X 1756 31
|
||||||
|
Y 1806 21
|
||||||
|
Z 1844 14
|
||||||
|
linkedto 1858 35
|
||||||
|
notlinked 1941 46
|
||||||
|
linkingto 2008 38
|
||||||
|
isbusy 2091 47
|
||||||
@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 by Geoffrey Merck F4FXL / KC3FRA
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "AMBEFileReader.h"
|
||||||
|
#include "AMBEData.h"
|
||||||
|
|
||||||
|
namespace AMBEFileReaderTests
|
||||||
|
{
|
||||||
|
class AMBEFileReader_lookup : public ::testing::Test {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(AMBEFileReader_lookup, nonExistentFiles)
|
||||||
|
{
|
||||||
|
CAMBEFileReader reader("/this/file/does/not/exist", "/neither/does/this/file");
|
||||||
|
bool res = reader.read();
|
||||||
|
EXPECT_FALSE(res) << "read shall return false on non existent files";
|
||||||
|
|
||||||
|
std::vector<CAMBEData *> data;
|
||||||
|
res = reader.lookup("0", data);
|
||||||
|
EXPECT_FALSE(res) << "read shall return false on non existent files";
|
||||||
|
|
||||||
|
for(auto d : data) {
|
||||||
|
delete d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AMBEFileReader_lookup, onlyIndexFileExists)
|
||||||
|
{
|
||||||
|
std::string indexFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.indx";
|
||||||
|
CAMBEFileReader reader(indexFile, "/this/file/does/not/exist");
|
||||||
|
bool res = reader.read();
|
||||||
|
EXPECT_FALSE(res) << "read shall return false on non existent file";
|
||||||
|
|
||||||
|
std::vector<CAMBEData *> data;
|
||||||
|
res = reader.lookup("0", data);
|
||||||
|
EXPECT_FALSE(res) << "read shall return false on non existent file";
|
||||||
|
|
||||||
|
for(auto d : data) {
|
||||||
|
delete d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AMBEFileReader_lookup, onlyAmbeFileExists)
|
||||||
|
{
|
||||||
|
std::string ambeFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.ambe";
|
||||||
|
CAMBEFileReader reader("/this/file/does/not/exist", ambeFile);
|
||||||
|
bool res = reader.read();
|
||||||
|
EXPECT_FALSE(res) << "read shall return false on non existent file";
|
||||||
|
|
||||||
|
std::vector<CAMBEData *> data;
|
||||||
|
res = reader.lookup("0", data);
|
||||||
|
EXPECT_FALSE(res) << "read shall return false on non existent file";
|
||||||
|
|
||||||
|
for(auto d : data) {
|
||||||
|
delete d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AMBEFileReader_lookup, validId)
|
||||||
|
{
|
||||||
|
std::string indexFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.indx";
|
||||||
|
std::string ambeFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.ambe";
|
||||||
|
CAMBEFileReader reader(indexFile, ambeFile);
|
||||||
|
bool res = reader.read();
|
||||||
|
EXPECT_TRUE(res) << "read shall return true on existent files";
|
||||||
|
|
||||||
|
std::vector<CAMBEData *> data;
|
||||||
|
res = reader.lookup("0", data);
|
||||||
|
EXPECT_TRUE(res) << "read shall return true on existent files and valid Id";
|
||||||
|
EXPECT_NE(data.size(), 0U) << "Vector shall contain data";
|
||||||
|
|
||||||
|
for(auto d : data) {
|
||||||
|
delete d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AMBEFileReader_lookup, invalidId)
|
||||||
|
{
|
||||||
|
std::string indexFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.indx";
|
||||||
|
std::string ambeFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.ambe";
|
||||||
|
CAMBEFileReader reader(indexFile, ambeFile);
|
||||||
|
bool res = reader.read();
|
||||||
|
EXPECT_TRUE(res) << "read shall return true on existent files";
|
||||||
|
|
||||||
|
std::vector<CAMBEData *> data;
|
||||||
|
res = reader.lookup("This Id does not exist", data);
|
||||||
|
EXPECT_FALSE(res) << "read shall return false on existent files and invalid Id";
|
||||||
|
EXPECT_EQ(data.size(), 0U) << "Vector shall not contain data";
|
||||||
|
|
||||||
|
for(auto d : data) {
|
||||||
|
delete d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 by Geoffrey Merck F4FXL / KC3FRA
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "AMBEFileReader.h"
|
||||||
|
#include "Log.h"
|
||||||
|
|
||||||
|
namespace AMBEFileReaderTests
|
||||||
|
{
|
||||||
|
class AMBEFileReader_read : public ::testing::Test {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(AMBEFileReader_read, nonExistentFiles)
|
||||||
|
{
|
||||||
|
CAMBEFileReader reader("/this/file/does/not/exist", "/neither/does/this/file");
|
||||||
|
bool res = reader.read();
|
||||||
|
EXPECT_FALSE(res) << "read shall return false on non existent files";
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AMBEFileReader_read, onlyIndexFileExists)
|
||||||
|
{
|
||||||
|
std::string indexFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.indx";
|
||||||
|
CAMBEFileReader reader(indexFile, "/this/file/does/not/exist");
|
||||||
|
bool res = reader.read();
|
||||||
|
EXPECT_FALSE(res) << "read shall return false on non existent file";
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AMBEFileReader_read, onlyAmbeFileExists)
|
||||||
|
{
|
||||||
|
std::string ambeFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.ambe";
|
||||||
|
CAMBEFileReader reader("/this/file/does/not/exist", ambeFile);
|
||||||
|
bool res = reader.read();
|
||||||
|
EXPECT_FALSE(res) << "read shall return false on non existent file";
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(AMBEFileReader_read, bothFileExist)
|
||||||
|
{
|
||||||
|
std::string indexFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.indx";
|
||||||
|
std::string ambeFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.ambe";
|
||||||
|
|
||||||
|
CAMBEFileReader reader(indexFile, ambeFile);
|
||||||
|
bool res = reader.read();
|
||||||
|
EXPECT_TRUE(res) << "read shall return true on existent files";
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=D-STAR Time Server Daemon
|
||||||
|
After=network.target,network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=dstar
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/local/bin/dgwtimeserver %CFG_DIR%/dgwtimeserver.cfg
|
||||||
|
Restart=on-failure
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
Loading…
Reference in new issue