459 lines
16 KiB
C++
459 lines
16 KiB
C++
//=========================================================================
|
|
// 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 <getopt.h>
|
|
|
|
#include <map>
|
|
#include <chrono>
|
|
#include <thread>
|
|
#include <mutex>
|
|
#include <atomic>
|
|
#include <sstream>
|
|
#include <cinttypes>
|
|
#include <stdlib.h>
|
|
#include <signal.h>
|
|
|
|
#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<bool> 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<MinimalRealtimeHelper>();
|
|
FlexVitaTask vitaTask(realtimeHelper, volumeAdjustmentDecibel);
|
|
|
|
std::map<std::string, std::string> radioList;
|
|
std::mutex radioMapMutex;
|
|
bool enableRadioLookup = true;
|
|
vitaTask.setOnRadioDiscoveredFn([&](FlexVitaTask&, std::string const& friendlyName, std::string const& ip, void*)
|
|
{
|
|
std::unique_lock<std::mutex> 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<std::mutex> 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;
|
|
}
|