//========================================================================= // Name: main.cpp // Purpose: Main entry point for Flex waveform. // // Authors: Mooneer Salem // License: // // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // // - Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // - Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER // OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // //========================================================================= #include #include #include #include #include #include #include #include #include #include #include "flex_defines.h" #include "FlexVitaTask.h" #include "FlexTcpTask.h" #include "../common/MinimalTxRxThread.h" #include "../common/MinimalRealTimeHelper.h" #include "../common/ReportingController.h" #include "../pipeline/rade_text.h" #include "../util/logging/ulog.h" #include "../reporting/FreeDVReporter.h" #include "rade_api.h" #include "git_version.h" extern "C" { #include "fargan_config.h" #include "fargan.h" #include "lpcnet.h" } #define SOFTWARE_NAME "freedv-flex" using namespace std::chrono_literals; std::atomic g_tx; bool endingTx; bool exitingApplication; int spotTimeoutSeconds = 600; // 10 minute default void OnSignalExit(int) { // Infinite loop at bottom will check for this and trigger cleanup exitingApplication = true; } struct CallsignReporting { ReportingController* reporter; FlexTcpTask* tcpTask; MinimalTxRxThread* rxThread; }; void ReportReceivedCallsign(rade_text_t, const char *txt_ptr, int length, void *state) { CallsignReporting* reportObj = (CallsignReporting*)state; if (txt_ptr != nullptr && length > 0) { std::string callsign(txt_ptr, length); auto snr = reportObj->rxThread->getSnr(); if (reportObj->reporter != nullptr) { reportObj->reporter->reportCallsign(callsign, snr); } reportObj->tcpTask->addSpot(callsign, snr, spotTimeoutSeconds); } } void printUsage(char* appName) { log_info("Usage: %s [-d|--disable-reporting] [-l|--reporting-locator LOCATOR] [-m|--reporting-message MESSAGE] [-r|--rx-volume DB] [-t|--spot-timeout SEC] [-h|--help] [-v|--version]", appName); log_info(" -d|--disable-reporting: Disables FreeDV Reporter reporting."); log_info(" -l|--reporting-locator: Overrides grid square/locator from radio for FreeDV Reporter reporting."); log_info(" -m|--reporting-message: Sets reporting message for FreeDV Reporter reporting."); log_info(" -r|--rx-volume: Increases or decreases receive volume by the provided dB figure."); log_info(" -t|--spot-timeout: Timeout for reported spots (default: 600s or 10min)."); log_info(" -h|--help: This help message."); log_info(" -v|--version: Prints the application version and exits."); log_info(""); } int main(int argc, char** argv) { std::string stationGridSquare; std::string stationUserMessage; bool disableReporting = false; float volumeAdjustmentDecibel = 0.0f; // Print version log_info("%s version %s", SOFTWARE_NAME, GetFreeDVVersion().c_str()); // Check command line options static struct option longOptions[] = { {"disable-reporting", no_argument, 0, 'd' }, {"reporting-locator", required_argument, 0, 'l' }, {"reporting-message", required_argument, 0, 'm' }, {"rx-volume", required_argument, 0, 'r' }, {"spot-timeout", required_argument, 0, 't' }, {"help", no_argument, 0, 'h' }, {"version", no_argument, 0, 'v' }, {0, 0, 0, 0 } }; constexpr char shortOptions[] = "dl:m:r:t:hv"; int optionIndex = 0; int c = 0; while ((c = getopt_long(argc, argv, shortOptions, longOptions, &optionIndex)) != -1) // NOLINT { switch(c) { case 'd': { log_info("Disabling FreeDV Reporter reporting"); disableReporting = true; break; } case 'm': { if (optarg == nullptr || strlen(optarg) == 0) { log_error("Message required if specifying -m/--reporting-message."); printUsage(argv[0]); exit(-1); // NOLINT } log_info("Using '%s' for FreeDV Reporter user message", optarg); stationUserMessage = optarg; break; } case 'l': { if (optarg == nullptr || strlen(optarg) == 0) { log_error("Grid square required if specifying -l/--reporting-locator."); printUsage(argv[0]); exit(-1); // NOLINT } log_info("Using grid square %s for FreeDV Reporter reporting", optarg); stationGridSquare = optarg; break; } case 'r': { if (optarg == nullptr || strlen(optarg) == 0) { log_error("Adjustment level in dB required if specifying -r/--rx-volume."); printUsage(argv[0]); exit(-1); // NOLINT } char* tmp = nullptr; volumeAdjustmentDecibel = strtof(optarg, &tmp); if (tmp != (optarg + strlen(optarg)) || errno == ERANGE) { log_error("Provided level for -r/--rx-volume must be a number."); printUsage(argv[0]); exit(-1); // NOLINT } log_info("Adjusting receive audio volume by %f dB", volumeAdjustmentDecibel); break; } case 't': { if (optarg == nullptr || strlen(optarg) == 0) { log_error("Timeout in seconds required if specifying -t/--spot-timeout."); printUsage(argv[0]); exit(-1); // NOLINT } char* tmp = nullptr; errno = 0; spotTimeoutSeconds = (int)strtol(optarg, &tmp, 10); if (tmp != (optarg + strlen(optarg)) || errno == ERANGE) { log_error("Provided timeout for -t/--spot-timeout must be a number."); printUsage(argv[0]); exit(-1); // NOLINT } break; } case 'v': { // Version already printed above. exit(0); // NOLINT } case 'h': default: { printUsage(argv[0]); exit(-1); // NOLINT } } } log_info("Spot timeout is %d seconds", spotTimeoutSeconds); // Environment setup -- make sure we don't use more threads than needed. // Prevents conflicts between numpy/OpenBLAS threading and Python/C++ threading, // improving performance. // NOLINTBEGIN setenv("OMP_NUM_THREADS", "1", 1); setenv("OPENBLAS_NUM_THREADS", "1", 1); // Enable maximum optimization for Python. setenv("PYTHONOPTIMIZE", "2", 1); // Enable Python JIT (if version of Python supports it). setenv("PYTHON_JIT", "1", 1); // NOLINTEND // Initialize and start RADE. log_info("Initializing RADE library..."); rade_initialize(); // Override signal handlers. Needed to ensure we can properly // clean up the waveform if the user wants to exit. log_info("Setting up signal handlers..."); struct sigaction action; memset(&action, 0, sizeof(action)); action.sa_handler = &OnSignalExit; action.sa_flags = SA_RESTART; sigaction(SIGHUP, &action, nullptr); sigaction(SIGINT, &action, nullptr); sigaction(SIGTERM, &action, nullptr); log_info("Creating RADE object"); char modelFile[1]; modelFile[0] = 0; struct rade* radeObj = rade_open(modelFile, RADE_USE_C_ENCODER | RADE_USE_C_DECODER | RADE_VERBOSE_0); assert(radeObj != nullptr); log_info("Creating RADE text object"); auto radeTextPtr = rade_text_create(); assert(radeTextPtr != nullptr); log_info("Creating FARGAN objects"); FARGANState fargan; LPCNetEncState *lpcnetEncState = nullptr; float zeros[320] = {0}; float in_features[5*NB_TOTAL_FEATURES] = {0}; fargan_init(&fargan); fargan_cont(&fargan, zeros, in_features); lpcnetEncState = lpcnet_encoder_create(); assert(lpcnetEncState != nullptr); std::string radioIp = ""; auto radioAddrEnv = getenv("SSDR_RADIO_ADDRESS"); // NOLINT if (radioAddrEnv != nullptr) { radioIp = radioAddrEnv; log_info("Got radio address from environment, likely executing on radio itself"); } // Start up VITA task so we can get the list of available radios. auto realtimeHelper = std::make_shared(); FlexVitaTask vitaTask(realtimeHelper, volumeAdjustmentDecibel); std::map radioList; std::mutex radioMapMutex; bool enableRadioLookup = true; vitaTask.setOnRadioDiscoveredFn([&](FlexVitaTask&, std::string const& friendlyName, std::string const& ip, void*) { std::unique_lock lk(radioMapMutex); if (enableRadioLookup) { log_info("Got discovery callback (radio %s, IP %s)", friendlyName.c_str(), ip.c_str()); radioList[ip] = friendlyName; } }, nullptr); // Initialize audio pipelines auto callbackObj = vitaTask.getCallbackData(); MinimalTxRxThread txThread(true, FLEX_SAMPLE_RATE, FLEX_SAMPLE_RATE, realtimeHelper, radeObj, lpcnetEncState, &fargan, radeTextPtr, callbackObj); MinimalTxRxThread rxThread(false, FLEX_SAMPLE_RATE, FLEX_SAMPLE_RATE, realtimeHelper, radeObj, lpcnetEncState, &fargan, radeTextPtr, callbackObj); log_info("Starting TX/RX threads"); txThread.start(); rxThread.start(); log_info("Synchronizing startup of TX/RX threads"); txThread.waitForReady(); rxThread.waitForReady(); txThread.signalToStart(); rxThread.signalToStart(); log_info("Getting available radios from network"); while (radioIp == "") { std::unique_lock lk(radioMapMutex); if (radioList.size() == 0) { log_info("No radios found yet, sleeping 1s"); lk.unlock(); std::this_thread::sleep_for(1s); lk.lock(); continue; } radioIp = radioList.begin()->first; enableRadioLookup = false; } log_info("Connecting to radio at IP %s", radioIp.c_str()); FlexTcpTask tcpTask(vitaTask.getPort()); CallsignReporting reportData; ReportingController reportController(SOFTWARE_NAME, false, disableReporting); reportData.tcpTask = &tcpTask; reportData.reporter = &reportController; reportData.rxThread = &rxThread; if (!disableReporting) { rade_text_set_rx_callback(radeTextPtr, &ReportReceivedCallsign, &reportData); if (stationGridSquare != "") { reportController.updateRadioGridSquare(stationGridSquare); } reportController.updateUserMessage(stationUserMessage); } // Set up reporting of actual receive state (prior to getting callsign). int rxCounter = 0; uint16_t meterMeterId = 0; ThreadedTimer rxNoCallsignReporting(100, [&](ThreadedTimer&) { if (rxThread.getSync() && !reportController.isHidden()) { rxCounter = (rxCounter + 1) % 10; auto snr = rxThread.getSnr(); if (rxCounter == 0) { reportController.reportCallsign("", snr); } vitaTask.sendMeter(meterMeterId, snr); } else { vitaTask.sendMeter(meterMeterId, -99); } }, true); if (!disableReporting) { rxNoCallsignReporting.start(); } tcpTask.setWaveformSnrMeterIdentifiersFn([&](FlexTcpTask&, uint16_t meterId, void*) { meterMeterId = meterId; }, nullptr); tcpTask.setWaveformCallsignRxFn([&](FlexTcpTask&, std::string const& callsign, void*) { // Add callsign to EOO so others can report us log_info("Setting EOO bits"); int nsyms = rade_n_eoo_bits(radeObj); float* eooSyms = new float[nsyms]; assert(eooSyms); rade_text_generate_tx_string(radeTextPtr, callsign.c_str(), callsign.size(), eooSyms, nsyms); rade_tx_set_eoo_bits(radeObj, eooSyms); reportController.updateRadioCallsign(callsign); }, nullptr); tcpTask.setWaveformGridSquareUpdateFn([&](FlexTcpTask&, std::string const& gridSquare, void*) { if (stationGridSquare == "") { reportController.updateRadioGridSquare(gridSquare); } }, nullptr); tcpTask.setWaveformConnectedFn([&](FlexTcpTask&, void*) { vitaTask.clearStreamIds(); vitaTask.enableAudio(true); vitaTask.radioConnected(radioIp.c_str()); }, nullptr); tcpTask.setWaveformUserConnectedFn([&](FlexTcpTask&, void*) { reportController.showSelf(); }, nullptr); tcpTask.setWaveformUserDisconnectedFn([&](FlexTcpTask&, void*) { reportController.hideSelf(); }, nullptr); tcpTask.setWaveformFreqChangeFn([&](FlexTcpTask&, uint64_t freq, void*) { reportController.changeFrequency(freq); }, nullptr); tcpTask.setWaveformTransmitFn([&](FlexTcpTask&, FlexTcpTask::TxState tx, void*) { if (tx == FlexTcpTask::ENDING_TX) { endingTx = true; // EOO should then queue out vitaTask.setEndingTx(true); } else { endingTx = false; bool txFlag = tx == FlexTcpTask::TRANSMITTING; vitaTask.setEndingTx(false); vitaTask.setTransmit(txFlag); g_tx.store(txFlag, std::memory_order_release); reportController.transmit(txFlag); } }, nullptr); tcpTask.setWaveformAddValidStreamIdentifiersFn([&](FlexTcpTask&, uint32_t txInStreamId, uint32_t txOutStreamId, uint32_t rxInStreamId, uint32_t rxOutStreamId, void*) { vitaTask.registerStreamIds(txInStreamId, txOutStreamId, rxInStreamId, rxOutStreamId); }, nullptr); tcpTask.connect(radioIp.c_str(), FLEX_TCP_PORT, true); while (!exitingApplication) { std::this_thread::sleep_for(100ms); } // Stop TX/RX threads. Needed to prevent RADE calls after finalize. txThread.stop(); rxThread.stop(); // Clean up RADE rade_close(radeObj); rade_finalize(); return 0; }