Files
NODEDC_MISSION_CORE/plugins/vesc/native/offline_main.cpp
T

95 lines
5.9 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
// Mission Core adapter to the unmodified VESC Tool engine. No hardware transport
// is admitted by this executable. Input is one archived snapshot on stdin.
#include "config_export.h"
int main(int argc, char **argv) {
// This application never calls connectSerial/connectTcp/connectBle or starts
// a Qt event loop. Upstream timers therefore cannot reconnect or poll.
qputenv("QT_QPA_PLATFORM", "offscreen");
QApplication app(argc, argv);
QCoreApplication::setOrganizationName("MissionCore");
QCoreApplication::setApplicationName("VescToolOfflineAdapter");
QJsonObject result{{"schema", "missioncore.vesc.native-offline/v1"},
{"upstream_commit", "01d5f10901116c311e3fb84d5a1541f663d3ce20"},
{"upstream_version", QString::number(VT_VERSION, 'f', 2)},
{"hardware_access", false}};
try {
require(argc == 1, "No command-line transport or operation arguments are accepted");
QFile input; require(input.open(stdin, QIODevice::ReadOnly), "Cannot read input");
const auto raw = input.read(1024 * 1024 + 1);
require(!raw.isEmpty() && raw.size() <= 1024 * 1024, "Archive input is empty or too large");
QJsonParseError parse;
auto document = QJsonDocument::fromJson(raw, &parse);
require(parse.error == QJsonParseError::NoError && document.isObject(), "Invalid archive JSON");
const auto archive = document.object();
const auto identity = archive.value("identity").toObject();
FW_RX_PARAMS fw;
fw.major = identity.value("major").toInt(-1);
fw.minor = identity.value("minor").toInt(-1);
fw.hw = identity.value("hardware").toString();
fw.isTestFw = identity.value("test_firmware").toInt(-1);
fw.customConfigNum = identity.value("custom_configs").toInt(-1);
const auto uuid = identity.value("uuid").toString();
require(QRegularExpression("^[0-9a-fA-F]{24}$").match(uuid).hasMatch(), "Invalid archived UUID");
fw.uuid = QByteArray::fromHex(uuid.toLatin1());
require(fw.major == 5 && fw.minor == 2 && fw.isTestFw == 0 && fw.customConfigNum == 0
&& identity.value("hardware_type").toInt(-1) == 0,
"Offline acceptance currently admits stable firmware 5.02 only");
VescInterface vesc;
require(Utility::configLoadLatest(&vesc), "Bundled upstream configuration resources missing");
auto *commands = vesc.commands();
// Replay the archived identity through the real firmware negotiation
// signal. The real engine selects bundled schemas and compatibility.
commands->fwVersionReceived(fw);
require(!vesc.isPortConnected(), "Offline adapter unexpectedly connected");
require(vesc.mcConfig()->getSerializeOrder().size() > 0, "Native schema not selected");
bool failed = false;
QObject::connect(commands, &Commands::deserializeConfigFailed,
[&](bool, bool) { failed = true; });
auto motor = archivePacket(archive, "motor", COMM_GET_MCCONF);
auto application = archivePacket(archive, "application", COMM_GET_APPCONF);
// FW 5.02 has fixed-size configurations. Obtain their size through the
// public native serializer; do not reproduce the private schema walker.
VByteArray motorShape, applicationShape;
vesc.mcConfig()->serialize(motorShape);
vesc.appConfig()->serialize(applicationShape);
require(motor.size() == motorShape.size() + 1 && application.size() == applicationShape.size() + 1,
"Archive length differs from the native firmware schema");
commands->processPacket(motor);
commands->processPacket(application);
require(!failed, "Upstream rejected archived configuration");
result.insert("motor", exportConfig(vesc.mcConfig(), motor, "MCConfiguration"));
result.insert("application", exportConfig(vesc.appConfig(), application, "APPConfiguration"));
// A disconnected serializer-only probe demonstrates that FW-specific
// detect corrections execute upstream. This byte array goes nowhere.
QByteArray encodedDetect;
QObject::connect(commands, &Commands::dataToSend, [&](QByteArray &data) {
if (!data.isEmpty() && quint8(data.at(0)) == COMM_DETECT_APPLY_ALL_FOC) encodedDetect = data;
});
commands->detectAllFoc(false, 100.0, 0.0, 0.0, 0.0, 0.0);
require(!encodedDetect.isEmpty() && !vesc.isPortConnected(), "Offline serialization failed");
result.insert("compatibility", QJsonObject{
{"legacy_power_loss_correction", commands->getMaxPowerLossBug()},
{"offline_detect_example_base64", QString::fromLatin1(encodedDetect.toBase64())},
{"example_requested_power_loss_w", 100.0}, {"transmitted_to_hardware", false}});
QJsonArray rpmExamples;
QObject::connect(commands, &Commands::dataToSend, [&](QByteArray &data) {
if (!data.isEmpty() && quint8(data.at(0)) == COMM_SET_RPM)
rpmExamples.append(QString::fromLatin1(data.toBase64()));
});
commands->setRpm(3000);
commands->setRpm(-3000);
require(rpmExamples.size() == 2 && !vesc.isPortConnected(), "Offline signed RPM serialization failed");
result.insert("offline_signed_rpm_examples_base64", rpmExamples);
result.insert("archive_sha256", sha256(raw));
result.insert("ok", true);
} catch (const std::exception &error) {
result = QJsonObject{{"schema", "missioncore.vesc.native-offline/v1"},
{"ok", false}, {"hardware_access", false}, {"error", error.what()}};
}
QFile output; output.open(stdout, QIODevice::WriteOnly);
output.write(QJsonDocument(result).toJson(QJsonDocument::Compact) + '\n');
return result.value("ok").toBool() ? 0 : 1;
}