/** * 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 MMDVMCal project. (https://github.com/g4klx/MMDVMCal) // Licensed under the GPLv2 License (https://opensource.org/licenses/GPL-2.0) // /* * Copyright (C) 2021 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 "host/setup/HostSetup.h" #include "HostMain.h" #include "Log.h" #include "Utils.h" using namespace lookups; #include #include #if !defined(_WIN32) && !defined(_WIN64) #include #endif // --------------------------------------------------------------------------- // Public Class Members // --------------------------------------------------------------------------- /// /// Initializes a new instance of the HostSetup class. /// /// Full-path to the configuration file. HostSetup::HostSetup(const std::string& confFile) : m_confFile(confFile), m_conf(), m_console(), m_duplex(true), m_rxFrequency(0U), m_txFrequency(0U), m_channelId(0U), m_channelNo(0U), m_idenTable(NULL) { /* stub */ } /// /// Finalizes a instance of the HostSetup class. /// HostSetup::~HostSetup() { /* stub */ } /// /// Executes the processing loop. /// /// Zero if successful, otherwise error occurred. int HostSetup::run() { bool ret = yaml::Parse(m_conf, m_confFile.c_str()); if (!ret) { ::fatal("cannot read the configuration file, %s\n", m_confFile.c_str()); } // initialize system logging ret = ::LogInitialise("", "", 0U, 1U); if (!ret) { ::fprintf(stderr, "unable to open the log file\n"); return 1; } getHostVersion(); ::LogInfo(">> Modem Setup"); yaml::Node systemConf = m_conf["system"]; m_duplex = systemConf["duplex"].as(true); // try to load bandplan identity table std::string idenLookupFile = systemConf["iden_table"]["file"].as(); uint32_t idenReloadTime = systemConf["iden_table"]["time"].as(0U); if (idenLookupFile.length() <= 0U) { ::LogError(LOG_HOST, "No bandplan identity table? This must be defined!"); return 1; } 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(); LogInfo("General Parameters"); std::string identity = systemConf["identity"].as(); ::LogInfo(" Identity: %s", identity.c_str()); yaml::Node cwId = systemConf["cwId"]; bool cwEnabled = cwId["enable"].as(false); uint32_t cwTime = cwId["time"].as(10U); std::string callsign = cwId["callsign"].as(); LogInfo("CW Id Parameters"); LogInfo(" Enabled: %s", cwEnabled ? "enabled" : "disabled"); LogInfo(" Time: %u mins", cwTime); LogInfo(" Callsign: %s", callsign.c_str()); yaml::Node rfssConfig = systemConf["config"]; m_channelId = (uint8_t)rfssConfig["channelId"].as(0U); if (m_channelId > 15U) { // clamp to 15 m_channelId = 15U; } m_channelNo = (uint32_t)::strtoul(rfssConfig["channelNo"].as("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 (!calculateRxTxFreq()) { return false; } 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; } LogInfo("System Config Parameters"); LogInfo(" RX Frequency: %uHz", m_rxFrequency); LogInfo(" TX Frequency: %uHz", m_txFrequency); LogInfo(" Base Frequency: %uHz", entry.baseFrequency()); LogInfo(" TX Offset: %fMHz", entry.txOffsetMhz()); // open terminal console ret = m_console.open(); if (!ret) { return 1; } displayHelp(); printStatus(); bool end = false; while (!end) { int c = m_console.getChar(); switch (c) { /** Setup Commands */ case 'I': { char value[9] = { '\0' }; ::fprintf(stdout, "> Identity ? "); ::fflush(stdout); m_console.getLine(value, 9, 0); std::string identity = std::string(value); if (identity.length() > 0) { m_conf["system"]["identity"] = identity; } writeConfig(); } break; case 'C': { bool enabled = cwId["enable"].as(false); char value[9] = { '\0' }; ::fprintf(stdout, "> Callsign ? "); ::fflush(stdout); m_console.getLine(value, 9, 0); std::string callsign = std::string(value); if (callsign.length() > 0) { m_conf["system"]["cwId"]["callsign"] = callsign; } ::fprintf(stdout, "> CW Enabled (Y/N) ? "); ::fflush(stdout); m_console.getLine(value, 2, 0); if (toupper(value[0]) == 'Y' || toupper(value[0]) == 'N') { enabled = value[0] == 'Y' ? true : false; } m_conf["system"]["cwId"]["enable"] = __BOOL_STR(enabled); ::fprintf(stdout, "> CW Interval (minutes) ? "); ::fflush(stdout); m_console.getLine(value, 4, 0); uint32_t time = cwTime; sscanf(value, "%u", &time); if (value > 0) { m_conf["system"]["cwId"]["time"] = __INT_STR(time); } writeConfig(); } break; case 'i': { char value[3] = { '\0' }; ::fprintf(stdout, "> Channel ID ? "); ::fflush(stdout); m_console.getLine(value, 3, 0); uint8_t prevChannelId = m_channelId; // bryanb: appease the compiler... uint32_t channelId = m_channelId; sscanf(value, "%u", &channelId); m_channelId = (uint8_t)channelId; IdenTable entry = m_idenTable->find(m_channelId); if (entry.baseFrequency() == 0U) { ::LogError(LOG_SETUP, "Channel Id %u has an invalid base frequency.", m_channelId); m_channelId = prevChannelId; } writeConfig(); } break; case 'c': { char value[5] = { '\0' }; ::fprintf(stdout, "> Channel No ? "); ::fflush(stdout); m_console.getLine(value, 5, 0); uint8_t prevChannelNo = m_channelNo; sscanf(value, "%u", &m_channelNo); if (m_channelNo < 0 || m_channelNo > 4096) { ::LogError(LOG_SETUP, "Channel No %u is invalid.", m_channelNo); m_channelNo = prevChannelNo; } writeConfig(); } break; case 'f': { char value[10] = { '\0' }; ::fprintf(stdout, "> Tx Frequency (Hz) ? "); ::fflush(stdout); m_console.getLine(value, 10, 0); uint32_t txFrequency = m_txFrequency; sscanf(value, "%u", &txFrequency); IdenTable entry = m_idenTable->find(m_channelId); if (txFrequency < entry.baseFrequency()) { ::LogError(LOG_SETUP, "Tx Frequency %uHz is out of band range for base frequency %uHz. Tx Frequency must be greater then base frequency!", txFrequency, entry.baseFrequency()); break; } if (txFrequency > entry.baseFrequency() + 25500000) { ::LogError(LOG_SETUP, "Tx Frequency %uHz is out of band range for base frequency %uHz. Tx Frequency must be no more then 25.5 Mhz higher then base frequency!", txFrequency, entry.baseFrequency()); break; } uint32_t prevTxFrequency = m_txFrequency; m_txFrequency = txFrequency; uint32_t prevRxFrequency = m_rxFrequency; m_rxFrequency = m_txFrequency + (uint32_t)(entry.txOffsetMhz() * 1000000); float spaceHz = entry.chSpaceKhz() * 1000; uint32_t rootFreq = m_txFrequency - entry.baseFrequency(); uint8_t prevChannelNo = m_channelNo; m_channelNo = (uint32_t)(rootFreq / spaceHz); if (m_channelNo < 0 || m_channelNo > 4096) { ::LogError(LOG_SETUP, "Channel No %u is invalid.", m_channelNo); m_channelNo = prevChannelNo; m_txFrequency = prevTxFrequency; m_rxFrequency = prevRxFrequency; break; } writeConfig(); } break; /** General Commands */ case '`': printStatus(); break; case 'V': getHostVersion(); break; case 'H': case 'h': displayHelp(); break; case 'S': case 's': { yaml::Serialize(m_conf, m_confFile.c_str(), yaml::SerializeConfig(4, 64, false, false)); LogMessage(LOG_SETUP, " - Saved configuration to %s", m_confFile.c_str()); } break; case 'Q': case 'q': end = true; break; case 13: case 10: case -1: break; default: LogError(LOG_SETUP, "Unknown command - %c (H/h for help)", c); break; } sleep(5U); } m_console.close(); return 0; } // --------------------------------------------------------------------------- // Private Class Members // --------------------------------------------------------------------------- /// /// Helper to print the help to the console. /// void HostSetup::displayHelp() { LogMessage(LOG_SETUP, "General Commands:"); LogMessage(LOG_SETUP, " ` Display current settings"); LogMessage(LOG_SETUP, " V Display version of host"); LogMessage(LOG_SETUP, " H/h Display help"); LogMessage(LOG_SETUP, " S/s Save settings to configuration file"); LogMessage(LOG_SETUP, " Q/q Quit"); LogMessage(LOG_SETUP, "Setup Commands:"); LogMessage(LOG_SETUP, " I Set identity (logical name)"); LogMessage(LOG_SETUP, " C Set callsign and CW configuration"); LogMessage(LOG_SETUP, " i Set logical channel ID"); LogMessage(LOG_SETUP, " c Set logical channel number (by channel number)"); LogMessage(LOG_SETUP, " f Set logical channel number (by Tx frequency)"); } /// /// Helper to calculate the Rx/Tx frequencies. /// bool HostSetup::calculateRxTxFreq() { 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; } if (m_channelNo == 0U) { // clamp to 1 m_channelNo = 1U; } if (m_channelNo > 4095U) { // clamp to 4095 m_channelNo = 4095U; } if (m_duplex) { 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_rxFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * m_channelNo)) + calcTxOffset); m_txFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * m_channelNo))); } else { uint32_t calcSpace = (uint32_t)(entry.chSpaceKhz() / 0.125); m_rxFrequency = (uint32_t)((entry.baseFrequency() + ((calcSpace * 125) * m_channelNo))); m_txFrequency = m_rxFrequency; } return true; } /// /// Write configuration file. /// /// True, if configuration is written, otherwise false. bool HostSetup::writeConfig() { m_conf["system"]["config"]["channelId"] = __INT_STR(m_channelId); m_conf["system"]["config"]["channelNo"] = __INT_STR(m_channelNo); printStatus(); return true; } /// /// Helper to sleep the thread. /// /// Milliseconds to sleep. void HostSetup::sleep(uint32_t ms) { #if defined(_WIN32) || defined(_WIN64) ::Sleep(ms); #else ::usleep(ms * 1000); #endif } /// /// Prints the current status of the calibration. /// void HostSetup::printStatus() { yaml::Node systemConf = m_conf["system"]; std::string identity = systemConf["identity"].as(); IdenTable entry = m_idenTable->find(m_channelId); if (entry.baseFrequency() == 0U) { ::LogError(LOG_HOST, "Channel Id %u has an invalid base frequency.", m_channelId); } calculateRxTxFreq(); LogMessage(LOG_SETUP, " - Identity: %s", identity.c_str()); LogMessage(LOG_SETUP, " - Channel ID: %u, Channel No: %u", m_channelId, m_channelNo); LogMessage(LOG_SETUP, " - Base Freq: %uHz, TX Offset: %fMHz, Bandwidth: %fKHz, Channel Spacing: %fKHz", entry.baseFrequency(), entry.txOffsetMhz(), entry.chBandwidthKhz(), entry.chSpaceKhz()); LogMessage(LOG_SETUP, " - Rx Freq: %uHz, Tx Freq: %uHz", m_rxFrequency, m_txFrequency); { yaml::Node cwId = systemConf["cwId"]; bool enabled = cwId["enable"].as(false); uint32_t cwTime = cwId["time"].as(10U); std::string callsign = cwId["callsign"].as(); LogMessage(LOG_SETUP, " - Callsign: %s, CW Interval: %u mins, CW Enabled: %u", callsign.c_str(), cwTime, enabled); } }