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