mirror of
https://github.com/f4exb/sdrangel.git
synced 2026-03-31 20:25:38 -04:00
Meshtastic demod: implemented USER preset allowing arbitrary modulation characteristics and frequencies
This commit is contained in:
parent
3c0277bfd8
commit
fc23cd2cc8
@ -326,6 +326,16 @@ std::vector<MeshtasticDemod::PipelineConfig> MeshtasticDemod::buildPipelineConfi
|
||||
|
||||
void MeshtasticDemod::makePipelineConfigFromSettings(int configId, PipelineConfig& config, const MeshtasticDemodSettings& settings) const
|
||||
{
|
||||
// USER preset: all LoRa parameters are user-controlled; skip derivation from the mesh radio table.
|
||||
if (settings.m_meshtasticPresetName.trimmed().compare("USER", Qt::CaseInsensitive) == 0)
|
||||
{
|
||||
config.id = configId;
|
||||
config.name = QString("CONF%1").arg(configId);
|
||||
config.presetName = settings.m_meshtasticPresetName;
|
||||
config.settings = settings;
|
||||
return;
|
||||
}
|
||||
|
||||
const QString region = settings.m_meshtasticRegionCode.trimmed().isEmpty()
|
||||
? QString("US")
|
||||
: settings.m_meshtasticRegionCode.trimmed();
|
||||
@ -923,8 +933,10 @@ void MeshtasticDemod::applySettings(MeshtasticDemodSettings settings, bool force
|
||||
|
||||
// Copy LoRa params derived from the preset (bandwidth, spread factor, etc.) back into
|
||||
// settings so that m_settings and the GUI stay in sync with what was actually applied.
|
||||
// Skip for USER preset: those parameters are controlled entirely by the user via the GUI.
|
||||
if (m_running && !m_pipelineConfigs.empty() &&
|
||||
settings.m_codingScheme == MeshtasticDemodSettings::CodingLoRa)
|
||||
settings.m_codingScheme == MeshtasticDemodSettings::CodingLoRa &&
|
||||
settings.m_meshtasticPresetName.trimmed().compare("USER", Qt::CaseInsensitive) != 0)
|
||||
{
|
||||
const MeshtasticDemodSettings& derived = m_pipelineConfigs[0].settings;
|
||||
const bool bwChanged = (settings.m_bandwidthIndex != derived.m_bandwidthIndex);
|
||||
@ -964,8 +976,12 @@ void MeshtasticDemod::applySettings(MeshtasticDemodSettings settings, bool force
|
||||
m_settings = settings;
|
||||
|
||||
// Forward preset-derived settings back to GUI so controls (e.g. BW slider) reflect
|
||||
// the values actually applied.
|
||||
if (getMessageQueueToGUI())
|
||||
// the values actually applied. Skip for USER preset: no parameters were derived, so
|
||||
// there is nothing to sync back — and echoing would trigger an infinite apply loop
|
||||
// (GUI apply → demod echo → GUI displaySettings → rebuildMeshtasticChannelOptions
|
||||
// → queued apply → …).
|
||||
const bool isUserPreset = m_settings.m_meshtasticPresetName.trimmed().compare("USER", Qt::CaseInsensitive) == 0;
|
||||
if (!isUserPreset && getMessageQueueToGUI())
|
||||
{
|
||||
MsgConfigureMeshtasticDemod *msgToGUI = MsgConfigureMeshtasticDemod::create(m_settings, false);
|
||||
getMessageQueueToGUI()->push(msgToGUI);
|
||||
|
||||
@ -59,6 +59,8 @@
|
||||
#include "util/db.h"
|
||||
#include "maincore.h"
|
||||
|
||||
#include <QGraphicsOpacityEffect>
|
||||
|
||||
#include "meshtasticdemod.h"
|
||||
#include "meshtasticdemodmsg.h"
|
||||
#include "meshtasticdemodgui.h"
|
||||
@ -385,6 +387,12 @@ void MeshtasticDemodGUI::on_meshPreset_currentIndexChanged(int index)
|
||||
return;
|
||||
}
|
||||
|
||||
ui->meshRegion->setEnabled(index != ui->meshPreset->count() - 1); // USER preset has no region and is the last item
|
||||
ui->BW->setEnabled(index == ui->meshPreset->count() - 1); // USER preset has user-defined bandwidth and is the last item
|
||||
ui->Spread->setEnabled(index == ui->meshPreset->count() - 1); // USER preset has user-defined spread and is the last item
|
||||
ui->deBits->setEnabled(index == ui->meshPreset->count() - 1); // USER preset has user-defined deBits and is the last item
|
||||
ui->preambleChirps->setEnabled(index == ui->meshPreset->count() - 1); // USER preset has user-defined preambleChirps and is the last item
|
||||
|
||||
rebuildMeshtasticChannelOptions();
|
||||
applyMeshtasticProfileFromSelection();
|
||||
}
|
||||
@ -984,7 +992,7 @@ bool MeshtasticDemodGUI::retuneDeviceToFrequency(qint64 centerFrequencyHz)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MeshtasticDemodGUI::autoTuneDeviceSampleRateForBandwidth(int bandwidthHz, QString& summary)
|
||||
bool MeshtasticDemodGUI::autoTuneDeviceSampleRateForBandwidth(int bandwidthHz, QString& summary, int* newBasebandSampleRateOut)
|
||||
{
|
||||
summary.clear();
|
||||
|
||||
@ -1133,6 +1141,10 @@ bool MeshtasticDemodGUI::autoTuneDeviceSampleRateForBandwidth(int bandwidthHz, Q
|
||||
}
|
||||
}
|
||||
|
||||
if (newBasebandSampleRateOut) {
|
||||
*newBasebandSampleRateOut = finalEffectiveRate;
|
||||
}
|
||||
|
||||
const bool belowTarget = finalEffectiveRate < minEffectiveRate;
|
||||
const bool changed = (finalDevSampleRate != initialDevSampleRate)
|
||||
|| (finalLog2Decim != initialLog2Decim)
|
||||
@ -1174,6 +1186,53 @@ void MeshtasticDemodGUI::applyMeshtasticProfileFromSelection()
|
||||
return;
|
||||
}
|
||||
|
||||
// USER preset: all LoRa parameters and frequency are controlled manually from the GUI.
|
||||
// Skip auto-configuration entirely; just persist the selection and optionally auto-tune sample rate.
|
||||
if (preset == "USER")
|
||||
{
|
||||
bool selectionStateChanged = false;
|
||||
|
||||
if (m_settings.m_meshtasticRegionCode != region)
|
||||
{
|
||||
m_settings.m_meshtasticRegionCode = region;
|
||||
selectionStateChanged = true;
|
||||
}
|
||||
|
||||
if (m_settings.m_meshtasticPresetName != preset)
|
||||
{
|
||||
m_settings.m_meshtasticPresetName = preset;
|
||||
selectionStateChanged = true;
|
||||
}
|
||||
|
||||
const int thisBW = MeshtasticDemodSettings::bandwidths[m_settings.m_bandwidthIndex];
|
||||
QString sampleRateSummary;
|
||||
bool sampleRateChanged = false;
|
||||
int newBasebandSampleRate = 0;
|
||||
|
||||
if (m_settings.m_meshtasticAutoSampleRate) {
|
||||
sampleRateChanged = autoTuneDeviceSampleRateForBandwidth(thisBW, sampleRateSummary, &newBasebandSampleRate);
|
||||
}
|
||||
|
||||
if (sampleRateChanged && newBasebandSampleRate > m_basebandSampleRate) {
|
||||
m_basebandSampleRate = newBasebandSampleRate;
|
||||
setBandwidths();
|
||||
}
|
||||
|
||||
if (selectionStateChanged || sampleRateChanged) {
|
||||
applySettings();
|
||||
}
|
||||
|
||||
const QString statusMsg = tr("MESH CFG|USER preset: BW=%1 Hz SF=%2 DE=%3 preamble=%4%5")
|
||||
.arg(thisBW)
|
||||
.arg(m_settings.m_spreadFactor)
|
||||
.arg(m_settings.m_deBits)
|
||||
.arg(m_settings.m_preambleChirps)
|
||||
.arg(sampleRateSummary.isEmpty() ? QString() : " " + sampleRateSummary);
|
||||
updateControlAvailabilityHints();
|
||||
displayStatus(statusMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
const QString command = QString("MESH:preset=%1;region=%2;channel_num=%3").arg(preset, region).arg(channelNum);
|
||||
modemmeshtastic::TxRadioSettings meshRadio;
|
||||
QString error;
|
||||
@ -1265,12 +1324,21 @@ void MeshtasticDemodGUI::applyMeshtasticProfileFromSelection()
|
||||
QString sampleRateSummary;
|
||||
bool sampleRateChanged = false;
|
||||
|
||||
int newBasebandSampleRate = 0;
|
||||
if (m_settings.m_meshtasticAutoSampleRate) {
|
||||
sampleRateChanged = autoTuneDeviceSampleRateForBandwidth(thisBW, sampleRateSummary);
|
||||
sampleRateChanged = autoTuneDeviceSampleRateForBandwidth(thisBW, sampleRateSummary, &newBasebandSampleRate);
|
||||
} else {
|
||||
sampleRateSummary = "auto sample-rate control: disabled";
|
||||
}
|
||||
|
||||
// If the device sample rate was just raised, update m_basebandSampleRate immediately
|
||||
// so that setBandwidths() can widen the BW slider maximum before we write to it.
|
||||
// Without this, the slider silently clamps the desired BW to the old (too-small) max.
|
||||
if (sampleRateChanged && newBasebandSampleRate > m_basebandSampleRate) {
|
||||
m_basebandSampleRate = newBasebandSampleRate;
|
||||
setBandwidths();
|
||||
}
|
||||
|
||||
if (!changed && !sampleRateChanged && !selectionStateChanged) {
|
||||
return;
|
||||
}
|
||||
@ -1357,6 +1425,25 @@ void MeshtasticDemodGUI::rebuildMeshtasticChannelOptions()
|
||||
m_meshControlsUpdating = true;
|
||||
ui->meshChannel->clear();
|
||||
|
||||
// USER preset: channel selection is not applicable — the user sets all parameters manually
|
||||
if (preset == "USER")
|
||||
{
|
||||
ui->meshChannel->addItem(tr("(user-defined)"), 0);
|
||||
ui->meshChannel->setEnabled(false);
|
||||
ui->meshChannel->setToolTip(tr("Not applicable in USER preset. All LoRa parameters and frequency are set manually."));
|
||||
m_meshControlsUpdating = false;
|
||||
// Do NOT queue applyMeshtasticProfileFromSelection here: this function is
|
||||
// called from displaySettings() on every demod echo-back, and queueing an
|
||||
// apply would create an infinite loop (apply → demod → echo → displaySettings
|
||||
// → rebuildMeshtasticChannelOptions → apply → …).
|
||||
// Initial and explicit applies happen via the constructor's queued call and
|
||||
// direct user actions (Apply button, region/preset combo changes).
|
||||
return;
|
||||
}
|
||||
|
||||
ui->meshChannel->setEnabled(true);
|
||||
ui->meshChannel->setToolTip(tr("Meshtastic channel number (zero-based, shown with center frequency)"));
|
||||
|
||||
int added = 0;
|
||||
for (int meshChannel = 0; meshChannel <= 200; ++meshChannel)
|
||||
{
|
||||
@ -1401,14 +1488,6 @@ void MeshtasticDemodGUI::rebuildMeshtasticChannelOptions()
|
||||
<< "region=" << region
|
||||
<< "preset=" << preset
|
||||
<< "channels=" << added;
|
||||
|
||||
// Ensure the rebuilt combo state is actually applied, even when the rebuild
|
||||
// was triggered from code paths where index-change handlers are suppressed.
|
||||
QMetaObject::invokeMethod(this, [this]() {
|
||||
if (!m_meshControlsUpdating) {
|
||||
applyMeshtasticProfileFromSelection();
|
||||
}
|
||||
}, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void MeshtasticDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown)
|
||||
@ -1621,7 +1700,9 @@ MeshtasticDemodGUI::MeshtasticDemodGUI(PluginAPI* pluginAPI, DeviceUISet *device
|
||||
resetLoRaStatus();
|
||||
applySettings(true);
|
||||
// On first creation, combo signals haven't fired yet. Apply selected Meshtastic profile once.
|
||||
applyMeshtasticProfileFromSelection();
|
||||
// Use a queued connection so this runs after SDRangel calls deserialize() on the newly created
|
||||
// object — ensuring the saved preset/settings are in effect before any device retuning occurs.
|
||||
QMetaObject::invokeMethod(this, &MeshtasticDemodGUI::applyMeshtasticProfileFromSelection, Qt::QueuedConnection);
|
||||
DialPopup::addPopupsToChildDials(this);
|
||||
m_resizer.enableChildMouseTracking();
|
||||
}
|
||||
@ -1664,6 +1745,31 @@ void MeshtasticDemodGUI::updateControlAvailabilityHints()
|
||||
ui->messageLengthLabel->setToolTip(messageLengthTip);
|
||||
ui->messageLengthText->setToolTip(messageLengthTip);
|
||||
|
||||
const bool isUserPreset = m_settings.m_meshtasticPresetName.trimmed().compare("USER", Qt::CaseInsensitive) == 0;
|
||||
ui->meshRegion->setEnabled(!isUserPreset);
|
||||
ui->BW->setEnabled(isUserPreset);
|
||||
ui->Spread->setEnabled(isUserPreset);
|
||||
ui->deBits->setEnabled(isUserPreset);
|
||||
ui->preambleChirps->setEnabled(isUserPreset);
|
||||
|
||||
// Apply an opacity effect to give a clear greyed-out appearance when disabled,
|
||||
// because the platform or dark-theme style may not provide enough visual contrast.
|
||||
auto setSliderDimmed = [](QSlider* slider, bool dimmed) {
|
||||
if (dimmed) {
|
||||
if (!qobject_cast<QGraphicsOpacityEffect*>(slider->graphicsEffect())) {
|
||||
auto* effect = new QGraphicsOpacityEffect(slider);
|
||||
effect->setOpacity(0.35);
|
||||
slider->setGraphicsEffect(effect);
|
||||
}
|
||||
} else {
|
||||
slider->setGraphicsEffect(nullptr);
|
||||
}
|
||||
};
|
||||
setSliderDimmed(ui->BW, !isUserPreset);
|
||||
setSliderDimmed(ui->Spread, !isUserPreset);
|
||||
setSliderDimmed(ui->deBits, !isUserPreset);
|
||||
setSliderDimmed(ui->preambleChirps, !isUserPreset);
|
||||
|
||||
const bool headerControlsEnabled = !m_settings.m_hasHeader;
|
||||
ui->fecParity->setEnabled(headerControlsEnabled);
|
||||
ui->packetLength->setEnabled(headerControlsEnabled);
|
||||
@ -1760,6 +1866,12 @@ void MeshtasticDemodGUI::displaySettings()
|
||||
}
|
||||
ui->meshRegion->setCurrentIndex(regionIndex);
|
||||
|
||||
ui->meshRegion->setEnabled(m_settings.m_meshtasticPresetName != "USER");
|
||||
ui->BW->setEnabled(m_settings.m_meshtasticPresetName == "USER");
|
||||
ui->Spread->setEnabled(m_settings.m_meshtasticPresetName == "USER");
|
||||
ui->deBits->setEnabled(m_settings.m_meshtasticPresetName == "USER");
|
||||
ui->preambleChirps->setEnabled(m_settings.m_meshtasticPresetName == "USER");
|
||||
|
||||
int presetIndex = ui->meshPreset->findData(m_settings.m_meshtasticPresetName);
|
||||
if (presetIndex < 0) {
|
||||
presetIndex = ui->meshPreset->findData("LONG_FAST");
|
||||
|
||||
@ -208,7 +208,7 @@ private:
|
||||
void setupMeshtasticAutoProfileControls();
|
||||
void rebuildMeshtasticChannelOptions();
|
||||
bool retuneDeviceToFrequency(qint64 centerFrequencyHz);
|
||||
bool autoTuneDeviceSampleRateForBandwidth(int bandwidthHz, QString& summary);
|
||||
bool autoTuneDeviceSampleRateForBandwidth(int bandwidthHz, QString& summary, int* newBasebandSampleRateOut = nullptr);
|
||||
int findBandwidthIndex(int bandwidthHz) const;
|
||||
void applyMeshtasticProfileFromSelection();
|
||||
void editMeshtasticKeys();
|
||||
|
||||
@ -643,6 +643,11 @@
|
||||
<string>SHORT_TURBO</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>USER</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user