From 47e3519bba9cb4e1471113c096595f85c63907d4 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sun, 6 Feb 2022 14:37:18 +0100 Subject: [PATCH 01/40] #20 add build/run stuff --- .vscode/launch.json | 24 ++++++++++++++++++++++++ .vscode/tasks.json | 21 +++++++++++++++++---- DGWTimeServer/Makefile | 22 ++++++++++++++++++++++ Makefile | 6 +++++- 4 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 DGWTimeServer/Makefile diff --git a/.vscode/launch.json b/.vscode/launch.json index 14711bb..03f8b49 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -76,6 +76,30 @@ } ] }, + { + "name": "(gdb) dgwtimeserver", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/DGWTimeServer/dgwtimeserver", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Activer l'impression en mode Pretty pour gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Définir la version désassemblage sur Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ] + }, { "name": "(gdb) dgwvoicetransmit", "type": "cppdbg", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 6066b4b..0545c42 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -13,10 +13,7 @@ "USE_GPSD=1", "all" ], - "group": { - "kind": "build", - "isDefault": true - }, + "group": "build", "problemMatcher": [] }, { @@ -58,6 +55,22 @@ "group": "build", "problemMatcher": [] }, + { + "label": "Build DGWTimeServer", + "type": "shell", + "command": "make", + "args": [ + "-j3", + "ENABLE_DEBUG=1", + "USE_GPSD=1", + "DGWTimeServer/dgwtimeserver" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [] + }, { "label": "Build DGWVoiceTransmit", "type": "shell", diff --git a/DGWTimeServer/Makefile b/DGWTimeServer/Makefile new file mode 100644 index 0000000..4ea197d --- /dev/null +++ b/DGWTimeServer/Makefile @@ -0,0 +1,22 @@ +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) + +../BaseCommon/BaseCommon.a: +../DStarBase/DStarBase.a: +../VersionInfo/GitVersion.h: diff --git a/Makefile b/Makefile index 7a131cb..d48596c 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ endif .PHONY: all -all: DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol DGWTextTransmit/dgwtexttransmit DGWVoiceTransmit/dgwvoicetransmit #tests +all: DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol DGWTextTransmit/dgwtexttransmit DGWTimeServer/dgwtimeserver DGWVoiceTransmit/dgwvoicetransmit #tests APRS/APRS.a: BaseCommon/BaseCommon.a FORCE $(MAKE) -C APRS @@ -63,6 +63,9 @@ DGWRemoteControl/dgwremotecontrol: VersionInfo/GitVersion.h $(OBJS) DStarBase/DS DGWTextTransmit/dgwtexttransmit: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE $(MAKE) -C DGWTextTransmit +DGWTimeServer/dgwtimeserver: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE + $(MAKE) -C DGWTimeServer + DGWVoiceTransmit/dgwvoicetransmit: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE $(MAKE) -C DGWVoiceTransmit @@ -99,6 +102,7 @@ install : DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol # install accessories $(MAKE) -C DGWRemoteControl install $(MAKE) -C DGWTextTransmit install + $(MAKE) -C DGWTimeServer install $(MAKE) -C DGWVoiceTransmit install # create user for daemon From dc989239f9080b1df456fef5c010ae2ada6f05ec Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 12 Feb 2022 06:57:12 +0100 Subject: [PATCH 02/40] #20 Comment timeserver --- .vscode/tasks.json | 10 +++++----- Makefile | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 0545c42..88ce123 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -13,7 +13,10 @@ "USE_GPSD=1", "all" ], - "group": "build", + "group": { + "kind": "build", + "isDefault": true + }, "problemMatcher": [] }, { @@ -65,10 +68,7 @@ "USE_GPSD=1", "DGWTimeServer/dgwtimeserver" ], - "group": { - "kind": "build", - "isDefault": true - }, + "group": "build", "problemMatcher": [] }, { diff --git a/Makefile b/Makefile index d48596c..8f8d3b5 100644 --- a/Makefile +++ b/Makefile @@ -65,6 +65,7 @@ DGWTextTransmit/dgwtexttransmit: VersionInfo/GitVersion.h $(OBJS) DStarBase/DSta DGWTimeServer/dgwtimeserver: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE $(MAKE) -C DGWTimeServer + echo bla DGWVoiceTransmit/dgwvoicetransmit: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE $(MAKE) -C DGWVoiceTransmit From 5ec67b9f8a90392e48c8699338d0db0831af8afc Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Fri, 18 Feb 2022 15:20:09 +0100 Subject: [PATCH 03/40] #20 move install to respective makefiles --- DGWTimeServer/Makefile | 5 +++-- DStarGateway/Makefile | 9 +++++++++ Makefile | 11 +---------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/DGWTimeServer/Makefile b/DGWTimeServer/Makefile index 4ea197d..74ef75c 100644 --- a/DGWTimeServer/Makefile +++ b/DGWTimeServer/Makefile @@ -3,7 +3,8 @@ 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) +# $(CC) $(CPPFLAGS) -o dgwtimeserver $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a $(LDFLAGS) + echo bla %.o : %.cpp $(CC) -I../BaseCommon -I../DStarBase -I../VersionInfo $(CPPFLAGS) -MMD -MD -c $< -o $@ @@ -15,7 +16,7 @@ clean: .PHONY install: install: dgwtimeserver # copy executable - @cp -f dgwtimeserver $(BIN_DIR) +# @cp -f dgwtimeserver $(BIN_DIR) ../BaseCommon/BaseCommon.a: ../DStarBase/DStarBase.a: diff --git a/DStarGateway/Makefile b/DStarGateway/Makefile index 73318d1..03b2ea3 100644 --- a/DStarGateway/Makefile +++ b/DStarGateway/Makefile @@ -22,6 +22,15 @@ install: dstargateway @sed -i "s|path=/var/log/dstargateway/|path=$(LOG_DIR)|g" $(CFG_DIR)/dstargateway.cfg @sed -i "s|data=/usr/local/share/dstargateway.d/|data=$(DATA_DIR)|g" $(CFG_DIR)/dstargateway.cfg +# SystemD service install + @cp -f ../debian/dstargateway.service /lib/systemd/system/ + @sed -i "s|%CFG_DIR%|$(CFG_DIR)|g" /lib/systemd/system/dstargateway.service + systemctl enable dstargateway.service + @systemctl daemon-reload + @echo "\n" + @echo "DStarGateway Install complete, edit $(CFG_DIR)dstargateway.cfg and start the daemon with 'systemctl start dstargateway.service'" + @echo "\n" + ../APRS/APRS.a: ../Common/Common.a: ../DStarBase/DStarBase.a: diff --git a/Makefile b/Makefile index 8f8d3b5..e34632e 100644 --- a/Makefile +++ b/Makefile @@ -120,18 +120,9 @@ install : DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol $(MAKE) -C Data install @chown -R dstar:dstar $(DATA_DIR) -#install executables +# install services executables $(MAKE) -C DStarGateway install -# SystemD service install - @cp -f debian/dstargateway.service /lib/systemd/system/ - @sed -i "s|%CFG_DIR%|$(CFG_DIR)|g" /lib/systemd/system/dstargateway.service - systemctl enable dstargateway.service - @systemctl daemon-reload - @echo "\n\n" - @echo "Install complete, edit $(CFG_DIR)dstargateway.cfg and start the daemon with 'systemctl start dstargateway.service'" - @echo "\n\n" - .PHONY: uninstall uninstall : systemctl stop dstargateway.service || true From 874c3ab7b44e16de57f131f854870cfdf317aa33 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Fri, 18 Feb 2022 15:37:18 +0100 Subject: [PATCH 04/40] #20 add app --- DGWTimeServer/DGWTimeServerApp.cpp | 32 ++++++++++++++++++++++++++++++ DGWTimeServer/DGWTimeServerApp.h | 21 ++++++++++++++++++++ DGWTimeServer/Makefile | 4 ++-- 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 DGWTimeServer/DGWTimeServerApp.cpp create mode 100644 DGWTimeServer/DGWTimeServerApp.h diff --git a/DGWTimeServer/DGWTimeServerApp.cpp b/DGWTimeServer/DGWTimeServerApp.cpp new file mode 100644 index 0000000..ac1c3c2 --- /dev/null +++ b/DGWTimeServer/DGWTimeServerApp.cpp @@ -0,0 +1,32 @@ +/* + * 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 +#include + +#include "DGWTimeServerApp.h" + +int main(int argc, char * argv[]) +{ + if (2 != argc) { + printf("usage: %s path_to_config_file\n", argv[0]); + printf(" %s --version\n", argv[0]); + return 1; + } +} \ No newline at end of file diff --git a/DGWTimeServer/DGWTimeServerApp.h b/DGWTimeServer/DGWTimeServerApp.h new file mode 100644 index 0000000..fc87275 --- /dev/null +++ b/DGWTimeServer/DGWTimeServerApp.h @@ -0,0 +1,21 @@ +/* + * 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 + diff --git a/DGWTimeServer/Makefile b/DGWTimeServer/Makefile index 74ef75c..5400a76 100644 --- a/DGWTimeServer/Makefile +++ b/DGWTimeServer/Makefile @@ -3,7 +3,7 @@ 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) + $(CC) $(CPPFLAGS) -o dgwtimeserver $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a $(LDFLAGS) echo bla %.o : %.cpp @@ -16,7 +16,7 @@ clean: .PHONY install: install: dgwtimeserver # copy executable -# @cp -f dgwtimeserver $(BIN_DIR) + @cp -f dgwtimeserver $(BIN_DIR) ../BaseCommon/BaseCommon.a: ../DStarBase/DStarBase.a: From 36750136a0731efdbb442a1c2abd589c03e541a9 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Fri, 18 Feb 2022 15:37:54 +0100 Subject: [PATCH 05/40] #20 add time server to git ignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8bf1885..b2f773d 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,4 @@ Tests/dstargateway_tests DGWRemoteControl/dgwremotecontrol DGWVoiceTransmit/dgwvoicetransmit DGWTextTransmit/dgwtexttransmit +DGWTimeServer/dgwtimeserver From 31f41be0249d58a1391446bdcc4b42d536eb8783 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 07:46:15 +0100 Subject: [PATCH 06/40] Add new line at end of file (keep git happy) --- DStarGateway/example.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DStarGateway/example.cfg b/DStarGateway/example.cfg index 36e38de..0d349ad 100644 --- a/DStarGateway/example.cfg +++ b/DStarGateway/example.cfg @@ -183,4 +183,4 @@ password=CHANGE_ME # If password is left blank, remote will be disabled regardle [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 \ No newline at end of file +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 From 5a8091c58dc3a8006a8cb5144c564e450a82a884 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 08:52:50 +0100 Subject: [PATCH 07/40] #20 clear repeater list --- DStarGateway/DStarGatewayConfig.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/DStarGateway/DStarGatewayConfig.cpp b/DStarGateway/DStarGatewayConfig.cpp index 5d236ca..0afd547 100644 --- a/DStarGateway/DStarGatewayConfig.cpp +++ b/DStarGateway/DStarGatewayConfig.cpp @@ -199,6 +199,7 @@ bool CDStarGatewayConfig::loadPaths(const CConfig & cfg) bool CDStarGatewayConfig::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; From 77634729d40b57a633515231b58ea6479d339309 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 09:21:05 +0100 Subject: [PATCH 08/40] #20 add config --- .vscode/launch.json | 2 +- DGWTimeServer/DGWTimeServerApp.cpp | 8 ++ DGWTimeServer/Makefile | 1 - DGWTimeServer/TimeServerConfig.cpp | 139 +++++++++++++++++++++++++++++ DGWTimeServer/TimeServerConfig.h | 65 ++++++++++++++ DGWTimeServer/TimeServerDefs.h | 49 ++++++++++ DGWTimeServer/example.cfg | 32 +++++++ Makefile | 1 - 8 files changed, 294 insertions(+), 3 deletions(-) create mode 100644 DGWTimeServer/TimeServerConfig.cpp create mode 100644 DGWTimeServer/TimeServerConfig.h create mode 100644 DGWTimeServer/TimeServerDefs.h create mode 100644 DGWTimeServer/example.cfg diff --git a/.vscode/launch.json b/.vscode/launch.json index bd85764..f15168f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -85,7 +85,7 @@ "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/DGWTimeServer/dgwtimeserver", - "args": [], + "args": ["${workspaceFolder}/Sandbox/__timeserver_test.cfg"], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], diff --git a/DGWTimeServer/DGWTimeServerApp.cpp b/DGWTimeServer/DGWTimeServerApp.cpp index ac1c3c2..6b894ec 100644 --- a/DGWTimeServer/DGWTimeServerApp.cpp +++ b/DGWTimeServer/DGWTimeServerApp.cpp @@ -21,6 +21,7 @@ #include #include "DGWTimeServerApp.h" +#include "TimeServerConfig.h" int main(int argc, char * argv[]) { @@ -29,4 +30,11 @@ int main(int argc, char * argv[]) printf(" %s --version\n", argv[0]); return 1; } + + std::string configfile(argv[1]); + CTimeServerConfig config(configfile); + if(!config.load()) + return 0; + + return 1; } \ No newline at end of file diff --git a/DGWTimeServer/Makefile b/DGWTimeServer/Makefile index 5400a76..4ea197d 100644 --- a/DGWTimeServer/Makefile +++ b/DGWTimeServer/Makefile @@ -4,7 +4,6 @@ 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) - echo bla %.o : %.cpp $(CC) -I../BaseCommon -I../DStarBase -I../VersionInfo $(CPPFLAGS) -MMD -MD -c $< -o $@ diff --git a/DGWTimeServer/TimeServerConfig.cpp b/DGWTimeServer/TimeServerConfig.cpp new file mode 100644 index 0000000..ec31444 --- /dev/null +++ b/DGWTimeServer/TimeServerConfig.cpp @@ -0,0 +1,139 @@ +/* + * 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 +#include + +#include "TimeServerConfig.h" +#include "Log.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; + } + + 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; + + 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, "voiceandtext", {"voice", "text", "voiceandtext"}) && ret; + if(format == "voice") m_timeServer.format = FORMAT_VOICE_TIME; + else if(format == "text") m_timeServer.format = FORMAT_TEXT_TIME; + else if(format == "voiceandtext") m_timeServer.format = FORMAT_VOICE_ALL; + + 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; +} diff --git a/DGWTimeServer/TimeServerConfig.h b/DGWTimeServer/TimeServerConfig.h new file mode 100644 index 0000000..d17a650 --- /dev/null +++ b/DGWTimeServer/TimeServerConfig.h @@ -0,0 +1,65 @@ +/* + * 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 +#include + +#include "Config.h" +#include "TimeServerDefs.h" + +typedef struct { + std::string callsign; + std::string address; + FORMAT format; + LANGUAGE language; + INTERVAL interval; +} TTimeServer; + +typedef struct { + bool enabled; + std::string band; +} TRepeater; + +typedef struct { + bool daemon; + std::string pidFile; + std::string user; +} TDaemon; + + +class CTimeServerConfig +{ +public: + CTimeServerConfig(const std::string &pathname); + ~CTimeServerConfig(); + + bool load(); + +private: + bool open(CConfig & cfg); + bool loadRepeaters(const CConfig & cfg); + bool loadTimeServer(const CConfig & cfg); + bool loadDaemon(const CConfig & cfg); + + std::string m_fileName; + std::vector m_repeaters; + TTimeServer m_timeServer; + TDaemon m_daemon; +}; diff --git a/DGWTimeServer/TimeServerDefs.h b/DGWTimeServer/TimeServerDefs.h new file mode 100644 index 0000000..d82a57e --- /dev/null +++ b/DGWTimeServer/TimeServerDefs.h @@ -0,0 +1,49 @@ +/* + * 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("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_VOICE_ALL, + FORMAT_TEXT_TIME +}; diff --git a/DGWTimeServer/example.cfg b/DGWTimeServer/example.cfg new file mode 100644 index 0000000..cc6b0cf --- /dev/null +++ b/DGWTimeServer/example.cfg @@ -0,0 +1,32 @@ +[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 + +# 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 + + +# 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 system 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 diff --git a/Makefile b/Makefile index e34632e..1ed1c97 100644 --- a/Makefile +++ b/Makefile @@ -65,7 +65,6 @@ DGWTextTransmit/dgwtexttransmit: VersionInfo/GitVersion.h $(OBJS) DStarBase/DSta DGWTimeServer/dgwtimeserver: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE $(MAKE) -C DGWTimeServer - echo bla DGWVoiceTransmit/dgwvoicetransmit: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE $(MAKE) -C DGWVoiceTransmit From b237829b95703720aaec39726e0324b495b21df5 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 16:46:16 +0100 Subject: [PATCH 09/40] #20 first working prototype of time server --- DGWTimeServer/DGWTimeServerApp.cpp | 61 +- DGWTimeServer/DGWTimeServerApp.h | 21 + DGWTimeServer/TimeServerConfig.cpp | 32 + DGWTimeServer/TimeServerConfig.h | 11 +- DGWTimeServer/TimeServerDefs.h | 2 +- DGWTimeServer/TimeServerThread.cpp | 1458 ++++++++++++++++++++++++++++ DGWTimeServer/TimeServerThread.h | 122 +++ DGWTimeServer/example.cfg | 3 + 8 files changed, 1705 insertions(+), 5 deletions(-) create mode 100644 DGWTimeServer/TimeServerThread.cpp create mode 100644 DGWTimeServer/TimeServerThread.h diff --git a/DGWTimeServer/DGWTimeServerApp.cpp b/DGWTimeServer/DGWTimeServerApp.cpp index 6b894ec..5088ee2 100644 --- a/DGWTimeServer/DGWTimeServerApp.cpp +++ b/DGWTimeServer/DGWTimeServerApp.cpp @@ -19,9 +19,9 @@ #include #include +#include #include "DGWTimeServerApp.h" -#include "TimeServerConfig.h" int main(int argc, char * argv[]) { @@ -34,7 +34,62 @@ int main(int argc, char * argv[]) std::string configfile(argv[1]); CTimeServerConfig config(configfile); if(!config.load()) + return 1; + + CDGWTimeServerApp app(&config); + + if(!app.init()) return 0; - return 1; -} \ No newline at end of file + app.run(); + + return 0; +} + +CDGWTimeServerApp::CDGWTimeServerApp(const CTimeServerConfig * config) : +m_config(config) +{ + 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 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; +} diff --git a/DGWTimeServer/DGWTimeServerApp.h b/DGWTimeServer/DGWTimeServerApp.h index fc87275..ffcecdf 100644 --- a/DGWTimeServer/DGWTimeServerApp.h +++ b/DGWTimeServer/DGWTimeServerApp.h @@ -19,3 +19,24 @@ #pragma once +#include "TimeServerDefs.h" +#include "TimeServerConfig.h" +#include "TimeServerThread.h" + +class CDGWTimeServerApp +{ +private: + /* data */ +public: + CDGWTimeServerApp(const CTimeServerConfig * config); + ~CDGWTimeServerApp(); + + bool init(); + void run(); + +private: + bool createThread(); + + const CTimeServerConfig * m_config; + CTimeServerThread * m_thread; +}; diff --git a/DGWTimeServer/TimeServerConfig.cpp b/DGWTimeServer/TimeServerConfig.cpp index ec31444..5d4b067 100644 --- a/DGWTimeServer/TimeServerConfig.cpp +++ b/DGWTimeServer/TimeServerConfig.cpp @@ -49,6 +49,7 @@ bool CTimeServerConfig::load() ret = loadTimeServer(cfg) && ret; ret = loadRepeaters(cfg) && ret; ret = loadDaemon(cfg) && ret; + ret = loadPaths(cfg) && ret; } return ret; @@ -137,3 +138,34 @@ bool CTimeServerConfig::loadDaemon(const CConfig & cfg) 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; +} + +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; +} diff --git a/DGWTimeServer/TimeServerConfig.h b/DGWTimeServer/TimeServerConfig.h index d17a650..a28d36d 100644 --- a/DGWTimeServer/TimeServerConfig.h +++ b/DGWTimeServer/TimeServerConfig.h @@ -33,7 +33,6 @@ typedef struct { } TTimeServer; typedef struct { - bool enabled; std::string band; } TRepeater; @@ -43,6 +42,9 @@ typedef struct { std::string user; } TDaemon; +typedef struct { + std::string data; +} TPaths; class CTimeServerConfig { @@ -51,15 +53,22 @@ public: ~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; 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); std::string m_fileName; std::vector m_repeaters; TTimeServer m_timeServer; TDaemon m_daemon; + TPaths m_paths; }; diff --git a/DGWTimeServer/TimeServerDefs.h b/DGWTimeServer/TimeServerDefs.h index d82a57e..6b5ca4b 100644 --- a/DGWTimeServer/TimeServerDefs.h +++ b/DGWTimeServer/TimeServerDefs.h @@ -19,7 +19,7 @@ #pragma once -const std::string APPLICATION_NAME("Time Server"); +const std::string APPLICATION_NAME("DStarGateway time Server"); enum LANGUAGE { LANG_ENGLISH_UK_1, diff --git a/DGWTimeServer/TimeServerThread.cpp b/DGWTimeServer/TimeServerThread.cpp new file mode 100644 index 0000000..2bfcccc --- /dev/null +++ b/DGWTimeServer/TimeServerThread.cpp @@ -0,0 +1,1458 @@ +/* + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "TimeServerThread.h" +#include "DStarDefines.h" +#include "Utils.h" +#include "NetUtils.h" +#include "StringUtils.h" +#include "Log.h" + +const unsigned int MAX_FRAMES = 60U * DSTAR_FRAMES_PER_SEC; + +const unsigned int SILENCE_LENGTH = 10U; + +enum SLOW_DATA { + SD_HEADER, + SD_TEXT +}; + +CTimeServerThread::CTimeServerThread() : +CThread("Time Server"), +m_socket("", 0U), +m_callsign(), +m_callsignA(), +m_callsignB(), +m_callsignC(), +m_callsignD(), +m_callsignE(), +m_callsignG(), +m_address(), +m_language(LANG_ENGLISH_UK_1), +m_format(FORMAT_VOICE_TIME), +m_interval(INTERVAL_15MINS), +m_ambe(NULL), +m_ambeLength(0U), +m_index(), +m_seqNo(0U), +m_in(0U), +m_encoder(), +m_data(NULL), +m_killed(false), +m_dataPath("") +{ + CHeaderData::initialise(); + m_address.s_addr = INADDR_NONE; + + m_data = new CAMBEData*[MAX_FRAMES]; + + for (unsigned int i = 0U; i < MAX_FRAMES; i++) + m_data[i] = nullptr; +} + +CTimeServerThread::~CTimeServerThread() +{ + for (auto it = m_index.begin(); it != m_index.end(); it++) + delete it->second; + + delete[] m_ambe; + delete[] m_data; +} + +void * CTimeServerThread::Entry() +{ + // Wait here until we have the essentials to run + while (!m_killed && m_address.s_addr == INADDR_NONE && m_callsignA.empty() && m_callsignB.empty() && m_callsignC.empty() && m_callsignD.empty() && m_callsignE.empty()) + Sleep(500UL); // 1/2 sec + + if (m_killed) + return nullptr; + + if (m_format != FORMAT_TEXT_TIME) { + bool ret = loadAMBE(); + if (!ret) { + CLog::logWarning(("Cannot load the AMBE data, using text only time")); + m_format = FORMAT_TEXT_TIME; + } + } + + CLog::logInfo(("Starting the Time Server thread")); + + unsigned int lastMin = 0U; + + while (!m_killed) { + time_t now; + ::time(&now); + + struct tm* tm = ::localtime(&now); + + unsigned int hour = tm->tm_hour; + unsigned int min = tm->tm_min; + + if (min != lastMin) { + if (m_interval == INTERVAL_15MINS && (min == 0U || min == 15U || min == 30U || min == 45U)) + sendTime(hour, min); + else if (m_interval == INTERVAL_30MINS && (min == 0U || min == 30U)) + sendTime(hour, min); + else if (m_interval == INTERVAL_60MINS && min == 0U) + sendTime(hour, min); + } + + lastMin = min; + + Sleep(450UL); + } + + CLog::logInfo(("Stopping the Time Server thread")); + + m_socket.close(); + + return nullptr; +} + +void CTimeServerThread::kill() +{ + m_killed = true; +} + +bool CTimeServerThread::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) +{ + m_callsign = callsign; + m_callsign.resize(LONG_CALLSIGN_LENGTH - 1U, (' ')); + + m_callsignG = m_callsign; + m_callsignG.push_back('G'); + + if (!rpt1.empty()) { + m_callsignA = m_callsign + rpt1; + } + + if (!rpt2.empty()) { + m_callsignB = m_callsign + rpt2; + } + + if (!rpt3.empty()) { + m_callsignC = m_callsign + rpt3; + } + + if (!rpt4.empty()) { + m_callsignD = m_callsign + rpt4; + } + + m_callsign.push_back(' '); + + m_address = CUDPReaderWriter::lookup(address); + m_dataPath.assign(dataPath); + + bool ret = m_socket.open(); + if (!ret) + return false; + + return true; +} + +void CTimeServerThread::setAnnouncements(LANGUAGE language, FORMAT format, INTERVAL interval) +{ + m_language = language; + m_format = format; + m_interval = interval; +} + +void CTimeServerThread::sendTime(unsigned int hour, unsigned int min) +{ + std::vector words; + + switch (m_language) { + case LANG_ENGLISH_UK_1: + words = sendTimeEnGB1(hour, min); + break; + case LANG_ENGLISH_UK_2: + words = sendTimeEnGB2(hour, min); + break; + case LANG_ENGLISH_US_1: + words = sendTimeEnUS1(hour, min); + break; + case LANG_ENGLISH_US_2: + words = sendTimeEnUS2(hour, min); + break; + case LANG_DEUTSCH_1: + words = sendTimeDeDE1(hour, min); + break; + case LANG_DEUTSCH_2: + words = sendTimeDeDE2(hour, min); + break; + case LANG_FRANCAIS: + words = sendTimeFrFR(hour, min); + break; + case LANG_NEDERLANDS: + words = sendTimeNlNL(hour, min); + break; + case LANG_SVENSKA: + words = sendTimeSeSE(hour, min); + break; + case LANG_ESPANOL: + words = sendTimeEsES(hour, min); + break; + case LANG_NORSK: + words = sendTimeNoNO(hour, min); + break; + case LANG_PORTUGUES: + words = sendTimePtPT(hour, min); + break; + default: + break; + } + + send(words, hour, min); +} + +std::vector CTimeServerThread::sendTimeEnGB1(unsigned int hour, unsigned int min) +{ + std::vector words; + + words.push_back(("It_is")); + + switch (hour) { + case 0U: + case 12U: + words.push_back(("twelve")); + break; + case 1U: + case 13U: + words.push_back(("one")); + break; + case 2U: + case 14U: + words.push_back(("two")); + break; + case 3U: + case 15U: + words.push_back(("three")); + break; + case 4U: + case 16U: + words.push_back(("four")); + break; + case 5U: + case 17U: + words.push_back(("five")); + break; + case 6U: + case 18U: + words.push_back(("six")); + break; + case 7U: + case 19U: + words.push_back(("seven")); + break; + case 8U: + case 20U: + words.push_back(("eight")); + break; + case 9U: + case 21U: + words.push_back(("nine")); + break; + case 10U: + case 22U: + words.push_back(("ten")); + break; + case 11U: + case 23U: + words.push_back(("eleven")); + break; + default: + break; + } + + switch (min) { + case 15U: + words.push_back(("fifteen")); + break; + case 30U: + words.push_back(("thirty")); + break; + case 45U: + words.push_back(("forty-five")); + break; + default: + break; + } + + if (hour >= 12U) + words.push_back(("PM")); + else + words.push_back(("AM")); + + return words; +} + +std::vector CTimeServerThread::sendTimeEnGB2(unsigned int hour, unsigned int min) +{ + std::vector words; + + words.push_back("It_is"); + + if (min == 15U) { + words.push_back(("a_quarter_past")); + } else if (min == 30U) { + words.push_back(("half_past")); + } else if (min == 45U) { + words.push_back(("a_quarter_to")); + if (hour == 23U) + hour = 0U; + else + hour++; + } + + if (hour == 0U && min == 0U) { + words.push_back(("midnight")); + } else if (hour == 12U && min == 0U) { + words.push_back(("twelve")); + words.push_back(("noon")); + } else if (hour == 0U || hour == 12U) { + words.push_back(("twelve")); + } else if (hour == 1U || hour == 13U) { + words.push_back(("one")); + } else if (hour == 2U || hour == 14U) { + words.push_back(("two")); + } else if (hour == 3U || hour == 15U) { + words.push_back(("three")); + } else if (hour == 4U || hour == 16U) { + words.push_back(("four")); + } else if (hour == 5U || hour == 17U) { + words.push_back(("five")); + } else if (hour == 6U || hour == 18U) { + words.push_back(("six")); + } else if (hour == 7U || hour == 19U) { + words.push_back(("seven")); + } else if (hour == 8U || hour == 20U) { + words.push_back(("eight")); + } else if (hour == 9U || hour == 21U) { + words.push_back(("nine")); + } else if (hour == 10U || hour == 22U) { + words.push_back(("ten")); + } else if (hour == 11U || hour == 23U) { + words.push_back(("eleven")); + } + + if (hour != 0U && hour != 12U && min == 0U) + words.push_back(("O_Clock")); + + return words; +} + +std::vector CTimeServerThread::sendTimeEnUS1(unsigned int hour, unsigned int min) +{ + std::vector words; + + words.push_back(("It_is")); + + switch (hour) { + case 0U: + case 12U: + words.push_back(("twelve")); + break; + case 1U: + case 13U: + words.push_back(("one")); + break; + case 2U: + case 14U: + words.push_back(("two")); + break; + case 3U: + case 15U: + words.push_back(("three")); + break; + case 4U: + case 16U: + words.push_back(("four")); + break; + case 5U: + case 17U: + words.push_back(("five")); + break; + case 6U: + case 18U: + words.push_back(("six")); + break; + case 7U: + case 19U: + words.push_back(("seven")); + break; + case 8U: + case 20U: + words.push_back(("eight")); + break; + case 9U: + case 21U: + words.push_back(("nine")); + break; + case 10U: + case 22U: + words.push_back(("ten")); + break; + case 11U: + case 23U: + words.push_back(("eleven")); + break; + default: + break; + } + + switch (min) { + case 15U: + words.push_back(("fifteen")); + break; + case 30U: + words.push_back(("thirty")); + break; + case 45U: + words.push_back(("forty-five")); + break; + default: + break; + } + + if (hour >= 12U) + words.push_back(("PM")); + else + words.push_back(("AM")); + + return words; +} + +std::vector CTimeServerThread::sendTimeEnUS2(unsigned int hour, unsigned int min) +{ + std::vector words; + + words.push_back(("It_is")); + + if (min == 15U) { + words.push_back(("a_quarter_past")); + } else if (min == 30U) { + words.push_back(("half_past")); + } else if (min == 45U) { + words.push_back(("a_quarter_to")); + if (hour == 23U) + hour = 0U; + else + hour++; + } + + if (hour == 0U && min == 0U) { + words.push_back(("midnight")); + } else if (hour == 12U && min == 0U) { + words.push_back(("twelve")); + words.push_back(("noon")); + } else if (hour == 0U || hour == 12U) { + words.push_back(("twelve")); + } else if (hour == 1U || hour == 13U) { + words.push_back(("one")); + } else if (hour == 2U || hour == 14U) { + words.push_back(("two")); + } else if (hour == 3U || hour == 15U) { + words.push_back(("three")); + } else if (hour == 4U || hour == 16U) { + words.push_back(("four")); + } else if (hour == 5U || hour == 17U) { + words.push_back(("five")); + } else if (hour == 6U || hour == 18U) { + words.push_back(("six")); + } else if (hour == 7U || hour == 19U) { + words.push_back(("seven")); + } else if (hour == 8U || hour == 20U) { + words.push_back(("eight")); + } else if (hour == 9U || hour == 21U) { + words.push_back(("nine")); + } else if (hour == 10U || hour == 22U) { + words.push_back(("ten")); + } else if (hour == 11U || hour == 23U) { + words.push_back(("eleven")); + } + + if (hour != 0U && hour != 12U && min == 0U) + words.push_back(("O_Clock")); + + return words; +} + +std::vector CTimeServerThread::sendTimeDeDE1(unsigned int hour, unsigned int min) +{ + std::vector words; + + words.push_back(("Es_ist")); + + switch (hour) { + case 0U: words.push_back(("null")); break; + case 1U: words.push_back(("ein")); break; + case 2U: words.push_back(("zwei")); break; + case 3U: words.push_back(("drei")); break; + case 4U: words.push_back(("vier")); break; + case 5U: words.push_back(("fuenf")); break; + case 6U: words.push_back(("sechs")); break; + case 7U: words.push_back(("sieben")); break; + case 8U: words.push_back(("acht")); break; + case 9U: words.push_back(("neun")); break; + case 10U: words.push_back(("zehn")); break; + case 11U: words.push_back(("elf")); break; + case 12U: words.push_back(("zwoelf")); break; + case 13U: words.push_back(("dreizehn")); break; + case 14U: words.push_back(("vierzehn")); break; + case 15U: words.push_back(("fuenfzehn")); break; + case 16U: words.push_back(("sechzehn")); break; + case 17U: words.push_back(("siebzehn")); break; + case 18U: words.push_back(("achtzehn")); break; + case 19U: words.push_back(("neunzehn")); break; + case 20U: words.push_back(("zwanzig")); break; + case 21U: words.push_back(("einundzwanzig")); break; + case 22U: words.push_back(("zweiundzwanzig")); break; + case 23U: words.push_back(("dreiundzwanzig")); break; + default: break; + } + + words.push_back(("Uhr")); + + switch (min) { + case 15U: + words.push_back(("fuenfzehn")); + break; + case 30U: + words.push_back(("dreissig")); + break; + case 45U: + words.push_back(("fuenfundvierzig")); + break; + default: + break; + } + + return words; +} + +std::vector CTimeServerThread::sendTimeDeDE2(unsigned int hour, unsigned int min) +{ + std::vector words; + + words.push_back(("Es_ist")); + + if (min == 15U) { + words.push_back(("viertel_nach")); + } else if (min == 30U) { + words.push_back(("halb")); + if (hour == 23U) + hour = 0U; + else + hour++; + } else if (min == 45U) { + words.push_back(("viertel_vor")); + if (hour == 23U) + hour = 0U; + else + hour++; + } + + if (hour == 0U) { + words.push_back(("null")); + } else if (hour == 1U || hour == 13U) { + words.push_back(("ein")); + } else if (hour == 2U || hour == 14U) { + words.push_back(("zwei")); + } else if (hour == 3U || hour == 15U) { + words.push_back(("drei")); + } else if (hour == 4U || hour == 16U) { + words.push_back(("vier")); + } else if (hour == 5U || hour == 17U) { + words.push_back(("fuenf")); + } else if (hour == 6U || hour == 18U) { + words.push_back(("sechs")); + } else if (hour == 7U || hour == 19U) { + words.push_back(("sieben")); + } else if (hour == 8U || hour == 20U) { + words.push_back(("acht")); + } else if (hour == 9U || hour == 21U) { + words.push_back(("neun")); + } else if (hour == 10U || hour == 22U) { + words.push_back(("zehn")); + } else if (hour == 11U || hour == 23U) { + words.push_back(("elf")); + } else if (hour == 12U) { + words.push_back(("zwoelf")); + } + + if (min == 0U) + words.push_back(("Uhr")); + + return words; +} + +std::vector CTimeServerThread::sendTimeFrFR(unsigned int hour, unsigned int min) +{ + std::vector words; + + // if (hour > 17U) + // words.push_back(("bonsoir")); + // else + // words.push_back(("bonjour")); + + words.push_back(("il_est")); + + if (min == 45U) + hour++; + + if (hour == 0U) { + words.push_back(("minuit")); + } else if (hour == 1U || hour == 13U) { + words.push_back(("une")); + } else if (hour == 2U || hour == 14U) { + words.push_back(("deux")); + } else if (hour == 3U || hour == 15U) { + words.push_back(("trois")); + } else if (hour == 4U || hour == 16U) { + words.push_back(("quatre")); + } else if (hour == 5U || hour == 17U) { + words.push_back(("cinq")); + } else if (hour == 6U || hour == 18U) { + words.push_back(("six")); + } else if (hour == 7U || hour == 19U) { + words.push_back(("sept")); + } else if (hour == 8U || hour == 20U) { + words.push_back(("huit")); + } else if (hour == 9U || hour == 21U) { + words.push_back(("neuf")); + } else if (hour == 10U || hour == 22U) { + words.push_back(("dix")); + } else if (hour == 11U || hour == 23U) { + words.push_back(("onze")); + } else if (hour == 12U) { + words.push_back(("midi")); + } + + if (hour == 1U || hour == 13U) + words.push_back(("heure")); + else if (hour != 12U && hour != 0U) + words.push_back(("heures")); + + if (min == 15U) { + words.push_back(("et_quart")); + } else if (min == 30U) { + words.push_back(("et_demie")); + } else if (min == 45U) { + words.push_back(("moins_le_quart")); + if (hour == 23U) + hour = 0U; + else + hour++; + } + + return words; +} + +std::vector CTimeServerThread::sendTimeNlNL(unsigned int hour, unsigned int min) +{ + std::vector words; + + words.push_back(("Het_is")); + + if (min == 15U) { + words.push_back(("kwart_over")); + } else if (min == 30U) { + words.push_back(("half")); + if (hour == 23U) + hour = 0U; + else + hour++; + } else if (min == 45U) { + words.push_back(("kwart_voor")); + if (hour == 23U) + hour = 0U; + else + hour++; + } + + if (hour == 0U || hour == 12U) { + words.push_back(("twaalf")); + } else if (hour == 1U || hour == 13U) { + words.push_back(("een")); + } else if (hour == 2U || hour == 14U) { + words.push_back(("twee")); + } else if (hour == 3U || hour == 15U) { + words.push_back(("drie")); + } else if (hour == 4U || hour == 16U) { + words.push_back(("vier")); + } else if (hour == 5U || hour == 17U) { + words.push_back(("vijf")); + } else if (hour == 6U || hour == 18U) { + words.push_back(("zes")); + } else if (hour == 7U || hour == 19U) { + words.push_back(("zeven")); + } else if (hour == 8U || hour == 20U) { + words.push_back(("acht")); + } else if (hour == 9U || hour == 21U) { + words.push_back(("negen")); + } else if (hour == 10U || hour == 22U) { + words.push_back(("tien")); + } else if (hour == 11U || hour == 23U) { + words.push_back(("elf")); + } + + if (min == 0U) + words.push_back(("uur")); + + return words; +} + +std::vector CTimeServerThread::sendTimeSeSE(unsigned int hour, unsigned int min) +{ + std::vector words; + + words.push_back(("Klockan_ar")); + + if (min == 15U) { + words.push_back(("kvart_over")); + } else if (min == 30U) { + words.push_back(("halv")); + if (hour == 23U) + hour = 0U; + else + hour++; + } else if (min == 45U) { + words.push_back(("kvart_i")); + if (hour == 23U) + hour = 0U; + else + hour++; + } + + if (hour == 0U || hour == 12U) { + words.push_back(("tolv")); + } else if (hour == 1U || hour == 13U) { + words.push_back(("ett")); + } else if (hour == 2U || hour == 14U) { + words.push_back(("tva")); + } else if (hour == 3U || hour == 15U) { + words.push_back(("tre")); + } else if (hour == 4U || hour == 16U) { + words.push_back(("fyra")); + } else if (hour == 5U || hour == 17U) { + words.push_back(("fem")); + } else if (hour == 6U || hour == 18U) { + words.push_back(("sex")); + } else if (hour == 7U || hour == 19U) { + words.push_back(("sju")); + } else if (hour == 8U || hour == 20U) { + words.push_back(("atta")); + } else if (hour == 9U || hour == 21U) { + words.push_back(("nio")); + } else if (hour == 10U || hour == 22U) { + words.push_back(("tio")); + } else if (hour == 11U || hour == 23U) { + words.push_back(("elva")); + } + + return words; +} + +std::vector CTimeServerThread::sendTimeEsES(unsigned int hour, unsigned int min) +{ + std::vector words; + + if (min == 45U) { + hour++; + if (hour == 24U) + hour = 0U; + } + + if (hour == 1U) + words.push_back(("Es_la")); + else if (hour == 0U || hour == 12U) + words.push_back(("Es")); + else + words.push_back(("Son_las")); + + if (hour == 0U) { + words.push_back(("medianoche")); + } else if (hour == 1U || hour == 13U) { + words.push_back(("una")); + } else if (hour == 2U || hour == 14U) { + words.push_back(("dos")); + } else if (hour == 3U || hour == 15U) { + words.push_back(("tres")); + } else if (hour == 4U || hour == 16U) { + words.push_back(("cuarto")); + } else if (hour == 5U || hour == 17U) { + words.push_back(("cinco")); + } else if (hour == 6U || hour == 18U) { + words.push_back(("seis")); + } else if (hour == 7U || hour == 19U) { + words.push_back(("siete")); + } else if (hour == 8U || hour == 20U) { + words.push_back(("ocho")); + } else if (hour == 9U || hour == 21U) { + words.push_back(("nueve")); + } else if (hour == 10U || hour == 22U) { + words.push_back(("diez")); + } else if (hour == 11U || hour == 23U) { + words.push_back(("once")); + } else { + words.push_back(("mediodia")); + } + + if (min == 15U) + words.push_back(("y_cuarto")); + else if (min == 30U) + words.push_back(("y_media")); + else if (min == 45U) + words.push_back(("menos_cuarto")); + + if (hour > 0U && hour < 12U) + words.push_back(("de_la_manana")); + else if (hour > 12U && hour < 19U) + words.push_back(("de_la_tarde")); + else if (hour >= 19U && hour <= 23U) + words.push_back(("de_la_noche")); + + return words; +} + +std::vector CTimeServerThread::sendTimeNoNO(unsigned int hour, unsigned int min) +{ + std::vector words; + + words.push_back(("Klokken_er")); + + if (min == 15U) { + words.push_back(("kvart_over")); + } else if (min == 30U) { + words.push_back(("halv")); + if (hour == 23U) + hour = 0U; + else + hour++; + } else if (min == 45U) { + words.push_back(("kvart_pa")); + if (hour == 23U) + hour = 0U; + else + hour++; + } + + if (hour == 0U || hour == 12U) { + words.push_back(("tolv")); + } else if (hour == 1U || hour == 13U) { + words.push_back(("ett")); + } else if (hour == 2U || hour == 14U) { + words.push_back(("to")); + } else if (hour == 3U || hour == 15U) { + words.push_back(("tre")); + } else if (hour == 4U || hour == 16U) { + words.push_back(("fire")); + } else if (hour == 5U || hour == 17U) { + words.push_back(("fem")); + } else if (hour == 6U || hour == 18U) { + words.push_back(("seks")); + } else if (hour == 7U || hour == 19U) { + words.push_back(("sju")); + } else if (hour == 8U || hour == 20U) { + words.push_back(("atte")); + } else if (hour == 9U || hour == 21U) { + words.push_back(("ni")); + } else if (hour == 10U || hour == 22U) { + words.push_back(("ti")); + } else if (hour == 11U || hour == 23U) { + words.push_back(("elleve")); + } + + return words; +} + +std::vector CTimeServerThread::sendTimePtPT(unsigned int hour, unsigned int min) +{ + std::vector words; + + if (min == 45U) { + hour++; + if (hour == 24U) + hour = 0U; + } + + if (hour == 1U || hour == 13U) + words.push_back(("E")); + else if (hour == 0U || hour == 12U) + words.push_back(("Es")); + else + words.push_back(("Sao")); + + if (min == 45U) { + if (hour == 0U || hour == 12U || hour == 1U || hour == 13U) + words.push_back(("quinze_para")); + else + words.push_back(("quinze_para_as")); + } + + if (hour == 0U) { + words.push_back(("meia-noite")); + } else if (hour == 1U || hour == 13U) { + words.push_back(("uma")); + } else if (hour == 2U || hour == 14U) { + words.push_back(("duas")); + } else if (hour == 3U || hour == 15U) { + words.push_back(("tres")); + } else if (hour == 4U || hour == 16U) { + words.push_back(("quatro")); + } else if (hour == 5U || hour == 17U) { + words.push_back(("cinco")); + } else if (hour == 6U || hour == 18U) { + words.push_back(("seis")); + } else if (hour == 7U || hour == 19U) { + words.push_back(("sete")); + } else if (hour == 8U || hour == 20U) { + words.push_back(("oito")); + } else if (hour == 9U || hour == 21U) { + words.push_back(("nove")); + } else if (hour == 10U || hour == 22U) { + words.push_back(("dez")); + } else if (hour == 11U || hour == 23U) { + words.push_back(("onze")); + } else { + words.push_back(("meio-dia")); + } + + if (min == 0U) + words.push_back(("hora")); + else if (min == 15U) + words.push_back(("e_quinze")); + else if (min == 30U) + words.push_back(("e_meia")); + + return words; +} + +bool CTimeServerThread::loadAMBE() +{ + std::string ambeFileName; + std::string indxFileName; + + switch (m_language) { + case LANG_ENGLISH_US_1: + case LANG_ENGLISH_US_2: + ambeFileName = "TIME_en_US.ambe"; + indxFileName = "TIME_en_US.indx"; + break; + case LANG_DEUTSCH_1: + case LANG_DEUTSCH_2: + ambeFileName = "TIME_de_DE.ambe"; + indxFileName = "TIME_de_DE.indx"; + break; + case LANG_FRANCAIS: + ambeFileName = "TIME_fr_FR.ambe"; + indxFileName = "TIME_fr_FR.indx"; + break; + case LANG_NEDERLANDS: + ambeFileName = "TIME_nl_NL.ambe"; + indxFileName = "TIME_nl_NL.indx"; + break; + case LANG_SVENSKA: + ambeFileName = "TIME_se_SE.ambe"; + indxFileName = "TIME_se_SE.indx"; + break; + case LANG_ESPANOL: + ambeFileName = "TIME_es_ES.ambe"; + indxFileName = "TIME_es_ES.indx"; + break; + case LANG_NORSK: + ambeFileName = "TIME_no_NO.ambe"; + indxFileName = "TIME_no_NO.indx"; + break; + case LANG_PORTUGUES: + ambeFileName = "TIME_pt_PT.ambe"; + indxFileName = "TIME_pt_PT.indx"; + break; + default: + ambeFileName = "TIME_en_GB.ambe"; + indxFileName = "TIME_en_GB.indx"; + break; + } + + bool ret = readAMBE(m_dataPath, ambeFileName); + if (!ret) { + delete[] m_ambe; + m_ambe = NULL; + return false; + } + + ret = readIndex(m_dataPath, indxFileName); + if (!ret) { + delete[] m_ambe; + m_ambe = NULL; + return false; + } + + return true; +} + +bool CTimeServerThread::readAMBE(const std::string& dir, const std::string& name) +{ + std::string fileName = dir + "/" + name; + struct stat sbuf; + + if (stat(fileName.c_str(), &sbuf)) { + CLog::logInfo("File %s not readable\n", fileName.c_str()); + fileName.append("/data/"); + fileName += name; + if (stat(fileName.c_str(), &sbuf)) { + CLog::logInfo("File %s not readable\n", fileName.c_str()); + return false; + } + } + unsigned int fsize = sbuf.st_size; + + FILE *file = fopen(fileName.c_str(), "rb"); + if (NULL == file) { + CLog::logInfo("Cannot open %s for reading\n", fileName.c_str()); + return false; + } + + CLog::logInfo("Reading %s\n", fileName.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", fileName.c_str()); + fclose(file); + return false; + } + + if (memcmp(buffer, "AMBE", 4)) { + CLog::logError("Invalid header from %s\n", fileName.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", fileName.c_str()); + fclose(file); + delete[] m_ambe; + m_ambe = NULL; + return false; + } + + fclose(file); + + return true; +} + +bool CTimeServerThread::readIndex(const std::string& dir, const std::string& name) +{ + std::string fileName = dir + "/" + name; + struct stat sbuf; + + if (stat(fileName.c_str(), &sbuf)) { + CLog::logInfo("File %s not readable\n", fileName.c_str()); + fileName.append("/data/"); + fileName += name; + if (stat(fileName.c_str(), &sbuf)) { + CLog::logInfo("File %s not readable\n", fileName.c_str()); + return false; + } + } + + FILE *file = fopen(fileName.c_str(), "r"); + if (NULL == file) { + CLog::logInfo("Cannot open %s for reading\n", fileName.c_str()); + return false; + } + + // Add a silence entry at the beginning + m_index[" "] = new CIndexRecord(" ", 0, SILENCE_LENGTH); + + CLog::logInfo("Reading %s\n", fileName.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 CTimeServerThread::lookup(const std::string &id) +{ + CIndexRecord* info = m_index[id]; + if (info == NULL) { + // wxLogError(("Cannot find the AMBE index for *%s*"), id.c_str()); + return false; + } + + unsigned int start = info->getStart(); + unsigned int length = info->getLength(); + + SLOW_DATA slowData = SD_TEXT; + + for (unsigned int i = 0U; i < length; i++) { + unsigned char* dataIn = m_ambe + (start + i) * VOICE_FRAME_LENGTH_BYTES; + + CAMBEData* dataOut = new CAMBEData; + dataOut->setDestination(m_address, G2_DV_PORT); + dataOut->setSeq(m_seqNo); + + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + ::memcpy(buffer + 0U, dataIn, VOICE_FRAME_LENGTH_BYTES); + + // Insert sync bytes when the sequence number is zero, slow data otherwise + if (m_seqNo == 0U) { + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + m_encoder.sync(); + + switch (slowData) { + case SD_HEADER: + slowData = SD_TEXT; + break; + case SD_TEXT: + slowData = SD_HEADER; + break; + } + } else { + switch (slowData) { + case SD_HEADER: + m_encoder.getHeaderData(buffer + VOICE_FRAME_LENGTH_BYTES); + break; + case SD_TEXT: + m_encoder.getTextData(buffer + VOICE_FRAME_LENGTH_BYTES); + break; + } + } + + dataOut->setData(buffer, DV_FRAME_LENGTH_BYTES); + + m_seqNo++; + if (m_seqNo == 21U) + m_seqNo = 0U; + + m_data[m_in] = dataOut; + m_in++; + } + + return true; +} + +void CTimeServerThread::end() +{ + CAMBEData* dataOut = new CAMBEData; + dataOut->setData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES); + dataOut->setDestination(m_address, G2_DV_PORT); + dataOut->setSeq(m_seqNo); + dataOut->setEnd(true); + + m_data[m_in] = dataOut; + m_in++; +} + +bool CTimeServerThread::send(const std::vector &words, unsigned int hour, unsigned int min) +{ + unsigned int idA = CHeaderData::createId(); + unsigned int idB = CHeaderData::createId(); + unsigned int idC = CHeaderData::createId(); + unsigned int idD = CHeaderData::createId(); + unsigned int idE = CHeaderData::createId(); + + CHeaderData header; + header.setMyCall1(m_callsign); + header.setRptCall1(m_callsignG); + header.setRptCall2(m_callsign); // Just for the slow data header + header.setYourCall("CQCQCQ "); + header.setDestination(m_address, G2_DV_PORT); + + std::string slowData; + switch (m_language) { + case LANG_DEUTSCH_1: + case LANG_DEUTSCH_2: + header.setMyCall2(("ZEIT")); + slowData = CStringUtils::string_format(("Es ist %02u:%02u Uhr"), hour, min); + break; + case LANG_FRANCAIS: + header.setMyCall2(("TIME")); + slowData = CStringUtils::string_format(("Il est %02u:%02u"), hour, min); + break; + case LANG_NEDERLANDS: + header.setMyCall2(("TIJD")); + slowData = CStringUtils::string_format(("Het is %02u:%02u"), hour, min); + break; + case LANG_SVENSKA: + header.setMyCall2(("TID ")); + slowData = CStringUtils::string_format(("Klockan ar %02u:%02u"), hour, min); + break; + case LANG_ENGLISH_US_1: + case LANG_ENGLISH_UK_1: + header.setMyCall2(("TIME")); + if (hour == 0U) + slowData = CStringUtils::string_format(("It is 12:%02u AM"), min); + else if (hour == 12U) + slowData = CStringUtils::string_format(("It is 12:%02u PM"), min); + else if (hour > 12U) + slowData = CStringUtils::string_format(("It is %02u:%02u PM"), hour - 12U, min); + else + slowData = CStringUtils::string_format(("It is %02u:%02u AM"), hour, min); + break; + case LANG_ESPANOL: + header.setMyCall2(("HORA")); + if (hour == 1U) + slowData = CStringUtils::string_format(("Es la %02u:%02u"), hour, min); + else + slowData = CStringUtils::string_format(("Son las %02u:%02u"), hour, min); + break; + case LANG_NORSK: + header.setMyCall2(("TID ")); + slowData = CStringUtils::string_format(("Klokken er %02u:%02u"), hour, min); + break; + case LANG_PORTUGUES: + header.setMyCall2(("HORA")); + if (hour == 1U) + slowData = CStringUtils::string_format(("E %02u:%02u"), hour, min); + else + slowData = CStringUtils::string_format(("Sao %02u:%02u"), hour, min); + break; + default: + header.setMyCall2(("TIME")); + slowData = CStringUtils::string_format(("It is %02u:%02u"), hour, min); + break; + } + + m_encoder.setHeaderData(header); + m_encoder.setTextData(slowData); + + m_in = 0U; + + if (m_format != FORMAT_TEXT_TIME) { + std::string text = words.at(0U); + for (unsigned int i = 1U; i < words.size(); i++) { + text.push_back(' '); + text += words.at(i); + } + + boost::replace_all(text, "_", " "); + CLog::logInfo(("Sending voice \"%s\", sending text \"%s\""), text.c_str(), slowData.c_str()); + + m_seqNo = 0U; + + // Build the audio + lookup((" ")); + lookup((" ")); + lookup((" ")); + lookup((" ")); + + for (unsigned int i = 0U; i < words.size(); i++) + lookup(words.at(i)); + + lookup((" ")); + lookup((" ")); + lookup((" ")); + lookup((" ")); + + end(); + } else { + CLog::logInfo(("Sending text \"%s\""), slowData.c_str()); + + for (unsigned int i = 0U; i < 21U; i++) { + CAMBEData* dataOut = new CAMBEData; + dataOut->setDestination(m_address, G2_DV_PORT); + dataOut->setSeq(i); + + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + ::memcpy(buffer + 0U, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); + + // Insert sync bytes when the sequence number is zero, slow data otherwise + if (i == 0U) { + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + m_encoder.sync(); + } else { + m_encoder.getTextData(buffer + VOICE_FRAME_LENGTH_BYTES); + } + + dataOut->setData(buffer, DV_FRAME_LENGTH_BYTES); + + m_data[m_in] = dataOut; + m_in++; + } + + CAMBEData* dataOut = new CAMBEData; + dataOut->setData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES); + dataOut->setDestination(m_address, G2_DV_PORT); + dataOut->setSeq(0U); + dataOut->setEnd(true); + + m_data[m_in] = dataOut; + m_in++; + } + + if (m_in == 0U) { + CLog::logWarning(("Not sending, no audio files loaded")); + return false; + } + + if (!m_callsignA.empty()) { + header.setRptCall2(m_callsignA); + header.setId(idA); + sendHeader(header); + } + + if (!m_callsignB.empty()) { + header.setRptCall2(m_callsignB); + header.setId(idB); + sendHeader(header); + } + + if (!m_callsignC.empty()) { + header.setRptCall2(m_callsignC); + header.setId(idC); + sendHeader(header); + } + + if (!m_callsignD.empty()) { + header.setRptCall2(m_callsignD); + header.setId(idD); + sendHeader(header); + } + + if (!m_callsignE.empty()) { + header.setRptCall2(m_callsignE); + header.setId(idE); + sendHeader(header); + } + + unsigned int out = 0U; + + auto start = std::chrono::high_resolution_clock::now(); + + for (;;) { + unsigned int needed = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start).count(); + needed /= DSTAR_FRAME_TIME_MS; + + while (out < needed) { + CAMBEData* data = m_data[out]; + m_data[out] = NULL; + out++; + + if (!m_callsignA.empty()) { + data->setId(idA); + sendData(*data); + } + + if (!m_callsignB.empty()) { + data->setId(idB); + sendData(*data); + } + + if (!m_callsignC.empty()) { + data->setId(idC); + sendData(*data); + } + + if (!m_callsignD.empty()) { + data->setId(idD); + sendData(*data); + } + + if (!m_callsignE.empty()) { + data->setId(idE); + sendData(*data); + } + + delete data; + + if (m_in == out) + return true; + } + + Sleep(10UL); + } +} + +bool CTimeServerThread::sendHeader(const CHeaderData &header) +{ + unsigned char buffer[60U]; + unsigned int length = header.getG2Data(buffer, 60U, true); + +#if defined(DUMP_TX) + CUtils::dump(("Sending Header"), buffer, length); + return true; +#else + for (unsigned int i = 0U; i < 5U; i++) { + bool res = m_socket.write(buffer, length, header.getYourAddress(), header.getYourPort()); + if (!res) + return false; + } + + return true; +#endif +} + +bool CTimeServerThread::sendData(const CAMBEData& data) +{ + unsigned char buffer[40U]; + unsigned int length = data.getG2Data(buffer, 40U); + +#if defined(DUMP_TX) + CUtils::dump(("Sending Data"), buffer, length); + return true; +#else + return m_socket.write(buffer, length, data.getYourAddress(), data.getYourPort()); +#endif +} diff --git a/DGWTimeServer/TimeServerThread.h b/DGWTimeServer/TimeServerThread.h new file mode 100644 index 0000000..ee3f967 --- /dev/null +++ b/DGWTimeServer/TimeServerThread.h @@ -0,0 +1,122 @@ +/* + * 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 + +#include "SlowDataEncoder.h" +#include "UDPReaderWriter.h" +#include "TimeServerDefs.h" +#include "HeaderData.h" +#include "AMBEData.h" +#include "Thread.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 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: + CUDPReaderWriter m_socket; + std::string m_callsign; + std::string m_callsignA; + std::string m_callsignB; + std::string m_callsignC; + std::string m_callsignD; + std::string m_callsignE; + std::string m_callsignG; + in_addr m_address; + LANGUAGE m_language; + FORMAT m_format; + INTERVAL m_interval; + unsigned char* m_ambe; + unsigned int m_ambeLength; + std::unordered_map m_index; + unsigned int m_seqNo; + unsigned int m_in; + CSlowDataEncoder m_encoder; + CAMBEData** m_data; + bool m_killed; + std::string m_dataPath; + + void sendTime(unsigned int hour, unsigned int min); + + std::vector sendTimeEnGB1(unsigned int hour, unsigned int min); + std::vector sendTimeEnGB2(unsigned int hour, unsigned int min); + std::vector sendTimeEnUS1(unsigned int hour, unsigned int min); + std::vector sendTimeEnUS2(unsigned int hour, unsigned int min); + std::vector sendTimeDeDE1(unsigned int hour, unsigned int min); + std::vector sendTimeDeDE2(unsigned int hour, unsigned int min); + std::vector sendTimeFrFR(unsigned int hour, unsigned int min); + std::vector sendTimeNlNL(unsigned int hour, unsigned int min); + std::vector sendTimeSeSE(unsigned int hour, unsigned int min); + std::vector sendTimeEsES(unsigned int hour, unsigned int min); + std::vector sendTimeNoNO(unsigned int hour, unsigned int min); + std::vector sendTimePtPT(unsigned int hour, unsigned int min); + + bool send(const std::vector& words, unsigned int hour, unsigned int min); + bool sendHeader(const CHeaderData& header); + bool sendData(const CAMBEData& data); + + bool loadAMBE(); + bool readAMBE(const std::string& dir, const std::string& name); + bool readIndex(const std::string& dir, const std::string& name); + + bool lookup(const std::string& id); + void end(); +}; diff --git a/DGWTimeServer/example.cfg b/DGWTimeServer/example.cfg index cc6b0cf..27a40df 100644 --- a/DGWTimeServer/example.cfg +++ b/DGWTimeServer/example.cfg @@ -5,6 +5,9 @@ format= # possible values are voice, text, voiceandtext, defaults to voice a 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 From 0161d4879f4dcb7d2e271e5e5f5438043a3e56f2 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 16:58:20 +0100 Subject: [PATCH 10/40] #20 add systemd stuff for time server --- DGWTimeServer/Makefile | 14 ++++++++++++++ debian/dgwtimeserver.service | 13 +++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 debian/dgwtimeserver.service diff --git a/DGWTimeServer/Makefile b/DGWTimeServer/Makefile index 4ea197d..1212731 100644 --- a/DGWTimeServer/Makefile +++ b/DGWTimeServer/Makefile @@ -17,6 +17,20 @@ 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)dstargateway.cfg and start the daemon with 'systemctl start dgwtimeserver.service'" + @echo "\n" + ../BaseCommon/BaseCommon.a: ../DStarBase/DStarBase.a: ../VersionInfo/GitVersion.h: diff --git a/debian/dgwtimeserver.service b/debian/dgwtimeserver.service new file mode 100644 index 0000000..5c00484 --- /dev/null +++ b/debian/dgwtimeserver.service @@ -0,0 +1,13 @@ +[Unit] +Description=D-STAR Gateway Daemon +After=network.target,network-online.target +Wants=network-online.target + +[Service] +User=dstar +Type=simple +ExecStart=/usr/local/bin/dgwtimeserver %CFG_DIR%/dstargateway.cfg +Restart=on-failure + +[Install] +WantedBy=multi-user.target From c290a0f50bbf44e2138729b0b816c03536b1646d Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 17:30:27 +0100 Subject: [PATCH 11/40] #20 fix copy paste type --- debian/dgwtimeserver.service | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/debian/dgwtimeserver.service b/debian/dgwtimeserver.service index 5c00484..6767c9c 100644 --- a/debian/dgwtimeserver.service +++ b/debian/dgwtimeserver.service @@ -1,12 +1,12 @@ [Unit] -Description=D-STAR Gateway Daemon +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%/dstargateway.cfg +ExecStart=/usr/local/bin/dgwtimeserver %CFG_DIR%/dgwtimeserver.cfg Restart=on-failure [Install] From 8f5f878d23c251e0266a90098cbb3776cc8ffe12 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 18:04:20 +0100 Subject: [PATCH 12/40] #20 simplify slow data --- DGWTimeServer/TimeServerThread.cpp | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/DGWTimeServer/TimeServerThread.cpp b/DGWTimeServer/TimeServerThread.cpp index 2bfcccc..8150858 100644 --- a/DGWTimeServer/TimeServerThread.cpp +++ b/DGWTimeServer/TimeServerThread.cpp @@ -36,11 +36,6 @@ const unsigned int MAX_FRAMES = 60U * DSTAR_FRAMES_PER_SEC; const unsigned int SILENCE_LENGTH = 10U; -enum SLOW_DATA { - SD_HEADER, - SD_TEXT -}; - CTimeServerThread::CTimeServerThread() : CThread("Time Server"), m_socket("", 0U), @@ -113,6 +108,9 @@ void * CTimeServerThread::Entry() unsigned int hour = tm->tm_hour; unsigned int min = tm->tm_min; + if (min != lastMin) + sendTime(15, 45); + if (min != lastMin) { if (m_interval == INTERVAL_15MINS && (min == 0U || min == 15U || min == 30U || min == 45U)) sendTime(hour, min); @@ -1144,8 +1142,6 @@ bool CTimeServerThread::lookup(const std::string &id) unsigned int start = info->getStart(); unsigned int length = info->getLength(); - SLOW_DATA slowData = SD_TEXT; - for (unsigned int i = 0U; i < length; i++) { unsigned char* dataIn = m_ambe + (start + i) * VOICE_FRAME_LENGTH_BYTES; @@ -1161,23 +1157,8 @@ bool CTimeServerThread::lookup(const std::string &id) ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); m_encoder.sync(); - switch (slowData) { - case SD_HEADER: - slowData = SD_TEXT; - break; - case SD_TEXT: - slowData = SD_HEADER; - break; - } } else { - switch (slowData) { - case SD_HEADER: - m_encoder.getHeaderData(buffer + VOICE_FRAME_LENGTH_BYTES); - break; - case SD_TEXT: - m_encoder.getTextData(buffer + VOICE_FRAME_LENGTH_BYTES); - break; - } + m_encoder.getInterleavedData(buffer + VOICE_FRAME_LENGTH_BYTES); } dataOut->setData(buffer, DV_FRAME_LENGTH_BYTES); From 318d1b7c621b297c7b5f942852600543bfc5f121 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 18:10:15 +0100 Subject: [PATCH 13/40] #20 add banner and signal handlers --- DGWTimeServer/DGWTimeServerApp.cpp | 76 ++++++++++++++++++++++++++++++ DGWTimeServer/DGWTimeServerApp.h | 8 +++- DGWTimeServer/TimeServerDefs.h | 2 +- 3 files changed, 83 insertions(+), 3 deletions(-) diff --git a/DGWTimeServer/DGWTimeServerApp.cpp b/DGWTimeServer/DGWTimeServerApp.cpp index 5088ee2..64fe964 100644 --- a/DGWTimeServer/DGWTimeServerApp.cpp +++ b/DGWTimeServer/DGWTimeServerApp.cpp @@ -20,17 +20,42 @@ #include #include #include +#include +#ifdef DEBUG_DSTARGW +#include +#endif #include "DGWTimeServerApp.h" +#include "Version.h" +#include "Log.h" + +CDGWTimeServerApp * CDGWTimeServerApp::g_app = nullptr; +const std::string BANNER_1 = CStringUtils::string_format("%s %s Copyright (C) %s\n", APPLICATION_NAME.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; + } + std::string configfile(argv[1]); CTimeServerConfig config(configfile); if(!config.load()) @@ -93,3 +118,54 @@ bool CDGWTimeServerApp::createThread() return ret; } + +void CDGWTimeServerApp::sigHandler(int sig) +{ + CLog::logInfo("Caught signal : %s, shutting down gateway", 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); +} \ No newline at end of file diff --git a/DGWTimeServer/DGWTimeServerApp.h b/DGWTimeServer/DGWTimeServerApp.h index ffcecdf..a25cdaf 100644 --- a/DGWTimeServer/DGWTimeServerApp.h +++ b/DGWTimeServer/DGWTimeServerApp.h @@ -25,8 +25,6 @@ class CDGWTimeServerApp { -private: - /* data */ public: CDGWTimeServerApp(const CTimeServerConfig * config); ~CDGWTimeServerApp(); @@ -34,9 +32,15 @@ public: 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; }; diff --git a/DGWTimeServer/TimeServerDefs.h b/DGWTimeServer/TimeServerDefs.h index 6b5ca4b..8917675 100644 --- a/DGWTimeServer/TimeServerDefs.h +++ b/DGWTimeServer/TimeServerDefs.h @@ -19,7 +19,7 @@ #pragma once -const std::string APPLICATION_NAME("DStarGateway time Server"); +const std::string APPLICATION_NAME("DStarGateway Time Server"); enum LANGUAGE { LANG_ENGLISH_UK_1, From d22ae5f02eb33baa7e73a99c7eecd48065ee2183 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 18:13:16 +0100 Subject: [PATCH 14/40] #20 remove test code --- DGWTimeServer/TimeServerThread.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DGWTimeServer/TimeServerThread.cpp b/DGWTimeServer/TimeServerThread.cpp index 8150858..253edfb 100644 --- a/DGWTimeServer/TimeServerThread.cpp +++ b/DGWTimeServer/TimeServerThread.cpp @@ -108,8 +108,8 @@ void * CTimeServerThread::Entry() unsigned int hour = tm->tm_hour; unsigned int min = tm->tm_min; - if (min != lastMin) - sendTime(15, 45); + // if (min != lastMin) + // sendTime(15, 45); if (min != lastMin) { if (m_interval == INTERVAL_15MINS && (min == 0U || min == 15U || min == 30U || min == 45U)) From 31d11136ec0e4414612d4d5a78388b1235deb031 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 18:14:56 +0100 Subject: [PATCH 15/40] #20 fix copy paste typo --- DGWTimeServer/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DGWTimeServer/Makefile b/DGWTimeServer/Makefile index 1212731..87b80ab 100644 --- a/DGWTimeServer/Makefile +++ b/DGWTimeServer/Makefile @@ -28,7 +28,7 @@ install: dgwtimeserver systemctl enable dgwtimeserver.service @systemctl daemon-reload @echo "\n" - @echo "DGWTimeserver Install complete, edit $(CFG_DIR)dstargateway.cfg and start the daemon with 'systemctl start dgwtimeserver.service'" + @echo "DGWTimeserver Install complete, edit $(CFG_DIR)dgwtimeserver.cfg and start the daemon with 'systemctl start dgwtimeserver.service'" @echo "\n" ../BaseCommon/BaseCommon.a: From 5798c187dc534e5d36ad6ecbf07a3703d6a9286d Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 18:20:36 +0100 Subject: [PATCH 16/40] #20 set g_app to allow proper terminating --- DGWTimeServer/DGWTimeServerApp.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/DGWTimeServer/DGWTimeServerApp.cpp b/DGWTimeServer/DGWTimeServerApp.cpp index 64fe964..245c79a 100644 --- a/DGWTimeServer/DGWTimeServerApp.cpp +++ b/DGWTimeServer/DGWTimeServerApp.cpp @@ -74,6 +74,7 @@ int main(int argc, char * argv[]) CDGWTimeServerApp::CDGWTimeServerApp(const CTimeServerConfig * config) : m_config(config) { + g_app = this; assert(config != nullptr); } From b3e0b16e6cb4d5bd47b24da3d3a6bd7d313d43f2 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 18:22:38 +0100 Subject: [PATCH 17/40] #20 add version number in banner --- DGWTimeServer/DGWTimeServerApp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DGWTimeServer/DGWTimeServerApp.cpp b/DGWTimeServer/DGWTimeServerApp.cpp index 245c79a..9b26922 100644 --- a/DGWTimeServer/DGWTimeServerApp.cpp +++ b/DGWTimeServer/DGWTimeServerApp.cpp @@ -30,7 +30,7 @@ #include "Log.h" CDGWTimeServerApp * CDGWTimeServerApp::g_app = nullptr; -const std::string BANNER_1 = CStringUtils::string_format("%s %s Copyright (C) %s\n", APPLICATION_NAME.c_str(), VENDOR_NAME.c_str()); +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"; From c84275802ed092bd936f9c60e4ad7d18e98355e1 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 18:29:26 +0100 Subject: [PATCH 18/40] #20 check band --- DGWTimeServer/TimeServerConfig.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/DGWTimeServer/TimeServerConfig.cpp b/DGWTimeServer/TimeServerConfig.cpp index 5d4b067..dd03332 100644 --- a/DGWTimeServer/TimeServerConfig.cpp +++ b/DGWTimeServer/TimeServerConfig.cpp @@ -81,6 +81,11 @@ bool CTimeServerConfig::loadRepeaters(const CConfig & cfg) 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) { From 20b2d8b912ef674b176333adfaf2bcc78495052d Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 18:40:19 +0100 Subject: [PATCH 19/40] #20 make it a daemon --- DGWTimeServer/DGWTimeServerApp.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/DGWTimeServer/DGWTimeServerApp.cpp b/DGWTimeServer/DGWTimeServerApp.cpp index 9b26922..7fb39d2 100644 --- a/DGWTimeServer/DGWTimeServerApp.cpp +++ b/DGWTimeServer/DGWTimeServerApp.cpp @@ -28,6 +28,7 @@ #include "DGWTimeServerApp.h" #include "Version.h" #include "Log.h" +#include "Daemon.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()); @@ -61,6 +62,27 @@ int main(int argc, char * argv[]) if(!config.load()) return 1; + 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; + } + } + CDGWTimeServerApp app(&config); if(!app.init()) From c0a49a4cf3036303af0b7bc22cd39a140353717d Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 18:42:07 +0100 Subject: [PATCH 20/40] #20 typo --- DGWTimeServer/example.cfg | 2 +- DStarGateway/example.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DGWTimeServer/example.cfg b/DGWTimeServer/example.cfg index 27a40df..a643d60 100644 --- a/DGWTimeServer/example.cfg +++ b/DGWTimeServer/example.cfg @@ -27,7 +27,7 @@ band= # Module letter of the repeater # 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 system handles the user under which the program is started. This is provided as convenience for people who might +# 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 diff --git a/DStarGateway/example.cfg b/DStarGateway/example.cfg index 0d349ad..f79e4df 100644 --- a/DStarGateway/example.cfg +++ b/DStarGateway/example.cfg @@ -178,7 +178,7 @@ port=4242 password=CHANGE_ME # If password is left blank, remote will be disabled regardless of the enabled field # 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 system handles the user under which the program is started. This is provided as convenience for people who might +# 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 From 7d12e567ac4dbd02bf6a4bb959724603fa28a295 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 19:10:09 +0100 Subject: [PATCH 21/40] #20 remove hard coded file root --- BaseCommon/LogFileTarget.cpp | 12 ++++++------ BaseCommon/LogFileTarget.h | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/BaseCommon/LogFileTarget.cpp b/BaseCommon/LogFileTarget.cpp index d99d09e..081222d 100644 --- a/BaseCommon/LogFileTarget.cpp +++ b/BaseCommon/LogFileTarget.cpp @@ -21,19 +21,19 @@ #include #include #include +#include #include "LogFileTarget.h" -#define LOG_FILE_ROOT "dstargateway" - -CLogFileTarget::CLogFileTarget(LOG_SEVERITY logLevel, const std::string & dir, bool rotate) : +CLogFileTarget::CLogFileTarget(LOG_SEVERITY logLevel, const std::string & dir, const std::string& fileRoot, bool rotate) : CLogTarget(logLevel), m_dir(dir), +m_fileRoot(fileRoot), m_rotate(rotate), m_file(), m_day(0) { - + assert(!fileRoot.empty()); } CLogFileTarget::~CLogFileTarget() @@ -53,7 +53,7 @@ void CLogFileTarget::printLogIntFixed(const std::string& msg) std::string filename(m_dir); if(filename[filename.length() - 1U] != '/') filename.push_back('/'); - filename.append(LOG_FILE_ROOT).append(".log"); + filename.append(m_fileRoot).append(".log"); m_file.open(filename, std::ios::app); if(m_file.is_open()) { @@ -80,7 +80,7 @@ void CLogFileTarget::printLogIntRotate(const std::string& msg) if(filename[filename.length() - 1U] != '/') filename.push_back('/'); char buf[64]; std::strftime(buf, 42, "-%Y-%m-%d", now_tm); - filename.append(LOG_FILE_ROOT).append(buf).append(".log"); + filename.append(m_fileRoot).append(buf).append(".log"); m_file.open(filename, std::ios::app); if(!m_file.is_open()) { std::cerr << "FAILED TO OPEN LOG FILE :" << filename; diff --git a/BaseCommon/LogFileTarget.h b/BaseCommon/LogFileTarget.h index 8df8c8b..10dc77d 100644 --- a/BaseCommon/LogFileTarget.h +++ b/BaseCommon/LogFileTarget.h @@ -26,7 +26,7 @@ class CLogFileTarget : public CLogTarget { public: - CLogFileTarget(LOG_SEVERITY logLevel, const std::string& directory, bool rotate); + CLogFileTarget(LOG_SEVERITY logLevel, const std::string& directory, const std::string& fileRoot, bool rotate); ~CLogFileTarget(); protected: @@ -37,6 +37,7 @@ private: void printLogIntFixed(const std::string& msg); std::string buildFileName(); std::string m_dir; + std::string m_fileRoot; bool m_rotate; std::fstream m_file; int m_day; From 263d5e83bfe2415e4b0d906a74ae0429b80b0baf Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 19:10:18 +0100 Subject: [PATCH 22/40] #20 add logging --- DGWTimeServer/DGWTimeServerApp.cpp | 21 ++++++++++---- DGWTimeServer/TimeServerConfig.cpp | 43 ++++++++++++++++++++++++++++- DGWTimeServer/TimeServerConfig.h | 12 ++++++++ DGWTimeServer/example.cfg | 6 ++++ DStarGateway/DStarGatewayApp.cpp | 4 +-- DStarGateway/DStarGatewayConfig.cpp | 34 +++++++++++------------ DStarGateway/DStarGatewayConfig.h | 8 +++--- 7 files changed, 98 insertions(+), 30 deletions(-) diff --git a/DGWTimeServer/DGWTimeServerApp.cpp b/DGWTimeServer/DGWTimeServerApp.cpp index 7fb39d2..eebb8d2 100644 --- a/DGWTimeServer/DGWTimeServerApp.cpp +++ b/DGWTimeServer/DGWTimeServerApp.cpp @@ -29,6 +29,8 @@ #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()); @@ -57,11 +59,13 @@ int main(int argc, char * argv[]) 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) { @@ -83,14 +87,21 @@ int main(int argc, char * argv[]) } } - CDGWTimeServerApp app(&config); + // 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)); - if(!app.init()) + // Start the app + CDGWTimeServerApp app(&config); + if(app.init()) { + app.run(); return 0; + } - app.run(); - - return 0; + return 1; } CDGWTimeServerApp::CDGWTimeServerApp(const CTimeServerConfig * config) : diff --git a/DGWTimeServer/TimeServerConfig.cpp b/DGWTimeServer/TimeServerConfig.cpp index dd03332..d2c4fab 100644 --- a/DGWTimeServer/TimeServerConfig.cpp +++ b/DGWTimeServer/TimeServerConfig.cpp @@ -20,7 +20,6 @@ #include #include "TimeServerConfig.h" -#include "Log.h" #include "StringUtils.h" CTimeServerConfig::CTimeServerConfig(const std::string &pathname) : @@ -50,6 +49,7 @@ bool CTimeServerConfig::load() ret = loadRepeaters(cfg) && ret; ret = loadDaemon(cfg) && ret; ret = loadPaths(cfg) && ret; + ret = loadLog(cfg) && ret; } return ret; @@ -150,6 +150,42 @@ bool CTimeServerConfig::loadPaths(const CConfig & cfg) 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; @@ -174,3 +210,8 @@ void CTimeServerConfig::getPaths(TPaths& paths) const { paths = m_paths; } + +void CTimeServerConfig::getLog(TLog& log) const +{ + log = m_log; +} diff --git a/DGWTimeServer/TimeServerConfig.h b/DGWTimeServer/TimeServerConfig.h index a28d36d..7eeaacf 100644 --- a/DGWTimeServer/TimeServerConfig.h +++ b/DGWTimeServer/TimeServerConfig.h @@ -23,6 +23,7 @@ #include "Config.h" #include "TimeServerDefs.h" +#include "Log.h" typedef struct { std::string callsign; @@ -46,6 +47,14 @@ 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: @@ -58,6 +67,7 @@ public: 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); @@ -65,10 +75,12 @@ private: 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 m_repeaters; TTimeServer m_timeServer; TDaemon m_daemon; TPaths m_paths; + TLog m_log; }; diff --git a/DGWTimeServer/example.cfg b/DGWTimeServer/example.cfg index a643d60..94a823a 100644 --- a/DGWTimeServer/example.cfg +++ b/DGWTimeServer/example.cfg @@ -25,6 +25,12 @@ band= # Module letter of the repeater 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 diff --git a/DStarGateway/DStarGatewayApp.cpp b/DStarGateway/DStarGatewayApp.cpp index d0d5c52..634dd41 100644 --- a/DStarGateway/DStarGatewayApp.cpp +++ b/DStarGateway/DStarGatewayApp.cpp @@ -115,8 +115,8 @@ int main(int argc, char *argv[]) TLog logConf; config->getLog(logConf); CLog::finalise(); - if(logConf.m_displayLevel != LOG_NONE && !daemon.daemon) CLog::addTarget(new CLogConsoleTarget(logConf.m_displayLevel)); - if(logConf.m_fileLevel != LOG_NONE) CLog::addTarget(new CLogFileTarget(logConf.m_fileLevel, logConf.logDir, logConf.m_fileRotate)); + 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)); //write banner in log file if we are dameon if(daemon.daemon) { diff --git a/DStarGateway/DStarGatewayConfig.cpp b/DStarGateway/DStarGatewayConfig.cpp index 0afd547..77f8086 100644 --- a/DStarGateway/DStarGatewayConfig.cpp +++ b/DStarGateway/DStarGatewayConfig.cpp @@ -153,34 +153,32 @@ bool CDStarGatewayConfig::loadLog(const CConfig & cfg) m_log.logDir.push_back('/'); } - ret = cfg.getValue("log", "fileRoot", m_log.m_fileRoot, 0, 64, "dstargateway") && ret; - ret = cfg.getValue("log", "fileRotate", m_log.m_fileRotate, true) && ret; + ret = cfg.getValue("log", "fileRoot", m_log.fileRoot, 0, 64, "dstargateway") && 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.m_fileLevel = LOG_TRACE; - else if(levelStr == "debug") m_log.m_fileLevel = LOG_DEBUG; - else if(levelStr == "info") m_log.m_fileLevel = LOG_INFO; - else if(levelStr == "warning") m_log.m_fileLevel = LOG_WARNING; - else if(levelStr == "error") m_log.m_fileLevel = LOG_ERROR; - else if(levelStr == "fatal") m_log.m_fileLevel = LOG_FATAL; - else if(levelStr == "none") m_log.m_fileLevel = LOG_NONE; + 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.m_displayLevel = LOG_TRACE; - else if(levelStr == "debug") m_log.m_displayLevel = LOG_DEBUG; - else if(levelStr == "info") m_log.m_displayLevel = LOG_INFO; - else if(levelStr == "warning") m_log.m_displayLevel = LOG_WARNING; - else if(levelStr == "error") m_log.m_displayLevel = LOG_ERROR; - else if(levelStr == "fatal") m_log.m_displayLevel = LOG_FATAL; - else if(levelStr == "none") m_log.m_displayLevel = LOG_NONE; + 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; } - //TODO 20211226 check if directories are accessible - return ret; } diff --git a/DStarGateway/DStarGatewayConfig.h b/DStarGateway/DStarGatewayConfig.h index dec8ca5..1bfce6c 100644 --- a/DStarGateway/DStarGatewayConfig.h +++ b/DStarGateway/DStarGatewayConfig.h @@ -86,10 +86,10 @@ typedef struct { typedef struct { std::string logDir; - LOG_SEVERITY m_displayLevel; - LOG_SEVERITY m_fileLevel; - std::string m_fileRoot; - bool m_fileRotate; + LOG_SEVERITY displayLevel; + LOG_SEVERITY fileLevel; + std::string fileRoot; + bool fileRotate; } TLog; typedef struct { From a009e094ab6d55170a2f52ccc7f46ecdaf2f0bfe Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 19 Feb 2022 20:45:46 +0100 Subject: [PATCH 23/40] #20 one socket per repetaer (fixes time not sent on multi band repeaters) --- DGWTimeServer/TimeServerThread.cpp | 90 ++++++++++++++++++------------ DGWTimeServer/TimeServerThread.h | 7 +-- 2 files changed, 56 insertions(+), 41 deletions(-) diff --git a/DGWTimeServer/TimeServerThread.cpp b/DGWTimeServer/TimeServerThread.cpp index 253edfb..cfcfc47 100644 --- a/DGWTimeServer/TimeServerThread.cpp +++ b/DGWTimeServer/TimeServerThread.cpp @@ -38,15 +38,14 @@ const unsigned int SILENCE_LENGTH = 10U; CTimeServerThread::CTimeServerThread() : CThread("Time Server"), -m_socket("", 0U), m_callsign(), m_callsignA(), m_callsignB(), m_callsignC(), m_callsignD(), -m_callsignE(), m_callsignG(), m_address(), +m_addressStr(), m_language(LANG_ENGLISH_UK_1), m_format(FORMAT_VOICE_TIME), m_interval(INTERVAL_15MINS), @@ -81,7 +80,7 @@ CTimeServerThread::~CTimeServerThread() void * CTimeServerThread::Entry() { // Wait here until we have the essentials to run - while (!m_killed && m_address.s_addr == INADDR_NONE && m_callsignA.empty() && m_callsignB.empty() && m_callsignC.empty() && m_callsignD.empty() && m_callsignE.empty()) + while (!m_killed && m_address.s_addr == INADDR_NONE && m_callsignA.empty() && m_callsignB.empty() && m_callsignC.empty() && m_callsignD.empty()) Sleep(500UL); // 1/2 sec if (m_killed) @@ -127,8 +126,6 @@ void * CTimeServerThread::Entry() CLog::logInfo(("Stopping the Time Server thread")); - m_socket.close(); - return nullptr; } @@ -163,13 +160,10 @@ bool CTimeServerThread::setGateway(const std::string& callsign, const std::strin m_callsign.push_back(' '); + m_addressStr.assign(address); m_address = CUDPReaderWriter::lookup(address); m_dataPath.assign(dataPath); - bool ret = m_socket.open(); - if (!ret) - return false; - return true; } @@ -1192,7 +1186,6 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int unsigned int idB = CHeaderData::createId(); unsigned int idC = CHeaderData::createId(); unsigned int idD = CHeaderData::createId(); - unsigned int idE = CHeaderData::createId(); CHeaderData header; header.setMyCall1(m_callsign); @@ -1328,41 +1321,48 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int return false; } + CUDPReaderWriter * socketA = nullptr; + CUDPReaderWriter * socketB = nullptr; + CUDPReaderWriter * socketC = nullptr; + CUDPReaderWriter * socketD = nullptr; + if (!m_callsignA.empty()) { + socketA = new CUDPReaderWriter("", 0U); + socketA->open(); header.setRptCall2(m_callsignA); header.setId(idA); - sendHeader(header); + sendHeader(*socketA, header); } if (!m_callsignB.empty()) { + socketB = new CUDPReaderWriter("", 0U); + socketB->open(); header.setRptCall2(m_callsignB); header.setId(idB); - sendHeader(header); + sendHeader(*socketB, header); } if (!m_callsignC.empty()) { + socketC = new CUDPReaderWriter("", 0U); + socketC->open(); header.setRptCall2(m_callsignC); header.setId(idC); - sendHeader(header); + sendHeader(*socketC, header); } if (!m_callsignD.empty()) { + socketD = new CUDPReaderWriter("", 0U); + socketD->open(); header.setRptCall2(m_callsignD); header.setId(idD); - sendHeader(header); - } - - if (!m_callsignE.empty()) { - header.setRptCall2(m_callsignE); - header.setId(idE); - sendHeader(header); + sendHeader(*socketD, header); } + bool loop = true; unsigned int out = 0U; - auto start = std::chrono::high_resolution_clock::now(); - for (;;) { + for (;loop;) { unsigned int needed = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start).count(); needed /= DSTAR_FRAME_TIME_MS; @@ -1373,40 +1373,56 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int if (!m_callsignA.empty()) { data->setId(idA); - sendData(*data); + sendData(*socketA, *data); } if (!m_callsignB.empty()) { data->setId(idB); - sendData(*data); + sendData(*socketB, *data); } if (!m_callsignC.empty()) { data->setId(idC); - sendData(*data); + sendData(*socketC, *data); } if (!m_callsignD.empty()) { data->setId(idD); - sendData(*data); - } - - if (!m_callsignE.empty()) { - data->setId(idE); - sendData(*data); + sendData(*socketD, *data); } delete data; - if (m_in == out) - return true; + if (m_in == out) { + loop = false; + break; + } } Sleep(10UL); } + + if(socketA != nullptr) { + socketA->close(); + delete socketA; + } + if(socketB != nullptr) { + socketB->close(); + delete socketB; + } + if(socketC != nullptr) { + socketC->close(); + delete socketC; + } + if(socketD != nullptr) { + socketD->close(); + delete socketD; + } + + return true; } -bool CTimeServerThread::sendHeader(const CHeaderData &header) +bool CTimeServerThread::sendHeader(CUDPReaderWriter& socket, const CHeaderData &header) { unsigned char buffer[60U]; unsigned int length = header.getG2Data(buffer, 60U, true); @@ -1416,7 +1432,7 @@ bool CTimeServerThread::sendHeader(const CHeaderData &header) return true; #else for (unsigned int i = 0U; i < 5U; i++) { - bool res = m_socket.write(buffer, length, header.getYourAddress(), header.getYourPort()); + bool res = socket.write(buffer, length, header.getYourAddress(), header.getYourPort()); if (!res) return false; } @@ -1425,7 +1441,7 @@ bool CTimeServerThread::sendHeader(const CHeaderData &header) #endif } -bool CTimeServerThread::sendData(const CAMBEData& data) +bool CTimeServerThread::sendData(CUDPReaderWriter& socket, const CAMBEData& data) { unsigned char buffer[40U]; unsigned int length = data.getG2Data(buffer, 40U); @@ -1434,6 +1450,6 @@ bool CTimeServerThread::sendData(const CAMBEData& data) CUtils::dump(("Sending Data"), buffer, length); return true; #else - return m_socket.write(buffer, length, data.getYourAddress(), data.getYourPort()); + return socket.write(buffer, length, data.getYourAddress(), data.getYourPort()); #endif } diff --git a/DGWTimeServer/TimeServerThread.h b/DGWTimeServer/TimeServerThread.h index ee3f967..a1dffd8 100644 --- a/DGWTimeServer/TimeServerThread.h +++ b/DGWTimeServer/TimeServerThread.h @@ -72,15 +72,14 @@ public: void kill(); private: - CUDPReaderWriter m_socket; std::string m_callsign; std::string m_callsignA; std::string m_callsignB; std::string m_callsignC; std::string m_callsignD; - std::string m_callsignE; std::string m_callsignG; in_addr m_address; + std::string m_addressStr; LANGUAGE m_language; FORMAT m_format; INTERVAL m_interval; @@ -110,8 +109,8 @@ private: std::vector sendTimePtPT(unsigned int hour, unsigned int min); bool send(const std::vector& words, unsigned int hour, unsigned int min); - bool sendHeader(const CHeaderData& header); - bool sendData(const CAMBEData& data); + bool sendHeader(CUDPReaderWriter& socket, const CHeaderData& header); + bool sendData(CUDPReaderWriter& socket, const CAMBEData& data); bool loadAMBE(); bool readAMBE(const std::string& dir, const std::string& name); From 9a691eb1903d2dd74d704d621572b0fce568bf8b Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sun, 20 Feb 2022 09:02:23 +0100 Subject: [PATCH 24/40] #20 start simplifying things --- DGWTimeServer/TimeServerThread.cpp | 132 ++++++++++------------------- DGWTimeServer/TimeServerThread.h | 6 +- 2 files changed, 49 insertions(+), 89 deletions(-) diff --git a/DGWTimeServer/TimeServerThread.cpp b/DGWTimeServer/TimeServerThread.cpp index cfcfc47..575f220 100644 --- a/DGWTimeServer/TimeServerThread.cpp +++ b/DGWTimeServer/TimeServerThread.cpp @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include "TimeServerThread.h" #include "DStarDefines.h" @@ -39,10 +41,7 @@ const unsigned int SILENCE_LENGTH = 10U; CTimeServerThread::CTimeServerThread() : CThread("Time Server"), m_callsign(), -m_callsignA(), -m_callsignB(), -m_callsignC(), -m_callsignD(), +m_repeaters(), m_callsignG(), m_address(), m_addressStr(), @@ -80,7 +79,7 @@ CTimeServerThread::~CTimeServerThread() void * CTimeServerThread::Entry() { // Wait here until we have the essentials to run - while (!m_killed && m_address.s_addr == INADDR_NONE && m_callsignA.empty() && m_callsignB.empty() && m_callsignC.empty() && m_callsignD.empty()) + while (!m_killed && m_address.s_addr == INADDR_NONE && m_repeaters.size() == 0U) Sleep(500UL); // 1/2 sec if (m_killed) @@ -143,19 +142,19 @@ bool CTimeServerThread::setGateway(const std::string& callsign, const std::strin m_callsignG.push_back('G'); if (!rpt1.empty()) { - m_callsignA = m_callsign + rpt1; + m_repeaters.push_back(m_callsign + rpt1); } if (!rpt2.empty()) { - m_callsignB = m_callsign + rpt2; + m_repeaters.push_back(m_callsign + rpt2); } if (!rpt3.empty()) { - m_callsignC = m_callsign + rpt3; + m_repeaters.push_back(m_callsign + rpt3); } if (!rpt4.empty()) { - m_callsignD = m_callsign + rpt4; + m_repeaters.push_back(m_callsign + rpt4); } m_callsign.push_back(' '); @@ -1182,11 +1181,6 @@ void CTimeServerThread::end() bool CTimeServerThread::send(const std::vector &words, unsigned int hour, unsigned int min) { - unsigned int idA = CHeaderData::createId(); - unsigned int idB = CHeaderData::createId(); - unsigned int idC = CHeaderData::createId(); - unsigned int idD = CHeaderData::createId(); - CHeaderData header; header.setMyCall1(m_callsign); header.setRptCall1(m_callsignG); @@ -1321,43 +1315,47 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int return false; } - CUDPReaderWriter * socketA = nullptr; - CUDPReaderWriter * socketB = nullptr; - CUDPReaderWriter * socketC = nullptr; - CUDPReaderWriter * socketD = nullptr; - - if (!m_callsignA.empty()) { - socketA = new CUDPReaderWriter("", 0U); - socketA->open(); - header.setRptCall2(m_callsignA); - header.setId(idA); - sendHeader(*socketA, header); + bool res = true; + for(auto rpt : m_repeaters) { + res = sendToRepeater(header, rpt) && res; } - if (!m_callsignB.empty()) { - socketB = new CUDPReaderWriter("", 0U); - socketB->open(); - header.setRptCall2(m_callsignB); - header.setId(idB); - sendHeader(*socketB, header); - } + // std::vector *> tasks; - if (!m_callsignC.empty()) { - socketC = new CUDPReaderWriter("", 0U); - socketC->open(); - header.setRptCall2(m_callsignC); - header.setId(idC); - sendHeader(*socketC, header); - } + // for(auto rpt : m_repeaters) { + // std::packaged_task * task = new std::packaged_task([header, rpt, this] { return sendToRepeater(header, rpt);} ); + // std::thread t(std::move(*task)); + // } + + // bool res = true; + // for(auto task : tasks) { + // auto future = task->get_future(); + // future.wait(); + // res = future.get() && res; + // delete task; + // } - if (!m_callsignD.empty()) { - socketD = new CUDPReaderWriter("", 0U); - socketD->open(); - header.setRptCall2(m_callsignD); - header.setId(idD); - sendHeader(*socketD, header); + for(unsigned int i = 0U; i < MAX_FRAMES; i++) { + delete m_data[i]; + m_data[i] = nullptr; } + return res; +} + +bool CTimeServerThread::sendToRepeater(const CHeaderData& h, const std::string& rptCall2) +{ + CUDPReaderWriter socket("", 0U); + if(!socket.open()) + return false; + + auto id = CHeaderData::createId(); + CHeaderData header(h); + header.setId(id); + header.setRptCall2(rptCall2); + + sendHeader(socket, header); + bool loop = true; unsigned int out = 0U; auto start = std::chrono::high_resolution_clock::now(); @@ -1367,31 +1365,10 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int needed /= DSTAR_FRAME_TIME_MS; while (out < needed) { - CAMBEData* data = m_data[out]; - m_data[out] = NULL; + CAMBEData data(*(m_data[out])); out++; - - if (!m_callsignA.empty()) { - data->setId(idA); - sendData(*socketA, *data); - } - - if (!m_callsignB.empty()) { - data->setId(idB); - sendData(*socketB, *data); - } - - if (!m_callsignC.empty()) { - data->setId(idC); - sendData(*socketC, *data); - } - - if (!m_callsignD.empty()) { - data->setId(idD); - sendData(*socketD, *data); - } - - delete data; + data.setId(id); + sendData(socket, data); if (m_in == out) { loop = false; @@ -1402,22 +1379,7 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int Sleep(10UL); } - if(socketA != nullptr) { - socketA->close(); - delete socketA; - } - if(socketB != nullptr) { - socketB->close(); - delete socketB; - } - if(socketC != nullptr) { - socketC->close(); - delete socketC; - } - if(socketD != nullptr) { - socketD->close(); - delete socketD; - } + socket.close(); return true; } diff --git a/DGWTimeServer/TimeServerThread.h b/DGWTimeServer/TimeServerThread.h index a1dffd8..ef501fc 100644 --- a/DGWTimeServer/TimeServerThread.h +++ b/DGWTimeServer/TimeServerThread.h @@ -73,10 +73,7 @@ public: private: std::string m_callsign; - std::string m_callsignA; - std::string m_callsignB; - std::string m_callsignC; - std::string m_callsignD; + std::vector m_repeaters; std::string m_callsignG; in_addr m_address; std::string m_addressStr; @@ -111,6 +108,7 @@ private: bool send(const std::vector& words, unsigned int hour, unsigned int min); bool sendHeader(CUDPReaderWriter& socket, const CHeaderData& header); bool sendData(CUDPReaderWriter& socket, const CAMBEData& data); + bool sendToRepeater(const CHeaderData& header, const std::string& rptCall2); bool loadAMBE(); bool readAMBE(const std::string& dir, const std::string& name); From 663dffdb1934ebc2d17d412a2814dc171cb133c3 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sun, 20 Feb 2022 16:54:59 +0100 Subject: [PATCH 25/40] #20 Add some delay when firing headers --- DGWTimeServer/TimeServerThread.cpp | 71 ++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 9 deletions(-) diff --git a/DGWTimeServer/TimeServerThread.cpp b/DGWTimeServer/TimeServerThread.cpp index 575f220..8018255 100644 --- a/DGWTimeServer/TimeServerThread.cpp +++ b/DGWTimeServer/TimeServerThread.cpp @@ -106,8 +106,8 @@ void * CTimeServerThread::Entry() unsigned int hour = tm->tm_hour; unsigned int min = tm->tm_min; - // if (min != lastMin) - // sendTime(15, 45); + if (min != lastMin) + sendTime(15, 45); if (min != lastMin) { if (m_interval == INTERVAL_15MINS && (min == 0U || min == 15U || min == 30U || min == 45U)) @@ -1315,11 +1315,64 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int return false; } - bool res = true; + std::vector ids; + std::vector sockets; for(auto rpt : m_repeaters) { - res = sendToRepeater(header, rpt) && res; + auto socket = new CUDPReaderWriter("", 0U); + sockets.push_back(socket); + ids.push_back(CHeaderData::createId()); } + bool allOpen = std::all_of(sockets.begin(), sockets.end(), [](CUDPReaderWriter* s) { return s->open(); }); + if(allOpen) { + //send headers + for(unsigned int i = 0; i < m_repeaters.size(); i++) { + CHeaderData headerCopy(header); + headerCopy.setId(ids[i]); + headerCopy.setRptCall2(m_repeaters[i]); + sendHeader(*(sockets[i]), headerCopy); + Sleep(5); + } + + // send audio + bool loop = true; + unsigned int out = 0U; + auto start = std::chrono::high_resolution_clock::now(); + + for (;loop;) { + unsigned int needed = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start).count(); + needed /= DSTAR_FRAME_TIME_MS; + + while (out < needed) { + for(unsigned int i = 0; i < m_repeaters.size(); i++) { + CAMBEData data(*(m_data[out])); + data.setId(ids[i]); + sendData(*(sockets[i]), data); + Sleep(5); + } + + delete m_data[out]; + m_data[out] = nullptr; + out++; + + if (m_in == out) { + loop = false; + break; + } + } + } + } + + for(auto socket : sockets) { + socket->close(); + delete socket; + } + + // bool res = true; + // for(auto rpt : m_repeaters) { + // res = sendToRepeater(header, rpt) && res; + // } + // std::vector *> tasks; // for(auto rpt : m_repeaters) { @@ -1335,12 +1388,12 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int // delete task; // } - for(unsigned int i = 0U; i < MAX_FRAMES; i++) { - delete m_data[i]; - m_data[i] = nullptr; - } + // for(unsigned int i = 0U; i < MAX_FRAMES; i++) { + // delete m_data[i]; + // m_data[i] = nullptr; + // } - return res; + return true; } bool CTimeServerThread::sendToRepeater(const CHeaderData& h, const std::string& rptCall2) From 8d3c716c5f9423aa7df32742acec721f3ca94cd9 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sun, 20 Feb 2022 17:03:00 +0100 Subject: [PATCH 26/40] #20 clean up --- DGWTimeServer/TimeServerThread.cpp | 71 ++---------------------------- DGWTimeServer/TimeServerThread.h | 1 - 2 files changed, 3 insertions(+), 69 deletions(-) diff --git a/DGWTimeServer/TimeServerThread.cpp b/DGWTimeServer/TimeServerThread.cpp index 8018255..3efea8d 100644 --- a/DGWTimeServer/TimeServerThread.cpp +++ b/DGWTimeServer/TimeServerThread.cpp @@ -106,8 +106,8 @@ void * CTimeServerThread::Entry() unsigned int hour = tm->tm_hour; unsigned int min = tm->tm_min; - if (min != lastMin) - sendTime(15, 45); + // if (min != lastMin) + // sendTime(15, 45); if (min != lastMin) { if (m_interval == INTERVAL_15MINS && (min == 0U || min == 15U || min == 30U || min == 45U)) @@ -1339,7 +1339,7 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int unsigned int out = 0U; auto start = std::chrono::high_resolution_clock::now(); - for (;loop;) { + while(loop) { unsigned int needed = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start).count(); needed /= DSTAR_FRAME_TIME_MS; @@ -1368,74 +1368,9 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int delete socket; } - // bool res = true; - // for(auto rpt : m_repeaters) { - // res = sendToRepeater(header, rpt) && res; - // } - - // std::vector *> tasks; - - // for(auto rpt : m_repeaters) { - // std::packaged_task * task = new std::packaged_task([header, rpt, this] { return sendToRepeater(header, rpt);} ); - // std::thread t(std::move(*task)); - // } - - // bool res = true; - // for(auto task : tasks) { - // auto future = task->get_future(); - // future.wait(); - // res = future.get() && res; - // delete task; - // } - - // for(unsigned int i = 0U; i < MAX_FRAMES; i++) { - // delete m_data[i]; - // m_data[i] = nullptr; - // } - return true; } -bool CTimeServerThread::sendToRepeater(const CHeaderData& h, const std::string& rptCall2) -{ - CUDPReaderWriter socket("", 0U); - if(!socket.open()) - return false; - - auto id = CHeaderData::createId(); - CHeaderData header(h); - header.setId(id); - header.setRptCall2(rptCall2); - - sendHeader(socket, header); - - bool loop = true; - unsigned int out = 0U; - auto start = std::chrono::high_resolution_clock::now(); - - for (;loop;) { - unsigned int needed = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start).count(); - needed /= DSTAR_FRAME_TIME_MS; - - while (out < needed) { - CAMBEData data(*(m_data[out])); - out++; - data.setId(id); - sendData(socket, data); - - if (m_in == out) { - loop = false; - break; - } - } - - Sleep(10UL); - } - - socket.close(); - - return true; -} bool CTimeServerThread::sendHeader(CUDPReaderWriter& socket, const CHeaderData &header) { diff --git a/DGWTimeServer/TimeServerThread.h b/DGWTimeServer/TimeServerThread.h index ef501fc..3abe883 100644 --- a/DGWTimeServer/TimeServerThread.h +++ b/DGWTimeServer/TimeServerThread.h @@ -108,7 +108,6 @@ private: bool send(const std::vector& words, unsigned int hour, unsigned int min); bool sendHeader(CUDPReaderWriter& socket, const CHeaderData& header); bool sendData(CUDPReaderWriter& socket, const CAMBEData& data); - bool sendToRepeater(const CHeaderData& header, const std::string& rptCall2); bool loadAMBE(); bool readAMBE(const std::string& dir, const std::string& name); From 4511d88e0f84d60ceed3a1a89c69099148c8b8cb Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sun, 20 Feb 2022 17:08:06 +0100 Subject: [PATCH 27/40] #20 remove unused includes --- DGWTimeServer/TimeServerThread.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/DGWTimeServer/TimeServerThread.cpp b/DGWTimeServer/TimeServerThread.cpp index 3efea8d..696099f 100644 --- a/DGWTimeServer/TimeServerThread.cpp +++ b/DGWTimeServer/TimeServerThread.cpp @@ -24,8 +24,6 @@ #include #include #include -#include -#include #include "TimeServerThread.h" #include "DStarDefines.h" From 05e3aa7fa9764dfc6590502807f77919a7509af7 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sun, 20 Feb 2022 17:12:34 +0100 Subject: [PATCH 28/40] #20 fix copy paste typo --- DGWTimeServer/DGWTimeServerApp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DGWTimeServer/DGWTimeServerApp.cpp b/DGWTimeServer/DGWTimeServerApp.cpp index eebb8d2..623f610 100644 --- a/DGWTimeServer/DGWTimeServerApp.cpp +++ b/DGWTimeServer/DGWTimeServerApp.cpp @@ -155,7 +155,7 @@ bool CDGWTimeServerApp::createThread() void CDGWTimeServerApp::sigHandler(int sig) { - CLog::logInfo("Caught signal : %s, shutting down gateway", strsignal(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(); From 1bb29f31a96e55fd97c7535c4b1372539492e548 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sun, 20 Feb 2022 18:13:00 +0100 Subject: [PATCH 29/40] #20 add missing include --- DGWTimeServer/DGWTimeServerApp.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/DGWTimeServer/DGWTimeServerApp.cpp b/DGWTimeServer/DGWTimeServerApp.cpp index 623f610..847b327 100644 --- a/DGWTimeServer/DGWTimeServerApp.cpp +++ b/DGWTimeServer/DGWTimeServerApp.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #ifdef DEBUG_DSTARGW #include #endif From 1ff39d65d8ad1ba6527ceba9eeecda91750b9cd6 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sun, 20 Feb 2022 18:21:51 +0100 Subject: [PATCH 30/40] #20 Udpate readme --- README.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 091d893..03649c1 100644 --- a/README.md +++ b/README.md @@ -135,26 +135,27 @@ the testing framwework used is Google Test. # 5. Version History ## 5.1. Version 0.6 -- [Improvement] Gracefully exit on SIGINT and SIGTERM ([#21](https://github.com/F4FXL/DStarGateway/issues/21)) -- [Improvement] Add text transmit utility dgwtexttransmit ([#18](https://github.com/F4FXL/DStarGateway/issues/18)) -- [Improvement] Add voice transmit utility dgwvoicetransmit ([#18](https://github.com/F4FXL/DStarGateway/issues/18)) +- [**Improvement**] Add time server +- [**Improvement**] Gracefully exit on SIGINT and SIGTERM ([#21](https://github.com/F4FXL/DStarGateway/issues/21)). DStarGateway can also be run as a "forking" daemon. This might be required for distros still using sysv. Systemd can live without it. +- [**Improvement**] Add text transmit utility dgwtexttransmit ([#18](https://github.com/F4FXL/DStarGateway/issues/18)) +- [**Improvement**] Add voice transmit utility dgwvoicetransmit ([#18](https://github.com/F4FXL/DStarGateway/issues/18)) ## 5.2. Version 0.5 -- [Improvement] Add remote control utility dgwremotecontrol ([#17](https://github.com/F4FXL/DStarGateway/issues/17)) -- [Bugfix] Two simultaneous incoming G2 streams would fail to be transmitted on dual band repeaters ([#16](https://github.com/F4FXL/DStarGateway/issues/16)) -- [Improvement] Add NAT Traversal for G2 and DExtra, using IRCDDB as a Rendez Vous server ([#5](https://github.com/F4FXL/DStarGateway/issues/5)) -- [Improvement] Add forwarding of RS-MS1A messages to APRS-IS ([#9](https://github.com/F4FXL/DStarGateway/issues/9)) -- [Bugfix] Failed to download XLX Hosts when URL contains a = sign ([#14](https://github.com/F4FXL/DStarGateway/issues/14)) -- [Bugfix] Remote control connection failed ([#13](https://github.com/F4FXL/DStarGateway/issues/13)) -- [Bugfix] Trying to connect to ghost ircDDB when no ircDDB is configured +- [**Improvement**] Add remote control utility dgwremotecontrol ([#17](https://github.com/F4FXL/DStarGateway/issues/17)) +- [**Bugfix**] Two simultaneous incoming G2 streams would fail to be transmitted on dual band repeaters ([#16](https://github.com/F4FXL/DStarGateway/issues/16)) +- [**Improvement**] Add NAT Traversal for G2 and DExtra, using IRCDDB as a Rendez Vous server ([#5](https://github.com/F4FXL/DStarGateway/issues/5)) +- [**Improvement**] Add forwarding of RS-MS1A messages to APRS-IS ([#9](https://github.com/F4FXL/DStarGateway/issues/9)) +- [**Bugfix**] Failed to download XLX Hosts when URL contains a = sign ([#14](https://github.com/F4FXL/DStarGateway/issues/14)) +- [**Bugfix**] Remote control connection failed ([#13](https://github.com/F4FXL/DStarGateway/issues/13)) +- [**Bugfix**] Trying to connect to ghost ircDDB when no ircDDB is configured ## 5.3. Version 0.4 -- [Improvement] Add APRS status link feature ([#8](https://github.com/F4FXL/DStarGateway/issues/8)) -- [Bugfix] Posotions received over radio were not sent to APRS-IS when GPDS connection failed. ([#7](https://github.com/F4FXL/DStarGateway/issues/7)) -- [Improvement] Bring back GPSD support ([#6](https://github.com/F4FXL/DStarGateway/issues/6)) -- [Improvement] Log enhancements ([#4](https://github.com/F4FXL/DStarGateway/issues/4)) +- [**Improvement**] Add APRS status link feature ([#8](https://github.com/F4FXL/DStarGateway/issues/8)) +- [**Bugfix**] Posotions received over radio were not sent to APRS-IS when GPDS connection failed. ([#7](https://github.com/F4FXL/DStarGateway/issues/7)) +- [**Improvement**] Bring back GPSD support ([#6](https://github.com/F4FXL/DStarGateway/issues/6)) +- [**Improvement**] Log enhancements ([#4](https://github.com/F4FXL/DStarGateway/issues/4)) ## 5.4. Version 0.3 -- [Improvement] Get ride of libcongif++ dependency. When upgrading from earlier version you need to manualy delete the config file before reinstalling. +- [**Improvement**] Get ride of libcongig++ dependency. When upgrading from earlier version you need to manualy delete the config file before reinstalling. ## 5.5. Version 0.2 -- [Bugfix] ircDDBFreeze when repeater not found ([#1](https://github.com/F4FXL/DStarGateway/issues/1)) +- [**Bugfix**] ircDDBFreeze when repeater not found ([#1](https://github.com/F4FXL/DStarGateway/issues/1)) - Code sanitization ## 5.6. Version 0.1 First working version @@ -163,10 +164,9 @@ I started this during my 2021 seasons holiday. It took me almost 8 days to get t - ☑ Better NatTraversal - No banging on every gateway: use ircDDB (or something else) as mitigation server to notify peer - Support for all protocols (G2, DExtra, DPlus) DCS does nto make sense as it was historically never used as protocol for linking repeaters - - A [branch](https://github.com/F4FXL/DStarGateway/tree/feature/NatTraversal) already exists for this - ☑ Send the connection status to APRS-IS as a status frame - ☒ Reinstantiate DRATS -- ☒ Migrate all the "accessories" (VoiceTransmit, RemoteControl ...) +- ☑ Migrate all the "accessories" (VoiceTransmit, RemoteControl ...) - ☒ Automatic refresh of host files - ☒ Reduce ircDDB dependency, build something more P2P, maybe based on [Distributed Hashtable](https://github.com/DavidKeller/kademlia) ? - ☒ Forward messages from RS-MS1A to APRS and vice versa From bb652f5265b307d4c45f6642e6aefaf8aa0a64d4 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 26 Feb 2022 11:52:45 +0100 Subject: [PATCH 31/40] #20 fix test clean up --- Tests/Makefile | 4 +++- Tests/{ => ProgramArgs}/ProgramArgs.cpp | 0 2 files changed, 3 insertions(+), 1 deletion(-) rename Tests/{ => ProgramArgs}/ProgramArgs.cpp (100%) diff --git a/Tests/Makefile b/Tests/Makefile index 2e29d24..f338acc 100644 --- a/Tests/Makefile +++ b/Tests/Makefile @@ -15,7 +15,9 @@ dstargateway_tests: ../VersionInfo/GitVersion.h $(OBJS) ../APRS/APRS.a ../IRCDDB .PHONY clean : clean : - @$(RM) *.o *.d dstargateway_tests + find . -name "*.o" -type f -delete + find . -name "*.d" -type f -delete + $(RM) *.o *.d dstargateway_tests ../APRS/APRS.a: ../Common/Common.a: diff --git a/Tests/ProgramArgs.cpp b/Tests/ProgramArgs/ProgramArgs.cpp similarity index 100% rename from Tests/ProgramArgs.cpp rename to Tests/ProgramArgs/ProgramArgs.cpp From d31ecf63c07927541773b4f7063fb10b4d15a149 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 26 Feb 2022 16:13:48 +0100 Subject: [PATCH 32/40] #20 add AMBEFileReader and corresponding tests --- .vscode/tasks.json | 10 +- DStarBase/AMBEFileReader.cpp | 179 ++++++++++++++++++++++++++++++++ DStarBase/AMBEFileReader.h | 74 +++++++++++++ Tests/AMBEFileReader/fr_FR.ambe | Bin 0 -> 20137 bytes Tests/AMBEFileReader/fr_FR.indx | 44 ++++++++ Tests/AMBEFileReader/lookup.cpp | 115 ++++++++++++++++++++ Tests/AMBEFileReader/read.cpp | 62 +++++++++++ 7 files changed, 479 insertions(+), 5 deletions(-) create mode 100644 DStarBase/AMBEFileReader.cpp create mode 100644 DStarBase/AMBEFileReader.h create mode 100644 Tests/AMBEFileReader/fr_FR.ambe create mode 100644 Tests/AMBEFileReader/fr_FR.indx create mode 100644 Tests/AMBEFileReader/lookup.cpp create mode 100644 Tests/AMBEFileReader/read.cpp diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 88ce123..995a9f1 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -13,10 +13,7 @@ "USE_GPSD=1", "all" ], - "group": { - "kind": "build", - "isDefault": true - }, + "group": "build", "problemMatcher": [] }, { @@ -94,7 +91,10 @@ "ENABLE_DEBUG=1", "USE_GPSD=1" ], - "group": "build", + "group": { + "kind": "build", + "isDefault": true + }, "problemMatcher": [] } ] diff --git a/DStarBase/AMBEFileReader.cpp b/DStarBase/AMBEFileReader.cpp new file mode 100644 index 0000000..119ea4a --- /dev/null +++ b/DStarBase/AMBEFileReader.cpp @@ -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 +#include +#include + +#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& 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; +} \ No newline at end of file diff --git a/DStarBase/AMBEFileReader.h b/DStarBase/AMBEFileReader.h new file mode 100644 index 0000000..cd66e6c --- /dev/null +++ b/DStarBase/AMBEFileReader.h @@ -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 +#include +#include + +#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& 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 m_index; +}; \ No newline at end of file diff --git a/Tests/AMBEFileReader/fr_FR.ambe b/Tests/AMBEFileReader/fr_FR.ambe new file mode 100644 index 0000000000000000000000000000000000000000..c81d0630940a7fa373c5b4882ae9a765679c95d3 GIT binary patch literal 20137 zcmaHScQ{;a_q9&+-iheZTl5Ig5+zL%M1-i(MTsbh(c54!7=857JEQjmQG)11kKTKW zZw8U)ect!?{WsUK?{hBa+2hKnm0VqU`IFa2xJV2GeKt(HoVY_^b>FARm|P%Re*~t$e2)UNmvuen5QAav>4PB_SOwp zfn5kd+=sX2&$Bw?lnX&kWM*!_KhFY&V#A?`V4j91nr+J6gL}lay)F=~b@j%-Px7Z=j{@~V`BrZM=6TpO`&K%bscE)U zmiZ_Rg7H2*4l`{U9jL&57#dorf#of-og5k}s@mc$VmKcfQmUa06iH1F3N4ktymA@S z_m7yKLJh>zg%MW}%dwoz{$DZM$=CO(v?xdYMJ#tRTWU&- zX8?EJNQ3ptZrPr#x`{%bc8;LBwybWhMRz|-*&DCQnm=f9dAr#{oeF${BXvqzjP@`= z1S0=vQ8O5lAoSg3@lZPC2w3(K^<(n=?;e{X?QE7GuyY&HVwTE7nBo;AU1>JTHa2?K zV+e=ep99Y-i8?iP-&Rx^Xq?O^l*nS>zHiY8YbnEulc;H->I8;odvw+XJH*3=N}EQF zNyoqwMa5&Qc_XEG0Z-VQ+bRLBM9J~wf;UZA8S~o{T6Cz}&n32lb$@n1Fl$uag)uxk z<_b=GXXronV2oynOY%JI-pl1S4^SNc;;Vh8v-{}l76TtkpT#}oV>-%m;cDjUkoykp zv87-9!kt~#95zYa`!39k*S7K|v+XysX&wLE`nm~-7uw$R_j}n1Scltn8pAGdI1}wR z+;2teG9D&$I28#Sd@`i>u^b#CS2{&;r$db9CTG6+yR?$1AJ85Lp{cZnJ@kuw_K+t| zAJ=&P-u)Y_IcR||#LnaJ;fCpW#H35z{CudbVNag9^r>-4a6n!+mU{8P4Y9n5h4}+h z$zrf+9xD9a9y}^&j^}iO?Hd)wG11dU&IP8DCM!glm6vuHquqW>WF_`6qM*hV6UY={ z6R3bI+vb(is+h!hc zC0CD6;P)HD!_}DnG*Z;X)lXIJP3;WF5len_#)-D{xI3(fl%a|?@l_m8nZ5X2;;Acj z^&`F4YoX3ru3Q!%vQG7hDe^-ynA30#Pwf-Jp%|(;LT*r^|5iX;8z&ACjVk;H+|#0A zwreo6!5J$jO$Qb!+09Hn3}qCQQ}2)`KV$~o4wqX>&Fx?ZJ+16%on<{%qJICe^&q2o z$%u~Rz+l85jaUED->Lz@M=9cQ0`Ri8et<+#t2$0YqP-jU@tqE|5eT zP{LYQ=tXX&-sXoyBqX+6pbg0JE-(&>V@Qa*SVdx{c8Nb^W(d?mVjVIj^ba29EBMQ@ zqY^x=4O&CvEm`DYA#LS=(5Q<446*NY(0H71`n8Cu>}9XQYx`K-u7Jo9%bTaMqqxt0 zT6F84mAoyer5rJ0unV;4Oc)q5x(Ba1!uBVV|8RG&k-&dEY^crswySt&=g>Hy+9SzBdUXq zf#F#YtuEirsXi<}?yxCR_Y z+3Z1QTo_WF;$Nfn4x`cH=$uZ6n!HK+6IG6X`{(9rEJLYF)8$W3ixj=CI$0yM08 zV3Ocv|B=}Cytr~!t3lWpI};woD^l1Gi40uKln>ZPF-f8UEsUIOq)R|pkvyuLHkr|RkWtFZXiE`QB>*)^UQmIk>Kul&{QT05TvUC4z~08{LvN6ux8uwb z$c(TNJrEVF_j{d-CL`tv>CxadYigS7-%H-|>&$bX+fIbH(BouO-OyJzI))5N! z^7SA9M2EM-aQPhQFdxrkiICEmxIE~ow<&(5>%Xi`(%)G_CxMZum)S5Pl=7}?)raGU zS;{OY=~2SKydzjPK(BVEa)kUD?s5u87RP*)>QTPa^z!hb-Pb6!^qkT;$oIGOF1eZF zt*f*T)dP>3B%2q<_LPVU(~ZhQw}?C1e$)wpwr#yv-tQ-VUpYzhZVDeaiNc$;65@j5 zKfroA%a@`$P2#Awk;=|K4fR*}wOfH*jAR9Oz5j54&YVlWVcFuxQ3q{ea83XwVbwrU zD6K62a)}Feah=Nu-qPuHnWvvF83;0|>oIByy}-s+{@1+a0uxBBr2oeJC)*)$W&nQ0 z;DMwAccI_XJN?M= zW4;|Ndz5MVuu~^-7M|sjh`+Bg>0TeN?@KovtU2)f>^xlS+qmu6Lc)n>?gS|EY%k`l ze>}K$WP-zjKSToD_;I48eTu*#Z`%p27xSL@56zenE?B<P&C zVRJ{&EI%HB`$++PK?xD6FyCGQRlJte*-pC5;fya@NFObxrAX&%Zy%`Vd710DQEoBBZ3`pFy*gD?VA1XDKv}C3HgeR55i9hl{8$y=M-9s*eJKh z3>0~v#Z4(1)k4T7a^jv*I*T4^b*r`90!1p78r9b3{86zG8k&O=Rdr9fst-VbC7q`Tr*-QbpthMplId}H5UrYaRu{axXiPR_AE5*9TsRn3Y99_+*mpkqKJ;+b-$^L zOGOQaazDu{J74HwgLesbc)ld}M+#ij$l^@sXhpiHrs=T}=W&DYjqcuW>O2-u3g=JR zlWU?}Ea zjd?&5l)yPUwn3lO4DS=eD956$ICXJX4gzuEtaXaLXmk*UF*M|hc*n1sZi0~LokQAS~(tGb`H!0atV()XY zeq&H<&W%bZykk^|eh7a1z{;w1;)u?T3ES3PYw{z>$4jKD3HVqMuO=f?wS|-3s1ez# zFt|%4bny|@lov;syQ`OKWwk5K;?nQgFjnrqKWytAQF_P?>_zW7CA@vOr%S@S`ik$q z+v5%S(84ulNxhK<5vsCxw9lM98o}dDOlikG6*^Kw;fCDaeWQR*@*V_+c^y(!ape_9$%&5Q;`v?Yj#9WSZp!+f2dmkQjE zOV;DJ9V>q~uI$KE8G03vz0z2&B+Y&{WFokK&LfE?r)Z1uG&;Y~aNbke&@5J;G>bjb zu}rEsg+5@OK`y$a+t>F7Dp1#?=RA#?G@V$k$)KNuP=^DkkOPi0CbwXrrWv5yu@>P% z!Bi_Bh|aIE#|pq{T-sC)X_mkF5cT+{C2@%y)=Nh;Ha7C}^T zXNUb+M3dLQo-|G6d?SU7ylzBW2oEhqj~T=&((KO0>8N@TCRfCA7Yl@IZ)`@VoG=>? z`CPRjO{aGj|J!&hMJ)Ko#PiRN*^+n(5&cv25<9GgTYb)-xY8uMGSeSi;}^`gx>T8;yYF4|1GddF#tS1K%*KA+oP4Xy*5AIvOIH&7ktSk4{T# zQwj>nzG=~Zj~HB$rt7l+p-SX*SJ`9Aau(Zs&j9qJL_0zSj(p&BzX%A{ag%>ly+R>$ zJl;=buYv-|tq4LYSTAbICx0*Os!O4I!9_uiyud#k@{)^^nf;^6{HGQsF?$X4`=fI! zkn9v$r2cZzOC`?m4=(zL(-vG2V4ttXJkS|H1SP;GWjPo1|$wjRkJ{w{CRF-`V8DhB}+y= z2k(wUK+r}mjgiWsdv^|2?ozXdg3~8+bdg&a!MCS#RZxp>WB}Pv`MSR3+qJ}tlsdtD z1UMIn)J$>%TESr~XtyJBvalJm06>O*E2X%U5;>rA`tc)#d}F?$E9SO}ML3p1sx1re zY=)qrqTP-xY=F{pr3Boi+QwQU)-sBhP34FCml^`V`k7*IyefImFo&q4Eh55KXnq8Z z?Zv6&mF$pDLqh9l;HgFmwoR3UmG~JST3$hTf<^qy9GHNlR4N4xng*gdUSZVI%U2P^ zQYX=iZ9UHttr*dCr5W=LF!Uw1eoQ@4&b?GFZ=k0&QNFUAa%)j)c-AVRG|m`KRlk?} zdH(C$^B?8yl=sZLRj7xFClB82WsK9p5ml!8C~8N_|M^nsSnBqEHGJYRgU(X#DLC?X z=QK_lzj)J;MA|bNX<4kUy^pLa)kl_M!5bW;+C1;rK)}_umu-_TvkeVBEc~q@h*W>dA`k@h+aJfuAC>I9t4ZP^C6=sQM4|3M|ydbD7HbCDtA$yg{7KK58 zLUy|#Um`R1@Q-6@IbDNj%)(Rj6C4QUe@mh9&;#A z{|WTBS}utq>1!d0{72cgO<}~D->z+z@2Om{h6qa&8fXgB(LBAhKtV)-fDSgkG;v;N zzL07+2eyz3`BnZl1qJ@o<50P0w;b`1t0;*(|$@)SGkfx|N;2 z1M9IJF+J;9pQSgW7N37Q&D^CDra!CsoZ9^4p5&-uVmuV?y}dOQY=2vVed`Bcr|wnI zFCDgx78-me7n0yIK-ya116rKy`{1fag)UaXNK05-3z=cnOzjK zUV%*Y>Jtf&uSz(3CAX$xjv8N5!$a-n$4^JR+g3Ax-fi){r8r!PdBK(ABhej;8w(2o zqi2qv%Qj^+D|L@b~`q0F2E z?tBZ+Z73v#?6$8L?01rZRqZTPZi}6LhnP=jahH0Wen5d|<=!d=O_h%wRaO$W^ZRJ> zxZ%C!xs?X{uGTQ>&PXYL`b?2UvNBRzJbggZZM`Z{tEZBxNAS^n=%zi8tkq8MXh{aA zHEf>v#umC#-65IVp|)~F%g~tySM%NHL1^N&cZz53=?1J1`8+K=sRoRGzL&LCGpnV% z9_pHP+#;28vyC^Sv##3rfhWM=;Oj3bNDk}QQU#)9LBp_qw08I!4gNkZso zA78jAs1hhz#1@Y~uRWwMV8#x9QsH|-SVP(v;9%UZtHLF$d{cJ)`TI5@^93w3MU#bJ zYUkkx_QO1B+>&#*afe3teFejo^)l;)u5#IJm0jz$7!%a216a0_H?+3EME zf?UZH24KM-;giXpF%1Zi{k3}`g(yF{U5X=w(Saxme{5A`nLx@Mzdyp~mr3?a^9>rJ zkvc5#zLt15rilYfN!kYQk4}*VHrev!4hC3yJ4CSiFzO_~30PxVtjw*~)8FEWcp9~3 z-fe+$ALWl63BKNe?6r9_S*OdS29E`b+VUFOV=^|cNNeF++P{o_M*VWfz23T-*?gpb z_oi|6&rcR(jgyCGC92^TujDsIB;v**q(&{&L(2*<@9{&-)E`%eV%CU_&VRD`m|8CK zIKhaZz}h=`%0~Q4i+1iSpg;Ec>yZb%Ms~C2lfP(3f@h}TMNe_av{NbAu;O1*j`Di$ z`{D(IlDYYOw!Xz|3P_%Oj4~nfKXZZ|KR|7J+h3_osXcsCT28*Rvj z&xdX3Na`!!H_RyUarq&>Y>ftLN8YtKC2H4X2*G^ZN@poEAi&5eCW)ZN-rFCeFz+&D z1peU(Cm|B#0_(2Q3Tfe9JHQv|fXu-^Df54&)xTr>$;tkIsdt^-7kP?woA(!i2ANd4 zrqMyX;;zO4Oesq>Sd?_%?bY3vw)VH%giJl?x_<2ZMjN#0eK!T6axtU0EoGK5z2|A~ zdWa^c>8hg>b;#){Yr-x)#BgYm=qE)-JRK4@*LNdKG*MMe%dX;62kG5jHk{Q&rAl|H zE_23|;PMvk*EDN5oU+f>WD)~Y6NiOg0hB&c&y?4cCJ*#kNvj$smDY|Vh2*2X7`Pn= zs#Y(TSHj{cUN3pbL@Ox^ZoP4sd8{T?a4zq&UZ!jM zw}a#ut)mwc+o$fj?c=}E2;8~cNn7Z^Q}K}SXS|E`kk}`pSvxCJxqK(j8`LOz+H9Db zS<&2vu;Ub9y^S~z*h{;b>QdRT^{4B5p>?U4A#R10C6-YjtQ!h#g_)*YTge1!zNLSd zRa+ePMW)=nBwa{z@8VBLOE7{%5`JMMh9Z*@(XBxEhlrL$*jq~ki$h!?F~dg$Us1k) zBR{dU~4O6P~of*zN7%(4Qd6{r{oItC}IKr1> zJevCzF7OL+H50z0q8c~z z26OnoWrl$)qF|{vnSTCi{#7{LN`3obX@zliqlb^thtew#mbdfb^&;`3*yxge`5H@i~94zuU@Y-AOh*`B3n^O%81Sp;y#_39EBjn zeR$d?H^gaKyk9$x^vX+eHO5$t@5SM}9Jk?N;DM$CK33lR>?c`r#_j)s zMDYN(oSl|ol6=;hN+Va_8>>__kFY^Vzrp8ylwPez)l36654-@u8+Qg*;_hjVP!x#Kxtz=c&rTeTRj1*Q&eezSt??)k&6g5VYv%FTr0+MX1^tX5nZ9U6?#M?AyXn-yUoQ zdOvYB<~#@_+Zy@4z<~qn5J+G8K8+oe`}+w?%}a}TuJpl!Qn;95Zd+cH$?gP+so07^ z$1PYwxL#-1@wxY=4)s`KJ5P#!zH8ak@7t$m<3x`rR=GwC@2P64YAhS8z|=EY^n}G} z3Xh6^-AosrC;InYmvNk1jFXgN7-oK1#@+2s9p$~#C{rC1xVf`$lpfe_!zBJDT$^HG>7M-Ine za$e1|Ek&HuZ2i+I*wgWc5em&~I&RC4 zCnR7_;f0I1va#NK9*hUbFjOLcx>>z!C9Y+Hp$);#CV-Gvm;cc3YtnvQ->)gef0Op0>s5b=#}(gLZ-Ch~0*p43 zi}u5b#Rh~EiW+IU!{SajR}<8xU)!W##W!!(PYaWYq*YQw|ZF%a?%Z~wGk zQAIW2^ZB5XZ-kBBy-q)5c>L8fho2r4#lpUSOc;N1kla4Mcr?;Laux=e-qfBvE8hCR zRnt>B0wFy&j@|VYC=MG?6Zux8vHNSHAY!rD&eO>%+?p+JIs)S8Y89 z2MeC4tf&k;;d0d$6aKP5c@l0~dup0JoI!pr)RsuKTh%dEAQkpVS<9AL1b@b$mY^xC zFsUhQpnPbF*t7`bM{e4B>{37z#JI-)s%OXh$LGJAPe2ymzmyoc3Q@ZeR}j6we-wIM z|BL?Mb-&=cT7|tul$$@#x|nJ};JLB($BehGyZs{zZw@o+$Z9JZNf=h@i=*`Mj%d0h5#pw=7orsCCt=-yoIrTcbVM8dU6qu*}IG7;eL+P|-^FVpMT5 zz03C2LMAOK8~AB6Z09RIGX|+6$!gN$8Vz3{D|6mPXQ5C-un}7%G7G!`BDa?Q?&ts7 znb%VBFKzxG+Zd54$h`QsVnxvJzuNOEo363|DQ5pD*1r$EiWyad31UN@Pw%F`$Le4w z3*Sij&X}m*IdR|yikFC2Ns@Th@TBFl_O03~2xtiUD#?uZmfybLL=3)EZt0lfc<=%?Nk{WfhrE$}oi}Ch% z9SjkOUtT^fN5bvw-Ua>)dk=DBb^c6Kcp;)|X4O`Wap@nnUDOMtLi9t{0^|VC1-V7G zurF!>nKQD@jUczkw7Q_FAY*C3HM9D&>XLmSTk2O81kq&w!_Y2PU8WVHS|N0<|8*t0 z;B1JrLU8P>+T>>jzb`Z>|NdKUHX*u9md?Yem{C2vj|O$^;L~AM#gp^j7VT#PC;Q@u z#Zh@M@J_4b*<}1z;B!;(k-_?lqqV0_*0G<*xO@FUE3B{93bX=NHF^>mrh>}SvR*V| zexA!QtTxpdNUWMqOj)}x)t!7M5{}vU$^ykB*ftzn-g7yZ^rs`biHc`CAMPiCd%J)y zreM!WVJsA%juucj!5$h}=+_5TPw-7l6=MS*fdZ)Ac^%N|)Mx{o?NGtE%c0Qw^%)q? zA_CECWPY2*)HN#D$uM&AsUPf0Gw6ERc@ zVr5=R@`bgh61{Gz-7tZ|FoESwl7L`Yv55;O$AgJwI_{6a{JXI)s|-Rj{@vjh zdtM%qDgQOI^dD)3v~>QFO;>qwm8aKf<@L`2rz=ZmBM++(N-Oc@bjX_7)q~c|E$`Gf z@$J{O@g$G1Sk3gcIbA>;FhN(ZYK>${sz|5x#aOz_ZgFqqNox$u61gd-r|{`PszyAU1Te4W zeZFY?02f}RJIT7z^ADgi&kO~A$-zZj_W|lB8$|Y{t{79?jw%{WJY1U$5+)t9)@X2a zYJ#CVCqN-d6rh_aLqSNPs!|}iXEI{U#vYS^r=*~qBfr`d^%G5UrcuT;I)Rq&ZXC8` zVJedCTvQO`(Au?OlFCsGLTqDvggimi3*@ZY)%1uPLY`dCiU6*q;&n~Ao(#G6@UNZ! zD-ZuJa1w(FmIk0VXsPGtqWyAHqB3TY7*0Y_L><2m8;ehF)9Wjv$~pl@p5A_8+h3^7 z_#xNT1)$Ld0dSr#k+PunR(mr|m@>+C2lNZXuCUgkxQ>i@GbJwKq5~@F2%W1;tbMlg zXmQ_)FS<}}SGxMj#aS9q3FIQQ+OR#I*KB*4%*gP9J(EP^i!{in%(H5r-XG#i%ckyy zM|+W<+i{@o==f2FCd5HmR!aPe34UBV6mto{W% zNU(}Vs&ynZ{Q;|n)JtLb54!sY;w}Xy60EKoG{{MwD_|T`jaj_QoC&o zjuJw=GB9a~qpArdbufcDnREQ|iZ!rsZ~)w+c}#Rfa*q>kRuPoH)n*2&1o`4anQ)I2 zn2%}&>V!kTaT6^&jz)3L;PTC(H8a8 z$(g4y?NQTy)XCM-I%hvVtyjC{4^53kxLdpK_vrLxJyET_Y5A)F+d6LT{^ag&HxfO) zR-5C`%jpZG-<=a4osQmo|nLC3nY5C<>|Y;OwS&4?+H6&hBUGv2Zop@>Bbv_v;w zO3>8TId+_ADV+Fj!+pcxH`x@t*GYTyIK@y5Kp<%L*sS9~qJI-$>lr|gT&^8Ep0o9r?IDzb?;KBacBNT{^ZKQ_N ze2^I_lW*W1*4B;HQf6j+%TkS^cZ|y|H&MTzHZ}C_SEfUmXBc2 z-_YJoWtW5jD9w0*nV8wVjW=FL!LKXzmEVFKenTikBwxMQ*d)&Ch>CxCo8= zY2Wo_9>hPlh(B6yqL5zev;R!{aiuC6rg3ImJ5}j+#aU`A$d4x6|*pn)joP zt|TVSg45ikXpZpncmhN3ot8wU@U5^=#`5q^ z)3rT2=ar^?()uog>g%cym!a2_msIC-iD*iUW-004cp*nn0(|;TaB^CED+X$^WE;%Y_laozh9VySAd4J4X+r{zuDJ6tVM)Th6J<=*7OIOt^kdv z?Sg$Fjk5p6zAm2?FlodG^6^EH3-Zk3dT-?I{%bDaDc2aaBRJ;now-NI-@+GIg?mm;l1H1{MtOx zQdDX{M?i^1-AODZo36Y`RegneP=dP*9}=YCEqjcc5#PqXxM{NQ>LEGmKrF+w5}jM_ zsu&{8Y|+yxL)u7SsYw>9R%(b9N-sNqk_QCHfo@Tjf6W6RTIzSocV`J}XqkM}_O(+t z1XQt5SW|PH+HpxUe0jnvO#?#Ej8O0eN-FXTbvSTQN~|(B3gxi?oT)iE-6E={xSTV2 zk=XPxxA1A~vkpH0(t8_e?8ZITEh3~3(83Cfwl0=8L6dIEi7OD&Q6xkOzg4nYsG$(R zc*H@YS<)~Kd5tcaW3z1Ex-UGoPis4TfG z5;DZMCx}-K;9lvWzx zosx0%9%dUZyHkCi8(%tiKe9J4$o0aekg*wR^Ng;^jm~tiAi55Y62F($Y!h)|d#?<0iJxTILumix68d>MmaUl-yU zsk{G^V*Nv;tNULQs%wh%N0MKzx>PP#W4+ggz{P+r!utX=XW`;B6RW8-j2UM`=KSNyirpZMU#jm<=X&;Gn zT(Ge;!a_k*2P6UhtJ*I0AaWe#+EKhhq`0f*YpKFvE}0_vNh z($O$~aQNLh^d?9@Cddsh*TZ~k-Pua_i$>PH5BSv&o|~0TTR>@3w<)tC%Fb7%Y2Tu` zRgeYslv-c~koPSe*rZt8B73i!<;PJ4Xav9DZA)d9gnNP<`7VL_QePlmFVc^?uy8Nc z#N~_nE5QFWi2n-bKP8~^ukm~NQT|~LUzHNXheMVLGUrd1vK^^hu7o^Nxm-NU4d}%s zU`{&j>KV{ZM5!9%;8RDKHKsxLiYY{Aays&CpV~H%oPX`RDL?J>pzy{(|1`&m#c@OO zNcc3-@%$HqysijCs^h7qh^(%@S!^w%<&><6@C6Q6i*=H>VB#akk4NRr=M5z~3-?Y_ zen(AFj+iEFecfXEkllE_BCauzJl)S#TZ=NG|db@~iq|bss z&gdt4!BWJ5lFsJXtLVaKJBrk&DYEVe0&J}19PU**MWt(V>e>S^c~(99A<1$1UX0}H zAqp4cI;VR#3*A?A?tBx5;eyOolAMGJL6S#_!bwab-?8OG7X50o?rG^lAL?78$KoxB;5;S1OJB_|WX-_;I_mPT&{bq0sC=Tqxfg zeB1AX{DB}uNhaN`uqNaZc9}nD?CC8perDU?)%F#Isks39KoOmrP0AKe67=GLP=+)$;K4x>_akr-AkC;wrp{M#P+d&57U^2(@3j=o$OFMnhHJoNlZF3oo~3$-@} zKdny#%w|k`hchujzxhCLO-PJOIN~sgRlP=Ol%rRJv5vKQ%{B67QDS9v98~O*+xucz zElq3HG?z46)dEr%z^We4fUn&w18ALoOD|-1A6Sy8HXP+l3&+rCTvQy2$ zcm!5k8?yc6?lKXglNW`Wqxz}UJ!tF8{uJ)>18?fNyj4=y$X88zYp&kYjJr*$9|xkh z>-C?l-*yIwBbVMsIb9xN+6VxtqFQ3W?pI`Pq*I@s2flHU28&8A1O z4h=&bo+(sP|LH?+`21vAK!|@SY+6J|0^MgjlB7LK-40s?v`YTsf!-`3`IZ>t#Fw~F zd5UN3H+w0}*kW5ZRkzQGv@8>rybML|M-+$~JnoZx58~uip>{tY>t&R%7k>x-vj1KCh8A>qk5)it=_VnqDkq$^xCUI^^y(dM%)s!IJs;Y+)qto~ zkP~@shW-3||lP53wDK z(ZgQsQcjX4cDru-D|pk)h7M$k+eMHrhyV(mE)z|0uD4e+goV4AI5s|-=i z>Jw=Ru`=R`qRPnaEMwL##?o<(8njI2vv+y$-fTD+uDk|I2;-WWl6Z8#EX+u{#B#e2 zol2e#{;(~5`{M}*y<8{`e~o1q7rjow?>XnXlY0!ulIBbpi^uY>cC$YNK3kmwsXP~> z7D69CgFR(`8z4J}iGCzZ60SP+s3opF>P@a?!4HH3ia2BVD7|y2Xm~Tp<&XuYfzuZK zOjEl0o9uD|X!3{d$*fUbLa)w`yS3k1D%<+?CW(XseorZFpgD`VwW zEpy#2)!IP!ttx%58Yi1l82{*(TG-2#G+gFx#d`S5d2kl{?{bmW2t$sCSw969Ac9-E z-^!wFYv^KaUhp2}h}xRGddTx?5wMhG;5bhRFBDsec(u7Mmp9TU%&z+W;|+uJ;xecn z6CVvT?s#E*96`CgF(yVIe>gV)4;p(*Aa5B5-#{VeMa5(i;#i!wx5!l9HSzdI#{D5D ze`?D0pRVdoAHvoD&wRgvf`83$Ud3Fe@IU%!R~>|ZH|7vs=|2x$;D@6bdh7oR^UR#ze!XE~{~SVK%JJ=Xl=m+U>EP&MdyZwZ=%A(7UcAgEe{4 zjiLmnk2n^c#!c^7&lL)6=#1;uCkT%i0@3UH*PJ1o3wt#R|k zU*o?6%Nu-S7gv*)&1g(n6<3uL@uM<5R_8%0C8}gA$XqM7zzpP>x{WxLXRm-OV8{*) zX7`*5CPuBBvn;PNqR@U*6KzrjYKTWj*K2wE-?RExJq2Xb4KdMytY}xK&pustu91zo zi_E>qWn>IeGK4A%`6GUO0$k-g!rA_hP6YC3d=B>I?9=6akSE=)Po(DY33*^qaft-IN_*rNzF&Bk3?agqJh*Z4~h1_5`ehIA(6Bn)*HQS4hl~5r` zS%TMMB&(g!e&tXHT(c|xk)pP~rqRIj==B8rS1hUd7n82Wcyz=j!pm)<#mq{d!+~NN| zn17uhH7Ye4C=dnqy&7D&d^6bSLs5tV?gM228}nW)2hi3hh%LEt?gt&#h88$}G5v=O zJj9fP!nbk|4qwX`_VrNJ5*Yi;iuqJbcyzXNgelqFilxS4FYn_oxbOX>wO^BhNrqf7 z4P_-uc;jdP_Trx6v3~mL)W!SM;p)__Pw0;EnP0gXQ=`#UaSNEgnBAoOI;kcptOH2w zSqtjwRzHSBek>ZZG801M6mS)6Duo&!Q z5$=YN#@f^oz60&b1UM-&bS;<6){Q*-YBg~V#+oe~k{2a7vc{NO6d8kM^{Wn~ACd4> zmu(6uV1aGQ&A^(!Jk+ak$twjI=*Hqg@`<;Y5&+-rn~5@>agX|e<0RYjJ>pLl(O90v zTl@|#_1j^6IbqR_UmDk+k5=t?a9WzT)*usWQ?9pIQ&DPiwy@tV?5DwBOVQ3oXj%Pz z???Fy_ciBplGNd+69S>*;6ttU7Z1+5EjlM^MwGRW zBY7MBX(vv#N_`H;^4=(o(3s+N1OOR2(K-&vC47K^;dGXe+dSHSX5!}4(aNZ;2fQMZ zQ}{-j8ne0Lw8;c+4w2D#<>t>`I|+LHT!GsUH_^OoZ<5WPNs0nwV67-|jXf5XC0%Z` z3E#4GH5mYeW((cIVdqA(S;*sL$}+sm>*M)I#K2!heRA^jh5v2qI-;9jKGRp_5G99v ze3ATPOf6}*&Rk`cLc=EM{cK;(?h|Blp#sYcKxS`z@~Ns&2K;;}-gW8AVEHPfRZ&ZF zn??aXqP@ypSauv<2|wry`#cbw3xwnzKR+f8Cf!>p-c}gt!x*lV6G(1zQ!7n98Av<~ zx<{t_s+ekJb3%%DMfsSKQA$#0K%@dpC^N8#%9sYNUWq;2v3Fn#TUf?VX7zgHt-Zig zgDdN$Z%PtqTGND;V1x<|XO-?SCD8|rrb27PXBjc{Kl$X=1YmKS(W1foeAgDYAH}%NN(Y+sZo>a7BAIA1Nb^NVq+v&wE0LKIO z;*|NR-?qz@GLFlc!e>5rJpCMhAY!*39h+LYhf(zGu6y}#OeP~p3+G(t&6iMSgEc5I zQ?Ff3f%1*%u-XHYlZBVOXU8EEnv+L+Bg;tx!K6uV>#N@neR+BoK0=Pqo30iqmSpy` zhMk{Rlb_MGexi&K118LHRG;T4^qd{7#v(L8bVai_K(vt|xFFiFI=8o`z@j?1O0AvKZvEam9V#3>&xu)2*um@oRD^taP{_w%L`6nVvq z6b18YC%+1X0IF@;*Jw$FH##GW(iVUE;_?vRWBH7r&uuJ*ThkQ-GAYzADD=u@W6U|9 zd<~L)|2n@ZP0G|u{0CWb4xv{IK7Nd6I*xxeCZTk-XA-`sA=V864bE}dY^Y(9?EYjY z5w#gRHHoIdI!>UuQM4lE)6ft}!%Pnm;)3XIAq&<3&K>@hN0PLt*bY&V^_GPiegG7? zji0`R5)FQeQ3_LPJSQ!f4w1@zhWQ5azy}5!d346F(+JeI1`?>^E13h?>}~~MQwQjy zlgwJIzBWE@B~Z&~NMys;%0S`mlSop?*g^+d&J{-(u`Zbi zCSVf0i0VfIstZ`G4GXb0#V+pzt$u2x_-9D|Ji%rUash+mvUjVkU{0m-Y2#dR5&@(X zk261+eIe$19C0E>W0WV+=t^Sx2+tirBH=1LuS6$UDqo_fX{y|{FqGQ+NO&VXAT`I} z)I?~-cck{EW2R}Ge=#S;xD!dR2tF>Oor&){*baCo`3bE>emKJ$qN-1_H3aDnw0-c! zx8xNNAOC@iyl?W$U=AS*Db*Zd%rp+T{aUd=WRK>U%rn>>{-i}ay)>Pbyo9cDQ~LMM z@!i7$C|$>U_G_~@+m6W?6+>y+fj~4CTB{euw%AonqSyNa^{tMxnwjPX2sn7JSF z$(An#5JeTf`$#)gHQ6Dfub$^5Lbs61!?b8|*Rna#8iArU>KjSJcl;%+;Z&a=KYCqP zO$ZHf(TxO;J@~ml9?BlRa&pTqqtk0{xLvvI=6E13+3kG z6^gcGjjMM_AO`B%J0&&%-=Sr-)iuMgWvVJxf*)HeLdQm0KShQo03wIoj^g^0ITYbx zkHws-nZsYMOaWbPsbI#bCPH7m*bC3zdnjqLSUhHe0Po`)QL+(DMNRt# zVuZ(hRc54BqIq$jZ2pKv5s_Bpcd7mv4vYxr?gf;yIELw{1ocv*y(Y*j}uVqA%oqCm-Jz zqILLZTio8YVXhwJV=@T2MO`@HL^H^wtFJ4?`6jW4_6-YrPY!Lu@dLF?29bQy@jTD| zVobt36$aKs$Jgv4#Gmn*1AV_!?hYp<7v`)*3mO15UY}Uryc4a-nm^z;cYQL8Di#`@OvfuYc!%v2-|Pe_`st|uh_R>CEO~-8#pVpX+Qv7P!`<#) zwhPH^z4DOFVtPq|2EpA@KMQJ< zQHfU7L$^!A5O%R!tl+V~gN^sG{xNpRW~H+31GnQtltW$ZdUJbI+VI-S;=Tsafz?F_ zJolKYkn@Z%W3%bXG zc^IJ(7Pl^gLQdkZaxTMyyN&4i=&Zs~jpU_IXL&_&?+4prktA8kxcXqR%GvZ2)uFL? zyxt#MK%8VZ47S}LFo#5;Hb(O7kK3uss5Da3*a^{E7qa*;K4kL_Y=(p>CRnkG^cfKf z>IcgieFF_KcxxM#@63=s5 zSFG4oS5&R+0;Kf=Uj3(^d@e>Jdl~DcujD3L+a(da6mju zHP920g|!;d8=$nr+3gmt8jte5lMnUgk(>7}9LH^rF{ zVGlYNII!p(NLx%gc&_@W+RE6AjPw4ju-U@%d*3?SeyCea+q=>-E5DfA%){DTDoHo! zdiE&YKo8F8;d!(|m`*kTI{3IUf%uRKp}Y4t0txpz{z8q7yBL5rtg9(&h}s7KnpPGF zaIC&M>G;GF)S~G4s>}$1Ec!$BTe_GfZ)~mwrLQodC7B@C8~TnGzeWcU;F`+MoWF+% z2_jauKrSofi~x5Q^DeM50}oV()!~x7qh*R +#include +#include +#include + +#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 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()) + "/Tests/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 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()) + "/Tests/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 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()) + "/Tests/AMBEFileReader/fr_FR.indx"; + std::string ambeFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.ambe"; + CAMBEFileReader reader(indexFile, ambeFile); + bool res = reader.read(); + EXPECT_TRUE(res) << "read shall return true on existent files"; + + std::vector 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()) + "/Tests/AMBEFileReader/fr_FR.indx"; + std::string ambeFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.ambe"; + CAMBEFileReader reader(indexFile, ambeFile); + bool res = reader.read(); + EXPECT_TRUE(res) << "read shall return true on existent files"; + + std::vector 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; + } + } +} \ No newline at end of file diff --git a/Tests/AMBEFileReader/read.cpp b/Tests/AMBEFileReader/read.cpp new file mode 100644 index 0000000..99f7bf4 --- /dev/null +++ b/Tests/AMBEFileReader/read.cpp @@ -0,0 +1,62 @@ +/* + * 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 +#include +#include + +#include "AMBEFileReader.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()) + "/Tests/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()) + "/Tests/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()) + "/Tests/AMBEFileReader/fr_FR.indx"; + std::string ambeFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.ambe"; + CAMBEFileReader reader(indexFile, ambeFile); + bool res = reader.read(); + EXPECT_TRUE(res) << "read shall return true on existent files"; + } +} \ No newline at end of file From c8ec37bd8595788a415c682b9eede01f1557036b Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 26 Feb 2022 19:37:46 +0100 Subject: [PATCH 33/40] #20 using AmbeFilReader --- .vscode/tasks.json | 10 +- DGWTimeServer/TimeServerConfig.cpp | 3 +- DGWTimeServer/TimeServerDefs.h | 1 - DGWTimeServer/TimeServerThread.cpp | 324 ++++++++--------------------- DGWTimeServer/TimeServerThread.h | 65 ++---- 5 files changed, 107 insertions(+), 296 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 995a9f1..0545c42 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -65,7 +65,10 @@ "USE_GPSD=1", "DGWTimeServer/dgwtimeserver" ], - "group": "build", + "group": { + "kind": "build", + "isDefault": true + }, "problemMatcher": [] }, { @@ -91,10 +94,7 @@ "ENABLE_DEBUG=1", "USE_GPSD=1" ], - "group": { - "kind": "build", - "isDefault": true - }, + "group": "build", "problemMatcher": [] } ] diff --git a/DGWTimeServer/TimeServerConfig.cpp b/DGWTimeServer/TimeServerConfig.cpp index d2c4fab..0a2f170 100644 --- a/DGWTimeServer/TimeServerConfig.cpp +++ b/DGWTimeServer/TimeServerConfig.cpp @@ -107,10 +107,9 @@ bool CTimeServerConfig::loadTimeServer(const CConfig & cfg) ret = cfg.getValue("timeserver", "address", m_timeServer.address, 0, 1024, "127.0.0.1") && ret; std::string format; - ret = cfg.getValue("timeserver", "format", format, "voiceandtext", {"voice", "text", "voiceandtext"}) && ret; + 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; - else if(format == "voiceandtext") m_timeServer.format = FORMAT_VOICE_ALL; 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;; diff --git a/DGWTimeServer/TimeServerDefs.h b/DGWTimeServer/TimeServerDefs.h index 8917675..b6910c2 100644 --- a/DGWTimeServer/TimeServerDefs.h +++ b/DGWTimeServer/TimeServerDefs.h @@ -44,6 +44,5 @@ enum INTERVAL { enum FORMAT { FORMAT_VOICE_TIME, - FORMAT_VOICE_ALL, FORMAT_TEXT_TIME }; diff --git a/DGWTimeServer/TimeServerThread.cpp b/DGWTimeServer/TimeServerThread.cpp index 696099f..be8d20e 100644 --- a/DGWTimeServer/TimeServerThread.cpp +++ b/DGWTimeServer/TimeServerThread.cpp @@ -46,32 +46,23 @@ m_addressStr(), m_language(LANG_ENGLISH_UK_1), m_format(FORMAT_VOICE_TIME), m_interval(INTERVAL_15MINS), -m_ambe(NULL), -m_ambeLength(0U), -m_index(), -m_seqNo(0U), -m_in(0U), -m_encoder(), -m_data(NULL), +m_data(), m_killed(false), -m_dataPath("") +m_dataPath(""), +m_ambeFileReader(nullptr) { CHeaderData::initialise(); - m_address.s_addr = INADDR_NONE; - - m_data = new CAMBEData*[MAX_FRAMES]; - - for (unsigned int i = 0U; i < MAX_FRAMES; i++) - m_data[i] = nullptr; + m_address.s_addr = INADDR_NONE; } CTimeServerThread::~CTimeServerThread() { - for (auto it = m_index.begin(); it != m_index.end(); it++) - delete it->second; + for(auto d : m_data) + delete d; - delete[] m_ambe; - delete[] m_data; + m_data.clear(); + + delete[] m_ambeFileReader; } void * CTimeServerThread::Entry() @@ -158,8 +149,8 @@ bool CTimeServerThread::setGateway(const std::string& callsign, const std::strin m_callsign.push_back(' '); m_addressStr.assign(address); - m_address = CUDPReaderWriter::lookup(address); m_dataPath.assign(dataPath); + m_address = CUDPReaderWriter::lookup(address); return true; } @@ -988,197 +979,103 @@ bool CTimeServerThread::loadAMBE() break; } - bool ret = readAMBE(m_dataPath, ambeFileName); - if (!ret) { - delete[] m_ambe; - m_ambe = NULL; - return false; - } + m_ambeFileReader = new CAMBEFileReader(m_dataPath + "/" + indxFileName, m_dataPath + "/" + ambeFileName); + bool ret = m_ambeFileReader->read(); - ret = readIndex(m_dataPath, indxFileName); if (!ret) { - delete[] m_ambe; - m_ambe = NULL; + delete[] m_ambeFileReader; + m_ambeFileReader = nullptr; return false; } return true; } -bool CTimeServerThread::readAMBE(const std::string& dir, const std::string& name) +void CTimeServerThread::buildAudio(const std::vector& words, CSlowDataEncoder& slowDataEncoder) { - std::string fileName = dir + "/" + name; - struct stat sbuf; - - if (stat(fileName.c_str(), &sbuf)) { - CLog::logInfo("File %s not readable\n", fileName.c_str()); - fileName.append("/data/"); - fileName += name; - if (stat(fileName.c_str(), &sbuf)) { - CLog::logInfo("File %s not readable\n", fileName.c_str()); - return false; - } - } - unsigned int fsize = sbuf.st_size; - - FILE *file = fopen(fileName.c_str(), "rb"); - if (NULL == file) { - CLog::logInfo("Cannot open %s for reading\n", fileName.c_str()); - return false; - } - - CLog::logInfo("Reading %s\n", fileName.c_str()); + unsigned int seqNo = 0U; - unsigned char buffer[VOICE_FRAME_LENGTH_BYTES]; + m_data.clear(); - size_t n = fread(buffer, sizeof(unsigned char), 4, file); - if (n != 4) { - CLog::logError("Unable to read the header from %s\n", fileName.c_str()); - fclose(file); - return false; - } + if(words.size() == 0U || m_ambeFileReader == nullptr) + CLog::logWarning("No words, falling back to text only"); - if (memcmp(buffer, "AMBE", 4)) { - CLog::logError("Invalid header from %s\n", fileName.c_str()); - fclose(file); - return false; - } + if(m_format == FORMAT_VOICE_TIME && words.size() != 0U) { + // Build the audio + m_ambeFileReader->lookup(" ", m_data); + m_ambeFileReader->lookup(" ", m_data); + m_ambeFileReader->lookup(" ", m_data); + m_ambeFileReader->lookup(" ", m_data); - // Length of the file minus the header - unsigned int length = fsize - 4U; + for (unsigned int i = 0U; i < words.size(); i++) + m_ambeFileReader->lookup(words.at(i), m_data); - // 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; + m_ambeFileReader->lookup(" ", m_data); + m_ambeFileReader->lookup(" ", m_data); + m_ambeFileReader->lookup(" ", m_data); + m_ambeFileReader->lookup(" ", m_data); - // 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); + for(unsigned int i = 0U; i < m_data.size(); i++) { + m_data[i]->setDestination(m_address, G2_DV_PORT); + m_data[i]->setSeq(seqNo); - n = fread(p, 1, length, file); - if (n != length) { - CLog::logError("Unable to read the AMBE data from %s\n", fileName.c_str()); - fclose(file); - delete[] m_ambe; - m_ambe = NULL; - return false; - } + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + m_data[i]->getData(buffer, DV_FRAME_LENGTH_BYTES); - fclose(file); + // Insert sync bytes when the sequence number is zero, slow data otherwise + if (seqNo == 0U) { + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + slowDataEncoder.sync(); + } else { + slowDataEncoder.getInterleavedData(buffer + VOICE_FRAME_LENGTH_BYTES); + } - return true; -} + m_data[i]->setData(buffer, DV_FRAME_LENGTH_BYTES); -bool CTimeServerThread::readIndex(const std::string& dir, const std::string& name) -{ - std::string fileName = dir + "/" + name; - struct stat sbuf; - - if (stat(fileName.c_str(), &sbuf)) { - CLog::logInfo("File %s not readable\n", fileName.c_str()); - fileName.append("/data/"); - fileName += name; - if (stat(fileName.c_str(), &sbuf)) { - CLog::logInfo("File %s not readable\n", fileName.c_str()); - return false; + seqNo++; + if(seqNo >= 21U) seqNo = 0U; } } + else { + for (unsigned int i = 0U; i < 21U; i++, seqNo++) { + CAMBEData* dataOut = new CAMBEData; + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + dataOut->setDestination(m_address, G2_DV_PORT); + dataOut->setSeq(i); - FILE *file = fopen(fileName.c_str(), "r"); - if (NULL == file) { - CLog::logInfo("Cannot open %s for reading\n", fileName.c_str()); - return false; - } - - // Add a silence entry at the beginning - m_index[" "] = new CIndexRecord(" ", 0, SILENCE_LENGTH); - - CLog::logInfo("Reading %s\n", fileName.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); + ::memcpy(buffer + 0U, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); - 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); + // Insert sync bytes when the sequence number is zero, slow data otherwise + if (i == 0U) { + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + slowDataEncoder.sync(); + } else { + slowDataEncoder.getTextData(buffer + VOICE_FRAME_LENGTH_BYTES); } - } - } - fclose(file); - - return true; -} - -bool CTimeServerThread::lookup(const std::string &id) -{ - CIndexRecord* info = m_index[id]; - if (info == NULL) { - // wxLogError(("Cannot find the AMBE index for *%s*"), id.c_str()); - return false; - } - - 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; - - CAMBEData* dataOut = new CAMBEData; - dataOut->setDestination(m_address, G2_DV_PORT); - dataOut->setSeq(m_seqNo); - - unsigned char buffer[DV_FRAME_LENGTH_BYTES]; - ::memcpy(buffer + 0U, dataIn, VOICE_FRAME_LENGTH_BYTES); - - // Insert sync bytes when the sequence number is zero, slow data otherwise - if (m_seqNo == 0U) { - ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); - m_encoder.sync(); + dataOut->setData(buffer, DV_FRAME_LENGTH_BYTES); - } else { - m_encoder.getInterleavedData(buffer + VOICE_FRAME_LENGTH_BYTES); + m_data.push_back(dataOut); } - - dataOut->setData(buffer, DV_FRAME_LENGTH_BYTES); - - m_seqNo++; - if (m_seqNo == 21U) - m_seqNo = 0U; - - m_data[m_in] = dataOut; - m_in++; } - return true; -} + if(seqNo >= 21U) { + seqNo = 0U; + } -void CTimeServerThread::end() -{ CAMBEData* dataOut = new CAMBEData; dataOut->setData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES); dataOut->setDestination(m_address, G2_DV_PORT); - dataOut->setSeq(m_seqNo); + dataOut->setSeq(seqNo); dataOut->setEnd(true); - m_data[m_in] = dataOut; - m_in++; + m_data.push_back(dataOut); } bool CTimeServerThread::send(const std::vector &words, unsigned int hour, unsigned int min) { + CSlowDataEncoder encoder; + CHeaderData header; header.setMyCall1(m_callsign); header.setRptCall1(m_callsignG); @@ -1241,78 +1138,26 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int break; } - m_encoder.setHeaderData(header); - m_encoder.setTextData(slowData); + encoder.setHeaderData(header); + encoder.setTextData(slowData); - m_in = 0U; + buildAudio(words, encoder); - if (m_format != FORMAT_TEXT_TIME) { - std::string text = words.at(0U); - for (unsigned int i = 1U; i < words.size(); i++) { - text.push_back(' '); - text += words.at(i); - } + if (m_data.size() == 0U) { + CLog::logWarning(("Not sending, no audio files loaded")); + return false; + } + if(m_format == FORMAT_VOICE_TIME) { + std::string text = boost::algorithm::join(words, " "); boost::replace_all(text, "_", " "); - CLog::logInfo(("Sending voice \"%s\", sending text \"%s\""), text.c_str(), slowData.c_str()); - - m_seqNo = 0U; - - // Build the audio - lookup((" ")); - lookup((" ")); - lookup((" ")); - lookup((" ")); - - for (unsigned int i = 0U; i < words.size(); i++) - lookup(words.at(i)); - - lookup((" ")); - lookup((" ")); - lookup((" ")); - lookup((" ")); - - end(); - } else { - CLog::logInfo(("Sending text \"%s\""), slowData.c_str()); - - for (unsigned int i = 0U; i < 21U; i++) { - CAMBEData* dataOut = new CAMBEData; - dataOut->setDestination(m_address, G2_DV_PORT); - dataOut->setSeq(i); - - unsigned char buffer[DV_FRAME_LENGTH_BYTES]; - ::memcpy(buffer + 0U, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); - - // Insert sync bytes when the sequence number is zero, slow data otherwise - if (i == 0U) { - ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); - m_encoder.sync(); - } else { - m_encoder.getTextData(buffer + VOICE_FRAME_LENGTH_BYTES); - } - - dataOut->setData(buffer, DV_FRAME_LENGTH_BYTES); - - m_data[m_in] = dataOut; - m_in++; - } - - CAMBEData* dataOut = new CAMBEData; - dataOut->setData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES); - dataOut->setDestination(m_address, G2_DV_PORT); - dataOut->setSeq(0U); - dataOut->setEnd(true); - - m_data[m_in] = dataOut; - m_in++; + CLog::logInfo("Sending voice \"%s\", sending text \"%s\"", text.c_str(), slowData.c_str()); } - - if (m_in == 0U) { - CLog::logWarning(("Not sending, no audio files loaded")); - return false; + else { + CLog::logInfo("Sending text \"%s\"", slowData.c_str()); } + // Build id and socket lists std::vector ids; std::vector sockets; for(auto rpt : m_repeaters) { @@ -1321,6 +1166,7 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int ids.push_back(CHeaderData::createId()); } + // open them all bool allOpen = std::all_of(sockets.begin(), sockets.end(), [](CUDPReaderWriter* s) { return s->open(); }); if(allOpen) { //send headers @@ -1332,7 +1178,7 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int Sleep(5); } - // send audio + // send audio bool loop = true; unsigned int out = 0U; auto start = std::chrono::high_resolution_clock::now(); @@ -1353,7 +1199,7 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int m_data[out] = nullptr; out++; - if (m_in == out) { + if (out >= m_data.size()) { loop = false; break; } @@ -1361,6 +1207,8 @@ bool CTimeServerThread::send(const std::vector &words, unsigned int } } + m_data.clear(); + for(auto socket : sockets) { socket->close(); delete socket; diff --git a/DGWTimeServer/TimeServerThread.h b/DGWTimeServer/TimeServerThread.h index 3abe883..36bb9c7 100644 --- a/DGWTimeServer/TimeServerThread.h +++ b/DGWTimeServer/TimeServerThread.h @@ -27,37 +27,7 @@ #include "HeaderData.h" #include "AMBEData.h" #include "Thread.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; -}; - +#include "AMBEFileReader.h" class CTimeServerThread : public CThread { @@ -72,23 +42,19 @@ public: void kill(); private: - std::string m_callsign; + std::string m_callsign; std::vector m_repeaters; - std::string m_callsignG; - in_addr m_address; - std::string m_addressStr; - LANGUAGE m_language; - FORMAT m_format; - INTERVAL m_interval; - unsigned char* m_ambe; - unsigned int m_ambeLength; - std::unordered_map m_index; - unsigned int m_seqNo; - unsigned int m_in; - CSlowDataEncoder m_encoder; - CAMBEData** m_data; - bool m_killed; - std::string m_dataPath; + 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 m_data; + bool m_killed; + std::string m_dataPath; + CAMBEFileReader * m_ambeFileReader; void sendTime(unsigned int hour, unsigned int min); @@ -110,9 +76,8 @@ private: bool sendData(CUDPReaderWriter& socket, const CAMBEData& data); bool loadAMBE(); - bool readAMBE(const std::string& dir, const std::string& name); - bool readIndex(const std::string& dir, const std::string& name); bool lookup(const std::string& id); - void end(); + + void buildAudio(const std::vector& words, CSlowDataEncoder& slowDataEncoder); }; From e05493633e561348531039d2dc089fedcb63ec3f Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Thu, 10 Mar 2022 20:40:24 +0100 Subject: [PATCH 34/40] #20 add some comment --- DGWTimeServer/TimeServerThread.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DGWTimeServer/TimeServerThread.cpp b/DGWTimeServer/TimeServerThread.cpp index be8d20e..e3f90ff 100644 --- a/DGWTimeServer/TimeServerThread.cpp +++ b/DGWTimeServer/TimeServerThread.cpp @@ -1015,6 +1015,8 @@ void CTimeServerThread::buildAudio(const std::vector& words, CSlowD m_ambeFileReader->lookup(" ", m_data); m_ambeFileReader->lookup(" ", m_data); + + // add the slow data for(unsigned int i = 0U; i < m_data.size(); i++) { m_data[i]->setDestination(m_address, G2_DV_PORT); m_data[i]->setSeq(seqNo); From 0e940f117e0a66e91213519917960f684719bf9e Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Fri, 11 Mar 2022 17:42:58 +0100 Subject: [PATCH 35/40] #20 audio Unit now also using ambe fiel reader --- .vscode/tasks.json | 10 +- Common/AudioUnit.cpp | 368 +++++++++++++------------------------------ Common/AudioUnit.h | 47 +----- 3 files changed, 118 insertions(+), 307 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 0545c42..587d251 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -26,7 +26,10 @@ "USE_GPSD=1", "DStarGateway/dstargateway" ], - "group": "build", + "group": { + "kind": "build", + "isDefault": true + }, "problemMatcher": [] }, { @@ -65,10 +68,7 @@ "USE_GPSD=1", "DGWTimeServer/dgwtimeserver" ], - "group": { - "kind": "build", - "isDefault": true - }, + "group": "build", "problemMatcher": [] }, { diff --git a/Common/AudioUnit.cpp b/Common/AudioUnit.cpp index 66684a0..997acc9 100644 --- a/Common/AudioUnit.cpp +++ b/Common/AudioUnit.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "DStarDefines.h" #include "HeaderData.h" @@ -30,9 +31,8 @@ #include "Utils.h" #include "Log.h" -unsigned char* CAudioUnit::m_ambe = NULL; -unsigned int CAudioUnit::m_ambeLength = 0U; -std::map CAudioUnit::m_index; + +CAMBEFileReader * CAudioUnit::m_ambeFilereader = nullptr; TEXT_LANG CAudioUnit::m_language = TL_ENGLISH_UK; @@ -46,6 +46,11 @@ void CAudioUnit::initialise() void CAudioUnit::setLanguage(const std::string & dir, TEXT_LANG language) { + if(m_ambeFilereader != nullptr) { + delete m_ambeFilereader; + m_ambeFilereader = nullptr; + } + m_language = language; std::string ambeFileName; @@ -94,32 +99,22 @@ void CAudioUnit::setLanguage(const std::string & dir, TEXT_LANG language) break; } - bool ret = readAMBE(dir, ambeFileName); - if (!ret) { - delete[] m_ambe; - m_ambe = NULL; - return; - } - - ret = readIndex(dir, indxFileName); - if (!ret) { - delete[] m_ambe; - m_ambe = NULL; + m_ambeFilereader = new CAMBEFileReader(dir + "/" + indxFileName, dir + "/" + ambeFileName); + bool ret = m_ambeFilereader->read(); + if(!ret) { + delete m_ambeFilereader; + m_ambeFilereader = nullptr; } } void CAudioUnit::finalise() { - for (std::map::iterator it = m_index.begin(); it != m_index.end(); ++it) - delete it->second; - - delete[] m_ambe; + delete m_ambeFilereader; } CAudioUnit::CAudioUnit(IRepeaterCallback* handler, const std::string& callsign) : m_handler(handler), m_callsign(callsign), -m_encoder(), m_status(AS_IDLE), m_linkStatus(LS_NONE), m_tempLinkStatus(LS_NONE), @@ -129,28 +124,24 @@ m_reflector(), m_tempReflector(), m_hasTemporary(false), m_timer(1000U, REPLY_TIME), -m_data(NULL), -m_in(0U), -m_out(0U), -m_seqNo(0U) //, +m_data(), +m_out(0U) //m_time() { assert(handler != NULL); - - m_data = new CAMBEData*[MAX_FRAMES]; - - for (unsigned int i = 0U; i < MAX_FRAMES; i++) - m_data[i] = NULL; } CAudioUnit::~CAudioUnit() { - delete[] m_data; + for (auto item : m_data) { + delete item; + } + m_data.clear(); } void CAudioUnit::sendStatus() { - if (m_ambe == NULL) + if (m_ambeFilereader == nullptr) return; if (m_status != AS_IDLE) @@ -190,7 +181,6 @@ void CAudioUnit::clock(unsigned int ms) m_timer.stop(); m_out = 0U; - m_seqNo = 0U; m_status = AS_TRANSMIT; m_time = std::chrono::high_resolution_clock::now(); @@ -199,29 +189,21 @@ void CAudioUnit::clock(unsigned int ms) } if (m_status == AS_TRANSMIT) { - std::chrono::high_resolution_clock::time_point hrctp = std::chrono::high_resolution_clock::now(); - auto elapse = std::chrono::duration_cast(hrctp - m_time); - unsigned int needed = elapse.count() / DSTAR_FRAME_TIME_MS; + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + unsigned int needed = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - m_time).count(); + needed /= DSTAR_FRAME_TIME_MS; - while (m_out < needed) { + while (m_out < needed && m_out < m_data.size()) { CAMBEData* data = m_data[m_out]; - m_data[m_out] = NULL; m_out++; - - if (m_in == m_out) - data->setEnd(true); - + CLog::logTrace("m_out %u, needed %u, m_data %u", m_out, needed, m_data.size()); m_handler->process(*data, DIR_INCOMING, AS_INFO); + } - delete data; - - if (m_in == m_out) { - m_in = 0U; - m_out = 0U; - m_status = AS_IDLE; - m_timer.stop(); - return; - } + if (m_out >= m_data.size()) { + m_out = 0U; + m_status = AS_IDLE; + m_timer.stop(); } return; @@ -230,63 +212,14 @@ void CAudioUnit::clock(unsigned int ms) void CAudioUnit::cancel() { - for (unsigned int i = 0U; i < MAX_FRAMES; i++) { - if (m_data[i] != NULL) { - delete m_data[i]; - m_data[i] = NULL; - } - } - + CLog::logTrace("Audio Unit Cancel"); m_status = AS_IDLE; m_out = 0U; - m_in = 0U; m_timer.stop(); } -bool CAudioUnit::lookup(unsigned int id, const std::string &name) -{ - CIndexRecord* info = m_index[name]; - if (info == NULL) { - // CLog::logError("Cannot find the AMBE index for *%s*", name.c_str()); - return false; - } - - 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; - - CAMBEData* dataOut = new CAMBEData; - dataOut->setSeq(m_seqNo); - dataOut->setId(id); - - unsigned char buffer[DV_FRAME_LENGTH_BYTES]; - memcpy(buffer + 0U, dataIn, VOICE_FRAME_LENGTH_BYTES); - - // Insert sync bytes when the sequence number is zero, slow data otherwise - if (m_seqNo == 0U) { - memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); - m_encoder.sync(); - } else { - m_encoder.getTextData(buffer + VOICE_FRAME_LENGTH_BYTES); - } - - dataOut->setData(buffer, DV_FRAME_LENGTH_BYTES); - - m_seqNo++; - if (m_seqNo == 21) - m_seqNo = 0; - - m_data[m_in] = dataOut; - m_in++; - } - - return true; -} - -void CAudioUnit::spellReflector(unsigned int id, const std::string &reflector) +void CAudioUnit::spellReflector(const std::string &reflector) { unsigned int length = reflector.size(); @@ -294,11 +227,10 @@ void CAudioUnit::spellReflector(unsigned int id, const std::string &reflector) std::string c = reflector.substr(i, 1); if (c.compare(" ")) - lookup(id, c); + m_ambeFilereader->lookup(c, m_data); } char c = reflector.at(length - 1); - if (c == ' ') return; @@ -306,197 +238,113 @@ void CAudioUnit::spellReflector(unsigned int id, const std::string &reflector) cstr.push_back(c); if (m_linkStatus == LS_LINKING_DCS || m_linkStatus == LS_LINKED_DCS || m_linkStatus == LS_LINKING_CCS || m_linkStatus == LS_LINKED_CCS) { - lookup(id, cstr); + m_ambeFilereader->lookup(cstr, m_data); return; } switch (c) { case 'A': - lookup(id, "alpha"); + m_ambeFilereader->lookup("alpha", m_data); break; case 'B': - lookup(id, "bravo"); + m_ambeFilereader->lookup("bravo", m_data); break; case 'C': - lookup(id, "charlie"); + m_ambeFilereader->lookup("charlie", m_data); break; case 'D': - lookup(id, "delta"); + m_ambeFilereader->lookup("delta", m_data); break; default: - lookup(id, cstr); + m_ambeFilereader->lookup(cstr, m_data); break; } } -bool CAudioUnit::readAMBE(const std::string& dir, const std::string& name) +void CAudioUnit::sendStatus(LINK_STATUS status, const std::string& reflector, const std::string &text) { - std::string fileName = dir + "/" + name; - struct stat sbuf; - - if (stat(fileName.c_str(), &sbuf)) { - CLog::logInfo("File %s not readable\n", fileName.c_str()); - fileName.append("/data/"); - fileName += name; - if (stat(fileName.c_str(), &sbuf)) { - CLog::logInfo("File %s not readable\n", fileName.c_str()); - return false; - } - } - unsigned int fsize = sbuf.st_size; - - FILE *file = fopen(fileName.c_str(), "rb"); - if (NULL == file) { - CLog::logInfo("Cannot open %s for reading\n", fileName.c_str()); - return false; - } - - CLog::logInfo("Reading %s\n", fileName.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", fileName.c_str()); - fclose(file); - return false; - } + CLog::logTrace("Audio Unit sendStatus"); - if (memcmp(buffer, "AMBE", 4)) { - CLog::logError("Invalid header from %s\n", fileName.c_str()); - fclose(file); - return false; + // do some clean up, delete old message + for (auto item : m_data) { + delete item; } + m_data.clear(); - // 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", fileName.c_str()); - fclose(file); - delete[] m_ambe; - m_ambe = NULL; - return false; - } + // Create the message + m_ambeFilereader->lookup(" ", m_data); + m_ambeFilereader->lookup(" ", m_data); + m_ambeFilereader->lookup(" ", m_data); + m_ambeFilereader->lookup(" ", m_data); - fclose(file); + bool found; - return true; -} - -bool CAudioUnit::readIndex(const std::string& dir, const std::string& name) -{ - std::string fileName = dir + "/" + name; - struct stat sbuf; - - if (stat(fileName.c_str(), &sbuf)) { - CLog::logInfo("File %s not readable\n", fileName.c_str()); - fileName.append("/data/"); - fileName += name; - if (stat(fileName.c_str(), &sbuf)) { - CLog::logInfo("File %s not readable\n", fileName.c_str()); - return false; - } - } - - FILE *file = fopen(fileName.c_str(), "r"); - if (NULL == file) { - CLog::logInfo("Cannot open %s for reading\n", fileName.c_str()); - return false; + switch (status) { + case LS_NONE: + m_ambeFilereader->lookup("notlinked", m_data); + break; + case LS_LINKED_CCS: + case LS_LINKED_DCS: + case LS_LINKED_DPLUS: + case LS_LINKED_DEXTRA: + case LS_LINKED_LOOPBACK: + found = m_ambeFilereader->lookup("linkedto", m_data); + if (!found) { + m_ambeFilereader->lookup("linked", m_data); + m_ambeFilereader->lookup("2", m_data); + } + spellReflector(reflector); + break; + default: + found = m_ambeFilereader->lookup("linkingto", m_data); + if (!found) { + m_ambeFilereader->lookup("linking", m_data); + m_ambeFilereader->lookup("2", m_data); + } + spellReflector(reflector); + break; } - // Add a silence entry at the beginning - m_index[" "] = new CIndexRecord(" ", 0, SILENCE_LENGTH); + m_ambeFilereader->lookup(" ", m_data); + m_ambeFilereader->lookup(" ", m_data); + m_ambeFilereader->lookup(" ", m_data); + m_ambeFilereader->lookup(" ", m_data); - CLog::logInfo("Reading %s\n", fileName.c_str()); + unsigned int id = CHeaderData::createId(); + // RPT1 and RPT2 will be filled in later + CHeaderData header; + header.setMyCall1(m_callsign); + header.setMyCall2("INFO"); + header.setYourCall("CQCQCQ "); + header.setId(id); - char line[128]; - while (fgets(line, 128, file)) { + CSlowDataEncoder slowDataEncoder; + slowDataEncoder.setTextData(text); + unsigned int seqNo = 0U; - 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())); + // add the slow data, id, seq num etc ... + for(unsigned int i = 0U; i < m_data.size(); i++) { + m_data[i]->setId(id); + m_data[i]->setSeq(seqNo); - if (name.size() && strt.size() && leng.size()) { - unsigned long start = std::stoul(strt); - unsigned long length = std::stoul(leng); + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + m_data[i]->getData(buffer, DV_FRAME_LENGTH_BYTES); - 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); - } + // Insert sync bytes when the sequence number is zero, slow data otherwise + if (seqNo == 0U) { + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + slowDataEncoder.sync(); + } else { + slowDataEncoder.getInterleavedData(buffer + VOICE_FRAME_LENGTH_BYTES); } - } - - fclose(file); - - return true; -} -void CAudioUnit::sendStatus(LINK_STATUS status, const std::string& reflector, const std::string &text) -{ - m_encoder.setTextData(text); - - // Create the message - unsigned int id = CHeaderData::createId(); - - lookup(id, " "); - lookup(id, " "); - lookup(id, " "); - lookup(id, " "); - - bool found; - - switch (status) { - case LS_NONE: - lookup(id, "notlinked"); - break; - case LS_LINKED_CCS: - case LS_LINKED_DCS: - case LS_LINKED_DPLUS: - case LS_LINKED_DEXTRA: - case LS_LINKED_LOOPBACK: - found = lookup(id, "linkedto"); - if (!found) { - lookup(id, "linked"); - lookup(id, "2"); - } - spellReflector(id, reflector); - break; - default: - found = lookup(id, "linkingto"); - if (!found) { - lookup(id, "linking"); - lookup(id, "2"); - } - spellReflector(id, reflector); - break; - } + m_data[i]->setData(buffer, DV_FRAME_LENGTH_BYTES); - lookup(id, " "); - lookup(id, " "); - lookup(id, " "); - lookup(id, " "); + seqNo++; + if(seqNo >= 21U) seqNo = 0U; + } - // RPT1 and RPT2 will be filled in later - CHeaderData header; - header.setMyCall1(m_callsign); - header.setMyCall2("INFO"); - header.setYourCall("CQCQCQ "); - header.setId(id); + m_data[m_data.size() - 1]->setEnd(true); - m_handler->process(header, DIR_INCOMING, AS_INFO); + m_handler->process(header, DIR_INCOMING, AS_INFO); } diff --git a/Common/AudioUnit.h b/Common/AudioUnit.h index 6cdd862..7b27ba8 100644 --- a/Common/AudioUnit.h +++ b/Common/AudioUnit.h @@ -23,42 +23,14 @@ #include #include #include +#include #include "RepeaterCallback.h" #include "SlowDataEncoder.h" #include "AMBEData.h" #include "Timer.h" #include "Defs.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; -}; +#include "AMBEFileReader.h" enum AUDIO_STATUS { AS_IDLE, @@ -87,14 +59,10 @@ public: static void finalise(); private: - static std::map m_index; - static unsigned char* m_ambe; - static unsigned int m_ambeLength; static TEXT_LANG m_language; IRepeaterCallback* m_handler; std::string m_callsign; - CSlowDataEncoder m_encoder; AUDIO_STATUS m_status; LINK_STATUS m_linkStatus; LINK_STATUS m_tempLinkStatus; @@ -104,17 +72,12 @@ private: std::string m_tempReflector; bool m_hasTemporary; CTimer m_timer; - CAMBEData** m_data; - unsigned int m_in; + std::vector m_data; + static CAMBEFileReader* m_ambeFilereader; unsigned int m_out; - unsigned int m_seqNo; std::chrono::high_resolution_clock::time_point m_time; - bool lookup(unsigned int id, const std::string& name); - void spellReflector(unsigned int id, const std::string& reflector); + void spellReflector(const std::string& reflector); void sendStatus(LINK_STATUS status, const std::string& reflector, const std::string& text); - - static bool readAMBE(const std::string& dir, const std::string& name); - static bool readIndex(const std::string& dir, const std::string& name); }; From 7864f33c3c847bbde7b559840059371b332219c8 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Fri, 11 Mar 2022 17:51:55 +0100 Subject: [PATCH 36/40] #20 reduce verbosity --- Common/AudioUnit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common/AudioUnit.cpp b/Common/AudioUnit.cpp index 997acc9..db704a6 100644 --- a/Common/AudioUnit.cpp +++ b/Common/AudioUnit.cpp @@ -196,7 +196,7 @@ void CAudioUnit::clock(unsigned int ms) while (m_out < needed && m_out < m_data.size()) { CAMBEData* data = m_data[m_out]; m_out++; - CLog::logTrace("m_out %u, needed %u, m_data %u", m_out, needed, m_data.size()); + // CLog::logTrace("m_out %u, needed %u, m_data %u", m_out, needed, m_data.size()); m_handler->process(*data, DIR_INCOMING, AS_INFO); } From 45584655f86ff93844fc448133652aa70b141668 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 12 Mar 2022 04:42:47 +0100 Subject: [PATCH 37/40] #20 remove sleep --- Common/AudioUnit.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Common/AudioUnit.cpp b/Common/AudioUnit.cpp index db704a6..0adeac4 100644 --- a/Common/AudioUnit.cpp +++ b/Common/AudioUnit.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include "DStarDefines.h" #include "HeaderData.h" @@ -189,7 +188,6 @@ void CAudioUnit::clock(unsigned int ms) } if (m_status == AS_TRANSMIT) { - std::this_thread::sleep_for(std::chrono::milliseconds(20)); unsigned int needed = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - m_time).count(); needed /= DSTAR_FRAME_TIME_MS; From 7fad5bfbc33b321e30e1673aa79b7a2547c66fb9 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 12 Mar 2022 04:59:14 +0100 Subject: [PATCH 38/40] #20 Add Readme --- DGWTimeServer/README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 DGWTimeServer/README.md diff --git a/DGWTimeServer/README.md b/DGWTimeServer/README.md new file mode 100644 index 0000000..1a48592 --- /dev/null +++ b/DGWTimeServer/README.md @@ -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 +``` \ No newline at end of file From ee4270940dfb00dbce43b99d3358c2518a409d55 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 12 Mar 2022 07:44:29 +0100 Subject: [PATCH 39/40] #20 add soem debug info --- .vscode/tasks.json | 10 +++++----- Tests/AMBEFileReader/read.cpp | 5 +++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 587d251..995a9f1 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -26,10 +26,7 @@ "USE_GPSD=1", "DStarGateway/dstargateway" ], - "group": { - "kind": "build", - "isDefault": true - }, + "group": "build", "problemMatcher": [] }, { @@ -94,7 +91,10 @@ "ENABLE_DEBUG=1", "USE_GPSD=1" ], - "group": "build", + "group": { + "kind": "build", + "isDefault": true + }, "problemMatcher": [] } ] diff --git a/Tests/AMBEFileReader/read.cpp b/Tests/AMBEFileReader/read.cpp index 99f7bf4..a60b8e6 100644 --- a/Tests/AMBEFileReader/read.cpp +++ b/Tests/AMBEFileReader/read.cpp @@ -21,6 +21,7 @@ #include #include "AMBEFileReader.h" +#include "Log.h" namespace AMBEFileReaderTests { @@ -55,6 +56,10 @@ namespace AMBEFileReaderTests { std::string indexFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.indx"; std::string ambeFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.ambe"; + + CLog::logInfo("Reading file: %s", indexFile.c_str()); + CLog::logInfo("Reading file: %s", ambeFile.c_str()); + CAMBEFileReader reader(indexFile, ambeFile); bool res = reader.read(); EXPECT_TRUE(res) << "read shall return true on existent files"; From d5b78104c633d78d2c174d4ba1bca9bd89be19e8 Mon Sep 17 00:00:00 2001 From: Geoffrey Merck Date: Sat, 12 Mar 2022 07:53:52 +0100 Subject: [PATCH 40/40] #20 fix broken test on Circle CI --- .vscode/launch.json | 2 +- Tests/AMBEFileReader/lookup.cpp | 12 ++++++------ Tests/AMBEFileReader/read.cpp | 11 ++++------- Tests/Makefile | 2 +- 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index f15168f..55284ba 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -135,7 +135,7 @@ "program": "${workspaceFolder}/Tests/dstargateway_tests", "args": [ ], "stopAtEntry": false, - "cwd": "${workspaceFolder}", + "cwd": "${workspaceFolder}/Tests/", "environment": [], "externalConsole": false, "MIMode": "gdb", diff --git a/Tests/AMBEFileReader/lookup.cpp b/Tests/AMBEFileReader/lookup.cpp index f239eae..9f75475 100644 --- a/Tests/AMBEFileReader/lookup.cpp +++ b/Tests/AMBEFileReader/lookup.cpp @@ -47,7 +47,7 @@ namespace AMBEFileReaderTests TEST_F(AMBEFileReader_lookup, onlyIndexFileExists) { - std::string indexFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.indx"; + 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"; @@ -63,7 +63,7 @@ namespace AMBEFileReaderTests TEST_F(AMBEFileReader_lookup, onlyAmbeFileExists) { - std::string ambeFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.ambe"; + 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"; @@ -79,8 +79,8 @@ namespace AMBEFileReaderTests TEST_F(AMBEFileReader_lookup, validId) { - std::string indexFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.indx"; - std::string ambeFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.ambe"; + 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"; @@ -97,8 +97,8 @@ namespace AMBEFileReaderTests TEST_F(AMBEFileReader_lookup, invalidId) { - std::string indexFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.indx"; - std::string ambeFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.ambe"; + 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"; diff --git a/Tests/AMBEFileReader/read.cpp b/Tests/AMBEFileReader/read.cpp index a60b8e6..f7b1753 100644 --- a/Tests/AMBEFileReader/read.cpp +++ b/Tests/AMBEFileReader/read.cpp @@ -38,7 +38,7 @@ namespace AMBEFileReaderTests TEST_F(AMBEFileReader_read, onlyIndexFileExists) { - std::string indexFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.indx"; + 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"; @@ -46,7 +46,7 @@ namespace AMBEFileReaderTests TEST_F(AMBEFileReader_read, onlyAmbeFileExists) { - std::string ambeFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.ambe"; + 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"; @@ -54,11 +54,8 @@ namespace AMBEFileReaderTests TEST_F(AMBEFileReader_read, bothFileExist) { - std::string indexFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.indx"; - std::string ambeFile = std::string(std::filesystem::current_path()) + "/Tests/AMBEFileReader/fr_FR.ambe"; - - CLog::logInfo("Reading file: %s", indexFile.c_str()); - CLog::logInfo("Reading file: %s", ambeFile.c_str()); + 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(); diff --git a/Tests/Makefile b/Tests/Makefile index f338acc..d3b30bb 100644 --- a/Tests/Makefile +++ b/Tests/Makefile @@ -10,7 +10,7 @@ dstargateway_tests: ../VersionInfo/GitVersion.h $(OBJS) ../APRS/APRS.a ../IRCDDB -include $(DEPS) -.PHONY run-tests: dstargateway_tests +.PHONY run-tests: dstargateway_tests ./dstargateway_tests .PHONY clean :