86 lines
5.3 KiB
C++
86 lines
5.3 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}});
|
|
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;
|
|
}
|