From 748f02477199ccffa2604f397e162fa2d324c7ad Mon Sep 17 00:00:00 2001 From: f4exb Date: Thu, 19 Mar 2026 04:43:13 +0100 Subject: [PATCH] Meshtastic demod: make key dialog an object on its own --- modemmeshtastic/README.md | 84 +++++++++++++ .../channelrx/demodmeshtastic/CMakeLists.txt | 3 + .../demodmeshtastic/meshtasticdemodgui.cpp | 117 ++++-------------- .../demodmeshtastic/meshtastickeysdialog.cpp | 71 +++++++++++ .../demodmeshtastic/meshtastickeysdialog.h | 31 +++++ .../demodmeshtastic/meshtastickeysdialog.ui | 112 +++++++++++++++++ 6 files changed, 322 insertions(+), 96 deletions(-) create mode 100644 modemmeshtastic/README.md create mode 100644 plugins/channelrx/demodmeshtastic/meshtastickeysdialog.cpp create mode 100644 plugins/channelrx/demodmeshtastic/meshtastickeysdialog.h create mode 100644 plugins/channelrx/demodmeshtastic/meshtastickeysdialog.ui diff --git a/modemmeshtastic/README.md b/modemmeshtastic/README.md new file mode 100644 index 000000000..d5be914ef --- /dev/null +++ b/modemmeshtastic/README.md @@ -0,0 +1,84 @@ +# Meshtastic port payload reference + +This note summarizes Meshtastic port names used by SDRangel packet decoding and what payload kind to expect. + +Scope: +- Port names and IDs come from the local map in `meshtasticpacket.cpp` (`kPortMap`). +- Payload types are practical expectations based on Meshtastic app semantics. +- SDRangel currently does **generic** data decode only (header + protobuf envelope fields + raw payload), not per-port protobuf decoding. + +## How to interpret payload display in SDRangel + +- Text is shown only when payload bytes look like valid UTF-8. +- Otherwise payload is shown as hex. +- For most ports (POSITION, NODEINFO, TELEMETRY, ROUTING, etc.), payload is protobuf/binary, so hex is the normal representation. +- Seeing a mix of readable text and binary garbage (for example NODEINFO_APP showing the node name) is normal: protobuf stores `string` fields as raw UTF-8 bytes inline in the binary stream. The readable parts are string field values; the surrounding bytes are protobuf field tags (varints), numeric IDs, and other binary-encoded fields. + +## Port list (current SDRangel map) + +| Port name | ID | Expected payload kind | Typical content | SDRangel decode level | +|---|---:|---|---|---| +| UNKNOWN_APP | 0 | binary/unknown | unspecified | envelope + raw payload | +| TEXT / TEXT_MESSAGE_APP | 1 | UTF-8 text | plain chat text | envelope + text/hex payload | +| REMOTE_HARDWARE_APP | 2 | protobuf/binary | remote hardware control messages | envelope + raw payload | +| POSITION_APP | 3 | protobuf/binary | GPS position fields | envelope + raw payload | +| NODEINFO_APP | 4 | protobuf/binary | node metadata / identity (long name, short name, hardware model, node ID) | envelope + raw payload. Node name strings appear in clear because protobuf stores `string` fields as raw UTF-8 bytes; surrounding bytes are binary (field tags, varint IDs, enums). | +| ROUTING_APP | 5 | protobuf/binary | routing/control data | envelope + raw payload | +| ADMIN_APP | 6 | protobuf/binary | admin/config operations | envelope + raw payload | +| TEXT_MESSAGE_COMPRESSED_APP | 7 | compressed/binary | compressed text payload | envelope + raw payload | +| WAYPOINT_APP | 8 | protobuf/binary | waypoint structures | envelope + raw payload | +| AUDIO_APP | 9 | binary | encoded audio frames | envelope + raw payload | +| DETECTION_SENSOR_APP | 10 | protobuf/binary | sensor detection events | envelope + raw payload | +| ALERT_APP | 11 | protobuf/binary | alert/notification message | envelope + raw payload | +| KEY_VERIFICATION_APP | 12 | protobuf/binary | key verification/signaling | envelope + raw payload | +| REPLY_APP | 32 | protobuf/binary | reply wrapper/ack data | envelope + raw payload | +| IP_TUNNEL_APP | 33 | binary | tunneled IP packet bytes | envelope + raw payload | +| PAXCOUNTER_APP | 34 | protobuf/binary | pax counter telemetry | envelope + raw payload | +| STORE_FORWARD_PLUSPLUS_APP | 35 | protobuf/binary | store-and-forward control/data | envelope + raw payload | +| NODE_STATUS_APP | 36 | protobuf/binary | node status information | envelope + raw payload | +| SERIAL_APP | 64 | binary | serial tunnel bytes | envelope + raw payload | +| STORE_FORWARD_APP | 65 | protobuf/binary | store-and-forward messages | envelope + raw payload | +| RANGE_TEST_APP | 66 | protobuf/binary | range test metadata/results | envelope + raw payload | +| TELEMETRY_APP | 67 | protobuf/binary | telemetry records | envelope + raw payload | +| ZPS_APP | 68 | protobuf/binary | ZPS-specific messages | envelope + raw payload | +| SIMULATOR_APP | 69 | protobuf/binary | simulator-generated messages | envelope + raw payload | +| TRACEROUTE_APP | 70 | protobuf/binary | traceroute path/hops | envelope + raw payload | +| NEIGHBORINFO_APP | 71 | protobuf/binary | neighbor table entries | envelope + raw payload | +| ATAK_PLUGIN | 72 | protobuf/binary | ATAK integration payloads | envelope + raw payload | + +## Notes + +- This is a practical operator reference, not a protocol authority. +- For authoritative and exhaustive app payload schemas, use Meshtastic upstream protobuf definitions (for example `portnums.proto` and related message protos). +- If SDRangel later adds per-port protobuf decoding, this table should be updated with "decoded fields available" per port. + +## Protocol availability and per-port decoding + +The Meshtastic protocol is fully public and open-source (GPL v3, same license as SDRangel). + +- Full `.proto` definitions: https://github.com/meshtastic/protobufs +- Browsable API reference with all message types and fields: https://buf.build/meshtastic/protobufs +- Protocol architecture doc: https://meshtastic.org/docs/overview/mesh-algo/ + +Key message types relevant to SDRangel decoding: + +| Proto message | Port | Fields of interest | +|---|---|---| +| `Position` | POSITION_APP (3) | latitude, longitude, altitude, time, speed, heading | +| `User` | NODEINFO_APP (4) | id (node ID string), long_name, short_name, hw_model, is_licensed | +| `Routing` | ROUTING_APP (5) | route success/failure error codes | +| `Data` | (envelope, already decoded) | portnum, payload, want_response, dest, source | +| `Telemetry` | TELEMETRY_APP (67) | device metrics (battery, voltage, utilization), environment (temp, humidity), air quality | +| `RouteDiscovery` | TRACEROUTE_APP (70) | route (node ID list), snr_towards, snr_back | +| `NeighborInfo` | NEIGHBORINFO_APP (71) | node_id, node_broadcast_interval_secs, neighbors list | +| `Waypoint` | WAYPOINT_APP (8) | id, latitude, longitude, expire, locked_to, name, description | + +### Adding per-port decoding to SDRangel + +Three practical approaches (in order of fit for this codebase): + +1. **Extend the existing hand-parser** — `meshtasticpacket.cpp` already contains a minimal protobuf varint/length-delimited parser for the `Data` envelope. The same approach can be extended per-port for common types (POSITION, NODEINFO, TELEMETRY) with no extra dependency. Best choice for portability and minimal build impact. + +2. **nanopb** — the C library used by Meshtastic firmware itself; tiny footprint, no external runtime, generates `.pb.c`/`.pb.h` from the upstream `.proto` files. Would require adding it to `CMakeLists.txt` as a dependency. + +3. **Qt Protobuf** (`QProtobufSerializer`) — available in Qt 6.5+. Adds a Qt version constraint that may conflict with Qt5 builds. diff --git a/plugins/channelrx/demodmeshtastic/CMakeLists.txt b/plugins/channelrx/demodmeshtastic/CMakeLists.txt index 39e01c39c..cee9ee1ec 100644 --- a/plugins/channelrx/demodmeshtastic/CMakeLists.txt +++ b/plugins/channelrx/demodmeshtastic/CMakeLists.txt @@ -34,10 +34,13 @@ if(NOT SERVER_MODE) ${meshtastic_SOURCES} meshtasticdemodgui.cpp meshtasticdemodgui.ui + meshtastickeysdialog.cpp + meshtastickeysdialog.ui ) set(meshtastic_HEADERS ${meshtastic_HEADERS} meshtasticdemodgui.h + meshtastickeysdialog.h ) set(TARGET_NAME ${PLUGINS_PREFIX}demodmeshtastic) set(TARGET_LIB "Qt::Widgets") diff --git a/plugins/channelrx/demodmeshtastic/meshtasticdemodgui.cpp b/plugins/channelrx/demodmeshtastic/meshtasticdemodgui.cpp index e84dede9c..9b8e64fee 100644 --- a/plugins/channelrx/demodmeshtastic/meshtasticdemodgui.cpp +++ b/plugins/channelrx/demodmeshtastic/meshtasticdemodgui.cpp @@ -21,8 +21,6 @@ #include "device/deviceuiset.h" #include "device/deviceapi.h" -#include -#include #include #include #include @@ -30,10 +28,7 @@ #include #include #include -#include #include -#include -#include #include #include #include @@ -41,10 +36,8 @@ #include #include #include -#include #include #include -#include #include #include #include @@ -69,6 +62,7 @@ #include "meshtasticdemod.h" #include "meshtasticdemodmsg.h" #include "meshtasticdemodgui.h" +#include "meshtastickeysdialog.h" #include "meshtasticpacket.h" namespace @@ -940,100 +934,31 @@ void MeshtasticDemodGUI::advanceMeshAutoLock() void MeshtasticDemodGUI::editMeshtasticKeys() { - QDialog dialog(this); - dialog.setWindowTitle(tr("Meshtastic Keys")); - dialog.resize(760, 460); + MeshtasticKeysDialog dialog(this); + dialog.setKeySpecList(m_settings.m_meshtasticKeySpecList); - QVBoxLayout* layout = new QVBoxLayout(&dialog); + if (dialog.exec() != QDialog::Accepted) { + return; + } - QLabel* helpLabel = new QLabel(tr( - "One key entry per line (or comma/semicolon separated).\n" - "Formats: default, none, simple1..simple10, hex:, b64:, base64:, raw hex, raw base64.\n" - "Optional channel mapping: channelName=keySpec (example: LongFast=default)."), - &dialog); - helpLabel->setWordWrap(true); - layout->addWidget(helpLabel); + m_settings.m_meshtasticKeySpecList = dialog.getKeySpecList(); - QTextEdit* keyEditor = new QTextEdit(&dialog); - keyEditor->setPlainText(m_settings.m_meshtasticKeySpecList); - keyEditor->setToolTip(tr("Enter one or more key specs used to decrypt Meshtastic packets.")); - keyEditor->setPlaceholderText("LongFast=default\nnone\nLongSlow=hex:00112233445566778899aabbccddeeff"); - layout->addWidget(keyEditor, 1); + if (m_meshKeysButton) + { + const bool hasCustomKeys = !m_settings.m_meshtasticKeySpecList.isEmpty(); + m_meshKeysButton->setText(hasCustomKeys ? tr("Keys*") : tr("Keys...")); + m_meshKeysButton->setToolTip(hasCustomKeys ? + tr("Custom Meshtastic decode keys configured. Click to edit.") : + tr("Open Meshtastic key manager.")); + } - QHBoxLayout* statusLayout = new QHBoxLayout(); - QPushButton* validateButton = new QPushButton(tr("Validate"), &dialog); - validateButton->setToolTip(tr("Validate key syntax and count without saving.")); - QLabel* statusLabel = new QLabel(&dialog); - statusLabel->setToolTip(tr("Validation status for the current key list.")); - statusLabel->setWordWrap(true); - statusLayout->addWidget(validateButton); - statusLayout->addWidget(statusLabel, 1); - layout->addLayout(statusLayout); + applySettings(); - QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dialog); - layout->addWidget(buttons); - - auto validateInput = [keyEditor, statusLabel]() -> bool { - const QString keyText = keyEditor->toPlainText().trimmed(); - - if (keyText.isEmpty()) - { - statusLabel->setStyleSheet("QLabel { color: #bbbbbb; }"); - statusLabel->setText(QObject::tr("No custom keys set. Decoder will use environment/default keys.")); - return true; - } - - QString error; - int keyCount = 0; - - if (!modemmeshtastic::Packet::validateKeySpecList(keyText, error, &keyCount)) - { - statusLabel->setStyleSheet("QLabel { color: #ff5555; }"); - statusLabel->setText(QObject::tr("Invalid key list: %1").arg(error)); - return false; - } - - statusLabel->setStyleSheet("QLabel { color: #7cd67c; }"); - statusLabel->setText(QObject::tr("Valid: %1 key(s) parsed").arg(keyCount)); - return true; - }; - - QObject::connect(validateButton, &QPushButton::clicked, &dialog, [validateInput]() { - validateInput(); - }); - - QObject::connect(buttons, &QDialogButtonBox::accepted, &dialog, [this, &dialog, keyEditor, validateInput]() { - if (!validateInput()) - { - QMessageBox::warning(this, tr("Invalid Keys"), tr("Fix the Meshtastic key list before saving.")); - return; - } - - m_settings.m_meshtasticKeySpecList = keyEditor->toPlainText().trimmed(); - - if (m_meshKeysButton) - { - const bool hasCustomKeys = !m_settings.m_meshtasticKeySpecList.isEmpty(); - m_meshKeysButton->setText(hasCustomKeys ? tr("Keys*") : tr("Keys...")); - m_meshKeysButton->setToolTip(hasCustomKeys ? - tr("Custom Meshtastic decode keys configured. Click to edit.") : - tr("Open Meshtastic key manager.")); - } - - applySettings(); - - if (m_settings.m_meshtasticKeySpecList.isEmpty()) { - displayStatus(tr("MESH KEYS|using environment/default key set")); - } else { - displayStatus(tr("MESH KEYS|custom key set saved")); - } - - dialog.accept(); - }); - - QObject::connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); - validateInput(); - dialog.exec(); + if (m_settings.m_meshtasticKeySpecList.isEmpty()) { + displayStatus(tr("MESH KEYS|using environment/default key set")); + } else { + displayStatus(tr("MESH KEYS|custom key set saved")); + } } int MeshtasticDemodGUI::findBandwidthIndex(int bandwidthHz) const diff --git a/plugins/channelrx/demodmeshtastic/meshtastickeysdialog.cpp b/plugins/channelrx/demodmeshtastic/meshtastickeysdialog.cpp new file mode 100644 index 000000000..44471510a --- /dev/null +++ b/plugins/channelrx/demodmeshtastic/meshtastickeysdialog.cpp @@ -0,0 +1,71 @@ +#include + +#include "meshtastickeysdialog.h" +#include "ui_meshtastickeysdialog.h" +#include "meshtasticpacket.h" + +MeshtasticKeysDialog::MeshtasticKeysDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::MeshtasticKeysDialog) +{ + ui->setupUi(this); + validateCurrentInput(); +} + +MeshtasticKeysDialog::~MeshtasticKeysDialog() +{ + delete ui; +} + +void MeshtasticKeysDialog::setKeySpecList(const QString& keySpecList) +{ + ui->keyEditor->setPlainText(keySpecList); + validateCurrentInput(); +} + +QString MeshtasticKeysDialog::getKeySpecList() const +{ + return ui->keyEditor->toPlainText().trimmed(); +} + +void MeshtasticKeysDialog::on_validate_clicked() +{ + validateCurrentInput(); +} + +void MeshtasticKeysDialog::accept() +{ + if (!validateCurrentInput()) + { + QMessageBox::warning(this, tr("Invalid Keys"), tr("Fix the Meshtastic key list before saving.")); + return; + } + + QDialog::accept(); +} + +bool MeshtasticKeysDialog::validateCurrentInput() +{ + const QString keyText = ui->keyEditor->toPlainText().trimmed(); + + if (keyText.isEmpty()) + { + ui->statusLabel->setStyleSheet("QLabel { color: #bbbbbb; }"); + ui->statusLabel->setText(tr("No custom keys set. Decoder will use environment/default keys.")); + return true; + } + + QString error; + int keyCount = 0; + + if (!modemmeshtastic::Packet::validateKeySpecList(keyText, error, &keyCount)) + { + ui->statusLabel->setStyleSheet("QLabel { color: #ff5555; }"); + ui->statusLabel->setText(tr("Invalid key list: %1").arg(error)); + return false; + } + + ui->statusLabel->setStyleSheet("QLabel { color: #7cd67c; }"); + ui->statusLabel->setText(tr("Valid: %1 key(s) parsed").arg(keyCount)); + return true; +} diff --git a/plugins/channelrx/demodmeshtastic/meshtastickeysdialog.h b/plugins/channelrx/demodmeshtastic/meshtastickeysdialog.h new file mode 100644 index 000000000..f47cec29c --- /dev/null +++ b/plugins/channelrx/demodmeshtastic/meshtastickeysdialog.h @@ -0,0 +1,31 @@ +#ifndef INCLUDE_MESHTASTICKEYSDIALOG_H +#define INCLUDE_MESHTASTICKEYSDIALOG_H + +#include + +namespace Ui { +class MeshtasticKeysDialog; +} + +class MeshtasticKeysDialog : public QDialog +{ + Q_OBJECT + +public: + explicit MeshtasticKeysDialog(QWidget* parent = nullptr); + ~MeshtasticKeysDialog(); + + void setKeySpecList(const QString& keySpecList); + QString getKeySpecList() const; + +private slots: + void on_validate_clicked(); + void accept() override; + +private: + bool validateCurrentInput(); + + Ui::MeshtasticKeysDialog* ui; +}; + +#endif // INCLUDE_MESHTASTICKEYSDIALOG_H diff --git a/plugins/channelrx/demodmeshtastic/meshtastickeysdialog.ui b/plugins/channelrx/demodmeshtastic/meshtastickeysdialog.ui new file mode 100644 index 000000000..75678b168 --- /dev/null +++ b/plugins/channelrx/demodmeshtastic/meshtastickeysdialog.ui @@ -0,0 +1,112 @@ + + + MeshtasticKeysDialog + + + + 0 + 0 + 760 + 460 + + + + Meshtastic Keys + + + + + + One key entry per line (or comma/semicolon separated). +Formats: default, none, simple1..simple10, hex:<hex>, b64:<base64>, base64:<base64>, raw hex, raw base64. +Optional channel mapping: channelName=keySpec (example: LongFast=default). + + + true + + + + + + + Enter one or more key specs used to decrypt Meshtastic packets. + + + LongFast=default +none +LongSlow=hex:00112233445566778899aabbccddeeff + + + + + + + + + Validate key syntax and count without saving. + + + Validate + + + + + + + Validation status for the current key list. + + + true + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + MeshtasticKeysDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + MeshtasticKeysDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +