parent
c035a7ba60
commit
2e65458008
@ -0,0 +1,787 @@
|
||||
/**
|
||||
* Digital Voice Modem - Host Software
|
||||
* GPLv2 Open Source. Use is subject to license terms.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* @package DVM / Host Software
|
||||
*
|
||||
*/
|
||||
//
|
||||
// Based on code from the MMDVMHost project. (https://github.com/g4klx/MMDVMHost)
|
||||
// Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0)
|
||||
//
|
||||
/*
|
||||
* Copyright (C) 2017-2023 by Bryan Biedenkapp N2PLL
|
||||
*
|
||||
* 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 "Defines.h"
|
||||
#include "modem/port/ModemNullPort.h"
|
||||
#include "modem/port/UARTPort.h"
|
||||
#include "modem/port/PseudoPTYPort.h"
|
||||
#include "modem/port/UDPPort.h"
|
||||
#include "network/UDPSocket.h"
|
||||
#include "host/Host.h"
|
||||
#include "HostMain.h"
|
||||
|
||||
using namespace network;
|
||||
using namespace modem;
|
||||
using namespace lookups;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Private Class Members
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Reads basic configuration parameters from the YAML configuration file.
|
||||
/// </summary>
|
||||
bool Host::readParams()
|
||||
{
|
||||
yaml::Node modemConf = m_conf["system"]["modem"];
|
||||
|
||||
yaml::Node modemProtocol = modemConf["protocol"];
|
||||
std::string portType = modemProtocol["type"].as<std::string>("null");
|
||||
|
||||
yaml::Node udpProtocol = modemProtocol["udp"];
|
||||
std::string udpMode = udpProtocol["mode"].as<std::string>("master");
|
||||
|
||||
bool udpMasterMode = false;
|
||||
std::transform(portType.begin(), portType.end(), portType.begin(), ::tolower);
|
||||
if ((portType == UART_PORT || portType == PTY_PORT) && g_remoteModemMode) {
|
||||
udpMasterMode = true;
|
||||
}
|
||||
|
||||
yaml::Node protocolConf = m_conf["protocols"];
|
||||
#if defined(ENABLE_DMR)
|
||||
m_dmrEnabled = protocolConf["dmr"]["enable"].as<bool>(false);
|
||||
#else
|
||||
m_dmrEnabled = false; // hardcode to false when no DMR support is compiled in
|
||||
#endif // defined(ENABLE_DMR)
|
||||
#if defined(ENABLE_P25)
|
||||
m_p25Enabled = protocolConf["p25"]["enable"].as<bool>(false);
|
||||
#else
|
||||
m_p25Enabled = false; // hardcode to false when no P25 support is compiled in
|
||||
#endif // defined(ENABLE_P25)
|
||||
#if defined(ENABLE_NXDN)
|
||||
m_nxdnEnabled = protocolConf["nxdn"]["enable"].as<bool>(false);
|
||||
#else
|
||||
m_nxdnEnabled = false; // hardcode to false when no NXDN support is compiled in
|
||||
#endif // defined(ENABLE_NXDN)
|
||||
|
||||
yaml::Node systemConf = m_conf["system"];
|
||||
m_duplex = systemConf["duplex"].as<bool>(true);
|
||||
bool simplexSameFreq = systemConf["simplexSameFrequency"].as<bool>(false);
|
||||
|
||||
m_timeout = systemConf["timeout"].as<uint32_t>(120U);
|
||||
m_rfModeHang = systemConf["rfModeHang"].as<uint32_t>(10U);
|
||||
m_rfTalkgroupHang = systemConf["rfTalkgroupHang"].as<uint32_t>(10U);
|
||||
m_netModeHang = systemConf["netModeHang"].as<uint32_t>(3U);
|
||||
if (!systemConf["modeHang"].isNone()) {
|
||||
m_rfModeHang = m_netModeHang = systemConf["modeHang"].as<uint32_t>();
|
||||
}
|
||||
|
||||
m_activeTickDelay = (uint8_t)systemConf["activeTickDelay"].as<uint32_t>(5U);
|
||||
if (m_activeTickDelay < 1U)
|
||||
m_activeTickDelay = 1U;
|
||||
m_idleTickDelay = (uint8_t)systemConf["idleTickDelay"].as<uint32_t>(5U);
|
||||
if (m_idleTickDelay < 1U)
|
||||
m_idleTickDelay = 1U;
|
||||
|
||||
m_identity = systemConf["identity"].as<std::string>();
|
||||
m_fixedMode = systemConf["fixedMode"].as<bool>(false);
|
||||
|
||||
if (m_identity.length() > 8) {
|
||||
std::string identity = m_identity;
|
||||
m_identity = identity.substr(0, 8);
|
||||
|
||||
::LogWarning(LOG_HOST, "System Identity \"%s\" is too long; truncating to 8 characters, \"%s\".", identity.c_str(), m_identity.c_str());
|
||||
}
|
||||
|
||||
int8_t lto = (int8_t)systemConf["localTimeOffset"].as<int32_t>(0);
|
||||
|
||||
removeLockFile();
|
||||
|
||||
LogInfo("General Parameters");
|
||||
if (!udpMasterMode) {
|
||||
LogInfo(" DMR: %s", m_dmrEnabled ? "enabled" : "disabled");
|
||||
LogInfo(" P25: %s", m_p25Enabled ? "enabled" : "disabled");
|
||||
LogInfo(" NXDN: %s", m_nxdnEnabled ? "enabled" : "disabled");
|
||||
LogInfo(" Duplex: %s", m_duplex ? "yes" : "no");
|
||||
if (!m_duplex) {
|
||||
LogInfo(" Simplex Same Frequency: %s", simplexSameFreq ? "yes" : "no");
|
||||
}
|
||||
LogInfo(" Active Tick Delay: %ums", m_activeTickDelay);
|
||||
LogInfo(" Idle Tick Delay: %ums", m_idleTickDelay);
|
||||
LogInfo(" Timeout: %us", m_timeout);
|
||||
LogInfo(" RF Mode Hang: %us", m_rfModeHang);
|
||||
LogInfo(" RF Talkgroup Hang: %us", m_rfTalkgroupHang);
|
||||
LogInfo(" Net Mode Hang: %us", m_netModeHang);
|
||||
LogInfo(" Identity: %s", m_identity.c_str());
|
||||
LogInfo(" Fixed Mode: %s", m_fixedMode ? "yes" : "no");
|
||||
LogInfo(" Lock Filename: %s", g_lockFile.c_str());
|
||||
LogInfo(" Local Time Offset: %dh", lto);
|
||||
|
||||
yaml::Node systemInfo = systemConf["info"];
|
||||
m_latitude = systemInfo["latitude"].as<float>(0.0F);
|
||||
m_longitude = systemInfo["longitude"].as<float>(0.0F);
|
||||
m_height = systemInfo["height"].as<int>(0);
|
||||
m_power = systemInfo["power"].as<uint32_t>(0U);
|
||||
m_location = systemInfo["location"].as<std::string>();
|
||||
|
||||
LogInfo("System Info Parameters");
|
||||
LogInfo(" Latitude: %fdeg N", m_latitude);
|
||||
LogInfo(" Longitude: %fdeg E", m_longitude);
|
||||
LogInfo(" Height: %um", m_height);
|
||||
LogInfo(" Power: %uW", m_power);
|
||||
LogInfo(" Location: \"%s\"", m_location.c_str());
|
||||
|
||||
// try to load bandplan identity table
|
||||
std::string idenLookupFile = systemConf["iden_table"]["file"].as<std::string>();
|
||||
uint32_t idenReloadTime = systemConf["iden_table"]["time"].as<uint32_t>(0U);
|
||||
|
||||
if (idenLookupFile.length() <= 0U) {
|
||||
::LogError(LOG_HOST, "No bandplan identity table? This must be defined!");
|
||||
return false;
|
||||
}
|
||||
|
||||
LogInfo("Iden Table Lookups");
|
||||
LogInfo(" File: %s", idenLookupFile.length() > 0U ? idenLookupFile.c_str() : "None");
|
||||
if (idenReloadTime > 0U)
|
||||
LogInfo(" Reload: %u mins", idenReloadTime);
|
||||
|
||||
m_idenTable = new IdenTableLookup(idenLookupFile, idenReloadTime);
|
||||
m_idenTable->read();
|
||||
|
||||
/*
|
||||
** Channel Configuration
|
||||
*/
|
||||
yaml::Node rfssConfig = systemConf["config"];
|
||||
m_channelId = (uint8_t)rfssConfig["channelId"].as<uint32_t>(0U);
|
||||
if (m_channelId > 15U) { // clamp to 15
|
||||
m_channelId = 15U;
|
||||
}
|
||||
|
||||
IdenTable entry = m_idenTable->find(m_channelId);
|
||||
if (entry.baseFrequency() == 0U) {
|
||||
::LogError(LOG_HOST, "Channel Id %u has an invalid base frequency.", m_channelId);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_channelNo = (uint32_t)::strtoul(rfssConfig["channelNo"].as<std::string>("1").c_str(), NULL, 16);
|
||||
if (m_channelNo == 0U) { // clamp to 1
|
||||
m_channelNo = 1U;
|
||||
}
|
||||
if (m_channelNo > 4095U) { // clamp to 4095
|
||||
m_channelNo = 4095U;
|
||||
}
|
||||
|
||||
if (entry.txOffsetMhz() == 0U) {
|
||||
::LogError(LOG_HOST, "Channel Id %u has an invalid Tx offset.", m_channelId);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t calcSpace = (uint32_t)(entry.chSpaceKhz() / 0.125);
|
||||
float calcTxOffset = entry.txOffsetMhz() * 1000000;
|
||||
|
||||
m_txFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * m_channelNo)));
|
||||
m_rxFrequency = (uint32_t)(m_txFrequency + calcTxOffset);
|
||||
|
||||
if (calcTxOffset < 0.0f && m_rxFrequency < entry.baseFrequency()) {
|
||||
::LogWarning(LOG_HOST, "Channel Id %u Channel No $%04X has an invalid frequency. Rx Frequency (%u) is less then the base frequency (%u), this may result in incorrect trunking behavior.", m_channelId, m_channelNo,
|
||||
m_rxFrequency, entry.baseFrequency());
|
||||
}
|
||||
|
||||
if (!m_duplex && simplexSameFreq) {
|
||||
m_rxFrequency = m_txFrequency;
|
||||
}
|
||||
|
||||
/*
|
||||
** Control Channel
|
||||
*/
|
||||
{
|
||||
yaml::Node controlCh = rfssConfig["controlCh"];
|
||||
|
||||
std::string restApiAddress = controlCh["restAddress"].as<std::string>("");
|
||||
uint16_t restApiPort = (uint16_t)controlCh["restPort"].as<uint32_t>(REST_API_DEFAULT_PORT);
|
||||
std::string restApiPassword = controlCh["restPassword"].as<std::string>();
|
||||
|
||||
VoiceChData data = VoiceChData(m_channelId, m_channelNo, restApiAddress, restApiPort, restApiPassword);
|
||||
m_controlChData = data;
|
||||
|
||||
if (!m_controlChData.address().empty() && m_controlChData.port() > 0) {
|
||||
::LogInfoEx(LOG_HOST, "Control Channel REST API Address %s:%u", m_controlChData.address().c_str(), m_controlChData.port());
|
||||
} else {
|
||||
::LogInfoEx(LOG_HOST, "No Control Channel REST API Configured, CC notify disabled");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Voice Channels
|
||||
*/
|
||||
yaml::Node& voiceChList = rfssConfig["voiceChNo"];
|
||||
|
||||
if (voiceChList.size() == 0U) {
|
||||
::LogError(LOG_HOST, "No voice channel list defined!");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < voiceChList.size(); i++) {
|
||||
yaml::Node& channel = voiceChList[i];
|
||||
|
||||
uint8_t chId = (uint8_t)channel["channelId"].as<uint32_t>(255U);
|
||||
|
||||
// special case default handling for if the channelId field is missing from the
|
||||
// configuration
|
||||
if (chId == 255U) {
|
||||
chId = m_channelId;
|
||||
}
|
||||
|
||||
if (chId > 15U) { // clamp to 15
|
||||
chId = 15U;
|
||||
}
|
||||
|
||||
uint32_t chNo = (uint32_t)::strtoul(channel["channelNo"].as<std::string>("1").c_str(), NULL, 16);
|
||||
if (chNo == 0U) { // clamp to 1
|
||||
chNo = 1U;
|
||||
}
|
||||
if (chNo > 4095U) { // clamp to 4095
|
||||
chNo = 4095U;
|
||||
}
|
||||
|
||||
std::string restApiAddress = channel["restAddress"].as<std::string>("127.0.0.1");
|
||||
uint16_t restApiPort = (uint16_t)channel["restPort"].as<uint32_t>(REST_API_DEFAULT_PORT);
|
||||
std::string restApiPassword = channel["restPassword"].as<std::string>();
|
||||
|
||||
::LogInfoEx(LOG_HOST, "Voice Channel Id %u Channel No $%04X REST API Address %s:%u", chId, chNo, restApiAddress.c_str(), restApiPort);
|
||||
|
||||
VoiceChData data = VoiceChData(chId, chNo, restApiAddress, restApiPort, restApiPassword);
|
||||
m_voiceChData[chNo] = data;
|
||||
m_voiceChNo.push_back(chNo);
|
||||
}
|
||||
|
||||
std::string strVoiceChNo = "";
|
||||
for (auto it = m_voiceChNo.begin(); it != m_voiceChNo.end(); ++it) {
|
||||
uint32_t chNo = ::atoi(std::to_string(*it).c_str());
|
||||
::lookups::VoiceChData voiceChData = m_voiceChData[chNo];
|
||||
|
||||
char hexStr[29];
|
||||
|
||||
::sprintf(hexStr, "$%01X.%01X (%u.%u)", voiceChData.chId(), chNo, voiceChData.chId(), chNo);
|
||||
|
||||
strVoiceChNo.append(std::string(hexStr));
|
||||
strVoiceChNo.append(",");
|
||||
}
|
||||
strVoiceChNo.erase(strVoiceChNo.find_last_of(","));
|
||||
|
||||
/*
|
||||
** Site Parameters
|
||||
*/
|
||||
m_siteId = (uint8_t)::strtoul(rfssConfig["siteId"].as<std::string>("1").c_str(), NULL, 16);
|
||||
m_siteId = p25::P25Utils::siteId(m_siteId);
|
||||
|
||||
m_dmrColorCode = rfssConfig["colorCode"].as<uint32_t>(2U);
|
||||
m_dmrColorCode = dmr::DMRUtils::colorCode(m_dmrColorCode);
|
||||
|
||||
m_dmrNetId = (uint32_t)::strtoul(rfssConfig["dmrNetId"].as<std::string>("1").c_str(), NULL, 16);
|
||||
m_dmrNetId = dmr::DMRUtils::netId(m_dmrNetId, dmr::SITE_MODEL_TINY);
|
||||
|
||||
m_p25NAC = (uint32_t)::strtoul(rfssConfig["nac"].as<std::string>("293").c_str(), NULL, 16);
|
||||
m_p25NAC = p25::P25Utils::nac(m_p25NAC);
|
||||
|
||||
uint32_t p25TxNAC = (uint32_t)::strtoul(rfssConfig["txNAC"].as<std::string>("F7E").c_str(), NULL, 16);
|
||||
if (p25TxNAC == m_p25NAC) {
|
||||
LogWarning(LOG_HOST, "Only use txNAC when split NAC operations are needed. nac and txNAC should not be the same!");
|
||||
}
|
||||
|
||||
m_p25NetId = (uint32_t)::strtoul(rfssConfig["netId"].as<std::string>("BB800").c_str(), NULL, 16);
|
||||
m_p25NetId = p25::P25Utils::netId(m_p25NetId);
|
||||
if (m_p25NetId == 0xBEE00) {
|
||||
::fatal("error 4\n");
|
||||
}
|
||||
|
||||
m_sysId = (uint32_t)::strtoul(rfssConfig["sysId"].as<std::string>("001").c_str(), NULL, 16);
|
||||
m_sysId = p25::P25Utils::sysId(m_sysId);
|
||||
|
||||
m_p25RfssId = (uint8_t)::strtoul(rfssConfig["rfssId"].as<std::string>("1").c_str(), NULL, 16);
|
||||
m_p25RfssId = p25::P25Utils::rfssId(m_p25RfssId);
|
||||
|
||||
m_nxdnRAN = rfssConfig["ran"].as<uint32_t>(1U);
|
||||
|
||||
m_authoritative = rfssConfig["authoritative"].as<bool>(true);
|
||||
|
||||
LogInfo("System Config Parameters");
|
||||
LogInfo(" Authoritative: %s", m_authoritative ? "yes" : "no");
|
||||
if (m_authoritative) {
|
||||
m_supervisor = rfssConfig["supervisor"].as<bool>(false);
|
||||
LogInfo(" Supervisor: %s", m_supervisor ? "yes" : "no");
|
||||
}
|
||||
LogInfo(" RX Frequency: %uHz", m_rxFrequency);
|
||||
LogInfo(" TX Frequency: %uHz", m_txFrequency);
|
||||
LogInfo(" Base Frequency: %uHz", entry.baseFrequency());
|
||||
LogInfo(" TX Offset: %fMHz", entry.txOffsetMhz());
|
||||
LogInfo(" Bandwidth: %fKHz", entry.chBandwidthKhz());
|
||||
LogInfo(" Channel Spacing: %fKHz", entry.chSpaceKhz());
|
||||
LogInfo(" Channel Id: %u", m_channelId);
|
||||
LogInfo(" Channel No.: $%04X (%u)", m_channelNo, m_channelNo);
|
||||
LogInfo(" Voice Channel No(s).: %s", strVoiceChNo.c_str());
|
||||
LogInfo(" Site Id: $%02X", m_siteId);
|
||||
LogInfo(" System Id: $%03X", m_sysId);
|
||||
LogInfo(" DMR Color Code: %u", m_dmrColorCode);
|
||||
LogInfo(" DMR Network Id: $%05X", m_dmrNetId);
|
||||
LogInfo(" P25 NAC: $%03X", m_p25NAC);
|
||||
|
||||
if (p25TxNAC != 0xF7EU && p25TxNAC != m_p25NAC) {
|
||||
LogInfo(" P25 Tx NAC: $%03X", p25TxNAC);
|
||||
}
|
||||
|
||||
LogInfo(" P25 Network Id: $%05X", m_p25NetId);
|
||||
LogInfo(" P25 RFSS Id: $%02X", m_p25RfssId);
|
||||
LogInfo(" NXDN RAN: %u", m_nxdnRAN);
|
||||
|
||||
if (!m_authoritative) {
|
||||
m_supervisor = false;
|
||||
LogWarning(LOG_HOST, "Host is non-authoritative! This requires REST API to handle permit TG for VCs and grant TG for CCs!");
|
||||
}
|
||||
}
|
||||
else {
|
||||
LogInfo(" Modem Remote Control: yes");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the modem DSP.
|
||||
/// </summary>
|
||||
bool Host::createModem()
|
||||
{
|
||||
yaml::Node protocolConf = m_conf["protocols"];
|
||||
|
||||
yaml::Node dmrProtocol = protocolConf["dmr"];
|
||||
uint32_t dmrQueueSize = dmrProtocol["queueSize"].as<uint32_t>(24U);
|
||||
|
||||
// clamp queue size to no less then 24 and no greater the 100
|
||||
if (dmrQueueSize < 24U) {
|
||||
LogWarning(LOG_HOST, "DMR queue size must be greater then 24 frames, defaulting to 24 frames!");
|
||||
dmrQueueSize = 24U;
|
||||
}
|
||||
if (dmrQueueSize > 100U) {
|
||||
LogWarning(LOG_HOST, "DMR queue size must be less then 100 frames, defaulting to 100 frames!");
|
||||
dmrQueueSize = 100U;
|
||||
}
|
||||
if (dmrQueueSize > 60U) {
|
||||
LogWarning(LOG_HOST, "DMR queue size is excessive, >60 frames!");
|
||||
}
|
||||
|
||||
m_dmrQueueSizeBytes = dmrQueueSize * (dmr::DMR_FRAME_LENGTH_BYTES * 5U);
|
||||
|
||||
yaml::Node p25Protocol = protocolConf["p25"];
|
||||
uint32_t p25QueueSize = p25Protocol["queueSize"].as<uint16_t>(12U);
|
||||
|
||||
// clamp queue size to no less then 12 and no greater the 100 frames
|
||||
if (p25QueueSize < 12U) {
|
||||
LogWarning(LOG_HOST, "P25 queue size must be greater then 12 frames, defaulting to 12 frames!");
|
||||
p25QueueSize = 12U;
|
||||
}
|
||||
if (p25QueueSize > 50U) {
|
||||
LogWarning(LOG_HOST, "P25 queue size must be less then 50 frames, defaulting to 50 frames!");
|
||||
p25QueueSize = 50U;
|
||||
}
|
||||
if (p25QueueSize > 30U) {
|
||||
LogWarning(LOG_HOST, "P25 queue size is excessive, >30 frames!");
|
||||
}
|
||||
|
||||
m_p25QueueSizeBytes = p25QueueSize * p25::P25_LDU_FRAME_LENGTH_BYTES;
|
||||
|
||||
yaml::Node nxdnProtocol = protocolConf["nxdn"];
|
||||
uint32_t nxdnQueueSize = nxdnProtocol["queueSize"].as<uint32_t>(31U);
|
||||
|
||||
// clamp queue size to no less then 31 and no greater the 50 frames
|
||||
if (nxdnQueueSize < 31U) {
|
||||
LogWarning(LOG_HOST, "NXDN queue size must be greater then 31 frames, defaulting to 31 frames!");
|
||||
nxdnQueueSize = 31U;
|
||||
}
|
||||
if (nxdnQueueSize > 50U) {
|
||||
LogWarning(LOG_HOST, "NXDN queue size must be less then 50 frames, defaulting to 50 frames!");
|
||||
nxdnQueueSize = 50U;
|
||||
}
|
||||
|
||||
m_nxdnQueueSizeBytes = nxdnQueueSize * nxdn::NXDN_FRAME_LENGTH_BYTES;
|
||||
|
||||
yaml::Node modemConf = m_conf["system"]["modem"];
|
||||
|
||||
yaml::Node modemProtocol = modemConf["protocol"];
|
||||
std::string portType = modemProtocol["type"].as<std::string>("null");
|
||||
yaml::Node uartProtocol = modemProtocol["uart"];
|
||||
std::string uartPort = uartProtocol["port"].as<std::string>();
|
||||
uint32_t uartSpeed = uartProtocol["speed"].as<uint32_t>(115200);
|
||||
|
||||
bool rxInvert = modemConf["rxInvert"].as<bool>(false);
|
||||
bool txInvert = modemConf["txInvert"].as<bool>(false);
|
||||
bool pttInvert = modemConf["pttInvert"].as<bool>(false);
|
||||
bool dcBlocker = modemConf["dcBlocker"].as<bool>(true);
|
||||
bool cosLockout = modemConf["cosLockout"].as<bool>(false);
|
||||
uint8_t fdmaPreamble = (uint8_t)modemConf["fdmaPreamble"].as<uint32_t>(80U);
|
||||
uint8_t dmrRxDelay = (uint8_t)modemConf["dmrRxDelay"].as<uint32_t>(7U);
|
||||
uint8_t p25CorrCount = (uint8_t)modemConf["p25CorrCount"].as<uint32_t>(4U);
|
||||
int rxDCOffset = modemConf["rxDCOffset"].as<int>(0);
|
||||
int txDCOffset = modemConf["txDCOffset"].as<int>(0);
|
||||
|
||||
yaml::Node hotspotParams = modemConf["hotspot"];
|
||||
|
||||
int dmrDiscBWAdj = hotspotParams["dmrDiscBWAdj"].as<int>(0);
|
||||
int p25DiscBWAdj = hotspotParams["p25DiscBWAdj"].as<int>(0);
|
||||
int nxdnDiscBWAdj = hotspotParams["nxdnDiscBWAdj"].as<int>(0);
|
||||
int dmrPostBWAdj = hotspotParams["dmrPostBWAdj"].as<int>(0);
|
||||
int p25PostBWAdj = hotspotParams["p25PostBWAdj"].as<int>(0);
|
||||
int nxdnPostBWAdj = hotspotParams["nxdnPostBWAdj"].as<int>(0);
|
||||
ADF_GAIN_MODE adfGainMode = (ADF_GAIN_MODE)hotspotParams["adfGainMode"].as<uint32_t>(0U);
|
||||
bool afcEnable = hotspotParams["afcEnable"].as<bool>(false);
|
||||
uint8_t afcKI = (uint8_t)hotspotParams["afcKI"].as<uint32_t>(11U);
|
||||
uint8_t afcKP = (uint8_t)hotspotParams["afcKP"].as<uint32_t>(4U);
|
||||
uint8_t afcRange = (uint8_t)hotspotParams["afcRange"].as<uint32_t>(1U);
|
||||
int rxTuning = hotspotParams["rxTuning"].as<int>(0);
|
||||
int txTuning = hotspotParams["txTuning"].as<int>(0);
|
||||
uint8_t rfPower = (uint8_t)hotspotParams["rfPower"].as<uint32_t>(100U);
|
||||
|
||||
yaml::Node repeaterParams = modemConf["repeater"];
|
||||
|
||||
int dmrSymLevel3Adj = repeaterParams["dmrSymLvl3Adj"].as<int>(0);
|
||||
int dmrSymLevel1Adj = repeaterParams["dmrSymLvl1Adj"].as<int>(0);
|
||||
int p25SymLevel3Adj = repeaterParams["p25SymLvl3Adj"].as<int>(0);
|
||||
int p25SymLevel1Adj = repeaterParams["p25SymLvl1Adj"].as<int>(0);
|
||||
int nxdnSymLevel3Adj = repeaterParams["nxdnSymLvl3Adj"].as<int>(0);
|
||||
int nxdnSymLevel1Adj = repeaterParams["nxdnSymLvl1Adj"].as<int>(0);
|
||||
|
||||
yaml::Node softpotParams = modemConf["softpot"];
|
||||
|
||||
uint8_t rxCoarse = (uint8_t)softpotParams["rxCoarse"].as<uint32_t>(127U);
|
||||
uint8_t rxFine = (uint8_t)softpotParams["rxFine"].as<uint32_t>(127U);
|
||||
uint8_t txCoarse = (uint8_t)softpotParams["txCoarse"].as<uint32_t>(127U);
|
||||
uint8_t txFine = (uint8_t)softpotParams["txFine"].as<uint32_t>(127U);
|
||||
uint8_t rssiCoarse = (uint8_t)softpotParams["rssiCoarse"].as<uint32_t>(127U);
|
||||
uint8_t rssiFine = (uint8_t)softpotParams["rssiFine"].as<uint32_t>(127U);
|
||||
|
||||
uint16_t dmrFifoLength = (uint16_t)modemConf["dmrFifoLength"].as<uint32_t>(DMR_TX_BUFFER_LEN);
|
||||
uint16_t p25FifoLength = (uint16_t)modemConf["p25FifoLength"].as<uint32_t>(P25_TX_BUFFER_LEN);
|
||||
uint16_t nxdnFifoLength = (uint16_t)modemConf["nxdnFifoLength"].as<uint32_t>(NXDN_TX_BUFFER_LEN);
|
||||
|
||||
float rxLevel = modemConf["rxLevel"].as<float>(50.0F);
|
||||
float cwIdTXLevel = modemConf["cwIdTxLevel"].as<float>(50.0F);
|
||||
float dmrTXLevel = modemConf["dmrTxLevel"].as<float>(50.0F);
|
||||
float p25TXLevel = modemConf["p25TxLevel"].as<float>(50.0F);
|
||||
float nxdnTXLevel = modemConf["nxdnTxLevel"].as<float>(50.0F);
|
||||
if (!modemConf["txLevel"].isNone()) {
|
||||
cwIdTXLevel = dmrTXLevel = p25TXLevel = nxdnTXLevel = modemConf["txLevel"].as<float>(50.0F);
|
||||
}
|
||||
bool disableOFlowReset = modemConf["disableOFlowReset"].as<bool>(false);
|
||||
bool ignoreModemConfigArea = modemConf["ignoreModemConfigArea"].as<bool>(false);
|
||||
bool dumpModemStatus = modemConf["dumpModemStatus"].as<bool>(false);
|
||||
bool trace = modemConf["trace"].as<bool>(false);
|
||||
bool debug = modemConf["debug"].as<bool>(false);
|
||||
|
||||
if (rfPower == 0U) { // clamp to 1
|
||||
rfPower = 1U;
|
||||
}
|
||||
if (rfPower > 100U) { // clamp to 100
|
||||
rfPower = 100U;
|
||||
}
|
||||
|
||||
LogInfo("Modem Parameters");
|
||||
LogInfo(" Port Type: %s", portType.c_str());
|
||||
|
||||
port::IModemPort* modemPort = nullptr;
|
||||
std::transform(portType.begin(), portType.end(), portType.begin(), ::tolower);
|
||||
if (portType == NULL_PORT) {
|
||||
modemPort = new port::ModemNullPort();
|
||||
}
|
||||
else if (portType == UART_PORT || portType == PTY_PORT) {
|
||||
port::SERIAL_SPEED serialSpeed = port::SERIAL_115200;
|
||||
switch (uartSpeed) {
|
||||
case 1200:
|
||||
serialSpeed = port::SERIAL_1200;
|
||||
break;
|
||||
case 2400:
|
||||
serialSpeed = port::SERIAL_2400;
|
||||
break;
|
||||
case 4800:
|
||||
serialSpeed = port::SERIAL_4800;
|
||||
break;
|
||||
case 9600:
|
||||
serialSpeed = port::SERIAL_9600;
|
||||
break;
|
||||
case 19200:
|
||||
serialSpeed = port::SERIAL_19200;
|
||||
break;
|
||||
case 38400:
|
||||
serialSpeed = port::SERIAL_38400;
|
||||
break;
|
||||
case 76800:
|
||||
serialSpeed = port::SERIAL_76800;
|
||||
break;
|
||||
case 230400:
|
||||
serialSpeed = port::SERIAL_230400;
|
||||
break;
|
||||
case 460800:
|
||||
serialSpeed = port::SERIAL_460800;
|
||||
break;
|
||||
default:
|
||||
LogWarning(LOG_HOST, "Unsupported serial speed %u, defaulting to %u", uartSpeed, port::SERIAL_115200);
|
||||
uartSpeed = 115200;
|
||||
case 115200:
|
||||
break;
|
||||
}
|
||||
|
||||
if (portType == PTY_PORT) {
|
||||
modemPort = new port::UARTPort(uartPort, serialSpeed, false);
|
||||
LogInfo(" PTY Port: %s", uartPort.c_str());
|
||||
LogInfo(" PTY Speed: %u", uartSpeed);
|
||||
}
|
||||
else {
|
||||
modemPort = new port::UARTPort(uartPort, serialSpeed, true);
|
||||
LogInfo(" UART Port: %s", uartPort.c_str());
|
||||
LogInfo(" UART Speed: %u", uartSpeed);
|
||||
}
|
||||
}
|
||||
else {
|
||||
LogError(LOG_HOST, "Invalid protocol port type, %s!", portType.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (g_remoteModemMode) {
|
||||
if (portType == UART_PORT || portType == PTY_PORT) {
|
||||
m_modemRemotePort = new port::UDPPort(g_remoteAddress, g_remotePort);
|
||||
m_modemRemote = true;
|
||||
ignoreModemConfigArea = true;
|
||||
|
||||
}
|
||||
else {
|
||||
delete modemPort;
|
||||
modemPort = new port::UDPPort(g_remoteAddress, g_remotePort);
|
||||
m_modemRemote = false;
|
||||
}
|
||||
|
||||
LogInfo(" UDP Mode: %s", m_modemRemote ? "master" : "peer");
|
||||
LogInfo(" UDP Address: %s", g_remoteAddress.c_str());
|
||||
LogInfo(" UDP Port: %u", g_remotePort);
|
||||
}
|
||||
|
||||
if (!m_modemRemote) {
|
||||
LogInfo(" RX Invert: %s", rxInvert ? "yes" : "no");
|
||||
LogInfo(" TX Invert: %s", txInvert ? "yes" : "no");
|
||||
LogInfo(" PTT Invert: %s", pttInvert ? "yes" : "no");
|
||||
LogInfo(" DC Blocker: %s", dcBlocker ? "yes" : "no");
|
||||
LogInfo(" COS Lockout: %s", cosLockout ? "yes" : "no");
|
||||
LogInfo(" FDMA Preambles: %u (%.1fms)", fdmaPreamble, float(fdmaPreamble) * 0.2222F);
|
||||
LogInfo(" DMR RX Delay: %u (%.1fms)", dmrRxDelay, float(dmrRxDelay) * 0.0416666F);
|
||||
LogInfo(" P25 Corr. Count: %u (%.1fms)", p25CorrCount, float(p25CorrCount) * 0.667F);
|
||||
LogInfo(" RX DC Offset: %d", rxDCOffset);
|
||||
LogInfo(" TX DC Offset: %d", txDCOffset);
|
||||
LogInfo(" RX Tuning Offset: %dhz", rxTuning);
|
||||
LogInfo(" TX Tuning Offset: %dhz", txTuning);
|
||||
LogInfo(" RX Effective Frequency: %uhz", m_rxFrequency + rxTuning);
|
||||
LogInfo(" TX Effective Frequency: %uhz", m_txFrequency + txTuning);
|
||||
LogInfo(" RX Coarse: %u, Fine: %u", rxCoarse, rxFine);
|
||||
LogInfo(" TX Coarse: %u, Fine: %u", txCoarse, txFine);
|
||||
LogInfo(" RSSI Coarse: %u, Fine: %u", rssiCoarse, rssiFine);
|
||||
LogInfo(" RF Power Level: %u", rfPower);
|
||||
LogInfo(" RX Level: %.1f%%", rxLevel);
|
||||
LogInfo(" CW Id TX Level: %.1f%%", cwIdTXLevel);
|
||||
LogInfo(" DMR TX Level: %.1f%%", dmrTXLevel);
|
||||
LogInfo(" P25 TX Level: %.1f%%", p25TXLevel);
|
||||
LogInfo(" NXDN TX Level: %.1f%%", nxdnTXLevel);
|
||||
LogInfo(" Disable Overflow Reset: %s", disableOFlowReset ? "yes" : "no");
|
||||
LogInfo(" DMR Queue Size: %u (%u bytes)", dmrQueueSize, m_dmrQueueSizeBytes);
|
||||
LogInfo(" P25 Queue Size: %u (%u bytes)", p25QueueSize, m_p25QueueSizeBytes);
|
||||
LogInfo(" NXDN Queue Size: %u (%u bytes)", nxdnQueueSize, m_nxdnQueueSizeBytes);
|
||||
LogInfo(" DMR FIFO Size: %u bytes", dmrFifoLength);
|
||||
LogInfo(" P25 FIFO Size: %u bytes", p25FifoLength);
|
||||
LogInfo(" NXDN FIFO Size: %u bytes", nxdnFifoLength);
|
||||
|
||||
if (ignoreModemConfigArea) {
|
||||
LogInfo(" Ignore Modem Configuration Area: yes");
|
||||
}
|
||||
|
||||
if (dumpModemStatus) {
|
||||
LogInfo(" Dump Modem Status: yes");
|
||||
}
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
LogInfo(" Debug: yes");
|
||||
}
|
||||
|
||||
m_modem = new Modem(modemPort, m_duplex, rxInvert, txInvert, pttInvert, dcBlocker, cosLockout, fdmaPreamble, dmrRxDelay, p25CorrCount,
|
||||
m_dmrQueueSizeBytes, m_p25QueueSizeBytes, m_nxdnQueueSizeBytes, disableOFlowReset, ignoreModemConfigArea, dumpModemStatus, trace, debug);
|
||||
if (!m_modemRemote) {
|
||||
m_modem->setModeParams(m_dmrEnabled, m_p25Enabled, m_nxdnEnabled);
|
||||
m_modem->setLevels(rxLevel, cwIdTXLevel, dmrTXLevel, p25TXLevel, nxdnTXLevel);
|
||||
m_modem->setSymbolAdjust(dmrSymLevel3Adj, dmrSymLevel1Adj, p25SymLevel3Adj, p25SymLevel1Adj, nxdnSymLevel3Adj, nxdnSymLevel1Adj);
|
||||
m_modem->setDCOffsetParams(txDCOffset, rxDCOffset);
|
||||
m_modem->setRFParams(m_rxFrequency, m_txFrequency, rxTuning, txTuning, rfPower, dmrDiscBWAdj, p25DiscBWAdj, nxdnDiscBWAdj, dmrPostBWAdj,
|
||||
p25PostBWAdj, nxdnPostBWAdj, adfGainMode, afcEnable, afcKI, afcKP, afcRange);
|
||||
m_modem->setSoftPot(rxCoarse, rxFine, txCoarse, txFine, rssiCoarse, rssiFine);
|
||||
m_modem->setDMRColorCode(m_dmrColorCode);
|
||||
m_modem->setP25NAC(m_p25NAC);
|
||||
}
|
||||
|
||||
if (m_modemRemote) {
|
||||
m_modem->setOpenHandler(MODEM_OC_PORT_HANDLER_BIND(Host::rmtPortModemOpen, this));
|
||||
m_modem->setCloseHandler(MODEM_OC_PORT_HANDLER_BIND(Host::rmtPortModemClose, this));
|
||||
m_modem->setResponseHandler(MODEM_RESP_HANDLER_BIND(Host::rmtPortModemHandler, this));
|
||||
}
|
||||
|
||||
bool ret = m_modem->open();
|
||||
if (!ret) {
|
||||
delete m_modem;
|
||||
m_modem = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_modem->setFifoLength(dmrFifoLength, p25FifoLength, nxdnFifoLength);
|
||||
|
||||
// are we on a protocol version older then 3?
|
||||
if (m_modem->getVersion() < 3U) {
|
||||
if (m_nxdnEnabled) {
|
||||
::LogError(LOG_HOST, "NXDN is not supported on legacy firmware.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes network connectivity.
|
||||
/// </summary>
|
||||
bool Host::createNetwork()
|
||||
{
|
||||
yaml::Node networkConf = m_conf["network"];
|
||||
bool netEnable = networkConf["enable"].as<bool>(false);
|
||||
bool restApiEnable = networkConf["restEnable"].as<bool>(false);
|
||||
|
||||
// dump out if both networking and REST API are disabled
|
||||
if (!netEnable && !restApiEnable) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string address = networkConf["address"].as<std::string>();
|
||||
uint16_t port = (uint16_t)networkConf["port"].as<uint32_t>(TRAFFIC_DEFAULT_PORT);
|
||||
uint16_t local = (uint16_t)networkConf["local"].as<uint32_t>(0U);
|
||||
std::string restApiAddress = networkConf["restAddress"].as<std::string>("127.0.0.1");
|
||||
uint16_t restApiPort = (uint16_t)networkConf["restPort"].as<uint32_t>(REST_API_DEFAULT_PORT);
|
||||
std::string restApiPassword = networkConf["restPassword"].as<std::string>();
|
||||
bool restApiDebug = networkConf["restDebug"].as<bool>(false);
|
||||
uint32_t id = networkConf["id"].as<uint32_t>(1000U);
|
||||
uint32_t jitter = networkConf["talkgroupHang"].as<uint32_t>(360U);
|
||||
std::string password = networkConf["password"].as<std::string>();
|
||||
bool slot1 = networkConf["slot1"].as<bool>(true);
|
||||
bool slot2 = networkConf["slot2"].as<bool>(true);
|
||||
bool allowActivityTransfer = networkConf["allowActivityTransfer"].as<bool>(false);
|
||||
bool allowDiagnosticTransfer = networkConf["allowDiagnosticTransfer"].as<bool>(false);
|
||||
bool updateLookup = networkConf["updateLookups"].as<bool>(false);
|
||||
bool debug = networkConf["debug"].as<bool>(false);
|
||||
|
||||
if (id > 999999999U) {
|
||||
::LogError(LOG_HOST, "Network Peer ID cannot be greater then 999999999.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (restApiPassword.length() > 64) {
|
||||
std::string password = restApiPassword;
|
||||
restApiPassword = password.substr(0, 64);
|
||||
|
||||
::LogWarning(LOG_HOST, "REST API password is too long; truncating to the first 64 characters.");
|
||||
}
|
||||
|
||||
if (restApiPassword.empty() && restApiEnable) {
|
||||
::LogWarning(LOG_HOST, "REST API password not provided; REST API disabled.");
|
||||
restApiEnable = false;
|
||||
}
|
||||
|
||||
IdenTable entry = m_idenTable->find(m_channelId);
|
||||
|
||||
LogInfo("Network Parameters");
|
||||
LogInfo(" Enabled: %s", netEnable ? "yes" : "no");
|
||||
if (netEnable) {
|
||||
LogInfo(" Peer ID: %u", id);
|
||||
LogInfo(" Address: %s", address.c_str());
|
||||
LogInfo(" Port: %u", port);
|
||||
if (local > 0U)
|
||||
LogInfo(" Local: %u", local);
|
||||
else
|
||||
LogInfo(" Local: random");
|
||||
LogInfo(" DMR Jitter: %ums", jitter);
|
||||
LogInfo(" Slot 1: %s", slot1 ? "enabled" : "disabled");
|
||||
LogInfo(" Slot 2: %s", slot2 ? "enabled" : "disabled");
|
||||
LogInfo(" Allow Activity Log Transfer: %s", allowActivityTransfer ? "yes" : "no");
|
||||
LogInfo(" Allow Diagnostic Log Transfer: %s", allowDiagnosticTransfer ? "yes" : "no");
|
||||
LogInfo(" Update Lookups: %s", updateLookup ? "yes" : "no");
|
||||
|
||||
if (debug) {
|
||||
LogInfo(" Debug: yes");
|
||||
}
|
||||
}
|
||||
LogInfo(" REST API Enabled: %s", restApiEnable ? "yes" : "no");
|
||||
if (restApiEnable) {
|
||||
LogInfo(" REST API Address: %s", restApiAddress.c_str());
|
||||
LogInfo(" REST API Port: %u", restApiPort);
|
||||
|
||||
if (restApiDebug) {
|
||||
LogInfo(" REST API Debug: yes");
|
||||
}
|
||||
}
|
||||
|
||||
// initialize networking
|
||||
if (netEnable) {
|
||||
m_network = new Network(address, port, local, id, password, m_duplex, debug, m_dmrEnabled, m_p25Enabled, m_nxdnEnabled, slot1, slot2, allowActivityTransfer, allowDiagnosticTransfer, updateLookup);
|
||||
|
||||
m_network->setLookups(m_ridLookup, m_tidLookup);
|
||||
m_network->setMetadata(m_identity, m_rxFrequency, m_txFrequency, entry.txOffsetMhz(), entry.chBandwidthKhz(), m_channelId, m_channelNo,
|
||||
m_power, m_latitude, m_longitude, m_height, m_location);
|
||||
if (restApiEnable) {
|
||||
m_network->setRESTAPIData(restApiPassword, restApiPort);
|
||||
}
|
||||
|
||||
m_network->enable(true);
|
||||
bool ret = m_network->open();
|
||||
if (!ret) {
|
||||
delete m_network;
|
||||
m_network = nullptr;
|
||||
LogError(LOG_HOST, "failed to initialize traffic networking!");
|
||||
return false;
|
||||
}
|
||||
|
||||
::LogSetNetwork(m_network);
|
||||
}
|
||||
|
||||
// initialize network remote command
|
||||
if (restApiEnable) {
|
||||
m_RESTAPI = new RESTAPI(restApiAddress, restApiPort, restApiPassword, this, restApiDebug);
|
||||
m_RESTAPI->setLookups(m_ridLookup, m_tidLookup);
|
||||
bool ret = m_RESTAPI->open();
|
||||
if (!ret) {
|
||||
delete m_RESTAPI;
|
||||
m_RESTAPI = nullptr;
|
||||
LogError(LOG_HOST, "failed to initialize REST API networking! REST API will be unavailable!");
|
||||
// REST API failing isn't fatal -- we'll allow this to return normally
|
||||
}
|
||||
}
|
||||
else {
|
||||
m_RESTAPI = nullptr;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
Loading…
Reference in new issue