| 44 | namespace AetherSDR { |
| 45 | |
| 46 | void MainWindow::wireSpotSubsystem() |
| 47 | { |
| 48 | // ── DX Cluster — forward parsed spots to radio ────────────────────── |
| 49 | // ── Spot clients on worker thread ───────────────────────────────────── |
| 50 | m_dxCluster = new DxClusterClient; |
| 51 | m_rbnClient = new DxClusterClient; |
| 52 | m_rbnClient->setLogFileName("rbn.log"); |
| 53 | m_rbnClient->setStartupCommandsKey("RbnStartupCommands"); |
| 54 | m_wsjtxClient = new WsjtxClient; |
| 55 | m_spotCollectorClient = new SpotCollectorClient; |
| 56 | m_potaClient = new PotaClient; |
| 57 | #ifdef HAVE_WEBSOCKETS |
| 58 | m_freedvClient = new FreeDvClient; |
| 59 | #endif |
| 60 | #ifdef HAVE_MQTT |
| 61 | m_mqttClient = new MqttClient(this); |
| 62 | m_appletPanel->mqttApplet()->setMqttClient(m_mqttClient); |
| 63 | |
| 64 | connect(m_appletPanel->mqttApplet(), &MqttApplet::connectRequested, |
| 65 | this, [this](const QString& host, quint16 port, |
| 66 | const QString& user, const QString& pass, |
| 67 | const QStringList& topics, |
| 68 | bool useTls, const QString& caFile) { |
| 69 | m_mqttClient->setSubscriptions(mqttSubscriptionTopics(topics)); |
| 70 | m_mqttClient->connectToBroker(host, port, user, pass, useTls, caFile); |
| 71 | }); |
| 72 | connect(m_appletPanel->mqttApplet(), &MqttApplet::disconnectRequested, |
| 73 | this, [this] { m_mqttClient->disconnect(); }); |
| 74 | connect(m_appletPanel->mqttApplet(), &MqttApplet::settingsRequested, |
| 75 | this, &MainWindow::showMqttSettingsDialog); |
| 76 | m_appletPanel->mqttApplet()->restoreConnectionState(); |
| 77 | |
| 78 | // CW decode → MQTT topic "aethersdr/cw/decode". |
| 79 | // Publishes {"text":"K","freq":14.025,"rx":true} per decoded character. |
| 80 | // RX decoder uses rx:true; TX sidetone decoder uses rx:false. |
| 81 | // Any MQTT subscriber (e.g. a contest logger) receives the stream |
| 82 | // without additional AetherSDR interfaces. |
| 83 | connect(&m_cwDecoder, &CwDecoder::textDecoded, this, |
| 84 | [this](const QString& t, float cost) { publishCwDecodeMqtt(t, cost, true); }); |
| 85 | connect(&m_cwDecoderTx, &CwDecoder::textDecoded, this, |
| 86 | [this](const QString& t, float cost) { publishCwDecodeMqtt(t, cost, false); }); |
| 87 | |
| 88 | // aethersdr/radio/state — publish on PTT transitions. |
| 89 | // Slice freq/mode changes are wired per-slice in setActiveSliceInternal(). |
| 90 | m_radioStateCoalesceTimer.setSingleShot(true); |
| 91 | m_radioStateCoalesceTimer.setInterval(150); |
| 92 | connect(&m_radioStateCoalesceTimer, &QTimer::timeout, |
| 93 | this, &MainWindow::publishRadioStateMqtt); |
| 94 | connect(&m_radioModel, &RadioModel::radioTransmittingChanged, |
| 95 | this, [this](bool) { publishRadioStateMqtt(); }); |
| 96 | // Debounce timer for end-of-CWX detection (queueEmpty unreliable with sync_cwx=0). |
| 97 | // Fires 1 s after the last tx:false with no intervening tx:true = transmission done. |
| 98 | m_cwxTxEndTimer.setSingleShot(true); |
| 99 | connect(&m_cwxTxEndTimer, &QTimer::timeout, this, [this]() { |
| 100 | if (m_cwxSavedWpm > 0 && m_radioModel.cwxModel().speed() == m_cwxSentWpm) |
| 101 | m_radioModel.cwxModel().setSpeed(m_cwxSavedWpm); |
| 102 | if (m_cwxSavedHz > 0 && m_radioModel.transmitModel().cwPitch() == m_cwxSentHz) |
| 103 | m_radioModel.transmitModel().setCwPitch(m_cwxSavedHz); |
nothing calls this directly
no test coverage detected