WSJT-X/DXLabSuiteCommanderTransceiver.cpp
Bill Somerville 098ec3f923 Similar TCP/IP retry mechanism for DX Lab as that used on with HRD.
Assume     the    defect     requiring    occasional     retries    of
QTCPSocket::waitReadReady() is  generic; the  same retry  mechanism as
used in  the HRD interface  has been impelemnted  in the DX  Lab Suite
Commander TCP/IP interface.

git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@4295 ab8295b8-cf94-4d9e-aec4-7959e3be5d79
2014-09-10 09:18:32 +00:00

430 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"};
int socket_wait_time {5000};
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 (socket_wait_time))
{
#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 (QTcpSocket::ConnectedState != commander_->state ())
{
#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 ()};
}
commander_->write (cmd);
if (!commander_->waitForBytesWritten (socket_wait_time))
{
#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 (QTcpSocket::ConnectedState != commander_->state ())
{
#if WSJT_TRACE_CAT
qDebug () << "DXLabSuiteCommanderTransceiver::command_with_reply \"" << cmd << "\" connection failed:" << commander_->errorString ();
#endif
throw error {
tr ("DX Lab Suite Commander failed to send command \"%1\" connection failed: %2\n")
.arg (cmd.constData ())
.arg (commander_->errorString ())
};
}
commander_->write (cmd);
if (!commander_->waitForBytesWritten (socket_wait_time) || QTcpSocket::ConnectedState != commander_->state ())
{
#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 (socket_wait_time);
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;
}