mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2026-06-03 06:24:39 -04:00
improve physical structure
This commit is contained in:
@@ -0,0 +1,506 @@
|
||||
#include "DXLabSuiteCommanderTransceiver.hpp"
|
||||
|
||||
#include <QTcpSocket>
|
||||
#include <QRegularExpression>
|
||||
#include <QLocale>
|
||||
#include <QThread>
|
||||
#include <QDateTime>
|
||||
|
||||
#include "Network/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};
|
||||
}
|
||||
|
||||
do_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>");
|
||||
|
||||
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")};
|
||||
}
|
||||
}
|
||||
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 = ("<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::do_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;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
#ifndef DX_LAB_SUITE_COMMANDER_TRANSCEIVER_HPP__
|
||||
#define DX_LAB_SUITE_COMMANDER_TRANSCEIVER_HPP__
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "TransceiverFactory.hpp"
|
||||
#include "PollingTransceiver.hpp"
|
||||
|
||||
class QTcpSocket;
|
||||
class QByteArray;
|
||||
class QString;
|
||||
|
||||
//
|
||||
// DX Lab Suite Commander Interface
|
||||
//
|
||||
// Implemented as a Transceiver decorator because we may want the PTT
|
||||
// services of another Transceiver type such as the HamlibTransceiver
|
||||
// which can be enabled by wrapping a HamlibTransceiver instantiated
|
||||
// as a "Hamlib Dummy" transceiver in the Transceiver factory method.
|
||||
//
|
||||
class DXLabSuiteCommanderTransceiver final
|
||||
: public PollingTransceiver
|
||||
{
|
||||
Q_OBJECT; // for translation context
|
||||
|
||||
public:
|
||||
static void register_transceivers (TransceiverFactory::Transceivers *, int id);
|
||||
|
||||
// takes ownership of wrapped Transceiver
|
||||
explicit DXLabSuiteCommanderTransceiver (std::unique_ptr<TransceiverBase> wrapped,
|
||||
QString const& address, bool use_for_ptt,
|
||||
int poll_interval, QObject * parent = nullptr);
|
||||
|
||||
protected:
|
||||
int do_start () override;
|
||||
void do_stop () override;
|
||||
void do_frequency (Frequency, MODE, bool no_ignore) override;
|
||||
void do_tx_frequency (Frequency, MODE, bool no_ignore) override;
|
||||
void do_mode (MODE) override;
|
||||
void do_ptt (bool on) override;
|
||||
|
||||
void do_poll () override;
|
||||
|
||||
private:
|
||||
MODE get_mode (bool no_debug = false);
|
||||
void simple_command (QString const&, bool no_debug = false);
|
||||
QString command_with_reply (QString const&, bool no_debug = false);
|
||||
bool write_to_port (QString const&);
|
||||
QString frequency_to_string (Frequency) const;
|
||||
Frequency string_to_frequency (QString) const;
|
||||
|
||||
std::unique_ptr<TransceiverBase> wrapped_; // may be null
|
||||
bool use_for_ptt_;
|
||||
QString server_;
|
||||
QTcpSocket * commander_;
|
||||
QLocale locale_;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,65 @@
|
||||
#include "EmulateSplitTransceiver.hpp"
|
||||
|
||||
EmulateSplitTransceiver::EmulateSplitTransceiver (std::unique_ptr<Transceiver> wrapped, QObject * parent)
|
||||
: Transceiver {parent}
|
||||
, wrapped_ {std::move (wrapped)}
|
||||
, rx_frequency_ {0}
|
||||
, tx_frequency_ {0}
|
||||
, split_ {false}
|
||||
{
|
||||
// Connect update signal of wrapped Transceiver object instance to ours.
|
||||
connect (wrapped_.get (), &Transceiver::update, this, &EmulateSplitTransceiver::handle_update);
|
||||
|
||||
// Connect other signals of wrapped Transceiver object to our
|
||||
// parent matching signals.
|
||||
connect (wrapped_.get (), &Transceiver::resolution, this, &Transceiver::resolution);
|
||||
connect (wrapped_.get (), &Transceiver::finished, this, &Transceiver::finished);
|
||||
connect (wrapped_.get (), &Transceiver::failure, this, &Transceiver::failure);
|
||||
}
|
||||
|
||||
void EmulateSplitTransceiver::set (TransceiverState const& s, unsigned sequence_number) noexcept
|
||||
{
|
||||
#if WSJT_TRACE_CAT
|
||||
qDebug () << "EmulateSplitTransceiver::set: state:" << s << "#:" << sequence_number;
|
||||
#endif
|
||||
// save for use in updates
|
||||
rx_frequency_ = s.frequency ();
|
||||
tx_frequency_ = s.tx_frequency ();
|
||||
split_ = s.split ();
|
||||
|
||||
TransceiverState emulated_state {s};
|
||||
if (s.ptt () && split_) emulated_state.frequency (s.tx_frequency ());
|
||||
emulated_state.split (false);
|
||||
emulated_state.tx_frequency (0);
|
||||
wrapped_->set (emulated_state, sequence_number);
|
||||
}
|
||||
|
||||
void EmulateSplitTransceiver::handle_update (TransceiverState const& state,
|
||||
unsigned sequence_number)
|
||||
{
|
||||
#if WSJT_TRACE_CAT
|
||||
qDebug () << "EmulateSplitTransceiver::handle_update: from wrapped:" << state;
|
||||
#endif
|
||||
|
||||
if (state.split ())
|
||||
{
|
||||
Q_EMIT failure (tr ("Emulated split mode requires rig to be in simplex mode"));
|
||||
}
|
||||
else
|
||||
{
|
||||
TransceiverState new_state {state};
|
||||
// Follow the rig if in RX mode.
|
||||
if (state.ptt ()) new_state.frequency (rx_frequency_);
|
||||
|
||||
// These are always what was requested in prior set state operation
|
||||
new_state.tx_frequency (tx_frequency_);
|
||||
new_state.split (split_);
|
||||
|
||||
#if WSJT_TRACE_CAT
|
||||
qDebug () << "EmulateSplitTransceiver::handle_update: signalling:" << state;
|
||||
#endif
|
||||
|
||||
// signal emulated state
|
||||
Q_EMIT update (new_state, sequence_number);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#ifndef EMULATE_SPLIT_TRANSCEIVER_HPP__
|
||||
#define EMULATE_SPLIT_TRANSCEIVER_HPP__
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "Transceiver.hpp"
|
||||
|
||||
//
|
||||
// Emulate Split Transceiver
|
||||
//
|
||||
// Helper decorator class that encapsulates the emulation of split TX
|
||||
// operation.
|
||||
//
|
||||
// Responsibilities
|
||||
//
|
||||
// Delegates all but setting of other (split) frequency to the
|
||||
// wrapped Transceiver instance. Also routes failure signals from the
|
||||
// wrapped Transceiver instance to this instances failure signal.
|
||||
//
|
||||
// Intercepts status updates from the wrapped Transceiver instance
|
||||
// and re-signals it with the emulated status.
|
||||
//
|
||||
// Generates a status update signal if the other (split) frequency is
|
||||
// changed, this is necessary since the wrapped transceiver instance
|
||||
// never receives other frequency changes.
|
||||
//
|
||||
class EmulateSplitTransceiver final
|
||||
: public Transceiver
|
||||
{
|
||||
public:
|
||||
// takes ownership of wrapped Transceiver
|
||||
explicit EmulateSplitTransceiver (std::unique_ptr<Transceiver> wrapped,
|
||||
QObject * parent = nullptr);
|
||||
|
||||
void set (TransceiverState const&,
|
||||
unsigned sequence_number) noexcept override;
|
||||
|
||||
// forward everything else to wrapped Transceiver
|
||||
void start (unsigned sequence_number) noexcept override {wrapped_->start (sequence_number);}
|
||||
void stop () noexcept override {wrapped_->stop ();}
|
||||
|
||||
private:
|
||||
void handle_update (TransceiverState const&, unsigned seqeunce_number);
|
||||
|
||||
std::unique_ptr<Transceiver> wrapped_;
|
||||
Frequency rx_frequency_; // requested Rx frequency
|
||||
Frequency tx_frequency_; // requested Tx frequency
|
||||
bool split_; // requested split state
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,547 @@
|
||||
#include "EqualizationToolsDialog.hpp"
|
||||
|
||||
#include <iterator>
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <cmath>
|
||||
|
||||
#include <QDir>
|
||||
#include <QVector>
|
||||
#include <QHBoxLayout>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QPushButton>
|
||||
#include <QFileDialog>
|
||||
#include <QSettings>
|
||||
|
||||
#include "SettingsGroup.hpp"
|
||||
#include "qcustomplot.h"
|
||||
#include "pimpl_impl.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
float constexpr PI = 3.1415927f;
|
||||
char const * const title = "Equalization Tools";
|
||||
size_t constexpr intervals = 144;
|
||||
|
||||
// plot data loaders - wraps a plot providing value_type and
|
||||
// push_back so that a std::back_inserter output iterator can be
|
||||
// used to load plot data
|
||||
template<typename T, typename A>
|
||||
struct plot_data_loader
|
||||
{
|
||||
public:
|
||||
typedef T value_type;
|
||||
|
||||
// the adjust argument is a function that is passed the plot
|
||||
// pointer, the graph index and a data point, it returns a
|
||||
// possibly adjusted data point and can modify the graph including
|
||||
// adding extra points or gaps (quiet_NaN)
|
||||
plot_data_loader (QCustomPlot * plot, int graph_index, A adjust)
|
||||
: plot_ {plot}
|
||||
, index_ {graph_index}
|
||||
, adjust_ (adjust)
|
||||
{
|
||||
}
|
||||
|
||||
// load point into graph
|
||||
void push_back (value_type const& d)
|
||||
{
|
||||
plot_->graph (index_)->data ()->add (adjust_ (plot_, index_, d));
|
||||
}
|
||||
|
||||
private:
|
||||
QCustomPlot * plot_;
|
||||
int index_;
|
||||
A adjust_;
|
||||
};
|
||||
// helper function template to make a plot_data_loader instance
|
||||
template<typename A>
|
||||
auto make_plot_data_loader (QCustomPlot * plot, int index, A adjust)
|
||||
-> plot_data_loader<QCPGraphData, decltype (adjust)>
|
||||
{
|
||||
return plot_data_loader<QCPGraphData, decltype (adjust)> {plot, index, adjust};
|
||||
}
|
||||
// identity adjust function when no adjustment is needed with the
|
||||
// above instantiation helper
|
||||
QCPGraphData adjust_identity (QCustomPlot *, int, QCPGraphData const& v) {return v;}
|
||||
|
||||
// a plot_data_loader adjustment function that wraps Y values of
|
||||
// (-1..+1) plotting discontinuities as gaps in the graph data
|
||||
auto wrap_pi = [] (QCustomPlot * plot, int index, QCPGraphData d)
|
||||
{
|
||||
double constexpr limit {1};
|
||||
static unsigned wrap_count {0};
|
||||
static double last_x {std::numeric_limits<double>::lowest ()};
|
||||
|
||||
d.value += 2 * limit * wrap_count;
|
||||
if (d.value > limit)
|
||||
{
|
||||
// insert a gap in the graph
|
||||
plot->graph (index)->data ()->add ({last_x + (d.key - last_x) / 2
|
||||
, std::numeric_limits<double>::quiet_NaN ()});
|
||||
while (d.value > limit)
|
||||
{
|
||||
--wrap_count;
|
||||
d.value -= 2 * limit;
|
||||
}
|
||||
}
|
||||
else if (d.value < -limit)
|
||||
{
|
||||
// insert a gap into the graph
|
||||
plot->graph (index)->data ()->add ({last_x + (d.key - last_x) / 2
|
||||
, std::numeric_limits<double>::quiet_NaN ()});
|
||||
while (d.value < -limit)
|
||||
{
|
||||
++wrap_count;
|
||||
d.value += 2 * limit;
|
||||
}
|
||||
}
|
||||
last_x = d.key;
|
||||
return d;
|
||||
};
|
||||
|
||||
// generate points of type R from a function of type F for X in
|
||||
// (-1..+1) with N intervals and function of type SX to scale X and
|
||||
// of type SY to scale Y
|
||||
//
|
||||
// it is up to the user to call the generator sufficient times which
|
||||
// is interval+1 times to reach +1
|
||||
template<typename R, typename F, typename SX, typename SY>
|
||||
struct graph_generator
|
||||
{
|
||||
public:
|
||||
graph_generator (F f, size_t intervals, SX x_scaling, SY y_scaling)
|
||||
: x_ {0}
|
||||
, f_ (f)
|
||||
, intervals_ {intervals}
|
||||
, x_scaling_ (x_scaling)
|
||||
, y_scaling_ (y_scaling)
|
||||
{
|
||||
}
|
||||
|
||||
R operator () ()
|
||||
{
|
||||
typename F::value_type x {x_++ * 2.f / intervals_ - 1.f};
|
||||
return {x_scaling_ (x), y_scaling_ (f_ (x))};
|
||||
}
|
||||
|
||||
private:
|
||||
int x_;
|
||||
F f_;
|
||||
size_t intervals_;
|
||||
SX x_scaling_;
|
||||
SY y_scaling_;
|
||||
};
|
||||
// helper function template to make a graph_generator instance for
|
||||
// QCPGraphData type points with intervals intervals
|
||||
template<typename F, typename SX, typename SY>
|
||||
auto make_graph_generator (F function, SX x_scaling, SY y_scaling)
|
||||
-> graph_generator<QCPGraphData, F, decltype (x_scaling), decltype (y_scaling)>
|
||||
{
|
||||
return graph_generator<QCPGraphData, F, decltype (x_scaling), decltype (y_scaling)>
|
||||
{function, intervals, x_scaling, y_scaling};
|
||||
}
|
||||
|
||||
// template function object for a polynomial with coefficients
|
||||
template<typename C>
|
||||
class polynomial
|
||||
{
|
||||
public:
|
||||
typedef typename C::value_type value_type;
|
||||
|
||||
explicit polynomial (C const& coefficients)
|
||||
: c_ {coefficients}
|
||||
{
|
||||
}
|
||||
|
||||
value_type operator () (value_type const& x)
|
||||
{
|
||||
value_type y {};
|
||||
for (typename C::size_type i = c_.size (); i > 0; --i)
|
||||
{
|
||||
y = c_[i - 1] + x * y;
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
private:
|
||||
C c_;
|
||||
};
|
||||
// helper function template to instantiate a polynomial instance
|
||||
template<typename C>
|
||||
auto make_polynomial (C const& coefficients) -> polynomial<C>
|
||||
{
|
||||
return polynomial<C> (coefficients);
|
||||
}
|
||||
|
||||
// template function object for a group delay with coefficients
|
||||
template<typename C>
|
||||
class group_delay
|
||||
{
|
||||
public:
|
||||
typedef typename C::value_type value_type;
|
||||
|
||||
explicit group_delay (C const& coefficients)
|
||||
: c_ {coefficients}
|
||||
{
|
||||
}
|
||||
|
||||
value_type operator () (value_type const& x)
|
||||
{
|
||||
value_type tau {};
|
||||
for (typename C::size_type i = 2; i < c_.size (); ++i)
|
||||
{
|
||||
tau += i * c_[i] * std::pow (x, i - 1);
|
||||
}
|
||||
return -1 / (2 * PI) * tau;
|
||||
}
|
||||
|
||||
private:
|
||||
C c_;
|
||||
};
|
||||
// helper function template to instantiate a group_delay function
|
||||
// object
|
||||
template<typename C>
|
||||
auto make_group_delay (C const& coefficients) -> group_delay<C>
|
||||
{
|
||||
return group_delay<C> (coefficients);
|
||||
}
|
||||
|
||||
// handy identity function
|
||||
template<typename T> T identity (T const& v) {return v;}
|
||||
|
||||
// a lambda that scales the X axis from normalized to (500..2500)Hz
|
||||
auto freq_scaling = [] (float v) -> float {return 1500.f + 1000.f * v;};
|
||||
|
||||
// a lambda that scales the phase Y axis from radians to units of Pi
|
||||
auto pi_scaling = [] (float v) -> float {return v / PI;};
|
||||
}
|
||||
|
||||
class EqualizationToolsDialog::impl final
|
||||
: public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit impl (EqualizationToolsDialog * self, QSettings * settings
|
||||
, QDir const& data_directory, QVector<double> const& coefficients
|
||||
, QWidget * parent);
|
||||
~impl () {save_window_state ();}
|
||||
|
||||
protected:
|
||||
void closeEvent (QCloseEvent * e) override
|
||||
{
|
||||
save_window_state ();
|
||||
QDialog::closeEvent (e);
|
||||
}
|
||||
|
||||
private:
|
||||
void save_window_state ()
|
||||
{
|
||||
SettingsGroup g (settings_, title);
|
||||
settings_->setValue ("geometry", saveGeometry ());
|
||||
}
|
||||
|
||||
void plot_current ();
|
||||
void plot_phase ();
|
||||
void plot_amplitude ();
|
||||
|
||||
EqualizationToolsDialog * self_;
|
||||
QSettings * settings_;
|
||||
QDir data_directory_;
|
||||
QHBoxLayout layout_;
|
||||
QVector<double> current_coefficients_;
|
||||
QVector<double> new_coefficients_;
|
||||
unsigned amp_poly_low_;
|
||||
unsigned amp_poly_high_;
|
||||
QVector<double> amp_coefficients_;
|
||||
QCustomPlot plot_;
|
||||
QDialogButtonBox button_box_;
|
||||
};
|
||||
|
||||
#include "EqualizationToolsDialog.moc"
|
||||
|
||||
EqualizationToolsDialog::EqualizationToolsDialog (QSettings * settings
|
||||
, QDir const& data_directory
|
||||
, QVector<double> const& coefficients
|
||||
, QWidget * parent)
|
||||
: m_ {this, settings, data_directory, coefficients, parent}
|
||||
{
|
||||
}
|
||||
|
||||
void EqualizationToolsDialog::show ()
|
||||
{
|
||||
m_->show ();
|
||||
}
|
||||
|
||||
EqualizationToolsDialog::impl::impl (EqualizationToolsDialog * self
|
||||
, QSettings * settings
|
||||
, QDir const& data_directory
|
||||
, QVector<double> const& coefficients
|
||||
, QWidget * parent)
|
||||
: QDialog {parent}
|
||||
, self_ {self}
|
||||
, settings_ {settings}
|
||||
, data_directory_ {data_directory}
|
||||
, current_coefficients_ {coefficients}
|
||||
, amp_poly_low_ {0}
|
||||
, amp_poly_high_ {6000}
|
||||
, button_box_ {QDialogButtonBox::Apply
|
||||
| QDialogButtonBox::RestoreDefaults | QDialogButtonBox::Close
|
||||
, Qt::Vertical}
|
||||
{
|
||||
setWindowTitle (windowTitle () + ' ' + tr (title));
|
||||
resize (500, 600);
|
||||
{
|
||||
SettingsGroup g {settings_, title};
|
||||
restoreGeometry (settings_->value ("geometry", saveGeometry ()).toByteArray ());
|
||||
}
|
||||
|
||||
auto legend_title = new QCPTextElement {&plot_, tr ("Phase"), QFont {"sans", 9, QFont::Bold}};
|
||||
legend_title->setLayer (plot_.legend->layer ());
|
||||
plot_.legend->addElement (0, 0, legend_title);
|
||||
plot_.legend->setVisible (true);
|
||||
|
||||
plot_.xAxis->setLabel (tr ("Freq (Hz)"));
|
||||
plot_.xAxis->setRange (500, 2500);
|
||||
plot_.yAxis->setLabel (tr ("Phase (Π)"));
|
||||
plot_.yAxis->setRange (-1, +1);
|
||||
plot_.yAxis2->setLabel (tr ("Delay (ms)"));
|
||||
plot_.axisRect ()->setRangeDrag (Qt::Vertical);
|
||||
plot_.axisRect ()->setRangeZoom (Qt::Vertical);
|
||||
plot_.yAxis2->setVisible (true);
|
||||
plot_.axisRect ()->setRangeDragAxes (nullptr, plot_.yAxis2);
|
||||
plot_.axisRect ()->setRangeZoomAxes (nullptr, plot_.yAxis2);
|
||||
plot_.axisRect ()->insetLayout ()->setInsetAlignment (0, Qt::AlignBottom|Qt::AlignRight);
|
||||
plot_.setInteractions (QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);
|
||||
|
||||
plot_.addGraph ()->setName (tr ("Measured"));
|
||||
plot_.graph ()->setPen (QPen {Qt::blue});
|
||||
plot_.graph ()->setVisible (false);
|
||||
plot_.graph ()->removeFromLegend ();
|
||||
|
||||
plot_.addGraph ()->setName (tr ("Proposed"));
|
||||
plot_.graph ()->setPen (QPen {Qt::red});
|
||||
plot_.graph ()->setVisible (false);
|
||||
plot_.graph ()->removeFromLegend ();
|
||||
|
||||
plot_.addGraph ()->setName (tr ("Current"));
|
||||
plot_.graph ()->setPen (QPen {Qt::green});
|
||||
|
||||
plot_.addGraph (plot_.xAxis, plot_.yAxis2)->setName (tr ("Group Delay"));
|
||||
plot_.graph ()->setPen (QPen {Qt::darkGreen});
|
||||
|
||||
plot_.plotLayout ()->addElement (new QCPAxisRect {&plot_});
|
||||
plot_.plotLayout ()->setRowStretchFactor (1, 0.5);
|
||||
|
||||
auto amp_legend = new QCPLegend;
|
||||
plot_.axisRect (1)->insetLayout ()->addElement (amp_legend, Qt::AlignTop | Qt::AlignRight);
|
||||
plot_.axisRect (1)->insetLayout ()->setMargins (QMargins {12, 12, 12, 12});
|
||||
amp_legend->setVisible (true);
|
||||
amp_legend->setLayer (QLatin1String {"legend"});
|
||||
legend_title = new QCPTextElement {&plot_, tr ("Amplitude"), QFont {"sans", 9, QFont::Bold}};
|
||||
legend_title->setLayer (amp_legend->layer ());
|
||||
amp_legend->addElement (0, 0, legend_title);
|
||||
|
||||
plot_.axisRect (1)->axis (QCPAxis::atBottom)->setLabel (tr ("Freq (Hz)"));
|
||||
plot_.axisRect (1)->axis (QCPAxis::atBottom)->setRange (0, 6000);
|
||||
plot_.axisRect (1)->axis (QCPAxis::atLeft)->setLabel (tr ("Relative Power (dB)"));
|
||||
plot_.axisRect (1)->axis (QCPAxis::atLeft)->setRangeLower (0);
|
||||
plot_.axisRect (1)->setRangeDragAxes (nullptr, nullptr);
|
||||
plot_.axisRect (1)->setRangeZoomAxes (nullptr, nullptr);
|
||||
|
||||
plot_.addGraph (plot_.axisRect (1)->axis (QCPAxis::atBottom)
|
||||
, plot_.axisRect (1)->axis (QCPAxis::atLeft))->setName (tr ("Reference"));
|
||||
plot_.graph ()->setPen (QPen {Qt::blue});
|
||||
plot_.graph ()->removeFromLegend ();
|
||||
plot_.graph ()->addToLegend (amp_legend);
|
||||
|
||||
layout_.addWidget (&plot_);
|
||||
|
||||
auto load_phase_button = button_box_.addButton (tr ("Phase ..."), QDialogButtonBox::ActionRole);
|
||||
auto refresh_button = button_box_.addButton (tr ("Refresh"), QDialogButtonBox::ActionRole);
|
||||
auto discard_measured_button = button_box_.addButton (tr ("Discard Measured"), QDialogButtonBox::ActionRole);
|
||||
layout_.addWidget (&button_box_);
|
||||
setLayout (&layout_);
|
||||
|
||||
connect (&button_box_, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
connect (&button_box_, &QDialogButtonBox::clicked, [=] (QAbstractButton * button) {
|
||||
if (button == load_phase_button)
|
||||
{
|
||||
plot_phase ();
|
||||
}
|
||||
else if (button == refresh_button)
|
||||
{
|
||||
plot_current ();
|
||||
}
|
||||
else if (button == button_box_.button (QDialogButtonBox::Apply))
|
||||
{
|
||||
if (plot_.graph (0)->dataCount ()) // something loaded
|
||||
{
|
||||
current_coefficients_ = new_coefficients_;
|
||||
Q_EMIT self_->phase_equalization_changed (current_coefficients_);
|
||||
plot_current ();
|
||||
}
|
||||
}
|
||||
else if (button == button_box_.button (QDialogButtonBox::RestoreDefaults))
|
||||
{
|
||||
current_coefficients_ = QVector<double> {0., 0., 0., 0., 0.};
|
||||
Q_EMIT self_->phase_equalization_changed (current_coefficients_);
|
||||
plot_current ();
|
||||
}
|
||||
else if (button == discard_measured_button)
|
||||
{
|
||||
new_coefficients_ = QVector<double> {0., 0., 0., 0., 0.};
|
||||
|
||||
plot_.graph (0)->data ()->clear ();
|
||||
plot_.graph (0)->setVisible (false);
|
||||
plot_.graph (0)->removeFromLegend ();
|
||||
|
||||
plot_.graph (1)->data ()->clear ();
|
||||
plot_.graph (1)->setVisible (false);
|
||||
plot_.graph (1)->removeFromLegend ();
|
||||
|
||||
plot_.replot ();
|
||||
}
|
||||
});
|
||||
|
||||
plot_current ();
|
||||
}
|
||||
|
||||
struct PowerSpectrumPoint
|
||||
{
|
||||
operator QCPGraphData () const
|
||||
{
|
||||
return QCPGraphData {freq_, power_};
|
||||
}
|
||||
|
||||
float freq_;
|
||||
float power_;
|
||||
};
|
||||
|
||||
// read an amplitude point line from a stream (refspec.dat)
|
||||
std::istream& operator >> (std::istream& is, PowerSpectrumPoint& r)
|
||||
{
|
||||
float y1, y3, y4; // discard these
|
||||
is >> r.freq_ >> y1 >> r.power_ >> y3 >> y4;
|
||||
return is;
|
||||
}
|
||||
|
||||
void EqualizationToolsDialog::impl::plot_current ()
|
||||
{
|
||||
auto phase_graph = make_plot_data_loader (&plot_, 2, wrap_pi);
|
||||
plot_.graph (2)->data ()->clear ();
|
||||
std::generate_n (std::back_inserter (phase_graph), intervals + 1
|
||||
, make_graph_generator (make_polynomial (current_coefficients_), freq_scaling, pi_scaling));
|
||||
|
||||
auto group_delay_graph = make_plot_data_loader (&plot_, 3, adjust_identity);
|
||||
plot_.graph (3)->data ()->clear ();
|
||||
std::generate_n (std::back_inserter (group_delay_graph), intervals + 1
|
||||
, make_graph_generator (make_group_delay (current_coefficients_), freq_scaling, identity<double>));
|
||||
plot_.graph (3)->rescaleValueAxis ();
|
||||
|
||||
QFileInfo refspec_file_info {data_directory_.absoluteFilePath ("refspec.dat")};
|
||||
std::ifstream refspec_file (refspec_file_info.absoluteFilePath ().toLatin1 ().constData (), std::ifstream::in);
|
||||
unsigned n;
|
||||
if (refspec_file >> amp_poly_low_ >> amp_poly_high_ >> n)
|
||||
{
|
||||
std::istream_iterator<double> isi {refspec_file};
|
||||
amp_coefficients_.clear ();
|
||||
std::copy_n (isi, n, std::back_inserter (amp_coefficients_));
|
||||
}
|
||||
else
|
||||
{
|
||||
// may be old format refspec.dat with no header so rewind
|
||||
refspec_file.clear ();
|
||||
refspec_file.seekg (0);
|
||||
}
|
||||
|
||||
auto reference_spectrum_graph = make_plot_data_loader (&plot_, 4, adjust_identity);
|
||||
plot_.graph (4)->data ()->clear ();
|
||||
std::copy (std::istream_iterator<PowerSpectrumPoint> {refspec_file},
|
||||
std::istream_iterator<PowerSpectrumPoint> {},
|
||||
std::back_inserter (reference_spectrum_graph));
|
||||
plot_.graph (4)->rescaleValueAxis (true);
|
||||
|
||||
plot_.replot ();
|
||||
}
|
||||
|
||||
struct PhasePoint
|
||||
{
|
||||
operator QCPGraphData () const
|
||||
{
|
||||
return QCPGraphData {freq_, phase_};
|
||||
}
|
||||
|
||||
double freq_;
|
||||
double phase_;
|
||||
};
|
||||
|
||||
// read a phase point line from a stream (pcoeff file)
|
||||
std::istream& operator >> (std::istream& is, PhasePoint& c)
|
||||
{
|
||||
double pp, sigmay; // discard these
|
||||
if (is >> c.freq_ >> pp >> c.phase_ >> sigmay)
|
||||
{
|
||||
c.freq_ = 1500. + 1000. * c.freq_; // scale frequency to Hz
|
||||
c.phase_ /= PI; // scale to units of Pi
|
||||
}
|
||||
return is;
|
||||
}
|
||||
|
||||
void EqualizationToolsDialog::impl::plot_phase ()
|
||||
{
|
||||
auto const& phase_file_name = QFileDialog::getOpenFileName (this
|
||||
, "Select Phase Response Coefficients"
|
||||
, data_directory_.absolutePath ()
|
||||
, "Phase Coefficient Files (*.pcoeff)");
|
||||
if (!phase_file_name.size ()) return;
|
||||
|
||||
std::ifstream phase_file (phase_file_name.toLatin1 ().constData (), std::ifstream::in);
|
||||
int n;
|
||||
float chi;
|
||||
float rmsdiff;
|
||||
unsigned freq_low;
|
||||
unsigned freq_high;
|
||||
unsigned terms;
|
||||
// read header information
|
||||
if (phase_file >> n >> chi >> rmsdiff >> freq_low >> freq_high >> terms)
|
||||
{
|
||||
std::istream_iterator<double> isi {phase_file};
|
||||
new_coefficients_.clear ();
|
||||
std::copy_n (isi, terms, std::back_inserter (new_coefficients_));
|
||||
|
||||
if (phase_file)
|
||||
{
|
||||
plot_.graph (0)->data ()->clear ();
|
||||
plot_.graph (1)->data ()->clear ();
|
||||
|
||||
// read the phase data and plot as graph 0
|
||||
auto graph = make_plot_data_loader (&plot_, 0, adjust_identity);
|
||||
std::copy_n (std::istream_iterator<PhasePoint> {phase_file},
|
||||
intervals + 1, std::back_inserter (graph));
|
||||
|
||||
if (phase_file)
|
||||
{
|
||||
plot_.graph(0)->setLineStyle(QCPGraph::lsNone);
|
||||
plot_.graph(0)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, 4));
|
||||
plot_.graph (0)->setVisible (true);
|
||||
plot_.graph (0)->addToLegend ();
|
||||
|
||||
// generate the proposed polynomial plot as graph 1
|
||||
auto graph = make_plot_data_loader (&plot_, 1, wrap_pi);
|
||||
std::generate_n (std::back_inserter (graph), intervals + 1
|
||||
, make_graph_generator (make_polynomial (new_coefficients_)
|
||||
, freq_scaling, pi_scaling));
|
||||
plot_.graph (1)->setVisible (true);
|
||||
plot_.graph (1)->addToLegend ();
|
||||
}
|
||||
|
||||
plot_.replot ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_EqualizationToolsDialog.cpp"
|
||||
@@ -0,0 +1,31 @@
|
||||
#ifndef EQUALIZATION_TOOLS_DIALOG_HPP__
|
||||
#define EQUALIZATION_TOOLS_DIALOG_HPP__
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "pimpl_h.hpp"
|
||||
|
||||
class QWidget;
|
||||
class QSettings;
|
||||
class QDir;
|
||||
|
||||
class EqualizationToolsDialog
|
||||
: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit EqualizationToolsDialog (QSettings *
|
||||
, QDir const& data_directory
|
||||
, QVector<double> const& coefficients
|
||||
, QWidget * = nullptr);
|
||||
Q_SLOT void show ();
|
||||
|
||||
Q_SIGNAL void phase_equalization_changed (QVector<double> const&);
|
||||
|
||||
private:
|
||||
class impl;
|
||||
pimpl<impl> m_;
|
||||
};
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,197 @@
|
||||
#ifndef HRD_TRANSCEIVER_HPP__
|
||||
#define HRD_TRANSCEIVER_HPP__
|
||||
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
#include <memory>
|
||||
|
||||
#include <QScopedPointer>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
#include "TransceiverFactory.hpp"
|
||||
#include "PollingTransceiver.hpp"
|
||||
|
||||
class QRegExp;
|
||||
class QTcpSocket;
|
||||
class QByteArray;
|
||||
|
||||
//
|
||||
// Ham Radio Deluxe Transceiver Interface
|
||||
//
|
||||
// Implemented as a Transceiver decorator because we may want the PTT
|
||||
// services of another Transceiver type such as the HamlibTransceiver
|
||||
// which can be enabled by wrapping a HamlibTransceiver instantiated
|
||||
// as a "Hamlib Dummy" transceiver in the Transceiver factory method.
|
||||
//
|
||||
class HRDTransceiver final
|
||||
: public PollingTransceiver
|
||||
{
|
||||
public:
|
||||
static void register_transceivers (TransceiverFactory::Transceivers *, int id);
|
||||
|
||||
// takes ownership of wrapped Transceiver
|
||||
explicit HRDTransceiver (std::unique_ptr<TransceiverBase> wrapped
|
||||
, QString const& server
|
||||
, bool use_for_ptt
|
||||
, TransceiverFactory::TXAudioSource
|
||||
, int poll_interval
|
||||
, QObject * parent = nullptr);
|
||||
|
||||
protected:
|
||||
// Implement the TransceiverBase interface.
|
||||
int do_start () override;
|
||||
void do_stop () override;
|
||||
void do_frequency (Frequency, MODE, bool no_ignore) override;
|
||||
void do_tx_frequency (Frequency, MODE, bool no_ignore) override;
|
||||
void do_mode (MODE) override;
|
||||
void do_ptt (bool on) override;
|
||||
|
||||
// Implement the PollingTransceiver interface.
|
||||
void do_poll () override;
|
||||
|
||||
private:
|
||||
QString send_command (QString const&, bool no_debug = false, bool prepend_context = true, bool recurse = false);
|
||||
QByteArray read_reply (QString const& command);
|
||||
void send_simple_command (QString const&, bool no_debug = false);
|
||||
bool write_to_port (char const *, qint64 length);
|
||||
int find_button (QRegExp const&) const;
|
||||
int find_dropdown (QRegExp const&) const;
|
||||
std::vector<int> find_dropdown_selection (int dropdown, QRegExp const&) const;
|
||||
int get_dropdown (int, bool no_debug = false);
|
||||
void set_dropdown (int, int);
|
||||
void set_button (int button_index, bool checked = true);
|
||||
bool is_button_checked (int button_index, bool no_debug = false);
|
||||
|
||||
// This dictionary type maps Transceiver::MODE to a list of mode
|
||||
// drop down selection indexes that equate to that mode. It is used
|
||||
// to map internal MODE values to HRD drop down selections and vice
|
||||
// versa.
|
||||
using ModeMap = std::vector<std::tuple<MODE, std::vector<int> > >;
|
||||
|
||||
void map_modes (int dropdown, ModeMap *);
|
||||
int lookup_mode (MODE, ModeMap const&) const;
|
||||
MODE lookup_mode (int, ModeMap const&) const;
|
||||
void set_data_mode (MODE);
|
||||
MODE get_data_mode (MODE, bool no_debug = false);
|
||||
|
||||
// An alternate TransceiverBase instance that can be used to drive
|
||||
// PTT if required.
|
||||
std::unique_ptr<TransceiverBase> wrapped_; // may be null
|
||||
|
||||
bool use_for_ptt_; // Use HRD for PTT.
|
||||
TransceiverFactory::TXAudioSource audio_source_; // Select rear/data
|
||||
// audio if available
|
||||
|
||||
QString server_; // The TCP/IP addrress and port for
|
||||
// the HRD server.
|
||||
|
||||
QTcpSocket * hrd_; // The TCP/IP client that links to the
|
||||
// HRD server.
|
||||
|
||||
enum {none, v4, v5} protocol_; // The HRD protocol that has been
|
||||
// detected.
|
||||
|
||||
using RadioMap = std::vector<std::tuple<unsigned, QString> >;
|
||||
|
||||
RadioMap radios_; // Dictionary of available radios.
|
||||
|
||||
unsigned current_radio_; // The current addressed radio.
|
||||
|
||||
unsigned vfo_count_; // How many VFOs are supported.
|
||||
|
||||
QStringList buttons_; // The buttons available to click.
|
||||
|
||||
QStringList dropdown_names_; // The names of drop down selectors
|
||||
// available.
|
||||
|
||||
QMap<QString, QStringList> dropdowns_; // Dictionary of available
|
||||
// drop down selections.
|
||||
|
||||
QStringList slider_names_; // The name of available sliders.
|
||||
|
||||
QMap<QString, QStringList> sliders_; // Dictionary of available
|
||||
// slider ranges.
|
||||
|
||||
int vfo_A_button_; // The button we use to select VFO
|
||||
// A. May be -1 if none available.
|
||||
|
||||
int vfo_B_button_; // Index of button we use to select
|
||||
// VFO B. May be -1 if none available.
|
||||
|
||||
int vfo_toggle_button_; // Index of button we use to toggle
|
||||
// the VFOs. Use this if VFO A and VFO
|
||||
// B selection are not available.
|
||||
|
||||
int mode_A_dropdown_; // Index of the mode drop down for VFO
|
||||
// A.
|
||||
|
||||
ModeMap mode_A_map_; // The map of modes available for VFO
|
||||
// A.
|
||||
|
||||
int mode_B_dropdown_; // The drop down index for VFO B mode
|
||||
// setting. May be -1 if independent
|
||||
// VFO mode setting not available.
|
||||
|
||||
ModeMap mode_B_map_; // The map of modes for VFO B.
|
||||
|
||||
int data_mode_toggle_button_; // Button to toggle DATA mode
|
||||
int data_mode_on_button_; // Button to enable DATA mode
|
||||
int data_mode_off_button_; // Button to disable DATA mode
|
||||
int data_mode_dropdown_; // Index of data mode drop down, may
|
||||
// be -1 if no such drop down exists
|
||||
std::vector<int> data_mode_dropdown_selection_on_; // The drop down
|
||||
// selection to turn on data mode.
|
||||
|
||||
std::vector<int> data_mode_dropdown_selection_off_; // The drop
|
||||
// down selection to disable data mode.
|
||||
|
||||
int split_mode_button_; // Button to use to select split
|
||||
// operation. May be -1 if no button
|
||||
// is available.
|
||||
|
||||
int split_mode_dropdown_; // The drop down index that allows
|
||||
// split mode to be turned on and
|
||||
// off. May be -1 if no such drop down
|
||||
// exists.
|
||||
|
||||
bool split_mode_dropdown_write_only_; // Some rigs cannot report
|
||||
// split status.
|
||||
|
||||
std::vector<int> split_mode_dropdown_selection_on_; // The drop down
|
||||
// selection to
|
||||
// turn on
|
||||
// split.
|
||||
|
||||
std::vector<int> split_mode_dropdown_selection_off_; // The drop
|
||||
// down
|
||||
// selection to
|
||||
// disable
|
||||
// split.
|
||||
|
||||
int split_off_button_; // The button to turn off split mode.
|
||||
|
||||
int tx_A_button_; // The button to transmit on VFO A.
|
||||
|
||||
int tx_B_button_; // The button to transmit on VFO B.
|
||||
|
||||
int rx_A_button_; // The button to receive on VFO A
|
||||
// A. May be -1 if none available.
|
||||
|
||||
int rx_B_button_; // The button to receive on VFO B
|
||||
// May be -1 if none available.
|
||||
|
||||
int receiver_dropdown_; // Select receiver
|
||||
|
||||
std::vector<int> rx_A_selection_;
|
||||
|
||||
std::vector<int> rx_B_selection_;
|
||||
|
||||
int ptt_button_; // The button to toggle PTT.
|
||||
int alt_ptt_button_; // The alternative button to toggle
|
||||
// PTT - used to select rear audio.
|
||||
|
||||
bool reversed_; // True if VFOs are reversed.
|
||||
};
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,76 @@
|
||||
#ifndef HAMLIB_TRANSCEIVER_HPP_
|
||||
#define HAMLIB_TRANSCEIVER_HPP_
|
||||
|
||||
#include <tuple>
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include <hamlib/rig.h>
|
||||
|
||||
#include "TransceiverFactory.hpp"
|
||||
#include "PollingTransceiver.hpp"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
typedef struct rig RIG;
|
||||
struct rig_caps;
|
||||
typedef int vfo_t;
|
||||
}
|
||||
|
||||
// hamlib transceiver and PTT mostly delegated directly to hamlib Rig class
|
||||
class HamlibTransceiver final
|
||||
: public PollingTransceiver
|
||||
{
|
||||
Q_OBJECT; // for translation context
|
||||
|
||||
public:
|
||||
static void register_transceivers (TransceiverFactory::Transceivers *);
|
||||
static void unregister_transceivers ();
|
||||
|
||||
explicit HamlibTransceiver (int model_number, TransceiverFactory::ParameterPack const&,
|
||||
QObject * parent = nullptr);
|
||||
explicit HamlibTransceiver (TransceiverFactory::PTTMethod ptt_type, QString const& ptt_port,
|
||||
QObject * parent = nullptr);
|
||||
|
||||
private:
|
||||
int do_start () override;
|
||||
void do_stop () override;
|
||||
void do_frequency (Frequency, MODE, bool no_ignore) override;
|
||||
void do_tx_frequency (Frequency, MODE, bool no_ignore) override;
|
||||
void do_mode (MODE) override;
|
||||
void do_ptt (bool) override;
|
||||
|
||||
void do_poll () override;
|
||||
|
||||
void error_check (int ret_code, QString const& doing) const;
|
||||
void set_conf (char const * item, char const * value);
|
||||
QByteArray get_conf (char const * item);
|
||||
Transceiver::MODE map_mode (rmode_t) const;
|
||||
rmode_t map_mode (Transceiver::MODE mode) const;
|
||||
std::tuple<vfo_t, vfo_t> get_vfos (bool for_split) const;
|
||||
|
||||
struct RIGDeleter {static void cleanup (RIG *);};
|
||||
QScopedPointer<RIG, RIGDeleter> rig_;
|
||||
|
||||
bool back_ptt_port_;
|
||||
bool one_VFO_;
|
||||
bool is_dummy_;
|
||||
|
||||
// these are saved on destruction so we can start new instances
|
||||
// where the last one left off
|
||||
static freq_t dummy_frequency_;
|
||||
static rmode_t dummy_mode_;
|
||||
|
||||
bool mutable reversed_;
|
||||
|
||||
bool freq_query_works_;
|
||||
bool mode_query_works_;
|
||||
bool split_query_works_;
|
||||
bool tickle_hamlib_; // Hamlib requires a
|
||||
// rig_set_split_vfo() call to
|
||||
// establish the Tx VFO
|
||||
bool get_vfo_works_; // Net rigctl promises what it can't deliver
|
||||
bool set_vfo_works_; // More rigctl promises which it can't deliver
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,783 @@
|
||||
#include "OmniRigTransceiver.hpp"
|
||||
|
||||
#include <QTimer>
|
||||
#include <QDebug>
|
||||
#include <objbase.h>
|
||||
#include <QThread>
|
||||
#include <QEventLoop>
|
||||
|
||||
#include "qt_helpers.hpp"
|
||||
|
||||
#include "moc_OmniRigTransceiver.cpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
auto constexpr OmniRig_transceiver_one_name = "OmniRig Rig 1";
|
||||
auto constexpr OmniRig_transceiver_two_name = "OmniRig Rig 2";
|
||||
}
|
||||
|
||||
auto OmniRigTransceiver::map_mode (OmniRig::RigParamX param) -> MODE
|
||||
{
|
||||
if (param & OmniRig::PM_CW_U)
|
||||
{
|
||||
return CW_R;
|
||||
}
|
||||
else if (param & OmniRig::PM_CW_L)
|
||||
{
|
||||
return CW;
|
||||
}
|
||||
else if (param & OmniRig::PM_SSB_U)
|
||||
{
|
||||
return USB;
|
||||
}
|
||||
else if (param & OmniRig::PM_SSB_L)
|
||||
{
|
||||
return LSB;
|
||||
}
|
||||
else if (param & OmniRig::PM_DIG_U)
|
||||
{
|
||||
return DIG_U;
|
||||
}
|
||||
else if (param & OmniRig::PM_DIG_L)
|
||||
{
|
||||
return DIG_L;
|
||||
}
|
||||
else if (param & OmniRig::PM_AM)
|
||||
{
|
||||
return AM;
|
||||
}
|
||||
else if (param & OmniRig::PM_FM)
|
||||
{
|
||||
return FM;
|
||||
}
|
||||
TRACE_CAT ("OmniRigTransceiver", "unrecognized mode");
|
||||
throw_qstring (tr ("OmniRig: unrecognized mode"));
|
||||
return UNK;
|
||||
}
|
||||
|
||||
OmniRig::RigParamX OmniRigTransceiver::map_mode (MODE mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case AM: return OmniRig::PM_AM;
|
||||
case CW: return OmniRig::PM_CW_L;
|
||||
case CW_R: return OmniRig::PM_CW_U;
|
||||
case USB: return OmniRig::PM_SSB_U;
|
||||
case LSB: return OmniRig::PM_SSB_L;
|
||||
case FSK: return OmniRig::PM_DIG_L;
|
||||
case FSK_R: return OmniRig::PM_DIG_U;
|
||||
case DIG_L: return OmniRig::PM_DIG_L;
|
||||
case DIG_U: return OmniRig::PM_DIG_U;
|
||||
case FM: return OmniRig::PM_FM;
|
||||
case DIG_FM: return OmniRig::PM_FM;
|
||||
default: break;
|
||||
}
|
||||
return OmniRig::PM_SSB_U; // quieten compiler grumble
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::register_transceivers (TransceiverFactory::Transceivers * registry, int id1, int id2)
|
||||
{
|
||||
(*registry)[OmniRig_transceiver_one_name] = TransceiverFactory::Capabilities {
|
||||
id1
|
||||
, TransceiverFactory::Capabilities::none // COM isn't serial or network
|
||||
, true // does PTT
|
||||
, false // doesn't select mic/data (use OmniRig config file)
|
||||
, true // can remote control RTS nd DTR
|
||||
, true // asynchronous interface
|
||||
};
|
||||
(*registry)[OmniRig_transceiver_two_name] = TransceiverFactory::Capabilities {
|
||||
id2
|
||||
, TransceiverFactory::Capabilities::none // COM isn't serial or network
|
||||
, true // does PTT
|
||||
, false // doesn't select mic/data (use OmniRig config file)
|
||||
, true // can remote control RTS nd DTR
|
||||
, true // asynchronous interface
|
||||
};
|
||||
}
|
||||
|
||||
OmniRigTransceiver::OmniRigTransceiver (std::unique_ptr<TransceiverBase> wrapped,
|
||||
RigNumber n, TransceiverFactory::PTTMethod ptt_type,
|
||||
QString const& ptt_port, QObject * parent)
|
||||
: TransceiverBase {parent}
|
||||
, wrapped_ {std::move (wrapped)}
|
||||
, use_for_ptt_ {TransceiverFactory::PTT_method_CAT == ptt_type || ("CAT" == ptt_port && (TransceiverFactory::PTT_method_RTS == ptt_type || TransceiverFactory::PTT_method_DTR == ptt_type))}
|
||||
, ptt_type_ {ptt_type}
|
||||
, rig_number_ {n}
|
||||
, readable_params_ {0}
|
||||
, writable_params_ {0}
|
||||
, send_update_signal_ {false}
|
||||
, reversed_ {false}
|
||||
{
|
||||
}
|
||||
|
||||
// returns false on time out
|
||||
bool OmniRigTransceiver::await_notification_with_timeout (int timeout)
|
||||
{
|
||||
QEventLoop el;
|
||||
connect (this, &OmniRigTransceiver::notified, &el, [&el] () {el.exit (1);});
|
||||
QTimer::singleShot (timeout, Qt::CoarseTimer, &el, [&el] () {el.exit (0);});
|
||||
return 1 == el.exec (); // wait for notify or timer
|
||||
}
|
||||
|
||||
int OmniRigTransceiver::do_start ()
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "starting");
|
||||
if (wrapped_) wrapped_->start (0);
|
||||
|
||||
CoInitializeEx (nullptr, 0 /*COINIT_APARTMENTTHREADED*/); // required because Qt only does this for GUI thread
|
||||
|
||||
omni_rig_.reset (new OmniRig::OmniRigX {this});
|
||||
if (omni_rig_->isNull ())
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "failed to start COM server");
|
||||
throw_qstring (tr ("Failed to start OmniRig COM server"));
|
||||
}
|
||||
|
||||
// COM/OLE exceptions get signaled
|
||||
connect (&*omni_rig_, SIGNAL (exception (int, QString, QString, QString)), this, SLOT (handle_COM_exception (int, QString, QString, QString)));
|
||||
|
||||
// IOmniRigXEvent interface signals
|
||||
connect (&*omni_rig_, SIGNAL (VisibleChange ()), this, SLOT (handle_visible_change ()));
|
||||
connect (&*omni_rig_, SIGNAL (RigTypeChange (int)), this, SLOT (handle_rig_type_change (int)));
|
||||
connect (&*omni_rig_, SIGNAL (StatusChange (int)), this, SLOT (handle_status_change (int)));
|
||||
connect (&*omni_rig_, SIGNAL (ParamsChange (int, int)), this, SLOT (handle_params_change (int, int)));
|
||||
connect (&*omni_rig_
|
||||
, SIGNAL (CustomReply (int, QVariant const&, QVariant const&))
|
||||
, this, SLOT (handle_custom_reply (int, QVariant const&, QVariant const&)));
|
||||
|
||||
TRACE_CAT ("OmniRigTransceiver", "OmniRig s/w version:" << QString::number (omni_rig_->SoftwareVersion ()).toLocal8Bit ()
|
||||
<< "i/f version:" << QString::number (omni_rig_->InterfaceVersion ()).toLocal8Bit ());
|
||||
|
||||
// fetch the interface of the RigX CoClass and instantiate a proxy object
|
||||
switch (rig_number_)
|
||||
{
|
||||
case One: rig_.reset (new OmniRig::RigX (omni_rig_->Rig1 ())); break;
|
||||
case Two: rig_.reset (new OmniRig::RigX (omni_rig_->Rig2 ())); break;
|
||||
}
|
||||
|
||||
Q_ASSERT (rig_);
|
||||
Q_ASSERT (!rig_->isNull ());
|
||||
|
||||
if (use_for_ptt_ && (TransceiverFactory::PTT_method_DTR == ptt_type_ || TransceiverFactory::PTT_method_RTS == ptt_type_))
|
||||
{
|
||||
// fetch the interface for the serial port if we need it for PTT
|
||||
port_.reset (new OmniRig::PortBits (rig_->PortBits ()));
|
||||
|
||||
Q_ASSERT (port_);
|
||||
Q_ASSERT (!port_->isNull ());
|
||||
TRACE_CAT ("OmniRigTransceiver", "OmniRig RTS state:" << port_->Rts ());
|
||||
|
||||
if (!port_->Lock ()) // try to take exclusive use of the OmniRig serial port for PTT
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "Failed to get exclusive use of serial port for PTT from OmniRig");
|
||||
}
|
||||
|
||||
// start off so we don't accidentally key the radio
|
||||
if (TransceiverFactory::PTT_method_DTR == ptt_type_)
|
||||
{
|
||||
port_->SetDtr (false);
|
||||
}
|
||||
else // RTS
|
||||
{
|
||||
port_->SetRts (false);
|
||||
}
|
||||
}
|
||||
|
||||
rig_type_ = rig_->RigType ();
|
||||
readable_params_ = rig_->ReadableParams ();
|
||||
writable_params_ = rig_->WriteableParams ();
|
||||
|
||||
TRACE_CAT ("OmniRigTransceiver", QString {"OmniRig initial rig type: %1 readable params = 0x%2 writable params = 0x%3 for rig %4"}
|
||||
.arg (rig_type_)
|
||||
.arg (readable_params_, 8, 16, QChar ('0'))
|
||||
.arg (writable_params_, 8, 16, QChar ('0'))
|
||||
.arg (rig_number_).toLocal8Bit ());
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
if (OmniRig::ST_ONLINE == rig_->Status ())
|
||||
{
|
||||
break;
|
||||
}
|
||||
await_notification_with_timeout (1000);
|
||||
}
|
||||
if (OmniRig::ST_ONLINE != rig_->Status ())
|
||||
{
|
||||
throw_qstring ("OmniRig: " + rig_->StatusStr ());
|
||||
}
|
||||
auto f = rig_->GetRxFrequency ();
|
||||
for (int i = 0; (f == 0) && (i < 5); ++i)
|
||||
{
|
||||
await_notification_with_timeout (1000);
|
||||
f = rig_->GetRxFrequency ();
|
||||
}
|
||||
update_rx_frequency (f);
|
||||
int resolution {0};
|
||||
if (OmniRig::PM_UNKNOWN == rig_->Vfo ()
|
||||
&& (writable_params_ & (OmniRig::PM_VFOA | OmniRig::PM_VFOB))
|
||||
== (OmniRig::PM_VFOA | OmniRig::PM_VFOB))
|
||||
{
|
||||
// start with VFO A (probably MAIN) on rigs that we
|
||||
// can't query VFO but can set explicitly
|
||||
rig_->SetVfo (OmniRig::PM_VFOA);
|
||||
}
|
||||
f = state ().frequency ();
|
||||
if (f % 10) return resolution; // 1Hz resolution
|
||||
auto test_frequency = f - f % 100 + 55;
|
||||
if (OmniRig::PM_FREQ & writable_params_)
|
||||
{
|
||||
rig_->SetFreq (test_frequency);
|
||||
}
|
||||
else if (reversed_ && (OmniRig::PM_FREQB & writable_params_))
|
||||
{
|
||||
rig_->SetFreqB (test_frequency);
|
||||
}
|
||||
else if (!reversed_ && (OmniRig::PM_FREQA & writable_params_))
|
||||
{
|
||||
rig_->SetFreqA (test_frequency);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw_qstring (tr ("OmniRig: don't know how to set rig frequency"));
|
||||
}
|
||||
if (!await_notification_with_timeout (1000))
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "do_start 1: wait timed out");
|
||||
throw_qstring (tr ("OmniRig: timeout waiting for update from rig"));
|
||||
}
|
||||
switch (rig_->GetRxFrequency () - 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;
|
||||
if (OmniRig::PM_FREQ & writable_params_)
|
||||
{
|
||||
rig_->SetFreq (test_frequency);
|
||||
}
|
||||
else if (reversed_ && (OmniRig::PM_FREQB & writable_params_))
|
||||
{
|
||||
rig_->SetFreqB (test_frequency);
|
||||
}
|
||||
else if (!reversed_ && (OmniRig::PM_FREQA & writable_params_))
|
||||
{
|
||||
rig_->SetFreqA (test_frequency);
|
||||
}
|
||||
if (!await_notification_with_timeout (2000))
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "do_start 2: wait timed out");
|
||||
throw_qstring (tr ("OmniRig: timeout waiting for update from rig"));
|
||||
}
|
||||
if (9 == rig_->GetRxFrequency () - test_frequency)
|
||||
{
|
||||
resolution = 2; // 20Hz rounded
|
||||
}
|
||||
}
|
||||
if (OmniRig::PM_FREQ & writable_params_)
|
||||
{
|
||||
rig_->SetFreq (f);
|
||||
}
|
||||
else if (reversed_ && (OmniRig::PM_FREQB & writable_params_))
|
||||
{
|
||||
rig_->SetFreqB (f);
|
||||
}
|
||||
else if (!reversed_ && (OmniRig::PM_FREQA & writable_params_))
|
||||
{
|
||||
rig_->SetFreqA (f);
|
||||
}
|
||||
update_rx_frequency (f);
|
||||
return resolution;
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::do_stop ()
|
||||
{
|
||||
QThread::msleep (200); // leave some time for pending
|
||||
// commands at the server end
|
||||
if (port_)
|
||||
{
|
||||
port_->Unlock (); // release serial port
|
||||
port_->clear ();
|
||||
port_.reset ();
|
||||
}
|
||||
if (omni_rig_)
|
||||
{
|
||||
if (rig_)
|
||||
{
|
||||
rig_->clear ();
|
||||
rig_.reset ();
|
||||
}
|
||||
omni_rig_->clear ();
|
||||
omni_rig_.reset ();
|
||||
CoUninitialize ();
|
||||
}
|
||||
if (wrapped_) wrapped_->stop ();
|
||||
TRACE_CAT ("OmniRigTransceiver", "stopped");
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::handle_COM_exception (int code, QString source, QString desc, QString help)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", QString::number (code) + " at " + source + ": " + desc + " (" + help + ')');
|
||||
throw_qstring (tr ("OmniRig COM/OLE error: %1 at %2: %3 (%4)").arg (QString::number (code)).arg (source). arg (desc). arg (help));
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::handle_visible_change ()
|
||||
{
|
||||
if (!omni_rig_ || omni_rig_->isNull ()) return;
|
||||
TRACE_CAT ("OmniRigTransceiver", "visibility change: visibility =" << omni_rig_->DialogVisible ());
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::handle_rig_type_change (int rig_number)
|
||||
{
|
||||
if (!omni_rig_ || omni_rig_->isNull ()) return;
|
||||
TRACE_CAT ("OmniRigTransceiver", "rig type change: rig =" << rig_number);
|
||||
if (rig_number_ == rig_number)
|
||||
{
|
||||
if (!rig_ || rig_->isNull ()) return;
|
||||
readable_params_ = rig_->ReadableParams ();
|
||||
writable_params_ = rig_->WriteableParams ();
|
||||
TRACE_CAT ("OmniRigTransceiver", QString {"rig type change to: %1 readable params = 0x%2 writable params = 0x%3 for rig %4"}
|
||||
.arg (rig_->RigType ())
|
||||
.arg (readable_params_, 8, 16, QChar ('0'))
|
||||
.arg (writable_params_, 8, 16, QChar ('0'))
|
||||
.arg (rig_number).toLocal8Bit ());
|
||||
}
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::handle_status_change (int rig_number)
|
||||
{
|
||||
if (!omni_rig_ || omni_rig_->isNull ()) return;
|
||||
TRACE_CAT ("OmniRigTransceiver", QString {"status change for rig %1"}.arg (rig_number).toLocal8Bit ());
|
||||
if (rig_number_ == rig_number)
|
||||
{
|
||||
if (!rig_ || rig_->isNull ()) return;
|
||||
auto const& status = rig_->StatusStr ().toLocal8Bit ();
|
||||
TRACE_CAT ("OmniRigTransceiver", "OmniRig status change: new status = " << status);
|
||||
if (OmniRig::ST_ONLINE != rig_->Status ())
|
||||
{
|
||||
offline ("Rig went offline");
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_EMIT notified ();
|
||||
}
|
||||
// else
|
||||
// {
|
||||
// update_rx_frequency (rig_->GetRxFrequency ());
|
||||
// update_complete ();
|
||||
// TRACE_CAT ("OmniRigTransceiver", "frequency:" << state ().frequency ());
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::handle_params_change (int rig_number, int params)
|
||||
{
|
||||
if (!omni_rig_ || omni_rig_->isNull ()) return;
|
||||
TRACE_CAT ("OmniRigTransceiver", QString {"params change: params = 0x%1 for rig %2"}
|
||||
.arg (params, 8, 16, QChar ('0'))
|
||||
.arg (rig_number).toLocal8Bit ()
|
||||
<< "state before:" << state ());
|
||||
if (rig_number_ == rig_number)
|
||||
{
|
||||
if (!rig_ || rig_->isNull ()) return;
|
||||
// starting_ = false;
|
||||
TransceiverState old_state {state ()};
|
||||
auto need_frequency = false;
|
||||
|
||||
if (params & OmniRig::PM_VFOAA)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "VFOAA");
|
||||
update_split (false);
|
||||
reversed_ = false;
|
||||
update_rx_frequency (rig_->FreqA ());
|
||||
update_other_frequency (rig_->FreqB ());
|
||||
}
|
||||
if (params & OmniRig::PM_VFOAB)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "VFOAB");
|
||||
update_split (true);
|
||||
reversed_ = false;
|
||||
update_rx_frequency (rig_->FreqA ());
|
||||
update_other_frequency (rig_->FreqB ());
|
||||
}
|
||||
if (params & OmniRig::PM_VFOBA)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "VFOBA");
|
||||
update_split (true);
|
||||
reversed_ = true;
|
||||
update_other_frequency (rig_->FreqA ());
|
||||
update_rx_frequency (rig_->FreqB ());
|
||||
}
|
||||
if (params & OmniRig::PM_VFOBB)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "VFOBB");
|
||||
update_split (false);
|
||||
reversed_ = true;
|
||||
update_other_frequency (rig_->FreqA ());
|
||||
update_rx_frequency (rig_->FreqB ());
|
||||
}
|
||||
if (params & OmniRig::PM_VFOA)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "VFOA");
|
||||
reversed_ = false;
|
||||
need_frequency = true;
|
||||
}
|
||||
if (params & OmniRig::PM_VFOB)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "VFOB");
|
||||
reversed_ = true;
|
||||
need_frequency = true;
|
||||
}
|
||||
|
||||
if (params & OmniRig::PM_FREQ)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "FREQ");
|
||||
need_frequency = true;
|
||||
}
|
||||
if (params & OmniRig::PM_FREQA)
|
||||
{
|
||||
auto f = rig_->FreqA ();
|
||||
TRACE_CAT ("OmniRigTransceiver", "FREQA = " << f);
|
||||
if (reversed_)
|
||||
{
|
||||
update_other_frequency (f);
|
||||
}
|
||||
else
|
||||
{
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
}
|
||||
if (params & OmniRig::PM_FREQB)
|
||||
{
|
||||
auto f = rig_->FreqB ();
|
||||
TRACE_CAT ("OmniRigTransceiver", "FREQB = " << f);
|
||||
if (reversed_)
|
||||
{
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
else
|
||||
{
|
||||
update_other_frequency (f);
|
||||
}
|
||||
}
|
||||
if (need_frequency)
|
||||
{
|
||||
if (readable_params_ & OmniRig::PM_FREQA)
|
||||
{
|
||||
auto f = rig_->FreqA ();
|
||||
if (f)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "FREQA = " << f);
|
||||
if (reversed_)
|
||||
{
|
||||
update_other_frequency (f);
|
||||
}
|
||||
else
|
||||
{
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (readable_params_ & OmniRig::PM_FREQB)
|
||||
{
|
||||
auto f = rig_->FreqB ();
|
||||
if (f)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "FREQB = " << f);
|
||||
if (reversed_)
|
||||
{
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
else
|
||||
{
|
||||
update_other_frequency (f);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (readable_params_ & OmniRig::PM_FREQ && !state ().ptt ())
|
||||
{
|
||||
auto f = rig_->Freq ();
|
||||
if (f)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "FREQ = " << f);
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (params & OmniRig::PM_PITCH)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "PITCH");
|
||||
}
|
||||
if (params & OmniRig::PM_RITOFFSET)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "RITOFFSET");
|
||||
}
|
||||
if (params & OmniRig::PM_RIT0)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "RIT0");
|
||||
}
|
||||
if (params & OmniRig::PM_VFOEQUAL)
|
||||
{
|
||||
auto f = readable_params_ & OmniRig::PM_FREQA ? rig_->FreqA () : rig_->Freq ();
|
||||
auto m = map_mode (rig_->Mode ());
|
||||
TRACE_CAT ("OmniRigTransceiver", QString {"VFOEQUAL f=%1 m=%2"}.arg (f).arg (m));
|
||||
update_rx_frequency (f);
|
||||
update_other_frequency (f);
|
||||
update_mode (m);
|
||||
}
|
||||
if (params & OmniRig::PM_VFOSWAP)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "VFOSWAP");
|
||||
auto f = state ().tx_frequency ();
|
||||
update_other_frequency (state ().frequency ());
|
||||
update_rx_frequency (f);
|
||||
update_mode (map_mode (rig_->Mode ()));
|
||||
}
|
||||
if (params & OmniRig::PM_SPLITON)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "SPLITON");
|
||||
update_split (true);
|
||||
}
|
||||
if (params & OmniRig::PM_SPLITOFF)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "SPLITOFF");
|
||||
update_split (false);
|
||||
}
|
||||
if (params & OmniRig::PM_RITON)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "RITON");
|
||||
}
|
||||
if (params & OmniRig::PM_RITOFF)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "RITOFF");
|
||||
}
|
||||
if (params & OmniRig::PM_XITON)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "XITON");
|
||||
}
|
||||
if (params & OmniRig::PM_XITOFF)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "XITOFF");
|
||||
}
|
||||
if (params & OmniRig::PM_RX)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "RX");
|
||||
update_PTT (false);
|
||||
}
|
||||
if (params & OmniRig::PM_TX)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "TX");
|
||||
update_PTT ();
|
||||
}
|
||||
if (params & OmniRig::PM_CW_U)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "CW-R");
|
||||
update_mode (CW_R);
|
||||
}
|
||||
if (params & OmniRig::PM_CW_L)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "CW");
|
||||
update_mode (CW);
|
||||
}
|
||||
if (params & OmniRig::PM_SSB_U)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "USB");
|
||||
update_mode (USB);
|
||||
}
|
||||
if (params & OmniRig::PM_SSB_L)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "LSB");
|
||||
update_mode (LSB);
|
||||
}
|
||||
if (params & OmniRig::PM_DIG_U)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "DATA-U");
|
||||
update_mode (DIG_U);
|
||||
}
|
||||
if (params & OmniRig::PM_DIG_L)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "DATA-L");
|
||||
update_mode (DIG_L);
|
||||
}
|
||||
if (params & OmniRig::PM_AM)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "AM");
|
||||
update_mode (AM);
|
||||
}
|
||||
if (params & OmniRig::PM_FM)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "FM");
|
||||
update_mode (FM);
|
||||
}
|
||||
|
||||
if (old_state != state () || send_update_signal_)
|
||||
{
|
||||
update_complete ();
|
||||
send_update_signal_ = false;
|
||||
}
|
||||
TRACE_CAT ("OmniRigTransceiver", "OmniRig params change: state after:" << state ());
|
||||
}
|
||||
Q_EMIT notified ();
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::handle_custom_reply (int rig_number, QVariant const& command, QVariant const& reply)
|
||||
{
|
||||
(void)command;
|
||||
(void)reply;
|
||||
|
||||
if (!omni_rig_ || omni_rig_->isNull ()) return;
|
||||
if (rig_number_ == rig_number)
|
||||
{
|
||||
if (!rig_ || rig_->isNull ()) return;
|
||||
TRACE_CAT ("OmniRigTransceiver", "custom command" << command.toString ().toLocal8Bit ()
|
||||
<< "with reply" << reply.toString ().toLocal8Bit ()
|
||||
<< QString ("for rig %1").arg (rig_number).toLocal8Bit ());
|
||||
TRACE_CAT ("OmniRigTransceiver", "rig number:" << rig_number_ << ':' << state ());
|
||||
}
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::do_ptt (bool on)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", on << state ());
|
||||
if (use_for_ptt_ && TransceiverFactory::PTT_method_CAT == ptt_type_)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "set PTT");
|
||||
rig_->SetTx (on ? OmniRig::PM_TX : OmniRig::PM_RX);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (port_)
|
||||
{
|
||||
if (TransceiverFactory::PTT_method_RTS == ptt_type_)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "set RTS");
|
||||
port_->SetRts (on);
|
||||
}
|
||||
else // "DTR"
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "set DTR");
|
||||
port_->SetDtr (on);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "set PTT using basic transceiver");
|
||||
Q_ASSERT (wrapped_);
|
||||
TransceiverState new_state {wrapped_->state ()};
|
||||
new_state.ptt (on);
|
||||
wrapped_->set (new_state, 0);
|
||||
}
|
||||
}
|
||||
update_PTT (on);
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::do_frequency (Frequency f, MODE m, bool /*no_ignore*/)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", f << state ());
|
||||
if (UNK != m)
|
||||
{
|
||||
do_mode (m);
|
||||
}
|
||||
if (OmniRig::PM_FREQ & writable_params_)
|
||||
{
|
||||
rig_->SetFreq (f);
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
else if (reversed_ && (OmniRig::PM_FREQB & writable_params_))
|
||||
{
|
||||
rig_->SetFreqB (f);
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
else if (!reversed_ && (OmniRig::PM_FREQA & writable_params_))
|
||||
{
|
||||
rig_->SetFreqA (f);
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw_qstring (tr ("OmniRig: don't know how to set rig frequency"));
|
||||
}
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::do_tx_frequency (Frequency tx, MODE m, bool /*no_ignore*/)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", tx << state ());
|
||||
bool split {tx != 0};
|
||||
if (split)
|
||||
{
|
||||
if (UNK != m)
|
||||
{
|
||||
do_mode (m);
|
||||
if (OmniRig::PM_UNKNOWN == rig_->Vfo ())
|
||||
{
|
||||
if (writable_params_ & OmniRig::PM_VFOEQUAL)
|
||||
{
|
||||
// nothing to do here because OmniRig will use VFO
|
||||
// equalize to set the mode of the Tx VFO for us
|
||||
}
|
||||
else if ((writable_params_ & (OmniRig::PM_VFOA | OmniRig::PM_VFOB))
|
||||
== (OmniRig::PM_VFOA | OmniRig::PM_VFOB))
|
||||
{
|
||||
rig_->SetVfo (OmniRig::PM_VFOB);
|
||||
do_mode (m);
|
||||
rig_->SetVfo (OmniRig::PM_VFOA);
|
||||
}
|
||||
else if (writable_params_ & OmniRig::PM_VFOSWAP)
|
||||
{
|
||||
rig_->SetVfo (OmniRig::PM_VFOSWAP);
|
||||
do_mode (m);
|
||||
rig_->SetVfo (OmniRig::PM_VFOSWAP);
|
||||
}
|
||||
}
|
||||
}
|
||||
TRACE_CAT ("OmniRigTransceiver", "set SPLIT mode on");
|
||||
rig_->SetSplitMode (state ().frequency (), tx);
|
||||
update_other_frequency (tx);
|
||||
update_split (true);
|
||||
}
|
||||
else
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "set SPLIT mode off");
|
||||
rig_->SetSimplexMode (state ().frequency ());
|
||||
update_split (false);
|
||||
}
|
||||
bool notify {false};
|
||||
if (readable_params_ & OmniRig::PM_FREQ || !(readable_params_ & (OmniRig::PM_FREQA | OmniRig::PM_FREQB)))
|
||||
{
|
||||
update_other_frequency (tx); // async updates won't return this
|
||||
// so just store it and hope
|
||||
// operator doesn't change the
|
||||
// "back" VFO on rig
|
||||
notify = true;
|
||||
}
|
||||
if (!((OmniRig::PM_VFOAB | OmniRig::PM_VFOBA | OmniRig::PM_SPLITON) & readable_params_))
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "setting SPLIT manually");
|
||||
update_split (split); // we can't read it so just set and
|
||||
// hope op doesn't change it
|
||||
notify = true;
|
||||
}
|
||||
if (notify)
|
||||
{
|
||||
update_complete ();
|
||||
}
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::do_mode (MODE mode)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", mode << state ());
|
||||
// TODO: G4WJS OmniRig doesn't seem to have any capability of tracking/setting VFO B mode
|
||||
auto mapped = map_mode (mode);
|
||||
if (mapped & writable_params_)
|
||||
{
|
||||
rig_->SetMode (mapped);
|
||||
update_mode (mode);
|
||||
}
|
||||
else
|
||||
{
|
||||
offline ("OmniRig invalid mode");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
#ifndef OMNI_RIG_TRANSCEIVER_HPP__
|
||||
#define OMNI_RIG_TRANSCEIVER_HPP__
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QScopedPointer>
|
||||
#include <QString>
|
||||
|
||||
#include "TransceiverFactory.hpp"
|
||||
#include "TransceiverBase.hpp"
|
||||
|
||||
#include "OmniRig.h"
|
||||
|
||||
//
|
||||
// OmniRig Transceiver Interface
|
||||
//
|
||||
// Implemented as a Transceiver decorator because we may want the PTT
|
||||
// services of another Transceiver type such as the HamlibTransceiver
|
||||
// which can be enabled by wrapping a HamlibTransceiver instantiated
|
||||
// as a "Hamlib Dummy" transceiver in the Transceiver factory method.
|
||||
//
|
||||
class OmniRigTransceiver final
|
||||
: public TransceiverBase
|
||||
{
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
static void register_transceivers (TransceiverFactory::Transceivers *, int id1, int id2);
|
||||
|
||||
enum RigNumber {One = 1, Two};
|
||||
|
||||
// takes ownership of wrapped Transceiver
|
||||
explicit OmniRigTransceiver (std::unique_ptr<TransceiverBase> wrapped, RigNumber, TransceiverFactory::PTTMethod ptt_type, QString const& ptt_port, QObject * parent = nullptr);
|
||||
|
||||
int do_start () override;
|
||||
void do_stop () override;
|
||||
void do_frequency (Frequency, MODE, bool no_ignore) override;
|
||||
void do_tx_frequency (Frequency, MODE, bool no_ignore) override;
|
||||
void do_mode (MODE) override;
|
||||
void do_ptt (bool on) override;
|
||||
|
||||
private:
|
||||
bool await_notification_with_timeout (int timeout);
|
||||
Q_SIGNAL void notified () const;
|
||||
// Q_SLOT void timeout_check ();
|
||||
Q_SLOT void handle_COM_exception (int, QString, QString, QString);
|
||||
Q_SLOT void handle_visible_change ();
|
||||
Q_SLOT void handle_rig_type_change (int rig_number);
|
||||
Q_SLOT void handle_status_change (int rig_number);
|
||||
Q_SLOT void handle_params_change (int rig_number, int params);
|
||||
Q_SLOT void handle_custom_reply (int, QVariant const& command, QVariant const& reply);
|
||||
|
||||
static MODE map_mode (OmniRig::RigParamX param);
|
||||
static OmniRig::RigParamX map_mode (MODE mode);
|
||||
|
||||
std::unique_ptr<TransceiverBase> wrapped_; // may be null
|
||||
bool use_for_ptt_;
|
||||
TransceiverFactory::PTTMethod ptt_type_;
|
||||
QScopedPointer<OmniRig::OmniRigX> omni_rig_;
|
||||
RigNumber rig_number_;
|
||||
QScopedPointer<OmniRig::RigX> rig_;
|
||||
QScopedPointer<OmniRig::PortBits> port_;
|
||||
QString rig_type_;
|
||||
int readable_params_;
|
||||
int writable_params_;
|
||||
// QScopedPointer<QTimer> offline_timer_;
|
||||
bool send_update_signal_;
|
||||
bool reversed_; // some rigs can reverse VFOs
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,185 @@
|
||||
#include "PollingTransceiver.hpp"
|
||||
|
||||
#include <exception>
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
|
||||
#include "moc_PollingTransceiver.cpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
unsigned const polls_to_stabilize {3};
|
||||
}
|
||||
|
||||
PollingTransceiver::PollingTransceiver (int poll_interval, QObject * parent)
|
||||
: TransceiverBase {parent}
|
||||
, interval_ {poll_interval * 1000}
|
||||
, poll_timer_ {nullptr}
|
||||
, retries_ {0}
|
||||
{
|
||||
}
|
||||
|
||||
void PollingTransceiver::start_timer ()
|
||||
{
|
||||
if (interval_)
|
||||
{
|
||||
if (!poll_timer_)
|
||||
{
|
||||
poll_timer_ = new QTimer {this}; // pass ownership to
|
||||
// QObject which handles
|
||||
// destruction for us
|
||||
|
||||
connect (poll_timer_, &QTimer::timeout, this,
|
||||
&PollingTransceiver::handle_timeout);
|
||||
}
|
||||
poll_timer_->start (interval_);
|
||||
}
|
||||
else
|
||||
{
|
||||
stop_timer ();
|
||||
}
|
||||
}
|
||||
|
||||
void PollingTransceiver::stop_timer ()
|
||||
{
|
||||
if (poll_timer_)
|
||||
{
|
||||
poll_timer_->stop ();
|
||||
}
|
||||
}
|
||||
|
||||
void PollingTransceiver::do_post_start ()
|
||||
{
|
||||
start_timer ();
|
||||
if (!next_state_.online ())
|
||||
{
|
||||
// remember that we are expecting to go online
|
||||
next_state_.online (true);
|
||||
retries_ = polls_to_stabilize;
|
||||
}
|
||||
}
|
||||
|
||||
void PollingTransceiver::do_post_stop ()
|
||||
{
|
||||
// not much point waiting for rig to go offline since we are ceasing
|
||||
// polls
|
||||
stop_timer ();
|
||||
}
|
||||
|
||||
void PollingTransceiver::do_post_frequency (Frequency f, MODE m)
|
||||
{
|
||||
// take care not to set the expected next mode to unknown since some
|
||||
// callers use mode == unknown to signify that they do not know the
|
||||
// mode and don't care
|
||||
if (next_state_.frequency () != f || (m != UNK && next_state_.mode () != m))
|
||||
{
|
||||
// update expected state with new frequency and set poll count
|
||||
next_state_.frequency (f);
|
||||
if (m != UNK)
|
||||
{
|
||||
next_state_.mode (m);
|
||||
}
|
||||
retries_ = polls_to_stabilize;
|
||||
}
|
||||
}
|
||||
|
||||
void PollingTransceiver::do_post_tx_frequency (Frequency f, MODE)
|
||||
{
|
||||
if (next_state_.tx_frequency () != f)
|
||||
{
|
||||
// update expected state with new TX frequency and set poll
|
||||
// count
|
||||
next_state_.tx_frequency (f);
|
||||
next_state_.split (f); // setting non-zero TX frequency means split
|
||||
retries_ = polls_to_stabilize;
|
||||
}
|
||||
}
|
||||
|
||||
void PollingTransceiver::do_post_mode (MODE m)
|
||||
{
|
||||
// we don't ever expect mode to goto to unknown
|
||||
if (m != UNK && next_state_.mode () != m)
|
||||
{
|
||||
// update expected state with new mode and set poll count
|
||||
next_state_.mode (m);
|
||||
retries_ = polls_to_stabilize;
|
||||
}
|
||||
}
|
||||
|
||||
void PollingTransceiver::do_post_ptt (bool p)
|
||||
{
|
||||
if (next_state_.ptt () != p)
|
||||
{
|
||||
// update expected state with new PTT and set poll count
|
||||
next_state_.ptt (p);
|
||||
retries_ = polls_to_stabilize;
|
||||
//retries_ = 0; // fast feedback on PTT
|
||||
}
|
||||
}
|
||||
|
||||
bool PollingTransceiver::do_pre_update ()
|
||||
{
|
||||
// if we are holding off a change then withhold the signal
|
||||
if (retries_ && state () != next_state_)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PollingTransceiver::handle_timeout ()
|
||||
{
|
||||
QString message;
|
||||
bool force_signal {false};
|
||||
|
||||
// we must catch all exceptions here since we are called by Qt and
|
||||
// inform our parent of the failure via the offline() message
|
||||
try
|
||||
{
|
||||
do_poll (); // tell sub-classes to update our state
|
||||
|
||||
// Signal new state if it what we expected or, hasn't become
|
||||
// what we expected after polls_to_stabilize polls. Unsolicited
|
||||
// changes will be signalled immediately unless they intervene
|
||||
// in a expected sequence where they will be delayed.
|
||||
if (retries_)
|
||||
{
|
||||
--retries_;
|
||||
if (state () == next_state_ || !retries_)
|
||||
{
|
||||
// the expected state has arrived or there are no more
|
||||
// retries
|
||||
force_signal = true;
|
||||
}
|
||||
}
|
||||
else if (state () != last_signalled_state_)
|
||||
{
|
||||
// here is the normal passive polling path where state has
|
||||
// changed asynchronously
|
||||
force_signal = true;
|
||||
}
|
||||
|
||||
if (force_signal)
|
||||
{
|
||||
// reset everything, record and signal the current state
|
||||
retries_ = 0;
|
||||
next_state_ = state ();
|
||||
last_signalled_state_ = state ();
|
||||
update_complete (true);
|
||||
}
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
message = e.what ();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
message = tr ("Unexpected rig error");
|
||||
}
|
||||
if (!message.isEmpty ())
|
||||
{
|
||||
offline (message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
#ifndef POLLING_TRANSCEIVER_HPP__
|
||||
#define POLLING_TRANSCEIVER_HPP__
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "Transceiver/TransceiverBase.hpp"
|
||||
|
||||
class QTimer;
|
||||
|
||||
//
|
||||
// Polling Transceiver
|
||||
//
|
||||
// Helper base class that encapsulates the emulation of continuous
|
||||
// update and caching of a transceiver state.
|
||||
//
|
||||
// Collaborations
|
||||
//
|
||||
// Implements the TransceiverBase post action interface and provides
|
||||
// the abstract poll() operation for sub-classes to implement. The
|
||||
// poll operation is invoked every poll_interval seconds.
|
||||
//
|
||||
// Responsibilities
|
||||
//
|
||||
// Because some rig interfaces don't immediately update after a state
|
||||
// change request; this class allows a rig a few polls to stabilise
|
||||
// to the requested state before signalling the change. This means
|
||||
// that clients don't see intermediate states that are sometimes
|
||||
// inaccurate, e.g. changing the split TX frequency on Icom rigs
|
||||
// requires a VFO switch and polls while switched will return the
|
||||
// wrong current frequency.
|
||||
//
|
||||
class PollingTransceiver
|
||||
: public TransceiverBase
|
||||
{
|
||||
Q_OBJECT; // for translation context
|
||||
|
||||
protected:
|
||||
explicit PollingTransceiver (int poll_interval, // in seconds
|
||||
QObject * parent);
|
||||
|
||||
protected:
|
||||
// Sub-classes implement this and fetch what they can from the rig
|
||||
// in a non-intrusive manner.
|
||||
virtual void do_poll () = 0;
|
||||
|
||||
void do_post_start () override final;
|
||||
void do_post_stop () override final;
|
||||
void do_post_frequency (Frequency, MODE) override final;
|
||||
void do_post_tx_frequency (Frequency, MODE) override final;
|
||||
void do_post_mode (MODE) override final;
|
||||
void do_post_ptt (bool = true) override final;
|
||||
bool do_pre_update () override final;
|
||||
|
||||
private:
|
||||
void start_timer ();
|
||||
void stop_timer ();
|
||||
|
||||
Q_SLOT void handle_timeout ();
|
||||
|
||||
int interval_; // polling interval in milliseconds
|
||||
QTimer * poll_timer_;
|
||||
|
||||
// keep a record of the last state signalled so we can elide
|
||||
// duplicate updates
|
||||
Transceiver::TransceiverState last_signalled_state_;
|
||||
|
||||
// keep a record of expected state so we can compare with actual
|
||||
// updates to determine when state changes have bubbled through
|
||||
Transceiver::TransceiverState next_state_;
|
||||
|
||||
unsigned retries_; // number of incorrect polls left
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,35 @@
|
||||
#include "Transceiver.hpp"
|
||||
|
||||
#include "moc_Transceiver.cpp"
|
||||
|
||||
#if !defined (QT_NO_DEBUG_STREAM)
|
||||
QDebug operator << (QDebug d, Transceiver::TransceiverState const& s)
|
||||
{
|
||||
d.nospace ()
|
||||
<< "Transceiver::TransceiverState(online: " << (s.online_ ? "yes" : "no")
|
||||
<< " Frequency {" << s.rx_frequency_ << "Hz, " << s.tx_frequency_ << "Hz} " << s.mode_
|
||||
<< "; SPLIT: " << (Transceiver::TransceiverState::Split::on == s.split_ ? "on" : Transceiver::TransceiverState::Split::off == s.split_ ? "off" : "unknown")
|
||||
<< "; PTT: " << (s.ptt_ ? "on" : "off")
|
||||
<< ')';
|
||||
return d.space ();
|
||||
}
|
||||
#endif
|
||||
|
||||
ENUM_QDATASTREAM_OPS_IMPL (Transceiver, MODE);
|
||||
|
||||
ENUM_CONVERSION_OPS_IMPL (Transceiver, MODE);
|
||||
|
||||
bool operator != (Transceiver::TransceiverState const& lhs, Transceiver::TransceiverState const& rhs)
|
||||
{
|
||||
return lhs.online_ != rhs.online_
|
||||
|| lhs.rx_frequency_ != rhs.rx_frequency_
|
||||
|| lhs.tx_frequency_ != rhs.tx_frequency_
|
||||
|| lhs.mode_ != rhs.mode_
|
||||
|| lhs.split_ != rhs.split_
|
||||
|| lhs.ptt_ != rhs.ptt_;
|
||||
}
|
||||
|
||||
bool operator == (Transceiver::TransceiverState const& lhs, Transceiver::TransceiverState const& rhs)
|
||||
{
|
||||
return !(lhs != rhs);
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
#ifndef TRANSCEIVER_HPP__
|
||||
#define TRANSCEIVER_HPP__
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "qt_helpers.hpp"
|
||||
#include "Radio.hpp"
|
||||
|
||||
class QString;
|
||||
|
||||
//
|
||||
// Abstract Transceiver Interface
|
||||
//
|
||||
// This is the minimal generic interface to a rig as required by
|
||||
// wsjtx.
|
||||
//
|
||||
// Responsibilities
|
||||
//
|
||||
// Provides a Qt slot to set the frequency, mode and PTT of some
|
||||
// transceiver. This is a Qt slot so that it may be invoked across a
|
||||
// thread boundary.
|
||||
//
|
||||
// Provides a synchronisation Qt slot which should be implemented in
|
||||
// sub-classes in such a way that normal operation of the rig is not
|
||||
// disturbed. This is intended to be use to poll rig state
|
||||
// periodically and changing VFO to read the other VFO frequency or
|
||||
// mode for example should not be done since the operator may be
|
||||
// tuning the VFO at the time and would be surprised by an unprompted
|
||||
// VFO change.
|
||||
//
|
||||
// Provides a control interface using Qt slots to start and stop the
|
||||
// rig control and PTT connections.
|
||||
//
|
||||
// These are Qt slots rather than the constructor and destructor
|
||||
// because it is expected that the concrete Transceiver
|
||||
// implementations will run in a separate thread from where they are
|
||||
// constructed.
|
||||
//
|
||||
// Qt signals are defined to notify clients of asynchronous rig state
|
||||
// changes and failures. These can and are expected to cross thread
|
||||
// boundaries.
|
||||
//
|
||||
// A signal finished() is defined that concrete Transceiver
|
||||
// implementations must emit when they are ripe for destruction. This
|
||||
// is intended to be used by clients that move the Transceiver
|
||||
// instance to a thread and need to use QObject::deleteLater() to
|
||||
// safely dispose of the Transceiver instance. Implementations should
|
||||
// expect Qt slot calls after emitting finished, it is up to the
|
||||
// implementation whether these slot invocations are ignored.
|
||||
//
|
||||
class Transceiver
|
||||
: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using Frequency = Radio::Frequency;
|
||||
|
||||
protected:
|
||||
Transceiver (QObject * parent) : QObject {parent} {}
|
||||
|
||||
public:
|
||||
virtual ~Transceiver () {}
|
||||
|
||||
enum MODE {UNK, CW, CW_R, USB, LSB, FSK, FSK_R, DIG_U, DIG_L, AM, FM, DIG_FM};
|
||||
Q_ENUM (MODE)
|
||||
|
||||
//
|
||||
// Aggregation of all of the rig and PTT state accessible via this
|
||||
// interface.
|
||||
//
|
||||
class TransceiverState
|
||||
{
|
||||
public:
|
||||
TransceiverState ()
|
||||
: online_ {false}
|
||||
, rx_frequency_ {0}
|
||||
, tx_frequency_ {0}
|
||||
, mode_ {UNK}
|
||||
, split_ {Split::unknown}
|
||||
, ptt_ {false}
|
||||
{
|
||||
}
|
||||
|
||||
bool online () const {return online_;}
|
||||
Frequency frequency () const {return rx_frequency_;}
|
||||
Frequency tx_frequency () const {return tx_frequency_;}
|
||||
bool split () const {return Split::on == split_;}
|
||||
MODE mode () const {return mode_;}
|
||||
bool ptt () const {return ptt_;}
|
||||
|
||||
void online (bool state) {online_ = state;}
|
||||
void frequency (Frequency f) {rx_frequency_ = f;}
|
||||
void tx_frequency (Frequency f) {tx_frequency_ = f;}
|
||||
void split (bool state) {split_ = state ? Split::on : Split::off;}
|
||||
void mode (MODE m) {mode_ = m;}
|
||||
void ptt (bool state) {ptt_ = state;}
|
||||
|
||||
private:
|
||||
bool online_;
|
||||
Frequency rx_frequency_;
|
||||
Frequency tx_frequency_; // 0 means use Rx
|
||||
MODE mode_;
|
||||
enum class Split {unknown, off, on} split_;
|
||||
bool ptt_;
|
||||
// Don't forget to update the debug print and != operator if you
|
||||
// add more members here
|
||||
|
||||
friend QDebug operator << (QDebug, TransceiverState const&);
|
||||
friend bool operator != (TransceiverState const&, TransceiverState const&);
|
||||
};
|
||||
|
||||
//
|
||||
// The following slots and signals are expected to all run in the
|
||||
// same thread which is not necessarily the main GUI thread. It is
|
||||
// up to the client of the Transceiver class to organise the
|
||||
// allocation to a thread and the lifetime of the object instances.
|
||||
//
|
||||
|
||||
// Apply state changes to the rig. The sequence_number parameter
|
||||
// will be included in any status updates generated after this
|
||||
// transaction is processed. The sequence number may be used to
|
||||
// ignore any status updates until the results of this transaction
|
||||
// have been processed thus avoiding any unwanted "ping-pong" due to
|
||||
// signals crossing in transit.
|
||||
Q_SLOT virtual void set (Transceiver::TransceiverState const&,
|
||||
unsigned sequence_number) noexcept = 0;
|
||||
|
||||
// Connect and disconnect.
|
||||
Q_SLOT virtual void start (unsigned sequence_number) noexcept = 0;
|
||||
Q_SLOT virtual void stop () noexcept = 0;
|
||||
|
||||
//
|
||||
// asynchronous status updates
|
||||
//
|
||||
|
||||
// 0 - 1Hz
|
||||
// 1 - 10Hz rounded
|
||||
// -1 - 10Hz truncated
|
||||
// 2 - 100Hz rounded
|
||||
// -2 - 100Hz truncated
|
||||
Q_SIGNAL void resolution (int);
|
||||
|
||||
// rig state changed
|
||||
Q_SIGNAL void update (Transceiver::TransceiverState const&,
|
||||
unsigned sequence_number) const;
|
||||
|
||||
// something went wrong - not recoverable, start new instance
|
||||
Q_SIGNAL void failure (QString const& reason) const;
|
||||
|
||||
// Ready to be destroyed.
|
||||
Q_SIGNAL void finished () const;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE (Transceiver::TransceiverState);
|
||||
|
||||
#if !defined (QT_NO_DEBUG_STREAM)
|
||||
QDebug operator << (QDebug, Transceiver::TransceiverState const&);
|
||||
#endif
|
||||
|
||||
ENUM_QDATASTREAM_OPS_DECL (Transceiver, MODE);
|
||||
ENUM_CONVERSION_OPS_DECL (Transceiver, MODE);
|
||||
|
||||
bool operator != (Transceiver::TransceiverState const&, Transceiver::TransceiverState const&);
|
||||
bool operator == (Transceiver::TransceiverState const&, Transceiver::TransceiverState const&);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,262 @@
|
||||
#include "TransceiverBase.hpp"
|
||||
|
||||
#include <exception>
|
||||
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
#include <QThread>
|
||||
#include <QDebug>
|
||||
|
||||
#include "moc_TransceiverBase.cpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
auto const unexpected = TransceiverBase::tr ("Unexpected rig error");
|
||||
}
|
||||
|
||||
void TransceiverBase::start (unsigned sequence_number) noexcept
|
||||
{
|
||||
QString message;
|
||||
try
|
||||
{
|
||||
last_sequence_number_ = sequence_number;
|
||||
may_update u {this, true};
|
||||
shutdown ();
|
||||
startup ();
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
message = e.what ();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
message = unexpected;
|
||||
}
|
||||
if (!message.isEmpty ())
|
||||
{
|
||||
offline (message);
|
||||
}
|
||||
}
|
||||
|
||||
void TransceiverBase::set (TransceiverState const& s,
|
||||
unsigned sequence_number) noexcept
|
||||
{
|
||||
TRACE_CAT ("TransceiverBase", "#:" << sequence_number << s);
|
||||
|
||||
QString message;
|
||||
try
|
||||
{
|
||||
last_sequence_number_ = sequence_number;
|
||||
may_update u {this, true};
|
||||
bool was_online {requested_.online ()};
|
||||
if (!s.online () && was_online)
|
||||
{
|
||||
shutdown ();
|
||||
}
|
||||
else if (s.online () && !was_online)
|
||||
{
|
||||
shutdown ();
|
||||
startup ();
|
||||
}
|
||||
if (requested_.online ())
|
||||
{
|
||||
bool ptt_on {false};
|
||||
bool ptt_off {false};
|
||||
if (s.ptt () != requested_.ptt ())
|
||||
{
|
||||
ptt_on = s.ptt ();
|
||||
ptt_off = !s.ptt ();
|
||||
}
|
||||
if (ptt_off)
|
||||
{
|
||||
do_ptt (false);
|
||||
do_post_ptt (false);
|
||||
QThread::msleep (100); // some rigs cannot process CAT
|
||||
// commands while switching from
|
||||
// Tx to Rx
|
||||
}
|
||||
if (s.frequency () // ignore bogus zero frequencies
|
||||
&& ((s.frequency () != requested_.frequency () // and QSY
|
||||
|| (s.mode () != UNK && s.mode () != requested_.mode ())) // or mode change
|
||||
|| ptt_off)) // or just returned to rx
|
||||
{
|
||||
do_frequency (s.frequency (), s.mode (), ptt_off);
|
||||
do_post_frequency (s.frequency (), s.mode ());
|
||||
|
||||
// record what actually changed
|
||||
requested_.frequency (actual_.frequency ());
|
||||
requested_.mode (actual_.mode ());
|
||||
}
|
||||
if (!s.tx_frequency ()
|
||||
|| (s.tx_frequency () > 10000 // ignore bogus startup values
|
||||
&& s.tx_frequency () < std::numeric_limits<Frequency>::max () - 10000))
|
||||
{
|
||||
if ((s.tx_frequency () != requested_.tx_frequency () // and QSY
|
||||
|| (s.mode () != UNK && s.mode () != requested_.mode ())) // or mode change
|
||||
// || s.split () != requested_.split ())) // or split change
|
||||
|| (s.tx_frequency () && ptt_on)) // or about to tx split
|
||||
{
|
||||
do_tx_frequency (s.tx_frequency (), s.mode (), ptt_on);
|
||||
do_post_tx_frequency (s.tx_frequency (), s.mode ());
|
||||
|
||||
// record what actually changed
|
||||
requested_.tx_frequency (actual_.tx_frequency ());
|
||||
requested_.split (actual_.split ());
|
||||
}
|
||||
}
|
||||
if (ptt_on)
|
||||
{
|
||||
do_ptt (true);
|
||||
do_post_ptt (true);
|
||||
QThread::msleep (100); // some rigs cannot process CAT
|
||||
// commands while switching from
|
||||
// Rx to Tx
|
||||
}
|
||||
|
||||
// record what actually changed
|
||||
requested_.ptt (actual_.ptt ());
|
||||
}
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
message = e.what ();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
message = unexpected;
|
||||
}
|
||||
if (!message.isEmpty ())
|
||||
{
|
||||
offline (message);
|
||||
}
|
||||
}
|
||||
|
||||
void TransceiverBase::startup ()
|
||||
{
|
||||
QString message;
|
||||
try
|
||||
{
|
||||
actual_.online (true);
|
||||
requested_.online (true);
|
||||
auto res = do_start ();
|
||||
do_post_start ();
|
||||
Q_EMIT resolution (res);
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
message = e.what ();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
message = unexpected;
|
||||
}
|
||||
if (!message.isEmpty ())
|
||||
{
|
||||
offline (message);
|
||||
}
|
||||
}
|
||||
|
||||
void TransceiverBase::shutdown ()
|
||||
{
|
||||
may_update u {this};
|
||||
if (requested_.online ())
|
||||
{
|
||||
try
|
||||
{
|
||||
// try and ensure PTT isn't left set
|
||||
do_ptt (false);
|
||||
do_post_ptt (false);
|
||||
if (requested_.split ())
|
||||
{
|
||||
// try and reset split mode
|
||||
do_tx_frequency (0, UNK, true);
|
||||
do_post_tx_frequency (0, UNK);
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// don't care about exceptions
|
||||
}
|
||||
}
|
||||
do_stop ();
|
||||
do_post_stop ();
|
||||
actual_ = TransceiverState {};
|
||||
requested_ = TransceiverState {};
|
||||
}
|
||||
|
||||
void TransceiverBase::stop () noexcept
|
||||
{
|
||||
QString message;
|
||||
try
|
||||
{
|
||||
shutdown ();
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
message = e.what ();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
message = unexpected;
|
||||
}
|
||||
if (!message.isEmpty ())
|
||||
{
|
||||
offline (message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_EMIT finished ();
|
||||
}
|
||||
}
|
||||
|
||||
void TransceiverBase::update_rx_frequency (Frequency rx)
|
||||
{
|
||||
if (rx)
|
||||
{
|
||||
actual_.frequency (rx);
|
||||
requested_.frequency (rx); // track rig changes
|
||||
}
|
||||
}
|
||||
|
||||
void TransceiverBase::update_other_frequency (Frequency tx)
|
||||
{
|
||||
actual_.tx_frequency (tx);
|
||||
}
|
||||
|
||||
void TransceiverBase::update_split (bool state)
|
||||
{
|
||||
actual_.split (state);
|
||||
}
|
||||
|
||||
void TransceiverBase::update_mode (MODE m)
|
||||
{
|
||||
actual_.mode (m);
|
||||
requested_.mode (m); // track rig changes
|
||||
}
|
||||
|
||||
void TransceiverBase::update_PTT (bool state)
|
||||
{
|
||||
actual_.ptt (state);
|
||||
}
|
||||
|
||||
void TransceiverBase::update_complete (bool force_signal)
|
||||
{
|
||||
if ((do_pre_update () && actual_ != last_) || force_signal)
|
||||
{
|
||||
Q_EMIT update (actual_, last_sequence_number_);
|
||||
last_ = actual_;
|
||||
}
|
||||
}
|
||||
|
||||
void TransceiverBase::offline (QString const& reason)
|
||||
{
|
||||
Q_EMIT failure (reason);
|
||||
try
|
||||
{
|
||||
shutdown ();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// don't care
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
#ifndef TRANSCEIVER_BASE_HPP__
|
||||
#define TRANSCEIVER_BASE_HPP__
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "Transceiver.hpp"
|
||||
|
||||
//
|
||||
// Base Transceiver Implementation
|
||||
//
|
||||
// Behaviour common to all Transceiver implementations.
|
||||
//
|
||||
// Collaborations
|
||||
//
|
||||
// Implements the Transceiver abstract interface as template methods
|
||||
// and provides a new abstract interface with similar functionality
|
||||
// (do_XXXXX operations). Provides and calls abstract interface that
|
||||
// gets called post the above operations (do_post_XXXXX) to allow
|
||||
// caching implementation etc.
|
||||
//
|
||||
// A key factor is to catch all exceptions thrown by sub-class
|
||||
// implementations where the template method is a Qt slot which is
|
||||
// therefore likely to be called by Qt which doesn't handle
|
||||
// exceptions. Any exceptions are converted to Transceiver::failure()
|
||||
// signals.
|
||||
//
|
||||
// Sub-classes update the stored state via a protected interface.
|
||||
//
|
||||
// Responsibilities:
|
||||
//
|
||||
// Wrap incoming Transceiver messages catching all exceptions in Qt
|
||||
// slot driven messages and converting them to Qt signals. This is
|
||||
// done because exceptions make concrete Transceiver implementations
|
||||
// simpler to write, but exceptions cannot cross signal/slot
|
||||
// boundaries (especially across threads). This also removes any
|
||||
// requirement for the client code to handle exceptions.
|
||||
//
|
||||
// Maintain the state of the concrete Transceiver instance that is
|
||||
// passed back via the Transceiver::update(TransceiverState) signal,
|
||||
// it is still the responsibility of concrete Transceiver
|
||||
// implementations to emit the state_change signal when they have a
|
||||
// status update.
|
||||
//
|
||||
// Maintain a go/no-go status for concrete Transceiver
|
||||
// implementations ensuring only a valid sequence of messages are
|
||||
// passed. A concrete Transceiver instance must be started before it
|
||||
// can receive messages, any exception thrown takes the Transceiver
|
||||
// offline.
|
||||
//
|
||||
// Implements methods that concrete Transceiver implementations use
|
||||
// to update the Transceiver state. These do not signal state change
|
||||
// to clients as this is the responsibility of the concrete
|
||||
// Transceiver implementation, thus allowing multiple state component
|
||||
// updates to be signalled together if required.
|
||||
//
|
||||
class TransceiverBase
|
||||
: public Transceiver
|
||||
{
|
||||
Q_OBJECT;
|
||||
|
||||
protected:
|
||||
TransceiverBase (QObject * parent)
|
||||
: Transceiver {parent}
|
||||
, last_sequence_number_ {0}
|
||||
{}
|
||||
|
||||
public:
|
||||
//
|
||||
// Implement the Transceiver abstract interface.
|
||||
//
|
||||
void start (unsigned sequence_number) noexcept override final;
|
||||
void set (TransceiverState const&,
|
||||
unsigned sequence_number) noexcept override final;
|
||||
void stop () noexcept override final;
|
||||
|
||||
//
|
||||
// Query operations
|
||||
//
|
||||
TransceiverState const& state () const {return actual_;}
|
||||
|
||||
protected:
|
||||
//
|
||||
// Error exception which is thrown to signal unexpected errors.
|
||||
//
|
||||
struct error
|
||||
: public std::runtime_error
|
||||
{
|
||||
explicit error (char const * const msg) : std::runtime_error (msg) {}
|
||||
explicit error (QString const& msg) : std::runtime_error (msg.toStdString ()) {}
|
||||
};
|
||||
|
||||
// Template methods that sub classes implement to do what they need to do.
|
||||
//
|
||||
// These methods may throw exceptions to signal errors.
|
||||
virtual int do_start () = 0; // returns resolution, See Transceiver::resolution
|
||||
virtual void do_post_start () {}
|
||||
|
||||
virtual void do_stop () = 0;
|
||||
virtual void do_post_stop () {}
|
||||
|
||||
virtual void do_frequency (Frequency, MODE, bool no_ignore) = 0;
|
||||
virtual void do_post_frequency (Frequency, MODE) {}
|
||||
|
||||
virtual void do_tx_frequency (Frequency, MODE, bool no_ignore) = 0;
|
||||
virtual void do_post_tx_frequency (Frequency, MODE) {}
|
||||
|
||||
virtual void do_mode (MODE) = 0;
|
||||
virtual void do_post_mode (MODE) {}
|
||||
|
||||
virtual void do_ptt (bool = true) = 0;
|
||||
virtual void do_post_ptt (bool = true) {}
|
||||
|
||||
virtual bool do_pre_update () {return true;}
|
||||
|
||||
// sub classes report rig state changes with these methods
|
||||
void update_rx_frequency (Frequency);
|
||||
void update_other_frequency (Frequency = 0);
|
||||
void update_split (bool);
|
||||
void update_mode (MODE);
|
||||
void update_PTT (bool = true);
|
||||
|
||||
// Calling this eventually triggers the Transceiver::update(State) signal.
|
||||
void update_complete (bool force_signal = false);
|
||||
|
||||
// sub class may asynchronously take the rig offline by calling this
|
||||
void offline (QString const& reason);
|
||||
|
||||
private:
|
||||
void startup ();
|
||||
void shutdown ();
|
||||
bool maybe_low_resolution (Frequency low_res, Frequency high_res);
|
||||
|
||||
// use this convenience class to notify in update methods
|
||||
class may_update
|
||||
{
|
||||
public:
|
||||
explicit may_update (TransceiverBase * self, bool force_signal = false)
|
||||
: self_ {self}
|
||||
, force_signal_ {force_signal}
|
||||
{}
|
||||
~may_update () {self_->update_complete (force_signal_);}
|
||||
private:
|
||||
TransceiverBase * self_;
|
||||
bool force_signal_;
|
||||
};
|
||||
|
||||
TransceiverState requested_;
|
||||
TransceiverState actual_;
|
||||
TransceiverState last_;
|
||||
unsigned last_sequence_number_; // from set state operation
|
||||
};
|
||||
|
||||
// some trace macros
|
||||
#if WSJT_TRACE_CAT
|
||||
#define TRACE_CAT(FAC, MSG) qDebug () << QString {"%1::%2:"}.arg ((FAC)).arg (__func__) << MSG
|
||||
#else
|
||||
#define TRACE_CAT(FAC, MSG)
|
||||
#endif
|
||||
|
||||
#if WSJT_TRACE_CAT && WSJT_TRACE_CAT_POLLS
|
||||
#define TRACE_CAT_POLL(FAC, MSG) qDebug () << QString {"%1::%2:"}.arg ((FAC)).arg (__func__) << MSG
|
||||
#else
|
||||
#define TRACE_CAT_POLL(FAC, MSG)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,212 @@
|
||||
#include "TransceiverFactory.hpp"
|
||||
|
||||
#include <QMetaType>
|
||||
|
||||
#include "HamlibTransceiver.hpp"
|
||||
#include "DXLabSuiteCommanderTransceiver.hpp"
|
||||
#include "HRDTransceiver.hpp"
|
||||
#include "EmulateSplitTransceiver.hpp"
|
||||
|
||||
#if defined (WIN32)
|
||||
#include "OmniRigTransceiver.hpp"
|
||||
#endif
|
||||
|
||||
#include "moc_TransceiverFactory.cpp"
|
||||
|
||||
// we use the hamlib "Hamlib Dummy" transceiver for non-CAT radios,
|
||||
// this allows us to still use the hamlib PTT control features for a
|
||||
// unified PTT control solution
|
||||
|
||||
char const * const TransceiverFactory::basic_transceiver_name_ = "None";
|
||||
|
||||
namespace
|
||||
{
|
||||
enum // supported non-hamlib radio interfaces
|
||||
{
|
||||
NonHamlibBaseId = 9899
|
||||
, CommanderId
|
||||
, HRDId
|
||||
, OmniRigOneId
|
||||
, OmniRigTwoId
|
||||
};
|
||||
}
|
||||
|
||||
TransceiverFactory::TransceiverFactory ()
|
||||
{
|
||||
HamlibTransceiver::register_transceivers (&transceivers_);
|
||||
DXLabSuiteCommanderTransceiver::register_transceivers (&transceivers_, CommanderId);
|
||||
HRDTransceiver::register_transceivers (&transceivers_, HRDId);
|
||||
|
||||
#if defined (WIN32)
|
||||
// OmniRig is ActiveX/COM server so only on Windows
|
||||
OmniRigTransceiver::register_transceivers (&transceivers_, OmniRigOneId, OmniRigTwoId);
|
||||
#endif
|
||||
}
|
||||
|
||||
TransceiverFactory::~TransceiverFactory ()
|
||||
{
|
||||
HamlibTransceiver::unregister_transceivers ();
|
||||
}
|
||||
|
||||
auto TransceiverFactory::supported_transceivers () const -> Transceivers const&
|
||||
{
|
||||
return transceivers_;
|
||||
}
|
||||
|
||||
auto TransceiverFactory::CAT_port_type (QString const& name) const -> Capabilities::PortType
|
||||
{
|
||||
return supported_transceivers ()[name].port_type_;
|
||||
}
|
||||
|
||||
bool TransceiverFactory::has_CAT_PTT (QString const& name) const
|
||||
{
|
||||
return
|
||||
supported_transceivers ()[name].has_CAT_PTT_
|
||||
|| supported_transceivers ()[name].model_number_ > NonHamlibBaseId;
|
||||
}
|
||||
|
||||
bool TransceiverFactory::has_CAT_PTT_mic_data (QString const& name) const
|
||||
{
|
||||
return supported_transceivers ()[name].has_CAT_PTT_mic_data_;
|
||||
}
|
||||
|
||||
bool TransceiverFactory::has_CAT_indirect_serial_PTT (QString const& name) const
|
||||
{
|
||||
return supported_transceivers ()[name].has_CAT_indirect_serial_PTT_;
|
||||
}
|
||||
|
||||
bool TransceiverFactory::has_asynchronous_CAT (QString const& name) const
|
||||
{
|
||||
return supported_transceivers ()[name].asynchronous_;
|
||||
}
|
||||
|
||||
std::unique_ptr<Transceiver> TransceiverFactory::create (ParameterPack const& params, QThread * target_thread)
|
||||
{
|
||||
std::unique_ptr<Transceiver> result;
|
||||
switch (supported_transceivers ()[params.rig_name].model_number_)
|
||||
{
|
||||
case CommanderId:
|
||||
{
|
||||
std::unique_ptr<TransceiverBase> basic_transceiver;
|
||||
if (PTT_method_CAT != params.ptt_type)
|
||||
{
|
||||
// we start with a dummy HamlibTransceiver object instance that can support direct PTT
|
||||
basic_transceiver.reset (new HamlibTransceiver {params.ptt_type, params.ptt_port});
|
||||
if (target_thread)
|
||||
{
|
||||
basic_transceiver.get ()->moveToThread (target_thread);
|
||||
}
|
||||
}
|
||||
|
||||
// wrap the basic Transceiver object instance with a decorator object that talks to DX Lab Suite Commander
|
||||
result.reset (new DXLabSuiteCommanderTransceiver {std::move (basic_transceiver), params.network_port, PTT_method_CAT == params.ptt_type, params.poll_interval});
|
||||
if (target_thread)
|
||||
{
|
||||
result->moveToThread (target_thread);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case HRDId:
|
||||
{
|
||||
std::unique_ptr<TransceiverBase> basic_transceiver;
|
||||
if (PTT_method_CAT != params.ptt_type)
|
||||
{
|
||||
// we start with a dummy HamlibTransceiver object instance that can support direct PTT
|
||||
basic_transceiver.reset (new HamlibTransceiver {params.ptt_type, params.ptt_port});
|
||||
if (target_thread)
|
||||
{
|
||||
basic_transceiver.get ()->moveToThread (target_thread);
|
||||
}
|
||||
}
|
||||
|
||||
// wrap the basic Transceiver object instance with a decorator object that talks to ham Radio Deluxe
|
||||
result.reset (new HRDTransceiver {std::move (basic_transceiver), params.network_port, PTT_method_CAT == params.ptt_type, params.audio_source, params.poll_interval});
|
||||
if (target_thread)
|
||||
{
|
||||
result->moveToThread (target_thread);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
#if defined (WIN32)
|
||||
case OmniRigOneId:
|
||||
{
|
||||
std::unique_ptr<TransceiverBase> basic_transceiver;
|
||||
if (PTT_method_CAT != params.ptt_type && "CAT" != params.ptt_port)
|
||||
{
|
||||
// we start with a dummy HamlibTransceiver object instance that can support direct PTT
|
||||
basic_transceiver.reset (new HamlibTransceiver {params.ptt_type, params.ptt_port});
|
||||
if (target_thread)
|
||||
{
|
||||
basic_transceiver.get ()->moveToThread (target_thread);
|
||||
}
|
||||
}
|
||||
|
||||
// wrap the basic Transceiver object instance with a decorator object that talks to OmniRig rig one
|
||||
result.reset (new OmniRigTransceiver {std::move (basic_transceiver), OmniRigTransceiver::One, params.ptt_type, params.ptt_port});
|
||||
if (target_thread)
|
||||
{
|
||||
result->moveToThread (target_thread);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OmniRigTwoId:
|
||||
{
|
||||
std::unique_ptr<TransceiverBase> basic_transceiver;
|
||||
if (PTT_method_CAT != params.ptt_type && "CAT" != params.ptt_port)
|
||||
{
|
||||
// we start with a dummy HamlibTransceiver object instance that can support direct PTT
|
||||
basic_transceiver.reset (new HamlibTransceiver {params.ptt_type, params.ptt_port});
|
||||
if (target_thread)
|
||||
{
|
||||
basic_transceiver.get ()->moveToThread (target_thread);
|
||||
}
|
||||
}
|
||||
|
||||
// wrap the basic Transceiver object instance with a decorator object that talks to OmniRig rig two
|
||||
result.reset (new OmniRigTransceiver {std::move (basic_transceiver), OmniRigTransceiver::Two, params.ptt_type, params.ptt_port});
|
||||
if (target_thread)
|
||||
{
|
||||
result->moveToThread (target_thread);
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
result.reset (new HamlibTransceiver {supported_transceivers ()[params.rig_name].model_number_, params});
|
||||
if (target_thread)
|
||||
{
|
||||
result->moveToThread (target_thread);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (split_mode_emulate == params.split_mode)
|
||||
{
|
||||
// wrap the Transceiver object instance with a decorator that emulates split mode
|
||||
result.reset (new EmulateSplitTransceiver {std::move (result)});
|
||||
if (target_thread)
|
||||
{
|
||||
result->moveToThread (target_thread);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ENUM_QDATASTREAM_OPS_IMPL (TransceiverFactory, DataBits);
|
||||
ENUM_QDATASTREAM_OPS_IMPL (TransceiverFactory, StopBits);
|
||||
ENUM_QDATASTREAM_OPS_IMPL (TransceiverFactory, Handshake);
|
||||
ENUM_QDATASTREAM_OPS_IMPL (TransceiverFactory, PTTMethod);
|
||||
ENUM_QDATASTREAM_OPS_IMPL (TransceiverFactory, TXAudioSource);
|
||||
ENUM_QDATASTREAM_OPS_IMPL (TransceiverFactory, SplitMode);
|
||||
|
||||
ENUM_CONVERSION_OPS_IMPL (TransceiverFactory, DataBits);
|
||||
ENUM_CONVERSION_OPS_IMPL (TransceiverFactory, StopBits);
|
||||
ENUM_CONVERSION_OPS_IMPL (TransceiverFactory, Handshake);
|
||||
ENUM_CONVERSION_OPS_IMPL (TransceiverFactory, PTTMethod);
|
||||
ENUM_CONVERSION_OPS_IMPL (TransceiverFactory, TXAudioSource);
|
||||
ENUM_CONVERSION_OPS_IMPL (TransceiverFactory, SplitMode);
|
||||
@@ -0,0 +1,177 @@
|
||||
#ifndef TRANSCEIVER_FACTORY_HPP__
|
||||
#define TRANSCEIVER_FACTORY_HPP__
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QObject>
|
||||
#include <QMap>
|
||||
|
||||
#include "Transceiver.hpp"
|
||||
|
||||
#include "qt_helpers.hpp"
|
||||
|
||||
class QString;
|
||||
class QThread;
|
||||
class QDir;
|
||||
|
||||
//
|
||||
// Transceiver Factory
|
||||
//
|
||||
class TransceiverFactory
|
||||
: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
//
|
||||
// Capabilities of a Transceiver that can be determined without
|
||||
// actually instantiating one, these are for use in Configuration
|
||||
// GUI behaviour determination
|
||||
//
|
||||
struct Capabilities
|
||||
{
|
||||
enum PortType {none, serial, network, usb};
|
||||
|
||||
explicit Capabilities (int model_number = 0
|
||||
, PortType port_type = none
|
||||
, bool has_CAT_PTT = false
|
||||
, bool has_CAT_PTT_mic_data = false
|
||||
, bool has_CAT_indirect_serial_PTT = false
|
||||
, bool asynchronous = false)
|
||||
: model_number_ {model_number}
|
||||
, port_type_ {port_type}
|
||||
, has_CAT_PTT_ {has_CAT_PTT}
|
||||
, has_CAT_PTT_mic_data_ {has_CAT_PTT_mic_data}
|
||||
, has_CAT_indirect_serial_PTT_ {has_CAT_indirect_serial_PTT}
|
||||
, asynchronous_ {asynchronous}
|
||||
{
|
||||
}
|
||||
|
||||
int model_number_;
|
||||
PortType port_type_;
|
||||
bool has_CAT_PTT_;
|
||||
bool has_CAT_PTT_mic_data_;
|
||||
bool has_CAT_indirect_serial_PTT_; // OmniRig controls RTS/DTR via COM interface
|
||||
bool asynchronous_;
|
||||
};
|
||||
|
||||
//
|
||||
// Dictionary of Transceiver types Capabilities
|
||||
//
|
||||
typedef QMap<QString, Capabilities> Transceivers;
|
||||
|
||||
//
|
||||
// various Transceiver parameters
|
||||
//
|
||||
enum DataBits {seven_data_bits = 7, eight_data_bits, default_data_bits};
|
||||
Q_ENUM (DataBits)
|
||||
enum StopBits {one_stop_bit = 1, two_stop_bits, default_stop_bits};
|
||||
Q_ENUM (StopBits)
|
||||
enum Handshake {handshake_default, handshake_none, handshake_XonXoff, handshake_hardware};
|
||||
Q_ENUM (Handshake)
|
||||
enum PTTMethod {PTT_method_VOX, PTT_method_CAT, PTT_method_DTR, PTT_method_RTS};
|
||||
Q_ENUM (PTTMethod)
|
||||
enum TXAudioSource {TX_audio_source_front, TX_audio_source_rear};
|
||||
Q_ENUM (TXAudioSource)
|
||||
enum SplitMode {split_mode_none, split_mode_rig, split_mode_emulate};
|
||||
Q_ENUM (SplitMode)
|
||||
|
||||
TransceiverFactory ();
|
||||
~TransceiverFactory ();
|
||||
|
||||
static char const * const basic_transceiver_name_; // dummy transceiver is basic model
|
||||
|
||||
//
|
||||
// fetch all supported rigs as a list of name and model id
|
||||
//
|
||||
Transceivers const& supported_transceivers () const;
|
||||
|
||||
// supported model queries
|
||||
Capabilities::PortType CAT_port_type (QString const& name) const; // how to talk to CAT
|
||||
bool has_CAT_PTT (QString const& name) const; // can be keyed via CAT
|
||||
bool has_CAT_PTT_mic_data (QString const& name) const; // Tx audio port is switchable via CAT
|
||||
bool has_CAT_indirect_serial_PTT (QString const& name) const; // Can PTT via CAT port use DTR or RTS (OmniRig for example)
|
||||
bool has_asynchronous_CAT (QString const& name) const; // CAT asynchronous rather than polled
|
||||
|
||||
struct ParameterPack
|
||||
{
|
||||
QString rig_name; // from supported_transceivers () key
|
||||
QString serial_port; // serial port device name or empty
|
||||
QString network_port; // hostname:port or empty
|
||||
QString usb_port; // [vid[:pid[:vendor[:product]]]]
|
||||
int baud;
|
||||
DataBits data_bits;
|
||||
StopBits stop_bits;
|
||||
Handshake handshake;
|
||||
bool force_dtr;
|
||||
bool dtr_high; // to power interface
|
||||
bool force_rts;
|
||||
bool rts_high; // to power interface
|
||||
PTTMethod ptt_type; // "CAT" | "DTR" | "RTS" | "VOX"
|
||||
TXAudioSource audio_source; // some rigs allow audio routing
|
||||
// to Mic/Data connector
|
||||
SplitMode split_mode; // how to support split TX mode
|
||||
QString ptt_port; // serial port device name or special
|
||||
// value "CAT"
|
||||
int poll_interval; // in seconds for interfaces that
|
||||
// require polling for state changes
|
||||
|
||||
bool operator == (ParameterPack const& rhs) const
|
||||
{
|
||||
return rhs.rig_name == rig_name
|
||||
&& rhs.serial_port == serial_port
|
||||
&& rhs.network_port == network_port
|
||||
&& rhs.usb_port == usb_port
|
||||
&& rhs.baud == baud
|
||||
&& rhs.data_bits == data_bits
|
||||
&& rhs.stop_bits == stop_bits
|
||||
&& rhs.handshake == handshake
|
||||
&& rhs.force_dtr == force_dtr
|
||||
&& rhs.dtr_high == dtr_high
|
||||
&& rhs.force_rts == force_rts
|
||||
&& rhs.rts_high == rts_high
|
||||
&& rhs.ptt_type == ptt_type
|
||||
&& rhs.audio_source == audio_source
|
||||
&& rhs.split_mode == split_mode
|
||||
&& rhs.ptt_port == ptt_port
|
||||
&& rhs.poll_interval == poll_interval
|
||||
;
|
||||
}
|
||||
};
|
||||
|
||||
// make a new Transceiver instance
|
||||
//
|
||||
// cat_port, cat_baud, cat_data_bits, cat_stop_bits, cat_handshake,
|
||||
// cat_dtr_control, cat_rts_control are only relevant to interfaces
|
||||
// that are served by Hamlib
|
||||
//
|
||||
// PTT port and to some extent ptt_type are independent of interface
|
||||
// type
|
||||
//
|
||||
std::unique_ptr<Transceiver> create (ParameterPack const&, QThread * target_thread = nullptr);
|
||||
|
||||
private:
|
||||
Transceivers transceivers_;
|
||||
};
|
||||
|
||||
inline
|
||||
bool operator != (TransceiverFactory::ParameterPack const& lhs, TransceiverFactory::ParameterPack const& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
ENUM_QDATASTREAM_OPS_DECL (TransceiverFactory, DataBits);
|
||||
ENUM_QDATASTREAM_OPS_DECL (TransceiverFactory, StopBits);
|
||||
ENUM_QDATASTREAM_OPS_DECL (TransceiverFactory, Handshake);
|
||||
ENUM_QDATASTREAM_OPS_DECL (TransceiverFactory, PTTMethod);
|
||||
ENUM_QDATASTREAM_OPS_DECL (TransceiverFactory, TXAudioSource);
|
||||
ENUM_QDATASTREAM_OPS_DECL (TransceiverFactory, SplitMode);
|
||||
|
||||
ENUM_CONVERSION_OPS_DECL (TransceiverFactory, DataBits);
|
||||
ENUM_CONVERSION_OPS_DECL (TransceiverFactory, StopBits);
|
||||
ENUM_CONVERSION_OPS_DECL (TransceiverFactory, Handshake);
|
||||
ENUM_CONVERSION_OPS_DECL (TransceiverFactory, PTTMethod);
|
||||
ENUM_CONVERSION_OPS_DECL (TransceiverFactory, TXAudioSource);
|
||||
ENUM_CONVERSION_OPS_DECL (TransceiverFactory, SplitMode);
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user