diff --git a/core/Migration.h b/core/Migration.h index 163b32c9..ac969be8 100644 --- a/core/Migration.h +++ b/core/Migration.h @@ -14,7 +14,7 @@ class DBSchemaMigration : public QObject bool run(bool force = false); static bool backupAllQSOsToADX(bool force = false); - static constexpr int latestVersion = 41; + static constexpr int latestVersion = 42; private: bool functionMigration(int version); diff --git a/data/RigProfile.cpp b/data/RigProfile.cpp index 0d0c2356..7d7e53d2 100644 --- a/data/RigProfile.cpp +++ b/data/RigProfile.cpp @@ -23,7 +23,7 @@ QDataStream& operator<<(QDataStream& out, const RigProfile& v) << v.getKeySpeed << v.assignedCWKey << v.keySpeedSync << v.driver << v.dxSpot2Rig << v.pttType << v.pttPortPath << v.rts << v.dtr << v.civAddr - << v.shareRigctld << v.rigctldPort << v.rigctldPath << v.rigctldArgs << v.getSplitInfo; + << v.shareRigctld << v.rigctldPort << v.rigctldPath << v.rigctldArgs << v.getSplitInfo << v.spotTuneCommand; return out; } @@ -69,6 +69,7 @@ QDataStream& operator>>(QDataStream& in, RigProfile& v) in >> v.rigctldPath; in >> v.rigctldArgs; in >> v.getSplitInfo; + in >> v.spotTuneCommand; return in; } @@ -88,7 +89,8 @@ RigProfilesManager::RigProfilesManager() : "key_speed_sync, driver, dxspot2rig, ptt_type, ptt_port_pathname, " "IFNULL(rts, '%0'), IFNULL(dtr, '%0'), IFNULL(civaddr, -1), " "IFNULL(share_rigctld, 0), IFNULL(rigctld_port, 4532), " - "IFNULL(rigctld_path, ''), IFNULL(rigctld_args, ''), get_split " + "IFNULL(rigctld_path, ''), IFNULL(rigctld_args, ''), get_split, " + "IFNULL(spot_tune_command, '') " "FROM rig_profiles").arg(SerialPort::SERIAL_SIGNAL_NONE))) { qWarning()<< "Cannot prepare select"; @@ -138,6 +140,7 @@ RigProfilesManager::RigProfilesManager() : profileDB.rigctldPath = profileQuery.value(36).toString(); profileDB.rigctldArgs = profileQuery.value(37).toString(); profileDB.getSplitInfo = profileQuery.value(38).toBool(); + profileDB.spotTuneCommand = profileQuery.value(39).toString(); addProfile(profileDB.profileName, profileDB); } @@ -166,13 +169,13 @@ void RigProfilesManager::save() "txfreq_end, get_freq, get_mode, get_vfo, get_pwr, rit_offset, xit_offset, get_rit, " "get_xit, default_pwr, get_ptt, qsy_wiping, get_key_speed, assigned_cw_key, key_speed_sync, " "driver, dxSpot2Rig, ptt_type, ptt_port_pathname, rts, dtr, civaddr, " - "share_rigctld, rigctld_port, rigctld_path, rigctld_args, get_split ) " + "share_rigctld, rigctld_port, rigctld_path, rigctld_args, get_split, spot_tune_command ) " "VALUES (:profile_name, :model, :port_pathname, :hostname, :netport, " ":baudrate, :databits, :stopbits, :flowcontrol, :parity, :pollinterval, :txfreq_start, " ":txfreq_end, :get_freq, :get_mode, :get_vfo, :get_pwr, :rit_offset, :xit_offset, :get_rit, " ":get_xit, :default_pwr, :get_ptt, :qsy_wiping, :get_key_speed, :assigned_cw_key, :key_speed_sync, " ":driver, :dxSpot2Rig, :ptt_type, :ptt_port_pathname, :rts, :dtr, :civaddr, " - ":share_rigctld, :rigctld_port, :rigctld_path, :rigctld_args, :get_split )") ) + ":share_rigctld, :rigctld_port, :rigctld_path, :rigctld_args, :get_split, :spot_tune_command )") ) { qWarning() << "cannot prepare Insert statement"; return; @@ -224,6 +227,7 @@ void RigProfilesManager::save() insertQuery.bindValue(":rigctld_path", rigProfile.rigctldPath); insertQuery.bindValue(":rigctld_args", rigProfile.rigctldArgs); insertQuery.bindValue(":get_split", rigProfile.getSplitInfo); + insertQuery.bindValue(":spot_tune_command", rigProfile.spotTuneCommand); if ( ! insertQuery.exec() ) { @@ -280,6 +284,7 @@ bool RigProfile::operator==(const RigProfile &profile) && profile.rigctldPath == this->rigctldPath && profile.rigctldArgs == this->rigctldArgs && profile.getSplitInfo == this->getSplitInfo + && profile.spotTuneCommand == this->spotTuneCommand ); } diff --git a/data/RigProfile.h b/data/RigProfile.h index d8d83af7..5da0d808 100644 --- a/data/RigProfile.h +++ b/data/RigProfile.h @@ -102,6 +102,7 @@ class RigProfile bool dxSpot2Rig; bool shareRigctld; bool getSplitInfo; + QString spotTuneCommand; bool operator== (const RigProfile &profile); bool operator!= (const RigProfile &profile); diff --git a/res/res.qrc b/res/res.qrc index 58a291ea..b36d73b5 100644 --- a/res/res.qrc +++ b/res/res.qrc @@ -56,5 +56,6 @@ sql/migration_039.sql sql/migration_040.sql sql/migration_041.sql + sql/migration_042.sql diff --git a/res/sql/migration_042.sql b/res/sql/migration_042.sql new file mode 100644 index 00000000..8a9d89b3 --- /dev/null +++ b/res/sql/migration_042.sql @@ -0,0 +1 @@ +ALTER TABLE rig_profiles ADD COLUMN spot_tune_command TEXT; diff --git a/rig/Rig.cpp b/rig/Rig.cpp index 9fb9426e..24ddd78e 100644 --- a/rig/Rig.cpp +++ b/rig/Rig.cpp @@ -742,6 +742,30 @@ void Rig::sendDXSpotImpl(const DxSpot &spot) rigDriver->sendDXSpot(spot); } +void Rig::sendRawCommand(const QString &command) +{ + FCT_IDENTIFICATION; + + qCDebug(function_parameters) << command; + + QMetaObject::invokeMethod(this, "sendRawCommandImpl", Qt::QueuedConnection, + Q_ARG(QString, command)); +} + +void Rig::sendRawCommandImpl(const QString &command) +{ + FCT_IDENTIFICATION; + + qCDebug(function_parameters) << command; + + MUTEXLOCKER; + + if ( ! rigDriver ) + return; + + rigDriver->sendRawCommand(command); +} + GenericRigDrv *Rig::getDriver(const RigProfile &profile, qint32 controlledRigModel) { FCT_IDENTIFICATION; diff --git a/rig/Rig.h b/rig/Rig.h index ba62eaee..ed9d1cf2 100644 --- a/rig/Rig.h +++ b/rig/Rig.h @@ -106,6 +106,7 @@ public slots: void stopMorse(); void sendState(); void sendDXSpot(DxSpot spot); + void sendRawCommand(const QString &command); signals: void frequencyChanged(VFOID, double, double, double); @@ -142,6 +143,7 @@ private slots: void stopMorseImpl(); void sendStateImpl(); void sendDXSpotImpl(const DxSpot &spot); + void sendRawCommandImpl(const QString &command); void sendHeartBeat(); private: diff --git a/rig/drivers/GenericRigDrv.cpp b/rig/drivers/GenericRigDrv.cpp index e28e1986..4b0fd8d5 100644 --- a/rig/drivers/GenericRigDrv.cpp +++ b/rig/drivers/GenericRigDrv.cpp @@ -44,3 +44,12 @@ void GenericRigDrv::setSplit(bool) // Default: do nothing — driver does not support split } + +void GenericRigDrv::sendRawCommand(const QString &command) +{ + FCT_IDENTIFICATION; + + qCDebug(function_parameters) << command; + + // Default: do nothing - driver has no raw command channel +} diff --git a/rig/drivers/GenericRigDrv.h b/rig/drivers/GenericRigDrv.h index 37024064..b9ddf9e2 100644 --- a/rig/drivers/GenericRigDrv.h +++ b/rig/drivers/GenericRigDrv.h @@ -41,6 +41,7 @@ class GenericRigDrv : public QObject virtual void sendState() = 0; virtual void stopTimers() = 0; virtual void sendDXSpot(const DxSpot &spot) = 0; + virtual void sendRawCommand(const QString &command); signals: // STATE Signals diff --git a/rig/drivers/HamlibRigDrv.cpp b/rig/drivers/HamlibRigDrv.cpp index 5189c785..76f55749 100644 --- a/rig/drivers/HamlibRigDrv.cpp +++ b/rig/drivers/HamlibRigDrv.cpp @@ -1,4 +1,5 @@ #include +#include #include #include "HamlibRigDrv.h" @@ -462,6 +463,60 @@ void HamlibRigDrv::setSplit(bool enabled) commandSleep(); } +void HamlibRigDrv::sendRawCommand(const QString &command) +{ + FCT_IDENTIFICATION; + + qCDebug(function_parameters) << command; + + MUTEXLOCKER; + + if ( !rig ) + { + qCWarning(runtime) << "Rig is not active"; + return; + } + + if ( rigProfile.model == RIG_MODEL_NETRIGCTL ) + { + // The Hamlib link speaks the rigctld protocol, so raw bytes cannot go + // down that socket. rigctld takes further clients: a short second + // connection forwards the command with rigctld's "send_cmd_rx" and + // 0 reply bytes, so a set command returns at once and the protocol + // state of the Hamlib link is not touched. + QTcpSocket socket; + + socket.connectToHost(rigProfile.hostname, rigProfile.netport); + + if ( !socket.waitForConnected(1000) ) + { + qCWarning(runtime) << "Raw command failed, rigctld not reachable" << command << socket.errorString(); + return; + } + + socket.write(QString("W %1 0\n").arg(command).toLatin1()); + socket.waitForBytesWritten(1000); + socket.waitForReadyRead(1000); // rigctld's answer line, nothing to evaluate for a set command + socket.disconnectFromHost(); + } + else + { +#if HAMLIB_VERSION >= HAMLIB_VERSION_CHECK(4,5,0) + const QByteArray bytes = command.toLatin1(); + const int status = rig_send_raw(rig, + reinterpret_cast(bytes.constData()), bytes.size(), + nullptr, 0, nullptr); + + if ( status < 0 ) + qCWarning(runtime) << "Raw command failed" << command << rigerror(status); +#else + qCWarning(runtime) << "Raw CAT commands need Hamlib 4.5 or newer" << command; +#endif + } + + commandSleep(); +} + void HamlibRigDrv::setRawMode(const QString &rawMode) { FCT_IDENTIFICATION; diff --git a/rig/drivers/HamlibRigDrv.h b/rig/drivers/HamlibRigDrv.h index 91dda611..5e15d814 100644 --- a/rig/drivers/HamlibRigDrv.h +++ b/rig/drivers/HamlibRigDrv.h @@ -41,6 +41,7 @@ class HamlibRigDrv : public GenericRigDrv virtual void sendState() override; virtual void stopTimers() override; virtual void sendDXSpot(const DxSpot &spot) override; + virtual void sendRawCommand(const QString &command) override; private slots: void checkRigStateChange(); diff --git a/ui/NewContactWidget.cpp b/ui/NewContactWidget.cpp index 456d0ddb..4f5110a2 100644 --- a/ui/NewContactWidget.cpp +++ b/ui/NewContactWidget.cpp @@ -3550,6 +3550,14 @@ void NewContactWidget::tuneDx(const DxSpot &spot) } emit userModeChanged(VFO1, QString(), mode, subMode, bandwidthFilter); } + + // The rig profile may name a raw CAT command for the end of a spot + // tune, e.g. "SY2;" lets a Yaesu FTDX101 take its second receiver + // along to the same frequency and mode. + const QString &spotTuneCommand = RigProfilesManager::instance()->getCurProfile1().spotTuneCommand; + + if ( !spotTuneCommand.isEmpty() ) + rig->sendRawCommand(spotTuneCommand); } resetContact(); diff --git a/ui/SettingsDialog.cpp b/ui/SettingsDialog.cpp index aeb58b17..0407b943 100644 --- a/ui/SettingsDialog.cpp +++ b/ui/SettingsDialog.cpp @@ -998,6 +998,7 @@ void SettingsDialog::addRigProfile() profile.getKeySpeed = ui->rigGetKeySpeedCheckBox->isChecked(); profile.keySpeedSync = ui->rigKeySpeedSyncCheckBox->isChecked(); profile.dxSpot2Rig = ui->rigDXSpots2RigCheckBox->isChecked(); + profile.spotTuneCommand = ui->rigSpotTuneCommandEdit->text().trimmed(); profile.getSplitInfo = ui->rigGetSplitCheckBox->isChecked(); // Rigctld sharing settings @@ -1076,6 +1077,7 @@ void SettingsDialog::doubleClickRigProfile(QModelIndex i) ui->rigGetKeySpeedCheckBox->setChecked(profile.getKeySpeed); ui->rigKeySpeedSyncCheckBox->setChecked(profile.keySpeedSync); ui->rigDXSpots2RigCheckBox->setChecked(profile.dxSpot2Rig); + ui->rigSpotTuneCommandEdit->setText(profile.spotTuneCommand); ui->rigGetSplitCheckBox->setChecked(profile.getSplitInfo); setComboByData(ui->rigFlowControlSelect, profile.flowcontrol.toLower()); diff --git a/ui/SettingsDialog.ui b/ui/SettingsDialog.ui index 8903b712..1f0e0605 100644 --- a/ui/SettingsDialog.ui +++ b/ui/SettingsDialog.ui @@ -1990,6 +1990,23 @@ + + + + CAT after spot tuning: + + + + + + + Raw CAT command sent to the rig after QLog has tuned it to a spot. Example for the Yaesu FTDX101: SY2; synchronizes the second receiver (frequency and mode) with the main receiver. Leave empty if not needed. + + + e.g. SY2; + + +