Merge branch 'release/0.3'

master
Geoffrey Merck 4 years ago
commit ebd8be966a

2
.gitignore vendored

@ -37,9 +37,11 @@ GitVersion.h
# test files
___test.cfg
Sandbox/*
# Executables
*.exe
*.out
*.app
dstargateway

@ -0,0 +1,281 @@
/*
* 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 <iostream>
#include <fstream>
#include <sstream>
#include <boost/algorithm/string.hpp>
#include "Utils.h"
#include "Config.h"
#include "Log.h"
CConfig::CConfig(const std::string filename) :
m_filename(filename)
{
}
CConfig::~CConfig()
{
for(auto it = m_sections.begin(); it != m_sections.end(); it++) {
delete it->second;
}
m_sections.clear();
}
bool CConfig::load()
{
std::ifstream file;
file.open(m_filename, std::ios::in);
if(!file.is_open()) {
CLog::logError("Failed to open configuration file %s", m_filename);
return false;
}
CConfigSection * currentSection = nullptr;
while(!file.eof()) {
std::string line;
std::getline(file, line);
stripComment(line);
if(isConfigSection(line)) {
auto it = m_sections.find(line);
currentSection = it != m_sections.end() ? it->second : nullptr;
if(currentSection == nullptr) {
currentSection = new CConfigSection(line);
m_sections[currentSection->getName()] = currentSection;
}
} else if(isConfigValue(line) && currentSection != nullptr) {
TConfigValue * configValue = readKeyAndValue(line);
if(configValue != nullptr) {
currentSection->getValues()[configValue->m_key] = configValue;
}
}
}
file.close();
return true;
}
void CConfig::stripComment(std::string& s) const
{
boost::trim(s);
if(s.length() > 0 && s[0] == '#') {
//we have a lien consisting only of comment, truncate it and leave
s.resize(0);
return;
}
char * sdup = strdup(s.c_str());
char * uncommentedPart = strtok(sdup, "#");
s = std::string(uncommentedPart != nullptr ? uncommentedPart : "");
boost::trim(s);
free(sdup);// could we use delete sdup here?
}
TConfigValue * CConfig::readKeyAndValue(const std::string s) const
{
TConfigValue* res = nullptr;
char * sdup = strdup(boost::trim_copy(s).c_str());
char * keyPtr = strtok(sdup, "=");
std::string key(keyPtr != nullptr ? keyPtr : "");
boost::trim(key);
if(!key.empty()) {
char * valuePtr = strtok(nullptr, "=");
std::string value(valuePtr != nullptr? valuePtr : "");
res = new TConfigValue;
res->m_key = key;
res->m_value = boost::trim_copy(value);
}
free(sdup);// could we use delete sdup here?
return res;
}
bool CConfig::getValue(const std::string &section, const std::string& key, bool &value, bool defaultValue) const
{
std::string valueTemp;
std::string dafaultValueStr = defaultValue ? "true" : "false";
bool ret = getValue(section, key, valueTemp, dafaultValueStr, {"true", "1", "false", "0"});
if(ret) {
if(isSameNoCase(valueTemp, std::string("true")) || valueTemp == "1")
value = true;
else if(isSameNoCase(valueTemp, std::string("false")) || valueTemp == "0")
value = false;
}
return ret;
}
bool CConfig::getValue(const std::string &section, const std::string& key, int &value, int min, int max, int defaultValue) const
{
TConfigValue * val = lookupValue(section, key);
if(val == nullptr || val->m_value.empty()) {
value = defaultValue;
return true;
}
if(!isDecimalInteger(val->m_value)){
CLog::logError("Configuration error: %s.%s is not a valid number (%s)", section.c_str(), key.c_str(), val->m_value.c_str());
return false;
}
int tmpValue = std::strtol(val->m_value.c_str(), nullptr, 10);
if(tmpValue < min || tmpValue > max) {
CLog::logError("Configuration error: %s.%s is out of range must be between %d and %d, actual %d", section.c_str(), key.c_str(), min, max, tmpValue);
return false;
}
value = tmpValue;
return true;
}
bool CConfig::getValue(const std::string &section, const std::string& key, double &value, double min, double max, double defaultValue) const
{
TConfigValue * val = lookupValue(section, key);
if(val == nullptr || val->m_value.empty()) {
value = defaultValue;
return true;
}
if(!isFloatingPoint(val->m_value)){
CLog::logError("Configuration error: %s.%s is not a valid floating point number (%s)", section.c_str(), key.c_str(), val->m_value.c_str());
return false;
}
double tmpValue = std::strtold(val->m_value.c_str(), nullptr);
if(tmpValue < min || tmpValue > max) {
CLog::logError("Configuration error: %s.%s is out of range must be between %f and %f, actual %f", section.c_str(), key.c_str(), min, max, tmpValue);
return false;
}
value = tmpValue;
return true;
}
bool CConfig::getValue(const std::string &section, const std::string& key, unsigned int &value, unsigned int min, unsigned int max, int defaultValue) const
{
TConfigValue * val = lookupValue(section, key);
if(val == nullptr || val->m_value.empty()) {
value = defaultValue;
return true;
}
if(!isDecimalInteger(val->m_value)){
CLog::logError("Configuration error: %s.%s is not a valid number (%s)", section.c_str(), key.c_str(), val->m_value.c_str());
return false;
}
unsigned int tmpValue = std::strtol(val->m_value.c_str(), nullptr, 10);
if(tmpValue < min || tmpValue > max) {
CLog::logError("Configuration error: %s.%s is out of range must be between %u and %u, actual %u", section.c_str(), key.c_str(), min, max, tmpValue);
return false;
}
value = tmpValue;
return true;
}
bool CConfig::getValue(const std::string &section, const std::string& key, std::string &value, unsigned int minLen, unsigned int maxLen, const std::string defaultValue) const
{
TConfigValue * val = lookupValue(section, key);
std::string valueTemp = val != nullptr ? val->m_value : defaultValue;
if(valueTemp.empty() && minLen == 0U)
valueTemp = defaultValue;
if(valueTemp.length() < minLen || valueTemp.length() > maxLen) {
CLog::logError("Configuration error: %s.%s has an invalid length, length must be between %u and %u, actual %u", section.c_str(), key.c_str(), minLen, maxLen, valueTemp.length());
return false;
}
value = valueTemp;
return true;
}
bool CConfig::getValue(const std::string &section, const std::string& key, unsigned char &value, unsigned char min, unsigned char max,unsigned char defaultValue) const
{
unsigned int tempValue;
bool ret = getValue(section, key, tempValue, (unsigned int)min, (unsigned int)max, (unsigned int) defaultValue);
if(ret) {
value = (unsigned char)tempValue;
}
return ret;
}
bool CConfig::getValue(const std::string &section, const std::string& key, std::string &value, const std::string defaultValue, const std::vector<std::string>& allowedValues) const
{
std::string valueTemp;
if(getValue(section, key, valueTemp, 0U, 2048, defaultValue)) {
for(auto s : allowedValues) {
if(isSameNoCase(s, valueTemp)) {
value = CUtils::ToLower(valueTemp);
return true;
}
}
}
// Build error message
std::stringstream allowedValuesStreamStr;
for(auto s : allowedValues) {
allowedValuesStreamStr << s << ", ";
}
// We want nice looking error message, so get rid of trailing ", "
std::string allowedValuesStr = allowedValuesStreamStr.str();
allowedValuesStr.resize(allowedValuesStr.length() - 2);
CLog::logError("Configuration error: %s.%s has an invalid value, valid values are: %s. Actual: %s", section.c_str(), key.c_str(), allowedValuesStr.c_str(), valueTemp.c_str());
return false;
}
TConfigValue * CConfig::lookupValue(const std::string& sectionName, const std::string& key) const
{
TConfigValue * res = nullptr;
std::string sectionNameTemp = sectionName;
// Make sure we have brackets around section names
if(sectionNameTemp[0] != '[') sectionNameTemp = "[" + sectionNameTemp;
if(sectionNameTemp[sectionNameTemp.length() - 1] != ']') sectionNameTemp.push_back(']');
auto itSection = m_sections.find(sectionNameTemp);
if(itSection != m_sections.end()) {
auto itValue = itSection->second->getValues().find(key);
if(itValue != itSection->second->getValues().end()) {
res = itValue->second;
}
}
return res;
}

@ -0,0 +1,114 @@
/*
* 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 <utility>
#include <cctype>
#include <cstring>
#include <cassert>
#include <vector>
#define isConfigSection(s)(!s.empty() && s[0] == '[' && s[s.length() - 1] == ']')
#define isConfigValue(s)(s.find('=') != std::string::npos && s.find('#') > s.find('='))
#define isSameNoCase(a,b)(strcasecmp(a.c_str(), b.c_str()) == 0)
#define isDecimalInteger(s)(!s.empty() && (s[0] == '-' || std::isdigit(s[0])) && std::find_if(s.begin(), s.end(), [](unsigned char c) { return !(std::isdigit(c) || c == '-'); }) == s.end())
#define isFloatingPoint(s)(!s.empty() && (s[0] == '-' || std::isdigit(s[0])) && std::find_if(s.begin(), s.end(), [](unsigned char c) { return !(std::isdigit(c) || c == '-' || c == '.'); }) == s.end())
// https://www.programmingnotes.org/6504/c-how-to-make-map-unordered-map-keys-case-insensitive-using-c/
struct case_insensitive_unordered_map {
struct comp {
bool operator() (const std::string& lhs, const std::string& rhs) const {
// On non Windows OS, use the function "strcasecmp" in #include <strings.h>
return strcasecmp(lhs.c_str(), rhs.c_str()) == 0;
}
};
struct hash {
std::size_t operator() (std::string str) const {
for (std::size_t index = 0; index < str.size(); ++index) {
auto ch = static_cast<unsigned char>(str[index]);
str[index] = static_cast<unsigned char>(std::tolower(ch));
}
return std::hash<std::string>{}(str);
}
};
};
typedef struct {
std::string m_key;
std::string m_value;
} TConfigValue;
typedef std::unordered_map<std::string, TConfigValue *, case_insensitive_unordered_map::hash, case_insensitive_unordered_map::comp> CConfigValuesMap;
class CConfigSection {
public:
CConfigSection(const std::string name) :
m_name(name)
{
assert(!name.empty());
}
~CConfigSection()
{
for(auto it = m_values.begin();it != m_values.end();it++) {
delete it->second;
}
m_values.clear();
}
std::string getName() const
{
return m_name;
}
CConfigValuesMap & getValues()
{
return m_values;
}
private:
std::string m_name;
CConfigValuesMap m_values;
};
typedef std::unordered_map<std::string, CConfigSection *, case_insensitive_unordered_map::hash, case_insensitive_unordered_map::comp> CConfigSectionsMap;
class CConfig
{
public:
CConfig(const std::string filename);
~CConfig();
bool load();
bool getValue(const std::string &section, const std::string& key, bool &value, bool defaultValue) const;
bool getValue(const std::string &section, const std::string& key, int &value, int min, int max, int defaultValue) const;
bool getValue(const std::string &section, const std::string& key, double &value, double min, double max, double defaultValue) const;
bool getValue(const std::string &section, const std::string& key, unsigned int &value, unsigned int min, unsigned int max,int defaultValue) const;
bool getValue(const std::string &section, const std::string& key, unsigned char &value, unsigned char min, unsigned char max,unsigned char defaultValue) const;
bool getValue(const std::string &section, const std::string& key, std::string &value, unsigned int minLen, unsigned int maxLen, const std::string defaultValue) const;
bool getValue(const std::string &section, const std::string& key, std::string &value, const std::string defaultValue, const std::vector<std::string>& allowedValues) const;
private:
void stripComment(std::string& s) const;
TConfigValue * readKeyAndValue(const std::string s) const;
TConfigValue * lookupValue(const std::string& section, const std::string& key) const;
std::string m_filename;
CConfigSectionsMap m_sections;
};

@ -98,10 +98,13 @@ bool CDStarGatewayApp::createThread()
return false;
}
TLog log;
config.getLog(log);
CLog::initialize(log.logDir + "dstargateway.log", LS_INFO, true);
Tpaths paths;
config.getPaths(paths);
CLog::initialize(paths.logDir + "dstargateway.log", LS_INFO, true);
m_thread = new CDStarGatewayThread(paths.logDir, paths.dataDir, "");
m_thread = new CDStarGatewayThread(log.logDir, paths.dataDir, "");
// Setup the gateway
TGateway gatewayConfig;

@ -35,340 +35,274 @@ CDStarGatewayConfig::CDStarGatewayConfig(const std::string &pathname)
bool CDStarGatewayConfig::load()
{
CLog::logInfo("Loading configuration from %s", m_fileName.c_str());
Config cfg;
if(open(cfg)
&& loadGateway(cfg)
&& loadIrcDDB(cfg)
&& loadRepeaters(cfg)
&& loadPaths(cfg)
&& loadAPRS(cfg)
&& loadDextra(cfg)
&& loadDCS(cfg)
&& loadDPlus(cfg)
&& loadRemote(cfg)
&& loadXLX(cfg)) {
bool ret = false;
CLog::logInfo("Loading Configuration from %s", m_fileName.c_str());
CConfig cfg(m_fileName);
ret = open(cfg);
if(ret) {
ret = loadGateway(cfg) && ret;
ret = loadIrcDDB(cfg) && ret;
ret = loadRepeaters(cfg) && ret;
ret = loadPaths(cfg) && ret;
ret = loadLog(cfg) && ret;
ret = loadAPRS(cfg) && ret;
ret = loadDextra(cfg) && ret;
ret = loadDCS(cfg) && ret;
ret = loadDPlus(cfg) && ret;
ret = loadRemote(cfg) && ret;
ret = loadXLX(cfg) && ret;
}
if(ret) {
//properly size values
m_gateway.callsign.resize(LONG_CALLSIGN_LENGTH - 1U, ' ');
m_gateway.callsign.push_back('G');
return true;
}
else {
CLog::logError("Loading Configuration from %s failed", m_fileName.c_str());
}
CLog::logError("Loading configuration from %s failed", m_fileName.c_str());
return false;
return ret;
}
bool CDStarGatewayConfig::loadXLX(const Config & cfg)
bool CDStarGatewayConfig::loadXLX(const CConfig & cfg)
{
bool ret = get_value(cfg, "xlx.enabled", m_xlx.enabled, true);
ret = get_value(cfg, "xlx.hostfileUrl", m_xlx.url, 0, 1024, "", false) && ret;
bool ret = cfg.getValue("xlx", "enabled", m_xlx.enabled, true);
ret = cfg.getValue("xlx", "hostfileUrl", m_xlx.url, 0, 1024, "") && ret;
m_xlx.enabled = m_xlx.enabled && !m_xlx.url.empty();
return ret;
}
bool CDStarGatewayConfig::loadRemote(const Config & cfg)
bool CDStarGatewayConfig::loadRemote(const CConfig & cfg)
{
bool ret = get_value(cfg, "remote.enabled", m_remote.enabled, false);
ret = get_value(cfg, "remote.port", m_remote.port, 1U, 65535U, 4242U) && ret;
ret = get_value(cfg, "remote.password", m_remote.password, 0, 1024, "", true) && ret;
bool ret = cfg.getValue("remote", "enabled", m_remote.enabled, false);
ret = cfg.getValue("remote", "port", m_remote.port, 1U, 65535U, 4242U) && ret;
ret = cfg.getValue("remote", "password", m_remote.password, 0, 1024, "") && ret;
m_remote.enabled = m_remote.enabled && !m_remote.password.empty();
return ret;
}
bool CDStarGatewayConfig::loadDextra(const Config & cfg)
bool CDStarGatewayConfig::loadDextra(const CConfig & cfg)
{
bool ret = get_value(cfg, "dextra.enabled", m_dextra.enabled, true);
ret = get_value(cfg, "dextra.maxDongles", m_dextra.maxDongles, 1U, 5U, 5U) && ret;
bool ret = cfg.getValue("dextra", "enabled", m_dextra.enabled, true);
ret = cfg.getValue("dextra", "maxDongles", m_dextra.maxDongles, 1U, 5U, 5U) && ret;
return ret;
}
bool CDStarGatewayConfig::loadDPlus(const Config & cfg)
bool CDStarGatewayConfig::loadDPlus(const CConfig & cfg)
{
bool ret = get_value(cfg, "dplus.enabled", m_dplus.enabled, true);
ret = get_value(cfg, "dplus.maxDongles", m_dplus.maxDongles, 1U, 5U, 5U) && ret;
ret = get_value(cfg, "dplus.login", m_dplus.login, 0, LONG_CALLSIGN_LENGTH, m_gateway.callsign, true) && ret;
bool ret = cfg.getValue("dplus", "enabled", m_dplus.enabled, true);
ret = cfg.getValue("dplus", "maxDongles", m_dplus.maxDongles, 1U, 5U, 5U) && ret;
ret = cfg.getValue("dplus", "login", m_dplus.login, 0, LONG_CALLSIGN_LENGTH, m_gateway.callsign) && ret;
m_dplus.enabled = m_dplus.enabled && !m_dplus.login.empty();
m_dplus.login = CUtils::ToUpper(m_dplus.login);
return ret;
}
bool CDStarGatewayConfig::loadDCS(const Config & cfg)
bool CDStarGatewayConfig::loadDCS(const CConfig & cfg)
{
bool ret = get_value(cfg, "dcs.enabled", m_dcs.enabled, true);
bool ret = cfg.getValue("dcs", "enabled", m_dcs.enabled, true);
return ret;
}
bool CDStarGatewayConfig::loadAPRS(const Config & cfg)
bool CDStarGatewayConfig::loadAPRS(const CConfig & cfg)
{
bool ret = get_value(cfg, "aprs.enabled", m_aprs.enabled, false);
ret = get_value(cfg, "aprs.port", m_aprs.port, 1U, 65535U, 14580U) && ret;
ret = get_value(cfg, "aprs.hostname", m_aprs.hostname, 0, 1024, "rotate.aprs2.net", true) && ret;
ret = get_value(cfg, "aprs.password", m_aprs.password, 0U, 30U, "", true) && ret;
bool ret = cfg.getValue("aprs", "enabled", m_aprs.enabled, false);
ret = cfg.getValue("aprs", "port", m_aprs.port, 1U, 65535U, 14580U) && ret;
ret = cfg.getValue("aprs", "hostname", m_aprs.hostname, 0, 1024, "rotate.aprs2.net") && ret;
ret = cfg.getValue("aprs", "password", m_aprs.password, 0U, 30U, "") && ret;
m_aprs.enabled = m_aprs.enabled && !m_aprs.password.empty();
return ret;
}
bool CDStarGatewayConfig::loadPaths(const Config & cfg)
bool CDStarGatewayConfig::loadLog(const CConfig & cfg)
{
get_value(cfg, "paths.log", m_paths.logDir, 0, 2048, "/var/log/dstargateway/", true);
get_value(cfg, "paths.data", m_paths.dataDir, 0, 2048, "/var/log/dstargateway/", true);
if(m_paths.logDir[m_paths.logDir.length() - 1] != '/') {
m_paths.logDir.push_back('/');
}
bool ret =cfg.getValue("log", "path", m_log.logDir, 0, 2048, "/var/log/dstargateway/");
if(m_paths.dataDir[m_paths.dataDir.length() - 1] != '/') {
m_paths.dataDir.push_back('/');
if(ret && m_log.logDir[m_log.logDir.length() - 1] != '/') {
m_log.logDir.push_back('/');
}
//TODO 20211226 check if directory are accessible
return true;
return ret;
}
bool CDStarGatewayConfig::loadRepeaters(const Config & cfg)
bool CDStarGatewayConfig::loadPaths(const CConfig & cfg)
{
cfg.lookup("repeaters");
// repeater parameters
for (int i=0; i<cfg.lookup("repeaters").getLength(); i++) {
std::stringstream key;
bool isOk = false;
TRepeater * repeater = new TRepeater;
key << "repeaters.[" << i << "]";
if (get_value(cfg, key.str() + ".callsign", repeater->callsign, 0, 7, m_gateway.callsign, true)) {
CUtils::ToUpper(repeater->callsign);
}
if(get_value(cfg, key.str() + ".band", repeater->band, 1, 1, "A")) {
CUtils::ToUpper(repeater->band);
}
if (get_value(cfg, key.str() + ".address", repeater->address, 0, 15, "127.0.0.1", true)) {
// ???
}
bool ret = cfg.getValue("paths", "data", m_paths.dataDir, 0, 2048, "/usr/local/share/dstargateway.d/");
if(get_value(cfg, key.str() + ".port", repeater->port, 1U, 65535U, 20011U)) {
// ???
}
if(ret && m_paths.dataDir[m_paths.dataDir.length() - 1] != '/') {
m_paths.dataDir.push_back('/');
}
std::string hwType;
if(get_value(cfg, key.str() + ".type", hwType, 1, 5, "", false, {"hb", "icom"} )) {
if(hwType == "hb") repeater->hwType = HW_HOMEBREW;
else if(hwType == "icom") repeater->hwType = HW_ICOM;
}
//TODO 20211226 check if directory are accessible
if (get_value(cfg, key.str() + ".reflector", repeater->reflector, 0, LONG_CALLSIGN_LENGTH, "", true)) {
// ???
}
return ret;
}
if (get_value(cfg, key.str() + ".reflectorAtStartup", repeater->reflectorAtStartup, true)) {
// ???
}
bool CDStarGatewayConfig::loadRepeaters(const CConfig & cfg)
{
for(unsigned int i = 0; i < 4; i++) {
std::string section = ((std::stringstream() << "repeater_" << (i + 1))).str();
bool repeaterEnabled;
std::string reconnect;
if (get_value(cfg, key.str() + ".reflectorReconnect", reconnect, 0, 5, "never", true,
{"never", "fixed", "5", "10", "15", "20", "25", "30", "60", "90", "120", "180"})) {
if(reconnect == "never") repeater->reflectorReconnect = RECONNECT_NEVER;
else if(reconnect == "5") repeater->reflectorReconnect = RECONNECT_5MINS;
else if(reconnect == "10") repeater->reflectorReconnect = RECONNECT_10MINS;
else if(reconnect == "15") repeater->reflectorReconnect = RECONNECT_15MINS;
else if(reconnect == "20") repeater->reflectorReconnect = RECONNECT_20MINS;
else if(reconnect == "25") repeater->reflectorReconnect = RECONNECT_25MINS;
else if(reconnect == "30") repeater->reflectorReconnect = RECONNECT_30MINS;
else if(reconnect == "60") repeater->reflectorReconnect = RECONNECT_60MINS;
else if(reconnect == "90") repeater->reflectorReconnect = RECONNECT_90MINS;
else if(reconnect == "120") repeater->reflectorReconnect = RECONNECT_120MINS;
else if(reconnect == "180") repeater->reflectorReconnect = RECONNECT_180MINS;
else if(reconnect == "fixed") repeater->reflectorReconnect = RECONNECT_FIXED;
}
bool ret = cfg.getValue(section, "enabled", repeaterEnabled, false);
if(!ret || !repeaterEnabled)
continue;
if(get_value(cfg, key.str() + ".frequency", repeater->frequency, 0.0, 1500.0, 434.0)) {
// ???
}
if(get_value(cfg, key.str() + ".offset", repeater->offset, -50.0, 50.0, 0.0)) {
// ???
}
if(get_value(cfg, key.str() + ".rangeKm", repeater->range, 0.0, 3000.0, 0.0)) {
// ???
}
if(get_value(cfg, key.str() + ".latitude", repeater->latitude, -90, 90.0, m_gateway.latitude)) {
// ???
}
if(get_value(cfg, key.str() + ".longitude", repeater->longitude, -180, 180.0, m_gateway.longitude)) {
// ???
}
if(get_value(cfg, key.str() + ".agl", repeater->agl, 0, 1000.0, 0.0)) {
// ???
}
TRepeater * repeater = new TRepeater;
ret = cfg.getValue(section, "band", repeater->band, 1, 2, "B") && ret;
ret = cfg.getValue(section, "callsign", repeater->callsign, 0, LONG_CALLSIGN_LENGTH - 1, m_gateway.callsign);
ret = cfg.getValue(section, "address", repeater->address, 0, 15, "127.0.0.1") && ret;
ret = cfg.getValue(section, "port", repeater->port, 1U, 65535U, 20011U) && ret;
if(get_value(cfg, key.str() + ".description1", m_gateway.description1, 0, 1024, "")) {
// ???
std::string hwType;
ret = cfg.getValue(section, "type", hwType, "", {"hb", "icom"}) && ret;
if(ret) {
if(hwType == "hb") repeater->hwType = HW_HOMEBREW;
else if(hwType == "icom") repeater->hwType = HW_ICOM;
}
if(get_value(cfg, key.str() + ".description2", m_gateway.description2, 0, 1024, "")) {
// ???
}
ret = cfg.getValue(section, "reflector", repeater->reflector, 0, LONG_CALLSIGN_LENGTH, "") && ret;
ret = cfg.getValue(section, "reflectorAtStartup", repeater->reflectorAtStartup, !repeater->reflector.empty()) && ret;
if(get_value(cfg, key.str() + ".url", m_gateway.url, 0, 1024, "")) {
// ???
}
int band;
if(get_value(cfg, key.str() + ".band1", band, 0, 255, 0)) {
repeater->band1 = (unsigned char)band;
}
if(get_value(cfg, key.str() + ".band2", band, 0, 255, 0)) {
repeater->band2 = (unsigned char)band;
}
if(get_value(cfg, key.str() + ".band3", band, 0, 255, 0)) {
repeater->band3 = (unsigned char)band;
std::string reconnect;
ret = cfg.getValue(section, "reflectorReconnect", reconnect, "never", {"never", "fixed", "5", "10", "15", "20", "25", "30", "60", "90", "120", "180"}) && ret;
if(ret) {
if(reconnect == "never") repeater->reflectorReconnect = RECONNECT_NEVER;
else if(reconnect == "5") repeater->reflectorReconnect = RECONNECT_5MINS;
else if(reconnect == "10") repeater->reflectorReconnect = RECONNECT_10MINS;
else if(reconnect == "15") repeater->reflectorReconnect = RECONNECT_15MINS;
else if(reconnect == "20") repeater->reflectorReconnect = RECONNECT_20MINS;
else if(reconnect == "25") repeater->reflectorReconnect = RECONNECT_25MINS;
else if(reconnect == "30") repeater->reflectorReconnect = RECONNECT_30MINS;
else if(reconnect == "60") repeater->reflectorReconnect = RECONNECT_60MINS;
else if(reconnect == "90") repeater->reflectorReconnect = RECONNECT_90MINS;
else if(reconnect == "120") repeater->reflectorReconnect = RECONNECT_120MINS;
else if(reconnect == "180") repeater->reflectorReconnect = RECONNECT_180MINS;
else if(reconnect == "fixed") repeater->reflectorReconnect = RECONNECT_FIXED;
}
// We have read a complete repeater record, validate it
if(repeater->callsign.length() > 0) {
isOk = true;
ret = cfg.getValue(section, "frequency", repeater->frequency, 0.1, 1500.0, 434.0) && ret;
ret = cfg.getValue(section, "offset", repeater->offset, -50.0, 50.0, 0.0) && ret;
ret = cfg.getValue(section, "rangeKm", repeater->range, 0.0, 3000.0, 0.0) && ret;
ret = cfg.getValue(section, "latitude", repeater->latitude, -90.0, 90.0, m_gateway.latitude) && ret;
ret = cfg.getValue(section, "longitude", repeater->longitude, -180.0, 180.0, m_gateway.longitude) && ret;
ret = cfg.getValue(section, "agl", repeater->agl, 0, 1000.0, 0.0) && ret;
ret = cfg.getValue(section, "description1", m_gateway.description1, 0, 1024, "") && ret;
ret = cfg.getValue(section, "description2", m_gateway.description2, 0, 1024, "") && ret;
ret = cfg.getValue(section, "url", m_gateway.url, 0, 1024, "") && ret;;
ret = cfg.getValue(section, "band1", repeater->band1, 0, 255, 0) && ret;
ret = cfg.getValue(section, "band2", repeater->band2, 0, 255, 0) && ret;
ret = cfg.getValue(section, "band3", repeater->band3, 0, 255, 0) && ret;
if(ret) {
m_repeaters.push_back(repeater);
}
else {
CLog::logError("Repeater %d has an empty callsign", i);
}
if (isOk && !isalpha(repeater->band[0])) {
isOk = false;
CLog::logError("Repeater %s band is not a letter", i);
}
if (isOk && repeater->address.length() == 0) {
isOk = false;
}
if(!isOk) {
delete repeater;
} else {
m_repeaters.push_back(repeater);
}
}
return m_repeaters.size() > 0;
if(m_repeaters.size() == 0U) {
CLog::logError("Configuration error: no repeaters configured !");
return false;
}
return true;
}
bool CDStarGatewayConfig::loadIrcDDB(const Config & cfg)
bool CDStarGatewayConfig::loadIrcDDB(const CConfig & cfg)
{
//ircDDB Networks
for(int i = 0; i < cfg.lookup("ircddb").getLength(); i++) {
TircDDB * ircddb = new TircDDB();
std::stringstream key;
key << "ircddb.[" << i << "].hostname";
if(! get_value(cfg, key.str(), ircddb->hostname, 5, 30, "") || ircddb->hostname == "") {//do not allow hostname to be empty
delete ircddb;
continue;
}
bool ret = true;
for(unsigned int i = 0; i < 4; i++) {
std::string section = ((std::stringstream() << "ircddb_" << (i + 1))).str();
bool ircEnabled;
key.str("");key.clear();
key << "ircddb.[" << i << "].username";
if (! get_value(cfg, key.str(), ircddb->username, 3, 8, m_gateway.callsign)) {//default user name to callsign
delete ircddb;
ret = cfg.getValue(section, "enabled", ircEnabled, false) && ret;
if(!ircEnabled)
continue;
}
CUtils::ToUpper(ircddb->username);
TircDDB * ircddb = new TircDDB;
ret = cfg.getValue(section, "hostname", ircddb->hostname, 0, 1024, "ircv4.openquad.net") && ret;
ret = cfg.getValue(section, "username", ircddb->username, 0, LONG_CALLSIGN_LENGTH - 1, m_gateway.callsign) && ret;
ret = cfg.getValue(section, "password", ircddb->password, 0, 1024, "") && ret;
key.str("");key.clear();
key << "ircddb.[" << i << "].password";
if(!get_value(cfg, key.str(), ircddb->password, 0, 30, "")) {
if(ret) {
m_ircDDB.push_back(ircddb);
}
else {
delete ircddb;
continue;
}
ircddb->isQuadNet = ircddb->hostname.find("openquad.net") != std::string::npos;
this->m_ircDDB.push_back(ircddb);
}
if(this->m_ircDDB.size() == 0) {//no ircddb network specified? Default to openquad!
TircDDB * ircddb = new TircDDB();
ircddb->hostname = "ircv4.openquad.net";
ircddb->password = "";
ircddb->username = m_gateway.callsign;
ircddb->isQuadNet = true;
this->m_ircDDB.push_back(ircddb);
CLog::logInfo("No ircDDB networks configured, defaulting to IRCDDB: host=%s user=%s", ircddb->hostname.c_str(), ircddb->username.c_str());
}
return true;
return ret;
}
bool CDStarGatewayConfig::loadGateway(const Config & cfg)
bool CDStarGatewayConfig::loadGateway(const CConfig & cfg)
{
bool ret = get_value(cfg, "gateway.callsign", m_gateway.callsign, 3, 8, "");
get_value(cfg, "gateway.address", m_gateway.address, 0, 20, "0.0.0.0", true) && ret;
get_value(cfg, "gateway.hbAddress", m_gateway.hbAddress, 0, 20, "127.0.0.1", true) && ret;
get_value(cfg, "gateway.hbPort", m_gateway.hbPort, 1U, 65535U, 20010U) && ret;
get_value(cfg, "gateway.icomAddress", m_gateway.icomAddress, 0, 20, "127.0.0.1", true) && ret;
get_value(cfg, "gateway.icomPort", m_gateway.icomPort, 1U, 65535U, 20000U) && ret;
get_value(cfg, "gateway.latitude", m_gateway.latitude, -90.0, 90.0, 0.0) && ret;
get_value(cfg, "gateway.longitude", m_gateway.longitude, -180.0, 180.0, 0.0) && ret;
get_value(cfg, "gateway.description1", m_gateway.description1, 0, 1024, "") && ret;
get_value(cfg, "gateway.description2", m_gateway.description2, 0, 1024, "") && ret;
get_value(cfg, "gateway.url", m_gateway.url, 0, 1024, "") && ret;
bool ret = cfg.getValue("gateway", "callsign", m_gateway.callsign, 3, 8, "");
ret = cfg.getValue("gateway", "address", m_gateway.address, 0, 20, "0.0.0.0") && ret;
ret = cfg.getValue("gateway", "hbAddress", m_gateway.hbAddress, 0, 20, "127.0.0.1") && ret;
ret = cfg.getValue("gateway", "hbPort", m_gateway.hbPort, 1U, 65535U, 20010U) && ret;
ret = cfg.getValue("gateway", "icomAddress", m_gateway.icomAddress, 0, 20, "127.0.0.1") && ret;
ret = cfg.getValue("gateway", "icomPort", m_gateway.icomPort, 1U, 65535U, 20000U) && ret;
ret = cfg.getValue("gateway", "latitude", m_gateway.latitude, -90.0, 90.0, 0.0) && ret;
ret = cfg.getValue("gateway", "longitude", m_gateway.longitude, -180.0, 180.0, 0.0) && ret;
ret = cfg.getValue("gateway", "description1", m_gateway.description1, 0, 1024, "") && ret;
ret = cfg.getValue("gateway", "description2", m_gateway.description2, 0, 1024, "") && ret;
ret = cfg.getValue("gateway", "url", m_gateway.url, 0, 1024, "") && ret;
std::string type;
get_value(cfg, "gateway.type", type, 0, 8, "repeater", true, {"repeater", "hotspot"}) && ret;
if(type == "repeater") m_gateway.type = GT_REPEATER;
if(type == "hotspot") m_gateway.type = GT_HOTSPOT;
ret = cfg.getValue("gateway", "type", type, "repeater", {"repeater", "hotspot"}) && ret;
if(type == "repeater") m_gateway.type = GT_REPEATER;
else if(type == "hotspot") m_gateway.type = GT_HOTSPOT;
std::string lang;
get_value(cfg, "gateway.language", lang, 0, 30, "english_uk", true, {"english_uk", "deutsch", "dansk", "francais", "italiano", "polski", "english_us", "espanol", "svenska", "nederlands_nl", "nederlands_be", "norsk", "portugues"});
if(lang == "english_uk") m_gateway.language = TL_ENGLISH_UK;
else if(lang == "deutsch") m_gateway.language = TL_DEUTSCH;
else if(lang == "dansk") m_gateway.language = TL_DANSK;
else if(lang == "francais") m_gateway.language = TL_FRANCAIS;
else if(lang == "italiano") m_gateway.language = TL_ITALIANO;
else if(lang == "polski") m_gateway.language = TL_POLSKI;
else if(lang == "english_us") m_gateway.language = TL_ENGLISH_US;
else if(lang == "espanol") m_gateway.language = TL_ESPANOL;
else if(lang == "svenska") m_gateway.language = TL_SVENSKA;
else if(lang == "nederlands_nl") m_gateway.language = TL_NEDERLANDS_NL;
else if(lang == "nederlands_be") m_gateway.language = TL_NEDERLANDS_BE;
else if(lang == "norsk") m_gateway.language = TL_NORSK;
else if(lang == "portugues") m_gateway.language = TL_PORTUGUES;
ret = cfg.getValue("gateway", "language", lang, "english_uk",
{"english_uk", "deutsch", "dansk", "francais", "italiano", "polski",
"english_us", "espanol", "svenska", "nederlands_nl", "nederlands_be", "norsk", "portugues"}) && ret;;
if(lang == "english_uk") m_gateway.language = TL_ENGLISH_UK;
else if(lang == "deutsch") m_gateway.language = TL_DEUTSCH;
else if(lang == "dansk") m_gateway.language = TL_DANSK;
else if(lang == "francais") m_gateway.language = TL_FRANCAIS;
else if(lang == "italiano") m_gateway.language = TL_ITALIANO;
else if(lang == "polski") m_gateway.language = TL_POLSKI;
else if(lang == "english_us") m_gateway.language = TL_ENGLISH_US;
else if(lang == "espanol") m_gateway.language = TL_ESPANOL;
else if(lang == "svenska") m_gateway.language = TL_SVENSKA;
else if(lang == "nederlands_nl")m_gateway.language = TL_NEDERLANDS_NL;
else if(lang == "nederlands_be")m_gateway.language = TL_NEDERLANDS_BE;
else if(lang == "norsk") m_gateway.language = TL_NORSK;
else if(lang == "portugues") m_gateway.language = TL_PORTUGUES;
CUtils::ToUpper(m_gateway.callsign);
CUtils::clean(m_gateway.description1, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .,&*()-+=@/?:;");
CUtils::clean(m_gateway.description2, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .,&*()-+=@/?:;");
CUtils::clean(m_gateway.url, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .,&*()-+=@/?:;");
return true;
return ret;
}
bool CDStarGatewayConfig::open(Config & cfg)
bool CDStarGatewayConfig::open(CConfig & cfg)
{
if (m_fileName.size() < 1) {
CLog::logError("Configuration filename too short!\n");
return false;
}
try {
cfg.readFile(m_fileName.c_str());
return cfg.load();
}
catch(const FileIOException &fioex) {
catch(...) {
CLog::logError("Can't read %s\n", m_fileName.c_str());
return false;
}
catch(const ParseException &pex) {
CLog::logError("Parse error at %s:%d - %s\n", pex.getFile(), pex.getLine(), pex.getError());
return false;
}
return true;
}
@ -385,93 +319,6 @@ CDStarGatewayConfig::~CDStarGatewayConfig()
}
}
bool CDStarGatewayConfig::get_value(const Config &cfg, const std::string &path, unsigned int &value, unsigned int min, unsigned int max, unsigned int default_value)
{
value = default_value;
int valueTmp;
if(get_value(cfg, path, valueTmp, (int)min, (int)max, (int)default_value)
&& valueTmp >= 0) {
value = valueTmp;
}
return true;
}
bool CDStarGatewayConfig::get_value(const Config &cfg, const std::string &path, int &value, int min, int max, int default_value)
{
if (cfg.lookupValue(path, value)) {
if (value < min || value > max)
value = default_value;
} else
value = default_value;
return true;
}
bool CDStarGatewayConfig::get_value(const Config &cfg, const std::string &path, double &value, double min, double max, double default_value)
{
if (cfg.lookupValue(path, value)) {
if (value < min || value > max)
value = default_value;
} else
value = default_value;
return true;
}
bool CDStarGatewayConfig::get_value(const Config &cfg, const std::string &path, bool &value, bool default_value)
{
if (! cfg.lookupValue(path, value))
value = default_value;
return true;
}
bool CDStarGatewayConfig::get_value(const Config &cfg, const std::string &path, std::string &value, int min, int max, const std::string &default_value)
{
return get_value(cfg, path, value, min, max, default_value, false);
}
bool CDStarGatewayConfig::get_value(const Config &cfg, const std::string &path, std::string &value, int min, int max, const std::string &default_value, bool emptyToDefault)
{
if (cfg.lookupValue(path, value)) {
int l = value.length();
if (l<min || l>max) {
CLog::logWarning("%s has an invalid length, must be between %d and %d, actual %d", path.c_str(), min, max, l);
return false;
}
} else
value = default_value;
if(emptyToDefault && value.length() == 0) {
value = default_value;
}
return true;
}
bool CDStarGatewayConfig::get_value(const Config &cfg, const std::string &path, std::string &value, int min, int max, const std::string &default_value, bool emptyToDefault, const std::vector<std::string>& allowedValues)
{
bool ret = get_value(cfg, path, value, min, max, default_value, emptyToDefault);
if(ret) {
for(std::string s : allowedValues) {
if(s == value) {
ret = true;
break;
}
}
}
if(!ret) {
std::stringstream message;
message << path << "=" << value << " has an invalid value. Valid values are : ";
for(std::string s : allowedValues) {
message << s << ", ";
}
CLog::logWarning(message.str());
}
return ret;
}
void CDStarGatewayConfig::getGateway(TGateway & gateway) const
{
gateway = m_gateway;
@ -497,6 +344,11 @@ void CDStarGatewayConfig::getRepeater(unsigned int index, TRepeater & repeater)
repeater = *(m_repeaters[index]);
}
void CDStarGatewayConfig::getLog(TLog & log) const
{
log = m_log;
}
void CDStarGatewayConfig::getPaths(Tpaths & paths) const
{
paths = m_paths;

@ -20,10 +20,9 @@
#include <string>
#include <vector>
#include <libconfig.h++>
#include "Defs.h"
using namespace libconfig;
#include "Defs.h"
#include "Config.h"
typedef struct {
GATEWAY_TYPE type;
@ -62,9 +61,9 @@ typedef struct {
std::string description1;
std::string description2;
std::string url;
char band1;
char band2;
char band3;
unsigned char band1;
unsigned char band2;
unsigned char band3;
} TRepeater;
typedef struct {
@ -75,10 +74,13 @@ typedef struct {
} TircDDB;
typedef struct {
std::string logDir;
std::string dataDir;
} Tpaths;
typedef struct {
std::string logDir;
} TLog;
typedef struct {
bool enabled;
std::string hostname;
@ -113,6 +115,7 @@ typedef struct {
std::string password;
} TRemote;
class CDStarGatewayConfig {
public:
CDStarGatewayConfig(const std::string &pathname);
@ -124,6 +127,7 @@ public:
unsigned int getIrcDDBCount() const;
void getRepeater(unsigned int repeaterIndex, TRepeater & repeater) const;
unsigned int getRepeaterCount() const;
void getLog(TLog& log) const;
void getPaths(Tpaths & paths) const;
void getAPRS(TAPRS & aprs) const;
void getDExtra(TDextra & dextra) const;
@ -133,24 +137,18 @@ public:
void getXLX(TXLX & xlx) const;
private:
bool open(Config & cfg);
bool loadGateway(const Config & cfg);
bool loadIrcDDB(const Config & cfg);
bool loadRepeaters(const Config & cfg);
bool loadPaths(const Config & cfg);
bool loadAPRS(const Config & cfg);
bool loadDextra(const Config & cfg);
bool loadDPlus(const Config & cfg);
bool loadDCS(const Config & cfg);
bool loadRemote(const Config & cfg);
bool loadXLX(const Config & cfg);
bool get_value(const Config &cfg, const std::string &path, unsigned int &value, unsigned int min, unsigned int max, unsigned int default_value);
bool get_value(const Config &cfg, const std::string &path, int &value, int min, int max, int default_value);
bool get_value(const Config &cfg, const std::string &path, double &value, double min, double max, double default_value);
bool get_value(const Config &cfg, const std::string &path, bool &value, bool default_value);
bool get_value(const Config &cfg, const std::string &path, std::string &value, int min, int max, const std::string &default_value);
bool get_value(const Config &cfg, const std::string &path, std::string &value, int min, int max, const std::string &default_value, bool emptyToDefault);
bool get_value(const Config &cfg, const std::string &path, std::string &value, int min, int max, const std::string &default_value, bool emptyToDefault, const std::vector<std::string>& allowedValues);
bool open(CConfig & cfg);
bool loadGateway(const CConfig & cfg);
bool loadIrcDDB(const CConfig & cfg);
bool loadRepeaters(const CConfig & cfg);
bool loadLog(const CConfig & cfg);
bool loadPaths(const CConfig & cfg);
bool loadAPRS(const CConfig & cfg);
bool loadDextra(const CConfig & cfg);
bool loadDPlus(const CConfig & cfg);
bool loadDCS(const CConfig & cfg);
bool loadRemote(const CConfig & cfg);
bool loadXLX(const CConfig & cfg);
std::string m_fileName;
TGateway m_gateway;
@ -161,6 +159,7 @@ private:
TDCS m_dcs;
TRemote m_remote;
TXLX m_xlx;
TLog m_log;
std::vector<TRepeater *> m_repeaters;
std::vector<TircDDB *> m_ircDDB;
};

@ -34,7 +34,7 @@ DEPS = $(SRCS:.cpp=.d)
all: dstargateway
dstargateway : GitVersion.h $(OBJS)
g++ $(CPPFLAGS) -o dstargateway $(OBJS) -lconfig++ -lcurl -pthread
g++ $(CPPFLAGS) -o dstargateway $(OBJS) -lcurl -pthread
%.o : %.cpp
g++ $(CPPFLAGS) -MMD -MD -c $< -o $@

@ -12,8 +12,10 @@
- [4. Contributing](#4-contributing)
- [4.1. Work Flow](#41-work-flow)
- [5. Version History](#5-version-history)
- [5.1. v0.2](#51-v02)
- [5.2. v0.1](#52-v01)
- [5.1. Version 0.3](#51-version-03)
- [5.2. Version 0.2](#52-version-02)
- [5.3. Version 0.1](#53-version-01)
# 1. Introduction
This is a port of G4KLX Jonathan Naylor's [ircddbGateway](https://github.com/g4klx/ircDDBGateway). It is wxWidgets free and has minimal dependencies to boost (header libs only), libconfig++ and libcurl
@ -64,7 +66,12 @@ sudo make install
```
## 3.5. Configuring
After installing you have to edit the configuration file. If you went with default paths, the config file is located in `/usr/local/etc/dstargateway.cfg`
The syntax is libconfig syntax, keep in my mind that **configuration keys are case sensitive**.
The configuration format is quite straight forward. It is organised in sections and key/value pairs.
The order of the sections or key/values pairs inside the sections does not matter nor does casing.
Boolean values can be set using true, false, 1 or 0
Floating point values must use . (point) as decimal separatorsensitive.
When done with configuration, the daemon will be started automatically on boot. To manual start and stop it use the usual systemd commands
```
sudo systemctl start dstargateway.service
@ -75,8 +82,10 @@ sudo systemctl stop dstargateway.service
I Use [Git flow](https://danielkummer.github.io/git-flow-cheatsheet/) as my workflow. PR are welcome and shall be done against the develop branch and follow the Git Flow branch naming rules.
# 5. Version History
## 5.1. v0.2
## 5.1. Version 0.3
- [Improvement] Get ride of libcongif++ dependency. When upgrading from earlier version you need to manualy delete the config file before reinstalling.
## 5.2. Version 0.2
- [bugfix] ircDDBFreeze when repeater not found ([#1](https://github.com/F4FXL/DStarGateway/issues/1))
- Code sanitization
## 5.2. v0.1
## 5.3. Version 0.1
First working version

@ -23,4 +23,4 @@
#include <string>
const std::string VENDOR_NAME("Geoffrey Merck F4FXL / KC3FRA");
const std::string VERSION("0.2");
const std::string VERSION("0.3");

@ -1,100 +1,169 @@
gateway = {
type= # repeater, hotspot, dongle. Defaults to repeater
callsign = "N0CALL"
address = "" # this is the computer interface for the outgoing connection. Usually leave it blank and it will use whatever is avaiable.
icomAddress="172.16.0.20"
icomPort=20000
hbAddress="127.0.0.1" #address to use for connectin with homebrew repeaters (MMDVMHost, DStarRepeater)
hbPort=20010
latitude=""
longitude=""
description1=""
description2=""
url=""
language= # "english_uk", "deutsch", "dansk", "francais", "italiano", "polski", "english_us", "espanol", "svenska", "nederlands_nl", "nederlands_be", "norsk", "portugues"
}
# NOTHING usually needs to be specified in the ircddb section
# If nothing is specified, defaults to openquad
ircddb = ( #ircddb networks in paretheses
# {
# hostname = "ipv4.openquad.net"
# username = "CHNGME" # The ircDDB username default to the value defined for server.callsign.
# password = ""
# },
# {
# hostname = "xx.ircddb.net"
# username = "CHNGME" # The ircDDB username default to the value defined for server.callsign.
# password = ""
# }
)
repeaters = ( # The modules list is contained in parentheses
{ # Up to 15 different modules can be specified, each in curly brackets
band = "B" # Each module has to have a band letter
callsign = "" # Has to be less than 8 characters, if empty gateway call will be used
address = "" # address where the repeater can be found, if left empty defaults to 127.0.0.1
port=20011
type="hb" # hb or icom
reflector=""
reflectorAtStartup=true
reflectorReconnect= # never, fixed, 5, 10, 15, 20, 25, 30, 60, 90, 120, 180
frequency=434.00000
offset=9.00000
rangeKm=0.1
latitude=""
longitude=""
agl=""
description1=""
description2=""
url=""
band1=""
band2=""
band3=""
# }, # be sure there is a comma between repeaters
# { # Up to 15 different modules can be specified, each in curly brackets
# band = "C" # Each module has to have a band letter
# callsign = "N0CALL" # Has to be less than 8 characters
# port=20012
# }
}
) # close paren to close out the module defines
aprs = {
enabled=true
hostname="rotate.aprs2.net" # Defaults to rotate.aprs2.net
port=14580 # Defaults to 14580, there is no reason to change this
password="1234"
}
paths = {
log="/var/log/dstargateway/"
data="/usr/local/share/dstargateway/" #Path where the data (hostfiles, audio files etc) can be found
}
dextra = {
enabled=true # There is no reason to disable this
maxDongles=5
}
dplus = {
enabled=true # There is no reason to disable this
maxDongles=5
login="" # defaults to gateway callsign
}
dcs = {
enabled=true # There is no reason to disable this
}
xlx = {
enabled=true
hostfileUrl="http://xlxapi.rlx.lu/api.php?do=GetXLXDMRMaster"
}
remote = {
enabled=false
port=4242
password="CHANGE_ME" # If password is left blank, remote will be disabled regardless of the enabled field
}
# The configuration format is quite straight forward. It is organised in sections and key/value pairs.
# The order of the sections or key/values pairs inside the sections does not matter nor does casing.
# Boolean values can be set using true, false, 1 or 0
# Floating point values must use . (point) as decimal separator
[Gateway]
type= # repeater, hotspot, dongle. Defaults to repeater
callsign=
address=0.0.0.0 # this is the computer interface for the outgoing connection. Usually leave it blank and it will use whatever is avaiable.
icomAddress=172.16.0.20
icomPort=20000
hbAddress= #address to use for connecting to the homebrew repeaters (MMDVMHost, DStarRepeater), defaults to 127.0.0.1
hbPort=20010
latitude=0.0
longitude=0.0
description1=
description2=
url=
language= # valid values: english_uk, deutsch, dansk, francais, italiano, polski, english_us, espanol, svenska, nederlands_nl, nederlands_be, norsk, portugues
#up to 4 ircddb networks can be specified
[ircddb_1]
enabled=true
hostname=ircv4.openquad.net
username= # The ircDDB username default to the value defined for gateway callsign.
password=
[ircddb_2]
enabled=false
hostname=
username= # The ircDDB username defaults to the value defined for gateway callsign.
password=
[ircddb_3]
hostname=false
username=CHNGME # The ircDDB username defaults to the value defined for gateway callsign.
password=
[ircddb_4]
hostname=false
username=CHNGME # The ircDDB username defaults to the value defined for gateway callsign.
password=
# up to 4 repeaters can be added
[Repeater_1]
enabled=true
band=B # Each module has to have a band letter
callsign= # Has to be less than 8 characters, if empty gateway call will be used
address= # address where the repeater can be found, if left empty defaults to 127.0.0.1
port=20011
type=hb # valid values: hb or icom
reflector=
reflectorAtStartup= # if reflector is set, this defaults to true
reflectorReconnect=30 # valid values: never, fixed, 5, 10, 15, 20, 25, 30, 60, 90, 120, 180
frequency=434.0
offset=-1.6
rangeKm=20
latitude=0.0
longitude=0.0
agl=
description1=
description2=
url=
band1=
band2=
band3=
[Repeater_2]
enabled=false
band= # Each module has to have a band letter
callsign= # Has to be less than 8 characters, if empty gateway call will be used
address= # address where the repeater can be found, if left empty defaults to 127.0.0.1
port=20012
type=hb # hb or icom
reflector=
reflectorAtStartup=true
reflectorReconnect=30 # never, fixed, 5, 10, 15, 20, 25, 30, 60, 90, 120, 180
frequency=434.0
offset=-1.6
rangeKm=20
latitude=0.0
longitude=0.0
agl=
description1=
description2=
url=
band1=
band2=
band3=
[Repeater_3]
enabled=false
band= # Each module has to have a band letter
callsign= # Has to be less than 8 characters, if empty gateway call will be used
address= # address where the repeater can be found, if left empty defaults to 127.0.0.1
port=20013
type=hb # hb or icom
reflector=DCS208 C
reflectorAtStartup=true
reflectorReconnect=30 # never, fixed, 5, 10, 15, 20, 25, 30, 60, 90, 120, 180
frequency=434.0
offset=-1.6
rangeKm=20
latitude=0.0
longitude=0.0
agl=
description1=
description2=
url=
band1=
band2=
band3=
[Repeater_4]
enabled=false
band= # Each module has to have a band letter
callsign= # Has to be less than 8 characters, if empty gateway call will be used
address= # address where the repeater can be found, if left empty defaults to 127.0.0.1
port=20014
type=hb # hb or icom
reflector=DCS208 C
reflectorAtStartup=true
reflectorReconnect=30 # never, fixed, 5, 10, 15, 20, 25, 30, 60, 90, 120, 180
frequency=434.0
offset=-1.6
rangeKm=20
latitude=0.0
longitude=0.0
agl=
description1=
description2=
url=
band1=
band2=
band3=
[APRS]
enabled=true
hostname=rotate.aprs2.net # Defaults to rotate.aprs2.net
port=14580 # Defaults to 14580, there is no reason to change this
password=12345
[Log]
path=/var/log/dstargateway/
[Paths]
data=/usr/local/share/dstargateway.d/ #Path where the data (hostfiles, audio files etc) can be found
[DExtra]
enabled=true # There is no reason to disable this
maxDongles=5
[DPlus]
enabled=true # There is no reason to disable this
maxDongles=5
login=F4FXL # defaults to gateway callsign
[DCS]
enabled=true # There is no reason to disable this
[XLX]
enabled=true
hostfileUrl=http://xlxapi.rlx.lu/api.php?do=GetXLXDMRMaster
[Remote]
enabled=false
port=4242
password=CHANGE_ME # If password is left blank, remote will be disabled regardless of the enabled field

Loading…
Cancel
Save

Powered by TurnKey Linux.