mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2024-11-01 16:13:57 -04:00
020e5e770e
Because Commander queues up CAT commands the timing of Tx audio was not being correctly synchronized to the rig PTT. This change polls the rig PTT state after changing PTT and waits for it to go to the expected state. As it happens, just doing a query does most of the synchronization as the query gets queued behind the PTT directive within Commander, with the reply being delayed until the query completes. This change goes further and checks the PTT state changes within one second. Note that users using Commander will should not suffer from ALC overshoot issues on rigs that suffer such a problem since Tx audio now only commences after the rig has switched to transmit. Users with hardware sequencers may need to adjust their "Settings->Advanced->Tx Delay" parameter, this change does not introduce any new timing hazard but there may be a slight lengthening of the delay to Tx audio if the rig reports PTT via CAT as the audio delay may now be timed from the actual PTT on the rig rather than the assertion of RTS/DTR. Still outstanding is the slow processing of Commander CAT requests which can cause considerable delays before Tx audio can start. git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@7764 ab8295b8-cf94-4d9e-aec4-7959e3be5d79
506 lines
16 KiB
C++
506 lines
16 KiB
C++
#include "DXLabSuiteCommanderTransceiver.hpp"
|
|
|
|
#include <QTcpSocket>
|
|
#include <QRegularExpression>
|
|
#include <QLocale>
|
|
#include <QThread>
|
|
#include <QDateTime>
|
|
|
|
#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, 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 ("<command:10>CmdGetFreq<parameters:0>");
|
|
if (0 == reply.indexOf ("<CmdFreq:"))
|
|
{
|
|
auto f = string_to_frequency (reply.mid (reply.indexOf ('>') + 1));
|
|
if (f && !(f % 10))
|
|
{
|
|
auto test_frequency = f - f % 100 + 55;
|
|
auto f_string = frequency_to_string (test_frequency);
|
|
auto params = ("<xcvrfreq:%1>" + f_string).arg (f_string.size ());
|
|
simple_command (("<command:10>CmdSetFreq<parameters:%1>" + params).arg (params.size ()));
|
|
QThread::msleep (2000);
|
|
reply = command_with_reply ("<command:10>CmdGetFreq<parameters:0>");
|
|
if (0 == reply.indexOf ("<CmdFreq:"))
|
|
{
|
|
auto new_frequency = string_to_frequency (reply.mid (reply.indexOf ('>') + 1));
|
|
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;
|
|
f_string = frequency_to_string (test_frequency);
|
|
params = ("<xcvrfreq:%1>" + f_string).arg (f_string.size ());
|
|
simple_command (("<command:10>CmdSetFreq<parameters:%1>" + params).arg (params.size ()));
|
|
QThread::msleep (2000);
|
|
reply = command_with_reply ("<command:10>CmdGetFreq<parameters:0>");
|
|
new_frequency = string_to_frequency (reply.mid (reply.indexOf ('>') + 1));
|
|
if (9 == static_cast<Radio::FrequencyDelta> (new_frequency - test_frequency))
|
|
{
|
|
resolution = 2; // 20Hz rounded
|
|
}
|
|
}
|
|
f_string = frequency_to_string (f);
|
|
params = ("<xcvrfreq:%1>" + f_string).arg (f_string.size ());
|
|
simple_command (("<command:10>CmdSetFreq<parameters:%1>" + 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 ? "<command:5>CmdTX<parameters:0>" : "<command:5>CmdRX<parameters:0>");
|
|
}
|
|
else
|
|
{
|
|
Q_ASSERT (wrapped_);
|
|
TransceiverState new_state {wrapped_->state ()};
|
|
new_state.ptt (on);
|
|
wrapped_->set (new_state, 0);
|
|
}
|
|
|
|
bool tx {!on};
|
|
auto start = QDateTime::currentMSecsSinceEpoch ();
|
|
// we must now wait for Tx on the rig, we will wait a short while
|
|
// before bailing out
|
|
while (tx != on && QDateTime::currentMSecsSinceEpoch () - start < 1000)
|
|
{
|
|
auto reply = command_with_reply ("<command:9>CmdSendTx<parameters:0>");
|
|
if (0 == reply.indexOf ("<CmdTX:"))
|
|
{
|
|
auto state = reply.mid (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")};
|
|
}
|
|
}
|
|
|
|
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 = ("<xcvrfreq:%1>" + f_string + "<xcvrmode:%2>" + m_string + "<preservesplitanddual:1>Y").arg (f_string.size ()).arg (m_string.size ());
|
|
simple_command (("<command:14>CmdSetFreqMode<parameters:%1>" + params).arg (params.size ()));
|
|
update_mode (m);
|
|
}
|
|
else
|
|
{
|
|
auto params = ("<xcvrfreq:%1>" + f_string).arg (f_string.size ());
|
|
simple_command (("<command:10>CmdSetFreq<parameters:%1>" + 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 = ("<xcvrfreq:%1>" + f_string + "<SuppressDual:1>Y").arg (f_string.size ());
|
|
if (UNK == mode)
|
|
{
|
|
params += "<SuppressModeChange:1>Y";
|
|
}
|
|
simple_command (("<command:11>CmdQSXSplit<parameters:%1>" + params).arg (params.size ()));
|
|
}
|
|
else
|
|
{
|
|
simple_command ("<command:8>CmdSplit<parameters:8><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 (("<command:10>CmdSetMode<parameters:%1>" + params).arg (params.size ()));
|
|
update_mode (m);
|
|
}
|
|
|
|
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:10>CmdGetFreq<parameters:0>", quiet);
|
|
if (0 == reply.indexOf ("<CmdFreq:"))
|
|
{
|
|
auto f = string_to_frequency (reply.mid (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 ("<command:12>CmdGetTXFreq<parameters:0>", quiet);
|
|
if (0 == reply.indexOf ("<CmdTXFreq:"))
|
|
{
|
|
auto f = string_to_frequency (reply.mid (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 ("<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
|
|
{
|
|
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 ("<command:11>CmdSendMode<parameters:0>", no_debug);
|
|
if (0 == reply.indexOf ("<CmdMode:"))
|
|
{
|
|
auto mode = reply.mid (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 ();
|
|
// qDebug () << "result: " << result;
|
|
// for (int i = 0; i < result.size (); ++i)
|
|
// {
|
|
// qDebug () << 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;
|
|
}
|