/** * @file DXLabSuiteCommanderTransceiver.cpp * @brief Implementation of the DXLabSuiteCommanderTransceiver class */ #include "DXLabSuiteCommanderTransceiver.h" #include "JS8_Main/DriftingDateTime.h" #include "JS8_Network/NetworkServerLookup.h" #include #include #include #include #include #include #include "moc_DXLabSuiteCommanderTransceiver.cpp" Q_DECLARE_LOGGING_CATEGORY(dxlabsuitecommandertransceiver_js8) namespace { char const *const commander_transceiver_name{"DX Lab Suite Commander"}; QString map_mode(Transceiver::MODE mode) { switch (mode) { case Transceiver::AM: return "AM"; case Transceiver::CW: return "CW"; case Transceiver::CW_R: return "CW-R"; case Transceiver::USB: return "USB"; case Transceiver::LSB: return "LSB"; case Transceiver::FSK: return "RTTY"; case Transceiver::FSK_R: return "RTTY-R"; case Transceiver::DIG_L: return "DATA-L"; case Transceiver::DIG_U: return "DATA-U"; case Transceiver::FM: case Transceiver::DIG_FM: return "FM"; default: break; } return "USB"; } } // namespace void DXLabSuiteCommanderTransceiver::register_transceivers( TransceiverFactory::Transceivers *registry, int id) { (*registry)[commander_transceiver_name] = TransceiverFactory::Capabilities{ id, TransceiverFactory::Capabilities::network, true}; } DXLabSuiteCommanderTransceiver::DXLabSuiteCommanderTransceiver( std::unique_ptr wrapped, QString const &address, bool use_for_ptt, int poll_interval, QObject *parent) : PollingTransceiver{poll_interval, parent}, wrapped_{std::move(wrapped)}, use_for_ptt_{use_for_ptt}, server_{address}, commander_{nullptr} {} int DXLabSuiteCommanderTransceiver::do_start() { TRACE_CAT("DXLabSuiteCommanderTransceiver", "starting"); if (wrapped_) wrapped_->start(0); auto server_details = network_server_lookup(server_, 52002u, QHostAddress::LocalHost, QAbstractSocket::IPv4Protocol); if (!commander_) { commander_ = new QTcpSocket{this}; // QObject takes ownership } commander_->connectToHost(std::get<0>(server_details), std::get<1>(server_details)); if (!commander_->waitForConnected()) { TRACE_CAT("DXLabSuiteCommanderTransceiver", "failed to connect" << commander_->errorString()); throw error{tr("Failed to connect to DX Lab Suite Commander\n") + commander_->errorString()}; } // sleeps here are to ensure Commander has actually queried the rig // rather than returning cached data which maybe stale or simply // read backs of not yet committed values, the 2s delays are // arbitrary but hopefully enough as the actual time required is rig // and Commander setting dependent int resolution{0}; QThread::msleep(2000); auto reply = command_with_reply("CmdGetFreq"); if (0 == reply.indexOf("') + 1)); if (f && !(f % 10)) { auto test_frequency = f - f % 100 + 55; auto f_string = frequency_to_string(test_frequency); auto params = ("" + f_string).arg(f_string.size()); simple_command(("CmdSetFreq" + params) .arg(params.size())); QThread::msleep(2000); reply = command_with_reply("CmdGetFreq"); if (0 == reply.indexOf("') + 1)); switch (static_cast(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; f_string = frequency_to_string(test_frequency); params = ("" + f_string).arg(f_string.size()); simple_command( ("CmdSetFreq" + params) .arg(params.size())); QThread::msleep(2000); reply = command_with_reply( "CmdGetFreq"); new_frequency = string_to_frequency(reply.mid(reply.indexOf('>') + 1)); if (9 == static_cast( new_frequency - test_frequency)) { resolution = 2; // 20Hz rounded } } f_string = frequency_to_string(f); params = ("" + f_string).arg(f_string.size()); simple_command( ("CmdSetFreq" + params) .arg(params.size())); } } } else { TRACE_CAT("DXLabSuiteCommanderTransceiver", "get frequency unexpected response" << reply); throw error{tr("DX Lab Suite Commander didn't respond correctly " "reading frequency: ") + reply}; } poll(); return resolution; } void DXLabSuiteCommanderTransceiver::do_stop() { if (commander_) { commander_->close(); delete commander_, commander_ = nullptr; } if (wrapped_) wrapped_->stop(); TRACE_CAT("DXLabSuiteCommanderTransceiver", "stopped"); } void DXLabSuiteCommanderTransceiver::do_ptt(bool on) { TRACE_CAT("DXLabSuiteCommanderTransceiver", on << state()); if (use_for_ptt_) { simple_command(on ? "CmdTX" : "CmdRX"); bool tx{!on}; auto start = DriftingDateTime::currentMSecsSinceEpoch(); // we must now wait for Tx on the rig, we will wait a short while // before bailing out while (tx != on && DriftingDateTime::currentMSecsSinceEpoch() - start < 1000) { auto reply = command_with_reply("CmdSendTx"); if (0 == reply.indexOf("') + 1); if ("ON" == state) { tx = true; } else if ("OFF" == state) { tx = false; } else { TRACE_CAT("DXLabSuiteCommanderTransceiver", "unexpected TX state" << state); throw error{tr("DX Lab Suite Commander sent an " "unrecognised TX state: ") + state}; } } else { TRACE_CAT("DXLabSuiteCommanderTransceiver", "get TX unexpected response" << reply); throw error{tr("DX Lab Suite Commander didn't respond " "correctly polling TX status: ") + reply}; } if (tx != on) QThread::msleep(10); // don't thrash Commander } update_PTT(tx); if (tx != on) { TRACE_CAT("DXLabSuiteCommanderTransceiver", "rig failed to respond to PTT: " << on); throw error{ tr("DX Lab Suite Commander rig did not respond to PTT: ") + (on ? "ON" : "OFF")}; } } else { Q_ASSERT(wrapped_); TransceiverState new_state{wrapped_->state()}; new_state.ptt(on); wrapped_->set(new_state, 0); update_PTT(on); } } void DXLabSuiteCommanderTransceiver::do_frequency(Frequency f, MODE m, bool /*no_ignore*/) { TRACE_CAT("DXLabSuiteCommanderTransceiver", f << state()); auto f_string = frequency_to_string(f); if (UNK != m && m != get_mode()) { auto m_string = map_mode(m); auto params = ("" + f_string + "" + m_string + "Y") .arg(f_string.size()) .arg(m_string.size()); simple_command(("CmdSetFreqMode" + params) .arg(params.size())); update_mode(m); } else { auto params = ("" + f_string).arg(f_string.size()); simple_command(("CmdSetFreq" + params) .arg(params.size())); } update_rx_frequency(f); } void DXLabSuiteCommanderTransceiver::do_tx_frequency(Frequency tx, MODE mode, bool /*no_ignore*/) { TRACE_CAT("DXLabSuiteCommanderTransceiver", tx << state()); if (tx) { auto f_string = frequency_to_string(tx); auto params = ("" + f_string + "Y") .arg(f_string.size()); if (UNK == mode) { params += "Y"; } simple_command(("CmdQSXSplit" + params) .arg(params.size())); } else { simple_command("CmdSplit<1:3>off"); } update_split(tx); update_other_frequency(tx); } void DXLabSuiteCommanderTransceiver::do_mode(MODE m) { TRACE_CAT("DXLabSuiteCommanderTransceiver", m << state()); auto m_string = map_mode(m); auto params = ("<1:%1>" + m_string).arg(m_string.size()); simple_command( ("CmdSetMode" + params).arg(params.size())); update_mode(m); } void DXLabSuiteCommanderTransceiver::poll() { bool quiet{!transceiverbase_js8().isDebugEnabled()}; auto reply = command_with_reply("CmdGetFreq", quiet); if (0 == reply.indexOf("') + 1)); if (f) { if (!state().ptt()) // Commander is not reliable on frequency // polls while transmitting { update_rx_frequency(f); } } } else { TRACE_CAT_POLL("DXLabSuiteCommanderTransceiver", "get frequency unexpected response" << reply); throw error{tr("DX Lab Suite Commander didn't respond correctly " "polling frequency: ") + reply}; } if (state().split()) { reply = command_with_reply("CmdGetTXFreq", quiet); if (0 == reply.indexOf("') + 1)); if (f) { if (!state().ptt()) // Commander is not reliable on frequency // polls while transmitting { update_other_frequency(f); } } } else { TRACE_CAT_POLL("DXLabSuiteCommanderTransceiver", "get tx frequency unexpected response" << reply); throw error{tr("DX Lab Suite Commander didn't respond correctly " "polling TX frequency: ") + reply}; } } reply = command_with_reply("CmdSendSplit", quiet); if (0 == reply.indexOf("') + 1); if ("ON" == split) { update_split(true); } else if ("OFF" == split) { update_split(false); } else { TRACE_CAT_POLL("DXLabSuiteCommanderTransceiver", "unexpected split state" << split); throw error{tr("DX Lab Suite Commander sent an unrecognised split " "state: ") + split}; } } else { TRACE_CAT_POLL("DXLabSuiteCommanderTransceiver", "get split mode unexpected response" << reply); throw error{tr("DX Lab Suite Commander didn't respond correctly " "polling split status: ") + reply}; } get_mode(quiet); } auto DXLabSuiteCommanderTransceiver::get_mode(bool no_debug) -> MODE { MODE m{UNK}; auto reply = command_with_reply("CmdSendMode", no_debug); if (0 == reply.indexOf("') + 1); if ("AM" == mode) { m = AM; } else if ("CW" == mode) { m = CW; } else if ("CW-R" == mode) { m = CW_R; } else if ("FM" == mode || "WBFM" == mode) { m = FM; } else if ("LSB" == mode) { m = LSB; } else if ("USB" == mode) { m = USB; } else if ("RTTY" == mode) { m = FSK; } else if ("RTTY-R" == mode) { m = FSK_R; } else if ("PKT" == mode || "DATA-L" == mode || "Data-L" == mode || "DIGL" == mode) { m = DIG_L; } else if ("PKT-R" == mode || "DATA-U" == mode || "Data-U" == mode || "DIGU" == mode) { m = DIG_U; } else { TRACE_CAT_POLL("DXLabSuiteCommanderTransceiver", "unexpected mode name" << mode); throw error{ tr("DX Lab Suite Commander sent an unrecognised mode: \"") + mode + '"'}; } update_mode(m); } else { TRACE_CAT_POLL("DXLabSuiteCommanderTransceiver", "unexpected response" << reply); throw error{tr("DX Lab Suite Commander didn't respond correctly " "polling mode: ") + reply}; } return m; } void DXLabSuiteCommanderTransceiver::simple_command(QString const &cmd, bool no_debug) { Q_ASSERT(commander_); if (!no_debug) { TRACE_CAT("DXLabSuiteCommanderTransceiver", cmd); } if (!write_to_port(cmd)) { TRACE_CAT("DXLabSuiteCommanderTransceiver", "failed:" << commander_->errorString()); throw error{tr("DX Lab Suite Commander send command failed\n") + commander_->errorString()}; } } QString DXLabSuiteCommanderTransceiver::command_with_reply(QString const &cmd, bool no_debug) { Q_ASSERT(commander_); if (!write_to_port(cmd)) { TRACE_CAT("DXLabSuiteCommanderTransceiver", "failed to send command:" << commander_->errorString()); throw error{ tr("DX Lab Suite Commander failed to send command \"%1\": %2\n") .arg(cmd) .arg(commander_->errorString())}; } // waitForReadReady appears to be unreliable on Windows timing out // when data is waiting so retry a few times unsigned retries{5}; bool replied{false}; while (!replied && --retries) { replied = commander_->waitForReadyRead(); if (!replied && commander_->error() != commander_->SocketTimeoutError) { TRACE_CAT("DXLabSuiteCommanderTransceiver", cmd << "failed to read reply:" << commander_->errorString()); throw error{tr("DX Lab Suite Commander send command \"%1\" read " "reply failed: %2\n") .arg(cmd) .arg(commander_->errorString())}; } } if (!replied) { TRACE_CAT("DXLabSuiteCommanderTransceiver", cmd << "retries exhausted"); throw error{tr("DX Lab Suite Commander retries exhausted sending " "command \"%1\"") .arg(cmd)}; } auto result = commander_->readAll(); // qCDebug (dxlabsuitecommandertransceiver_js8) << "result: " << result; // for (int i = 0; i < result.size (); ++i) // { // qCDebug (dxlabsuitecommandertransceiver_js8) << i << ":" << hex << // int (result[i]); // } if (!no_debug) { TRACE_CAT("DXLabSuiteCommanderTransceiver", cmd << "->" << result); } return result; // converting raw UTF-8 bytes to QString } bool DXLabSuiteCommanderTransceiver::write_to_port(QString const &s) { auto data = s.toLocal8Bit(); auto to_send = data.constData(); auto length = data.size(); qint64 total_bytes_sent{0}; while (total_bytes_sent < length) { auto bytes_sent = commander_->write(to_send + total_bytes_sent, length - total_bytes_sent); if (bytes_sent < 0 || !commander_->waitForBytesWritten()) { return false; } total_bytes_sent += bytes_sent; } return true; } QString DXLabSuiteCommanderTransceiver::frequency_to_string(Frequency f) const { // number is localized and in kHz, avoid floating point translation // errors by adding a small number (0.1Hz) return QString{"%L1"}.arg(f / 1e3 + 1e-4, 10, 'f', 3); } auto DXLabSuiteCommanderTransceiver::string_to_frequency(QString s) const -> Frequency { // temporary hack because Commander is returning invalid UTF-8 bytes s.replace(QChar{QChar::ReplacementCharacter}, locale_.groupSeparator()); // remove DP - relies on n.nnn kHz format so we can do ulonglong // conversion to Hz bool ok; // auto f = locale_.toDouble (s, &ok); // use when CmdSendFreq and // CmdSendTxFreq reinstated auto f = QLocale::c().toDouble(s, &ok); // temporary fix if (!ok) { throw error{ tr("DX Lab Suite Commander sent an unrecognized frequency")}; } return (f + 1e-4) * 1e3; } Q_LOGGING_CATEGORY(dxlabsuitecommandertransceiver_js8, "dxlabsuitecommandertransceiver.js8", QtWarningMsg)