准备工具:KD6630开发板、网线、PC端、软件升级包。TCP/UDS协议、QT、Wireshark抓包工具。
主要功能为给网关控制器的MCU端做升级更新。本质为HEX、asc文件的传输。
附加功能:能将路由表excel文件转换为hex文件。代码如下:
int client::readExcelAndGenerateHex(const QString &excelFilePath)
{
qDebug() << "Attempting to open file at path: " << excelFilePath;
QAxObject *excel = new QAxObject("Excel.Application", this);
if (!excel) {
qDebug() << "Failed to create Excel object";
return -1;
}
QAxObject *workbooks = excel->querySubObject("Workbooks");
if (!workbooks) {
qDebug() << "Failed to query Workbooks sub-object";
return -1;
}
QAxObject *workbook = workbooks->querySubObject("Open(const QString&)", excelFilePath);
if (!workbook) {
qDebug() << "Failed to open workbook";
return-1;
}
QAxObject *worksheet = workbook->querySubObject("Worksheets(int)", 1);
if (!worksheet) {
qDebug() << "Failed to query Worksheets sub-object";
return -1;
}
QAxObject *usedRange = worksheet->querySubObject("UsedRange");
if (!usedRange) {
qDebug() << "Failed to query UsedRange sub-object";
return -1;
}
QAxObject *lastCell = worksheet->querySubObject("Cells(int,int)", worksheet->querySubObject("Rows")->property("Count").toInt(), 1)
->querySubObject("End(int)", -4162); // xlUp constant
int rowCount = lastCell->property("Row").toInt();
qDebug() << "Total number of rows:" << rowCount;
QAxObject *rows = usedRange->querySubObject("Rows");
if (!rows) {
qDebug() << "Failed to query Rows sub-object";
return -1;
}
QAxObject *columns = usedRange->querySubObject("Columns");
if (!columns) {
qDebug() << "Failed to query Columns sub-object";
return -1;
}
int columnCount = columns->property("Count").toInt();
qDebug()<<"columnCount:"<<columnCount;
QString valid,num;
QStringList canswts;
QStringList canbauds;
for(int col = 1; col <= columnCount; ++col) {
QString header = worksheet->querySubObject("Cells(int,int)", 1, col)->dynamicCall("Value2()").toString();
// 检查列是否为空
if (header.isEmpty()) {
continue; // 如果列为空,跳过该列
}
qDebug() << "Column" << col << ":" << header;
}
for(int col=1;col<11;col++)
{
QString header=worksheet->querySubObject("Cells(int,int)",1,col)->dynamicCall("Value2()").toString();
if(header=="VALID")
{
valid=worksheet->querySubObject("Cells(int,int)",2,col)->dynamicCall("Value2()").toString();
}
else if(header.startsWith("CAN")&&header.endsWith("SWT"))
{
qDebug()<<"ENtering CANSWT";
QString canswt=worksheet->querySubObject("Cells(int,int)",2,col)->dynamicCall("Value2()").toString();
canswts.append(canswt);
}
else if(header=="NUM")
{
qDebug()<<"ENtering NUM";
QString num_str=worksheet->querySubObject("Cells(int,int)",2,col)->dynamicCall("Value2()").toString();
int true_count=rowCount-5;
int num_int=num_str.toInt();
qDebug()<<"num_int:"<<num_int;
qDebug()<<"true_count:"<<true_count;
if(num_int>true_count)
{
num="0002";
qDebug()<<"num::"<<num;
}
else {
QMessageBox::critical(this, "路由关系", "路由关系数量错误\n");
return -1;
}
}
else if(!header.isEmpty()&& header!= "NUM"&&header!="VALID"&&!header.startsWith("CAN")&&!header.endsWith("SWT") )
{
qDebug()<<"第一行有问题:"<<header;
QMessageBox::critical(this, "格式错误", "输入excel码格式错误\n");
return -1;
}
QString headersecond=worksheet->querySubObject("Cells(int,int)",3,col)->dynamicCall("Value2()").toString();
if(headersecond.startsWith("CAN")&&headersecond.endsWith("BAUD"))
{
qDebug()<<"ENtering CANBAUD";
QString canbaud=worksheet->querySubObject("Cells(int,int)",4,col)->dynamicCall("Value2()").toString();
canbauds.append(canbaud);
}
else if(headersecond=="VERSION")
{
QString ver_ascii=worksheet->querySubObject("Cells(int,int)",4,col)->dynamicCall("Value2()").toString();
qDebug()<<"ver_ascii:"<<ver_ascii;
for(QChar assic:ver_ascii)
{
int hex_assic=assic.toLatin1();
version.append(QString::number(hex_assic, 16).toUpper().rightJustified(2, '0'));
}
qDebug()<<"version:"<<version;
}
else if (!headersecond.isEmpty() && headersecond!="VERSION" && !headersecond.startsWith("CAN") && !headersecond.endsWith("BAUD") )
{
qDebug()<<"第二行有问题:"<<headersecond;
QMessageBox::critical(this, "格式错误", "输入excel码格式错误\n");
return -1;
}
}
if(valid=="ON")
{
QString headerhex="AAA5A5A5";
QString CANS;
for(auto &canswt:canswts)
{
// qDebug()<<"canswt:"<<canswt;
if(canswt=="ON")
{
CANS+="1";
}
else if(canswt=="OFF")
{
CANS+="0";
}
else if(canswt!="ON"&&canswt!="OFF")
{
QMessageBox::critical(this, "格式错误", "输入excel码格式错误\n");
return -1;
}
}
quint8 binvalue=0;
for(int i=0;i<8;i++)
{
qDebug()<<"CAN["<<i<<"]:"<<CANS[i];
if(CANS[i]=='1')
{
binvalue|=(1<<i);
}
}
qDebug()<<"binvalue:"<<binvalue;
QString hexValue = QString("%1").arg(binvalue, 2, 16, QChar('0'));
for(auto &canbaud:canbauds)
{
qDebug()<<"canbaud:"<<canbaud;
if(canbaud!="250K"&&canbaud!="500K"&&canbaud!="1000K")
{
QMessageBox::critical(this, "格式错误", "输入excel码格式错误\n");
return -1;
}
if(canbaud=="250K")
{
headerhex=headerhex+"01";
qDebug()<<"headerhex:"<<headerhex;
}
else if(canbaud=="500K")
{
headerhex=headerhex+"02";
qDebug()<<"headerhex:"<<headerhex;
}
else if(canbaud=="1000K")
{
headerhex=headerhex+"03";
qDebug()<<"headerhex:"<<headerhex;
}
}
qDebug()<<"num:"<<num;
qint8 numF=32-version.size();
qDebug()<<"numF:"<<numF;
qDebug()<<"version:"<<version;
qDebug()<<"version.size:"<<version.size();
QString HeaderVer=headerhex+hexValue+num+"FF"+version;
qDebug()<<"headerhex:"<<headerhex;
qDebug()<<"hexValue:"<<hexValue;
qDebug()<<"num:"<<num;
qDebug()<<"HeaderVer:"<<HeaderVer;
Headerstr=HeaderVer.append(QString(numF,'F'));
//Headerstr=headerhex+hexValue+num+"FF"+version+"FFFFFFFFFF";
qDebug()<<"num2:"<<num;
qDebug()<<"Headerstr:"<<Headerstr;
qDebug()<<"headerstr.size:"<<Headerstr.size();
string_hex+=":02000004103CAE";
string_hex+="\n";
//string_hex+=":20000000"+Headerstr;
QString line=":20000000"+Headerstr;
qDebug()<<"line:"<<line;
int linesize=line.size();
qDebug()<<"linesize:"<<linesize;
string_hex += line;
//qDebug()<<"string_hex:"<<string_hex;
string_hex+="\n";
string_hex+=":20002000";
int start_address=0x0040;
int current_address=0x0040;
for(int i=6;i<rowCount+1;i++)
{
if(prossrow(worksheet,i)!=0)
{
qDebug()<<"列表读取失败";
return -1;
}
else{
if(i>=7&&i%2==1)
{
qDebug()<<"current_address:"<<QString::number(current_address, 16).toUpper().rightJustified(4, '0');
// string_hex+=":20"+QString::number(current_address, 16).toUpper().rightJustified(4, '0')+"00";
//string_hex+="AC";
string_hex+="\n";
QString line=":20"+QString::number(current_address, 16).toUpper().rightJustified(4, '0')+"00";
string_hex+=line;
}else if(i>=7&&i%2==0) {
current_address=start_address+(i-6)*0x0010;
continue;
}
}
}
//qDebug()<<"string_hex:"<<string_hex;
QString lastline=string_hex.split("\n").last();
qDebug()<<"lastline:"<<lastline;
int lastline_length=lastline.length();
for(int i=lastline_length;i<linesize;i++)
{
string_hex+="0";
}
string_hex+="\n";
string_hex+=":00000001FF";
workbook->dynamicCall("Close()");
excel->dynamicCall("Quit()");
delete columns;
delete rows;
delete usedRange;
delete worksheet;
delete workbook;
delete workbooks;
excel->dynamicCall("ReleaseDispatch()");
delete excel;
}
else {
QMessageBox::critical(this, "VALID", "路由信息无效\n");
return -1;
}
return 0;
}
硬件连接:
首先保证电脑和板端在同一网段能ping通,上位机界面如下图所示。
点击选择含有指定flash、路由表、app等hex文件的目录。
asc文件hex的验签文件,先转成bin文件再加签生成asc文件。函数如下:
bool client::processFile(const QString &hexFilePath, const QString &binFilePath, const QString &ascFilePath)
{
if(!hexToBin(hexFilePath,binFilePath))
{
qDebug()<<hexFilePath<<"转bin文件失败";
QMessageBox::critical(this, "文件转换失败", hexFilePath+"文件转换bin文件失败\n");
return false;
}
QByteArray sign;
if(!DigestAndSign(binFilePath, "SHA256", sign))
{
qDebug()<<"验签文件失败"<<binFilePath;
QMessageBox::critical(this, "文件验签失败", binFilePath+"文件验签失败\n");
return false;
}
QFile binFile(binFilePath); // 生产的bin文件
if (binFile.open(QIODevice::WriteOnly))
{
qint64 writtenBytes = binFile.write(sign); // 写入签名数据
if (writtenBytes == -1)
{
qDebug() << "Failed to write to bin file.";
}
else
{
qDebug() << "Successfully wrote" << writtenBytes << "bytes to bin file.";
}
binFile.close(); // 关闭文件
}
else
{
qDebug() << "Failed to open bin file for writing.";
return false;
}
binToAsc(binFilePath,ascFilePath);
if (QFile::exists(binFilePath)) {
if (!QFile::remove(binFilePath)) {
qDebug() << "Failed to delete bin file after processing.";
} else {
qDebug() << "Bin file deleted after processing.";
}
}
return true;
}
生成验签文件后点击升级按钮,即可烧录6个文件,烧录函数如下,这里用到QT的信号和槽机制。
void client::on_pushButton_clicked()
{
qDebug() << "button start";
ui->pushButton->setEnabled(false);
qDebug() << "button after";
//timeValid=true;
if (timeValid) {
qDebug() << "Entering on_pushButton_clicked";
flash_file = mcu_package + "/NEVC_E3110_None_FLS_IAR_Project.hex";
flash_signfile = mcu_package + "/NEVC_E3110_None_FLS_IAR_Project.asc";
app_file = mcu_package + "/NEVC_E3110_IAR_8p50_Project.hex";
app_signfile = mcu_package + "/NEVC_E3110_IAR_8p50_Project.asc";
routetable_file = mcu_package + "/CAN2CAN.hex";
routetable_signfile = mcu_package + "/CAN2CAN.asc";
mcu_json_file = mcu_package + "/mcu.json";
public_key_file = mcu_package + "/public.txt";
ui->listWidget_2->clear();
ui->listWidget_2->addItem("正在连接...");
QCoreApplication::processEvents();
if (worker) {
worker->deleteLater();
}
worker = new Worker(flash_file, flash_signfile, app_file, app_signfile, routetable_file, routetable_signfile, public_key_file);
worker->moveToThread(workerThread);
connect(workerThread, &QThread::started, worker, &Worker::process);
connect(worker, &Worker::connectionResult, this, &client::onConnectionResult);
connect(worker, &Worker::updateResult, this, &client::onUpdateResult);
connect(worker, &Worker::fristFile, this, &client::onfrisetFile);
connect(worker, &Worker::restartResult, this, &client::onRestartResult);
connect(worker, &Worker::finished, this, &client::on_fininshed);
qDebug() << "emit statechanged";
QMetaObject::invokeMethod(worker, "process", Qt::QueuedConnection);
qDebug() << "进入后台线程";
} else {
qDebug() << "NO find valid!";
QMessageBox::critical(nullptr, "错误", "请检查网络连接!");
qApp->quit();
}
}
通过wireshark可抓取文件的传输的交互报文:
其中还涉及协议中的加解密,发送逻辑。想起来再写