mirror of
https://github.com/f4exb/sdrangel.git
synced 2026-06-26 21:43:25 -04:00
256 lines
8.7 KiB
C++
256 lines
8.7 KiB
C++
///////////////////////////////////////////////////////////////////////////////////
|
|
// 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
|
|
);
|
|
}
|