freedv-gui/src/rig_control/HamlibRigController.cpp

797 lines
23 KiB
C++

//=========================================================================
// Name: HamlibRigController.cpp
// Purpose: Controls radios using Hamlib library.
//
// Authors: Mooneer Salem
// License:
//
// All rights reserved.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2.1,
// as published by the Free Software Foundation. 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, see <http://www.gnu.org/licenses/>.
//
//=========================================================================
#include <mutex>
#include <condition_variable>
#include <functional>
#include <future>
#include <sstream>
#include <algorithm>
#include <cstring>
#include <chrono>
#include <strings.h>
#include "HamlibRigController.h"
#include "util/logging/ulog.h"
HamlibRigController::RigList HamlibRigController::RigList_;
HamlibRigController::RigNameList HamlibRigController::RigNameList_;
std::mutex HamlibRigController::RigListMutex_;
#if RIGCAPS_NOT_CONST && !HAMLIB_CONST_WORKAROUND
int HamlibRigController::BuildRigList_(struct rig_caps *rig, rig_ptr_t rigList) {
#else
int HamlibRigController::BuildRigList_(const struct rig_caps *rig, rig_ptr_t rigList) {
#endif // RIGCAPS_NOT_CONST && !HAMLIB_CONST_WORKAROUND
((HamlibRigController::RigList *)rigList)->push_back(rig);
return 1;
}
bool HamlibRigController::RigCompare_(const struct rig_caps *rig1, const struct rig_caps *rig2) {
/* Compare manufacturer. */
int r = strcasecmp(rig1->mfg_name, rig2->mfg_name);
if (r != 0)
return r < 0;
/* Compare model. */
r = strcasecmp(rig1->model_name, rig2->model_name);
if (r != 0)
return r < 0;
/* Compare rig ID. */
return rig1->rig_model < rig2->rig_model;
}
HamlibRigController::HamlibRigController(std::string rigName, std::string serialPort, const int serialRate, const int civHex, const PttType pttType, std::string pttSerialPort, bool restoreFreqModeOnDisconnect, bool freqOnly)
: rigName_(rigName)
, serialPort_(serialPort)
, serialRate_(serialRate)
, civHex_(civHex)
, pttType_(pttType)
, pttSerialPort_(pttSerialPort)
, rig_(nullptr)
, multipleVfos_(false)
, pttSet_(false)
, currFreq_(0)
, currMode_(RIG_MODE_NONE)
, restoreOnDisconnect_(restoreFreqModeOnDisconnect)
, origFreq_(0)
, origMode_(RIG_MODE_NONE)
, freqOnly_(freqOnly)
, destroying_(false)
, rigResponseTime_(0)
{
// Perform initial load of rig list if this is our first time being created.
InitializeHamlibLibrary();
}
HamlibRigController::HamlibRigController(int rigIndex, std::string serialPort, const int serialRate, const int civHex, const PttType pttType, std::string pttSerialPort, bool restoreFreqModeOnDisconnect, bool freqOnly)
: rigName_(RigIndexToName(rigIndex))
, serialPort_(serialPort)
, serialRate_(serialRate)
, civHex_(civHex)
, pttType_(pttType)
, pttSerialPort_(pttSerialPort)
, rig_(nullptr)
, multipleVfos_(false)
, pttSet_(false)
, currFreq_(0)
, currMode_(RIG_MODE_NONE)
, restoreOnDisconnect_(restoreFreqModeOnDisconnect)
, origFreq_(0)
, origMode_(RIG_MODE_NONE)
, freqOnly_(freqOnly)
, destroying_(false)
, rigResponseTime_(0)
{
// Perform initial load of rig list if this is our first time being created.
InitializeHamlibLibrary();
}
HamlibRigController::~HamlibRigController()
{
destroying_ = true;
// Disconnect in a synchronous fashion before killing our thread.
std::shared_ptr<std::promise<void>> prom = std::make_shared<std::promise<void>>();
auto fut = prom->get_future();
enqueue_([this, prom]() {
if (rig_ != nullptr)
{
disconnectImpl_();
}
prom->set_value();
});
fut.wait();
}
static int LogHamlibErrors_(
enum rig_debug_level_e debug_level,
rig_ptr_t user_data,
const char *fmt,
va_list ap)
{
char buf[1024];
vsnprintf (buf, sizeof(buf), fmt, ap);
// Strip newlines from end of log messages
std::string msg = buf;
if (!msg.empty() && msg[msg.length() - 1] == '\n')
{
msg.erase(msg.length() - 1);
}
if (!msg.empty() && msg[msg.length() - 1] == '\r')
{
msg.erase(msg.length() - 1);
}
switch (debug_level)
{
case RIG_DEBUG_BUG:
log_fatal("%s", msg.c_str());
break;
case RIG_DEBUG_ERR:
log_error("%s", msg.c_str());
break;
case RIG_DEBUG_WARN:
log_warn("%s", msg.c_str());
break;
case RIG_DEBUG_VERBOSE:
log_debug("%s", msg.c_str());
break;
default:
log_trace("%s", msg.c_str());
break;
}
return RIG_OK;
}
void HamlibRigController::InitializeHamlibLibrary()
{
std::unique_lock<std::mutex> lk(RigListMutex_);
if (RigList_.size() == 0)
{
/* Stop hamlib from spewing info to stderr. */
rig_set_debug(RIG_DEBUG_NONE);
/* Create sorted list of rigs. */
rig_load_all_backends();
rig_list_foreach(&HamlibRigController::BuildRigList_, &RigList_);
std::sort(RigList_.begin(), RigList_.end(), &HamlibRigController::RigCompare_);
// Capture names of rigs for configuration use.
for (auto& rig : RigList_)
{
RigNameList_.push_back(std::string(rig->mfg_name) + std::string(" ") + std::string(rig->model_name));
}
/* Reset debug output. */
rig_set_debug(RIG_DEBUG_VERBOSE);
/* Ensure that we use ulog to log hamlib messages. */
rig_set_debug_callback(&LogHamlibErrors_, nullptr);
}
}
void HamlibRigController::connect()
{
enqueue_(std::bind(&HamlibRigController::connectImpl_, this));
}
void HamlibRigController::disconnect()
{
enqueue_(std::bind(&HamlibRigController::disconnectImpl_, this));
}
bool HamlibRigController::isConnected()
{
return rig_ != nullptr;
}
void HamlibRigController::ptt(bool state)
{
enqueue_(std::bind(&HamlibRigController::pttImpl_, this, state));
}
void HamlibRigController::setFrequency(uint64_t frequency)
{
enqueue_(std::bind(&HamlibRigController::setFrequencyImpl_, this, frequency));
}
void HamlibRigController::setMode(IRigFrequencyController::Mode mode)
{
enqueue_(std::bind(&HamlibRigController::setModeImpl_, this, mode));
}
void HamlibRigController::requestCurrentFrequencyMode()
{
enqueue_(std::bind(&HamlibRigController::requestCurrentFrequencyModeImpl_, this));
}
int HamlibRigController::getRigResponseTimeMicroseconds()
{
return rigResponseTime_;
}
int HamlibRigController::RigNameToIndex(std::string rigName)
{
InitializeHamlibLibrary();
int index = 0;
for (auto& entry : RigNameList_)
{
if (rigName == entry)
{
return index;
}
index++;
}
return -1;
}
std::string HamlibRigController::RigIndexToName(unsigned int rigIndex)
{
InitializeHamlibLibrary();
return RigNameList_[rigIndex];
}
int HamlibRigController::GetNumberSupportedRadios()
{
InitializeHamlibLibrary();
return RigList_.size();
}
// Note: these execute in the thread created by ThreadedObject on object instantiation.
void HamlibRigController::connectImpl_()
{
if (rig_ != nullptr)
{
std::string errMsg = "Already connected to radio.";
onRigError(this, errMsg);
return;
}
auto rigIndex = RigNameToIndex(rigName_);
if (rigIndex == -1)
{
std::string errMsg = "Could not find rig " + rigName_ + " in Hamlib database.";
onRigError(this, errMsg);
return;
}
/* Initialise, configure and open. */
origFreq_ = 0;
origMode_ = RIG_MODE_NONE;
rig_ = rig_init(RigList_[rigIndex]->rig_model);
if (!rig_)
{
log_debug("rig_init() failed, returning");
std::string errMsg = "Hamlib could not initialize rig " + rigName_;
onRigError(this, errMsg);
return;
}
log_debug("rig_init() OK ....");
/* Set CI-V address if necessary. */
if (!strncmp(RigList_[rigIndex]->mfg_name, "Icom", 4))
{
char civAddr[5];
snprintf(civAddr, 5, "0x%0X", civHex_);
log_debug("hamlib: setting CI-V address to: %s", civAddr);
rig_set_conf(rig_, rig_token_lookup(rig_, "civaddr"), civAddr);
}
else
{
log_debug("hamlib: ignoring CI-V configuration due to non-Icom radio\r");
}
rig_set_conf(rig_, rig_token_lookup(rig_, "rig_pathname"), serialPort_.c_str());
if (pttSerialPort_.size() > 0 &&
pttSerialPort_ != serialPort_ &&
(pttType_ == PTT_VIA_RTS || pttType_ == PTT_VIA_DTR))
{
std::string fixedPttSerialPort = pttSerialPort_;
#if defined(WIN32)
// WSJT-X logic, not sure it's needed here.
fixedPttSerialPort = std::string("\\\\.\\") + fixedPttSerialPort;
#endif // defined(WIN32)
rig_set_conf(rig_, rig_token_lookup(rig_, "ptt_pathname"), fixedPttSerialPort.c_str());
}
if (serialRate_ > 0)
{
log_debug("hamlib: setting serial rate: %d", serialRate_);
std::stringstream ss;
ss << serialRate_;
rig_set_conf(rig_, rig_token_lookup(rig_, "serial_speed"), ss.str().c_str());
}
// Set PTT type as needed.
switch(pttType_)
{
case PTT_VIA_RTS:
rig_set_conf(rig_, rig_token_lookup(rig_, "ptt_type"), "RTS");
break;
case PTT_VIA_DTR:
rig_set_conf(rig_, rig_token_lookup(rig_, "ptt_type"), "DTR");
break;
case PTT_VIA_NONE:
rig_set_conf(rig_, rig_token_lookup(rig_, "ptt_type"), "None");
break;
case PTT_VIA_CAT:
rig_set_conf(rig_, rig_token_lookup(rig_, "ptt_type"), "RIG");
break;
case PTT_VIA_CAT_DATA:
// Need to explicitly use RIGMICDATA in order for RIG_PTT_ON_DATA to work.
rig_set_conf(rig_, rig_token_lookup(rig_, "ptt_type"), "RIGMICDATA");
break;
default:
break;
}
if (pttType_ == PTT_VIA_RTS || pttType_ == PTT_VIA_DTR)
{
rig_set_conf(rig_, rig_token_lookup(rig_, "ptt_share"), "1");
}
// Icom workaround from WSJT-X. Not sure it's needed here as
// FreeDV doesn't do split.
rig_set_conf(rig_, rig_token_lookup(rig_, "no_xchg"), "1");
auto result = rig_open(rig_);
if (result == RIG_OK)
{
log_debug("hamlib: rig_open() OK");
onRigConnected(this);
// Set timeouts so that we don't wait an extremely long time to begin TX.
rig_set_conf(rig_, rig_token_lookup(rig_, "timeout"), "500");
rig_set_conf(rig_, rig_token_lookup(rig_, "retry"), "0");
rig_set_conf(rig_, rig_token_lookup(rig_, "timeout_retry"), "0");
// Determine whether we have multiple VFOs.
multipleVfos_ = false;
vfo_t vfo;
if (rig_get_vfo (rig_, &vfo) == RIG_OK && (rig_->state.vfo_list & RIG_VFO_B))
{
multipleVfos_ = true;
}
// Get current frequency and mode when we first connect so we can
// revert on close.
requestCurrentFrequencyModeImpl_();
return;
}
else
{
std::string errMsg = std::string("Could not connect to radio: ") + rigerror(result);
onRigError(this, errMsg);
}
log_debug("hamlib: rig_open() failed: %s", rigerror(result));
rig_cleanup(rig_);
rig_ = nullptr;
}
void HamlibRigController::disconnectImpl_()
{
if (rig_)
{
// Stop transmitting.
if (pttSet_)
{
pttImpl_(false);
}
// If we're told to restore on disconnect, do so.
if (restoreOnDisconnect_)
{
vfo_t currVfo = getCurrentVfo_();
setFrequencyHelper_(currVfo, origFreq_);
if (!freqOnly_)
{
setModeHelper_(currVfo, origMode_);
}
}
origFreq_ = 0;
origMode_ = RIG_MODE_NONE;
rig_close(rig_);
rig_cleanup(rig_);
rig_ = nullptr;
onRigDisconnected(this);
}
}
void HamlibRigController::pttImpl_(bool state)
{
log_debug("Hamlib::ptt: %d", state);
if (rig_ == nullptr)
{
return;
}
ptt_t on = state ? RIG_PTT_ON : RIG_PTT_OFF;
if (pttType_ == PTT_VIA_CAT_DATA && on != RIG_PTT_OFF)
{
on = RIG_PTT_ON_DATA;
}
auto oldTime = std::chrono::steady_clock::now();
int result = RIG_OK;
if (pttType_ != PTT_VIA_NONE)
{
result = rig_set_ptt(rig_, RIG_VFO_CURR, on);
}
auto newTime = std::chrono::steady_clock::now();
auto totalTimeMicroseconds = (int)std::chrono::duration_cast<std::chrono::microseconds>(newTime - oldTime).count();
rigResponseTime_ = std::max(rigResponseTime_, totalTimeMicroseconds);
if (result != RIG_OK)
{
log_debug("rig_set_ptt: error = %s ", rigerror(result));
std::string errMsg = "Cannot set PTT: " + std::string(rigerror(result));
onRigError(this, errMsg);
}
else
{
bool changed = false;
if (pttSet_ != state)
{
changed = true;
onPttChange(this, state);
}
pttSet_ = state;
if (changed && !state && !destroying_)
{
requestCurrentFrequencyMode();
}
}
}
void HamlibRigController::setFrequencyImpl_(uint64_t frequencyHz)
{
if (rig_ == nullptr)
{
return;
}
if (currFreq_ == frequencyHz)
{
return;
}
if (pttSet_)
{
// If transmitting, temporarily stop transmitting so we can change the mode.
int result = rig_set_ptt(rig_, RIG_VFO_CURR, RIG_PTT_OFF);
if (result != RIG_OK)
{
// If we can't stop transmitting, we shouldn't try to change the mode
// as it'll fail on some radios.
log_debug("rig_set_ptt: error = %s ", rigerror(result));
std::string errMsg = std::string("Could not disable PTT prior to frequency change: ") + rigerror(result);
onRigError(this, errMsg);
return;
}
}
vfo_t currVfo = getCurrentVfo_();
setFrequencyHelper_(currVfo, frequencyHz);
if (pttSet_)
{
// If transmitting, temporarily stop transmitting so we can change the mode.
int result = rig_set_ptt(rig_, RIG_VFO_CURR, pttType_ == PTT_VIA_CAT_DATA ? RIG_PTT_ON_DATA : RIG_PTT_ON);
if (result != RIG_OK)
{
// If we can't stop transmitting, we shouldn't try to change the mode
// as it'll fail on some radios.
log_debug("rig_set_ptt: error = %s ", rigerror(result));
std::string errMsg = std::string("Could not enable PTT after frequency change: ") + rigerror(result);
onRigError(this, errMsg);
}
}
}
void HamlibRigController::setModeImpl_(IRigFrequencyController::Mode mode)
{
rmode_t hamlibMode;
switch (mode)
{
case USB:
hamlibMode = RIG_MODE_USB;
break;
case LSB:
hamlibMode = RIG_MODE_LSB;
break;
case DIGU:
hamlibMode = RIG_MODE_PKTUSB;
break;
case DIGL:
hamlibMode = RIG_MODE_PKTLSB;
break;
case FM:
hamlibMode = RIG_MODE_FM;
break;
case DIGFM:
hamlibMode = RIG_MODE_PKTFM;
break;
case AM:
hamlibMode = RIG_MODE_AM;
break;
default:
onRigError(this, "Cannot set mode: mode not recognized");
return;
}
if (rig_ == nullptr || currMode_ == hamlibMode)
{
return;
}
if (pttSet_)
{
// If transmitting, temporarily stop transmitting so we can change the mode.
int result = rig_set_ptt(rig_, RIG_VFO_CURR, RIG_PTT_OFF);
if (result != RIG_OK)
{
// If we can't stop transmitting, we shouldn't try to change the mode
// as it'll fail on some radios.
log_debug("rig_set_ptt: error = %s ", rigerror(result));
std::string errMsg = std::string("Could not disable PTT prior to mode change: ") + rigerror(result);
onRigError(this, errMsg);
return;
}
}
vfo_t currVfo = getCurrentVfo_();
setModeHelper_(currVfo, hamlibMode);
if (pttSet_)
{
// If transmitting, temporarily stop transmitting so we can change the mode.
int result = rig_set_ptt(rig_, RIG_VFO_CURR, pttType_ == PTT_VIA_CAT_DATA ? RIG_PTT_ON_DATA : RIG_PTT_ON);
if (result != RIG_OK)
{
// If we can't stop transmitting, we shouldn't try to change the mode
// as it'll fail on some radios.
log_debug("rig_set_ptt: error = %s ", rigerror(result));
std::string errMsg = std::string("Could not enable PTT after mode change: ") + rigerror(result);
onRigError(this, errMsg);
}
}
}
void HamlibRigController::requestCurrentFrequencyModeImpl_()
{
if (rig_ == nullptr)
{
return;
}
rmode_t mode = RIG_MODE_NONE;
pbwidth_t passband = 0;
vfo_t currVfo = getCurrentVfo_();
modeAttempt:
int result = rig_get_mode(rig_, currVfo, &mode, &passband);
if (result != RIG_OK && currVfo == RIG_VFO_CURR)
{
log_debug("rig_get_mode: error = %s ", rigerror(result));
}
else if (result != RIG_OK)
{
// We supposedly have multiple VFOs but ran into problems
// trying to get information about the supposed active VFO.
// Make a last ditch effort using RIG_VFO_CURR before fully failing.
currVfo = RIG_VFO_CURR;
goto modeAttempt;
}
else
{
freqAttempt:
freq_t freq = 0;
result = rig_get_freq(rig_, currVfo, &freq);
if (result != RIG_OK && currVfo == RIG_VFO_CURR)
{
log_debug("rig_get_freq: error = %s ", rigerror(result));
}
else if (result != RIG_OK)
{
// We supposedly have multiple VFOs but ran into problems
// trying to get information about the supposed active VFO.
// Make a last ditch effort using RIG_VFO_CURR before fully failing.
currVfo = RIG_VFO_CURR;
goto freqAttempt;
}
else
{
IRigFrequencyController::Mode currMode;
switch (mode)
{
case RIG_MODE_USB:
currMode = USB;
break;
case RIG_MODE_LSB:
currMode = LSB;
break;
case RIG_MODE_PKTUSB:
currMode = DIGU;
break;
case RIG_MODE_PKTLSB:
currMode = DIGL;
break;
case RIG_MODE_FM:
currMode = FM;
break;
case RIG_MODE_PKTFM:
currMode = DIGFM;
break;
case RIG_MODE_AM:
currMode = AM;
break;
default:
currMode = UNKNOWN;
break;
}
// If this is the first time we're retrieving the current frequency/mode,
// store it for later restore on disconnect.
if (origFreq_ == 0)
{
origFreq_ = freq;
origMode_ = mode;
}
onFreqModeChange(this, freq, currMode);
}
}
}
vfo_t HamlibRigController::getCurrentVfo_()
{
vfo_t vfo = RIG_VFO_CURR;
if (multipleVfos_)
{
int result = rig_get_vfo(rig_, &vfo);
if (result != RIG_OK && result != -RIG_ENAVAIL)
{
// Note: we'll still attempt operation using RIG_VFO_CURR just in case.
log_debug("rig_get_vfo: error = %s ", rigerror(result));
vfo = RIG_VFO_CURR;
}
}
return vfo;
}
void HamlibRigController::setFrequencyHelper_(vfo_t currVfo, uint64_t frequencyHz)
{
bool setOkay = false;
// Avoid setting invalid frequencies as hamlib sometimes doesn't handle this well.
if (currFreq_ == frequencyHz || frequencyHz == 0)
{
return;
}
freqAttempt:
int result = rig_set_freq(rig_, currVfo, frequencyHz);
if (result != RIG_OK && currVfo == RIG_VFO_CURR)
{
log_debug("rig_set_freq: error = %s ", rigerror(result));
}
else if (result != RIG_OK)
{
// We supposedly have multiple VFOs but ran into problems
// trying to get information about the supposed active VFO.
// Make a last ditch effort using RIG_VFO_CURR before fully failing.
currVfo = RIG_VFO_CURR;
goto freqAttempt;
}
else
{
setOkay = true;
}
// If we're able to set either frequency or mode, we should update the current state accordingly.
// The actual configuration of the radio (in case of failure) will auto-update within
// a few seconds after this update.
if (setOkay)
{
currFreq_ = frequencyHz;
if (!destroying_)
{
requestCurrentFrequencyMode();
}
}
}
void HamlibRigController::setModeHelper_(vfo_t currVfo, rmode_t mode)
{
bool setOkay = false;
// Avoid setting invalid mode as hamlib sometimes doesn't handle this well.
if (currMode_ == mode || mode == RIG_MODE_NONE)
{
return;
}
modeAttempt:
int result = rig_set_mode(rig_, currVfo, mode, RIG_PASSBAND_NOCHANGE);
if (result != RIG_OK && currVfo == RIG_VFO_CURR)
{
log_debug("rig_set_mode: error = %s ", rigerror(result));
}
else if (result != RIG_OK)
{
// We supposedly have multiple VFOs but ran into problems
// trying to get information about the supposed active VFO.
// Make a last ditch effort using RIG_VFO_CURR before fully failing.
currVfo = RIG_VFO_CURR;
goto modeAttempt;
}
else
{
setOkay = true;
}
// If we're able to set either frequency or mode, we should update the current state accordingly.
// The actual configuration of the radio (in case of failure) will auto-update within
// a few seconds after this update.
if (setOkay)
{
currMode_ = mode;
if (!destroying_)
{
requestCurrentFrequencyMode();
}
}
}