Add ability to set directory where Tesla firmware is stored, use it to populate fields and allow selection of ECU to flash.

This commit is contained in:
Collin Kidder
2026-05-14 19:56:07 -04:00
parent 035b65243b
commit 66e0987113
3 changed files with 279 additions and 20 deletions
+191
View File
@@ -7,6 +7,10 @@
#include <QFile>
#include <QFileDialog>
#include <QDateTime>
#include <QCoreApplication>
#include <QDir>
#include <QFileInfo>
#include <QJsonArray>
UDSFirmwareUploaderWindow::UDSFirmwareUploaderWindow(const QVector<CANFrame> *frames, QWidget *parent) :
QDialog(parent),
@@ -42,9 +46,15 @@ UDSFirmwareUploaderWindow::UDSFirmwareUploaderWindow(const QVector<CANFrame> *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<int>::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<uint32_t>(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");
+26
View File
@@ -4,9 +4,25 @@
#include <QDialog>
#include <QTimer>
#include <QElapsedTimer>
#include <QJsonDocument>
#include <QJsonObject>
#include <QMap>
#include <QVector>
#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<TeslaNodeInfo> teslaNodes;
QMap<QString, uint32_t> messageIdMap;
};
#endif // UDSFIRMWAREUPLOADERWINDOW_H
+62 -20
View File
@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>520</width>
<width>624</width>
<height>520</height>
</rect>
</property>
@@ -27,41 +27,69 @@
<widget class="QSpinBox" name="spinBus"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelTargetID">
<widget class="QLabel" name="labelTeslaDir">
<property name="text">
<string>Target Request ID:</string>
<string>Tesla Data Dir:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="txtTargetID">
<layout class="QHBoxLayout" name="horizontalLayoutTeslaDir">
<item>
<widget class="QLineEdit" name="txtTeslaDir">
<property name="placeholderText">
<string>Path to Tesla firmware root directory</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="5" column="0">
<widget class="QLabel" name="labelNode">
<property name="text">
<string>0x7E0</string>
<string>Target ECU:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<item row="5" column="1">
<widget class="QComboBox" name="cmbTeslaNode"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="labelRespID">
<property name="text">
<string>Target Response ID:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<item row="6" column="1">
<widget class="QLineEdit" name="txtResponseID">
<property name="text">
<string>0x7E8</string>
</property>
</widget>
</item>
<item row="3" column="0">
<item row="7" column="0">
<widget class="QLabel" name="labelTargetID">
<property name="text">
<string>Target Request ID:</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLineEdit" name="txtTargetID">
<property name="text">
<string>0x7E0</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="labelSession">
<property name="text">
<string>Session Type:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<item row="8" column="1">
<widget class="QComboBox" name="cbSessionType">
<item>
<property name="text">
@@ -75,14 +103,28 @@
</item>
</widget>
</item>
<item row="4" column="0">
<item row="9" column="0">
<widget class="QLabel" name="labelSecurity">
<property name="text">
<string>Security Level:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="10" column="0">
<widget class="QLabel" name="labelAddr">
<property name="text">
<string>Flash Address:</string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QLineEdit" name="txtFlashAddr">
<property name="text">
<string>0x08000000</string>
</property>
</widget>
</item>
<item row="9" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QSpinBox" name="spinSecurityLevel">
@@ -109,17 +151,17 @@
</item>
</layout>
</item>
<item row="5" column="0">
<widget class="QLabel" name="labelAddr">
<item row="3" column="1">
<widget class="QPushButton" name="btnReloadNodes">
<property name="text">
<string>Flash Address:</string>
<string>Reload</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLineEdit" name="txtFlashAddr">
<item row="2" column="1">
<widget class="QPushButton" name="btnBrowseTeslaDir">
<property name="text">
<string>0x08000000</string>
<string>Browse...</string>
</property>
</widget>
</item>
@@ -164,12 +206,12 @@
</item>
<item>
<widget class="QPushButton" name="btnAbort">
<property name="text">
<string>Abort</string>
</property>
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Abort</string>
</property>
</widget>
</item>
</layout>