diff --git a/plugins/channeltx/modmeshtastic/meshtasticmod.cpp b/plugins/channeltx/modmeshtastic/meshtasticmod.cpp index a35be3b3c..72bc3005e 100644 --- a/plugins/channeltx/modmeshtastic/meshtasticmod.cpp +++ b/plugins/channeltx/modmeshtastic/meshtasticmod.cpp @@ -205,25 +205,6 @@ void MeshtasticMod::setCenterFrequency(qint64 frequency) } } -int MeshtasticMod::findBandwidthIndex(int bandwidthHz) const -{ - int bestIndex = -1; - int bestDelta = 1 << 30; - - for (int i = 0; i < MeshtasticModSettings::nbBandwidths; i++) - { - const int delta = std::abs(MeshtasticModSettings::bandwidths[i] - bandwidthHz); - - if (delta < bestDelta) - { - bestDelta = delta; - bestIndex = i; - } - } - - return bestIndex; -} - bool MeshtasticMod::applyMeshtasticRadioSettingsIfPresent(MeshtasticModSettings& settings) const { if (settings.m_codingScheme != MeshtasticModSettings::CodingLoRa) { @@ -243,12 +224,6 @@ bool MeshtasticMod::applyMeshtasticRadioSettingsIfPresent(MeshtasticModSettings& } bool changed = false; - const int bwIndex = findBandwidthIndex(meshRadio.bandwidthHz); - - if ((bwIndex >= 0) && (bwIndex != settings.m_bandwidthIndex)) { - settings.m_bandwidthIndex = bwIndex; - changed = true; - } if ((meshRadio.spreadFactor > 0) && (meshRadio.spreadFactor != settings.m_spreadFactor)) { settings.m_spreadFactor = meshRadio.spreadFactor; diff --git a/plugins/channeltx/modmeshtastic/meshtasticmod.h b/plugins/channeltx/modmeshtastic/meshtasticmod.h index db0a32e0c..3641f0f75 100644 --- a/plugins/channeltx/modmeshtastic/meshtasticmod.h +++ b/plugins/channeltx/modmeshtastic/meshtasticmod.h @@ -213,7 +213,6 @@ private: void openUDP(const MeshtasticModSettings& settings); void closeUDP(); void sendCurrentSettingsMessage(); - int findBandwidthIndex(int bandwidthHz) const; bool applyMeshtasticRadioSettingsIfPresent(MeshtasticModSettings& settings) const; private slots: diff --git a/plugins/channeltx/modmeshtastic/meshtasticmodgui.cpp b/plugins/channeltx/modmeshtastic/meshtasticmodgui.cpp index c281a23a9..06c980cff 100644 --- a/plugins/channeltx/modmeshtastic/meshtasticmodgui.cpp +++ b/plugins/channeltx/modmeshtastic/meshtasticmodgui.cpp @@ -196,11 +196,6 @@ void MeshtasticModGUI::applyMeshtasticRadioSettingsIfPresent(const QString& payl } bool changed = false; - const int bwIndex = findBandwidthIndex(meshRadio.bandwidthHz); - if (bwIndex >= 0 && bwIndex != m_settings.m_bandwidthIndex) { - m_settings.m_bandwidthIndex = bwIndex; - changed = true; - } if (meshRadio.spreadFactor > 0 && meshRadio.spreadFactor != m_settings.m_spreadFactor) { m_settings.m_spreadFactor = meshRadio.spreadFactor; @@ -267,6 +262,217 @@ void MeshtasticModGUI::applyMeshtasticRadioSettingsIfPresent(const QString& payl updateAbsoluteCenterFrequency(); } +void MeshtasticModGUI::applyMeshtasticProfileFromSelection() +{ + const QString region = ui->meshRegion->currentData().toString(); + const QString preset = ui->meshPreset->currentData().toString(); + const int meshChannel = ui->meshChannel->currentData().toInt(); + const int channelNum = meshChannel + 1; // planner expects 1-based channel_num + + if (region.isEmpty() || preset.isEmpty()) { + return; + } + + const QString command = QString("MESH:preset=%1;region=%2;channel_num=%3").arg(preset, region).arg(channelNum); + modemmeshtastic::TxRadioSettings meshRadio; + QString error; + + if (!modemmeshtastic::Packet::deriveTxRadioSettings(command, meshRadio, error)) + { + qWarning() << "MeshtasticModGUI::applyMeshtasticProfileFromSelection:" << error; + return; + } + + bool changed = false; + 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; + } + if (m_settings.m_meshtasticChannelIndex != meshChannel) + { + m_settings.m_meshtasticChannelIndex = meshChannel; + selectionStateChanged = true; + } + + const int bwIndex = findBandwidthIndex(meshRadio.bandwidthHz); + if (bwIndex >= 0 && bwIndex != m_settings.m_bandwidthIndex) + { + m_settings.m_bandwidthIndex = bwIndex; + changed = true; + } + + if (meshRadio.spreadFactor > 0 && meshRadio.spreadFactor != m_settings.m_spreadFactor) + { + m_settings.m_spreadFactor = meshRadio.spreadFactor; + changed = true; + } + + if (meshRadio.deBits != m_settings.m_deBits) + { + m_settings.m_deBits = meshRadio.deBits; + changed = true; + } + + if (meshRadio.parityBits > 0 && meshRadio.parityBits != m_settings.m_nbParityBits) + { + m_settings.m_nbParityBits = meshRadio.parityBits; + changed = true; + } + + const int meshPreambleChirps = meshRadio.preambleChirps; + if (m_settings.m_preambleChirps != static_cast(meshPreambleChirps)) + { + m_settings.m_preambleChirps = static_cast(meshPreambleChirps); + changed = true; + } + + if (meshRadio.syncWord != m_settings.m_syncWord) + { + m_settings.m_syncWord = meshRadio.syncWord; + changed = true; + } + + if (meshRadio.hasCenterFrequency) + { + if (m_deviceCenterFrequency != 0) + { + const qint64 wantedOffset = meshRadio.centerFrequencyHz - m_deviceCenterFrequency; + if (wantedOffset != m_settings.m_inputFrequencyOffset) + { + m_settings.m_inputFrequencyOffset = static_cast(wantedOffset); + changed = true; + } + } + else + { + qWarning() << "MeshtasticModGUI::applyMeshtasticProfileFromSelection: device center frequency unknown, cannot auto-center"; + } + } + + if (!changed && !selectionStateChanged) { + return; + } + + qInfo() << "MeshtasticModGUI::applyMeshtasticProfileFromSelection:" << meshRadio.summary; + + if (!changed) + { + applySettings(); + return; + } + + const int thisBW = MeshtasticModSettings::bandwidths[m_settings.m_bandwidthIndex]; + m_channelMarker.blockSignals(true); + m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset); + m_channelMarker.setBandwidth(thisBW); + m_channelMarker.blockSignals(false); + + blockApplySettings(true); + ui->deltaFrequency->setValue(m_settings.m_inputFrequencyOffset); + ui->bw->setValue(m_settings.m_bandwidthIndex); + ui->bwText->setText(QString("%1 Hz").arg(thisBW)); + ui->spread->setValue(m_settings.m_spreadFactor); + ui->spreadText->setText(tr("%1").arg(m_settings.m_spreadFactor)); + ui->deBits->setValue(m_settings.m_deBits); + ui->deBitsText->setText(tr("%1").arg(m_settings.m_deBits)); + ui->preambleChirps->setValue(m_settings.m_preambleChirps); + ui->preambleChirpsText->setText(tr("%1").arg(m_settings.m_preambleChirps)); + ui->fecParity->setValue(m_settings.m_nbParityBits); + ui->fecParityText->setText(tr("%1").arg(m_settings.m_nbParityBits)); + ui->syncWord->setText(tr("%1").arg(m_settings.m_syncWord, 2, 16)); + blockApplySettings(false); + + updateAbsoluteCenterFrequency(); + applySettings(); +} + +void MeshtasticModGUI::rebuildMeshtasticChannelOptions() +{ + const QString region = ui->meshRegion->currentData().toString(); + const QString preset = ui->meshPreset->currentData().toString(); + const int previousChannel = ui->meshChannel->currentData().toInt(); + + m_meshControlsUpdating = true; + ui->meshChannel->clear(); + + int added = 0; + for (int meshChannel = 0; meshChannel <= 200; ++meshChannel) + { + modemmeshtastic::TxRadioSettings meshRadio; + QString error; + const int channelNum = meshChannel + 1; // planner expects 1-based channel_num + const QString command = QString("MESH:preset=%1;region=%2;channel_num=%3").arg(preset, region).arg(channelNum); + + if (!modemmeshtastic::Packet::deriveTxRadioSettings(command, meshRadio, error)) + { + if (added > 0) { + break; + } else { + continue; + } + } + + const QString label = meshRadio.hasCenterFrequency + ? QString("%1 (%2 MHz)").arg(meshChannel).arg(meshRadio.centerFrequencyHz / 1000000.0, 0, 'f', 3) + : QString::number(meshChannel); + + ui->meshChannel->addItem(label, meshChannel); + added++; + } + + if (added == 0) { + ui->meshChannel->addItem("0", 0); + } + + ui->meshChannel->setToolTip(tr("Meshtastic channel number (%1 available for %2/%3)") + .arg(added) + .arg(region) + .arg(preset)); + int restoreIndex = ui->meshChannel->findData(previousChannel); + if (restoreIndex < 0) { + restoreIndex = 0; + } + ui->meshChannel->setCurrentIndex(restoreIndex); + m_meshControlsUpdating = false; + + qInfo() << "MeshtasticModGUI::rebuildMeshtasticChannelOptions:" + << "region=" << region + << "preset=" << preset + << "channels=" << added; + + QMetaObject::invokeMethod(this, [this]() { + if (!m_meshControlsUpdating) { + applyMeshtasticProfileFromSelection(); + } + }, Qt::QueuedConnection); +} + +void MeshtasticModGUI::setupMeshtasticAutoProfileControls() +{ + for (int i = 0; i < ui->meshRegion->count(); ++i) { + ui->meshRegion->setItemData(i, ui->meshRegion->itemText(i), Qt::UserRole); + } + + for (int i = 0; i < ui->meshPreset->count(); ++i) { + ui->meshPreset->setItemData(i, ui->meshPreset->itemText(i), Qt::UserRole); + } + + QObject::connect(ui->meshRegion, QOverload::of(&QComboBox::currentIndexChanged), this, &MeshtasticModGUI::on_meshRegion_currentIndexChanged); + QObject::connect(ui->meshPreset, QOverload::of(&QComboBox::currentIndexChanged), this, &MeshtasticModGUI::on_meshPreset_currentIndexChanged); + QObject::connect(ui->meshChannel, QOverload::of(&QComboBox::currentIndexChanged), this, &MeshtasticModGUI::on_meshChannel_currentIndexChanged); + QObject::connect(ui->meshApply, &QPushButton::clicked, this, &MeshtasticModGUI::on_meshApply_clicked); + + rebuildMeshtasticChannelOptions(); +} + void MeshtasticModGUI::on_deltaFrequency_changed(qint64 value) { m_channelMarker.setCenterFrequency(value); @@ -402,6 +608,49 @@ void MeshtasticModGUI::on_invertRamps_stateChanged(int state) applySettings(); } +void MeshtasticModGUI::on_meshRegion_currentIndexChanged(int index) +{ + (void) index; + if (m_meshControlsUpdating) { + return; + } + + rebuildMeshtasticChannelOptions(); + applyMeshtasticProfileFromSelection(); +} + +void MeshtasticModGUI::on_meshPreset_currentIndexChanged(int index) +{ + (void) index; + if (m_meshControlsUpdating) { + return; + } + + rebuildMeshtasticChannelOptions(); + applyMeshtasticProfileFromSelection(); +} + +void MeshtasticModGUI::on_meshChannel_currentIndexChanged(int index) +{ + (void) index; + if (m_meshControlsUpdating) { + return; + } + + applyMeshtasticProfileFromSelection(); +} + +void MeshtasticModGUI::on_meshApply_clicked(bool checked) +{ + (void) checked; + if (m_meshControlsUpdating) { + return; + } + + rebuildMeshtasticChannelOptions(); + applyMeshtasticProfileFromSelection(); +} + void MeshtasticModGUI::onWidgetRolled(QWidget* widget, bool rollDown) { (void) widget; @@ -468,6 +717,7 @@ MeshtasticModGUI::MeshtasticModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISe m_deviceCenterFrequency(0), m_basebandSampleRate(125000), m_doApplySettings(true), + m_meshControlsUpdating(false), m_tickCount(0) { setAttribute(Qt::WA_DeleteOnClose, true); @@ -556,6 +806,7 @@ MeshtasticModGUI::MeshtasticModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISe m_settings.setRollupState(&m_rollupState); setBandwidths(); + setupMeshtasticAutoProfileControls(); displaySettings(); makeUIConnections(); applySettings(); @@ -624,6 +875,38 @@ void MeshtasticModGUI::displaySettings() ui->udpAddress->setText(m_settings.m_udpAddress); ui->udpPort->setText(QString::number(m_settings.m_udpPort)); ui->invertRamps->setChecked(m_settings.m_invertRamps); + + m_meshControlsUpdating = true; + + int regionIndex = ui->meshRegion->findData(m_settings.m_meshtasticRegionCode); + if (regionIndex < 0) { + regionIndex = ui->meshRegion->findData("US"); + } + if (regionIndex < 0) { + regionIndex = 0; + } + ui->meshRegion->setCurrentIndex(regionIndex); + + int presetIndex = ui->meshPreset->findData(m_settings.m_meshtasticPresetName); + if (presetIndex < 0) { + presetIndex = ui->meshPreset->findData("LONG_FAST"); + } + if (presetIndex < 0) { + presetIndex = 0; + } + ui->meshPreset->setCurrentIndex(presetIndex); + m_meshControlsUpdating = false; + + rebuildMeshtasticChannelOptions(); + + m_meshControlsUpdating = true; + int channelIndex = ui->meshChannel->findData(m_settings.m_meshtasticChannelIndex); + if (channelIndex < 0) { + channelIndex = 0; + } + ui->meshChannel->setCurrentIndex(channelIndex); + m_meshControlsUpdating = false; + getRollupContents()->restoreState(m_rollupState); updateAbsoluteCenterFrequency(); blockApplySettings(false); @@ -714,6 +997,10 @@ void MeshtasticModGUI::makeUIConnections() QObject::connect(ui->udpAddress, &QLineEdit::editingFinished, this, &MeshtasticModGUI::on_udpAddress_editingFinished); QObject::connect(ui->udpPort, &QLineEdit::editingFinished, this, &MeshtasticModGUI::on_udpPort_editingFinished); QObject::connect(ui->invertRamps, &QCheckBox::stateChanged, this, &MeshtasticModGUI::on_invertRamps_stateChanged); + QObject::connect(ui->meshRegion, QOverload::of(&QComboBox::currentIndexChanged), this, &MeshtasticModGUI::on_meshRegion_currentIndexChanged); + QObject::connect(ui->meshPreset, QOverload::of(&QComboBox::currentIndexChanged), this, &MeshtasticModGUI::on_meshPreset_currentIndexChanged); + QObject::connect(ui->meshChannel, QOverload::of(&QComboBox::currentIndexChanged), this, &MeshtasticModGUI::on_meshChannel_currentIndexChanged); + QObject::connect(ui->meshApply, &QPushButton::clicked, this, &MeshtasticModGUI::on_meshApply_clicked); } void MeshtasticModGUI::updateAbsoluteCenterFrequency() diff --git a/plugins/channeltx/modmeshtastic/meshtasticmodgui.h b/plugins/channeltx/modmeshtastic/meshtasticmodgui.h index 9899eb6fd..5c0a3732d 100644 --- a/plugins/channeltx/modmeshtastic/meshtasticmodgui.h +++ b/plugins/channeltx/modmeshtastic/meshtasticmodgui.h @@ -73,6 +73,7 @@ private: qint64 m_deviceCenterFrequency; int m_basebandSampleRate; bool m_doApplySettings; + bool m_meshControlsUpdating; MeshtasticMod* m_meshtasticMod; MovingAverageUtil m_channelPowerDbAvg; @@ -92,6 +93,9 @@ private: QString getActivePayloadText() const; int findBandwidthIndex(int bandwidthHz) const; void applyMeshtasticRadioSettingsIfPresent(const QString& payloadText); + void setupMeshtasticAutoProfileControls(); + void rebuildMeshtasticChannelOptions(); + void applyMeshtasticProfileFromSelection(); bool handleMessage(const Message& message); void makeUIConnections(); void updateAbsoluteCenterFrequency(); @@ -118,6 +122,10 @@ private slots: void on_udpAddress_editingFinished(); void on_udpPort_editingFinished(); void on_invertRamps_stateChanged(int state); + void on_meshRegion_currentIndexChanged(int index); + void on_meshPreset_currentIndexChanged(int index); + void on_meshChannel_currentIndexChanged(int index); + void on_meshApply_clicked(bool checked); void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDialogCalled(const QPoint& p); void tick(); diff --git a/plugins/channeltx/modmeshtastic/meshtasticmodgui.ui b/plugins/channeltx/modmeshtastic/meshtasticmodgui.ui index 280308801..2ff9bfaba 100644 --- a/plugins/channeltx/modmeshtastic/meshtasticmodgui.ui +++ b/plugins/channeltx/modmeshtastic/meshtasticmodgui.ui @@ -6,8 +6,8 @@ 0 0 - 429 - 573 + 650 + 548 @@ -18,16 +18,10 @@ - 392 + 650 180 - - - 560 - 16777215 - - Liberation Sans @@ -531,7 +525,7 @@ 0 140 - 421 + 951 401 @@ -542,6 +536,237 @@ 2 + + 10 + + + + + 2 + + + + + + 40 + 16777215 + + + + Meshtastic region (defines allowed frequency band) + + + Region + + + + + + + + 80 + 16777215 + + + + Meshtastic region. Combined with preset/channel to auto-apply LoRa receive parameters. + + + + US + + + + + EU_433 + + + + + EU_868 + + + + + ANZ + + + + + JP + + + + + CN + + + + + KR + + + + + TW + + + + + IN + + + + + TH + + + + + BR_902 + + + + + LORA_24 + + + + + + + + + 40 + 16777215 + + + + Meshtastic modem preset (LongFast, MediumSlow, ...) + + + Preset + + + + + + + + 120 + 16777215 + + + + Meshtastic modem preset. Applies LoRa BW/SF/CR/DE and header/CRC expectations. + + + + LONG_FAST + + + + + LONG_SLOW + + + + + LONG_MODERATE + + + + + LONG_TURBO + + + + + MEDIUM_FAST + + + + + MEDIUM_SLOW + + + + + SHORT_FAST + + + + + SHORT_SLOW + + + + + SHORT_TURBO + + + + + + + + + 50 + 16777215 + + + + Meshtastic channel number (zero-based) + + + Channel + + + + + + + + 160 + 16777215 + + + + Meshtastic channel number (zero-based, shown with center frequency) + + + + + + + + 40 + 16777215 + + + + Apply the currently selected Meshtastic region/preset/channel profile now. + + + Apply + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + diff --git a/plugins/channeltx/modmeshtastic/meshtasticmodsettings.cpp b/plugins/channeltx/modmeshtastic/meshtasticmodsettings.cpp index e3503cc05..3c83ecb35 100644 --- a/plugins/channeltx/modmeshtastic/meshtasticmodsettings.cpp +++ b/plugins/channeltx/modmeshtastic/meshtasticmodsettings.cpp @@ -87,6 +87,9 @@ void MeshtasticModSettings::resetToDefaults() m_udpAddress = "127.0.0.1"; m_udpPort = 9998; m_invertRamps = false; + m_meshtasticRegionCode = "US"; + m_meshtasticPresetName = "LONG_FAST"; + m_meshtasticChannelIndex = 0; m_rgbColor = QColor(255, 0, 255).rgb(); m_title = "Meshtastic Modulator"; m_streamIndex = 0; @@ -158,6 +161,9 @@ QByteArray MeshtasticModSettings::serialize() const s.writeS32(60, m_workspaceIndex); s.writeBlob(61, m_geometryBytes); s.writeBool(62, m_hidden); + s.writeString(63, m_meshtasticRegionCode); + s.writeString(64, m_meshtasticPresetName); + s.writeS32(65, m_meshtasticChannelIndex); return s.final(); } @@ -235,6 +241,9 @@ bool MeshtasticModSettings::deserialize(const QByteArray& data) d.readS32(60, &m_workspaceIndex, 0); d.readBlob(61, &m_geometryBytes); d.readBool(62, &m_hidden, false); + d.readString(63, &m_meshtasticRegionCode, "US"); + d.readString(64, &m_meshtasticPresetName, "LONG_FAST"); + d.readS32(65, &m_meshtasticChannelIndex, 0); return true; } @@ -303,6 +312,12 @@ void MeshtasticModSettings::applySettings(const QStringList& settingsKeys, const m_nbParityBits = settings.m_nbParityBits; if (settingsKeys.contains("messageRepeat")) m_messageRepeat = settings.m_messageRepeat; + if (settingsKeys.contains("meshtasticRegionCode")) + m_meshtasticRegionCode = settings.m_meshtasticRegionCode; + if (settingsKeys.contains("meshtasticPresetName")) + m_meshtasticPresetName = settings.m_meshtasticPresetName; + if (settingsKeys.contains("meshtasticChannelIndex")) + m_meshtasticChannelIndex = settings.m_meshtasticChannelIndex; } QString MeshtasticModSettings::getDebugString(const QStringList& settingsKeys, bool force) const @@ -364,5 +379,11 @@ QString MeshtasticModSettings::getDebugString(const QStringList& settingsKeys, b debug += QString("Has Header: %1\n").arg(m_hasHeader); if (settingsKeys.contains("messageRepeat") || force) debug += QString("Message Repeat: %1\n").arg(m_messageRepeat); + if (settingsKeys.contains("meshtasticRegionCode") || force) + debug += QString("Meshtastic Region Code: %1\n").arg(m_meshtasticRegionCode); + if (settingsKeys.contains("meshtasticPresetName") || force) + debug += QString("Meshtastic Preset Name: %1\n").arg(m_meshtasticPresetName); + if (settingsKeys.contains("meshtasticChannelIndex") || force) + debug += QString("Meshtastic Channel Index: %1\n").arg(m_meshtasticChannelIndex); return debug; } diff --git a/plugins/channeltx/modmeshtastic/meshtasticmodsettings.h b/plugins/channeltx/modmeshtastic/meshtasticmodsettings.h index 0d9d09e05..98afbeb02 100644 --- a/plugins/channeltx/modmeshtastic/meshtasticmodsettings.h +++ b/plugins/channeltx/modmeshtastic/meshtasticmodsettings.h @@ -60,6 +60,9 @@ struct MeshtasticModSettings QString m_udpAddress; uint16_t m_udpPort; bool m_invertRamps; //!< Invert chirp ramps vs standard LoRa (up/down/up is standard) + QString m_meshtasticRegionCode; //!< Meshtastic region code (e.g. "US", "EU_868") + QString m_meshtasticPresetName; //!< Meshtastic modem preset name (e.g. "LONG_FAST") + int m_meshtasticChannelIndex; //!< Meshtastic channel index (0-based) uint32_t m_rgbColor; QString m_title; int m_streamIndex;