From 66e0987113e71f60630452419c82d8b45734b507 Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Thu, 14 May 2026 19:56:07 -0400 Subject: [PATCH] Add ability to set directory where Tesla firmware is stored, use it to populate fields and allow selection of ECU to flash. --- re/udsfirmwareuploaderwindow.cpp | 191 +++++++++++++++++++++++++++++++ re/udsfirmwareuploaderwindow.h | 26 +++++ ui/udsfirmwareuploaderwindow.ui | 82 +++++++++---- 3 files changed, 279 insertions(+), 20 deletions(-) diff --git a/re/udsfirmwareuploaderwindow.cpp b/re/udsfirmwareuploaderwindow.cpp index 7b41ba1..cc072ab 100644 --- a/re/udsfirmwareuploaderwindow.cpp +++ b/re/udsfirmwareuploaderwindow.cpp @@ -7,6 +7,10 @@ #include #include #include +#include +#include +#include +#include UDSFirmwareUploaderWindow::UDSFirmwareUploaderWindow(const QVector *frames, QWidget *parent) : QDialog(parent), @@ -42,9 +46,15 @@ UDSFirmwareUploaderWindow::UDSFirmwareUploaderWindow(const QVector *fr connect(ui->btnLoadFile, &QPushButton::clicked, this, &UDSFirmwareUploaderWindow::handleLoadFile); connect(ui->btnStartStop, &QPushButton::clicked, this, &UDSFirmwareUploaderWindow::handleStartStop); connect(ui->btnAbort, &QPushButton::clicked, this, &UDSFirmwareUploaderWindow::handleAbort); + connect(ui->btnBrowseTeslaDir, &QPushButton::clicked, this, &UDSFirmwareUploaderWindow::handleBrowseTeslaDir); + connect(ui->btnReloadNodes, &QPushButton::clicked, this, &UDSFirmwareUploaderWindow::handleReloadNodes); connect(udsHandler, &UDS_HANDLER::newUDSMessage, this, &UDSFirmwareUploaderWindow::gotUDSReply); connect(timeoutTimer, &QTimer::timeout, this, &UDSFirmwareUploaderWindow::timeoutTriggered); connect(testerTimer, &QTimer::timeout, this, &UDSFirmwareUploaderWindow::testerPresentTick); + connect(ui->cmbTeslaNode, QOverload::of(&QComboBox::currentIndexChanged), + this, &UDSFirmwareUploaderWindow::handleNodeChanged); + + loadTeslaNodes(); } UDSFirmwareUploaderWindow::~UDSFirmwareUploaderWindow() @@ -450,6 +460,187 @@ void UDSFirmwareUploaderWindow::gotUDSReply(UDS_MESSAGE msg) } } +void UDSFirmwareUploaderWindow::loadTeslaNodes() +{ + messageIdMap.clear(); + teslaNodes.clear(); + + QStringList searchPaths; + + if (!teslaDir.isEmpty()) + { + searchPaths.append(teslaDir + "/opt/odin/data/Model3/dej"); + searchPaths.append(teslaDir + "/opt/odin/data/Model3"); + } + + searchPaths.append(QCoreApplication::applicationDirPath()); + searchPaths.append(QDir::currentPath()); + searchPaths.append(QCoreApplication::applicationDirPath() + "/../"); + searchPaths.append(QDir::currentPath() + "/../"); + + QString compactPath; + QString nodesPath; + for (const QString &path : searchPaths) + { + if (compactPath.isEmpty()) + { + QString test = path + "/Model3_ETH.compact.json"; + if (QFile::exists(test)) compactPath = test; + } + if (nodesPath.isEmpty()) + { + QString test = path + "/nodes.json"; + if (QFile::exists(test)) nodesPath = test; + } + } + + // Load Model3_ETH.compact.json first to build message name -> ID map + if (!compactPath.isEmpty()) + { + QFile compactFile(compactPath); + if (compactFile.open(QIODevice::ReadOnly)) + { + QByteArray data = compactFile.readAll(); + compactFile.close(); + QJsonDocument doc = QJsonDocument::fromJson(data); + if (!doc.isNull()) + { + QJsonObject root = doc.object(); + QJsonObject messages = root["messages"].toObject(); + for (auto it = messages.begin(); it != messages.end(); it++) + { + QJsonObject msgObj = it->toObject(); + int msgId = msgObj["message_id"].toInt(-1); + if (msgId >= 0) + messageIdMap[it.key()] = static_cast(msgId); + } + logMessage("Loaded " + QString::number(messageIdMap.size()) + + " message IDs from Model3_ETH.compact.json"); + } + else + logMessage("ERROR: Failed to parse Model3_ETH.compact.json"); + } + else + logMessage("ERROR: Could not open Model3_ETH.compact.json"); + } + else + logMessage("WARNING: Model3_ETH.compact.json not found"); + + // Load nodes.json + if (!nodesPath.isEmpty()) + { + QFile nodesFile(nodesPath); + if (nodesFile.open(QIODevice::ReadOnly)) + { + QByteArray data = nodesFile.readAll(); + nodesFile.close(); + QJsonDocument doc = QJsonDocument::fromJson(data); + if (!doc.isNull()) + { + QJsonObject root = doc.object(); + for (auto it = root.begin(); it != root.end(); it++) + { + TeslaNodeInfo node; + node.name = it.key(); + QJsonObject obj = it->toObject(); + + QString reqName = obj["request_message_name"].toString(); + QString respName = obj["response_message_name"].toString(); + + node.requestId = messageIdMap.value(reqName, 0x7E0); + node.responseId = messageIdMap.value(respName, 0x7E8); + node.securityLevel = 1; + node.useSecurity = true; + + QJsonObject sec = obj["security"].toObject(); + if (!sec.isEmpty()) + { + node.securityAlgorithm = sec["algorithm"].toString(); + node.securityBufferSize = sec["buffer_size"].toInt(); + } + + node.dataUpload = obj["data_upload"].toString(); + + teslaNodes.append(node); + } + logMessage("Loaded " + QString::number(teslaNodes.size()) + + " Tesla nodes from nodes.json"); + populateNodeCombo(); + } + else + logMessage("ERROR: Failed to parse nodes.json"); + } + else + logMessage("ERROR: Could not open nodes.json"); + } + else + logMessage("WARNING: nodes.json not found"); + + if (!teslaDir.isEmpty()) + ui->txtTeslaDir->setText(teslaDir); + else if (!nodesPath.isEmpty()) + { + QFileInfo info(nodesPath); + teslaDir = info.absolutePath(); + ui->txtTeslaDir->setText(teslaDir); + } +} + +void UDSFirmwareUploaderWindow::populateNodeCombo() +{ + ui->cmbTeslaNode->clear(); + ui->cmbTeslaNode->addItem("-- Manual --"); + for (const TeslaNodeInfo &node : teslaNodes) + ui->cmbTeslaNode->addItem(node.name); +} + +void UDSFirmwareUploaderWindow::handleBrowseTeslaDir() +{ + QString dir = QFileDialog::getExistingDirectory(this, "Select Tesla Data Directory", teslaDir); + if (!dir.isEmpty()) + { + teslaDir = dir; + ui->txtTeslaDir->setText(teslaDir); + handleReloadNodes(); + } +} + +void UDSFirmwareUploaderWindow::handleReloadNodes() +{ + teslaDir = ui->txtTeslaDir->text().trimmed(); + loadTeslaNodes(); +} + +void UDSFirmwareUploaderWindow::handleNodeChanged(int index) +{ + if (index <= 0) + return; + autoFillFromNode(index - 1); +} + +void UDSFirmwareUploaderWindow::autoFillFromNode(int idx) +{ + if (idx < 0 || idx >= teslaNodes.size()) + return; + + const TeslaNodeInfo &node = teslaNodes[idx]; + ui->txtTargetID->setText("0x" + QString::number(node.requestId, 16).toUpper()); + ui->txtResponseID->setText("0x" + QString::number(node.responseId, 16).toUpper()); + if (!node.securityAlgorithm.isEmpty() && node.securityAlgorithm != "tesla_hash") + { + ui->ckUseSecurity->setChecked(false); + logMessage("Node " + node.name + " uses " + node.securityAlgorithm + + " security; security disabled (unsupported)"); + } + else + { + ui->ckUseSecurity->setChecked(node.useSecurity); + } + logMessage("Selected node: " + node.name + + " (Req: 0x" + QString::number(node.requestId, 16) + + ", Resp: 0x" + QString::number(node.responseId, 16) + ")"); +} + void UDSFirmwareUploaderWindow::timeoutTriggered() { logMessage("TIMEOUT - No response received"); diff --git a/re/udsfirmwareuploaderwindow.h b/re/udsfirmwareuploaderwindow.h index e9c8c87..b3a2337 100644 --- a/re/udsfirmwareuploaderwindow.h +++ b/re/udsfirmwareuploaderwindow.h @@ -4,9 +4,25 @@ #include #include #include +#include +#include +#include +#include #include "can_structs.h" #include "bus_protocols/uds_handler.h" +struct TeslaNodeInfo +{ + QString name; + uint32_t requestId; + uint32_t responseId; + int securityLevel; + bool useSecurity; + QString securityAlgorithm; + int securityBufferSize; + QString dataUpload; +}; + namespace Ui { class UDSFirmwareUploaderWindow; } @@ -44,11 +60,17 @@ private slots: void handleLoadFile(); void handleStartStop(); void handleAbort(); + void handleBrowseTeslaDir(); + void handleReloadNodes(); + void handleNodeChanged(int index); void gotUDSReply(UDS_MESSAGE msg); void timeoutTriggered(); void testerPresentTick(); private: + void loadTeslaNodes(); + void populateNodeCombo(); + void autoFillFromNode(int index); void logMessage(const QString &msg); void setState(UPLOAD_STATE newState); void startUpload(); @@ -84,6 +106,10 @@ private: int maxBlockLength; int currentDataPos; bool useSecurity; + + QString teslaDir; + QVector teslaNodes; + QMap messageIdMap; }; #endif // UDSFIRMWAREUPLOADERWINDOW_H diff --git a/ui/udsfirmwareuploaderwindow.ui b/ui/udsfirmwareuploaderwindow.ui index 7a0204c..18986d9 100644 --- a/ui/udsfirmwareuploaderwindow.ui +++ b/ui/udsfirmwareuploaderwindow.ui @@ -6,7 +6,7 @@ 0 0 - 520 + 624 520 @@ -27,41 +27,69 @@ - + - Target Request ID: + Tesla Data Dir: - + + + + + Path to Tesla firmware root directory + + + + + + + - 0x7E0 + Target ECU: - + + + + Target Response ID: - + 0x7E8 - + + + + Target Request ID: + + + + + + + 0x7E0 + + + + Session Type: - + @@ -75,14 +103,28 @@ - + Security Level: - + + + + Flash Address: + + + + + + + 0x08000000 + + + + @@ -109,17 +151,17 @@ - - + + - Flash Address: + Reload - - + + - 0x08000000 + Browse... @@ -164,12 +206,12 @@ - - Abort - false + + Abort +