mirror of
https://github.com/f4exb/sdrangel.git
synced 2026-06-27 14:03:24 -04:00
meshcore: add MeshCore protocol RX/TX channel plugins
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2020 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QStringList>
|
||||
|
||||
#include "meshcoremodencoder.h"
|
||||
#include "meshcoremodencoderlora.h"
|
||||
#include "meshcorepacket.h"
|
||||
#include "meshcore_identity.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// Translate `MeshcoreModSettings::MessageType` + identity into a single
|
||||
// `MESHCORE: type=...; ...` command string that `Packet::buildFrameFromCommand`
|
||||
// already knows how to render into wire bytes. All optional knobs (lat/lon,
|
||||
// txt_type, route, ts) come from settings; the seed is loaded from the
|
||||
// shared identity store on demand.
|
||||
QString buildMeshcoreCommandFromSettings(const MeshcoreModSettings& s)
|
||||
{
|
||||
auto loadSeedHex = [&]() -> QString {
|
||||
const QString path = s.m_meshIdentityPath.isEmpty()
|
||||
? modemmeshcore::identity::defaultIdentityPath()
|
||||
: s.m_meshIdentityPath;
|
||||
modemmeshcore::identity::Identity id =
|
||||
modemmeshcore::identity::loadOrCreateIdentity(path);
|
||||
return id.isValid() ? id.seedHex() : QString();
|
||||
};
|
||||
|
||||
auto resolvedNodeName = [&]() -> QString {
|
||||
if (!s.m_meshNodeName.isEmpty()) return s.m_meshNodeName;
|
||||
const QString path = s.m_meshIdentityPath.isEmpty()
|
||||
? modemmeshcore::identity::defaultIdentityPath()
|
||||
: s.m_meshIdentityPath;
|
||||
modemmeshcore::identity::Identity id =
|
||||
modemmeshcore::identity::loadOrCreateIdentity(path);
|
||||
return modemmeshcore::identity::defaultNodeNameFor(id);
|
||||
};
|
||||
|
||||
const quint64 now = static_cast<quint64>(QDateTime::currentSecsSinceEpoch());
|
||||
QStringList toks;
|
||||
|
||||
switch (s.m_messageType)
|
||||
{
|
||||
case MeshcoreModSettings::MessageText:
|
||||
// Caller-managed: m_textMessage already carries either a literal or
|
||||
// a MESHCORE: command. Encoder's existing path handles both.
|
||||
return QString();
|
||||
|
||||
case MeshcoreModSettings::MessageAdvert: {
|
||||
const QString seed = loadSeedHex();
|
||||
if (seed.isEmpty()) return QString();
|
||||
toks << "type=advert"
|
||||
<< "seed=" + seed
|
||||
<< "name=" + resolvedNodeName()
|
||||
<< "ts=" + QString::number(now)
|
||||
<< "route=flood";
|
||||
if (s.m_meshAdvertLocationEnabled) {
|
||||
toks << QString("lat=%1").arg(s.m_meshAdvertLat, 0, 'f', 6);
|
||||
toks << QString("lon=%1").arg(s.m_meshAdvertLon, 0, 'f', 6);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MeshcoreModSettings::MessageTxtMsg: {
|
||||
const QString seed = loadSeedHex();
|
||||
if (seed.isEmpty() || s.m_meshDestPubKeyHex.isEmpty()) return QString();
|
||||
toks << "type=txt_msg"
|
||||
<< "seed=" + seed
|
||||
<< "dest=" + s.m_meshDestPubKeyHex
|
||||
<< "ts=" + QString::number(now)
|
||||
<< "text=" + s.m_textMessage;
|
||||
break;
|
||||
}
|
||||
|
||||
case MeshcoreModSettings::MessageGrpTxt: {
|
||||
toks << "type=grp_txt";
|
||||
if (!s.m_meshGroupChannelPskHex.isEmpty()) {
|
||||
toks << "channel_psk=" + s.m_meshGroupChannelPskHex;
|
||||
} else {
|
||||
toks << "channel=" + (s.m_meshGroupChannelName.isEmpty()
|
||||
? QStringLiteral("public")
|
||||
: s.m_meshGroupChannelName);
|
||||
}
|
||||
toks << "ts=" + QString::number(now)
|
||||
<< "text=" + s.m_textMessage;
|
||||
break;
|
||||
}
|
||||
|
||||
case MeshcoreModSettings::MessageAnonReq: {
|
||||
const QString seed = loadSeedHex();
|
||||
if (seed.isEmpty() || s.m_meshDestPubKeyHex.isEmpty()) return QString();
|
||||
toks << "type=anon_req"
|
||||
<< "seed=" + seed
|
||||
<< "dest=" + s.m_meshDestPubKeyHex
|
||||
<< "ts=" + QString::number(now)
|
||||
<< "data=text:" + s.m_textMessage;
|
||||
break;
|
||||
}
|
||||
|
||||
case MeshcoreModSettings::MessageAck: {
|
||||
if (s.m_meshDestPubKeyHex.isEmpty() || s.m_meshAckMsgHashHex.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
toks << "type=ack"
|
||||
<< "dest=" + s.m_meshDestPubKeyHex
|
||||
<< "msg_hash=" + s.m_meshAckMsgHashHex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (toks.isEmpty()) return QString();
|
||||
return QStringLiteral("MESHCORE: ") + toks.join(QStringLiteral("; "));
|
||||
}
|
||||
|
||||
} // anonymous
|
||||
|
||||
const MeshcoreModSettings::CodingScheme MeshcoreModEncoder::m_codingScheme = MeshcoreModSettings::CodingLoRa;
|
||||
const bool MeshcoreModEncoder::m_hasCRC = true;
|
||||
const bool MeshcoreModEncoder::m_hasHeader = true;
|
||||
|
||||
MeshcoreModEncoder::MeshcoreModEncoder() :
|
||||
m_nbSymbolBits(5),
|
||||
m_nbParityBits(1)
|
||||
{}
|
||||
|
||||
MeshcoreModEncoder::~MeshcoreModEncoder()
|
||||
{}
|
||||
|
||||
void MeshcoreModEncoder::setNbSymbolBits(unsigned int spreadFactor, unsigned int deBits)
|
||||
{
|
||||
m_spreadFactor = spreadFactor;
|
||||
|
||||
if (deBits >= spreadFactor) {
|
||||
m_deBits = m_spreadFactor - 1;
|
||||
} else {
|
||||
m_deBits = deBits;
|
||||
}
|
||||
|
||||
m_nbSymbolBits = m_spreadFactor - m_deBits;
|
||||
}
|
||||
|
||||
void MeshcoreModEncoder::encode(MeshcoreModSettings settings, std::vector<unsigned short>& symbols)
|
||||
{
|
||||
if (m_nbSymbolBits < 5) {
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray bytes;
|
||||
QString summary;
|
||||
QString error;
|
||||
|
||||
// Synthesise a MESHCORE: command from m_messageType + identity store +
|
||||
// settings fields. For MessageText we fall through to the legacy path
|
||||
// which honours either a literal payload or a hand-typed MESHCORE: line
|
||||
// in m_textMessage.
|
||||
QString command;
|
||||
if (settings.m_messageType != MeshcoreModSettings::MessageText) {
|
||||
command = buildMeshcoreCommandFromSettings(settings);
|
||||
} else if (modemmeshcore::Packet::isCommand(settings.m_textMessage)) {
|
||||
command = settings.m_textMessage;
|
||||
}
|
||||
|
||||
if (!command.isEmpty())
|
||||
{
|
||||
if (!modemmeshcore::Packet::buildFrameFromCommand(command, bytes, summary, error))
|
||||
{
|
||||
qWarning() << "MeshcoreModEncoder::encode: Meshcore command error:"
|
||||
<< error << " command=" << command;
|
||||
return;
|
||||
}
|
||||
qInfo() << "MeshcoreModEncoder::encode:" << summary;
|
||||
}
|
||||
else
|
||||
{
|
||||
bytes = settings.m_textMessage.toUtf8();
|
||||
}
|
||||
|
||||
encodeBytesLoRa(bytes, symbols);
|
||||
}
|
||||
|
||||
void MeshcoreModEncoder::encodeBytes(const QByteArray& bytes, std::vector<unsigned short>& symbols)
|
||||
{
|
||||
switch (m_codingScheme)
|
||||
{
|
||||
case MeshcoreModSettings::CodingLoRa:
|
||||
{
|
||||
QByteArray payload(bytes);
|
||||
|
||||
if (modemmeshcore::Packet::isCommand(QString::fromUtf8(bytes)))
|
||||
{
|
||||
QString summary;
|
||||
QString error;
|
||||
|
||||
if (!modemmeshcore::Packet::buildFrameFromCommand(QString::fromUtf8(bytes), payload, summary, error))
|
||||
{
|
||||
qWarning() << "MeshcoreModEncoder::encodeBytes: Meshcore command error:" << error;
|
||||
return;
|
||||
}
|
||||
|
||||
qInfo() << "MeshcoreModEncoder::encodeBytes:" << summary;
|
||||
}
|
||||
|
||||
encodeBytesLoRa(payload, symbols);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
void MeshcoreModEncoder::encodeBytesLoRa(const QByteArray& bytes, std::vector<unsigned short>& symbols)
|
||||
{
|
||||
QByteArray payload(bytes);
|
||||
|
||||
if ((payload.size() + (m_hasCRC ? 2 : 0)) > 255)
|
||||
{
|
||||
qWarning() << "MeshcoreModEncoder::encodeBytesLoRa: payload too large:" << payload.size();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_hasCRC) {
|
||||
MeshcoreModEncoderLoRa::addChecksum(payload);
|
||||
}
|
||||
|
||||
const unsigned int headerNbSymbolBits = (m_hasHeader && (m_spreadFactor > 2U))
|
||||
? (m_spreadFactor - 2U)
|
||||
: m_nbSymbolBits;
|
||||
|
||||
MeshcoreModEncoderLoRa::encodeBytes(
|
||||
payload,
|
||||
symbols,
|
||||
m_nbSymbolBits,
|
||||
headerNbSymbolBits,
|
||||
m_hasHeader,
|
||||
m_hasCRC,
|
||||
m_nbParityBits
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user