1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2026-05-14 05:12:09 -04:00

Meshtastic demod: make key dialog an object on its own

This commit is contained in:
f4exb 2026-03-19 04:43:13 +01:00
parent 23e17844d6
commit 748f024771
6 changed files with 322 additions and 96 deletions

84
modemmeshtastic/README.md Normal file
View File

@ -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.

View File

@ -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")

View File

@ -21,8 +21,6 @@
#include "device/deviceuiset.h"
#include "device/deviceapi.h"
#include <QDialog>
#include <QDialogButtonBox>
#include <QScrollBar>
#include <QDebug>
#include <QCoreApplication>
@ -30,10 +28,7 @@
#include <QDateTime>
#include <QComboBox>
#include <QCheckBox>
#include <QMessageBox>
#include <QPushButton>
#include <QLabel>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QAbstractItemView>
#include <QList>
@ -41,10 +36,8 @@
#include <QPlainTextEdit>
#include <QSplitter>
#include <QTabWidget>
#include <QTextEdit>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QVBoxLayout>
#include <algorithm>
#include <cmath>
#include <functional>
@ -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:<hex>, b64:<base64>, base64:<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

View File

@ -0,0 +1,71 @@
#include <QMessageBox>
#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;
}

View File

@ -0,0 +1,31 @@
#ifndef INCLUDE_MESHTASTICKEYSDIALOG_H
#define INCLUDE_MESHTASTICKEYSDIALOG_H
#include <QDialog>
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

View File

@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MeshtasticKeysDialog</class>
<widget class="QDialog" name="MeshtasticKeysDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>760</width>
<height>460</height>
</rect>
</property>
<property name="windowTitle">
<string>Meshtastic Keys</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="helpLabel">
<property name="text">
<string>One key entry per line (or comma/semicolon separated).
Formats: default, none, simple1..simple10, hex:&lt;hex&gt;, b64:&lt;base64&gt;, base64:&lt;base64&gt;, raw hex, raw base64.
Optional channel mapping: channelName=keySpec (example: LongFast=default).</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="keyEditor">
<property name="toolTip">
<string>Enter one or more key specs used to decrypt Meshtastic packets.</string>
</property>
<property name="placeholderText">
<string>LongFast=default
none
LongSlow=hex:00112233445566778899aabbccddeeff</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="statusLayout">
<item>
<widget class="QPushButton" name="validate">
<property name="toolTip">
<string>Validate key syntax and count without saving.</string>
</property>
<property name="text">
<string>Validate</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="statusLabel">
<property name="toolTip">
<string>Validation status for the current key list.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>MeshtasticKeysDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>MeshtasticKeysDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>