#include "framefileio.h" #include #include #include #include #include #include #include #include #include "pcaplite.h" #include "utility.h" #include "blfhandler.h" QFile FrameFileIO::continuousFile; struct TeslaAPCANRecord { #pragma pack(push, 1) int64_t sec; int32_t nano; int32_t padding1; uint16_t id; uint8_t ctr; uint8_t data[8]; uint8_t padding2; #pragma pack(pop) }; FrameFileIO::FrameFileIO() { } bool FrameFileIO::saveFrameFile(QString &fileName, const QVector* frameCache) { QString filename; QFileDialog dialog(qApp->activeWindow()); QSettings settings; bool result = false; QStringList filters; filters.append(QString(tr("GVRET Logs (*.csv *.CSV)"))); filters.append(QString(tr("CRTD Logs (*.crt *.crtd *.CRT *.CRTD)"))); filters.append(QString(tr("Generic ID/Data CSV (*.csv *.CSV)"))); filters.append(QString(tr("BusMaster Log (*.log *.LOG)"))); filters.append(QString(tr("Microchip Log (*.can *.CAN *.log *.LOG)"))); filters.append(QString(tr("Vector Trace Files (*.trace *.TRACE)"))); filters.append(QString(tr("IXXAT MiniLog (*.csv *.CSV)"))); filters.append(QString(tr("CAN-DO Log (*.can *.avc *.evc *.qcc *.CAN *.AVC *.EVC *.QCC)"))); filters.append(QString(tr("Vehicle Spy (*.csv *.CSV)"))); filters.append(QString(tr("Candump/Kayak (*.log)"))); filters.append(QString(tr("Cabana Log (*.csv *.CSV)"))); filters.append(QString(tr("CANalyzer Ascii Log (*.asc *.ASC)"))); filters.append(QString(tr("CARBUS Analyzer (*.trc *.TRC)"))); dialog.setDirectory(settings.value("FileIO/LoadSaveDirectory", dialog.directory().path()).toString()); dialog.setFileMode(QFileDialog::AnyFile); dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); dialog.setAcceptMode(QFileDialog::AcceptSave); if (dialog.exec() == QDialog::Accepted) { filename = dialog.selectedFiles()[0]; QProgressDialog progress(qApp->activeWindow()); progress.setWindowModality(Qt::WindowModal); progress.setLabelText("Saving file..."); progress.setCancelButton(nullptr); progress.setRange(0,0); progress.setMinimumDuration(0); progress.show(); qApp->processEvents(); if (dialog.selectedNameFilter() == filters[0]) { if (!filename.contains('.')) filename += ".csv"; result = saveNativeCSVFile(filename, frameCache); } if (dialog.selectedNameFilter() == filters[1]) { if (!filename.contains('.')) filename += ".txt"; result = saveCRTDFile(filename, frameCache); } if (dialog.selectedNameFilter() == filters[2]) { if (!filename.contains('.')) filename += ".csv"; result = saveGenericCSVFile(filename, frameCache); } if (dialog.selectedNameFilter() == filters[3]) { if (!filename.contains('.')) filename += ".log"; result = saveLogFile(filename, frameCache); } if (dialog.selectedNameFilter() == filters[4]) { if (!filename.contains('.')) filename += ".log"; result = saveMicrochipFile(filename, frameCache); } if (dialog.selectedNameFilter() == filters[5]) { if (!filename.contains('.')) filename += ".trace"; result = saveTraceFile(filename, frameCache); } if (dialog.selectedNameFilter() == filters[6]) { if (!filename.contains('.')) filename += ".csv"; result = saveIXXATFile(filename, frameCache); } if (dialog.selectedNameFilter() == filters[7]) { if (!filename.contains('.')) filename += ".can"; result = saveCANDOFile(filename, frameCache); } if (dialog.selectedNameFilter() == filters[8]) { if (!filename.contains('.')) filename += ".csv"; result = saveVehicleSpyFile(filename, frameCache); } if (dialog.selectedNameFilter() == filters[9]) { if (!filename.contains('.')) filename += ".log"; saveCanDumpFile(filename,frameCache); } if (dialog.selectedNameFilter() == filters[10]) { if (!filename.contains('.')) filename += ".csv"; result = saveCabanaFile(filename, frameCache); } if (dialog.selectedNameFilter() == filters[11]) { if (!filename.contains('.')) filename += ".asc"; result = saveCanalyzerASC(filename, frameCache); } if (dialog.selectedNameFilter() == filters[12]) { if (!filename.contains('.')) filename += ".trc"; result = saveCARBUSAnalzyer(filename, frameCache); } progress.cancel(); if (result) { QStringList fileList = filename.split('/'); fileName = fileList[fileList.length() - 1]; settings.setValue("FileIO/LoadSaveDirectory", dialog.directory().path()); return true; } return false; } return false; } bool FrameFileIO::loadFrameFile(QString &fileName, QVector* frameCache) { QString filename; QFileDialog dialog; QSettings settings; bool result = false; QStringList filters; filters.append(QString(tr("Autodetect File Type (*.*)"))); filters.append(QString(tr("GVRET Logs (*.csv *.CSV)"))); filters.append(QString(tr("CRTD Logs (*.crt *.crtd *.CRT *.CRTD)"))); filters.append(QString(tr("BusMaster Log (*.log *.LOG)"))); filters.append(QString(tr("Microchip Log (*.can *.CAN *.log *.LOG)"))); filters.append(QString(tr("Vector trace files (*.trace *.TRACE)"))); filters.append(QString(tr("IXXAT MiniLog (*.csv *.CSV)"))); filters.append(QString(tr("CAN-DO Log (*.avc *.can *.evc *.qcc *.AVC *.CAN *.EVC *.QCC)"))); filters.append(QString(tr("Vehicle Spy (*.csv *.CSV)"))); filters.append(QString(tr("Candump/Kayak (*.log *.LOG)"))); filters.append(QString(tr("CANDump Lawicel (*.txt *.TXT *.LOG *.log)"))); filters.append(QString(tr("PCAN Viewer (*.trc *.TRC)"))); filters.append(QString(tr("Kvaser Log Decimal (*.txt *.TXT)"))); filters.append(QString(tr("Kvaser Log Hex (*.txt *.TXT)"))); filters.append(QString(tr("CANalyzer Ascii Log (*.asc *.ASC)"))); filters.append(QString(tr("CANalyzer Binary Log Files (*.blf *.BLF)"))); filters.append(QString(tr("CARBUS Analyzer Trace Files (*.trc *.TRC)"))); filters.append(QString(tr("CANHacker Trace Files (*.trc *.TRC)"))); filters.append(QString(tr("Generic ID/Data CSV (*.csv *.CSV)"))); filters.append(QString(tr("Cabana Log (*.csv *.CSV)"))); filters.append(QString(tr("CANOpen Magic (*.csv *.CSV)"))); filters.append(QString(tr("Tesla Autopilot Snapshot (*.CAN *.can)"))); filters.append(QString(tr("CLX000 (*.txt *.TXT)"))); filters.append(QString(tr("CANServer Binary Log (*.log *.LOG)"))); filters.append(QString(tr("Wireshark (*.pcap *.PCAP *.pcapng *.PCAPNG)"))); filters.append(QString(tr("Wireshark SocketCAN (*.pcap *.PCAP"))); dialog.setDirectory(settings.value("FileIO/LoadSaveDirectory", dialog.directory().path()).toString()); dialog.setFileMode(QFileDialog::ExistingFile); dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); if (dialog.exec() == QDialog::Accepted) { filename = dialog.selectedFiles()[0]; QString selectedNameFilter = dialog.selectedNameFilter(); QProgressDialog progress(qApp->activeWindow()); progress.setWindowModality(Qt::WindowModal); progress.setLabelText("Loading file..."); progress.setCancelButton(nullptr); progress.setRange(0,0); progress.setMinimumDuration(0); progress.show(); qApp->processEvents(); if (selectedNameFilter == filters[0]) result = autoDetectLoadFile(filename, frameCache); if (selectedNameFilter == filters[1]) result = loadNativeCSVFile(filename, frameCache); if (selectedNameFilter == filters[2]) result = loadCRTDFile(filename, frameCache); if (selectedNameFilter == filters[3]) result = loadLogFile(filename, frameCache); if (selectedNameFilter == filters[4]) result = loadMicrochipFile(filename, frameCache); if (selectedNameFilter == filters[5]) result = loadTraceFile(filename, frameCache); if (selectedNameFilter == filters[6]) result = loadIXXATFile(filename, frameCache); if (selectedNameFilter == filters[7]) result = loadCANDOFile(filename, frameCache); if (selectedNameFilter == filters[8]) result = loadVehicleSpyFile(filename, frameCache); if (selectedNameFilter == filters[9]) result = loadCanDumpFile(filename, frameCache); if (selectedNameFilter == filters[10]) result = loadLawicelFile(filename, frameCache); if (selectedNameFilter == filters[11]) result = loadPCANFile(filename, frameCache); if (selectedNameFilter == filters[12]) result = loadKvaserFile(filename, frameCache, false); if (selectedNameFilter == filters[13]) result = loadKvaserFile(filename, frameCache, true); if (selectedNameFilter == filters[14]) result = loadCanalyzerASC(filename, frameCache); if (selectedNameFilter == filters[15]) result = loadCanalyzerBLF(filename, frameCache); if (selectedNameFilter == filters[16]) result = loadCARBUSAnalyzerFile(filename, frameCache); if (selectedNameFilter == filters[17]) result = loadCANHackerFile(filename, frameCache); if (selectedNameFilter == filters[18]) result = loadGenericCSVFile(filename, frameCache); if (selectedNameFilter == filters[19]) result = loadCabanaFile(filename, frameCache); if (selectedNameFilter == filters[20]) result = loadCANOpenFile(filename, frameCache); if (selectedNameFilter == filters[21]) result = loadTeslaAPFile(filename, frameCache); if (selectedNameFilter == filters[22]) result = loadCLX000File(filename, frameCache); if (selectedNameFilter == filters[23]) result = loadCANServerFile(filename, frameCache); if (selectedNameFilter == filters[24]) result = loadWiresharkFile(filename, frameCache); if (selectedNameFilter == filters[25]) result = loadWiresharkSocketCANFile(filename, frameCache); progress.cancel(); if (result) { QStringList fileList = filename.split('/'); fileName = fileList[fileList.length() - 1]; settings.setValue("FileIO/LoadSaveDirectory", dialog.directory().path()); return true; } else { if (dialog.selectedNameFilter() != filters[0]) { QMessageBox msgBox; msgBox.setText("File load completed with errors.\r\nPerhaps you selected the wrong file type?"); msgBox.exec(); } return false; } } return false; } //Try every format by first using the "is" functions which try to detect whether a given file is a good match to that //file format or not. Those functions are much less tolerant than the load functions and so should help to discriminate //whether a file could be loaded or not by a given loader. The loader return is still used in case the guess was wrong. bool FrameFileIO::autoDetectLoadFile(QString filename, QVector* frames) { qDebug() << "Attempting Canalyzer BLF"; if (isCanalyzerBLF(filename)) { if (loadCanalyzerBLF(filename, frames)) { qDebug() << "Loaded as Canalyzer BLF successfully!"; return true; } } qDebug() << "Attempting native CSV"; if (isNativeCSVFile(filename)) { if (loadNativeCSVFile(filename, frames)) { qDebug() << "Loaded as native CSV successfully!"; return true; } } // Attempt to load socket CAN first to avoid generic wireshark logic catching it qDebug() << "Attempting Wireshark Socket CAN Log"; if (isWiresharkSocketCANFile(filename)) { if (loadWiresharkSocketCANFile(filename, frames)) { qDebug() << "Loaded as Wireshark SocketCAN Log successfully!"; return true; } } // This and the decoder above were both moved above TeslaAPFile as they match based on magic numbers and sometimes these files were falling into the TeslaAP decoder qDebug() << "Attempting Wireshark Log"; if (isWiresharkFile(filename)) { if (loadWiresharkFile(filename, frames)) { qDebug() << "Loaded as Wireshark Log successfully!"; return true; } } qDebug() << "Attempting Tesla AP Snapshot"; if (isTeslaAPFile(filename)) { if (loadTeslaAPFile(filename, frames)) { qDebug() << "Loaded as Tesla AP Snapshot successfully!"; return true; } } qDebug() << "Attempting CANServer Binary Log"; if (isCANServerFile(filename)) { if (loadCANServerFile(filename, frames)) { qDebug() << "Loaded as CANServer Binary Log successfully!"; return true; } } qDebug() << "Attempting canalyzer ASC"; if (isCanalyzerASC(filename)) { if (loadCanalyzerASC(filename, frames)) { qDebug() << "Loaded as Canalyzer ASC successfully!"; return true; } } qDebug() << "Attempting CRTD"; if (isCRTDFile(filename)) { if (loadCRTDFile(filename, frames)) { qDebug() << "Loaded as CRTD successfully!"; return true; } } qDebug() << "Attempting trace file"; if (isTraceFile(filename)) { if (loadTraceFile(filename, frames)) { qDebug() << "Loaded as trace successfully!"; return true; } } qDebug() << "Attempting vehicle spy"; if (isVehicleSpyFile(filename)) { if (loadVehicleSpyFile(filename, frames)) { qDebug() << "Loaded as vehicle spy successfully!"; return true; } } qDebug() << "Attempting candump"; if (isCanDumpFile(filename)) { if (loadCanDumpFile(filename, frames)) { qDebug() << "Loaded as candump successfully!"; return true; } } qDebug() << "Attempting 'CARBUS Analyzer'"; if (isCARBUSAnalyzerFile(filename)) { if (loadCARBUSAnalyzerFile(filename, frames)) { qDebug() << "Loaded as 'CARBUS Analyzer' successfully!"; return true; } } qDebug() << "Attempting canhacker"; if (isCANHackerFile(filename)) { if (loadCANHackerFile(filename, frames)) { qDebug() << "Loaded as CANHacker successfully!"; return true; } } qDebug() << "Attempting cabana"; if (isCabanaFile(filename)) { if (loadCabanaFile(filename, frames)) { qDebug() << "Loaded as Cabana successfully!"; return true; } } qDebug() << "Attempting canopen"; if (isCANOpenFile(filename)) { if (loadCANOpenFile(filename, frames)) { qDebug() << "Loaded as CANOpen Magic successfully!"; return true; } } qDebug() << "Attempting busmaster log"; if (isLogFile(filename)) { if (loadLogFile(filename, frames)) { qDebug() << "Loaded as Busmaster Log successfully!"; return true; } } qDebug() << "Attempting pcan"; if (isPCANFile(filename)) { if (loadPCANFile(filename, frames)) { qDebug() << "Loaded as PCAN successfully!"; return true; } } qDebug() << "Attempting ixxat"; if (isIXXATFile(filename)) { if (loadIXXATFile(filename, frames)) { qDebug() << "Loaded as IXXAT successfully!"; return true; } } qDebug() << "Attempting microchip"; if (isMicrochipFile(filename)) { if (loadMicrochipFile(filename, frames)) { qDebug() << "Loaded as microchip successfully!"; return true; } } qDebug() << "Attempting CANDo"; if (isCANDOFile(filename)) { if (loadCANDOFile(filename, frames)) { qDebug() << "Loaded as CANDO successfully!"; return true; } } qDebug() << "Attempting kvaser"; if (isKvaserFile(filename)) { if (loadKvaserFile(filename, frames,true)) { qDebug() << "Loaded as Kvaser HEX successfully!"; return true; } if (loadKvaserFile(filename, frames,false)) { qDebug() << "Loaded as KVaser Decimal successfully!"; return true; } } qDebug() << "Attempting CLX000"; if (isCLX000File(filename)) { if (loadCLX000File(filename, frames)) { qDebug() << "Loaded as CLX000 successfully!"; return true; } } qDebug() << "Attempting lawicel"; if (isLawicelFile(filename)) { if (loadLawicelFile(filename, frames)) { qDebug() << "Loaded as lawicel successfully!"; return true; } } qDebug() << "Attempting generic CSV"; if (isGenericCSVFile(filename)) { if (loadGenericCSVFile(filename, frames)) { qDebug() << "Loaded as generic CSV successfully!"; return true; } } QMessageBox msgBox; msgBox.setText("Could not autodetect the file type.\rPlease try to manually select the file format."); msgBox.exec(); qDebug() << "Nothing worked... sorry..."; return false; } bool FrameFileIO::isVehicleSpyFile(QString filename) { QFile *inFile = new QFile(filename); QByteArray line; bool foundProbableHeader = false; bool isMatch = false; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { delete inFile; return false; } try { for (int i = 0; i < 10; i++) { if (!inFile->atEnd()) { line = inFile->readLine().simplified().toUpper(); if (line.startsWith("LINE") && line.contains("TIME") && line.contains("B1")) { foundProbableHeader = true; } } } if (foundProbableHeader) { if (!inFile->atEnd()) { line = inFile->readLine().simplified().toUpper(); inFile->close(); QList tokens = line.split(','); if (tokens.length() > 20) { if (tokens[9].toInt(nullptr, 16) > 0) isMatch = true; } } } } catch (...) { isMatch = false; } inFile->close(); return isMatch; } //2,2550.368293675,0.003818174999651092,67371008,F,F,HS CAN $119,HS CAN,,119,F,F,00,00,00,00,00,00,0D,8B,,, //Line,Abs Time(Sec),Rel Time (Sec),Status,Er,Tx,Description,Network,Node,Arb ID,Remote,Xtd,B1,B2,B3,B4,B5,B6,B7,B8,Value,Trigger,Signals // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 bool FrameFileIO::loadVehicleSpyFile(QString filename, QVector *frames) { QFile *inFile = new QFile(filename); CANFrame thisFrame; QByteArray line; int lineCounter = 0; bool pastHeader = false; bool foundErrors = false; QDateTime now = QDateTime::currentDateTime(); QDateTime tempTime; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { delete inFile; return false; } while (!inFile->atEnd() && !pastHeader) { line = inFile->readLine().simplified().toUpper(); if (line.startsWith("LINE")) lineCounter++; if (lineCounter == 2) pastHeader = true; } if (inFile->atEnd()) foundErrors = true; while (!inFile->atEnd()) { lineCounter++; if (lineCounter > 100) { qApp->processEvents(); lineCounter = 0; } line = inFile->readLine().simplified().toUpper(); QList tokens = line.split(','); if (tokens.length() > 20) { thisFrame.bus = 0; thisFrame.setFrameType(QCanBusFrame::DataFrame); tempTime = now; tempTime = tempTime.addMSecs(static_cast(tokens[1].toDouble() * 1000.0)); thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, static_cast(tempTime.toMSecsSinceEpoch() * 1000))); if (tokens[5].startsWith("T")) thisFrame.isReceived = false; else thisFrame.isReceived = true; thisFrame.setFrameId(static_cast(tokens[9].toInt(nullptr, 16))); if (tokens[11].startsWith("T")) thisFrame.setExtendedFrameFormat(true); else thisFrame.setExtendedFrameFormat(false); QByteArray bytes; for (int i = 0; i < 8; i++) { if (tokens[12 + i].length() > 0) { bytes.append(static_cast(tokens[12 + i].toInt(nullptr, 16))); } else break; } thisFrame.setPayload(bytes); frames->append(thisFrame); } else foundErrors = true; } inFile->close(); delete inFile; return !foundErrors; } bool FrameFileIO::saveVehicleSpyFile(QString filename, const QVector *frames) { Q_UNUSED(filename); Q_UNUSED(frames); return true; } bool FrameFileIO::isCRTDFile(QString filename) { QFile *inFile = new QFile(filename); QByteArray line; int lineCounter = 0; bool isMatch = false; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { delete inFile; return false; } try { line = inFile->readLine().toUpper(); //read out the header first and discard it. while (!inFile->atEnd() && lineCounter < 100) { lineCounter++; line = inFile->readLine().simplified(); if (line.length() > 2) { QList tokens = line.split(' '); if (tokens.length() > 2) { char firstChar = tokens[1].left(1)[0]; if (firstChar >= '1' && firstChar <= '9') { tokens[1].remove(0,1); // Remove leading digit (bus number) firstChar = tokens[1].left(1)[0]; } if (firstChar == 'R' || firstChar == 'T') { if (tokens[1] == "R29" || tokens[1] == "T29") isMatch = true; if (tokens[1] == "R11" || tokens[1] == "T11") isMatch = true; } } else isMatch = false; } if (lineCounter > 10) break; } } catch (...) { isMatch = false; } inFile->close(); delete inFile; return isMatch; } //CRTD format from Mark Webb-Johnson / OVMS project // Specification at: https://docs.openvehicles.com/en/latest/crtd/ /* Sample data in CRTD format Timestamp appears to be seconds since the epoch 1320745424.000 CXX OVMS Tesla Roadster cando2crtd converted log 1320745424.000 CXX OVMS Tesla roadster log: charge.20111108.csv 1320745424.002 R11 402 FA 01 C3 A0 96 00 07 01 1320745424.015 R11 400 02 9E 01 80 AB 80 55 00 1320745424.066 R11 400 01 01 00 00 00 00 4C 1D 1320745424.105 R11 100 A4 53 46 5A 52 45 38 42 1320745424.106 R11 100 A5 31 35 42 33 30 30 30 1320745424.106 R11 100 A6 35 36 39 1320745424.106 CEV Open charge port door 1320745424.106 R11 100 9B 97 A6 31 03 15 05 06 1320745424.107 R11 100 07 64 123.431 2R11 27E 00 00 00 00 00 00 00 00 123.441 2R11 7C8 00 00 00 00 00 00 00 00 123.451 2R11 402 FD 04 04 0F 10 00 00 07 123.461 2R11 318 92 08 33 0D 07 00 00 00 tokens: 0 = timestamp 1 = line type 2 = ID 3-x = The data bytes */ bool FrameFileIO::loadCRTDFile(QString filename, QVector* frames) { QFile *inFile = new QFile(filename); CANFrame thisFrame; QByteArray line; int lineCounter = 0; bool foundErrors = false; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { delete inFile; return false; } line = inFile->readLine().toUpper(); //read out the header first and discard it. while (!inFile->atEnd()) { lineCounter++; if (lineCounter > 100) { qApp->processEvents(); lineCounter = 0; } line = inFile->readLine().simplified(); if (line.length() > 2) { QList tokens = line.split(' '); int multiplier; if (tokens.length() > 2) { int idxOfDecimal = tokens[0].indexOf('.'); if (idxOfDecimal > -1) { //int decimalPlaces = tokens[0].length() - tokens[0].indexOf('.') - 1; //the result of the above is the # of digits after the decimal. //This program deals in microsecond so turn the value into microseconds multiplier = 1000000; //turn the decimal into full microseconds } else { multiplier = 1; //special case. Assume no decimal means microseconds } //qDebug() << "decimal places " << decimalPlaces; thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, static_cast((tokens[0].toDouble() * multiplier)))); thisFrame.bus = 0; char firstChar = tokens[1].left(1)[0]; if (firstChar >= '1' && firstChar <= '9') { thisFrame.bus = tokens[1].left(1)[0] - '1'; tokens[1].remove(0,1); // Remove leading digit (bus number) firstChar = tokens[1].left(1)[0]; } if (firstChar == 'R' || firstChar == 'T') { thisFrame.setFrameId(static_cast(tokens[2].toInt(nullptr, 16))); if (tokens[1] == "R29" || tokens[1] == "T29") thisFrame.setExtendedFrameFormat(true); else thisFrame.setExtendedFrameFormat(false); if (firstChar == 'T') thisFrame.isReceived = false; else thisFrame.isReceived = true; QByteArray bytes(tokens.length() - 3, 0); thisFrame.setFrameType(QCanBusFrame::DataFrame); for (int d = 0; d < bytes.length(); d++) { if (tokens[d + 3] != "") { bytes[d] = static_cast(tokens[d + 3].toInt(nullptr, 16)); } else bytes[d] = 0; } thisFrame.setPayload(bytes); frames->append(thisFrame); } } else foundErrors = true; } } inFile->close(); delete inFile; return !foundErrors; } bool FrameFileIO::isCARBUSAnalyzerFile(QString filename) { QFile *inFile = new QFile(filename); QByteArray line; bool isMatch = false; // not Text mode because file contains `\r` new lines if (!inFile->open(QIODevice::ReadOnly)) { delete inFile; return false; } try { //read header line = inFile->readLine().toUpper(); if (line.startsWith("@ TEXT @")) return true; } catch (...) { isMatch = false; } inFile->close(); delete inFile; return isMatch; } // CARBUS Analayzer trace format https://canhacker.ru/can-trace-format/ : // header //@ TEXT @ 2 @ 64 @ 0 @ 591 @ 38782 @ 00:00:38.782 @ // 2 - format version //14,687 1 0004 4E0 8 24 00 00 00 00 00 00 00 00000000 $ // timestamp: sec,ms - for version 2 // sec,us - for version 3 bool FrameFileIO::loadCARBUSAnalyzerFile(QString filename, QVector* frames) { QFile *inFile = new QFile(filename); if (!inFile->open(QIODevice::ReadOnly)) { delete inFile; return false; } CANFrame thisFrame; QString line; int lineCounter = 0; bool foundErrors = false; // readLine() works only with "\n" and "\r\n" QString localReadAll = inFile->readAll().replace("\r", "\r\n"); QTextStream txt(&localReadAll); line = txt.readLine().toUpper(); //read out the header int version = 3; // current default QRegularExpression re("@ TEXT @ (?\\d) @.*"); QRegularExpressionMatch match = re.match(line); if (match.hasMatch()) { version = match.captured("version").toInt(); } while (!txt.atEnd()) { lineCounter++; if (lineCounter > 100) { qApp->processEvents(); lineCounter = 0; } line = txt.readLine().simplified(); if (line.length() > 2) { QList tokens = line.split(QRegularExpression("\\s+")); if (tokens.length() > 3) { QString time = tokens[0].replace(",", ""); int64_t timeStamp = time.toInt(); if (version == 2) { timeStamp *= 1000; // ms -> us } thisFrame.isReceived = true; thisFrame.setFrameType(QCanBusFrame::DataFrame); thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, static_cast(timeStamp))); thisFrame.bus = tokens[1].toInt(); thisFrame.setFrameId(static_cast(tokens[3].toInt(nullptr, 16))); thisFrame.setExtendedFrameFormat(thisFrame.frameId() > 0x7FF); int numBytes = tokens[4].toInt(nullptr, 16); QByteArray bytes(numBytes , 0); for (int d = 0; d < numBytes; d++) { if (tokens[d + 5] != "") { bytes[d] = static_cast(tokens[d + 5].toInt(nullptr, 16)); } else bytes[d] = 0; } thisFrame.setPayload(bytes); frames->append(thisFrame); } else { foundErrors = true; } } } inFile->close(); delete inFile; return !foundErrors; } bool FrameFileIO::saveCARBUSAnalzyer(QString filename, const QVector* frames) { QFile *outFile = new QFile(filename); if (!outFile->open(QIODevice::WriteOnly | QIODevice::Text)) { delete outFile; return false; } QTextStream outTextStream(outFile); int lineCounter = 0; qint64 minTime = frames->at(0).timeStamp().microSeconds(); qint64 maxTime = minTime; for (int c = 0; c < frames->count(); c++) { if (frames->at(c).timeStamp().microSeconds() < minTime) minTime = frames->at(c).timeStamp().microSeconds(); if (frames->at(c).timeStamp().microSeconds() > maxTime) maxTime = frames->at(c).timeStamp().microSeconds(); } qint64 totalTime = maxTime - minTime; // looks like a bug in CARBUS format for 3 version. time is in ms, while packets in us. totalTime /= 1000; QTime someTime(0,0,0); someTime = someTime.addMSecs(totalTime); // header: // @see #loadCARBUSAnalyzer() // 14012 = number of packets // 15688 = max time - start time in ms // // "@ TEXT @ 3 @ 64 @ 0 @ 14012 @ 15688 @ 00:00:15.688 @" // saving in version format 3 with microseconds in packets time outTextStream << "@ TEXT @ 3 @ 64 @ 0 @ " << frames->count() << " @ " << totalTime <<" @ " << someTime.toString("hh:mm:ss.zzz") <<" @\r"; const unsigned char *data; for (int c = 0; c < frames->count(); c++) { lineCounter++; if (lineCounter > 100) { qApp->processEvents(); lineCounter = 0; } auto frame = frames->at(c); uint64_t timeStamp = frame.timeStamp().microSeconds(); QString canId = QString::number(frame.frameId(), 16).toUpper().rightJustified(3, '0').toUtf8(); QString canDlc = QString::number(frame.payload().length()).toUtf8(); QString ascii = ""; QString finalCanData; data = reinterpret_cast(frame.payload().constData()); for (int d = 0; d < frame.payload().length(); d++) { auto octet = data[d]; finalCanData.append(QString::number(octet, 16).toUpper().rightJustified(2, '0').toUtf8()); finalCanData.append(" "); QString strCode = QString::number(octet, 16); bool conv; uint symbolCode = strCode.toInt(&conv, 16); if ((symbolCode >= 32 && symbolCode < 126)) { QChar localQChar = QChar(symbolCode); if (localQChar.toLatin1() != 0) { ascii += QString::fromLocal8Bit(QByteArray::fromHex(strCode.toLatin1())); } } else { ascii += " "; } } finalCanData = finalCanData.trimmed().leftJustified(23, ' '); ascii = ascii.leftJustified(8, ' '); auto seconds = QString::number(timeStamp / 1000000); auto uSeconds = QString::number(timeStamp % 1000000); QString localArg = ""; localArg.append(seconds).append(",").append(uSeconds).append("\t") .append(QString::number(frame.bus)).append("\t") // bus channel .append("0004").append("\t") // it's CAN frame .append(canId).append("\t") .append(canDlc).append("\t") .append(finalCanData).append("\t") .append("00000000").append("\t") .append(ascii).append("\t") .append("\r"); qDebug() << localArg; outTextStream << localArg; } outFile->close(); delete outFile; return true; } bool FrameFileIO::isCANHackerFile(QString filename) { QFile *inFile = new QFile(filename); QByteArray line; int lineCounter = 0; bool isMatch = false; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { delete inFile; return false; } try { line = inFile->readLine().toUpper(); //read out the header first and discard it. if (line.contains("CANHACKER")) return true; while (!inFile->atEnd()) { lineCounter++; line = inFile->readLine().simplified(); if (line.length() > 2) { QList tokens = line.split(' '); if (tokens.length() > 3) { if (tokens[1].toInt(nullptr, 16) > 0) { int len = tokens[2].toInt(); if (len > -1 && len < 9) { isMatch = true; } } } else isMatch = false; } if (lineCounter > 10) break; } } catch (...) { isMatch = false; } inFile->close(); delete inFile; return isMatch; } // CANHacker trace format // Time ID DLC Data Comment // 00[.|,]000 00004000 8 36 47 19 43 01 00 00 80 bool FrameFileIO::loadCANHackerFile(QString filename, QVector* frames) { QFile *inFile = new QFile(filename); CANFrame thisFrame; QByteArray line; int lineCounter = 0; bool foundErrors = false; int addendumTime = 0; double previousTime = 0; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { delete inFile; return false; } line = inFile->readLine().toUpper(); //read out the header first and discard it. while (!inFile->atEnd()) { lineCounter++; if (lineCounter > 100) { qApp->processEvents(); lineCounter = 0; } line = inFile->readLine().simplified(); if (line.length() > 2) { QList tokens = line.split(' '); int multiplier; if (tokens.length() > 3) { int idxOfDecimal = tokens[0].indexOf('.'); // If no dot is found, try comma if(idxOfDecimal == -1) { idxOfDecimal = tokens[0].indexOf(','); } if (idxOfDecimal > -1) { //int decimalPlaces = tokens[0].length() - tokens[0].indexOf('.') - 1; //the result of the above is the # of digits after the decimal. //This program deals in microsecond so turn the value into microseconds multiplier = 1000000; //turn the decimal into full microseconds } else { multiplier = 1; //special case. Assume no decimal means microseconds } //qDebug() << "decimal places " << decimalPlaces; if (previousTime > tokens[0].toDouble()) { addendumTime += 60; } thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, static_cast((tokens[0].toDouble() + addendumTime) * multiplier))); previousTime = tokens[0].toDouble(); thisFrame.setFrameId( static_cast(tokens[1].toInt(nullptr, 16)) ); thisFrame.setExtendedFrameFormat((thisFrame.frameId() > 0x7FF)); thisFrame.isReceived = true; thisFrame.setFrameType(QCanBusFrame::DataFrame); thisFrame.bus = 0; int numBytes = tokens[2].toInt(nullptr, 16); QByteArray bytes( numBytes, 0); for (int d = 0; d < numBytes; d++) { if (tokens[d + 3] != "") { bytes[d] = static_cast(tokens[d + 3].toInt(nullptr, 16)); } else bytes[d] = 0; } thisFrame.setPayload(bytes); frames->append(thisFrame); } else foundErrors = true; } } inFile->close(); delete inFile; return !foundErrors; } bool FrameFileIO::isCANOpenFile(QString filename) { QFile *inFile = new QFile(filename); QByteArray line; int lineCounter = 0; bool isMatch = false; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { delete inFile; return false; } try { line = inFile->readLine().toUpper(); if (!line.contains("CANOPEN MAGIC")) return false; line = inFile->readLine(); line = inFile->readLine(); line = inFile->readLine(); line = inFile->readLine(); while (!inFile->atEnd()) { lineCounter++; if (lineCounter > 10) { break; } line = inFile->readLine().replace('\"', ' ').simplified(); if (line.length() > 2) { QList tokens = line.split(','); if (tokens.length() > 11) { if (Utility::ParseStringToNum(tokens[5].simplified()) > 0) { QList dataTok = tokens[11].simplified().split(' '); if ( dataTok.length() > -1 && dataTok.length() < 9) isMatch = true; } } else isMatch = false; } } } catch (...) { isMatch = false; } inFile->close(); delete inFile; return isMatch; } //"Message Number","Time (ms)","Time","Excel Time","Count","ID","Flags","Message Type","Node","Details","Process Data","Data (Hex)","Data (Text)","Data (Decimal)","Length","Raw Message" //"0","0.000","8:09:42:48.7953090'",43447.7100146116,"","0x2E1","","Default: PDO","","Default: TPDO 2 of Node 0x61 (97)","","10 21 04 00 00 00 00 00 ",". ! . . . . . . ","U:0 S:0","8","10 21 04 00 00 00 00 00" bool FrameFileIO::loadCANOpenFile(QString filename, QVector* frames) { QFile *inFile = new QFile(filename); CANFrame thisFrame; QByteArray line; int lineCounter = 0; bool foundErrors = false; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { delete inFile; qDebug() << "Could not open the file!"; return false; } line = inFile->readLine(); //read out the header first and discard it. line = inFile->readLine(); line = inFile->readLine(); line = inFile->readLine(); line = inFile->readLine(); while (!inFile->atEnd()) { lineCounter++; if (lineCounter > 100) { qApp->processEvents(); lineCounter = 0; } line = inFile->readLine().replace('\"', ' ').simplified(); if (line.length() > 2) { QList tokens = line.split(','); if (tokens.length() > 11) { thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, static_cast(tokens[1].simplified().toDouble() * 1000.0))); thisFrame.setFrameId(static_cast(Utility::ParseStringToNum(tokens[5].simplified()))); thisFrame.setExtendedFrameFormat( (thisFrame.frameId() > 0x7FF) ); thisFrame.isReceived = true; thisFrame.setFrameType(QCanBusFrame::DataFrame); thisFrame.bus = 0; QList dataTok = tokens[11].simplified().split(' '); QByteArray bytes(dataTok.length(), 0); for (int d = 0; d < dataTok.length(); d++) { if (dataTok[d] != "") { bytes[d] = static_cast(dataTok[d].simplified().toInt(nullptr, 16)); } else bytes[d] = 0; } thisFrame.setPayload(bytes); frames->append(thisFrame); } else foundErrors = true; } } inFile->close(); delete inFile; return !foundErrors; } bool FrameFileIO::saveCRTDFile(QString filename, const QVector* frames) { QFile *outFile = new QFile(filename); int lineCounter = 0; const unsigned char *data; int dataLen; const CANFrame *frame; if (!outFile->open(QIODevice::WriteOnly | QIODevice::Text)) { delete outFile; return false; } //write in float format with 6 digits after the decimal point outFile->write(QString::number(frames->at(0).timeStamp().microSeconds() / 1000000.0, 'f', 6).toUtf8() + tr(" CXX GVRET-PC Reverse Engineering Tool Output V").toUtf8() + QString::number(VERSION).toUtf8()); outFile->write("\n"); for (int c = 0; c < frames->count(); c++) { lineCounter++; if (lineCounter > 100) { qApp->processEvents(); lineCounter = 0; } frame = &frames->at(c); data = reinterpret_cast(frame->payload().constData()); dataLen = frame->payload().count(); outFile->write(QString::number(frame->timeStamp().microSeconds() / 1000000.0, 'f', 6).toUtf8()); outFile->putChar(' '); outFile->write(QString::number(frame->bus + 1).toUtf8()); if (frame->isReceived) outFile->putChar('R'); else outFile->putChar('T'); if (frame->hasExtendedFrameFormat()) { outFile->write("29 "); } else outFile->write("11 "); outFile->write(QString::number(frame->frameId(), 16).toUpper().rightJustified(8, '0').toUtf8()); outFile->putChar(' '); for (int temp = 0; temp < dataLen; temp++) { outFile->write(QString::number(data[temp], 16).toUpper().rightJustified(2, '0').toUtf8()); outFile->putChar(' '); } outFile->write("\n"); } outFile->close(); delete outFile; return true; } bool FrameFileIO::isPCANFile(QString filename) { QFile *inFile = new QFile(filename); QByteArray line; int lineCounter = 0; bool hasFileVer = false; bool isMatch = false; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { delete inFile; return false; } try { while (!inFile->atEnd()) { lineCounter++; if (lineCounter > 25) { break; } line = inFile->readLine(); if (line.startsWith(";$FILEVERSION=")) hasFileVer = true; if (line.toUpper().contains("PCAN") && hasFileVer) isMatch = true; } } catch (...) { isMatch = false; } inFile->close(); delete inFile; return isMatch; } /*Fixed length lines Version 1.1 //; Message Number //; | Time Offset (ms) //; | | Type //; | | | ID (hex) //; | | | | Data Length Code //; | | | | | Data Bytes (hex) ... //; | | | | | | //;---+-- ----+---- --+-- ----+--- + -+ -- -- -- -- -- -- -- // 0-6 10-18 21-25 28-35 38 41-? two chars each + space // Version 1.3 //; Message Time Bus Type ID Reserved //; Number Offset | | [hex] | Data Length Code //; | [ms] | | | | | Data [hex] ... //; | | | | | | | | //;---+-- ------+------ +- --+-- ---+---- +- -+-- -+ -- -- -- -- -- -- -- // 0-6 8-20 22 25-26 38 41-? two chars each + space // 1) 1004.898 1 Tx 079B - 8 02 21 04 00 00 00 00 00 Version 2.0 ; Message Time Type ID Rx/Tx ; Number Offset | [hex] | Data Length ; | [ms] | | | | Data [hex] ... ; | | | | | | | ;---+-- ------+------ +- --+----- +- +- +- +- -- -- -- -- -- -- -- 0-6 8-20 22-23 25-32 34-35 37-38 40-? two chars each + space Version 2.1 ; Message Time Type ID Rx/Tx ; Number Offset | Bus [hex] | Reserved ; | [ms] | | | | | Data Length Code ; | | | | | | | | Data [hex] ... ; | | | | | | | | | ;---+--- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- -- 0 1 2 3 4 5 6 7 8 + It seems as if Version 2 files might be able to store other protocols like ISO-TP or J1939 */ bool FrameFileIO::loadPCANFile(QString filename, QVector* frames) { QFile *inFile = new QFile(filename); CANFrame thisFrame; QByteArray line; int lineCounter = 0; int fileVersion = 11; bool foundErrors = false; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { delete inFile; return false; } while (!inFile->atEnd()) { lineCounter++; if (lineCounter > 100) { qApp->processEvents(); lineCounter = 0; } line = inFile->readLine(); if (line.startsWith(';')) { if (line.contains("$FILEVERSION=2.1")) fileVersion = 21; if (line.contains("$FILEVERSION=2.0")) fileVersion = 20; if (line.contains("$FILEVERSION=1.3")) fileVersion = 13; if (line.contains("$FILEVERSION=1.1")) fileVersion = 11; continue; } if (line.length() > 41) { line = line.simplified(); QList tokens = line.split(' '); if (fileVersion == 11) { if (tokens.length() > 4) { thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, (uint64_t)(tokens[1].toDouble() * 1000.0))); thisFrame.setFrameId(tokens[3].toUInt(nullptr, 16)); if (thisFrame.frameId() < 0x1FFFFFFF) { int numBytes = tokens[4].toInt(); QByteArray bytes(numBytes, 0); thisFrame.isReceived = true; thisFrame.bus = 0; if (thisFrame.frameId() > 0x10000000) { thisFrame.setExtendedFrameFormat(true); } else { thisFrame.setExtendedFrameFormat(false); } if (tokens[5] == "R") { thisFrame.setFrameType(QCanBusFrame::RemoteRequestFrame); } else { thisFrame.setFrameType(QCanBusFrame::DataFrame); for (int d = 0; d < numBytes; d++) { if (tokens[d + 5] != "") { bytes[d] = static_cast(tokens[d + 5].toInt(nullptr, 16)); } else bytes[d] = 0; } } thisFrame.setPayload(bytes); frames->append(thisFrame); } } } // Version 1.3 //; Message Time Bus Type ID Reserved //; Number Offset | | [hex] | Data Length Code //; | [ms] | | | | | Data [hex] ... //; | | | | | | | | //;---+-- ------+------ +- --+-- ---+---- +- -+-- -+ -- -- -- -- -- -- -- // 0-6 8-20 22 25-26 38 41-? two chars each + space // 1) 1004.898 1 Tx 1fff079B - 8 02 21 04 00 00 00 00 00 // 0 1 2 3 4 5 6 7-? else if (fileVersion == 13) { if (tokens.length() > 6) { thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, static_cast(tokens[1].toDouble() * 1000.0))); thisFrame.setFrameId(tokens[4].toUInt(nullptr, 16)); if (thisFrame.frameId() < 0x1FFFFFFF) { int numBytes = tokens[6].toInt(); QByteArray bytes(numBytes, 0); //qDebug() << thisFrame.payload().length(); thisFrame.isReceived = true; thisFrame.bus = tokens[2].toInt(); if (thisFrame.frameId() > 0x10000000) { thisFrame.setExtendedFrameFormat(true); } else { thisFrame.setExtendedFrameFormat(false); } if (tokens[7] == "R") { thisFrame.setFrameType(QCanBusFrame::RemoteRequestFrame); } else { thisFrame.setFrameType(QCanBusFrame::DataFrame); for (int d = 0; d < numBytes; d++) { if (tokens[d + 7] != "") { bytes[d] = static_cast(tokens[d + 7].toInt(nullptr, 16)); } else bytes[d] = 0; } } thisFrame.setPayload(bytes); frames->append(thisFrame); } } } else if (fileVersion == 20) { if (tokens.length() > 5) { thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, static_cast(tokens[1].toDouble() * 1000.0))); thisFrame.setFrameId(tokens[3].toUInt(nullptr, 16)); if (thisFrame.frameId() < 0x1FFFFFFF) { int numBytes = tokens[5].toInt(); QByteArray bytes(numBytes, 0); //qDebug() << thisFrame.payload().length(); thisFrame.isReceived = true; thisFrame.bus = 0; if (thisFrame.frameId() > 0x10000000) { thisFrame.setExtendedFrameFormat(true); } else { thisFrame.setExtendedFrameFormat(false); } if (tokens[6] == "R") { thisFrame.setFrameType(QCanBusFrame::RemoteRequestFrame); } else { thisFrame.setFrameType(QCanBusFrame::DataFrame); for (int d = 0; d < numBytes; d++) { if (tokens[d + 6] != "") { bytes[d] = static_cast(tokens[d + 6].toInt(nullptr, 16)); } else bytes[d] = 0; } } thisFrame.setPayload(bytes); frames->append(thisFrame); } } } /* Version 2.1 ; Message Time Type ID Rx/Tx ; Number Offset | Bus [hex] | Reserved ; | [ms] | | | | | Data Length Code ; | | | | | | | | Data [hex] ... ; | | | | | | | | | ;---+--- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- -- 0 1 2 3 4 5 6 7 8 + */ else if (fileVersion == 21) { if (tokens.length() > 7) { thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, static_cast(tokens[1].toDouble() * 1000.0))); thisFrame.setFrameId(tokens[4].toUInt(nullptr, 16)); if (thisFrame.frameId() < 0x1FFFFFFF) { int numBytes = tokens[7].toInt(); QByteArray bytes(numBytes, 0); //qDebug() << thisFrame.payload().length(); thisFrame.isReceived = true; thisFrame.bus = tokens[3].toInt(); if (thisFrame.frameId() > 0x10000000) { thisFrame.setExtendedFrameFormat(true); } else { thisFrame.setExtendedFrameFormat(false); } if (tokens[2] == "R") { thisFrame.setFrameType(QCanBusFrame::RemoteRequestFrame); } else { thisFrame.setFrameType(QCanBusFrame::DataFrame); for (int d = 0; d < numBytes; d++) { if (tokens[d + 8] != "") { bytes[d] = static_cast(tokens[d + 8].toInt(nullptr, 16)); } else bytes[d] = 0; } } thisFrame.setPayload(bytes); frames->append(thisFrame); } } } } //else foundErrors = true; } inFile->close(); delete inFile; return !foundErrors; } //supporting two styles now and they have very different line layouts. Just checking for the header for now. That should still match only ASC files. bool FrameFileIO::isCanalyzerASC(QString filename) { QFile *inFile = new QFile(filename); QByteArray line; //int lineCounter = 0; //bool inHeader = true; bool isMatch = true; QList tokens; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { delete inFile; return false; } try { if (!inFile->atEnd()) { line = inFile->readLine(); if (!line.startsWith("date")) isMatch = false; } if (!inFile->atEnd() && isMatch) { line = inFile->readLine(); if (!line.startsWith("base")) isMatch = false; } if (!inFile->atEnd() && isMatch) { line = inFile->readLine(); if (!line.contains("logged")) isMatch = false; } if (!inFile->atEnd() && isMatch) { line = inFile->readLine(); if (!line.contains("version")) isMatch = false; } } catch (...) { isMatch = false; } inFile->close(); delete inFile; return isMatch; } //There tends to be four lines of header first. The last of which starts with // so first burn off lines //until a line starting with // is seen, then the rest are formatted like this: //Version 8.0: //47.971842 2 248 Rx d 8 FF FF FF FF FF FF FF FF //version 8.1 //47244640.194244 CANFD 1 Rx 122 0 0 6 6 00 00 18 12 D2 00 0 0 200000 0 0 0 0 0 //Version 9.0 //0.001371 CANFD 1 Rx 171 A0066_Unknown 0 0 8 8 00 00 07 76 23 00 00 00 230199 119 220000 62e7 46500250 460a0250 20011736 20010205 //Version 16.0.0 //0.008600 1 358 Rx d 8 04 03 50 01 F0 40 54 2C Length = 233910 BitCount = 121 ID = 856 //Version 17.3.0 //0.001684 CANFD 1 Rx 65b 1 0 d 32 f1 01 00 00 50 00 00 00 00 00 00 00 00 00 00 40 00 88 f5 77 17 78 00 00 c0 03 00 00 00 00 00 00 213797 356 303000 11729 46500250 4b140250 20011736 2001040d //Time bus id dir ? len databytes (Ver 8.0) //Time bus id dir ? len databytes (addl info) (ver 16.0) //Time type bus dir ID ? ? length length (bytes) (many values of unknown type) (ver 8.1) //Time type bus dir ID SignalName ? ? length length bytes (then many values of unknown type)(ver 9.0) //Time Type Bus Dir ID ? ? (length) (Real Length) (bytes) (many values of unknown type) (Ver 17.3) //0 1 2 3 4 5 6 7 8 9 10 //This seems like a rather eclectic mix. It's almost arbitrary! bool FrameFileIO::loadCanalyzerASC(QString filename, QVector* frames) { QFile *inFile = new QFile(filename); CANFrame thisFrame; QByteArray line; int lineCounter = 0; bool foundErrors = false; bool inHeader = true; int verMajor; int verMinor; int verRevision; thisFrame.setFrameType(QCanBusFrame::DataFrame); QList tokens; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { delete inFile; return false; } while (!inFile->atEnd()) { lineCounter++; if (lineCounter > 100) { qApp->processEvents(); lineCounter = 0; } line = inFile->readLine(); if (inHeader) { if (line.startsWith("//")) { line = line.right(line.length() - 11); QList versionTokens = line.split('.'); if (versionTokens.length() > 2) { verMajor = versionTokens[0].toInt(); verMinor = versionTokens[1].toInt(); verRevision = versionTokens[2].toInt(); qDebug() << "Major: " << verMajor << " Minor:" << verMinor << " Rev:" << verRevision; } } if (line.startsWith("//") || lineCounter > 4) { inHeader = false; continue; } } if (inHeader) continue; if (line.length() > 2 && !line.startsWith("//")) { tokens = line.simplified().split(' '); if (tokens[0].contains("Begin")) continue; //probably begin triggerblock but we're ignoring that. //try to do some investigating to see if this line is a CAN frame or not. The file format has many other potential line types it seems... if (tokens.length() > 5) { if (tokens[3].toUpper().startsWith("RX") || tokens[3].toUpper().startsWith("TX")) { thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, static_cast(tokens[0].toDouble() * 1000000.0))); if (tokens[1].contains("CAN")) //the different format I haven't seen a whole lot of, seems to support CANFD in this format { if (tokens[4].endsWith('x')) { QByteArray copied_id = tokens[4]; copied_id.chop(1); thisFrame.setFrameId(copied_id.toUInt(nullptr, 16)); thisFrame.setExtendedFrameFormat(true); } else { thisFrame.setFrameId(tokens[4].toUInt(nullptr, 16)); thisFrame.setExtendedFrameFormat(thisFrame.frameId() > 0x7FF); //some .asc files have extended IDs without 'x' } int payloadLen = tokens[8].toInt(); qDebug() << "PayloadLen: " << payloadLen << " Tokens: " << tokens; QByteArray bytes(payloadLen, 0); if (payloadLen > 64) { qDebug() << "Payload length too long. Original line: " << line; return false; } if (payloadLen < 0) { qDebug() << "Payload length negative! Original line: " << line; return false; } thisFrame.isReceived = tokens[3].toUpper().contains("RX"); thisFrame.bus = tokens[2].toInt(); if (tokens[5].at(0) >= '0' && tokens[5].at(0) <= '9') { for (int d = 9; d < (9 + payloadLen); d++) { if (tokens.count() > d) { bytes[d - 9] = static_cast(tokens[d].toInt(nullptr, 16)); } else //expected byte wasn't there to read. Set it zero and set error flag { bytes[d - 9] = 0; foundErrors = true; qDebug() << "D:" << d << " Count:" << tokens.count(); qDebug() << "Expected byte missing! Original line: " << line; } } thisFrame.setPayload(bytes); } else { for (int d = 10; d < (10 + payloadLen); d++) { if (tokens.count() > d) { bytes[d - 10] = static_cast(tokens[d].toInt(nullptr, 16)); } else //expected byte wasn't there to read. Set it zero and set error flag { bytes[d - 10] = 0; foundErrors = true; qDebug() << "D:" << d << " Count:" << tokens.count(); qDebug() << "Expected byte missing! Original line: " << line; } } thisFrame.setPayload(bytes); } } else { int payloadLen = tokens[5].toInt(); thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, static_cast(tokens[0].toDouble() * 1000000.0))); if (tokens[2].endsWith('x')) { QByteArray copied_id = tokens[2]; copied_id.chop(1); thisFrame.setFrameId(copied_id.toUInt(nullptr, 16)); thisFrame.setExtendedFrameFormat(true); } else { thisFrame.setFrameId(tokens[2].toUInt(nullptr, 16)); thisFrame.setExtendedFrameFormat(thisFrame.frameId() > 0x7FF); //some .asc files have extended IDs without 'x' } QByteArray bytes(payloadLen, 0); if (payloadLen > 8) { qDebug() << "Payload length too long. Original line: " << line; return false; } if (payloadLen < 0) { qDebug() << "Payload length negative! Original line: " << line; return false; } thisFrame.isReceived = tokens[3].toUpper().contains("RX"); thisFrame.bus = tokens[1].toInt(); if (tokens[4] == "r") thisFrame.setFrameType(QCanBusFrame::RemoteRequestFrame); for (int d = 6; d < (6 + payloadLen); d++) { if (tokens.count() > d) { bytes[d - 6] = static_cast(tokens[d].toInt(nullptr, 16)); } else //expected byte wasn't there to read. Set it zero and set error flag { bytes[d - 6] = 0; foundErrors = true; qDebug() << "D:" << d << " Count:" << tokens.count(); qDebug() << "Expected byte missing! Original line: " << line; } } thisFrame.setPayload(bytes); } frames->append(thisFrame); } } } //else foundErrors = true; } inFile->close(); delete inFile; return !foundErrors; } bool FrameFileIO::saveCanalyzerASC(QString filename, const QVector* frames) { QFile *outFile = new QFile(filename); int lineCounter = 0; int64_t offsetTime = frames->at(0).timeStamp().microSeconds(); const unsigned char *data; int dataLen; const CANFrame *frame; for (int c = 0; c < frames->count(); c++) { if (frames->at(c).timeStamp().microSeconds() < offsetTime) offsetTime = frames->at(c).timeStamp().microSeconds(); } if (!outFile->open(QIODevice::WriteOnly | QIODevice::Text)) { delete outFile; return false; } QDateTime now; now = QDateTime::currentDateTime(); if (offsetTime > 10000000000) //chances are the input file had times as system time so load it { now.setMSecsSinceEpoch(offsetTime / 1000); //offsetTime was in microseconds } outFile->write("date " + now.toString("ddd MMM dd h:mm:ss.zzz a yyyy").toUtf8()); outFile->write("\nbase hex timestamps absolute\n"); outFile->write("no internal event logging\n"); outFile->write("// version 11.0.0\n"); for (int c = 0; c < frames->count(); c++) { lineCounter++; if (lineCounter > 100) { qApp->processEvents(); lineCounter = 0; } frame = &frames->at(c); data = reinterpret_cast(frame->payload().constData()); dataLen = frame->payload().count(); uint64_t timeStamp = (frame->timeStamp().microSeconds() - offsetTime) / 1000000ull; int tsLen = QString::number(timeStamp).length(); int precision = 6; //vector seems to keep 10 bytes at the start of the line for the timestamp. It should never exceed this //and there should never be a precision over 6 digits after the decimal if (tsLen > 3) precision = 9 - tsLen; outFile->write(QString::number((frame->timeStamp().microSeconds() - offsetTime) / 1000000.0, 'f', precision).rightJustified(10, ' ').toUtf8()); outFile->putChar(' '); outFile->write(QString::number(frame->bus + 1).toUtf8()); outFile->write(" "); if (frames->at(c).hasExtendedFrameFormat()) { outFile->write(QString::number(frame->frameId(), 16).toUpper().rightJustified(8, '0').toUtf8()); outFile->write("x"); } else { outFile->write(QString::number(frame->frameId(), 16).toUpper().rightJustified(3, '0').toUtf8()); outFile->write(" "); } outFile->write(" "); if (frames->at(c).isReceived) outFile->write("Rx "); else outFile->write("Tx "); if (frames->at(c).frameType() == QCanBusFrame::RemoteRequestFrame) outFile->write("r "); else outFile->write("d "); outFile->write(QString::number(dataLen).toUtf8()); outFile->write(" "); for (int temp = 0; temp < dataLen; temp++) { outFile->write(QString::number(data[temp], 16).toUpper().rightJustified(2, '0').toUtf8()); outFile->write(" "); } outFile->write("\n"); } outFile->close(); delete outFile; return true; } bool FrameFileIO::isCanalyzerBLF(QString filename) { BLF_FILE_HEADER header; QFile *inFile = new QFile(filename); bool isMatch = false; if (!inFile->open(QIODevice::ReadOnly)) { delete inFile; return false; } inFile->read(reinterpret_cast(&header), sizeof(header)); if (qFromLittleEndian(header.sig) == 0x47474F4C) { qDebug() << "Proper BLF file header token"; isMatch = true; } else isMatch = false; inFile->close(); delete inFile; return isMatch; } //this one is pretty complicated and handled by it's own class bool FrameFileIO::loadCanalyzerBLF(QString filename, QVector *frames) { BLFHandler blf; return blf.loadBLF(filename, frames); } bool FrameFileIO::isNativeCSVFile(QString filename) { QFile *inFile = new QFile(filename); QByteArray line; int fileVersion = 1; bool isMatch = true; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { delete inFile; return false; } try { line = inFile->readLine().toUpper(); //read out the header first and discard it. if (line.length() < 23) isMatch = false; else if (line.at(23) == 'D') fileVersion = 2; //Dir is found starting at position 23 if this is a V2 file if (!line.contains("TIME STAMP")) isMatch = false; if (!line.contains("EXTENDED")) isMatch = false; if (!line.contains("D1")) isMatch = false; if (!inFile->atEnd()) { line = inFile->readLine().simplified(); if (line.length() > 2) { QList tokens = line.split(','); if (tokens.length() >= 5) { if (fileVersion == 1) { if (tokens[4].toUInt() > 8) isMatch = false; } else if (fileVersion == 2) { if ( tokens[5].toUInt() > 8) isMatch = false; } } else isMatch = false; } } } catch (...) { isMatch = false; } inFile->close(); delete inFile; return isMatch; } //The "native" file format for this program //Time Stamp,ID,Extended,Dir,Bus,LEN,D1,D2,D3,D4,D5,D6,D7,D8 //39747828,000005EB,false,Rx,0,8,E8,45,85,4B,4A,28,36,69, bool FrameFileIO::loadNativeCSVFile(QString filename, QVector* frames) { QFile *inFile = new QFile(filename); CANFrame thisFrame; QByteArray line; int fileVersion = 1; uint64_t timeStamp = Utility::GetTimeMS(); int lineCounter = 0; bool foundErrors = false; thisFrame.setFrameType(QCanBusFrame::DataFrame); if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { delete inFile; return false; } line = inFile->readLine().toUpper(); //read out the header first and discard it. if (line.at(23) == 'D') fileVersion = 2; //Dir is found starting at position 23 if this is a V2 file while (!inFile->atEnd()) { lineCounter++; if (lineCounter > 100) { qApp->processEvents(); lineCounter = 0; } line = inFile->readLine().simplified(); if (line.length() > 2) { QList tokens = line.split(','); if (tokens.length() >= 5) { if (tokens[0].length() > 3) { thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, tokens[0].toULongLong())); } else { timeStamp += 5; thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, timeStamp)); } thisFrame.setFrameId(tokens[1].toUInt(nullptr, 16)); if (tokens[2].toUpper().contains("TRUE")) thisFrame.setExtendedFrameFormat(true); else thisFrame.setExtendedFrameFormat(false); //fix for faulty files that fail to set the extended flag when they should if (thisFrame.frameId() > 0x7FF) thisFrame.setExtendedFrameFormat(true); thisFrame.setFrameType(QCanBusFrame::DataFrame); if (fileVersion == 1) { thisFrame.isReceived = true; thisFrame.bus = tokens[3].toInt(); int lng = tokens[4].toInt(); if (lng > 8) lng = 8; if (lng < 0) lng = 0; if (lng + 5 > tokens.length()) lng = tokens.length() - 5; QByteArray bytes(lng, 0); for (int c = 0; c < lng; c++) bytes[c] = 0; for (int d = 0; d < lng; d++) bytes[d] = static_cast(tokens[5 + d].toInt(nullptr, 16)); thisFrame.setPayload(bytes); } else if (fileVersion == 2) { if (tokens[3].at(0) == 'R') thisFrame.isReceived = true; else thisFrame.isReceived = false; thisFrame.bus = tokens[4].toInt(); int lng = tokens[5].toInt(); if (lng > 8) lng = 8; if (lng < 0) lng = 0; if (lng + 6 > tokens.length()) lng = tokens.length() - 6; QByteArray bytes(lng, 0); for (int c = 0; c < lng; c++) bytes[c] = 0; for (int d = 0; d < lng; d++) bytes[d] = static_cast(tokens[6 + d].toInt(nullptr, 16)); thisFrame.setPayload(bytes); } frames->append(thisFrame); } else foundErrors = true; } } inFile->close(); delete inFile; return !foundErrors; } bool FrameFileIO::saveNativeCSVFile(QString filename, const QVector* frames) { QFile *outFile = new QFile(filename); int lineCounter = 0; const unsigned char *data; int dataLen; const CANFrame *frame; if (!outFile->open(QIODevice::WriteOnly | QIODevice::Text)) { delete outFile; return false; } outFile->write("Time Stamp,ID,Extended,Dir,Bus,LEN,D1,D2,D3,D4,D5,D6,D7,D8"); outFile->write("\n"); for (int c = 0; c < frames->count(); c++) { lineCounter++; if (lineCounter > 100) { qApp->processEvents(); lineCounter = 0; } frame = &frames->at(c); data = reinterpret_cast(frame->payload().constData()); dataLen = frame->payload().count(); outFile->write(QString::number(frame->timeStamp().microSeconds()).toUtf8()); outFile->putChar(44); outFile->write(QString::number(frame->frameId(), 16).toUpper().rightJustified(8, '0').toUtf8()); outFile->putChar(44); if (frame->hasExtendedFrameFormat()) outFile->write("true,"); else outFile->write("false,"); if (frame->isReceived) outFile->write("Rx,"); else outFile->write("Tx,"); outFile->write(QString::number(frame->bus).toUtf8()); outFile->putChar(44); outFile->write(QString::number(dataLen).toUtf8()); outFile->putChar(44); for (int temp = 0; temp < 8; temp++) { if (temp < dataLen) outFile->write(QString::number(data[temp], 16).toUpper().rightJustified(2, '0').toUtf8()); else outFile->write("00"); outFile->putChar(44); } outFile->write("\n"); } outFile->close(); delete outFile; return true; } bool FrameFileIO::openContinuousNative() { QString filename; QFileDialog dialog(qApp->activeWindow()); QSettings settings; QStringList filters; filters.append(QString(tr("GVRET Logs (*.csv *.CSV)"))); dialog.setDirectory(settings.value("FileIO/LoadSaveDirectory", dialog.directory().path()).toString()); dialog.setFileMode(QFileDialog::AnyFile); dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); dialog.setAcceptMode(QFileDialog::AcceptSave); if (dialog.exec() == QDialog::Accepted) { filename = dialog.selectedFiles()[0]; continuousFile.setFileName(filename); if (!continuousFile.open(QIODevice::WriteOnly | QIODevice::Text)) { return false; } continuousFile.write("Time Stamp,ID,Extended,Dir,Bus,LEN,D1,D2,D3,D4,D5,D6,D7,D8"); continuousFile.write("\n"); settings.setValue("FileIO/LoadSaveDirectory", dialog.directory().path()); return true; } return false; } bool FrameFileIO::closeContinuousNative() { if (continuousFile.isOpen()) { continuousFile.close(); return true; } return false; } bool FrameFileIO::writeContinuousNative(const QVector* frames, int beginningFrame) { const unsigned char *data; int dataLen; const CANFrame *frame; if (!continuousFile.isOpen()) return false; qDebug() << "Bgn: " << beginningFrame << " Count: " << frames->count(); for (int c = beginningFrame; c < frames->count(); c++) { frame = &frames->at(c); data = reinterpret_cast(frame->payload().constData()); dataLen = frame->payload().count(); continuousFile.write(QString::number(frame->timeStamp().microSeconds()).toUtf8()); continuousFile.putChar(44); continuousFile.write(QString::number(frame->frameId(), 16).toUpper().rightJustified(8, '0').toUtf8()); continuousFile.putChar(44); if (frame->hasExtendedFrameFormat()) continuousFile.write("true,"); else continuousFile.write("false,"); if (frame->isReceived) continuousFile.write("Rx,"); else continuousFile.write("Tx,"); continuousFile.write(QString::number(frame->bus).toUtf8()); continuousFile.putChar(44); continuousFile.write(QString::number(dataLen).toUtf8()); continuousFile.putChar(44); for (int temp = 0; temp < 8; temp++) { if (temp < dataLen) continuousFile.write(QString::number(data[temp], 16).toUpper().rightJustified(2, '0').toUtf8()); else continuousFile.write("00"); continuousFile.putChar(44); } continuousFile.write("\n"); } return true; } bool FrameFileIO::flushContinuousNative() { if (continuousFile.isOpen()) { return continuousFile.flush(); } return false; } bool FrameFileIO::isGenericCSVFile(QString filename) { QFile *inFile = new QFile(filename); QByteArray line; bool isMatch = true; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { delete inFile; return false; } try { line = inFile->readLine(); //read out the header first and discard it. if (!inFile->atEnd()) { line = inFile->readLine(); if (line.length() > 2) { QList tokens = line.split(','); int ID = tokens[0].toInt(nullptr, 16); if (ID < 1 || ID > 0x1FFFFFFF) isMatch = false; if (tokens.count() < 2) { isMatch = false; } if (isMatch) { QList dataTok = tokens[1].split(' '); int len = dataTok.length(); if (len > 8) isMatch = false; if (len < 2) isMatch = false; } } else isMatch = false; } } catch (...) { isMatch = false; } inFile->close(); delete inFile; return isMatch; } bool FrameFileIO::loadGenericCSVFile(QString filename, QVector* frames) { QFile *inFile = new QFile(filename); CANFrame thisFrame; QByteArray line; uint64_t timeStamp = Utility::GetTimeMS(); int lineCounter = 0; bool foundErrors = false; thisFrame.setFrameType(QCanBusFrame::DataFrame); if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { delete inFile; return false; } line = inFile->readLine(); //read out the header first and discard it. while (!inFile->atEnd()) { lineCounter++; if (lineCounter > 100) { qApp->processEvents(); lineCounter = 0; } line = inFile->readLine(); if (line.length() > 2) { QList tokens = line.split(','); timeStamp += 5000; thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, timeStamp)); thisFrame.setFrameId(tokens[0].toUInt(nullptr, 16)); if (thisFrame.frameId() > 0x7FF) thisFrame.setExtendedFrameFormat(true); else thisFrame.setExtendedFrameFormat(false); thisFrame.bus = 0; thisFrame.setFrameType(QCanBusFrame::DataFrame); if (tokens.length() > 1) { QList dataTok = tokens[1].split(' '); int dLen = dataTok.length(); if (dLen > 8) dLen = 8; QByteArray bytes(dLen, 0); for (int d = 0; d < dLen; d++) bytes[d] = static_cast(dataTok[d].toInt(nullptr, 16)); thisFrame.setPayload(bytes); frames->append(thisFrame); } } else foundErrors = true; } inFile->close(); delete inFile; return !foundErrors; } //4f5,ff 34 23 45 24 e4 bool FrameFileIO::saveGenericCSVFile(QString filename, const QVector* frames) { QFile *outFile = new QFile(filename); int lineCounter = 0; const unsigned char *data; int dataLen; const CANFrame *frame; if (!outFile->open(QIODevice::WriteOnly | QIODevice::Text)) { delete outFile; return false; } outFile->write("ID,Data Bytes"); outFile->write("\n"); for (int c = 0; c < frames->count(); c++) { lineCounter++; if (lineCounter > 100) { qApp->processEvents(); lineCounter = 0; } frame = &frames->at(c); data = reinterpret_cast(frame->payload().constData()); dataLen = frame->payload().count(); outFile->write(QString::number(frame->frameId(), 16).toUpper().rightJustified(8, '0').toUtf8()); outFile->putChar(44); for (int temp = 0; temp < dataLen; temp++) { outFile->write(QString::number(data[temp], 16).toUpper().rightJustified(2, '0').toUtf8()); outFile->putChar(' '); } outFile->write("\n"); } outFile->close(); delete outFile; return true; } bool FrameFileIO::isLogFile(QString filename) { QFile *inFile = new QFile(filename); QByteArray line; bool isMatch = true; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { delete inFile; return false; } try { line = inFile->readLine().toUpper(); if (!line.contains("BUSMASTER")) isMatch = false; while (!inFile->atEnd() && line.startsWith("***")) { line = inFile->readLine().toUpper(); } if (!inFile->atEnd()) { line = inFile->readLine().toUpper(); if (line.length() > 1) { QList tokens = line.split(' '); if (tokens.length() >= 6) { QList timeToks = tokens[0].split(':'); if (timeToks.count() != 4) isMatch = false; int ID = tokens[3].right(tokens[3].length() - 2).toInt(nullptr, 16); if (ID < 1 || ID > 0x1FFFFFFF) isMatch = false; if (tokens[4] != "S" && tokens[4] != "X" && tokens[4] != "SR" && tokens[4] != "XR") isMatch = false; int len = tokens[5].toInt(); if (len > 8) isMatch = false; } else isMatch = false; } } } catch (...) { isMatch = false; } inFile->close(); delete inFile; return isMatch; } //busmaster log file /* tokens: 0 = timestamp 1 = Transmission direction 2 = Channel 3 = ID 4 = Type (s = standard, x = extended, sr = standard remote, xr = extended remote) 5 = Data byte length 6-x = The data bytes Sample chunk of a busmaster log: ***BUSMASTER Ver 2.4.0*** ***PROTOCOL CAN*** ***NOTE: PLEASE DO NOT EDIT THIS DOCUMENT*** ***[START LOGGING SESSION]*** ***START DATE AND TIME 8:8:2014 11:49:7:965*** ***HEX*** ***SYSTEM MODE*** ***START CHANNEL BAUD RATE*** ***CHANNEL 1 - Kvaser - Kvaser Leaf Light HS #0 (Channel 0), Serial Number- 0, Firmware- 0x00000037 0x00020000 - 500000 bps*** ***END CHANNEL BAUD RATE*** ***START DATABASE FILES (DBF/DBC)*** ***END OF DATABASE FILES (DBF/DBC)*** ***