Merge branch 'feature/AddTimeServer_#20' into develop Closes #20

pull/32/head
Geoffrey Merck 4 years ago
commit a1cca3fbea

1
.gitignore vendored

@ -48,3 +48,4 @@ Tests/dstargateway_tests
DGWRemoteControl/dgwremotecontrol DGWRemoteControl/dgwremotecontrol
DGWVoiceTransmit/dgwvoicetransmit DGWVoiceTransmit/dgwvoicetransmit
DGWTextTransmit/dgwtexttransmit DGWTextTransmit/dgwtexttransmit
DGWTimeServer/dgwtimeserver

@ -80,6 +80,30 @@
} }
] ]
}, },
{
"name": "(gdb) dgwtimeserver",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/DGWTimeServer/dgwtimeserver",
"args": ["${workspaceFolder}/Sandbox/__timeserver_test.cfg"],
"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", "name": "(gdb) dgwvoicetransmit",
"type": "cppdbg", "type": "cppdbg",
@ -111,7 +135,7 @@
"program": "${workspaceFolder}/Tests/dstargateway_tests", "program": "${workspaceFolder}/Tests/dstargateway_tests",
"args": [ ], "args": [ ],
"stopAtEntry": false, "stopAtEntry": false,
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}/Tests/",
"environment": [], "environment": [],
"externalConsole": false, "externalConsole": false,
"MIMode": "gdb", "MIMode": "gdb",

23
.vscode/tasks.json vendored

@ -13,10 +13,7 @@
"USE_GPSD=1", "USE_GPSD=1",
"all" "all"
], ],
"group": { "group": "build",
"kind": "build",
"isDefault": true
},
"problemMatcher": [] "problemMatcher": []
}, },
{ {
@ -58,6 +55,19 @@
"group": "build", "group": "build",
"problemMatcher": [] "problemMatcher": []
}, },
{
"label": "Build DGWTimeServer",
"type": "shell",
"command": "make",
"args": [
"-j3",
"ENABLE_DEBUG=1",
"USE_GPSD=1",
"DGWTimeServer/dgwtimeserver"
],
"group": "build",
"problemMatcher": []
},
{ {
"label": "Build DGWVoiceTransmit", "label": "Build DGWVoiceTransmit",
"type": "shell", "type": "shell",
@ -81,7 +91,10 @@
"ENABLE_DEBUG=1", "ENABLE_DEBUG=1",
"USE_GPSD=1" "USE_GPSD=1"
], ],
"group": "build", "group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [] "problemMatcher": []
} }
] ]

@ -21,19 +21,19 @@
#include <fstream> #include <fstream>
#include <chrono> #include <chrono>
#include <ctime> #include <ctime>
#include <cassert>
#include "LogFileTarget.h" #include "LogFileTarget.h"
#define LOG_FILE_ROOT "dstargateway" CLogFileTarget::CLogFileTarget(LOG_SEVERITY logLevel, const std::string & dir, const std::string& fileRoot, bool rotate) :
CLogFileTarget::CLogFileTarget(LOG_SEVERITY logLevel, const std::string & dir, bool rotate) :
CLogTarget(logLevel), CLogTarget(logLevel),
m_dir(dir), m_dir(dir),
m_fileRoot(fileRoot),
m_rotate(rotate), m_rotate(rotate),
m_file(), m_file(),
m_day(0) m_day(0)
{ {
assert(!fileRoot.empty());
} }
CLogFileTarget::~CLogFileTarget() CLogFileTarget::~CLogFileTarget()
@ -53,7 +53,7 @@ void CLogFileTarget::printLogIntFixed(const std::string& msg)
std::string filename(m_dir); std::string filename(m_dir);
if(filename[filename.length() - 1U] != '/') filename.push_back('/'); 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); m_file.open(filename, std::ios::app);
if(m_file.is_open()) { if(m_file.is_open()) {
@ -80,7 +80,7 @@ void CLogFileTarget::printLogIntRotate(const std::string& msg)
if(filename[filename.length() - 1U] != '/') filename.push_back('/'); if(filename[filename.length() - 1U] != '/') filename.push_back('/');
char buf[64]; char buf[64];
std::strftime(buf, 42, "-%Y-%m-%d", now_tm); 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); m_file.open(filename, std::ios::app);
if(!m_file.is_open()) { if(!m_file.is_open()) {
std::cerr << "FAILED TO OPEN LOG FILE :" << filename; std::cerr << "FAILED TO OPEN LOG FILE :" << filename;

@ -26,7 +26,7 @@
class CLogFileTarget : public CLogTarget class CLogFileTarget : public CLogTarget
{ {
public: 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(); ~CLogFileTarget();
protected: protected:
@ -37,6 +37,7 @@ private:
void printLogIntFixed(const std::string& msg); void printLogIntFixed(const std::string& msg);
std::string buildFileName(); std::string buildFileName();
std::string m_dir; std::string m_dir;
std::string m_fileRoot;
bool m_rotate; bool m_rotate;
std::fstream m_file; std::fstream m_file;
int m_day; int m_day;

@ -30,9 +30,8 @@
#include "Utils.h" #include "Utils.h"
#include "Log.h" #include "Log.h"
unsigned char* CAudioUnit::m_ambe = NULL;
unsigned int CAudioUnit::m_ambeLength = 0U; CAMBEFileReader * CAudioUnit::m_ambeFilereader = nullptr;
std::map<std::string, CIndexRecord *> CAudioUnit::m_index;
TEXT_LANG CAudioUnit::m_language = TL_ENGLISH_UK; TEXT_LANG CAudioUnit::m_language = TL_ENGLISH_UK;
@ -46,6 +45,11 @@ void CAudioUnit::initialise()
void CAudioUnit::setLanguage(const std::string & dir, TEXT_LANG language) void CAudioUnit::setLanguage(const std::string & dir, TEXT_LANG language)
{ {
if(m_ambeFilereader != nullptr) {
delete m_ambeFilereader;
m_ambeFilereader = nullptr;
}
m_language = language; m_language = language;
std::string ambeFileName; std::string ambeFileName;
@ -94,32 +98,22 @@ void CAudioUnit::setLanguage(const std::string & dir, TEXT_LANG language)
break; break;
} }
bool ret = readAMBE(dir, ambeFileName); m_ambeFilereader = new CAMBEFileReader(dir + "/" + indxFileName, dir + "/" + ambeFileName);
if (!ret) { bool ret = m_ambeFilereader->read();
delete[] m_ambe; if(!ret) {
m_ambe = NULL; delete m_ambeFilereader;
return; m_ambeFilereader = nullptr;
}
ret = readIndex(dir, indxFileName);
if (!ret) {
delete[] m_ambe;
m_ambe = NULL;
} }
} }
void CAudioUnit::finalise() void CAudioUnit::finalise()
{ {
for (std::map<std::string, CIndexRecord *>::iterator it = m_index.begin(); it != m_index.end(); ++it) delete m_ambeFilereader;
delete it->second;
delete[] m_ambe;
} }
CAudioUnit::CAudioUnit(IRepeaterCallback* handler, const std::string& callsign) : CAudioUnit::CAudioUnit(IRepeaterCallback* handler, const std::string& callsign) :
m_handler(handler), m_handler(handler),
m_callsign(callsign), m_callsign(callsign),
m_encoder(),
m_status(AS_IDLE), m_status(AS_IDLE),
m_linkStatus(LS_NONE), m_linkStatus(LS_NONE),
m_tempLinkStatus(LS_NONE), m_tempLinkStatus(LS_NONE),
@ -129,28 +123,24 @@ m_reflector(),
m_tempReflector(), m_tempReflector(),
m_hasTemporary(false), m_hasTemporary(false),
m_timer(1000U, REPLY_TIME), m_timer(1000U, REPLY_TIME),
m_data(NULL), m_data(),
m_in(0U), m_out(0U)
m_out(0U),
m_seqNo(0U) //,
//m_time() //m_time()
{ {
assert(handler != NULL); assert(handler != NULL);
m_data = new CAMBEData*[MAX_FRAMES];
for (unsigned int i = 0U; i < MAX_FRAMES; i++)
m_data[i] = NULL;
} }
CAudioUnit::~CAudioUnit() CAudioUnit::~CAudioUnit()
{ {
delete[] m_data; for (auto item : m_data) {
delete item;
}
m_data.clear();
} }
void CAudioUnit::sendStatus() void CAudioUnit::sendStatus()
{ {
if (m_ambe == NULL) if (m_ambeFilereader == nullptr)
return; return;
if (m_status != AS_IDLE) if (m_status != AS_IDLE)
@ -190,7 +180,6 @@ void CAudioUnit::clock(unsigned int ms)
m_timer.stop(); m_timer.stop();
m_out = 0U; m_out = 0U;
m_seqNo = 0U;
m_status = AS_TRANSMIT; m_status = AS_TRANSMIT;
m_time = std::chrono::high_resolution_clock::now(); m_time = std::chrono::high_resolution_clock::now();
@ -199,29 +188,20 @@ void CAudioUnit::clock(unsigned int ms)
} }
if (m_status == AS_TRANSMIT) { if (m_status == AS_TRANSMIT) {
std::chrono::high_resolution_clock::time_point hrctp = std::chrono::high_resolution_clock::now(); unsigned int needed = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - m_time).count();
auto elapse = std::chrono::duration_cast<std::chrono::milliseconds>(hrctp - m_time); needed /= DSTAR_FRAME_TIME_MS;
unsigned int needed = elapse.count() / DSTAR_FRAME_TIME_MS;
while (m_out < needed) { while (m_out < needed && m_out < m_data.size()) {
CAMBEData* data = m_data[m_out]; CAMBEData* data = m_data[m_out];
m_data[m_out] = NULL;
m_out++; m_out++;
// CLog::logTrace("m_out %u, needed %u, m_data %u", m_out, needed, m_data.size());
if (m_in == m_out)
data->setEnd(true);
m_handler->process(*data, DIR_INCOMING, AS_INFO); m_handler->process(*data, DIR_INCOMING, AS_INFO);
}
delete data; if (m_out >= m_data.size()) {
m_out = 0U;
if (m_in == m_out) { m_status = AS_IDLE;
m_in = 0U; m_timer.stop();
m_out = 0U;
m_status = AS_IDLE;
m_timer.stop();
return;
}
} }
return; return;
@ -230,63 +210,14 @@ void CAudioUnit::clock(unsigned int ms)
void CAudioUnit::cancel() void CAudioUnit::cancel()
{ {
for (unsigned int i = 0U; i < MAX_FRAMES; i++) { CLog::logTrace("Audio Unit Cancel");
if (m_data[i] != NULL) {
delete m_data[i];
m_data[i] = NULL;
}
}
m_status = AS_IDLE; m_status = AS_IDLE;
m_out = 0U; m_out = 0U;
m_in = 0U;
m_timer.stop(); m_timer.stop();
} }
bool CAudioUnit::lookup(unsigned int id, const std::string &name) void CAudioUnit::spellReflector(const std::string &reflector)
{
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)
{ {
unsigned int length = reflector.size(); unsigned int length = reflector.size();
@ -294,11 +225,10 @@ void CAudioUnit::spellReflector(unsigned int id, const std::string &reflector)
std::string c = reflector.substr(i, 1); std::string c = reflector.substr(i, 1);
if (c.compare(" ")) if (c.compare(" "))
lookup(id, c); m_ambeFilereader->lookup(c, m_data);
} }
char c = reflector.at(length - 1); char c = reflector.at(length - 1);
if (c == ' ') if (c == ' ')
return; return;
@ -306,197 +236,113 @@ void CAudioUnit::spellReflector(unsigned int id, const std::string &reflector)
cstr.push_back(c); cstr.push_back(c);
if (m_linkStatus == LS_LINKING_DCS || m_linkStatus == LS_LINKED_DCS || if (m_linkStatus == LS_LINKING_DCS || m_linkStatus == LS_LINKED_DCS ||
m_linkStatus == LS_LINKING_CCS || m_linkStatus == LS_LINKED_CCS) { m_linkStatus == LS_LINKING_CCS || m_linkStatus == LS_LINKED_CCS) {
lookup(id, cstr); m_ambeFilereader->lookup(cstr, m_data);
return; return;
} }
switch (c) { switch (c) {
case 'A': case 'A':
lookup(id, "alpha"); m_ambeFilereader->lookup("alpha", m_data);
break; break;
case 'B': case 'B':
lookup(id, "bravo"); m_ambeFilereader->lookup("bravo", m_data);
break; break;
case 'C': case 'C':
lookup(id, "charlie"); m_ambeFilereader->lookup("charlie", m_data);
break; break;
case 'D': case 'D':
lookup(id, "delta"); m_ambeFilereader->lookup("delta", m_data);
break; break;
default: default:
lookup(id, cstr); m_ambeFilereader->lookup(cstr, m_data);
break; 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; CLog::logTrace("Audio Unit sendStatus");
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)) { // do some clean up, delete old message
CLog::logError("Invalid header from %s\n", fileName.c_str()); for (auto item : m_data) {
fclose(file); delete item;
return false;
} }
m_data.clear();
// Length of the file minus the header // Create the message
unsigned int length = fsize - 4U; m_ambeFilereader->lookup(" ", m_data);
m_ambeFilereader->lookup(" ", m_data);
// Hold the file data plus silence at the end m_ambeFilereader->lookup(" ", m_data);
m_ambe = new unsigned char[length + SILENCE_LENGTH * VOICE_FRAME_LENGTH_BYTES]; m_ambeFilereader->lookup(" ", m_data);
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); bool found;
return true; switch (status) {
} case LS_NONE:
m_ambeFilereader->lookup("notlinked", m_data);
bool CAudioUnit::readIndex(const std::string& dir, const std::string& name) break;
{ case LS_LINKED_CCS:
std::string fileName = dir + "/" + name; case LS_LINKED_DCS:
struct stat sbuf; case LS_LINKED_DPLUS:
case LS_LINKED_DEXTRA:
if (stat(fileName.c_str(), &sbuf)) { case LS_LINKED_LOOPBACK:
CLog::logInfo("File %s not readable\n", fileName.c_str()); found = m_ambeFilereader->lookup("linkedto", m_data);
fileName.append("/data/"); if (!found) {
fileName += name; m_ambeFilereader->lookup("linked", m_data);
if (stat(fileName.c_str(), &sbuf)) { m_ambeFilereader->lookup("2", m_data);
CLog::logInfo("File %s not readable\n", fileName.c_str()); }
return false; spellReflector(reflector);
} break;
} default:
found = m_ambeFilereader->lookup("linkingto", m_data);
FILE *file = fopen(fileName.c_str(), "r"); if (!found) {
if (NULL == file) { m_ambeFilereader->lookup("linking", m_data);
CLog::logInfo("Cannot open %s for reading\n", fileName.c_str()); m_ambeFilereader->lookup("2", m_data);
return false; }
spellReflector(reflector);
break;
} }
// Add a silence entry at the beginning m_ambeFilereader->lookup(" ", m_data);
m_index[" "] = new CIndexRecord(" ", 0, SILENCE_LENGTH); 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]; CSlowDataEncoder slowDataEncoder;
while (fgets(line, 128, file)) { slowDataEncoder.setTextData(text);
unsigned int seqNo = 0U;
if (strlen(line) && '#'!=line[0]) { // add the slow data, id, seq num etc ...
const std::string space(" \t\r\n"); for(unsigned int i = 0U; i < m_data.size(); i++) {
std::string name(strtok(line, space.c_str())); m_data[i]->setId(id);
std::string strt(strtok(NULL, space.c_str())); m_data[i]->setSeq(seqNo);
std::string leng(strtok(NULL, space.c_str()));
if (name.size() && strt.size() && leng.size()) { unsigned char buffer[DV_FRAME_LENGTH_BYTES];
unsigned long start = std::stoul(strt); m_data[i]->getData(buffer, DV_FRAME_LENGTH_BYTES);
unsigned long length = std::stoul(leng);
if (start >= m_ambeLength || (start + length) >= m_ambeLength) // Insert sync bytes when the sequence number is zero, slow data otherwise
CLog::logInfo("The start or end for *%s* is out of range, start: %lu, end: %lu\n", name.c_str(), start, start + length); if (seqNo == 0U) {
else ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES);
m_index[name] = new CIndexRecord(name, start + SILENCE_LENGTH, length); 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_data[i]->setData(buffer, DV_FRAME_LENGTH_BYTES);
{
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;
}
lookup(id, " "); seqNo++;
lookup(id, " "); if(seqNo >= 21U) seqNo = 0U;
lookup(id, " "); }
lookup(id, " ");
// RPT1 and RPT2 will be filled in later m_data[m_data.size() - 1]->setEnd(true);
CHeaderData header;
header.setMyCall1(m_callsign);
header.setMyCall2("INFO");
header.setYourCall("CQCQCQ ");
header.setId(id);
m_handler->process(header, DIR_INCOMING, AS_INFO); m_handler->process(header, DIR_INCOMING, AS_INFO);
} }

@ -23,42 +23,14 @@
#include <string> #include <string>
#include <map> #include <map>
#include <chrono> #include <chrono>
#include <vector>
#include "RepeaterCallback.h" #include "RepeaterCallback.h"
#include "SlowDataEncoder.h" #include "SlowDataEncoder.h"
#include "AMBEData.h" #include "AMBEData.h"
#include "Timer.h" #include "Timer.h"
#include "Defs.h" #include "Defs.h"
#include "AMBEFileReader.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;
};
enum AUDIO_STATUS { enum AUDIO_STATUS {
AS_IDLE, AS_IDLE,
@ -87,14 +59,10 @@ public:
static void finalise(); static void finalise();
private: private:
static std::map<std::string, CIndexRecord *> m_index;
static unsigned char* m_ambe;
static unsigned int m_ambeLength;
static TEXT_LANG m_language; static TEXT_LANG m_language;
IRepeaterCallback* m_handler; IRepeaterCallback* m_handler;
std::string m_callsign; std::string m_callsign;
CSlowDataEncoder m_encoder;
AUDIO_STATUS m_status; AUDIO_STATUS m_status;
LINK_STATUS m_linkStatus; LINK_STATUS m_linkStatus;
LINK_STATUS m_tempLinkStatus; LINK_STATUS m_tempLinkStatus;
@ -104,17 +72,12 @@ private:
std::string m_tempReflector; std::string m_tempReflector;
bool m_hasTemporary; bool m_hasTemporary;
CTimer m_timer; CTimer m_timer;
CAMBEData** m_data; std::vector<CAMBEData*> m_data;
unsigned int m_in; static CAMBEFileReader* m_ambeFilereader;
unsigned int m_out; unsigned int m_out;
unsigned int m_seqNo;
std::chrono::high_resolution_clock::time_point m_time; std::chrono::high_resolution_clock::time_point m_time;
bool lookup(unsigned int id, const std::string& name); void spellReflector(const std::string& reflector);
void spellReflector(unsigned int id, const std::string& reflector);
void sendStatus(LINK_STATUS status, const std::string& reflector, const std::string& text); 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);
}; };

@ -0,0 +1,206 @@
/*
* Copyright (C) 2014 by Jonathan Naylor G4KLX
* Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <string>
#include <iostream>
#include <cassert>
#include <sstream>
#include <csignal>
#ifdef DEBUG_DSTARGW
#include <boost/stacktrace.hpp>
#endif
#include "DGWTimeServerApp.h"
#include "Version.h"
#include "Log.h"
#include "Daemon.h"
#include "LogConsoleTarget.h"
#include "LogFileTarget.h"
CDGWTimeServerApp * CDGWTimeServerApp::g_app = nullptr;
const std::string BANNER_1 = CStringUtils::string_format("%s v%s Copyright (C) %s\n", APPLICATION_NAME.c_str(), LONG_VERSION.c_str(), VENDOR_NAME.c_str());
const std::string BANNER_2 = "DGWTimeServer comes with ABSOLUTELY NO WARRANTY; see the LICENSE for details.\n";
const std::string BANNER_3 = "This is free software, and you are welcome to distribute it under certain conditions that are discussed in the LICENSE file.\n\n";
int main(int argc, char * argv[])
{
std::set_terminate(CDGWTimeServerApp::terminateHandler);
signal(SIGSEGV, CDGWTimeServerApp::sigHandlerFatal);
signal(SIGILL, CDGWTimeServerApp::sigHandlerFatal);
signal(SIGFPE, CDGWTimeServerApp::sigHandlerFatal);
signal(SIGABRT, CDGWTimeServerApp::sigHandlerFatal);
signal(SIGTERM, CDGWTimeServerApp::sigHandler);
signal(SIGINT, CDGWTimeServerApp::sigHandler);
if (2 != argc) {
printf("usage: %s path_to_config_file\n", argv[0]);
printf(" %s --version\n", argv[0]);
return 1;
}
std::cout << std::endl << BANNER_1 << BANNER_2 << BANNER_3;
if(argv[1][0] == '-') {
return 0;
}
// Load config
std::string configfile(argv[1]);
CTimeServerConfig config(configfile);
if(!config.load())
return 1;
// Do daemon stuff
TDaemon daemon;
config.getDameon(daemon);
if (daemon.daemon) {
CLog::logInfo("Configured as a daemon, detaching ...");
auto res = CDaemon::daemonise(daemon.pidFile, daemon.user);
switch (res)
{
case DR_PARENT:
return 0;
case DR_CHILD:
break;
case DR_PIDFILE_FAILED:
case DR_FAILURE:
default:
CLog::logFatal("Failed to run as daemon");
CLog::finalise();
return 1;
}
}
// Setup Log
TLog logConf;
config.getLog(logConf);
CLog::finalise();
if(logConf.displayLevel != LOG_NONE && !daemon.daemon) CLog::addTarget(new CLogConsoleTarget(logConf.displayLevel));
if(logConf.fileLevel != LOG_NONE) CLog::addTarget(new CLogFileTarget(logConf.fileLevel, logConf.logDir, logConf.fileRoot, logConf.fileRotate));
// Start the app
CDGWTimeServerApp app(&config);
if(app.init()) {
app.run();
return 0;
}
return 1;
}
CDGWTimeServerApp::CDGWTimeServerApp(const CTimeServerConfig * config) :
m_config(config)
{
g_app = this;
assert(config != nullptr);
}
CDGWTimeServerApp::~CDGWTimeServerApp()
{
delete m_thread;
}
bool CDGWTimeServerApp::init()
{
return createThread();
}
void CDGWTimeServerApp::run()
{
m_thread->Run();
m_thread->Wait();
}
bool CDGWTimeServerApp::createThread()
{
m_thread = new CTimeServerThread();
TTimeServer timeserver;
m_config->getTimeServer(timeserver);
std::vector<std::string> rptrs = { "", "", "", "" };
TRepeater repeater;
for(unsigned int i = 0u; i < m_config->getRepeaterCount(); i++) {
m_config->getRepeater(i, repeater);
rptrs[i].assign(repeater.band);
}
TPaths paths;
m_config->getPaths(paths);
m_thread = new CTimeServerThread();
bool ret = m_thread->setGateway(timeserver.callsign, rptrs[0], rptrs[1], rptrs[2], rptrs[3], timeserver.address, paths.data);
if(ret) {
m_thread->setAnnouncements(timeserver.language, timeserver.format, timeserver.interval);
}
return ret;
}
void CDGWTimeServerApp::sigHandler(int sig)
{
CLog::logInfo("Caught signal : %s, shutting down time server", strsignal(sig));
if(g_app != nullptr && g_app->m_thread != nullptr) {
g_app->m_thread->kill();
}
}
void CDGWTimeServerApp::sigHandlerFatal(int sig)
{
CLog::logFatal("Caught signal : %s", strsignal(sig));
fprintf(stderr, "Caught signal : %s\n", strsignal(sig));
#ifdef DEBUG_DSTARGW
std::stringstream stackTrace;
stackTrace << boost::stacktrace::stacktrace();
CLog::logFatal("Stack Trace : \n%s", stackTrace.str().c_str());
fprintf(stderr, "Stack Trace : \n%s\n", stackTrace.str().c_str());
#endif
exit(3);
}
void CDGWTimeServerApp::terminateHandler()
{
#ifdef DEBUG_DSTARGW
std::stringstream stackTrace;
stackTrace << boost::stacktrace::stacktrace();
#endif
std::exception_ptr eptr;
eptr = std::current_exception();
try {
if (eptr != nullptr) {
std::rethrow_exception(eptr);
}
else {
CLog::logFatal("Unhandled unknown exception occured");
fprintf(stderr, "Unknown ex\n");
}
} catch(const std::exception& e) {
CLog::logFatal("Unhandled exception occured %s", e.what());
fprintf(stderr, "Unhandled ex %s\n", e.what());
}
#ifdef DEBUG_DSTARGW
CLog::logFatal("Stack Trace : \n%s", stackTrace.str().c_str());
#endif
exit(2);
}

@ -0,0 +1,46 @@
/*
* Copyright (C) 2014 by Jonathan Naylor G4KLX
* Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#pragma once
#include "TimeServerDefs.h"
#include "TimeServerConfig.h"
#include "TimeServerThread.h"
class CDGWTimeServerApp
{
public:
CDGWTimeServerApp(const CTimeServerConfig * config);
~CDGWTimeServerApp();
bool init();
void run();
static void sigHandler(int sig);
static void sigHandlerFatal(int sig);
static void terminateHandler();
private:
bool createThread();
static CDGWTimeServerApp * g_app;
const CTimeServerConfig * m_config;
CTimeServerThread * m_thread;
};

@ -0,0 +1,36 @@
SRCS = $(wildcard *.cpp)
OBJS = $(SRCS:.cpp=.o)
DEPS = $(SRCS:.cpp=.d)
dgwtimeserver: ../VersionInfo/GitVersion.h $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a
$(CC) $(CPPFLAGS) -o dgwtimeserver $(OBJS) ../DStarBase/DStarBase.a ../BaseCommon/BaseCommon.a $(LDFLAGS)
%.o : %.cpp
$(CC) -I../BaseCommon -I../DStarBase -I../VersionInfo $(CPPFLAGS) -MMD -MD -c $< -o $@
.PHONY clean:
clean:
$(RM) *.o *.d dgwtimeserver
.PHONY install:
install: dgwtimeserver
# copy executable
@cp -f dgwtimeserver $(BIN_DIR)
# copy and adjust config
@cp -fn example.cfg $(CFG_DIR)/dgwtimeserver.cfg
@sed -i "s|path=/var/log/dstargateway/|path=$(LOG_DIR)|g" $(CFG_DIR)/dgwtimeserver.cfg
@sed -i "s|data=/usr/local/share/dstargateway.d/|data=$(DATA_DIR)|g" $(CFG_DIR)/dgwtimeserver.cfg
# SystemD service install
@cp -f ../debian/dgwtimeserver.service /lib/systemd/system/
@sed -i "s|%CFG_DIR%|$(CFG_DIR)|g" /lib/systemd/system/dgwtimeserver.service
systemctl enable dgwtimeserver.service
@systemctl daemon-reload
@echo "\n"
@echo "DGWTimeserver Install complete, edit $(CFG_DIR)dgwtimeserver.cfg and start the daemon with 'systemctl start dgwtimeserver.service'"
@echo "\n"
../BaseCommon/BaseCommon.a:
../DStarBase/DStarBase.a:
../VersionInfo/GitVersion.h:

@ -0,0 +1,11 @@
Time Server for DStarGateway. Installs as a systemd service.
If you did not change build settings, the configuration can be found in
```
/usr/local/etc/dgwtimeserver.cfg
```
After configuring enable and start the dgwtimeserver systemd service using :
```
sudo systemctl enable dgwtimeserver
sudo systemctl start dgwtimeserver
```

@ -0,0 +1,216 @@
/*
* Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <algorithm>
#include <boost/algorithm/string.hpp>
#include "TimeServerConfig.h"
#include "StringUtils.h"
CTimeServerConfig::CTimeServerConfig(const std::string &pathname) :
m_fileName(pathname),
m_repeaters()
{
}
CTimeServerConfig::~CTimeServerConfig()
{
for(auto repeater : m_repeaters) {
delete repeater;
}
m_repeaters.clear();
}
bool CTimeServerConfig::load()
{
bool ret = false;
CLog::logInfo("Loading Configuration from %s", m_fileName.c_str());
CConfig cfg(m_fileName);
ret = open(cfg);
if(ret) {
ret = loadTimeServer(cfg) && ret;
ret = loadRepeaters(cfg) && ret;
ret = loadDaemon(cfg) && ret;
ret = loadPaths(cfg) && ret;
ret = loadLog(cfg) && ret;
}
return ret;
}
bool CTimeServerConfig::open(CConfig & cfg)
{
try {
return cfg.load();
}
catch(...) {
CLog::logError("Can't read %s\n", m_fileName.c_str());
return false;
}
return true;
}
bool CTimeServerConfig::loadRepeaters(const CConfig & cfg)
{
m_repeaters.clear();
for(unsigned int i = 0; i < 4; i++) {
std::string section = CStringUtils::string_format("repeater_%d", i+ 1);
bool repeaterEnabled;
bool ret = cfg.getValue(section, "enabled", repeaterEnabled, false);
if(!ret || !repeaterEnabled)
continue;
TRepeater * repeater = new TRepeater;
ret = cfg.getValue(section, "band", repeater->band, 1, 2, "") && ret;
if(!ret) {
delete repeater;
continue;
}
boost::to_upper(repeater->band);
bool alreadyConfigured = std::any_of(m_repeaters.begin(), m_repeaters.end(), [repeater](TRepeater * r) { return r->band == repeater->band;});
if(alreadyConfigured) {
CLog::logWarning("%s-%s repeater already configured, ignoring", m_timeServer.callsign.c_str(), repeater->band.c_str());
delete repeater;
continue;
}
m_repeaters.push_back(repeater);
}
return m_repeaters.size() > 0U;
}
bool CTimeServerConfig::loadTimeServer(const CConfig & cfg)
{
bool ret = cfg.getValue("timeserver", "callsign", m_timeServer.callsign, 3, 8, "");
boost::to_upper(m_timeServer.callsign);
ret = cfg.getValue("timeserver", "address", m_timeServer.address, 0, 1024, "127.0.0.1") && ret;
std::string format;
ret = cfg.getValue("timeserver", "format", format, "voice", {"voice", "text" }) && ret;
if(format == "voice") m_timeServer.format = FORMAT_VOICE_TIME;
else if(format == "text") m_timeServer.format = FORMAT_TEXT_TIME;
std::string lang;
ret = cfg.getValue("timeserver", "language", lang, "english_uk_1", {"english_uk_1", "english_uk_2", "english_us_1", "english_us_2", "deutsch_1", "deutsch_2", "francais", "nederlands", "svenska", "espanol", "norsk", "portugues"}) && ret;;
if (lang == "english_uk_1") m_timeServer.language = LANG_ENGLISH_UK_1;
else if(lang == "english_uk_2") m_timeServer.language = LANG_ENGLISH_UK_2;
else if(lang == "english_us_1") m_timeServer.language = LANG_ENGLISH_US_1;
else if(lang == "english_us_2") m_timeServer.language = LANG_ENGLISH_US_2;
else if(lang == "deutsch_1" ) m_timeServer.language = LANG_DEUTSCH_1;
else if(lang == "detusch_2" ) m_timeServer.language = LANG_DEUTSCH_2;
else if(lang == "francais" ) m_timeServer.language = LANG_FRANCAIS;
else if(lang == "nederlands" ) m_timeServer.language = LANG_NEDERLANDS;
else if(lang == "svenska" ) m_timeServer.language = LANG_SVENSKA;
else if(lang == "espanol" ) m_timeServer.language = LANG_ESPANOL;
else if(lang == "norsk" ) m_timeServer.language = LANG_NORSK;
else if(lang == "portugues" ) m_timeServer.language = LANG_PORTUGUES;
std::string interval;
ret = cfg.getValue("timeserver", "interval", interval, "30", {"15", "30", "60"}) && ret;
if(interval == "15") m_timeServer.interval = INTERVAL_15MINS;
else if(interval == "30") m_timeServer.interval = INTERVAL_30MINS;
else if(interval == "60") m_timeServer.interval = INTERVAL_60MINS;
return ret;
}
bool CTimeServerConfig::loadDaemon(const CConfig & cfg)
{
bool ret = cfg.getValue("daemon", "daemon", m_daemon.daemon, false);
ret = cfg.getValue("daemon", "pidfile", m_daemon.pidFile, 0, 1024, "") && ret;
ret = cfg.getValue("daemon", "user", m_daemon.user, 0, 1024, "") && ret;
return ret;
}
bool CTimeServerConfig::loadPaths(const CConfig & cfg)
{
bool ret = cfg.getValue("paths", "data", m_paths.data, 1, 1024, "");
return ret;
}
bool CTimeServerConfig::loadLog(const CConfig & cfg)
{
bool ret = cfg.getValue("log", "path", m_log.logDir, 0, 2048, "/var/log/dstargateway/");
if(ret && m_log.logDir[m_log.logDir.length() - 1] != '/') {
m_log.logDir.push_back('/');
}
ret = cfg.getValue("log", "fileRoot", m_log.fileRoot, 0, 64, "dgwtimeserver") && ret;
ret = cfg.getValue("log", "fileRotate", m_log.fileRotate, true) && ret;
std::string levelStr;
ret = cfg.getValue("log", "fileLevel", levelStr, "info", {"trace", "debug", "info", "warning", "error", "fatal", "none"}) && ret;
if(ret) {
if(levelStr == "trace") m_log.fileLevel = LOG_TRACE;
else if(levelStr == "debug") m_log.fileLevel = LOG_DEBUG;
else if(levelStr == "info") m_log.fileLevel = LOG_INFO;
else if(levelStr == "warning") m_log.fileLevel = LOG_WARNING;
else if(levelStr == "error") m_log.fileLevel = LOG_ERROR;
else if(levelStr == "fatal") m_log.fileLevel = LOG_FATAL;
else if(levelStr == "none") m_log.fileLevel = LOG_NONE;
}
ret = cfg.getValue("log", "displayLevel", levelStr, "info", {"trace", "debug", "info", "warning", "error", "fatal", "none"}) && ret;
if(ret) {
if(levelStr == "trace") m_log.displayLevel = LOG_TRACE;
else if(levelStr == "debug") m_log.displayLevel = LOG_DEBUG;
else if(levelStr == "info") m_log.displayLevel = LOG_INFO;
else if(levelStr == "warning") m_log.displayLevel = LOG_WARNING;
else if(levelStr == "error") m_log.displayLevel = LOG_ERROR;
else if(levelStr == "fatal") m_log.displayLevel = LOG_FATAL;
else if(levelStr == "none") m_log.displayLevel = LOG_NONE;
}
return ret;
}
void CTimeServerConfig::getTimeServer(TTimeServer& timeserver) const
{
timeserver = m_timeServer;
}
void CTimeServerConfig::getDameon(TDaemon& daemon) const
{
daemon = m_daemon;
}
unsigned int CTimeServerConfig::getRepeaterCount() const
{
return m_repeaters.size();
}
void CTimeServerConfig::getRepeater(unsigned int idx, TRepeater& repeater) const
{
repeater = *(m_repeaters[idx]);
}
void CTimeServerConfig::getPaths(TPaths& paths) const
{
paths = m_paths;
}
void CTimeServerConfig::getLog(TLog& log) const
{
log = m_log;
}

@ -0,0 +1,86 @@
/*
* Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#pragma once
#include <string>
#include <vector>
#include "Config.h"
#include "TimeServerDefs.h"
#include "Log.h"
typedef struct {
std::string callsign;
std::string address;
FORMAT format;
LANGUAGE language;
INTERVAL interval;
} TTimeServer;
typedef struct {
std::string band;
} TRepeater;
typedef struct {
bool daemon;
std::string pidFile;
std::string user;
} TDaemon;
typedef struct {
std::string data;
} TPaths;
typedef struct {
std::string logDir;
LOG_SEVERITY displayLevel;
LOG_SEVERITY fileLevel;
std::string fileRoot;
bool fileRotate;
} TLog;
class CTimeServerConfig
{
public:
CTimeServerConfig(const std::string &pathname);
~CTimeServerConfig();
bool load();
void getTimeServer(TTimeServer& timeserver) const;
void getDameon(TDaemon& daemon) const;
unsigned int getRepeaterCount() const;
void getRepeater(unsigned int idx, TRepeater& repeater) const;
void getPaths(TPaths& paths) const;
void getLog(TLog& log) const;
private:
bool open(CConfig & cfg);
bool loadRepeaters(const CConfig & cfg);
bool loadTimeServer(const CConfig & cfg);
bool loadDaemon(const CConfig & cfg);
bool loadPaths(const CConfig & cfg);
bool loadLog(const CConfig & cfg);
std::string m_fileName;
std::vector<TRepeater *> m_repeaters;
TTimeServer m_timeServer;
TDaemon m_daemon;
TPaths m_paths;
TLog m_log;
};

@ -0,0 +1,48 @@
/*
* Copyright (C) 2012,2013,2014 by Jonathan Naylor G4KLX
* Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#pragma once
const std::string APPLICATION_NAME("DStarGateway Time Server");
enum LANGUAGE {
LANG_ENGLISH_UK_1,
LANG_ENGLISH_UK_2,
LANG_ENGLISH_US_1,
LANG_ENGLISH_US_2,
LANG_DEUTSCH_1,
LANG_DEUTSCH_2,
LANG_FRANCAIS,
LANG_NEDERLANDS,
LANG_SVENSKA,
LANG_ESPANOL,
LANG_NORSK,
LANG_PORTUGUES
};
enum INTERVAL {
INTERVAL_15MINS,
INTERVAL_30MINS,
INTERVAL_60MINS
};
enum FORMAT {
FORMAT_VOICE_TIME,
FORMAT_TEXT_TIME
};

File diff suppressed because it is too large Load Diff

@ -0,0 +1,83 @@
/*
* Copyright (C) 2012,2013 by Jonathan Naylor G4KLX
* Copyright (C) 2022 by Geoffrey Merck F4FXL / KC3FRA
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#pragma once
#include <unordered_map>
#include "SlowDataEncoder.h"
#include "UDPReaderWriter.h"
#include "TimeServerDefs.h"
#include "HeaderData.h"
#include "AMBEData.h"
#include "Thread.h"
#include "AMBEFileReader.h"
class CTimeServerThread : public CThread
{
public:
CTimeServerThread();
~CTimeServerThread();
bool setGateway(const std::string& callsign, const std::string& rpt1, const std::string& rpt2, const std::string& rpt3, const std::string& rpt4, const std::string& address, const std::string& dataPath);
void setAnnouncements(LANGUAGE language, FORMAT format, INTERVAL interval);
void * Entry();
void kill();
private:
std::string m_callsign;
std::vector<std::string> m_repeaters;
std::string m_callsignG;
in_addr m_address;
std::string m_addressStr;
LANGUAGE m_language;
FORMAT m_format;
INTERVAL m_interval;
CSlowDataEncoder m_encoder;
std::vector<CAMBEData*> m_data;
bool m_killed;
std::string m_dataPath;
CAMBEFileReader * m_ambeFileReader;
void sendTime(unsigned int hour, unsigned int min);
std::vector<std::string> sendTimeEnGB1(unsigned int hour, unsigned int min);
std::vector<std::string> sendTimeEnGB2(unsigned int hour, unsigned int min);
std::vector<std::string> sendTimeEnUS1(unsigned int hour, unsigned int min);
std::vector<std::string> sendTimeEnUS2(unsigned int hour, unsigned int min);
std::vector<std::string> sendTimeDeDE1(unsigned int hour, unsigned int min);
std::vector<std::string> sendTimeDeDE2(unsigned int hour, unsigned int min);
std::vector<std::string> sendTimeFrFR(unsigned int hour, unsigned int min);
std::vector<std::string> sendTimeNlNL(unsigned int hour, unsigned int min);
std::vector<std::string> sendTimeSeSE(unsigned int hour, unsigned int min);
std::vector<std::string> sendTimeEsES(unsigned int hour, unsigned int min);
std::vector<std::string> sendTimeNoNO(unsigned int hour, unsigned int min);
std::vector<std::string> sendTimePtPT(unsigned int hour, unsigned int min);
bool send(const std::vector<std::string>& words, unsigned int hour, unsigned int min);
bool sendHeader(CUDPReaderWriter& socket, const CHeaderData& header);
bool sendData(CUDPReaderWriter& socket, const CAMBEData& data);
bool loadAMBE();
bool lookup(const std::string& id);
void buildAudio(const std::vector<std::string>& words, CSlowDataEncoder& slowDataEncoder);
};

@ -0,0 +1,41 @@
[TimeServer]
callsign= # call of the gateway to send time beacons without G letter
address= # address of the gateway, defaults to 127.0.0.1
format= # possible values are voice, text, voiceandtext, defaults to voice and text
language= # valid values: english_uk_1, english_uk_2, english_us_1, english_us_2, deutsch_1, deutsch_2, francais, nederlands, svenska, espanol, norsk, portugues. Defaults to english_uk_1
interval= # valid values are 15, 30 and 60, defaults to 30
[Paths]
data=/usr/local/share/dstargateway.d/ #Path where the data (hostfiles, audio files etc) can be found
# Up to 4 repeaters can be enabled to transmit time beacons
[Repeater_1]
enabled=true # enable time beacons on this repeater
band=B # Module letter of the repeater
[Repeater_2]
enabled=false # enable time beacons on this repeater
band= # Module letter of the repeater
[Repeater_3]
enabled=false # enable time beacons on this repeater
band= # Module letter of the repeater
[Repeater_4]
enabled=false # enable time beacons on this repeater
band= # Module letter of the repeater
[Log]
path=/var/log/dstargateway/
fileRoot= # defaults to dgwtimeserver
fileRotate= # rotate log files daily, defaults to true
fileLevel= # defaults to info, valid values are trace, debug, info, warning, error, fatal, none
displayLevel= # defaults to info, valid values are trace, debug, info, warning, error, fatal, none
# Provided install routines install the program as a systemd unit. SystemD does not recommand "old-school" forking daemons nor does systemd
# require a pid file. Moreover systemd handles the user under which the program is started. This is provided as convenience for people who might
# run the program using sysv or any other old school init system.
[Daemon]
daemon=false
pidfile=/var/run/dstargateway/dstargateway.pid # pid file is in our case useless when running as a daemon using systemd as systemd takes care of the service not being started twice
user=dstar # user account the daemon will run under, ideally a user with low privileges. Switching to this user will only happen when the program is started as root

@ -0,0 +1,179 @@
/*
* Copyright (c) 2021 by Geoffrey Merck F4FXL / KC3FRA
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <sys/stat.h>
#include <cstdio>
#include <cstring>
#include "AMBEFileReader.h"
#include "DStarDefines.h"
#include "Log.h"
const unsigned int SILENCE_LENGTH = 10U;
CAMBEFileReader::CAMBEFileReader(const std::string& indexFile, const std::string& ambeFile) :
m_indexFile(indexFile),
m_ambeFile(ambeFile),
m_ambe(nullptr),
m_ambeLength(0U),
m_index()
{
}
CAMBEFileReader::~CAMBEFileReader()
{
if(m_ambe != nullptr) {
delete[] m_ambe;
}
}
bool CAMBEFileReader::read()
{
bool ret = readAmbe() && readIndex();
return ret;
}
bool CAMBEFileReader::readAmbe()
{
struct stat sbuf;
if (stat(m_ambeFile.c_str(), &sbuf)) {
CLog::logWarning("File %s not readable\n", m_ambeFile.c_str());
return false;
}
unsigned int fsize = sbuf.st_size;
FILE *file = fopen(m_ambeFile.c_str(), "rb");
if (NULL == file) {
CLog::logError("Cannot open %s for reading\n", m_ambeFile.c_str());
return false;
}
CLog::logInfo("Reading %s\n", m_ambeFile.c_str());
unsigned char buffer[VOICE_FRAME_LENGTH_BYTES];
size_t n = fread(buffer, sizeof(unsigned char), 4, file);
if (n != 4) {
CLog::logError("Unable to read the header from %s\n", m_ambeFile.c_str());
fclose(file);
return false;
}
if (memcmp(buffer, "AMBE", 4)) {
CLog::logError("Invalid header from %s\n", m_ambeFile.c_str());
fclose(file);
return false;
}
// Length of the file minus the header
unsigned int length = fsize - 4U;
// Hold the file data plus silence at the end
m_ambe = new unsigned char[length + SILENCE_LENGTH * VOICE_FRAME_LENGTH_BYTES];
m_ambeLength = length / VOICE_FRAME_LENGTH_BYTES;
// Add silence to the beginning of the buffer
unsigned char* p = m_ambe;
for (unsigned int i = 0U; i < SILENCE_LENGTH; i++, p += VOICE_FRAME_LENGTH_BYTES)
memcpy(p, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES);
n = fread(p, 1, length, file);
if (n != length) {
CLog::logError("Unable to read the AMBE data from %s\n", m_ambeFile.c_str());
fclose(file);
delete[] m_ambe;
m_ambeLength = 0U;
m_ambe = nullptr;
return false;
}
fclose(file);
return true;
}
bool CAMBEFileReader::readIndex()
{
struct stat sbuf;
if (stat(m_indexFile.c_str(), &sbuf)) {
CLog::logError("File %s not readable\n", m_indexFile.c_str());
return false;
}
FILE *file = fopen(m_indexFile.c_str(), "r");
if (file == nullptr) {
CLog::logError("Cannot open %s for reading\n", m_indexFile.c_str());
return false;
}
// Add a silence entry at the beginning
m_index[" "] = new CIndexRecord(" ", 0, SILENCE_LENGTH);
CLog::logInfo("Reading %s\n", m_indexFile.c_str());
char line[128];
while (fgets(line, 128, file)) {
if (strlen(line) && '#'!=line[0]) {
const std::string space(" \t\r\n");
std::string name(strtok(line, space.c_str()));
std::string strt(strtok(NULL, space.c_str()));
std::string leng(strtok(NULL, space.c_str()));
if (name.size() && strt.size() && leng.size()) {
unsigned long start = std::stoul(strt);
unsigned long length = std::stoul(leng);
if (start >= m_ambeLength || (start + length) >= m_ambeLength)
CLog::logInfo("The start or end for *%s* is out of range, start: %lu, end: %lu\n", name.c_str(), start, start + length);
else
m_index[name] = new CIndexRecord(name, start + SILENCE_LENGTH, length);
}
}
}
fclose(file);
return true;
}
bool CAMBEFileReader::lookup(const std::string &id, std::vector<CAMBEData *>& data)
{
if(m_index.count(id) == 0U) {
CLog::logError("Cannot find the AMBE index for *%s*", id.c_str());
return false;
}
CIndexRecord* info = m_index[id];
unsigned int start = info->getStart();
unsigned int length = info->getLength();
for (unsigned int i = 0U; i < length; i++) {
unsigned char* dataIn = m_ambe + (start + i) * VOICE_FRAME_LENGTH_BYTES;
unsigned char buffer[DV_FRAME_LENGTH_BYTES];
::memcpy(buffer + 0U, dataIn, VOICE_FRAME_LENGTH_BYTES);
CAMBEData* dataOut = new CAMBEData;
dataOut->setData(buffer, DV_FRAME_LENGTH_BYTES);
data.push_back(dataOut);
}
return true;
}

@ -0,0 +1,74 @@
/*
* Copyright (c) 2021 by Geoffrey Merck F4FXL / KC3FRA
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#pragma once
#include <string>
#include <unordered_map>
#include <vector>
#include "AMBEData.h"
class CIndexRecord {
public:
CIndexRecord(const std::string& name, unsigned int start, unsigned int length) :
m_name(name),
m_start(start),
m_length(length)
{
}
std::string getName() const
{
return m_name;
}
unsigned int getStart() const
{
return m_start;
}
unsigned int getLength() const
{
return m_length;
}
private:
std::string m_name;
unsigned int m_start;
unsigned int m_length;
};
class CAMBEFileReader
{
public:
CAMBEFileReader(const std::string& indexFile, const std::string& ambeFile);
~CAMBEFileReader();
bool read();
bool lookup(const std::string &id, std::vector<CAMBEData *>& data);
private:
bool readAmbe();
bool readIndex();
std::string m_indexFile;
std::string m_ambeFile;
unsigned char* m_ambe;
unsigned int m_ambeLength;
std::unordered_map<std::string, CIndexRecord*> m_index;
};

@ -115,8 +115,8 @@ int main(int argc, char *argv[])
TLog logConf; TLog logConf;
config->getLog(logConf); config->getLog(logConf);
CLog::finalise(); CLog::finalise();
if(logConf.m_displayLevel != LOG_NONE && !daemon.daemon) CLog::addTarget(new CLogConsoleTarget(logConf.m_displayLevel)); if(logConf.displayLevel != LOG_NONE && !daemon.daemon) CLog::addTarget(new CLogConsoleTarget(logConf.displayLevel));
if(logConf.m_fileLevel != LOG_NONE) CLog::addTarget(new CLogFileTarget(logConf.m_fileLevel, logConf.logDir, logConf.m_fileRotate)); 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 //write banner in log file if we are dameon
if(daemon.daemon) { if(daemon.daemon) {

@ -153,34 +153,32 @@ bool CDStarGatewayConfig::loadLog(const CConfig & cfg)
m_log.logDir.push_back('/'); m_log.logDir.push_back('/');
} }
ret = cfg.getValue("log", "fileRoot", m_log.m_fileRoot, 0, 64, "dstargateway") && ret; ret = cfg.getValue("log", "fileRoot", m_log.fileRoot, 0, 64, "dstargateway") && ret;
ret = cfg.getValue("log", "fileRotate", m_log.m_fileRotate, true) && ret; ret = cfg.getValue("log", "fileRotate", m_log.fileRotate, true) && ret;
std::string levelStr; std::string levelStr;
ret = cfg.getValue("log", "fileLevel", levelStr, "info", {"trace", "debug", "info", "warning", "error", "fatal", "none"}) && ret; ret = cfg.getValue("log", "fileLevel", levelStr, "info", {"trace", "debug", "info", "warning", "error", "fatal", "none"}) && ret;
if(ret) { if(ret) {
if(levelStr == "trace") m_log.m_fileLevel = LOG_TRACE; if(levelStr == "trace") m_log.fileLevel = LOG_TRACE;
else if(levelStr == "debug") m_log.m_fileLevel = LOG_DEBUG; else if(levelStr == "debug") m_log.fileLevel = LOG_DEBUG;
else if(levelStr == "info") m_log.m_fileLevel = LOG_INFO; else if(levelStr == "info") m_log.fileLevel = LOG_INFO;
else if(levelStr == "warning") m_log.m_fileLevel = LOG_WARNING; else if(levelStr == "warning") m_log.fileLevel = LOG_WARNING;
else if(levelStr == "error") m_log.m_fileLevel = LOG_ERROR; else if(levelStr == "error") m_log.fileLevel = LOG_ERROR;
else if(levelStr == "fatal") m_log.m_fileLevel = LOG_FATAL; else if(levelStr == "fatal") m_log.fileLevel = LOG_FATAL;
else if(levelStr == "none") m_log.m_fileLevel = LOG_NONE; else if(levelStr == "none") m_log.fileLevel = LOG_NONE;
} }
ret = cfg.getValue("log", "displayLevel", levelStr, "info", {"trace", "debug", "info", "warning", "error", "fatal", "none"}) && ret; ret = cfg.getValue("log", "displayLevel", levelStr, "info", {"trace", "debug", "info", "warning", "error", "fatal", "none"}) && ret;
if(ret) { if(ret) {
if(levelStr == "trace") m_log.m_displayLevel = LOG_TRACE; if(levelStr == "trace") m_log.displayLevel = LOG_TRACE;
else if(levelStr == "debug") m_log.m_displayLevel = LOG_DEBUG; else if(levelStr == "debug") m_log.displayLevel = LOG_DEBUG;
else if(levelStr == "info") m_log.m_displayLevel = LOG_INFO; else if(levelStr == "info") m_log.displayLevel = LOG_INFO;
else if(levelStr == "warning") m_log.m_displayLevel = LOG_WARNING; else if(levelStr == "warning") m_log.displayLevel = LOG_WARNING;
else if(levelStr == "error") m_log.m_displayLevel = LOG_ERROR; else if(levelStr == "error") m_log.displayLevel = LOG_ERROR;
else if(levelStr == "fatal") m_log.m_displayLevel = LOG_FATAL; else if(levelStr == "fatal") m_log.displayLevel = LOG_FATAL;
else if(levelStr == "none") m_log.m_displayLevel = LOG_NONE; else if(levelStr == "none") m_log.displayLevel = LOG_NONE;
} }
//TODO 20211226 check if directories are accessible
return ret; return ret;
} }
@ -199,6 +197,7 @@ bool CDStarGatewayConfig::loadPaths(const CConfig & cfg)
bool CDStarGatewayConfig::loadRepeaters(const CConfig & cfg) bool CDStarGatewayConfig::loadRepeaters(const CConfig & cfg)
{ {
m_repeaters.clear();
for(unsigned int i = 0; i < 4; i++) { for(unsigned int i = 0; i < 4; i++) {
std::string section = CStringUtils::string_format("repeater_%d", i+ 1); std::string section = CStringUtils::string_format("repeater_%d", i+ 1);
bool repeaterEnabled; bool repeaterEnabled;

@ -86,10 +86,10 @@ typedef struct {
typedef struct { typedef struct {
std::string logDir; std::string logDir;
LOG_SEVERITY m_displayLevel; LOG_SEVERITY displayLevel;
LOG_SEVERITY m_fileLevel; LOG_SEVERITY fileLevel;
std::string m_fileRoot; std::string fileRoot;
bool m_fileRotate; bool fileRotate;
} TLog; } TLog;
typedef struct { typedef struct {

@ -22,6 +22,15 @@ install: dstargateway
@sed -i "s|path=/var/log/dstargateway/|path=$(LOG_DIR)|g" $(CFG_DIR)/dstargateway.cfg @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 @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: ../APRS/APRS.a:
../Common/Common.a: ../Common/Common.a:
../DStarBase/DStarBase.a: ../DStarBase/DStarBase.a:

@ -178,7 +178,7 @@ port=4242
password=CHANGE_ME # If password is left blank, remote will be disabled regardless of the enabled field 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 # 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. # run the program using sysv or any other old school init system.
[Daemon] [Daemon]
daemon=false daemon=false

@ -40,7 +40,7 @@ endif
.PHONY: all .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 APRS/APRS.a: BaseCommon/BaseCommon.a FORCE
$(MAKE) -C APRS $(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 DGWTextTransmit/dgwtexttransmit: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE
$(MAKE) -C DGWTextTransmit $(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 DGWVoiceTransmit/dgwvoicetransmit: VersionInfo/GitVersion.h $(OBJS) DStarBase/DStarBase.a BaseCommon/BaseCommon.a FORCE
$(MAKE) -C DGWVoiceTransmit $(MAKE) -C DGWVoiceTransmit
@ -99,6 +102,7 @@ install : DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol
# install accessories # install accessories
$(MAKE) -C DGWRemoteControl install $(MAKE) -C DGWRemoteControl install
$(MAKE) -C DGWTextTransmit install $(MAKE) -C DGWTextTransmit install
$(MAKE) -C DGWTimeServer install
$(MAKE) -C DGWVoiceTransmit install $(MAKE) -C DGWVoiceTransmit install
# create user for daemon # create user for daemon
@ -115,18 +119,9 @@ install : DStarGateway/dstargateway DGWRemoteControl/dgwremotecontrol
$(MAKE) -C Data install $(MAKE) -C Data install
@chown -R dstar:dstar $(DATA_DIR) @chown -R dstar:dstar $(DATA_DIR)
#install executables # install services executables
$(MAKE) -C DStarGateway install $(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 .PHONY: uninstall
uninstall : uninstall :
systemctl stop dstargateway.service || true systemctl stop dstargateway.service || true

@ -135,26 +135,27 @@ the testing framwework used is Google Test.
# 5. Version History # 5. Version History
## 5.1. Version 0.6 ## 5.1. Version 0.6
- [Improvement] Gracefully exit on SIGINT and SIGTERM ([#21](https://github.com/F4FXL/DStarGateway/issues/21)) - [**Improvement**] Add time server
- [Improvement] Add text transmit utility dgwtexttransmit ([#18](https://github.com/F4FXL/DStarGateway/issues/18)) - [**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 voice transmit utility dgwvoicetransmit ([#18](https://github.com/F4FXL/DStarGateway/issues/18)) - [**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 ## 5.2. Version 0.5
- [Improvement] Add remote control utility dgwremotecontrol ([#17](https://github.com/F4FXL/DStarGateway/issues/17)) - [**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)) - [**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 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)) - [**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**] 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**] Remote control connection failed ([#13](https://github.com/F4FXL/DStarGateway/issues/13))
- [Bugfix] Trying to connect to ghost ircDDB when no ircDDB is configured - [**Bugfix**] Trying to connect to ghost ircDDB when no ircDDB is configured
## 5.3. Version 0.4 ## 5.3. Version 0.4
- [Improvement] Add APRS status link feature ([#8](https://github.com/F4FXL/DStarGateway/issues/8)) - [**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)) - [**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**] Bring back GPSD support ([#6](https://github.com/F4FXL/DStarGateway/issues/6))
- [Improvement] Log enhancements ([#4](https://github.com/F4FXL/DStarGateway/issues/4)) - [**Improvement**] Log enhancements ([#4](https://github.com/F4FXL/DStarGateway/issues/4))
## 5.4. Version 0.3 ## 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 ## 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 - Code sanitization
## 5.6. Version 0.1 ## 5.6. Version 0.1
First working version First working version
@ -163,10 +164,9 @@ I started this during my 2021 seasons holiday. It took me almost 8 days to get t
- &#9745; Better NatTraversal - &#9745; Better NatTraversal
- No banging on every gateway: use ircDDB (or something else) as mitigation server to notify peer - 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 - 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
- &#9745; Send the connection status to APRS-IS as a status frame - &#9745; Send the connection status to APRS-IS as a status frame
- &#9746; Reinstantiate DRATS - &#9746; Reinstantiate DRATS
- &#9746; Migrate all the "accessories" (VoiceTransmit, RemoteControl ...) - &#9745; Migrate all the "accessories" (VoiceTransmit, RemoteControl ...)
- &#9746; Automatic refresh of host files - &#9746; Automatic refresh of host files
- &#9746; Reduce ircDDB dependency, build something more P2P, maybe based on [Distributed Hashtable](https://github.com/DavidKeller/kademlia) ? - &#9746; Reduce ircDDB dependency, build something more P2P, maybe based on [Distributed Hashtable](https://github.com/DavidKeller/kademlia) ?
- &#9746; Forward messages from RS-MS1A to APRS and vice versa - &#9746; Forward messages from RS-MS1A to APRS and vice versa

Binary file not shown.

@ -0,0 +1,44 @@
0 94 29
1 140 16
2 173 20
3 215 19
4 255 24
5 298 29
6 344 34
7 398 25
8 440 24
9 481 23
alpha 521 29
bravo 568 27
charlie 615 30
delta 666 30
A 714 21
B 753 27
C 796 34
D 848 30
E 895 23
F 936 27
G 982 27
H 1026 30
I 1075 20
J 1113 29
K 1162 22
L 1201 28
M 1248 28
N 1295 28
O 1341 23
P 1386 21
Q 1426 23
R 1466 28
S 1511 31
T 1567 21
U 1606 22
V 1646 30
W 1693 46
X 1756 31
Y 1806 21
Z 1844 14
linkedto 1858 35
notlinked 1941 46
linkingto 2008 38
isbusy 2091 47

@ -0,0 +1,115 @@
/*
* Copyright (c) 2021 by Geoffrey Merck F4FXL / KC3FRA
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <gtest/gtest.h>
#include <filesystem>
#include <string>
#include <vector>
#include "AMBEFileReader.h"
#include "AMBEData.h"
namespace AMBEFileReaderTests
{
class AMBEFileReader_lookup : public ::testing::Test {
};
TEST_F(AMBEFileReader_lookup, nonExistentFiles)
{
CAMBEFileReader reader("/this/file/does/not/exist", "/neither/does/this/file");
bool res = reader.read();
EXPECT_FALSE(res) << "read shall return false on non existent files";
std::vector<CAMBEData *> data;
res = reader.lookup("0", data);
EXPECT_FALSE(res) << "read shall return false on non existent files";
for(auto d : data) {
delete d;
}
}
TEST_F(AMBEFileReader_lookup, onlyIndexFileExists)
{
std::string indexFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.indx";
CAMBEFileReader reader(indexFile, "/this/file/does/not/exist");
bool res = reader.read();
EXPECT_FALSE(res) << "read shall return false on non existent file";
std::vector<CAMBEData *> data;
res = reader.lookup("0", data);
EXPECT_FALSE(res) << "read shall return false on non existent file";
for(auto d : data) {
delete d;
}
}
TEST_F(AMBEFileReader_lookup, onlyAmbeFileExists)
{
std::string ambeFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.ambe";
CAMBEFileReader reader("/this/file/does/not/exist", ambeFile);
bool res = reader.read();
EXPECT_FALSE(res) << "read shall return false on non existent file";
std::vector<CAMBEData *> data;
res = reader.lookup("0", data);
EXPECT_FALSE(res) << "read shall return false on non existent file";
for(auto d : data) {
delete d;
}
}
TEST_F(AMBEFileReader_lookup, validId)
{
std::string indexFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.indx";
std::string ambeFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.ambe";
CAMBEFileReader reader(indexFile, ambeFile);
bool res = reader.read();
EXPECT_TRUE(res) << "read shall return true on existent files";
std::vector<CAMBEData *> data;
res = reader.lookup("0", data);
EXPECT_TRUE(res) << "read shall return true on existent files and valid Id";
EXPECT_NE(data.size(), 0U) << "Vector shall contain data";
for(auto d : data) {
delete d;
}
}
TEST_F(AMBEFileReader_lookup, invalidId)
{
std::string indexFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.indx";
std::string ambeFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.ambe";
CAMBEFileReader reader(indexFile, ambeFile);
bool res = reader.read();
EXPECT_TRUE(res) << "read shall return true on existent files";
std::vector<CAMBEData *> data;
res = reader.lookup("This Id does not exist", data);
EXPECT_FALSE(res) << "read shall return false on existent files and invalid Id";
EXPECT_EQ(data.size(), 0U) << "Vector shall not contain data";
for(auto d : data) {
delete d;
}
}
}

@ -0,0 +1,64 @@
/*
* Copyright (c) 2021 by Geoffrey Merck F4FXL / KC3FRA
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <gtest/gtest.h>
#include <filesystem>
#include <string>
#include "AMBEFileReader.h"
#include "Log.h"
namespace AMBEFileReaderTests
{
class AMBEFileReader_read : public ::testing::Test {
};
TEST_F(AMBEFileReader_read, nonExistentFiles)
{
CAMBEFileReader reader("/this/file/does/not/exist", "/neither/does/this/file");
bool res = reader.read();
EXPECT_FALSE(res) << "read shall return false on non existent files";
}
TEST_F(AMBEFileReader_read, onlyIndexFileExists)
{
std::string indexFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.indx";
CAMBEFileReader reader(indexFile, "/this/file/does/not/exist");
bool res = reader.read();
EXPECT_FALSE(res) << "read shall return false on non existent file";
}
TEST_F(AMBEFileReader_read, onlyAmbeFileExists)
{
std::string ambeFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.ambe";
CAMBEFileReader reader("/this/file/does/not/exist", ambeFile);
bool res = reader.read();
EXPECT_FALSE(res) << "read shall return false on non existent file";
}
TEST_F(AMBEFileReader_read, bothFileExist)
{
std::string indexFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.indx";
std::string ambeFile = std::string(std::filesystem::current_path()) + "/AMBEFileReader/fr_FR.ambe";
CAMBEFileReader reader(indexFile, ambeFile);
bool res = reader.read();
EXPECT_TRUE(res) << "read shall return true on existent files";
}
}

@ -15,7 +15,9 @@ dstargateway_tests: ../VersionInfo/GitVersion.h $(OBJS) ../APRS/APRS.a ../IRCDDB
.PHONY clean : .PHONY clean :
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: ../APRS/APRS.a:
../Common/Common.a: ../Common/Common.a:

@ -0,0 +1,13 @@
[Unit]
Description=D-STAR Time Server Daemon
After=network.target,network-online.target
Wants=network-online.target
[Service]
User=dstar
Type=simple
ExecStart=/usr/local/bin/dgwtimeserver %CFG_DIR%/dgwtimeserver.cfg
Restart=on-failure
[Install]
WantedBy=multi-user.target
Loading…
Cancel
Save

Powered by TurnKey Linux.