1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2026-06-27 05:53:38 -04:00
Files
sdrangel/plugins/channeltx/modmeshcore/meshcoremodencoder.cpp
T

256 lines
8.7 KiB
C++
Raw Normal View History

///////////////////////////////////////////////////////////////////////////////////
// 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
);
}