mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2024-11-27 14:48:46 -05:00
2099db5ea9
Applied techniques learned from dealing with HRD quirks and TCP/IP client issues on Windows to the DX Lab Suite Commander interface. These changes should hopefully make both the HRD and DX Lab Suite CAT interfacing more robust in the face of network and other latency issues or server failures. git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@4296 ab8295b8-cf94-4d9e-aec4-7959e3be5d79
431 lines
12 KiB
C++
431 lines
12 KiB
C++
#include "DXLabSuiteCommanderTransceiver.hpp"
|
|
|
|
#include <QTcpSocket>
|
|
#include <QRegularExpression>
|
|
|
|
#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<TransceiverBase> 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 ? "<command:5>CmdTX<parameters:0>" : "<command:5>CmdRX<parameters:0>");
|
|
}
|
|
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 ("<command:10>CmdSetFreq<parameters:23><xcvrfreq:10>" + 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 ("<command:8>CmdSplit<parameters:7><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 ("<command:12>CmdSetTxFreq<parameters:23><xcvrfreq:10>" + QString ("%L1").arg (tx / 1e3 + 1e-4, 10, 'f', 3).toLocal8Bit ());
|
|
}
|
|
else
|
|
{
|
|
simple_command ("<command:8>CmdSplit<parameters:8><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 ("<command:10>CmdSetMode<parameters:%1><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 ("<command:8>CmdSplit<parameters:8><1:3>off");
|
|
simple_command ("<command:8>CmdSplit<parameters:7><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 ("<command:11>CmdSendFreq<parameters:0>", quiet);
|
|
if (0 == reply.indexOf ("<CmdFreq:"))
|
|
{
|
|
// remove thousands separator and DP - relies of n.nnn kHz
|
|
// format so we can do uint conversion
|
|
reply = reply.mid (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")};
|
|
}
|
|
|
|
reply = command_with_reply ("<command:13>CmdSendTXFreq<parameters:0>", quiet);
|
|
if (0 == reply.indexOf ("<CmdTXFreq:"))
|
|
{
|
|
// remove thousands separator and DP - relies of n.nnn kHz format so we ca do uint conversion
|
|
auto text = reply.mid (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 ("<command:12>CmdSendSplit<parameters:0>", quiet);
|
|
if (0 == reply.indexOf ("<CmdSplit:"))
|
|
{
|
|
auto split = reply.mid (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 ("<command:11>CmdSendMode<parameters:0>", quiet);
|
|
if (0 == reply.indexOf ("<CmdMode:"))
|
|
{
|
|
auto mode = reply.mid (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;
|
|
}
|