mirror of
https://github.com/JS8Call-improved/JS8Call-improved
synced 2026-08-13 17:47:36 -04:00
use the standard C++17 or later library utility std::as_const which replaces qAsConst - Configuration.cpp: the parameters, allow_monitors and show_monitors, are only used on Linux in a preprocessor guard. Silence the compiler spam for Mac and Windows which see them as declared but unused parameters
1133 lines
44 KiB
C++
1133 lines
44 KiB
C++
/**
|
|
* @file HRD.cpp
|
|
* @brief Implementation of the HRD class
|
|
* Implements interface with Ham Radio Deluxe
|
|
*/
|
|
|
|
#include "HRDTransceiver.h"
|
|
#include "JS8_Network/NetworkServerLookup.h"
|
|
|
|
#include <QByteArray>
|
|
#include <QDir>
|
|
#include <QHostAddress>
|
|
#include <QLoggingCategory>
|
|
#include <QRegularExpression>
|
|
#include <QStandardPaths>
|
|
#include <QTcpSocket>
|
|
#include <QThread>
|
|
|
|
#include <utility>
|
|
|
|
Q_DECLARE_LOGGING_CATEGORY(hrdtransceiver_js8)
|
|
|
|
namespace {
|
|
char const *const HRD_transceiver_name = "Ham Radio Deluxe";
|
|
|
|
// some commands require a settling time, particularly "RX A" and
|
|
// "RX B" on the Yaesu FTdx3000.
|
|
int constexpr yaesu_delay{250};
|
|
} // namespace
|
|
|
|
void HRDTransceiver::register_transceivers(
|
|
TransceiverFactory::Transceivers *registry, int id) {
|
|
(*registry)[HRD_transceiver_name] = TransceiverFactory::Capabilities(
|
|
id, TransceiverFactory::Capabilities::network, true, true /* maybe */);
|
|
}
|
|
|
|
struct HRDMessage {
|
|
// Placement style new overload for outgoing messages that does the
|
|
// construction too.
|
|
static void *operator new(size_t size, QString const &payload) {
|
|
size +=
|
|
sizeof(QChar) * (payload.size() + 1); // space for terminator too
|
|
HRDMessage *storage(reinterpret_cast<HRDMessage *>(new char[size]));
|
|
storage->size_ = size;
|
|
ushort const *pl(payload.utf16());
|
|
std::copy(pl, pl + payload.size() + 1,
|
|
storage->payload_); // copy terminator too
|
|
storage->magic_1_ = magic_1_value_;
|
|
storage->magic_2_ = magic_2_value_;
|
|
storage->checksum_ = 0;
|
|
return storage;
|
|
}
|
|
|
|
// Placement style new overload for incoming messages that does the
|
|
// construction too.
|
|
//
|
|
// No memory allocation here.
|
|
static void *operator new(size_t /* size */, QByteArray const &message) {
|
|
// Nasty const_cast here to avoid copying the message buffer.
|
|
return const_cast<HRDMessage *>(
|
|
reinterpret_cast<HRDMessage const *>(message.data()));
|
|
}
|
|
|
|
void operator delete(void *p, size_t) {
|
|
delete[] reinterpret_cast<char *>(
|
|
p); // Mirror allocation in operator new above.
|
|
}
|
|
|
|
quint32 size_;
|
|
qint32 magic_1_;
|
|
qint32 magic_2_;
|
|
qint32 checksum_; // Apparently not used.
|
|
QChar payload_[0]; // UTF-16 (which is wchar_t on Windows)
|
|
|
|
static qint32 constexpr magic_1_value_ = 0x1234ABCD;
|
|
static qint32 constexpr magic_2_value_ = 0xABCD1234;
|
|
};
|
|
|
|
HRDTransceiver::HRDTransceiver(std::unique_ptr<TransceiverBase> wrapped,
|
|
QString const &server, bool use_for_ptt,
|
|
TransceiverFactory::TXAudioSource audio_source,
|
|
int poll_interval, QObject *parent)
|
|
: PollingTransceiver{poll_interval, parent}, wrapped_{std::move(wrapped)},
|
|
use_for_ptt_{use_for_ptt}, audio_source_{audio_source}, server_{server},
|
|
hrd_{0}, protocol_{none}, current_radio_{0}, vfo_count_{0},
|
|
vfo_A_button_{-1}, vfo_B_button_{-1}, vfo_toggle_button_{-1},
|
|
mode_A_dropdown_{-1}, mode_B_dropdown_{-1}, data_mode_toggle_button_{-1},
|
|
data_mode_on_button_{-1}, data_mode_off_button_{-1},
|
|
data_mode_dropdown_{-1}, split_mode_button_{-1}, split_mode_dropdown_{-1},
|
|
split_mode_dropdown_write_only_{false}, split_off_button_{-1},
|
|
tx_A_button_{-1}, tx_B_button_{-1}, rx_A_button_{-1}, rx_B_button_{-1},
|
|
receiver_dropdown_{-1}, ptt_button_{-1}, alt_ptt_button_{-1},
|
|
reversed_{false} {}
|
|
|
|
int HRDTransceiver::do_start() {
|
|
TRACE_CAT("HRDTransceiver", "starting");
|
|
if (wrapped_)
|
|
wrapped_->start(0);
|
|
|
|
auto server_details = network_server_lookup(server_, 7809u);
|
|
if (!hrd_) {
|
|
hrd_ = new QTcpSocket{this}; // QObject takes ownership
|
|
}
|
|
hrd_->connectToHost(std::get<0>(server_details),
|
|
std::get<1>(server_details));
|
|
if (!hrd_->waitForConnected()) {
|
|
TRACE_CAT("HRDTransceiver",
|
|
"failed to connect:" << hrd_->errorString());
|
|
throw error{tr("Failed to connect to Ham Radio Deluxe\n") +
|
|
hrd_->errorString()};
|
|
}
|
|
|
|
if (none == protocol_) {
|
|
try {
|
|
protocol_ = v5; // try this first (works for v6 too)
|
|
send_command("get context", false, false);
|
|
} catch (error const &) {
|
|
protocol_ = none;
|
|
}
|
|
}
|
|
|
|
if (none == protocol_) {
|
|
hrd_->close();
|
|
|
|
protocol_ = v4; // try again with older protocol
|
|
hrd_->connectToHost(std::get<0>(server_details),
|
|
std::get<1>(server_details));
|
|
if (!hrd_->waitForConnected()) {
|
|
TRACE_CAT("HRDTransceiver",
|
|
"failed to connect:" << hrd_->errorString());
|
|
throw error{tr("Failed to connect to Ham Radio Deluxe\n") +
|
|
hrd_->errorString()};
|
|
}
|
|
|
|
send_command("get context", false, false);
|
|
}
|
|
|
|
QFile HRD_info_file{QDir{
|
|
QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)}
|
|
.absoluteFilePath("HRD Interface Information.txt")};
|
|
if (!HRD_info_file.open(QFile::WriteOnly | QFile::Text | QFile::Truncate)) {
|
|
throw error{tr("Failed to open file \"%1\": %2.")
|
|
.arg(HRD_info_file.fileName())
|
|
.arg(HRD_info_file.errorString())};
|
|
}
|
|
QTextStream HRD_info{&HRD_info_file};
|
|
|
|
auto id = send_command("get id", false, false);
|
|
auto version = send_command("get version", false, false);
|
|
|
|
TRACE_CAT("HRDTransceiver", "Id:" << id << "Version:" << version);
|
|
HRD_info << "Id: " << id << "\n";
|
|
HRD_info << "Version: " << version << "\n";
|
|
|
|
auto radios = send_command("get radios", false, false)
|
|
.trimmed()
|
|
.split(',', Qt::SkipEmptyParts);
|
|
if (radios.isEmpty()) {
|
|
TRACE_CAT("HRDTransceiver", "no rig found");
|
|
throw error{tr("Ham Radio Deluxe: no rig found")};
|
|
}
|
|
|
|
HRD_info << "Radios:\n";
|
|
for (const auto &radio_str : radios) {
|
|
HRD_info << "\t" << radio_str << "\n";
|
|
auto entries = radio_str.trimmed().split(':', Qt::SkipEmptyParts);
|
|
radios_.push_back(
|
|
std::forward_as_tuple(entries[0].toUInt(), entries[1]));
|
|
}
|
|
|
|
TRACE_CAT("HRDTransceiver", "radios:-");
|
|
for (const auto &radio : radios_) {
|
|
TRACE_CAT("HRDTransceiver",
|
|
"\t[" << std::get<0>(radio) << "] " << std::get<1>(radio));
|
|
}
|
|
|
|
auto current_radio_name = send_command("get radio", false, false, true);
|
|
HRD_info << "Current radio: " << current_radio_name << "\n";
|
|
if (current_radio_name.isEmpty()) {
|
|
TRACE_CAT("HRDTransceiver", "no rig found");
|
|
throw error{tr("Ham Radio Deluxe: no rig found")};
|
|
}
|
|
|
|
vfo_count_ = send_command("get vfo-count").toUInt();
|
|
HRD_info << "VFO count: " << vfo_count_ << "\n";
|
|
TRACE_CAT("HRDTransceiver", "vfo count:" << vfo_count_);
|
|
|
|
buttons_ = send_command("get buttons")
|
|
.trimmed()
|
|
.split(',', Qt::SkipEmptyParts)
|
|
.replaceInStrings(" ", "~");
|
|
TRACE_CAT("HRDTransceiver", "HRD Buttons: " << buttons_);
|
|
HRD_info << "Buttons: {" << buttons_.join(", ") << "}\n";
|
|
|
|
dropdown_names_ =
|
|
send_command("get dropdowns").trimmed().split(',', Qt::SkipEmptyParts);
|
|
TRACE_CAT("HRDTransceiver", "Dropdowns:");
|
|
HRD_info << "Dropdowns:\n";
|
|
for (auto const &dd : std::as_const(dropdown_names_)) {
|
|
auto selections =
|
|
send_command("get dropdown-list {" + dd + "}").trimmed().split(',');
|
|
TRACE_CAT("HRDTransceiver",
|
|
"\t" << dd << ": {" << selections.join(", ") << "}");
|
|
HRD_info << "\t" << dd << ": {" << selections.join(", ") << "}\n";
|
|
dropdowns_[dd] = selections;
|
|
}
|
|
|
|
slider_names_ = send_command("get sliders")
|
|
.trimmed()
|
|
.split(',', Qt::SkipEmptyParts)
|
|
.replaceInStrings(" ", "~");
|
|
TRACE_CAT("HRDTransceiver", "Sliders:-");
|
|
HRD_info << "Sliders:\n";
|
|
for (auto const &s : std::as_const(slider_names_)) {
|
|
auto range =
|
|
send_command("get slider-range " + current_radio_name + " " + s)
|
|
.trimmed()
|
|
.split(',', Qt::SkipEmptyParts);
|
|
TRACE_CAT("HRDTransceiver",
|
|
"\t" << s << ": {" << range.join(", ") << "}");
|
|
HRD_info << "\t" << s << ": {" << range.join(", ") << "}\n";
|
|
sliders_[s] = range;
|
|
}
|
|
|
|
// set RX VFO
|
|
rx_A_button_ = find_button(QRegularExpression("^(RX~A)$"));
|
|
rx_B_button_ = find_button(QRegularExpression("^(RX~B)$"));
|
|
|
|
// select VFO (sometime set as well)
|
|
vfo_A_button_ = find_button(QRegularExpression("^(VFO~A|Main)$"));
|
|
vfo_B_button_ = find_button(QRegularExpression("^(VFO~B|Sub)$"));
|
|
|
|
vfo_toggle_button_ = find_button(QRegularExpression("^(A~/~B)$"));
|
|
|
|
split_mode_button_ =
|
|
find_button(QRegularExpression("^(Spl~On|Spl_On|Split|Split~On)$"));
|
|
split_off_button_ =
|
|
find_button(QRegularExpression("^(Spl~Off|Spl_Off|Split~Off)$"));
|
|
|
|
if ((split_mode_dropdown_ =
|
|
find_dropdown(QRegularExpression("^(Split)$"))) >= 0) {
|
|
split_mode_dropdown_selection_on_ = find_dropdown_selection(
|
|
split_mode_dropdown_, QRegularExpression("^(On)$"));
|
|
split_mode_dropdown_selection_off_ = find_dropdown_selection(
|
|
split_mode_dropdown_, QRegularExpression("^(Off)$"));
|
|
} else if ((receiver_dropdown_ =
|
|
find_dropdown(QRegularExpression("^Receiver$"))) >= 0) {
|
|
rx_A_selection_ = find_dropdown_selection(
|
|
receiver_dropdown_, QRegularExpression("^(RX / Off)$"));
|
|
rx_B_selection_ = find_dropdown_selection(
|
|
receiver_dropdown_, QRegularExpression("^(Mute / RX)$"));
|
|
}
|
|
|
|
tx_A_button_ = find_button(QRegularExpression("^(TX~main|TX~-~A|TX~A)$"));
|
|
tx_B_button_ = find_button(QRegularExpression("^(TX~sub|TX~-~B|TX~B)$"));
|
|
|
|
if ((mode_A_dropdown_ = find_dropdown(
|
|
QRegularExpression("^(Main Mode|Mode|Mode A)$"))) >= 0) {
|
|
map_modes(mode_A_dropdown_, &mode_A_map_);
|
|
} else {
|
|
Q_ASSERT(mode_A_dropdown_ <= 0);
|
|
}
|
|
|
|
if ((mode_B_dropdown_ =
|
|
find_dropdown(QRegularExpression("^(Sub Mode|Mode B)$"))) >= 0) {
|
|
map_modes(mode_B_dropdown_, &mode_B_map_);
|
|
}
|
|
|
|
// Can't do this with ^Data$ as the button name because some Kenwood
|
|
// rigs have a "Data" button which is for turning the DSP on and off
|
|
// so we must filter by rig name as well
|
|
if (current_radio_name.startsWith("IC")) // works with Icom transceivers
|
|
{
|
|
data_mode_toggle_button_ = find_button(QRegularExpression("^(Data)$"));
|
|
}
|
|
|
|
data_mode_on_button_ =
|
|
find_button(QRegularExpression("^(DATA-ON\\(mid\\))$"));
|
|
data_mode_off_button_ = find_button(QRegularExpression("^(DATA-OFF)$"));
|
|
|
|
// Some newer Icoms have a Data drop down with (Off,On,D1,D2,D3)
|
|
// Some newer Icoms have a Data drop down with (Off,D1,D2,D3)
|
|
// Some newer Icoms (IC-7300) have a Data drop down with
|
|
// (Off,,D1-FIL1,D1-FIL2,D1-FIL3) the missing value counts as an
|
|
// index value - I think it is a drop down separator line - this
|
|
// appears to be an HRD defect and we cannot work around it
|
|
if ((data_mode_dropdown_ = find_dropdown(QRegularExpression("^(Data)$"))) >=
|
|
0) {
|
|
data_mode_dropdown_selection_on_ = find_dropdown_selection(
|
|
data_mode_dropdown_, QRegularExpression("^(On|Data1|D1|D1-FIL1)$"));
|
|
data_mode_dropdown_selection_off_ = find_dropdown_selection(
|
|
data_mode_dropdown_, QRegularExpression("^(Off)$"));
|
|
}
|
|
|
|
ptt_button_ = find_button(QRegularExpression("^(TX)$"));
|
|
alt_ptt_button_ = find_button(QRegularExpression("^(TX~Alt|TX~Data)$"));
|
|
|
|
if (vfo_count_ == 1 && ((vfo_B_button_ >= 0 && vfo_A_button_ >= 0) ||
|
|
vfo_toggle_button_ >= 0)) {
|
|
// put the rig into a known state for tricky cases like Icoms
|
|
|
|
auto f = send_command("get frequency").toUInt();
|
|
auto m = get_data_mode(
|
|
lookup_mode(get_dropdown(mode_A_dropdown_), mode_A_map_));
|
|
set_button(vfo_B_button_ >= 0 ? vfo_B_button_ : vfo_toggle_button_);
|
|
auto fo = send_command("get frequency").toUInt();
|
|
update_other_frequency(fo);
|
|
auto mo = get_data_mode(
|
|
lookup_mode(get_dropdown(mode_A_dropdown_), mode_A_map_));
|
|
set_button(vfo_A_button_ >= 0 ? vfo_A_button_ : vfo_toggle_button_);
|
|
if (f != fo || m != mo) {
|
|
// we must have started with A/MAIN
|
|
update_rx_frequency(f);
|
|
update_mode(m);
|
|
} else {
|
|
update_rx_frequency(send_command("get frequency").toUInt());
|
|
update_mode(get_data_mode(
|
|
lookup_mode(get_dropdown(mode_A_dropdown_), mode_A_map_)));
|
|
}
|
|
}
|
|
|
|
int resolution{0};
|
|
auto f = send_command("get frequency").toUInt();
|
|
if (f && !(f % 10)) {
|
|
auto test_frequency = f - f % 100 + 55;
|
|
send_simple_command("set frequency-hz " +
|
|
QString::number(test_frequency));
|
|
auto new_frequency = send_command("get frequency").toUInt();
|
|
switch (static_cast<Radio::FrequencyDelta>(new_frequency -
|
|
test_frequency)) {
|
|
case -5:
|
|
resolution = -1;
|
|
break; // 10Hz truncated
|
|
case 5:
|
|
resolution = 1;
|
|
break; // 10Hz rounded
|
|
case -15:
|
|
resolution = -2;
|
|
break; // 20Hz truncated
|
|
case -55:
|
|
resolution = -2;
|
|
break; // 100Hz truncated
|
|
case 45:
|
|
resolution = 2;
|
|
break; // 100Hz rounded
|
|
}
|
|
if (1 == resolution) // may be 20Hz rounded
|
|
{
|
|
test_frequency = f - f % 100 + 51;
|
|
send_simple_command("set frequency-hz " +
|
|
QString::number(test_frequency));
|
|
new_frequency = send_command("get frequency").toUInt();
|
|
if (9 == static_cast<Radio::FrequencyDelta>(new_frequency -
|
|
test_frequency)) {
|
|
resolution = 2; // 20Hz rounded
|
|
}
|
|
}
|
|
send_simple_command("set frequency-hz " + QString::number(f));
|
|
}
|
|
return resolution;
|
|
}
|
|
|
|
void HRDTransceiver::do_stop() {
|
|
if (hrd_) {
|
|
hrd_->close();
|
|
}
|
|
|
|
if (wrapped_)
|
|
wrapped_->stop();
|
|
TRACE_CAT("HRDTransceiver",
|
|
"stopped" << state() << "reversed" << reversed_);
|
|
}
|
|
|
|
int HRDTransceiver::find_button(QRegularExpression const &re) const {
|
|
return buttons_.indexOf(re);
|
|
}
|
|
|
|
int HRDTransceiver::find_dropdown(QRegularExpression const &re) const {
|
|
return dropdown_names_.indexOf(re);
|
|
}
|
|
|
|
std::vector<int>
|
|
HRDTransceiver::find_dropdown_selection(int dropdown,
|
|
QRegularExpression const &re) const {
|
|
std::vector<int> indices; // this will always contain at least a
|
|
// -1
|
|
auto list = dropdowns_.value(dropdown_names_.value(dropdown));
|
|
int index{0};
|
|
while (-1 != (index = list.lastIndexOf(re, index - 1))) {
|
|
// search backwards because more specialized modes tend to be
|
|
// later in list
|
|
indices.push_back(index);
|
|
if (!index) {
|
|
break;
|
|
}
|
|
}
|
|
return indices;
|
|
}
|
|
|
|
void HRDTransceiver::map_modes(int dropdown, ModeMap *map) {
|
|
// order matters here (both in the map and in the regexps)
|
|
map->push_back(std::forward_as_tuple(
|
|
CW, find_dropdown_selection(
|
|
dropdown, QRegularExpression("^(CW|CW\\(N\\))|CWL$"))));
|
|
map->push_back(std::forward_as_tuple(
|
|
CW_R,
|
|
find_dropdown_selection(
|
|
dropdown, QRegularExpression("^(CW-R|CW-R\\(N\\)|CW|CWU)$"))));
|
|
map->push_back(std::forward_as_tuple(
|
|
LSB, find_dropdown_selection(
|
|
dropdown, QRegularExpression("^(LSB\\(N\\)|LSB)$"))));
|
|
map->push_back(std::forward_as_tuple(
|
|
USB, find_dropdown_selection(
|
|
dropdown, QRegularExpression("^(USB\\(N\\)|USB)$"))));
|
|
map->push_back(std::forward_as_tuple(
|
|
DIG_U,
|
|
find_dropdown_selection(
|
|
dropdown,
|
|
QRegularExpression("^(DIG|DIGU|DATA-U|PKT-U|DATA|USER-U|USB)$"))));
|
|
map->push_back(std::forward_as_tuple(
|
|
DIG_L,
|
|
find_dropdown_selection(
|
|
dropdown, QRegularExpression(
|
|
"^(DIG|DIGL|DATA-L|PKT-L|DATA-R|USER-L|LSB)$"))));
|
|
map->push_back(std::forward_as_tuple(
|
|
FSK, find_dropdown_selection(
|
|
dropdown, QRegularExpression("^(DIG|FSK|RTTY|RTTY-LSB)$"))));
|
|
map->push_back(std::forward_as_tuple(
|
|
FSK_R, find_dropdown_selection(
|
|
dropdown,
|
|
QRegularExpression("^(DIG|FSK-R|RTTY-R|RTTY|RTTY-USB)$"))));
|
|
map->push_back(std::forward_as_tuple(
|
|
AM, find_dropdown_selection(dropdown,
|
|
QRegularExpression("^(AM|DSB|SAM|DRM)$"))));
|
|
map->push_back(std::forward_as_tuple(
|
|
FM, find_dropdown_selection(
|
|
dropdown, QRegularExpression("^(FM|FM\\(N\\)|FM-N|WFM)$"))));
|
|
map->push_back(std::forward_as_tuple(
|
|
DIG_FM, find_dropdown_selection(
|
|
dropdown, QRegularExpression("^(PKT-FM|PKT|FM)$"))));
|
|
|
|
TRACE_CAT("HRDTransceiver", "for dropdown" << dropdown_names_[dropdown]);
|
|
std::for_each(
|
|
map->begin(), map->end(),
|
|
[this, dropdown](ModeMap::value_type const &item) {
|
|
auto const &rhs = std::get<1>(item);
|
|
TRACE_CAT(
|
|
"HRDTransceiver",
|
|
'\t'
|
|
<< std::get<0>(item) << "<->"
|
|
<< (rhs.size()
|
|
? dropdowns_[dropdown_names_[dropdown]][rhs.front()]
|
|
: "None"));
|
|
});
|
|
}
|
|
|
|
int HRDTransceiver::lookup_mode(MODE mode, ModeMap const &map) const {
|
|
auto it = std::find_if(map.begin(), map.end(),
|
|
[mode](ModeMap::value_type const &item) {
|
|
return std::get<0>(item) == mode;
|
|
});
|
|
if (map.end() == it) {
|
|
throw error{tr("Ham Radio Deluxe: rig doesn't support mode")};
|
|
}
|
|
return std::get<1>(*it).front();
|
|
}
|
|
|
|
auto HRDTransceiver::lookup_mode(int mode, ModeMap const &map) const -> MODE {
|
|
if (mode < 0) {
|
|
return UNK; // no mode dropdown
|
|
}
|
|
|
|
auto it = std::find_if(
|
|
map.begin(), map.end(), [mode](ModeMap::value_type const &item) {
|
|
auto const &indices = std::get<1>(item);
|
|
return indices.cend() !=
|
|
std::find(indices.cbegin(), indices.cend(), mode);
|
|
});
|
|
if (map.end() == it) {
|
|
throw error{tr("Ham Radio Deluxe: sent an unrecognised mode")};
|
|
}
|
|
return std::get<0>(*it);
|
|
}
|
|
|
|
int HRDTransceiver::get_dropdown(int dd, bool no_debug) {
|
|
if (dd < 0) {
|
|
return -1; // no dropdown to interrogate
|
|
}
|
|
|
|
auto dd_name = dropdown_names_.value(dd);
|
|
auto reply = send_command("get dropdown-text {" + dd_name + "}", no_debug);
|
|
auto colon_index = reply.indexOf(':');
|
|
|
|
if (colon_index < 0) {
|
|
return -1;
|
|
}
|
|
|
|
Q_ASSERT(reply.left(colon_index).trimmed() == dd_name);
|
|
return dropdowns_.value(dropdown_names_.value(dd))
|
|
.indexOf(reply.mid(colon_index + 1).trimmed());
|
|
}
|
|
|
|
void HRDTransceiver::set_dropdown(int dd, int value) {
|
|
auto dd_name = dropdown_names_.value(dd);
|
|
if (value >= 0) {
|
|
send_simple_command(
|
|
"set dropdown " + dd_name.replace(' ', '~') + ' ' +
|
|
dropdowns_.value(dd_name).value(value).replace(' ', '~') + ' ' +
|
|
QString::number(value));
|
|
} else {
|
|
TRACE_CAT("HRDTransceiver",
|
|
"item" << value << "not found in" << dd_name);
|
|
throw error{tr("Ham Radio Deluxe: item not found in %1 dropdown list")
|
|
.arg(dd_name)};
|
|
}
|
|
}
|
|
|
|
void HRDTransceiver::do_ptt(bool on) {
|
|
TRACE_CAT("HRDTransceiver", on);
|
|
if (use_for_ptt_) {
|
|
if (alt_ptt_button_ >= 0 &&
|
|
TransceiverFactory::TX_audio_source_rear == audio_source_) {
|
|
set_button(alt_ptt_button_, on);
|
|
} else if (ptt_button_ >= 0) {
|
|
set_button(ptt_button_, on);
|
|
}
|
|
// else
|
|
// allow for pathological HRD rig interfaces that don't do
|
|
// PTT by simply not even trying
|
|
} else {
|
|
Q_ASSERT(wrapped_);
|
|
TransceiverState new_state{wrapped_->state()};
|
|
new_state.ptt(on);
|
|
wrapped_->set(new_state, 0);
|
|
}
|
|
update_PTT(on);
|
|
}
|
|
|
|
void HRDTransceiver::set_button(int button_index, bool checked) {
|
|
if (button_index >= 0) {
|
|
send_simple_command("set button-select " +
|
|
buttons_.value(button_index) +
|
|
(checked ? " 1" : " 0"));
|
|
if (button_index == rx_A_button_ || button_index == rx_B_button_) {
|
|
QThread::msleep(yaesu_delay);
|
|
}
|
|
} else {
|
|
TRACE_CAT("HRDTransceiver", "invalid button");
|
|
throw error{tr("Ham Radio Deluxe: button not available")};
|
|
}
|
|
}
|
|
|
|
void HRDTransceiver::set_data_mode(MODE m) {
|
|
if (data_mode_toggle_button_ >= 0) {
|
|
switch (m) {
|
|
case DIG_U:
|
|
case DIG_L:
|
|
case DIG_FM:
|
|
set_button(data_mode_toggle_button_, true);
|
|
break;
|
|
default:
|
|
set_button(data_mode_toggle_button_, false);
|
|
break;
|
|
}
|
|
} else if (data_mode_on_button_ >= 0 && data_mode_off_button_ >= 0) {
|
|
switch (m) {
|
|
case DIG_U:
|
|
case DIG_L:
|
|
case DIG_FM:
|
|
set_button(data_mode_on_button_, true);
|
|
break;
|
|
default:
|
|
set_button(data_mode_off_button_, true);
|
|
break;
|
|
}
|
|
} else if (data_mode_dropdown_ >= 0 &&
|
|
data_mode_dropdown_selection_off_.size() &&
|
|
data_mode_dropdown_selection_on_.size()) {
|
|
switch (m) {
|
|
case DIG_U:
|
|
case DIG_L:
|
|
case DIG_FM:
|
|
set_dropdown(data_mode_dropdown_,
|
|
data_mode_dropdown_selection_on_.front());
|
|
break;
|
|
default:
|
|
set_dropdown(data_mode_dropdown_,
|
|
data_mode_dropdown_selection_off_.front());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
auto HRDTransceiver::get_data_mode(MODE m, bool quiet) -> MODE {
|
|
if (data_mode_dropdown_ >= 0 && data_mode_dropdown_selection_off_.size()) {
|
|
auto selection = get_dropdown(data_mode_dropdown_, quiet);
|
|
// can't check for on here as there may be multiple on values so
|
|
// we must rely on the initial parse finding valid on values
|
|
if (selection >= 0 &&
|
|
selection != data_mode_dropdown_selection_off_.front()) {
|
|
switch (m) {
|
|
case USB:
|
|
m = DIG_U;
|
|
break;
|
|
case LSB:
|
|
m = DIG_L;
|
|
break;
|
|
case FM:
|
|
m = DIG_FM;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return m;
|
|
}
|
|
|
|
void HRDTransceiver::do_frequency(Frequency f, MODE m, bool /*no_ignore*/) {
|
|
TRACE_CAT("HRDTransceiver", f << "reversed" << reversed_);
|
|
if (UNK != m) {
|
|
do_mode(m);
|
|
}
|
|
auto fo_string = QString::number(f);
|
|
if (vfo_count_ > 1 && reversed_) {
|
|
auto frequencies = send_command("get frequencies")
|
|
.trimmed()
|
|
.split('-', Qt::SkipEmptyParts);
|
|
send_simple_command("set frequencies-hz " +
|
|
QString::number(frequencies[0].toUInt()) + ' ' +
|
|
fo_string);
|
|
} else {
|
|
send_simple_command("set frequency-hz " + QString::number(f));
|
|
}
|
|
update_rx_frequency(f);
|
|
}
|
|
|
|
void HRDTransceiver::do_tx_frequency(Frequency tx, MODE mode,
|
|
bool /*no_ignore*/) {
|
|
TRACE_CAT("HRDTransceiver", tx << "reversed" << reversed_);
|
|
|
|
// re-check if reversed VFOs
|
|
bool rx_A{true};
|
|
bool rx_B{false};
|
|
if (receiver_dropdown_ >= 0 && rx_A_selection_.size()) {
|
|
auto selection = get_dropdown(receiver_dropdown_);
|
|
rx_A = selection == rx_A_selection_.front();
|
|
if (!rx_A && rx_B_selection_.size()) {
|
|
rx_B = selection == rx_B_selection_.front();
|
|
}
|
|
} else if (vfo_B_button_ >= 0 || rx_B_button_ >= 0) {
|
|
rx_A =
|
|
is_button_checked(rx_A_button_ >= 0 ? rx_A_button_ : vfo_A_button_);
|
|
if (!rx_A) {
|
|
rx_B = is_button_checked(rx_B_button_ >= 0 ? rx_B_button_
|
|
: vfo_B_button_);
|
|
}
|
|
}
|
|
reversed_ = rx_B;
|
|
|
|
bool split{tx != 0};
|
|
if (split) {
|
|
if (mode != UNK) {
|
|
if (!reversed_ && mode_B_dropdown_ >= 0) {
|
|
set_dropdown(mode_B_dropdown_, lookup_mode(mode, mode_B_map_));
|
|
} else if (reversed_ && mode_B_dropdown_ >= 0) {
|
|
set_dropdown(mode_A_dropdown_, lookup_mode(mode, mode_A_map_));
|
|
} else {
|
|
Q_ASSERT(mode_A_dropdown_ >= 0 &&
|
|
((vfo_A_button_ >= 0 && vfo_B_button_ >= 0) ||
|
|
vfo_toggle_button_ >= 0));
|
|
|
|
if (rx_B_button_ >= 0) {
|
|
set_button(reversed_ ? rx_A_button_ : rx_B_button_);
|
|
set_dropdown(mode_A_dropdown_,
|
|
lookup_mode(mode, mode_A_map_));
|
|
set_data_mode(mode);
|
|
set_button(reversed_ ? rx_B_button_ : rx_A_button_);
|
|
} else if (receiver_dropdown_ >= 0 && rx_A_selection_.size() &&
|
|
rx_B_selection_.size()) {
|
|
set_dropdown(receiver_dropdown_,
|
|
(reversed_ ? rx_A_selection_ : rx_B_selection_)
|
|
.front());
|
|
set_dropdown(mode_A_dropdown_,
|
|
lookup_mode(mode, mode_A_map_));
|
|
set_data_mode(mode);
|
|
set_dropdown(receiver_dropdown_,
|
|
(reversed_ ? rx_B_selection_ : rx_A_selection_)
|
|
.front());
|
|
} else if (vfo_count_ > 1 &&
|
|
((vfo_A_button_ >= 0 && vfo_B_button_ >= 0) ||
|
|
vfo_toggle_button_ >= 0)) {
|
|
set_button(vfo_A_button_ >= 0
|
|
? (reversed_ ? vfo_A_button_ : vfo_B_button_)
|
|
: vfo_toggle_button_);
|
|
set_dropdown(mode_A_dropdown_,
|
|
lookup_mode(mode, mode_A_map_));
|
|
set_data_mode(mode);
|
|
set_button(vfo_A_button_ >= 0
|
|
? (reversed_ ? vfo_B_button_ : vfo_A_button_)
|
|
: vfo_toggle_button_);
|
|
}
|
|
// else Tx VFO mode gets set with frequency below or we
|
|
// don't have a way of setting it so we assume it is
|
|
// always the same as the Rx VFO mode
|
|
}
|
|
}
|
|
|
|
auto fo_string = QString::number(tx);
|
|
if (reversed_) {
|
|
Q_ASSERT(vfo_count_ > 1);
|
|
|
|
auto frequencies = send_command("get frequencies")
|
|
.trimmed()
|
|
.split('-', Qt::SkipEmptyParts);
|
|
send_simple_command("set frequencies-hz " + fo_string + ' ' +
|
|
QString::number(frequencies[1].toUInt()));
|
|
} else {
|
|
if (vfo_count_ > 1) {
|
|
auto frequencies = send_command("get frequencies")
|
|
.trimmed()
|
|
.split('-', Qt::SkipEmptyParts);
|
|
send_simple_command("set frequencies-hz " +
|
|
QString::number(frequencies[0].toUInt()) +
|
|
' ' + fo_string);
|
|
} else if ((vfo_B_button_ >= 0 && vfo_A_button_ >= 0) ||
|
|
vfo_toggle_button_ >= 0) {
|
|
// we rationalise the modes here as well as the frequencies
|
|
set_button(vfo_B_button_ >= 0 ? vfo_B_button_
|
|
: vfo_toggle_button_);
|
|
send_simple_command("set frequency-hz " + fo_string);
|
|
if (mode != UNK && mode_B_dropdown_ < 0) {
|
|
// do this here rather than later so we only
|
|
// toggle/switch VFOs once
|
|
set_dropdown(mode_A_dropdown_,
|
|
lookup_mode(mode, mode_A_map_));
|
|
set_data_mode(mode);
|
|
}
|
|
set_button(vfo_A_button_ >= 0 ? vfo_A_button_
|
|
: vfo_toggle_button_);
|
|
}
|
|
}
|
|
}
|
|
update_other_frequency(tx);
|
|
|
|
if (split_mode_button_ >= 0) {
|
|
if (split_off_button_ >= 0 && !split) {
|
|
set_button(split_off_button_);
|
|
} else {
|
|
set_button(split_mode_button_, split);
|
|
}
|
|
} else if (split_mode_dropdown_ >= 0 &&
|
|
split_mode_dropdown_selection_off_.size() &&
|
|
split_mode_dropdown_selection_on_.size()) {
|
|
set_dropdown(split_mode_dropdown_,
|
|
split ? split_mode_dropdown_selection_on_.front()
|
|
: split_mode_dropdown_selection_off_.front());
|
|
} else if (vfo_A_button_ >= 0 && vfo_B_button_ >= 0 && tx_A_button_ >= 0 &&
|
|
tx_B_button_ >= 0) {
|
|
if (split) {
|
|
if (reversed_ != is_button_checked(tx_A_button_)) {
|
|
if (rx_A_button_ >= 0 && rx_B_button_ >= 0) {
|
|
set_button(reversed_ ? rx_B_button_ : rx_A_button_);
|
|
} else if (receiver_dropdown_ >= 0 && rx_A_selection_.size() &&
|
|
rx_B_selection_.size()) {
|
|
set_dropdown(receiver_dropdown_,
|
|
(reversed_ ? rx_B_selection_ : rx_A_selection_)
|
|
.front());
|
|
} else {
|
|
set_button(reversed_ ? vfo_B_button_ : vfo_A_button_);
|
|
}
|
|
set_button(reversed_ ? tx_A_button_ : tx_B_button_);
|
|
}
|
|
} else {
|
|
if (reversed_ != is_button_checked(tx_B_button_)) {
|
|
if (rx_A_button_ >= 0 && rx_B_button_ >= 0) {
|
|
set_button(reversed_ ? rx_B_button_ : rx_A_button_);
|
|
} else if (receiver_dropdown_ >= 0 && rx_A_selection_.size() &&
|
|
rx_B_selection_.size()) {
|
|
set_dropdown(receiver_dropdown_,
|
|
(reversed_ ? rx_B_selection_ : rx_A_selection_)
|
|
.front());
|
|
} else {
|
|
set_button(reversed_ ? vfo_B_button_ : vfo_A_button_);
|
|
}
|
|
set_button(reversed_ ? tx_B_button_ : tx_A_button_);
|
|
}
|
|
}
|
|
}
|
|
update_split(split);
|
|
}
|
|
|
|
void HRDTransceiver::do_mode(MODE mode) {
|
|
TRACE_CAT("HRDTransceiver", mode);
|
|
if (reversed_ && mode_B_dropdown_ >= 0) {
|
|
set_dropdown(mode_B_dropdown_, lookup_mode(mode, mode_B_map_));
|
|
} else {
|
|
set_dropdown(mode_A_dropdown_, lookup_mode(mode, mode_A_map_));
|
|
}
|
|
if (mode != UNK && state().split()) // rationalise mode if split
|
|
{
|
|
if (reversed_) {
|
|
if (mode_B_dropdown_ >= 0) {
|
|
set_dropdown(mode_A_dropdown_, lookup_mode(mode, mode_A_map_));
|
|
} else {
|
|
Q_ASSERT((vfo_B_button_ >= 0 && vfo_A_button_ >= 0) ||
|
|
vfo_toggle_button_ >= 0);
|
|
|
|
if (rx_B_button_ >= 0) {
|
|
set_button(rx_A_button_);
|
|
set_dropdown(mode_A_dropdown_,
|
|
lookup_mode(mode, mode_A_map_));
|
|
set_button(rx_B_button_);
|
|
} else if (receiver_dropdown_ >= 0 && rx_A_selection_.size() &&
|
|
rx_B_selection_.size()) {
|
|
set_dropdown(receiver_dropdown_, rx_A_selection_.front());
|
|
set_dropdown(mode_A_dropdown_,
|
|
lookup_mode(mode, mode_A_map_));
|
|
set_dropdown(receiver_dropdown_, rx_B_selection_.front());
|
|
} else if (vfo_count_ > 1 &&
|
|
((vfo_A_button_ >= 0 && vfo_B_button_ >= 0) ||
|
|
vfo_toggle_button_ >= 0)) {
|
|
set_button(vfo_A_button_ >= 0 ? vfo_A_button_
|
|
: vfo_toggle_button_);
|
|
set_dropdown(mode_A_dropdown_,
|
|
lookup_mode(mode, mode_A_map_));
|
|
set_button(vfo_B_button_ >= 0 ? vfo_B_button_
|
|
: vfo_toggle_button_);
|
|
}
|
|
// else Tx VFO mode gets set when Tx VFO frequency is
|
|
// set
|
|
|
|
if (tx_A_button_ >= 0) {
|
|
set_button(tx_A_button_);
|
|
}
|
|
}
|
|
} else {
|
|
if (mode_B_dropdown_ >= 0) {
|
|
set_dropdown(mode_B_dropdown_, lookup_mode(mode, mode_B_map_));
|
|
} else {
|
|
Q_ASSERT((vfo_B_button_ >= 0 && vfo_A_button_ >= 0) ||
|
|
vfo_toggle_button_ >= 0);
|
|
|
|
if (rx_B_button_ >= 0) {
|
|
set_button(rx_B_button_);
|
|
set_dropdown(mode_A_dropdown_,
|
|
lookup_mode(mode, mode_A_map_));
|
|
set_button(rx_A_button_);
|
|
} else if (receiver_dropdown_ >= 0 && rx_A_selection_.size() &&
|
|
rx_B_selection_.size()) {
|
|
set_dropdown(receiver_dropdown_, rx_B_selection_.front());
|
|
set_dropdown(mode_A_dropdown_,
|
|
lookup_mode(mode, mode_A_map_));
|
|
set_dropdown(receiver_dropdown_, rx_A_selection_.front());
|
|
} else if (vfo_count_ > 1 &&
|
|
((vfo_A_button_ >= 0 && vfo_B_button_ >= 0) ||
|
|
vfo_toggle_button_ >= 0)) {
|
|
set_button(vfo_B_button_ >= 0 ? vfo_B_button_
|
|
: vfo_toggle_button_);
|
|
set_dropdown(mode_A_dropdown_,
|
|
lookup_mode(mode, mode_A_map_));
|
|
set_button(vfo_A_button_ >= 0 ? vfo_A_button_
|
|
: vfo_toggle_button_);
|
|
}
|
|
// else Tx VFO mode gets set when Tx VFO frequency is
|
|
// set
|
|
|
|
if (tx_B_button_ >= 0) {
|
|
set_button(tx_B_button_);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
set_data_mode(mode);
|
|
update_mode(mode);
|
|
}
|
|
|
|
bool HRDTransceiver::is_button_checked(int button_index, bool no_debug) {
|
|
if (button_index < 0) {
|
|
return false;
|
|
}
|
|
|
|
auto reply = send_command(
|
|
"get button-select " + buttons_.value(button_index), no_debug);
|
|
if ("1" != reply && "0" != reply) {
|
|
TRACE_CAT("HRDTransceiver", "bad response");
|
|
throw error{tr("Ham Radio Deluxe didn't respond as expected")};
|
|
}
|
|
return "1" == reply;
|
|
}
|
|
|
|
void HRDTransceiver::poll() {
|
|
bool quiet{hrdtransceiver_js8().isDebugEnabled()};
|
|
if (!quiet) {
|
|
qCDebug(hrdtransceiver_js8) << "+++++++ poll dump +++++++";
|
|
qCDebug(hrdtransceiver_js8) << "reversed:" << reversed_;
|
|
is_button_checked(vfo_A_button_);
|
|
is_button_checked(vfo_B_button_);
|
|
is_button_checked(vfo_toggle_button_);
|
|
is_button_checked(split_mode_button_);
|
|
is_button_checked(split_off_button_);
|
|
is_button_checked(rx_A_button_);
|
|
is_button_checked(rx_B_button_);
|
|
get_dropdown(receiver_dropdown_);
|
|
is_button_checked(tx_A_button_);
|
|
is_button_checked(tx_B_button_);
|
|
is_button_checked(ptt_button_);
|
|
is_button_checked(alt_ptt_button_);
|
|
get_dropdown(mode_A_dropdown_);
|
|
get_dropdown(mode_B_dropdown_);
|
|
is_button_checked(data_mode_toggle_button_);
|
|
is_button_checked(data_mode_on_button_);
|
|
is_button_checked(data_mode_off_button_);
|
|
if (data_mode_dropdown_ >= 0 &&
|
|
data_mode_dropdown_selection_off_.size() &&
|
|
data_mode_dropdown_selection_on_.size()) {
|
|
get_dropdown(data_mode_dropdown_);
|
|
}
|
|
if (!split_mode_dropdown_write_only_) {
|
|
get_dropdown(split_mode_dropdown_);
|
|
}
|
|
qCDebug(hrdtransceiver_js8) << "------- poll dump -------";
|
|
}
|
|
|
|
if (split_off_button_ >= 0) {
|
|
// we are probably dealing with an Icom and have to guess SPLIT mode :(
|
|
} else if (split_mode_button_ >= 0 &&
|
|
!(tx_A_button_ >= 0 && tx_B_button_ >= 0)) {
|
|
update_split(is_button_checked(split_mode_button_, quiet));
|
|
} else if (split_mode_dropdown_ >= 0) {
|
|
if (!split_mode_dropdown_write_only_) {
|
|
auto selection = get_dropdown(split_mode_dropdown_, quiet);
|
|
if (selection >= 0 && split_mode_dropdown_selection_off_.size()) {
|
|
update_split(selection ==
|
|
split_mode_dropdown_selection_on_.front());
|
|
} else {
|
|
// leave split alone as we can't query it - it should be
|
|
// correct so long as rig or HRD haven't been changed
|
|
split_mode_dropdown_write_only_ = true;
|
|
}
|
|
}
|
|
} else if (vfo_A_button_ >= 0 && vfo_B_button_ >= 0 && tx_A_button_ >= 0 &&
|
|
tx_B_button_ >= 0) {
|
|
bool rx_A{true}; // no Rx taken as not reversed
|
|
bool rx_B{false};
|
|
|
|
auto tx_A = is_button_checked(tx_A_button_, quiet);
|
|
|
|
// some rigs have dual Rx, we take VFO A/MAIN receiving as
|
|
// normal and only say reversed when only VFO B/SUB is active
|
|
// i.e. VFO A/MAIN muted VFO B/SUB active
|
|
if (receiver_dropdown_ >= 0 && rx_A_selection_.size()) {
|
|
auto selection = get_dropdown(receiver_dropdown_);
|
|
rx_A = selection == rx_A_selection_.front();
|
|
if (!rx_A && rx_B_selection_.size()) {
|
|
rx_B = selection == rx_B_selection_.front();
|
|
}
|
|
} else if (vfo_B_button_ >= 0 || rx_B_button_ >= 0) {
|
|
rx_A = is_button_checked(
|
|
rx_A_button_ >= 0 ? rx_A_button_ : vfo_A_button_, quiet);
|
|
if (!rx_A) {
|
|
rx_B = is_button_checked(
|
|
rx_B_button_ >= 0 ? rx_B_button_ : vfo_B_button_, quiet);
|
|
}
|
|
}
|
|
|
|
update_split(rx_B == tx_A);
|
|
reversed_ = rx_B;
|
|
}
|
|
|
|
if (vfo_count_ > 1) {
|
|
auto frequencies = send_command("get frequencies", quiet)
|
|
.trimmed()
|
|
.split('-', Qt::SkipEmptyParts);
|
|
update_rx_frequency(frequencies[reversed_ ? 1 : 0].toUInt());
|
|
update_other_frequency(frequencies[reversed_ ? 0 : 1].toUInt());
|
|
} else {
|
|
// read frequency is unreliable on single VFO addressing rigs
|
|
// while transmitting
|
|
if (!state().ptt()) {
|
|
update_rx_frequency(send_command("get frequency", quiet).toUInt());
|
|
}
|
|
}
|
|
|
|
// read mode is unreliable on single VFO addressing rigs while
|
|
// transmitting
|
|
if (vfo_count_ > 1 || !state().ptt()) {
|
|
update_mode(get_data_mode(
|
|
lookup_mode(get_dropdown(mode_A_dropdown_, quiet), mode_A_map_),
|
|
quiet));
|
|
}
|
|
}
|
|
|
|
QString HRDTransceiver::send_command(QString const &cmd, bool no_debug,
|
|
bool prepend_context, bool recurse) {
|
|
Q_ASSERT(hrd_);
|
|
|
|
QString result;
|
|
|
|
if (current_radio_ && prepend_context && vfo_count_ < 2) {
|
|
// required on some radios because commands don't get executed
|
|
// correctly otherwise (ICOM for example)
|
|
QThread::msleep(50);
|
|
}
|
|
|
|
if (!recurse && prepend_context) {
|
|
auto radio_name = send_command("get radio", true, current_radio_, true);
|
|
auto radio_iter =
|
|
std::find_if(radios_.begin(), radios_.end(),
|
|
[&radio_name](RadioMap::value_type const &radio) {
|
|
return std::get<1>(radio) == radio_name;
|
|
});
|
|
if (radio_iter == radios_.end()) {
|
|
TRACE_CAT("HRDTransceiver", "rig disappeared or changed");
|
|
throw error{tr("Ham Radio Deluxe: rig has disappeared or changed")};
|
|
}
|
|
|
|
if (0u == current_radio_ ||
|
|
std::get<0>(*radio_iter) != current_radio_) {
|
|
current_radio_ = std::get<0>(*radio_iter);
|
|
}
|
|
}
|
|
|
|
auto context = '[' + QString::number(current_radio_) + "] ";
|
|
|
|
if (QTcpSocket::ConnectedState != hrd_->state()) {
|
|
TRACE_CAT("HRDTransceiver", cmd << "failed" << hrd_->errorString());
|
|
throw error{tr("Ham Radio Deluxe send command \"%1\" failed %2\n")
|
|
.arg(cmd)
|
|
.arg(hrd_->errorString())};
|
|
}
|
|
|
|
if (v4 == protocol_) {
|
|
auto message =
|
|
((prepend_context ? context + cmd : cmd) + "\r").toLocal8Bit();
|
|
if (!write_to_port(message.constData(), message.size())) {
|
|
TRACE_CAT("HRDTransceiver",
|
|
"failed to write command" << cmd << "to HRD");
|
|
throw error{tr("Ham Radio Deluxe: failed to write command \"%1\"")
|
|
.arg(cmd)};
|
|
}
|
|
} else {
|
|
auto string = prepend_context ? context + cmd : cmd;
|
|
QScopedPointer<HRDMessage> message{new (string) HRDMessage};
|
|
if (!write_to_port(reinterpret_cast<char const *>(message.data()),
|
|
message->size_)) {
|
|
TRACE_CAT("HRDTransceiver",
|
|
"failed to write command" << cmd << "to HRD");
|
|
throw error{tr("Ham Radio Deluxe: failed to write command \"%1\"")
|
|
.arg(cmd)};
|
|
}
|
|
}
|
|
auto buffer = read_reply(cmd);
|
|
if (v4 == protocol_) {
|
|
result = QString{buffer}.trimmed();
|
|
} else {
|
|
HRDMessage const *reply{new (buffer) HRDMessage};
|
|
if (reply->magic_1_value_ != reply->magic_1_ &&
|
|
reply->magic_2_value_ != reply->magic_2_) {
|
|
TRACE_CAT("HRDTransceiver", cmd << "invalid reply");
|
|
throw error{tr("Ham Radio Deluxe sent an invalid reply to our "
|
|
"command \"%1\"")
|
|
.arg(cmd)};
|
|
}
|
|
|
|
// keep reading until expected size arrives
|
|
while (buffer.size() - offsetof(HRDMessage, size_) < reply->size_) {
|
|
if (!no_debug) {
|
|
TRACE_CAT("HRDTransceiver", cmd << "reading more reply data");
|
|
}
|
|
buffer += read_reply(cmd);
|
|
reply = new (buffer) HRDMessage;
|
|
}
|
|
|
|
result =
|
|
QString{reply->payload_}; // this is not a memory leak (honest!)
|
|
}
|
|
if (!no_debug) {
|
|
TRACE_CAT("HRDTransceiver", cmd << " ->" << result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool HRDTransceiver::write_to_port(char const *data, qint64 length) {
|
|
qint64 total_bytes_sent{0};
|
|
while (total_bytes_sent < length) {
|
|
auto bytes_sent =
|
|
hrd_->write(data + total_bytes_sent, length - total_bytes_sent);
|
|
if (bytes_sent < 0 || !hrd_->waitForBytesWritten()) {
|
|
return false;
|
|
}
|
|
|
|
total_bytes_sent += bytes_sent;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
QByteArray HRDTransceiver::read_reply(QString const &cmd) {
|
|
// waitForReadReady appears to be occasionally unreliable on Windows
|
|
// timing out when data is waiting so retry a few times
|
|
unsigned retries{3};
|
|
bool replied{false};
|
|
while (!replied && retries--) {
|
|
replied = hrd_->waitForReadyRead();
|
|
if (!replied && hrd_->error() != hrd_->SocketTimeoutError) {
|
|
TRACE_CAT("HRDTransceiver",
|
|
cmd << "failed to reply" << hrd_->errorString());
|
|
throw error{
|
|
tr("Ham Radio Deluxe failed to reply to command \"%1\" %2\n")
|
|
.arg(cmd)
|
|
.arg(hrd_->errorString())};
|
|
}
|
|
}
|
|
if (!replied) {
|
|
TRACE_CAT("HRDTransceiver", cmd << "retries exhausted");
|
|
throw error{
|
|
tr("Ham Radio Deluxe retries exhausted sending command \"%1\"")
|
|
.arg(cmd)};
|
|
}
|
|
return hrd_->readAll();
|
|
}
|
|
|
|
void HRDTransceiver::send_simple_command(QString const &command,
|
|
bool no_debug) {
|
|
if ("OK" != send_command(command, no_debug)) {
|
|
TRACE_CAT("HRDTransceiver", command << "unexpected response");
|
|
throw error{
|
|
tr("Ham Radio Deluxe didn't respond to command \"%1\" as expected")
|
|
.arg(command)};
|
|
}
|
|
}
|
|
|
|
Q_LOGGING_CATEGORY(hrdtransceiver_js8, "hrdtransceiver.js8", QtWarningMsg)
|