Skip to content

Commit 81bd324

Browse files
K5PTBclaude
andcommitted
fix(mqtt): address bugs found by ultra code-review and smoke testing
Six fixes from /code-review ultra: - startKissTncOnStartupIfConfigured: call setMqttClient() so auto-started KISS TNC has MQTT wired up - kCwDecodeTopic: set defaultEnabled=true so upgrades don't silently disable previously-always-on CW decode - routeCwDecoderOutput: save m_cwStatsConn handle to properly disconnect statsUpdated lambda (connection leak) - handleMqttMessage: add isVisible() guard so hidden dialog can't key the radio via MQTT - publishFrameMqtt: add isMqttTopicEnabled(kAx25RxTopic) guard - cw/transmit: save/restore cwSpeed/cwPitch after MQTT-commanded TX Four fixes from smoke testing: - queueEmpty doesn't fire with sync_cwx=0; replace with 1-second debounce timer (m_cwxTxEndTimer) — queueEmpty kept as fast path - Speed restore uses cwxModel().setSpeed() (cwx wpm N); CWX ignores transmitModel().setCwSpeed() (cw wpm N) - radio/state publishes one tx:true at keying start and one tx:false after 1 s silence, suppressing inter-element transitions - Remove flooding "MQTT CW decode suppressed" debug log Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 2cfb8ad commit 81bd324

5 files changed

Lines changed: 70 additions & 11 deletions

File tree

src/core/MqttSettings.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ const QVector<InternalMqttTopicDef>& internalMqttSubscribeTopicDefs()
303303
const QVector<InternalMqttTopicDef>& internalMqttPublishTopicDefs()
304304
{
305305
static const QVector<InternalMqttTopicDef> defs = {
306-
{ QString(kCwDecodeTopic), QStringLiteral("CW decoded text"), true },
306+
{ QString(kCwDecodeTopic), QStringLiteral("CW decoded text"), true, true },
307307
{ QString(kRadioStateTopic), QStringLiteral("Radio VFO / mode / TX state"), true },
308308
{ QString(kAx25RxTopic), QStringLiteral("AX.25 received frames"), true },
309309
};
@@ -325,12 +325,12 @@ bool isMqttTopicEnabled(const QString& topic)
325325
for (const auto& def : internalMqttSubscribeTopicDefs()) {
326326
if (def.topic == topic)
327327
return !def.gateable || AppSettings::instance()
328-
.value(topicEnabledKey(topic), false).toBool();
328+
.value(topicEnabledKey(topic), def.defaultEnabled).toBool();
329329
}
330330
for (const auto& def : internalMqttPublishTopicDefs()) {
331331
if (def.topic == topic)
332332
return !def.gateable || AppSettings::instance()
333-
.value(topicEnabledKey(topic), false).toBool();
333+
.value(topicEnabledKey(topic), def.defaultEnabled).toBool();
334334
}
335335
return false;
336336
}

src/core/MqttSettings.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ inline constexpr QLatin1String kAx25TxTopic {"aethersdr/ax25/tx"};
5959
struct InternalMqttTopicDef {
6060
QString topic;
6161
QString description;
62-
bool gateable{true}; // false = always on, not user-disableable
62+
bool gateable{true}; // false = always on, not user-disableable
63+
bool defaultEnabled{false}; // true = on by default (for topics always-on before per-topic gating was added)
6364
};
6465

6566
const QVector<InternalMqttTopicDef>& internalMqttSubscribeTopicDefs();

src/gui/Ax25HfPacketDecodeDialog.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1356,6 +1356,8 @@ void Ax25HfPacketDecodeDialog::publishFrameMqtt(const Ax25DecodedFrame& frame)
13561356
{
13571357
if (!m_mqtt)
13581358
return;
1359+
if (!isMqttTopicEnabled(QString::fromLatin1(kAx25RxTopic)))
1360+
return;
13591361
QString display = frame.source + QStringLiteral(">") + frame.destination;
13601362
if (!frame.path.isEmpty())
13611363
display += QStringLiteral(",") + frame.path.join(QStringLiteral(","));
@@ -1380,6 +1382,8 @@ void Ax25HfPacketDecodeDialog::handleMqttMessage(const QString& topic, const QBy
13801382
return;
13811383
if (!isMqttTopicEnabled(QString::fromLatin1(kAx25TxTopic)))
13821384
return;
1385+
if (!isVisible())
1386+
return;
13831387
startTransmit(QString::fromUtf8(payload).trimmed());
13841388
}
13851389
#endif

src/gui/MainWindow.cpp

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2010,6 +2010,18 @@ MainWindow::MainWindow(QWidget* parent)
20102010
// Slice freq/mode changes are wired per-slice in setActiveSliceInternal().
20112011
connect(&m_radioModel, &RadioModel::radioTransmittingChanged,
20122012
this, [this](bool) { publishRadioStateMqtt(); });
2013+
// Debounce timer for end-of-CWX detection (queueEmpty unreliable with sync_cwx=0).
2014+
// Fires 1 s after the last tx:false with no intervening tx:true = transmission done.
2015+
m_cwxTxEndTimer.setSingleShot(true);
2016+
connect(&m_cwxTxEndTimer, &QTimer::timeout, this, [this]() {
2017+
if (m_cwxSavedWpm > 0) m_radioModel.cwxModel().setSpeed(m_cwxSavedWpm);
2018+
if (m_cwxSavedHz > 0) m_radioModel.transmitModel().setCwPitch(m_cwxSavedHz);
2019+
m_cwxSavedWpm = 0;
2020+
m_cwxSavedHz = 0;
2021+
m_cwxTransmitting = false;
2022+
m_cwxPublishedTxTrue = false;
2023+
publishRadioStateMqtt();
2024+
});
20132025

20142026
// aethersdr/cw/transmit → CWX keyer.
20152027
// Payload: {"text":"de k5ptb","speed_wpm":28,"pitch_hz":600}
@@ -2024,10 +2036,34 @@ MainWindow::MainWindow(QWidget* parent)
20242036
if (text.isEmpty()) return;
20252037
auto& tx = m_radioModel.transmitModel();
20262038
const int wpm = obj.value(QStringLiteral("speed_wpm")).toInt(0);
2027-
if (wpm >= 5 && wpm <= 100) tx.setCwSpeed(wpm);
2028-
const int hz = obj.value(QStringLiteral("pitch_hz")).toInt(0);
2029-
if (hz >= 100 && hz <= 6000) tx.setCwPitch(hz);
2039+
const int hz = obj.value(QStringLiteral("pitch_hz")).toInt(0);
2040+
const bool changeWpm = (wpm >= 5 && wpm <= 100);
2041+
const bool changeHz = (hz >= 100 && hz <= 6000);
2042+
m_cwxSavedWpm = changeWpm ? m_radioModel.cwxModel().speed() : 0;
2043+
m_cwxSavedHz = changeHz ? tx.cwPitch() : 0;
2044+
if (changeWpm) m_radioModel.cwxModel().setSpeed(wpm);
2045+
if (changeHz) tx.setCwPitch(hz);
2046+
m_cwxTxEndTimer.stop();
2047+
m_cwxPublishedTxTrue = false;
2048+
m_cwxTransmitting = true;
20302049
m_radioModel.cwxModel().send(text);
2050+
disconnect(m_cwxSpeedRestoreConn);
2051+
m_cwxSpeedRestoreConn = connect(
2052+
&m_radioModel.cwxModel(), &CwxModel::queueEmpty,
2053+
this, [this]() {
2054+
// Fast path if queueEmpty fires (sync_cwx=1 or firmware sends queue=0).
2055+
// Identical work to m_cwxTxEndTimer.timeout — whichever fires first wins.
2056+
if (m_cwxSavedWpm > 0) m_radioModel.cwxModel().setSpeed(m_cwxSavedWpm);
2057+
if (m_cwxSavedHz > 0) m_radioModel.transmitModel().setCwPitch(m_cwxSavedHz);
2058+
m_cwxSavedWpm = 0;
2059+
m_cwxSavedHz = 0;
2060+
m_cwxTxEndTimer.stop();
2061+
m_cwxTransmitting = false;
2062+
m_cwxPublishedTxTrue = false;
2063+
if (!m_radioModel.isRadioTransmitting())
2064+
publishRadioStateMqtt();
2065+
disconnect(m_cwxSpeedRestoreConn);
2066+
});
20312067
});
20322068

20332069
// MQTT → panadapter overlay display
@@ -6014,10 +6050,8 @@ void MainWindow::publishCwDecodeMqtt(const QString& text, float cost, bool rx)
60146050
if (!m_mqttClient) return;
60156051
if (!isMqttTopicEnabled(QString::fromLatin1(kCwDecodeTopic))) return;
60166052
// No CW panel active → nothing is displayed → don't publish.
6017-
if (!m_cwDecoderApplet || cost >= m_cwDecoderApplet->cwCostThreshold()) {
6018-
qCDebug(lcMqtt) << "MQTT CW decode suppressed: no applet or cost threshold";
6053+
if (!m_cwDecoderApplet || cost >= m_cwDecoderApplet->cwCostThreshold())
60196054
return;
6020-
}
60216055
// Mirror panel normalization: \n → space; drop whitespace-only TX chunks.
60226056
QString clean = text;
60236057
clean.replace(QLatin1Char('\n'), QLatin1Char(' '));
@@ -6043,6 +6077,15 @@ void MainWindow::publishRadioStateMqtt()
60436077
{
60446078
if (!m_mqttClient) return;
60456079
if (!isMqttTopicEnabled(QString::fromLatin1(kRadioStateTopic))) return;
6080+
if (m_cwxTransmitting) {
6081+
if (!m_radioModel.isRadioTransmitting()) {
6082+
m_cwxTxEndTimer.start(1000); // might be done; confirm after 1 s silence
6083+
return;
6084+
}
6085+
m_cwxTxEndTimer.stop(); // element started — not done yet
6086+
if (m_cwxPublishedTxTrue) return;
6087+
m_cwxPublishedTxTrue = true;
6088+
}
60466089
auto* s = activeSlice();
60476090
if (!s) return;
60486091
QJsonObject obj;
@@ -6892,6 +6935,9 @@ void MainWindow::startKissTncOnStartupIfConfigured()
68926935
AppSettings::instance().value("FramelessWindow", "True").toString() == "True");
68936936
m_ax25HfPacketDecodeDialog = dlg;
68946937
m_persistentDialogs.append(QPointer<PersistentDialog>(dlg));
6938+
#ifdef HAVE_MQTT
6939+
m_ax25HfPacketDecodeDialog->setMqttClient(m_mqttClient);
6940+
#endif
68956941
}
68966942

68976943
void MainWindow::showFlexControlDialog()
@@ -13100,6 +13146,7 @@ void MainWindow::routeCwDecoderOutput()
1310013146
if (target == m_cwDecoderApplet) return;
1310113147

1310213148
// Disconnect from old applet
13149+
disconnect(m_cwStatsConn);
1310313150
if (m_cwDecoderApplet) {
1310413151
disconnect(&m_cwDecoder, &CwDecoder::textDecoded,
1310513152
m_cwDecoderApplet, &PanadapterApplet::appendCwText);
@@ -13135,7 +13182,7 @@ void MainWindow::routeCwDecoderOutput()
1313513182
m_cwDecoderApplet, &PanadapterApplet::appendCwTextTx);
1313613183
connect(&m_cwDecoder, &CwDecoder::statsUpdated,
1313713184
m_cwDecoderApplet, &PanadapterApplet::setCwStats);
13138-
connect(&m_cwDecoder, &CwDecoder::statsUpdated,
13185+
m_cwStatsConn = connect(&m_cwDecoder, &CwDecoder::statsUpdated,
1313913186
this, [this](float pitchHz, float speedWpm) {
1314013187
m_cwLastPitchHz = pitchHz;
1314113188
m_cwLastSpeedWpm = speedWpm;

src/gui/MainWindow.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,13 @@ private slots:
484484
QMetaObject::Connection m_radioStateFreqConn;
485485
QMetaObject::Connection m_radioStateModeConn;
486486
QTimer m_radioStateCoalesceTimer;
487+
QMetaObject::Connection m_cwStatsConn;
488+
QMetaObject::Connection m_cwxSpeedRestoreConn;
489+
int m_cwxSavedWpm{0};
490+
int m_cwxSavedHz{0};
491+
bool m_cwxTransmitting{false};
492+
bool m_cwxPublishedTxTrue{false};
493+
QTimer m_cwxTxEndTimer;
487494
CwDecoder m_cwDecoderTx;
488495
RttyDecoder m_rttyDecoder;
489496
DxClusterClient* m_dxCluster{nullptr};

0 commit comments

Comments
 (0)