#include "DXLabSuiteCommanderTransceiver.hpp" #include #include #include "NetworkServerLookup.hpp" #include "moc_DXLabSuiteCommanderTransceiver.cpp" 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"; } } 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) : PollingTransceiver {poll_interval} , wrapped_ {std::move (wrapped)} , use_for_ptt_ {use_for_ptt} , server_ {address} , commander_ {nullptr} { } DXLabSuiteCommanderTransceiver::~DXLabSuiteCommanderTransceiver () { } void DXLabSuiteCommanderTransceiver::do_start () { #if WSJT_TRACE_CAT qDebug () << "DXLabSuiteCommanderTransceiver::start"; #endif wrapped_->start (); 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 ()) { #if WSJT_TRACE_CAT qDebug () << "DXLabSuiteCommanderTransceiver::start failed to connect" << commander_->errorString (); #endif throw error {tr ("Failed to connect to DX Lab Suite Commander\n") + commander_->errorString ()}; } poll (); } void DXLabSuiteCommanderTransceiver::do_stop () { if (commander_) { commander_->close (); delete commander_, commander_ = nullptr; } wrapped_->stop (); #if WSJT_TRACE_CAT qDebug () << "DXLabSuiteCommanderTransceiver::stop"; #endif } void DXLabSuiteCommanderTransceiver::do_ptt (bool on) { #if WSJT_TRACE_CAT qDebug () << "DXLabSuiteCommanderTransceiver::do_ptt:" << on << state (); #endif if (use_for_ptt_) { simple_command (on ? "CmdTX" : "CmdRX"); } else { wrapped_->ptt (on); } update_PTT (on); do_frequency (state ().frequency ()); // gets Commander synchronized } void DXLabSuiteCommanderTransceiver::do_frequency (Frequency f) { #if WSJT_TRACE_CAT qDebug () << "DXLabSuiteCommanderTransceiver::do_frequency:" << f << state (); #endif // number is localised // avoid floating point translation errors by adding a small number (0.1Hz) simple_command ("CmdSetFreq" + QString ("%L1").arg (f / 1e3 + 1e-4, 10, 'f', 3).toLocal8Bit ()); update_rx_frequency (f); } void DXLabSuiteCommanderTransceiver::do_tx_frequency (Frequency tx, bool /* rationalise_mode */) { #if WSJT_TRACE_CAT qDebug () << "DXLabSuiteCommanderTransceiver::do_tx_frequency:" << tx << state (); #endif if (tx) { simple_command ("CmdSplit<1:2>on"); update_split (true); // number is localised // avoid floating point translation errors by adding a small number (0.1Hz) // set TX frequency after going split because going split // rationalises TX VFO mode and that can change the frequency on // Yaesu rigs if CW is involved simple_command ("CmdSetTxFreq" + QString ("%L1").arg (tx / 1e3 + 1e-4, 10, 'f', 3).toLocal8Bit ()); } else { simple_command ("CmdSplit<1:3>off"); } update_other_frequency (tx); do_frequency (state ().frequency ()); // gets Commander synchronized } void DXLabSuiteCommanderTransceiver::do_mode (MODE mode, bool /* rationalise */) { #if WSJT_TRACE_CAT qDebug () << "DXLabSuiteCommanderTransceiver::do_mode:" << mode << state (); #endif auto mapped = map_mode (mode); simple_command ((QString ("CmdSetMode<1:%2>").arg (5 + mapped.size ()).arg (mapped.size ()) + mapped).toLocal8Bit ()); if (state ().split ()) { // this toggle ensures that the TX VFO mode is the same as the RX VFO simple_command ("CmdSplit<1:3>off"); simple_command ("CmdSplit<1:2>on"); } do_frequency (state ().frequency ()); // gets Commander synchronized // setting TX frequency rationalises the mode on Icoms so get current and set poll (); } void DXLabSuiteCommanderTransceiver::poll () { #if WSJT_TRACE_CAT && WSJT_TRACE_CAT_POLLS bool quiet {false}; #else bool quiet {true}; #endif auto reply = command_with_reply ("CmdSendFreq", quiet); if (0 == reply.indexOf ("') + 1).replace (",", "").replace (".", ""); if (!state ().ptt ()) // Commander is not reliable on frequency // polls while transmitting { update_rx_frequency (reply.toUInt ()); } } else { #if WSJT_TRACE_CAT qDebug () << "DXLabSuiteCommanderTransceiver::poll: get frequency unexpected response"; #endif throw error {tr ("DX Lab Suite Commander didn't respond correctly polling frequency")}; } if (state ().split ()) { reply = command_with_reply ("CmdSendTXFreq", quiet); if (0 == reply.indexOf ("') + 1).replace (",", "").replace (".", ""); if ("000" != text) { update_other_frequency (text.toUInt ()); } } else { #if WSJT_TRACE_CAT qDebug () << "DXLabSuiteCommanderTransceiver::poll: get tx frequency unexpected response"; #endif throw error {tr ("DX Lab Suite Commander didn't respond correctly polling TX frequency")}; } } 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 { #if WSJT_TRACE_CAT qDebug () << "DXLabSuiteCommanderTransceiver::poll: unexpected split state" << split; #endif throw error {tr ("DX Lab Suite Commander sent an unrecognised split state: ") + split}; } } else { #if WSJT_TRACE_CAT qDebug () << "DXLabSuiteCommanderTransceiver::poll: get split mode unexpected response"; #endif throw error {tr ("DX Lab Suite Commander didn't respond correctly polling split status")}; } reply = command_with_reply ("CmdSendMode", quiet); if (0 == reply.indexOf ("') + 1); MODE m {UNK}; 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) { m = DIG_L; } else if ("PKT-R" == mode || "DATA-U" == mode || "Data-U" == mode) { m = DIG_U; } else { #if WSJT_TRACE_CAT qDebug () << "DXLabSuiteCommanderTransceiver::poll: unexpected mode name" << mode; #endif throw error {tr ("DX Lab Suite Commander sent an unrecognised mode: ") + mode}; } update_mode (m); } else { #if WSJT_TRACE_CAT qDebug () << "DXLabSuiteCommanderTransceiver::poll: unexpected response"; #endif throw error {tr ("DX Lab Suite Commander didn't respond correctly polling mode")}; } } void DXLabSuiteCommanderTransceiver::simple_command (QByteArray const& cmd, bool no_debug) { Q_ASSERT (commander_); if (!no_debug) { #if WSJT_TRACE_CAT qDebug () << "DXLabSuiteCommanderTransceiver:simple_command(" << cmd << ')'; #endif } if (!write_to_port (cmd)) { #if WSJT_TRACE_CAT qDebug () << "DXLabSuiteCommanderTransceiver::simple_command failed:" << commander_->errorString (); #endif throw error {tr ("DX Lab Suite Commander send command failed\n") + commander_->errorString ()}; } } QByteArray DXLabSuiteCommanderTransceiver::command_with_reply (QByteArray const& cmd, bool no_debug) { Q_ASSERT (commander_); if (!no_debug) { #if WSJT_TRACE_CAT qDebug () << "DXLabSuiteCommanderTransceiver:command_with_reply(" << cmd << ')'; #endif } if (!write_to_port (cmd)) { #if WSJT_TRACE_CAT qDebug () << "DXLabSuiteCommanderTransceiver::command_with_reply failed to send command:" << commander_->errorString (); #endif throw error { tr ("DX Lab Suite Commander failed to send command \"%1\": %2\n") .arg (cmd.constData ()) .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) { #if WSJT_TRACE_CAT qDebug () << "DXLabSuiteCommanderTransceiver::command_with_reply \"" << cmd << "\" failed to read reply:" << commander_->errorString (); #endif throw error { tr ("DX Lab Suite Commander send command \"%1\" read reply failed: %2\n") .arg (cmd.constData ()) .arg (commander_->errorString ()) }; } } if (!replied) { #if WSJT_TRACE_CAT qDebug () << "DXLabSuiteCommanderTransceiver::command_with_reply \"" << cmd << "\" retries exhausted"; #endif throw error { tr ("DX Lab Suite Commander retries exhausted sending command \"%1\"") .arg (cmd.constData ()) }; } auto result = commander_->readAll (); if (!no_debug) { #if WSJT_TRACE_CAT qDebug () << "DXLabSuiteCommanderTransceiver:command_with_reply(" << cmd << ") ->" << result; #endif } return result; } bool DXLabSuiteCommanderTransceiver::write_to_port (QByteArray const& data) { 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; }