mirror of https://github.com/LX3JL/xlxd.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
326 lines
10 KiB
326 lines
10 KiB
//
|
|
// cconfig.cpp
|
|
// ambed
|
|
//
|
|
// Created by Andy Taylor (MW0MWZ) on 31/10/2026.
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
// This file is part of ambed.
|
|
//
|
|
// xlxd 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 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// xlxd 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 Foobar. If not, see <http://www.gnu.org/licenses/>.
|
|
// ----------------------------------------------------------------------------
|
|
#include <cmath>
|
|
#include <cstdlib>
|
|
#include <fstream>
|
|
#include <sys/stat.h>
|
|
#include <arpa/inet.h>
|
|
#include "cconfig.h"
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// global objects
|
|
|
|
CConfig g_Config;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// keys, the defaults are the values in main.h
|
|
|
|
struct SConfigKey
|
|
{
|
|
const char *name;
|
|
float fDefault;
|
|
float fMin;
|
|
float fMax;
|
|
bool bInteger;
|
|
};
|
|
|
|
static const SConfigKey g_ConfigKeys[] =
|
|
{
|
|
{ "LISTEN_PORT", LISTEN_PORT, 1.0f, 65000.0f, true },
|
|
{ "DSCP_MARKING_ENABLE", DSCP_MARKING_ENABLE, 0.0f, 1.0f, true },
|
|
{ "DSCP_VALUE", DSCP_VALUE, 0.0f, 63.0f, true },
|
|
{ "DSTAR_TO_PCM", DSTAR_TO_PCM, -40.0f, 40.0f, false },
|
|
{ "DMR_TO_PCM", DMR_TO_PCM, -40.0f, 40.0f, false },
|
|
{ "M17_TO_PCM", M17_TO_PCM, -40.0f, 40.0f, false },
|
|
{ "P25_TO_PCM", P25_TO_PCM, -40.0f, 40.0f, false },
|
|
{ "PCM_TO_DSTAR", PCM_TO_DSTAR, -40.0f, 40.0f, false },
|
|
{ "PCM_TO_DMR", PCM_TO_DMR, -40.0f, 40.0f, false },
|
|
{ "PCM_TO_M17", PCM_TO_M17, -40.0f, 40.0f, false },
|
|
{ "PCM_TO_P25", PCM_TO_P25, -40.0f, 40.0f, false },
|
|
{ "USE_AGC", USE_AGC, 0.0f, 1.0f, true },
|
|
{ "AGC_TARGET", AGC_TARGET, -60.0f, -3.0f, false },
|
|
{ "AGC_CLAMPING", AGC_CLAMPING, 0.0f, 20.0f, false },
|
|
{ "AGC_ATTACK", AGC_ATTACK, 10.0f, 2000.0f, false },
|
|
{ "AGC_HANG", AGC_HANG, 0.0f, 10000.0f, false },
|
|
{ "AGC_RECOVERY", AGC_RECOVERY, 0.1f, 10.0f, false },
|
|
{ "AGC_RECOVERY_MIN", AGC_RECOVERY_MIN, 0.5f, 50.0f, false },
|
|
{ "AGC_RECOVERY_MAX", AGC_RECOVERY_MAX, 0.5f, 100.0f, false },
|
|
{ "AGC_GATE", AGC_GATE, -80.0f, -20.0f, false },
|
|
{ "USE_BANDPASSFILTER", USE_BANDPASSFILTER, 0.0f, 1.0f, true },
|
|
{ "STREAM_ACTIVITY_TIMEOUT", STREAM_ACTIVITY_TIMEOUT, 1.0f, 60.0f, true },
|
|
{ "TUNING_LOG", TUNING_LOG, 0.0f, 1.0f, true },
|
|
};
|
|
|
|
static const SConfigKey *FindKey(const std::string &name)
|
|
{
|
|
for ( const SConfigKey &key : g_ConfigKeys )
|
|
{
|
|
if ( name == key.name )
|
|
{
|
|
return &key;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static std::string Trim(const std::string &str)
|
|
{
|
|
size_t first = str.find_first_not_of(" \t\r");
|
|
if ( first == std::string::npos )
|
|
{
|
|
return "";
|
|
}
|
|
return str.substr(first, str.find_last_not_of(" \t\r") - first + 1);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// constructor
|
|
|
|
CConfig::CConfig()
|
|
{
|
|
for ( const SConfigKey &key : g_ConfigKeys )
|
|
{
|
|
m_Values[key.name] = key.fDefault;
|
|
}
|
|
m_IpValue = LISTEN_IP;
|
|
m_ListenIp = LISTEN_IP;
|
|
m_ListenPort = LISTEN_PORT;
|
|
m_VoiceTos = -1;
|
|
m_LastModified = 0;
|
|
m_bFileMissing = false;
|
|
Apply();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// load
|
|
|
|
void CConfig::Load(const char *filename)
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_Mutex);
|
|
m_Filename = filename;
|
|
|
|
struct stat st;
|
|
if ( ::stat(m_Filename.c_str(), &st) == 0 )
|
|
{
|
|
m_LastModified = st.st_mtime;
|
|
ReadFile();
|
|
}
|
|
else
|
|
{
|
|
std::cout << "Config: cannot read " << m_Filename << ", using main.h defaults" << std::endl;
|
|
m_bFileMissing = true;
|
|
}
|
|
m_ListenIp = m_IpValue;
|
|
m_ListenPort = (uint16)m_Values.at("LISTEN_PORT");
|
|
Apply();
|
|
Report();
|
|
}
|
|
|
|
void CConfig::Reload(void)
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_Mutex);
|
|
if ( m_Filename.empty() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
struct stat st;
|
|
if ( ::stat(m_Filename.c_str(), &st) != 0 )
|
|
{
|
|
if ( !m_bFileMissing )
|
|
{
|
|
std::cout << "Config: cannot read " << m_Filename << ", keeping current values" << std::endl;
|
|
m_bFileMissing = true;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ( m_bFileMissing || st.st_mtime != m_LastModified )
|
|
{
|
|
m_bFileMissing = false;
|
|
m_LastModified = st.st_mtime;
|
|
std::cout << "Config: reloading " << m_Filename << ", changes apply from the next stream" << std::endl;
|
|
ReadFile();
|
|
Apply();
|
|
Report();
|
|
if ( m_IpValue != m_ListenIp || (uint16)m_Values.at("LISTEN_PORT") != m_ListenPort )
|
|
{
|
|
std::cout << "Config: LISTEN_IP and LISTEN_PORT changes need a restart" << std::endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
// DSCP can follow the file, it is read for every voice packet
|
|
void CConfig::Apply(void)
|
|
{
|
|
bool enabled = (m_Values.at("DSCP_MARKING_ENABLE") != 0.0f);
|
|
m_VoiceTos = enabled ? ((int)m_Values.at("DSCP_VALUE") << 2) : -1;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// parsing
|
|
|
|
void CConfig::ReadFile(void)
|
|
{
|
|
std::ifstream file(m_Filename);
|
|
if ( !file.is_open() )
|
|
{
|
|
std::cout << "Config: cannot open " << m_Filename << ", keeping current values" << std::endl;
|
|
return;
|
|
}
|
|
|
|
// a key left out of the file goes back to its default
|
|
std::map<std::string, float> values;
|
|
for ( const SConfigKey &key : g_ConfigKeys )
|
|
{
|
|
values[key.name] = key.fDefault;
|
|
}
|
|
|
|
std::string ip = LISTEN_IP;
|
|
|
|
std::string line;
|
|
int lineNumber = 0;
|
|
while ( std::getline(file, line) )
|
|
{
|
|
lineNumber++;
|
|
line = Trim(line.substr(0, line.find_first_of("#;")));
|
|
if ( line.empty() || line[0] == '[' )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
size_t equals = line.find('=');
|
|
|
|
// the one setting that isn't a number
|
|
if ( equals != std::string::npos && Trim(line.substr(0, equals)) == "LISTEN_IP" )
|
|
{
|
|
std::string text = Trim(line.substr(equals + 1));
|
|
if ( IsValidIp(text) )
|
|
{
|
|
ip = text;
|
|
}
|
|
else
|
|
{
|
|
std::cout << "Config: line " << lineNumber << " ignored, bad value for LISTEN_IP: " << text << std::endl;
|
|
ip = m_IpValue;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
const SConfigKey *key = (equals == std::string::npos) ? NULL : FindKey(Trim(line.substr(0, equals)));
|
|
if ( key == NULL )
|
|
{
|
|
std::cout << "Config: line " << lineNumber << " ignored, unknown setting: " << line << std::endl;
|
|
continue;
|
|
}
|
|
|
|
// a bad value keeps the value already in use, not the default
|
|
values[key->name] = m_Values[key->name];
|
|
|
|
std::string text = Trim(line.substr(equals + 1));
|
|
char *end = NULL;
|
|
float value = ::strtof(text.c_str(), &end);
|
|
if ( text.empty() || *end != '\0' || !std::isfinite(value) ||
|
|
value < key->fMin || value > key->fMax || (key->bInteger && value != floorf(value)) )
|
|
{
|
|
std::cout << "Config: line " << lineNumber << " ignored, bad value for " << key->name
|
|
<< " (" << key->fMin << " to " << key->fMax << "): " << text << std::endl;
|
|
continue;
|
|
}
|
|
values[key->name] = value;
|
|
}
|
|
|
|
m_Values = values;
|
|
m_IpValue = ip;
|
|
}
|
|
|
|
bool CConfig::IsValidIp(const std::string &ip) const
|
|
{
|
|
struct in_addr addr;
|
|
return ::inet_pton(AF_INET, ip.c_str(), &addr) == 1;
|
|
}
|
|
|
|
void CConfig::Report(void) const
|
|
{
|
|
std::cout << "Config: LISTEN_IP=" << m_IpValue;
|
|
for ( const SConfigKey &key : g_ConfigKeys )
|
|
{
|
|
std::cout << " " << key.name << "=" << m_Values.at(key.name);
|
|
}
|
|
std::cout << std::endl;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
// get
|
|
|
|
CTuning CConfig::GetTuning(void) const
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_Mutex);
|
|
CTuning tuning;
|
|
|
|
tuning.fToPcm[CODEC_NONE] = 0.0f;
|
|
tuning.fToPcm[CODEC_AMBEPLUS] = m_Values.at("DSTAR_TO_PCM");
|
|
tuning.fToPcm[CODEC_AMBE2PLUS] = m_Values.at("DMR_TO_PCM");
|
|
tuning.fToPcm[CODEC_CODEC2] = m_Values.at("M17_TO_PCM");
|
|
tuning.fToPcm[CODEC_IMBE] = m_Values.at("P25_TO_PCM");
|
|
|
|
tuning.fFromPcm[CODEC_NONE] = 0.0f;
|
|
tuning.fFromPcm[CODEC_AMBEPLUS] = m_Values.at("PCM_TO_DSTAR");
|
|
tuning.fFromPcm[CODEC_AMBE2PLUS] = m_Values.at("PCM_TO_DMR");
|
|
tuning.fFromPcm[CODEC_CODEC2] = m_Values.at("PCM_TO_M17");
|
|
tuning.fFromPcm[CODEC_IMBE] = m_Values.at("PCM_TO_P25");
|
|
|
|
tuning.bUseAgc = (m_Values.at("USE_AGC") != 0.0f);
|
|
tuning.fAgcTarget = m_Values.at("AGC_TARGET");
|
|
tuning.fAgcClamping = m_Values.at("AGC_CLAMPING");
|
|
tuning.fAgcAttack = m_Values.at("AGC_ATTACK");
|
|
tuning.fAgcHang = m_Values.at("AGC_HANG");
|
|
tuning.fAgcRecovery = m_Values.at("AGC_RECOVERY");
|
|
tuning.fAgcRecoveryMin = m_Values.at("AGC_RECOVERY_MIN");
|
|
tuning.fAgcRecoveryMax = m_Values.at("AGC_RECOVERY_MAX");
|
|
tuning.fAgcGate = m_Values.at("AGC_GATE");
|
|
tuning.bUseFilter = (m_Values.at("USE_BANDPASSFILTER") != 0.0f);
|
|
tuning.iStreamTimeout = (int)m_Values.at("STREAM_ACTIVITY_TIMEOUT");
|
|
tuning.bTuningLog = (m_Values.at("TUNING_LOG") != 0.0f);
|
|
|
|
return tuning;
|
|
}
|
|
|
|
std::string CConfig::GetListenIp(void) const
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_Mutex);
|
|
return m_ListenIp;
|
|
}
|
|
|
|
const char *CConfig::GetModeName(uint8 uiCodec)
|
|
{
|
|
switch ( uiCodec )
|
|
{
|
|
case CODEC_AMBEPLUS: return "DSTAR";
|
|
case CODEC_AMBE2PLUS: return "DMR";
|
|
case CODEC_CODEC2: return "M17";
|
|
case CODEC_IMBE: return "P25";
|
|
default: return "NONE";
|
|
}
|
|
}
|