From 6e0000dcadb2a9aecb284d03eec0608033c325ea Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Mon, 22 Feb 2016 21:05:43 -0500 Subject: [PATCH 01/26] Additional coding on discrete state window. --- discretestatewindow.cpp | 193 ++++++++++++++++++++++--- discretestatewindow.h | 7 +- discretestatewindow.ui | 309 +++++++++++++++++++++++++++------------- 3 files changed, 390 insertions(+), 119 deletions(-) diff --git a/discretestatewindow.cpp b/discretestatewindow.cpp index d5b5f80..473402e 100644 --- a/discretestatewindow.cpp +++ b/discretestatewindow.cpp @@ -14,9 +14,47 @@ DiscreteStateWindow::DiscreteStateWindow(const QVector *frames, QWidge timer = new QTimer(); timer->setInterval(100); + isRealtime = ui->rbRealtime->isChecked(); + typeChanged(); + connect(ui->btnStart, SIGNAL(clicked(bool)), this, SLOT(handleStartButton())); connect(timer, SIGNAL(timeout()), this, SLOT(handleTick())); connect(MainWindow::getReference(), SIGNAL(framesUpdated(int)), this, SLOT(updatedFrames(int))); + connect(ui->rbLogged, SIGNAL(clicked(bool)), this, SLOT(typeChanged())); + connect(ui->rbRealtime, SIGNAL(clicked(bool)), this, SLOT(typeChanged())); + + connect(ui->btnAll, &QAbstractButton::clicked, + [=]() + { + for (int i = 0; i < ui->listID->count(); i++) + { + QListWidgetItem *item = ui->listID->item(i); + item->setCheckState(Qt::Checked); + idFilters[Utility::ParseStringToNum(item->text())] = true; + } + }); + + connect(ui->btnNone, &QAbstractButton::clicked, + [=]() + { + for (int i = 0; i < ui->listID->count(); i++) + { + QListWidgetItem *item = ui->listID->item(i); + item->setCheckState(Qt::Unchecked); + idFilters[Utility::ParseStringToNum(item->text())] = false; + } + }); + + connect(ui->listID, &QListWidget::itemChanged, + [=](QListWidgetItem *item) + { + bool isChecked = false; + int id = Utility::ParseStringToNum(item->text()); + if (item->checkState() == Qt::Checked) isChecked = true; + idFilters[id] = isChecked; + }); + + refreshFilterList(); } DiscreteStateWindow::~DiscreteStateWindow() @@ -33,14 +71,45 @@ DiscreteStateWindow::~DiscreteStateWindow() delete ui; } +void DiscreteStateWindow::typeChanged() +{ + if (ui->rbLogged->isChecked()) + { + ui->spinFreq->setEnabled(false); + ui->spinIterations->setEnabled(false); + ui->lblStatus->setEnabled(false); + ui->spinMaxBits->setEnabled(true); + ui->spinMinBits->setEnabled(true); + ui->listID->setEnabled(true); + ui->btnAll->setEnabled(true); + ui->btnNone->setEnabled(true); + isRealtime = false; + } + else + { + ui->spinFreq->setEnabled(true); + ui->spinIterations->setEnabled(true); + ui->lblStatus->setEnabled(true); + ui->spinMaxBits->setEnabled(false); + ui->spinMinBits->setEnabled(false); + ui->listID->setEnabled(false); + ui->btnAll->setEnabled(false); + ui->btnNone->setEnabled(false); + isRealtime = true; + } +} + void DiscreteStateWindow::updatedFrames(int numFrames) { CANFrame thisFrame; if (numFrames == -1) //all frames deleted. Kill the display { + ui->listID->clear(); + idFilters.clear(); } else if (numFrames == -2) //all new set of frames. Reset { + refreshFilterList(); } else //just got some new frames. See if they are relevant. { @@ -48,12 +117,43 @@ void DiscreteStateWindow::updatedFrames(int numFrames) for (int i = modelFrames->count() - numFrames; i < modelFrames->count(); i++) { thisFrame = modelFrames->at(i); + + if (!idFilters.contains(thisFrame.ID)) + { + idFilters.insert(thisFrame.ID, true); + QListWidgetItem* listItem = new QListWidgetItem(Utility::formatNumber(thisFrame.ID), ui->listID); + listItem->setFlags(listItem->flags() | Qt::ItemIsUserCheckable); // set checkable flag + listItem->setCheckState(Qt::Checked); //default all filters to be set active + } + if (operatingState == DWStates::IDLE) stateFrames[0]->append(thisFrame); else stateFrames[currToggleState + 1]->append(thisFrame); } } } +void DiscreteStateWindow::refreshFilterList() +{ + int id; + + idFilters.clear(); + ui->listID->clear(); + + for (int i = 0; i < modelFrames->length(); i++) + { + id = modelFrames->at(i).ID; + if (!idFilters.contains(id)) + { + idFilters.insert(id, true); + QListWidgetItem* listItem = new QListWidgetItem(Utility::formatNumber(id), ui->listID); + listItem->setFlags(listItem->flags() | Qt::ItemIsUserCheckable); // set checkable flag + listItem->setCheckState(Qt::Checked); //default all filters to be set active + } + } + + ui->listID->sortItems(); +} + void DiscreteStateWindow::showEvent(QShowEvent* event) { QDialog::showEvent(event); @@ -97,7 +197,7 @@ void DiscreteStateWindow::updateStateLabel() switch(operatingState) { case DWStates::IDLE: - ui->lblStatus->setText("WAIT"); + ui->lblStatus->setText("IDLE"); pal = ui->lblStatus->palette(); pal.setColor(QPalette::WindowText, Qt::red); ui->lblStatus->setPalette(pal); @@ -109,7 +209,7 @@ void DiscreteStateWindow::updateStateLabel() ui->lblStatus->setPalette(pal); break; case DWStates::COUNTDOWN_WAITING: - ui->lblStatus->setText("Return to resting state"); + ui->lblStatus->setText("Wait...."); pal = ui->lblStatus->palette(); pal.setColor(QPalette::WindowText, Qt::red); ui->lblStatus->setPalette(pal); @@ -120,32 +220,45 @@ void DiscreteStateWindow::updateStateLabel() pal.setColor(QPalette::WindowText, Qt::green); ui->lblStatus->setPalette(pal); break; + case DWStates::DONE: + ui->lblStatus->setText("DONE"); + pal = ui->lblStatus->palette(); + pal.setColor(QPalette::WindowText, Qt::green); + ui->lblStatus->setPalette(pal); + break; } } void DiscreteStateWindow::handleStartButton() { - operatingState = DWStates::COUNTDOWN_SIGNAL; - - ticksPerStateChange = ticksUntilStateChange = ui->spinFreq->value() * 10; - numToggleStates = ui->spinStates->value(); - numIterations = ui->spinIterations->value(); - - currToggleState = 0; - currIteration = 0; - - for (int i = 0; i < stateFrames.count(); i++) + if (isRealtime) { - stateFrames[i]->clear(); - delete(stateFrames[i]); - } + operatingState = DWStates::COUNTDOWN_SIGNAL; - for (int j = 0; j <= numToggleStates; j++) + ticksPerStateChange = ticksUntilStateChange = ui->spinFreq->value() * 10; + numToggleStates = ui->spinStates->value(); + numIterations = ui->spinIterations->value(); + + currToggleState = 0; + currIteration = 0; + + for (int i = stateFrames.count() - 1; i > 0; i--) + { + stateFrames[i]->clear(); + stateFrames.removeAt(i); + } + + for (int j = 0; j <= numToggleStates; j++) + { + stateFrames.append(new QVector()); + } + + timer->start(); + } + else { - stateFrames.append(new QVector()); + calculateResults(); } - - timer->start(); } void DiscreteStateWindow::handleTick() @@ -168,11 +281,11 @@ void DiscreteStateWindow::handleTick() { ticksUntilStateChange = ticksPerStateChange; currIteration++; - if (currIteration == numIterations) + if (currIteration > numIterations) { operatingState = DWStates::IDLE; timer->stop(); - //call to calculate our findings here. + calculateResults(); } else operatingState = DWStates::COUNTDOWN_SIGNAL; } @@ -184,7 +297,7 @@ void DiscreteStateWindow::handleTick() ticksUntilStateChange = ticksPerStateChange; operatingState = DWStates::COUNTDOWN_WAITING; currToggleState++; - if (currToggleState == numToggleStates) currToggleState = 0; + if (currToggleState > numToggleStates) currToggleState = 0; } break; } @@ -193,5 +306,41 @@ void DiscreteStateWindow::handleTick() void DiscreteStateWindow::calculateResults() { + int minBits, maxBits; + if (isRealtime) + { + } + else //use already loaded frames from main cache + { + //basic overview: run through all ID filters and see if it is enabled. + //If so add it to a giant list of messages by ID where each ID has its own + //list of messages + //Then, for each ID start a loop that runs from largest bits to smallest bits for scan + //For each value run through the algorithm for all messages. At the end list any matches + //the simplest approach seems to be to grab that number of bits and then record every + //unique value. IF the # of unique values is the same as the number of states then + //we've got a match.It should be noted that the # of states must be at least 2 - the idle + //state is 1 and then a second state at the minimum. Turn signals might be 3 states then + + minBits = ui->spinMinBits->value(); + maxBits = ui->spinMaxBits->value(); + QHash::const_iterator it; + QList frameCache; + for (it = idFilters.begin(); it != idFilters.end(); ++it) + { + if (it.value()) + { + frameCache.clear(); + for (int i = 0; i < modelFrames->count(); i++) + { + if (modelFrames->at(i).ID == it.key()) frameCache.append(modelFrames->at(i)); + } + for (int bits = maxBits; bits >= minBits; bits--) + { + QList values; + } + } + } + } } diff --git a/discretestatewindow.h b/discretestatewindow.h index 311f7be..e027cca 100644 --- a/discretestatewindow.h +++ b/discretestatewindow.h @@ -15,7 +15,8 @@ enum DiscreteWindowState IDLE, COUNTDOWN_SIGNAL, COUNTDOWN_WAITING, - GETTING_SIGNAL + GETTING_SIGNAL, + DONE }; } @@ -33,6 +34,7 @@ private slots: void updatedFrames(int); void handleStartButton(); void handleTick(); + void typeChanged(); private: Ui::DiscreteStateWindow *ui; @@ -46,7 +48,10 @@ private: int currToggleState; int numIterations; int currIteration; + bool isRealtime; + QHash idFilters; + void refreshFilterList(); void closeEvent(QCloseEvent *event); void readSettings(); void writeSettings(); diff --git a/discretestatewindow.ui b/discretestatewindow.ui index ed01b92..dd61278 100644 --- a/discretestatewindow.ui +++ b/discretestatewindow.ui @@ -6,8 +6,8 @@ 0 0 - 400 - 300 + 749 + 795 @@ -31,63 +31,232 @@ - - + + Number of States: - - - - Toggle Frequency (Seconds) - - - - - - - 1 - - - 30 - - - 2 - - - - + - 1 + 2 - 20 - - - - - - - Iterations - - - - - - - 1 - - - 10 + 64 + + + + + + Realtime + + + true + + + + + + + From logged data + + + + + + + + + + + + + Toggle Frequency (Seconds) + + + + + + + 1 + + + 30 + + + 2 + + + + + + + Iterations + + + + + + + 1 + + + 10 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 120 + 120 + 120 + + + + + + + + + Nirmala UI + 26 + 75 + true + + + + WAIT + + + + + + + + + + + Min Bits + + + + + + + 1 + + + 8 + + + 1 + + + + + + + Max Bits + + + + + + + 1 + + + 8 + + + 8 + + + + + + + IDs to Scan + + + + + + + + + + + + All + + + + + + + None + + + + + + + + + + + + @@ -95,58 +264,6 @@ - - - - - - - - - 255 - 0 - 0 - - - - - - - - - 255 - 0 - 0 - - - - - - - - - 120 - 120 - 120 - - - - - - - - - Nirmala UI - 26 - 75 - true - - - - WAIT - - - From 03f9a30687d205e9efafeef5c02acaed65e0ab52 Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Mon, 22 Feb 2016 21:06:12 -0500 Subject: [PATCH 02/26] Fixed bugs in UDS scanner and added additional functionality. --- udsscanwindow.cpp | 56 ++++++++++++++++++++++++++++++++++++++--------- udsscanwindow.ui | 20 +++++++++++++++++ 2 files changed, 66 insertions(+), 10 deletions(-) diff --git a/udsscanwindow.cpp b/udsscanwindow.cpp index 37c9d07..8e79734 100644 --- a/udsscanwindow.cpp +++ b/udsscanwindow.cpp @@ -11,7 +11,7 @@ UDSScanWindow::UDSScanWindow(const QVector *frames, QWidget *parent) : modelFrames = frames; waitTimer = new QTimer; - waitTimer->setInterval(500); + waitTimer->setInterval(300); connect(MainWindow::getReference(), SIGNAL(framesUpdated(int)), this, SLOT(updatedFrames(int))); connect(ui->btnScan, &QPushButton::clicked, this, &UDSScanWindow::scanUDS); @@ -36,6 +36,7 @@ void UDSScanWindow::scanUDS() sendingFrames.clear(); CANFrame frame; + int typ, id; int startID, endID; startID = Utility::ParseStringToNum(ui->txtStartID->text()); endID = Utility::ParseStringToNum(ui->txtEndID->text()); @@ -43,9 +44,35 @@ void UDSScanWindow::scanUDS() int buses = ui->cbBuses->currentIndex(); buses++; if (buses < 1) buses = 1; - for (int typ = 1; typ < 5; typ++) + + //start out by sending tester present to every address to see if anyone replies + for (id = startID; id < endID; id++) { - for (int id = startID; id < endID; id++) + frame.ID = id; + frame.len = 8; + frame.extended = false; + frame.data[0] = 2; + frame.data[1] = 0x3E; //tester present + frame.data[2] = 0; + frame.data[3] = 0;frame.data[4] = 0;frame.data[5] = 0; + frame.data[6] = 0;frame.data[7] = 0; + + if (buses & 1) + { + frame.bus = 0; + sendingFrames.append(frame); + } + if (buses & 2) + { + frame.bus = 1; + sendingFrames.append(frame); + } + } + + //then try asking for the various diagnostic session types + for (typ = 1; typ < 5; typ++) + { + for (id = startID; id <= endID; id++) { frame.ID = id; frame.len = 8; @@ -92,13 +119,22 @@ void UDSScanWindow::updatedFrames(int numFrames) { thisFrame = modelFrames->at(i); id = thisFrame.ID; - if (id >= 0x7E8 && id <= 0x7EF) + + id -= ui->spinReplyOffset->value(); //back to original ECU id + if (id == sendingFrames[currIdx].ID) { - id -= 8; //back to original ECU id - result = "ECU at bus " + QString::number(thisFrame.bus) + " ID: " + QString::number(id, 16) + " responds to mode " - + QString::number(sendingFrames[currIdx].data[2]) + " with: " + QString::number(thisFrame.data[0], 16) + " " - + QString::number(thisFrame.data[1], 16) + " " + QString::number(thisFrame.data[2], 16) - + " " + QString::number(thisFrame.data[3], 16); + if (thisFrame.data[1] == 0x40 + sendingFrames[currIdx].data[1]) + { + result = "ECU at bus " + QString::number(thisFrame.bus) + " ID: " + QString::number(id, 16) + " responds to mode " + + QString::number(sendingFrames[currIdx].data[1], 16) + + " " + QString::number(sendingFrames[currIdx].data[2], 16) + " with affirmation."; + } + else if ( thisFrame.data[1] == 0x7F) + { + result = "ECU at bus " + QString::number(thisFrame.bus) + " ID: " + QString::number(id, 16) + " responds to mode " + + QString::number(sendingFrames[currIdx].data[1], 16) + + " " + QString::number(sendingFrames[currIdx].data[2], 16) + " with an error."; + } ui->listResults->addItem(result); sendNextMsg(); } @@ -110,7 +146,7 @@ void UDSScanWindow::timeOut() { QString result; result = "ECU at bus " + QString::number(sendingFrames[currIdx].bus) + " ID: " + QString::number(sendingFrames[currIdx].ID, 16) + " did not respond to mode " - + QString::number(sendingFrames[currIdx].data[2]); + + QString::number(sendingFrames[currIdx].data[1], 16) + " " + QString::number(sendingFrames[currIdx].data[2], 16); ui->listResults->addItem(result); sendNextMsg(); diff --git a/udsscanwindow.ui b/udsscanwindow.ui index 14aff43..d4d67e8 100644 --- a/udsscanwindow.ui +++ b/udsscanwindow.ui @@ -64,6 +64,26 @@ + + + + Reply Offset: + + + + + + + -128 + + + 128 + + + 8 + + + From 6e64ab713485248e7d658390e0de8bcabc44da20 Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Thu, 25 Feb 2016 20:51:46 -0500 Subject: [PATCH 03/26] Changes to connection window to actually retrieve and show socketcan interfaces now. --- connectionwindow.cpp | 8 ++++++++ connectionwindow.h | 1 + 2 files changed, 9 insertions(+) diff --git a/connectionwindow.cpp b/connectionwindow.cpp index ad46e77..8a8ea45 100644 --- a/connectionwindow.cpp +++ b/connectionwindow.cpp @@ -48,6 +48,9 @@ ConnectionWindow::ConnectionWindow(QWidget *parent) : #endif connect(ui->btnOK, SIGNAL(clicked(bool)), this, SLOT(handleOKButton())); + connect(ui->rbGVRET, SIGNAL(toggled(bool)), this, SLOT(handleConnTypeChanged())); + connect(ui->rbKvaser, SIGNAL(toggled(bool)), this, SLOT(handleConnTypeChanged())); + connect(ui->rbSocketCAN, SIGNAL(toggled(bool)), this, SLOT(handleConnTypeChanged())); } ConnectionWindow::~ConnectionWindow() @@ -60,6 +63,11 @@ void ConnectionWindow::showEvent(QShowEvent* event) { QDialog::showEvent(event); qDebug() << "Show connectionwindow"; + handleConnTypeChanged(); +} + +void ConnectionWindow::handleConnTypeChanged() +{ if (ui->rbGVRET->isChecked()) getSerialPorts(); if (ui->rbKvaser->isChecked()) getKvaserPorts(); if (ui->rbSocketCAN->isChecked()) getSocketcanPorts(); diff --git a/connectionwindow.h b/connectionwindow.h index 3315006..e098f67 100644 --- a/connectionwindow.h +++ b/connectionwindow.h @@ -43,6 +43,7 @@ public slots: private slots: void handleOKButton(); + void handleConnTypeChanged(); private: Ui::ConnectionWindow *ui; From f8e72986f00ebaa38fe422755b0cac1417ad2f65 Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Thu, 25 Feb 2016 21:09:48 -0500 Subject: [PATCH 04/26] Fixed parsing and processing of modifiers in custom frame sender window. Added serialbus to project (Qt5.6 dependency) --- SavvyCAN.pro | 2 +- framesenderwindow.cpp | 85 ++++++++++++++++++++----------------------- 2 files changed, 41 insertions(+), 46 deletions(-) diff --git a/SavvyCAN.pro b/SavvyCAN.pro index 8d64e4d..344c9e0 100644 --- a/SavvyCAN.pro +++ b/SavvyCAN.pro @@ -6,7 +6,7 @@ QT += core gui -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets serialport printsupport qml +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets serialport printsupport qml serialbus CONFIG(release, debug|release):DEFINES += QT_NO_DEBUG_OUTPUT diff --git a/framesenderwindow.cpp b/framesenderwindow.cpp index 61cc598..91dd13c 100644 --- a/framesenderwindow.cpp +++ b/framesenderwindow.cpp @@ -430,7 +430,7 @@ int FrameSenderWindow::fetchOperand(int idx, ModifierOperand op) if (op.notOper) return ~op.databyte; else return op.databyte; } - else if (op.ID == -2) + else if (op.ID == -2) //fetch data from a data byte within the output frame { if (op.notOper) return ~sendingData.at(idx).data[op.databyte]; else return sendingData.at(idx).data[op.databyte]; @@ -473,6 +473,8 @@ void FrameSenderWindow::processModifierText(int line) QString modString; bool firstOp = true; bool abort = false; + QString token; + ModifierOp thisOp; //Example line: //d0 = D0 + 1,d1 = id:0x200:d3 + id:0x200:d4 AND 0xF0 - Original version @@ -493,6 +495,7 @@ void FrameSenderWindow::processModifierText(int line) { Modifier thisMod; thisMod.destByte = 0; + firstOp = true; QString leftSide = Utility::grabAlphaNumeric(mods[i]); if (leftSide.startsWith("D") && leftSide.length() == 2) @@ -511,58 +514,50 @@ void FrameSenderWindow::processModifierText(int line) continue; } abort = false; + + token = Utility::grabAlphaNumeric(mods[i]); + if (token[0] == '~') + { + thisOp.first.notOper = true; + token = token.remove(0, 1); //remove the ~ character + } + else thisOp.first.notOper = false; + parseOperandString(token.split(":"), thisOp.first); + + if (mods[i].length() < 2) { + abort = true; + thisOp.operation = ADDITION; + thisOp.second.ID = 0; + thisOp.second.databyte = 0; + thisOp.second.notOper = false; + thisMod.operations.append(thisOp); + } + while (!abort) { - QString token = Utility::grabAlphaNumeric(mods[i]); - ModifierOp thisOp; - - if (firstOp) + QString operation = Utility::grabOperation(mods[i]); + if (operation == "") { - if (token[0] == '~') - { - thisOp.first.notOper = true; - token = token.remove(0, 1); //remove the ~ character - } - else thisOp.first.notOper = false; - parseOperandString(token.split(":"), thisOp.first); - firstOp = false; + abort = true; } else { - thisOp.first.ID = -1; //shadow register - } - if (mods[i].length() == 0) //if this thing had no actual operation or second operand then fake it - { - abort = true; - thisOp.operation = ADDITION; - thisOp.second.ID = 0; - thisOp.second.databyte = 0; - thisOp.second.notOper = false; + thisOp.operation = parseOperation(operation); + QString secondOp = Utility::grabAlphaNumeric(mods[i]); + if (mods[i][0] == '~') + { + thisOp.second.notOper = true; + mods[i] = mods[i].remove(0, 1); //remove the ~ character + } + else thisOp.second.notOper = false; + thisOp.second.bus = sendingData[line].bus; + thisOp.second.ID = sendingData[line].ID; + parseOperandString(secondOp.split(":"), thisOp.second); thisMod.operations.append(thisOp); } - else //otherwise try to grab them - { - QString operation = Utility::grabOperation(mods[i]); - if (operation == "") - { - abort = true; - } - else - { - thisOp.operation = parseOperation(operation); - QString secondOp = Utility::grabAlphaNumeric(mods[i]); - if (mods[i][0] == '~') - { - thisOp.second.notOper = true; - mods[i] = mods[i].remove(0, 1); //remove the ~ character - } - else thisOp.second.notOper = false; - thisOp.second.bus = sendingData[line].bus; - thisOp.second.ID = sendingData[line].ID; - parseOperandString(secondOp.split(":"), thisOp.second); - thisMod.operations.append(thisOp); - } - } + + thisOp.first.ID = -1; //shadow register + if (mods[i].length() < 2) abort = true; } sendingData[line].modifiers.append(thisMod); From 3bc8d120cb8793ebe5552f1b9197fa955a94952b Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Thu, 25 Feb 2016 21:20:42 -0500 Subject: [PATCH 05/26] Updated readme file --- README.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 27b7b88..7a2edea 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SavvyCAN QT based cross platform canbus tool -(C) 2015 EVTV and Collin Kidder +(C) 2015-2016 EVTV and Collin Kidder A QT5 based cross platform tool which can be used to load, save, and capture canbus frames. This tool is designed to help with visualization, reverse engineering, debugging, and @@ -8,12 +8,17 @@ capturing of canbus frames. Really requires at a resolution of at least 1024x768. Fully multi-monitor capable. -Currently canbus capture requires a CANDue board from EVTV: +You are highly recommended to use the CANDue board from EVTV: (http://store.evtv.me/proddetail.php?prod=ArduinoDueCANBUS&cat=23) The CANDue board must be running the GVRET firmware which can also be found within the collin80 repos. +It is very soon to be possible to use any SocketCAN compatible device +under LINUX. There may, however, be some loss of some functionality as +some functions of SavvyCAN are designed for use directly with the +EVTVDue and CANDue 2.0 boards. + It should, however, be noted that use of a capture device is not required to make use of this program. It can load and save in several formats: @@ -27,6 +32,14 @@ of this program. It can load and save in several formats: 5. Generic CSV file (ID,D0 D1 D2 D3 D4 D5 D6 D7) +6. Vector Trace files + +7. IXXAT Minilog files + +8. CAN-DO Logs + +9. Vehicle Spy log files + Requires QScintilla library available at: https://www.riverbankcomputing.com/software/qscintilla/download @@ -37,3 +50,6 @@ http://www.qcustomplot.com/ However, this source code is integrated into the source for SavvyCAN and one isn't required to download it separately. +The project now requires (yes, requires) at least QT 5.6.0 because of +a dependency on QtSerialBus which is still an optional feature. You +will need to download it when installing QT. \ No newline at end of file From 7f536928d76b14e3eb7cd10653d5c2441c14113b Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Fri, 26 Feb 2016 14:11:07 -0500 Subject: [PATCH 06/26] More changes to UDS scanner to make it better able to find UDS nodes even if they don't quite match standard UDS conventions, Ability to start/stop UDS scan when it was running. --- udsscanwindow.cpp | 57 ++++++++++++++++++++++++++++++++++------------- udsscanwindow.h | 1 + udsscanwindow.ui | 7 ++++++ 3 files changed, 49 insertions(+), 16 deletions(-) diff --git a/udsscanwindow.cpp b/udsscanwindow.cpp index 8e79734..ad2272d 100644 --- a/udsscanwindow.cpp +++ b/udsscanwindow.cpp @@ -10,6 +10,8 @@ UDSScanWindow::UDSScanWindow(const QVector *frames, QWidget *parent) : modelFrames = frames; + currentlyRunning = false; + waitTimer = new QTimer; waitTimer->setInterval(300); @@ -32,6 +34,14 @@ UDSScanWindow::~UDSScanWindow() void UDSScanWindow::scanUDS() { + if (currentlyRunning) + { + waitTimer->stop(); + sendingFrames.clear(); + currentlyRunning = false; + ui->btnScan->setText("Start Scan"); + } + ui->listResults->clear(); sendingFrames.clear(); @@ -98,6 +108,8 @@ void UDSScanWindow::scanUDS() waitTimer->start(); currIdx = -1; + currentlyRunning = true; + ui->btnScan->setText("Abort Scan"); sendNextMsg(); } @@ -106,6 +118,9 @@ void UDSScanWindow::updatedFrames(int numFrames) CANFrame thisFrame; QString result; int id; + int offset = ui->spinReplyOffset->value(); + bool gotReply = false; + if (numFrames == -1) //all frames deleted. We don't care { } @@ -118,25 +133,33 @@ void UDSScanWindow::updatedFrames(int numFrames) for (int i = modelFrames->count() - numFrames; i < modelFrames->count(); i++) { thisFrame = modelFrames->at(i); - id = thisFrame.ID; + id = thisFrame.ID; - id -= ui->spinReplyOffset->value(); //back to original ECU id - if (id == sendingFrames[currIdx].ID) + if (id == (sendingFrames[currIdx].ID + offset) || ui->cbAllowAdaptiveOffset->isChecked()) { - if (thisFrame.data[1] == 0x40 + sendingFrames[currIdx].data[1]) + int temp = thisFrame.data[0] >> 4; + if (temp == 0 || temp == 1) { - result = "ECU at bus " + QString::number(thisFrame.bus) + " ID: " + QString::number(id, 16) + " responds to mode " - + QString::number(sendingFrames[currIdx].data[1], 16) - + " " + QString::number(sendingFrames[currIdx].data[2], 16) + " with affirmation."; + if (thisFrame.data[1] == 0x40 + sendingFrames[currIdx].data[1]) + { + result = "Request on bus " + QString::number(thisFrame.bus) + " ID: " + QString::number(id, 16) + " got response to mode " + + QString::number(sendingFrames[currIdx].data[1], 16) + + " " + QString::number(sendingFrames[currIdx].data[2], 16) + " with affirmation from ID " + QString::number(id, 16) + "."; + gotReply = true; + } + else if ( thisFrame.data[1] == 0x7F) + { + result = "Request on bus " + QString::number(thisFrame.bus) + " ID: " + QString::number(id, 16) + " got response to mode " + + QString::number(sendingFrames[currIdx].data[1], 16) + + " " + QString::number(sendingFrames[currIdx].data[2], 16) + " with an error from ID " + QString::number(id, 16) + "."; + gotReply = true; + } + if (gotReply) + { + ui->listResults->addItem(result); + sendNextMsg(); + } } - else if ( thisFrame.data[1] == 0x7F) - { - result = "ECU at bus " + QString::number(thisFrame.bus) + " ID: " + QString::number(id, 16) + " responds to mode " - + QString::number(sendingFrames[currIdx].data[1], 16) - + " " + QString::number(sendingFrames[currIdx].data[2], 16) + " with an error."; - } - ui->listResults->addItem(result); - sendNextMsg(); } } } @@ -145,7 +168,7 @@ void UDSScanWindow::updatedFrames(int numFrames) void UDSScanWindow::timeOut() { QString result; - result = "ECU at bus " + QString::number(sendingFrames[currIdx].bus) + " ID: " + QString::number(sendingFrames[currIdx].ID, 16) + " did not respond to mode " + result = "Request on bus " + QString::number(sendingFrames[currIdx].bus) + " ID: " + QString::number(sendingFrames[currIdx].ID, 16) + " got no response to mode " + QString::number(sendingFrames[currIdx].data[1], 16) + " " + QString::number(sendingFrames[currIdx].data[2], 16); ui->listResults->addItem(result); @@ -162,5 +185,7 @@ void UDSScanWindow::sendNextMsg() else { waitTimer->stop(); + ui->btnScan->setText("Start Scan"); + currentlyRunning = false; } } diff --git a/udsscanwindow.h b/udsscanwindow.h index 39b06d5..0b17737 100644 --- a/udsscanwindow.h +++ b/udsscanwindow.h @@ -31,6 +31,7 @@ private: QTimer *waitTimer; QList sendingFrames; int currIdx = 0; + bool currentlyRunning; void sendNextMsg(); }; diff --git a/udsscanwindow.ui b/udsscanwindow.ui index d4d67e8..f0e1c2a 100644 --- a/udsscanwindow.ui +++ b/udsscanwindow.ui @@ -84,6 +84,13 @@ + + + + Allow adaptive reply offset + + + From 403689c214dbfa649468468967be3f990bff338d Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Fri, 26 Feb 2016 14:16:23 -0500 Subject: [PATCH 07/26] Small fix for potential crash bug in UDS scanner --- udsscanwindow.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/udsscanwindow.cpp b/udsscanwindow.cpp index 8e79734..9e0c9fc 100644 --- a/udsscanwindow.cpp +++ b/udsscanwindow.cpp @@ -121,7 +121,9 @@ void UDSScanWindow::updatedFrames(int numFrames) id = thisFrame.ID; id -= ui->spinReplyOffset->value(); //back to original ECU id - if (id == sendingFrames[currIdx].ID) + int numFrames = sendingFrames.length(); + + if (numFrames > 0 && numFrames > currIdx && id == sendingFrames[currIdx].ID) { if (thisFrame.data[1] == 0x40 + sendingFrames[currIdx].data[1]) { From 4ab63856eeec8a9a9e4e7bccd87913a9a07d3ada Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Sun, 28 Feb 2016 16:08:43 -0500 Subject: [PATCH 08/26] Remove reference to serialbus so code can be compiled on pre 5.6.0 QT again. There isn't any reason it has to be there (yet) --- SavvyCAN.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SavvyCAN.pro b/SavvyCAN.pro index 344c9e0..8d64e4d 100644 --- a/SavvyCAN.pro +++ b/SavvyCAN.pro @@ -6,7 +6,7 @@ QT += core gui -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets serialport printsupport qml serialbus +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets serialport printsupport qml CONFIG(release, debug|release):DEFINES += QT_NO_DEBUG_OUTPUT From 96d9f40193787326f3908cf1e6bd9456bedb274f Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Sun, 28 Feb 2016 16:09:25 -0500 Subject: [PATCH 09/26] Fixed a crash bug in frame info window. --- frameinfowindow.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frameinfowindow.cpp b/frameinfowindow.cpp index 72d20a0..c06e30d 100644 --- a/frameinfowindow.cpp +++ b/frameinfowindow.cpp @@ -84,7 +84,9 @@ void FrameInfoWindow::updatedFrames(int numFrames) else //just got some new frames. See if they are relevant. { if (numFrames > modelFrames->count()) return; - int currID = ui->listFrameID->currentItem()->text().toInt(NULL, 16); + int currID = 0; + if (ui->listFrameID->currentItem()) + currID = ui->listFrameID->currentItem()->text().toInt(NULL, 16); bool foundID = false; for (int x = modelFrames->count() - numFrames; x < modelFrames->count(); x++) { From 07b63ac1274a45f2c023a25bffcd0eeed151f88f Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Sun, 28 Feb 2016 16:10:34 -0500 Subject: [PATCH 10/26] Fixes to UDS scan window. --- udsscanwindow.cpp | 48 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/udsscanwindow.cpp b/udsscanwindow.cpp index 5fcec33..a64f423 100644 --- a/udsscanwindow.cpp +++ b/udsscanwindow.cpp @@ -119,6 +119,7 @@ void UDSScanWindow::updatedFrames(int numFrames) QString result; int id; int offset = ui->spinReplyOffset->value(); + CANFrame sentFrame; bool gotReply = false; if (numFrames == -1) //all frames deleted. We don't care @@ -130,40 +131,60 @@ void UDSScanWindow::updatedFrames(int numFrames) else //just got some new frames. See if they are relevant. { if (numFrames > modelFrames->count()) return; + + int numSending = sendingFrames.length(); + if (numSending == 0) return; + if (currIdx >= numSending) return; + sentFrame = sendingFrames[currIdx]; + for (int i = modelFrames->count() - numFrames; i < modelFrames->count(); i++) { + if (currIdx >= numSending) return; thisFrame = modelFrames->at(i); id = thisFrame.ID; - int numFrames = sendingFrames.length(); - - if (numFrames > 0 && numFrames > currIdx && id == (sendingFrames[currIdx].ID + offset) || ui->cbAllowAdaptiveOffset->isChecked()) + if ((id == (sentFrame.ID + offset)) || ui->cbAllowAdaptiveOffset->isChecked()) { int temp = thisFrame.data[0] >> 4; - if (temp == 0 || temp == 1) + if (temp == 0) //single frame reply (maybe) { if (thisFrame.data[1] == 0x40 + sendingFrames[currIdx].data[1]) { - result = "Request on bus " + QString::number(thisFrame.bus) + " ID: " + QString::number(id, 16) + " got response to mode " - + QString::number(sendingFrames[currIdx].data[1], 16) - + " " + QString::number(sendingFrames[currIdx].data[2], 16) + " with affirmation from ID " + QString::number(id, 16) + "."; + result = "Request on bus " + QString::number(sentFrame.bus) + " ID: " + QString::number(sentFrame.ID, 16) + " got response to mode " + + QString::number(sentFrame.data[1], 16) + + " " + QString::number(sentFrame.data[2], 16) + " with affirmation from ID " + QString::number(id, 16) + + " on bus " + QString::number(thisFrame.bus) + "."; gotReply = true; } else if ( thisFrame.data[1] == 0x7F) { - result = "Request on bus " + QString::number(thisFrame.bus) + " ID: " + QString::number(id, 16) + " got response to mode " - + QString::number(sendingFrames[currIdx].data[1], 16) - + " " + QString::number(sendingFrames[currIdx].data[2], 16) + " with an error from ID " + QString::number(id, 16) + "."; + result = "Request on bus " + QString::number(sentFrame.bus) + " ID: " + QString::number(sentFrame.ID, 16) + " got response to mode " + + QString::number(sentFrame.data[1], 16) + + " " + QString::number(sentFrame.data[2], 16) + " with an error from ID " + QString::number(id, 16) + + " on bus " + QString::number(thisFrame.bus) + "."; gotReply = true; } - if (gotReply) + } + + if (temp == 1) //start of a multiframe reply + { + if (thisFrame.data[2] == 0x40 + sendingFrames[currIdx].data[1]) { - ui->listResults->addItem(result); - sendNextMsg(); + result = "Request on bus " + QString::number(sentFrame.bus) + " ID: " + QString::number(sentFrame.ID, 16) + " got response to mode " + + QString::number(sentFrame.data[1], 16) + + " " + QString::number(sentFrame.data[2], 16) + " with affirmation from ID " + QString::number(id, 16) + + " on bus " + QString::number(thisFrame.bus) + "."; + gotReply = true; } + //error replies are never multiframe so the check doesn't have to be done here. } } } + if (gotReply) + { + ui->listResults->addItem(result); + sendNextMsg(); + } } } @@ -183,6 +204,7 @@ void UDSScanWindow::sendNextMsg() if (currIdx < sendingFrames.count()) { emit sendCANFrame(&sendingFrames[currIdx], sendingFrames[currIdx].bus); + waitTimer->start(); } else { From 4767a7349f041bf87cbe47a09816ab0e3b398cee Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Sun, 28 Feb 2016 16:10:55 -0500 Subject: [PATCH 11/26] Added ability for sub windows to get frames much more rapidly than before. Decoupled SerialWorker from MainWindow a bit and refactored things. --- canframemodel.cpp | 9 +++++++-- mainwindow.cpp | 34 +++++++++++++++++++++++++++------- mainwindow.h | 8 +++++++- serialworker.cpp | 23 +++++++++-------------- serialworker.h | 12 +++++------- 5 files changed, 55 insertions(+), 31 deletions(-) diff --git a/canframemodel.cpp b/canframemodel.cpp index 98385cf..dc0216d 100644 --- a/canframemodel.cpp +++ b/canframemodel.cpp @@ -6,7 +6,12 @@ int CANFrameModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); - return filteredFrames.count(); + if (filteredFrames.data()) return filteredFrames.count(); + + //just in case somehow data is invalid which I have seen before. + //But, this should not happen so issue a debugging message too + qDebug() << "Invalid data for filteredFrames. Returning 0."; + return 0; } int CANFrameModel::totalFrameCount() @@ -341,7 +346,7 @@ void CANFrameModel::sendRefresh() } } filteredFrames.clear(); - filteredFrames = tempContainer; + filteredFrames.append(tempContainer); lastUpdateNumFrames = filteredFrames.count(); endResetModel(); mutex.unlock(); diff --git a/mainwindow.cpp b/mainwindow.cpp index 71bba18..1782687 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -69,7 +69,7 @@ MainWindow::MainWindow(QWidget *parent) : connect(&serialWorkerThread, &QThread::finished, worker, &QObject::deleteLater); connect(&serialWorkerThread, &QThread::started, worker, &SerialWorker::run); //setup timers within the proper thread connect(this, &MainWindow::sendSerialPort, worker, &SerialWorker::setSerialPort, Qt::QueuedConnection); - connect(worker, &SerialWorker::frameUpdateTick, this, &MainWindow::gotFrames, Qt::QueuedConnection); + connect(worker, &SerialWorker::frameUpdateRapid, this, &MainWindow::gotFrames, Qt::QueuedConnection); connect(this, &MainWindow::updateBaudRates, worker, &SerialWorker::updateBaudRates, Qt::QueuedConnection); connect(this, &MainWindow::sendCANFrame, worker, &SerialWorker::sendFrame, Qt::QueuedConnection); connect(worker, &SerialWorker::connectionSuccess, this, &MainWindow::connectionSucceeded, Qt::QueuedConnection); @@ -101,6 +101,7 @@ MainWindow::MainWindow(QWidget *parent) : dbcHandler = new DBCHandler; bDirty = false; inhibitFilterUpdate = false; + rxFrames = 0; model->setDBCHandler(dbcHandler); @@ -150,6 +151,13 @@ MainWindow::MainWindow(QWidget *parent) : ui->lbFPS->setText("0"); ui->lbNumFrames->setText("0"); + connect(&updateTimer, &QTimer::timeout, this, &MainWindow::tickGUIUpdate); + updateTimer.setInterval(250); + updateTimer.start(); + + elapsedTime = new QTime; + elapsedTime->start(); + isConnected = false; allowCapture = true; @@ -266,6 +274,8 @@ MainWindow::~MainWindow() delete udsScanWindow; } + delete elapsedTime; + delete ui; delete dbcHandler; model->clearFrames(); @@ -493,20 +503,30 @@ void MainWindow::filterClearAll() model->setAllFilters(false); } -//most of the work is handled elsewhere. Need only to update the # of frames -//and maybe auto scroll -void MainWindow::gotFrames(int FPS, int framesSinceLastUpdate) +void MainWindow::tickGUIUpdate() { + framesPerSec += rxFrames * 1000 / elapsedTime->elapsed() - (framesPerSec / 4); + elapsedTime->restart(); + model->sendBulkRefresh(rxFrames); + ui->lbNumFrames->setText(QString::number(model->rowCount())); if (ui->cbAutoScroll->isChecked()) ui->canFramesView->scrollToBottom(); - ui->lbFPS->setText(QString::number(FPS)); - if (framesSinceLastUpdate > 0) + ui->lbFPS->setText(QString::number(framesPerSec / 4)); + if (rxFrames > 0) { bDirty = true; - emit framesUpdated(framesSinceLastUpdate); //anyone care that frames were updated? + emit framesUpdated(rxFrames); //anyone care that frames were updated? } if (model->needsFilterRefresh()) updateFilterList(); + + rxFrames = 0; +} + +void MainWindow::gotFrames(int framesSinceLastUpdate) +{ + rxFrames += framesSinceLastUpdate; + emit frameUpdateRapid(framesSinceLastUpdate); } void MainWindow::addFrameToDisplay(CANFrame &frame, bool autoRefresh = false) diff --git a/mainwindow.h b/mainwindow.h index cf99eba..bbe6416 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -75,6 +75,7 @@ private slots: void gridDoubleClicked(QModelIndex); void interpretToggled(bool); void overwriteToggled(bool); + void tickGUIUpdate(); void toggleCapture(); void normalizeTiming(); void updateFilterList(); @@ -83,7 +84,7 @@ private slots: void filterClearAll(); public slots: - void gotFrames(int, int); + void gotFrames(int); void updateSettings(); void gotCenterTimeID(int32_t ID, double timestamp); void updateConnectionSettings(QString connectionType, QString port, int speed0, int speed1); @@ -98,6 +99,7 @@ signals: //-1 = frames cleared, -2 = a new file has been loaded (so all frames are different), otherwise # of new frames void framesUpdated(int numFrames); //something has updated the frame list + void frameUpdateRapid(int numFrames); void settingsUpdated(); void sendCenterTimeID(int32_t ID, double timestamp); @@ -111,6 +113,10 @@ private: QThread serialWorkerThread; SerialWorker *worker; QByteArray inputBuffer; + QTimer updateTimer; + QTime *elapsedTime; + int framesPerSec; + int rxFrames; bool inhibitFilterUpdate; bool useHex; bool allowCapture; diff --git a/serialworker.cpp b/serialworker.cpp index d70fc6c..b36940e 100644 --- a/serialworker.cpp +++ b/serialworker.cpp @@ -12,10 +12,8 @@ SerialWorker::SerialWorker(CANFrameModel *model, QObject *parent) : QObject(pare rx_step = 0; buildFrame = new CANFrame; canModel = model; - gotFrames = 0; ticker = NULL; - elapsedTime = NULL; - framesPerSec = 0; + framesRapid = 0; capturing = true; gotValidated = true; isAutoRestart = false; @@ -45,9 +43,6 @@ void SerialWorker::run() ticker = new QTimer; connect(ticker, SIGNAL(timeout()), this, SLOT(handleTick())); - elapsedTime = new QTime; - elapsedTime->start(); - ticker->setInterval(250); //tick four times per second ticker->setSingleShot(false); //keep ticking ticker->start(); @@ -148,6 +143,11 @@ void SerialWorker::readSerialData() c = data.at(i); procRXChar(c); } + if (framesRapid > 0) + { + emit frameUpdateRapid(framesRapid); + framesRapid = 0; + } } void SerialWorker::sendFrame(const CANFrame *frame, int bus = 0) @@ -162,7 +162,7 @@ void SerialWorker::sendFrame(const CANFrame *frame, int bus = 0) //show our sent frames in the list too. This happens even if we're not connected. canModel->addFrame(tempFrame, false); - gotFrames++; + framesRapid++; if (serial == NULL) return; if (!serial->isOpen()) return; @@ -315,8 +315,8 @@ void SerialWorker::procRXChar(unsigned char c) if (capturing) { buildFrame->isReceived = true; - canModel->addFrame(*buildFrame, false); - gotFrames++; + canModel->addFrame(*buildFrame, false); + framesRapid++; if (buildFrame->ID == targetID) emit gotTargettedFrame(canModel->rowCount() - 1); } } @@ -447,11 +447,6 @@ void SerialWorker::handleTick() } } - framesPerSec += gotFrames * 1000 / elapsedTime->elapsed() - (framesPerSec / 4); - elapsedTime->restart(); - emit frameUpdateTick(framesPerSec / 4, gotFrames); //sends stats to interested parties - canModel->sendBulkRefresh(gotFrames); - gotFrames = 0; if (doValidation && serial && serial->isOpen()) sendCommValidation(); } diff --git a/serialworker.h b/serialworker.h index 2b71266..e0730f0 100644 --- a/serialworker.h +++ b/serialworker.h @@ -40,8 +40,8 @@ public: void targetFrameID(int); signals: //we emit signals - void error(const QString &); - void frameUpdateTick(int, int); //update interested parties about the # of frames that have come in + void error(const QString &); + void frameUpdateRapid(int); //sent *much* more rapidly than the above signal - one param for # of frames void connectionSuccess(int, int); void connectionFailure(); void deviceInfo(int, int); @@ -74,11 +74,9 @@ private: QSerialPort *serial; QSerialPortInfo *currentPort; CANFrameModel *canModel; - QTimer *ticker; - QTime *elapsedTime; - QMutex sendBulkMutex; - int framesPerSec; - int gotFrames; + QTimer *ticker; + QMutex sendBulkMutex; + int framesRapid; int targetID; STATE rx_state; int rx_step; From e2cedf2482eef91f31155018e4a523c523834500 Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Sun, 28 Feb 2016 16:56:04 -0500 Subject: [PATCH 12/26] A small fix to make transmitted frame timestamps a bit more accurate but still not synced to capture device. --- serialworker.cpp | 3 +++ serialworker.h | 1 + 2 files changed, 4 insertions(+) diff --git a/serialworker.cpp b/serialworker.cpp index b36940e..33ea8db 100644 --- a/serialworker.cpp +++ b/serialworker.cpp @@ -19,6 +19,8 @@ SerialWorker::SerialWorker(CANFrameModel *model, QObject *parent) : QObject(pare isAutoRestart = false; targetID = -1; + txTimestampBasis = QDateTime::currentMSecsSinceEpoch(); + readSettings(); } @@ -157,6 +159,7 @@ void SerialWorker::sendFrame(const CANFrame *frame, int bus = 0) int ID; CANFrame tempFrame = *frame; tempFrame.isReceived = false; + tempFrame.timestamp = ((QDateTime::currentMSecsSinceEpoch() - txTimestampBasis) * 1000); //qDebug() << "Sending out frame with id " << frame->ID; diff --git a/serialworker.h b/serialworker.h index e0730f0..8aea384 100644 --- a/serialworker.h +++ b/serialworker.h @@ -85,6 +85,7 @@ private: bool can0Enabled, can1Enabled; int deviceBuildNum; int deviceSingleWireMode; + uint64_t txTimestampBasis; void procRXChar(unsigned char); void sendCommValidation(); From 18bbd8632f00475d1920dfbc28336b9903b5248e Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Sun, 28 Feb 2016 16:56:39 -0500 Subject: [PATCH 13/26] Changed UDS window to work more quickly and use the new rapid frame system. --- udsscanwindow.cpp | 113 ++++++++++++++++++++++++---------------------- udsscanwindow.h | 1 + 2 files changed, 61 insertions(+), 53 deletions(-) diff --git a/udsscanwindow.cpp b/udsscanwindow.cpp index a64f423..7398477 100644 --- a/udsscanwindow.cpp +++ b/udsscanwindow.cpp @@ -13,9 +13,10 @@ UDSScanWindow::UDSScanWindow(const QVector *frames, QWidget *parent) : currentlyRunning = false; waitTimer = new QTimer; - waitTimer->setInterval(300); + waitTimer->setInterval(100); connect(MainWindow::getReference(), SIGNAL(framesUpdated(int)), this, SLOT(updatedFrames(int))); + connect(MainWindow::getReference(), SIGNAL(frameUpdateRapid(int)), this, SLOT(rapidFrames(int))); connect(ui->btnScan, &QPushButton::clicked, this, &UDSScanWindow::scanUDS); connect(waitTimer, &QTimer::timeout, this, &UDSScanWindow::timeOut); @@ -56,7 +57,7 @@ void UDSScanWindow::scanUDS() if (buses < 1) buses = 1; //start out by sending tester present to every address to see if anyone replies - for (id = startID; id < endID; id++) + for (id = startID; id <= endID; id++) { frame.ID = id; frame.len = 8; @@ -113,15 +114,9 @@ void UDSScanWindow::scanUDS() sendNextMsg(); } +//Updates here are sent about every 1/4 second. That's fine for most windows but not this one. void UDSScanWindow::updatedFrames(int numFrames) { - CANFrame thisFrame; - QString result; - int id; - int offset = ui->spinReplyOffset->value(); - CANFrame sentFrame; - bool gotReply = false; - if (numFrames == -1) //all frames deleted. We don't care { } @@ -130,61 +125,73 @@ void UDSScanWindow::updatedFrames(int numFrames) } else //just got some new frames. See if they are relevant. { - if (numFrames > modelFrames->count()) return; + } +} - int numSending = sendingFrames.length(); - if (numSending == 0) return; +//Updates here are nearly once per millisecond if there is heavy traffic. That's more like it! +void UDSScanWindow::rapidFrames(int numFrames) +{ + CANFrame thisFrame; + QString result; + int id; + int offset = ui->spinReplyOffset->value(); + CANFrame sentFrame; + bool gotReply = false; + + if (numFrames > modelFrames->count()) return; + + int numSending = sendingFrames.length(); + if (numSending == 0) return; + if (currIdx >= numSending) return; + sentFrame = sendingFrames[currIdx]; + + for (int i = modelFrames->count() - numFrames; i < modelFrames->count(); i++) + { if (currIdx >= numSending) return; - sentFrame = sendingFrames[currIdx]; + thisFrame = modelFrames->at(i); + id = thisFrame.ID; - for (int i = modelFrames->count() - numFrames; i < modelFrames->count(); i++) + if ((id == (sentFrame.ID + offset)) || ui->cbAllowAdaptiveOffset->isChecked()) { - if (currIdx >= numSending) return; - thisFrame = modelFrames->at(i); - id = thisFrame.ID; - - if ((id == (sentFrame.ID + offset)) || ui->cbAllowAdaptiveOffset->isChecked()) + int temp = thisFrame.data[0] >> 4; + if (temp == 0) //single frame reply (maybe) { - int temp = thisFrame.data[0] >> 4; - if (temp == 0) //single frame reply (maybe) + if (thisFrame.data[1] == 0x40 + sendingFrames[currIdx].data[1]) { - if (thisFrame.data[1] == 0x40 + sendingFrames[currIdx].data[1]) - { - result = "Request on bus " + QString::number(sentFrame.bus) + " ID: " + QString::number(sentFrame.ID, 16) + " got response to mode " - + QString::number(sentFrame.data[1], 16) - + " " + QString::number(sentFrame.data[2], 16) + " with affirmation from ID " + QString::number(id, 16) - + " on bus " + QString::number(thisFrame.bus) + "."; - gotReply = true; - } - else if ( thisFrame.data[1] == 0x7F) - { - result = "Request on bus " + QString::number(sentFrame.bus) + " ID: " + QString::number(sentFrame.ID, 16) + " got response to mode " - + QString::number(sentFrame.data[1], 16) - + " " + QString::number(sentFrame.data[2], 16) + " with an error from ID " + QString::number(id, 16) - + " on bus " + QString::number(thisFrame.bus) + "."; - gotReply = true; - } + result = "Request on bus " + QString::number(sentFrame.bus) + " ID: " + QString::number(sentFrame.ID, 16) + " got response to mode " + + QString::number(sentFrame.data[1], 16) + + " " + QString::number(sentFrame.data[2], 16) + " with affirmation from ID " + QString::number(id, 16) + + " on bus " + QString::number(thisFrame.bus) + "."; + gotReply = true; } - - if (temp == 1) //start of a multiframe reply + else if ( thisFrame.data[1] == 0x7F) { - if (thisFrame.data[2] == 0x40 + sendingFrames[currIdx].data[1]) - { - result = "Request on bus " + QString::number(sentFrame.bus) + " ID: " + QString::number(sentFrame.ID, 16) + " got response to mode " - + QString::number(sentFrame.data[1], 16) - + " " + QString::number(sentFrame.data[2], 16) + " with affirmation from ID " + QString::number(id, 16) - + " on bus " + QString::number(thisFrame.bus) + "."; - gotReply = true; - } - //error replies are never multiframe so the check doesn't have to be done here. + result = "Request on bus " + QString::number(sentFrame.bus) + " ID: " + QString::number(sentFrame.ID, 16) + " got response to mode " + + QString::number(sentFrame.data[1], 16) + + " " + QString::number(sentFrame.data[2], 16) + " with an error from ID " + QString::number(id, 16) + + " on bus " + QString::number(thisFrame.bus) + "."; + gotReply = true; } } + + if (temp == 1) //start of a multiframe reply + { + if (thisFrame.data[2] == 0x40 + sendingFrames[currIdx].data[1]) + { + result = "Request on bus " + QString::number(sentFrame.bus) + " ID: " + QString::number(sentFrame.ID, 16) + " got response to mode " + + QString::number(sentFrame.data[1], 16) + + " " + QString::number(sentFrame.data[2], 16) + " with affirmation from ID " + QString::number(id, 16) + + " on bus " + QString::number(thisFrame.bus) + "."; + gotReply = true; + } + //error replies are never multiframe so the check doesn't have to be done here. + } } - if (gotReply) - { - ui->listResults->addItem(result); - sendNextMsg(); - } + } + if (gotReply) + { + ui->listResults->addItem(result); + sendNextMsg(); } } diff --git a/udsscanwindow.h b/udsscanwindow.h index 0b17737..1066198 100644 --- a/udsscanwindow.h +++ b/udsscanwindow.h @@ -22,6 +22,7 @@ signals: private slots: void updatedFrames(int numFrames); + void rapidFrames(int numFrames); void scanUDS(); void timeOut(); From f6fe53163c9b68a048c953ca918034d461c53d3e Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Sun, 28 Feb 2016 19:50:56 -0500 Subject: [PATCH 14/26] Added code to sync PC and GVRET timing so that frames sent from SavvyCAN line up properly in timestamp. Incremented version num. --- config.h | 2 +- serialworker.cpp | 40 ++++++++++++++++++++++++++++++++++------ serialworker.h | 2 ++ 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/config.h b/config.h index 8030d77..08e40d0 100644 --- a/config.h +++ b/config.h @@ -1,7 +1,7 @@ #ifndef CONFIG #define CONFIG -#define VERSION 151 +#define VERSION 152 //try to keep this in sync. //SavvyCAN will complain if you connect a GVRET board with a revision diff --git a/serialworker.cpp b/serialworker.cpp index 33ea8db..61a0b5f 100644 --- a/serialworker.cpp +++ b/serialworker.cpp @@ -115,6 +115,11 @@ void SerialWorker::setSerialPort(QSerialPortInfo *port) output.append((char)0xF1); //yet another command output.append((char)0x09); //comm validation command + output.append((char)0xF1); //and another command + output.append((char)0x01); //Time Sync - Not implemented until 333 but we can try + + continuousTimeSync = true; + serial->write(output); if (doValidation) connected = false; else connected = true; @@ -236,8 +241,9 @@ void SerialWorker::procRXChar(unsigned char c) rx_state = BUILD_CAN_FRAME; rx_step = 0; break; - case 1: //we don't accept time sync commands from the firmware - rx_state = IDLE; + case 1: //time sync + rx_state = TIME_SYNC; + rx_step = 0; break; case 2: //process a return reply for digital input states. rx_state = GET_DIG_INPUTS; @@ -318,7 +324,9 @@ void SerialWorker::procRXChar(unsigned char c) if (capturing) { buildFrame->isReceived = true; - canModel->addFrame(*buildFrame, false); + canModel->addFrame(*buildFrame, false); + //take the time the frame came in and try to resync the time base. + if (continuousTimeSync) txTimestampBasis = QDateTime::currentMSecsSinceEpoch() - (buildFrame->timestamp / 1000); framesRapid++; if (buildFrame->ID == targetID) emit gotTargettedFrame(canModel->rowCount() - 1); } @@ -327,6 +335,29 @@ void SerialWorker::procRXChar(unsigned char c) } rx_step++; break; + case TIME_SYNC: //gives a pretty good base guess for the proper timestamp. Can be refined when traffic starts to flow (if wanted) + switch (rx_step) + { + case 0: + buildTimeBasis = c; + break; + case 1: + buildTimeBasis += ((uint32_t)c << 8); + break; + case 2: + buildTimeBasis += ((uint32_t)c << 16); + break; + case 3: + buildTimeBasis += ((uint32_t)c << 24); + qDebug() << "GVRET firmware reports timestamp of " << buildTimeBasis; + txTimestampBasis = QDateTime::currentMSecsSinceEpoch() - ((uint64_t)buildTimeBasis / (uint64_t)1000ull); + continuousTimeSync = false; + rx_state = IDLE; + break; + } + rx_step++; + break; + case GET_ANALOG_INPUTS: //get 9 bytes - 2 per analog input plus checksum switch (rx_step) { @@ -414,9 +445,6 @@ void SerialWorker::procRXChar(unsigned char c) } rx_step++; break; - case TIME_SYNC: - rx_state = IDLE; - break; case SET_DIG_OUTPUTS: rx_state = IDLE; break; diff --git a/serialworker.h b/serialworker.h index 8aea384..1658c04 100644 --- a/serialworker.h +++ b/serialworker.h @@ -71,6 +71,7 @@ private: bool doValidation; bool gotValidated; bool isAutoRestart; + bool continuousTimeSync; QSerialPort *serial; QSerialPortInfo *currentPort; CANFrameModel *canModel; @@ -86,6 +87,7 @@ private: int deviceBuildNum; int deviceSingleWireMode; uint64_t txTimestampBasis; + uint32_t buildTimeBasis; void procRXChar(unsigned char); void sendCommValidation(); From 66675e47b314fddf13975687d02beeb32f004338 Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Sun, 6 Mar 2016 20:44:25 -0500 Subject: [PATCH 15/26] Added an ISO-TP decoder. At the moment it tries to decode all frames and that leads to odd results. --- SavvyCAN.pro | 11 ++- can_structs.h | 2 +- isotp_decoder.cpp | 160 ++++++++++++++++++++++++++++++++++++ isotp_decoder.h | 31 +++++++ isotp_interpreterwindow.cpp | 97 ++++++++++++++++++++++ isotp_interpreterwindow.h | 36 ++++++++ isotp_interpreterwindow.ui | 48 +++++++++++ mainwindow.cpp | 21 +++++ mainwindow.h | 3 + mainwindow.ui | 6 ++ udsscanwindow.ui | 2 +- 11 files changed, 412 insertions(+), 5 deletions(-) create mode 100644 isotp_decoder.cpp create mode 100644 isotp_decoder.h create mode 100644 isotp_interpreterwindow.cpp create mode 100644 isotp_interpreterwindow.h create mode 100644 isotp_interpreterwindow.ui diff --git a/SavvyCAN.pro b/SavvyCAN.pro index 8d64e4d..acc556d 100644 --- a/SavvyCAN.pro +++ b/SavvyCAN.pro @@ -46,7 +46,9 @@ SOURCES += main.cpp\ dbcloadsavewindow.cpp \ fuzzingwindow.cpp \ udsscanwindow.cpp \ - can_structs.cpp + can_structs.cpp \ + isotp_interpreterwindow.cpp \ + isotp_decoder.cpp HEADERS += mainwindow.h \ can_structs.h \ @@ -79,7 +81,9 @@ HEADERS += mainwindow.h \ rangestatewindow.h \ dbcloadsavewindow.h \ fuzzingwindow.h \ - udsscanwindow.h + udsscanwindow.h \ + isotp_interpreterwindow.h \ + isotp_decoder.h FORMS += mainwindow.ui \ graphingwindow.ui \ @@ -100,7 +104,8 @@ FORMS += mainwindow.ui \ rangestatewindow.ui \ dbcloadsavewindow.ui \ fuzzingwindow.ui \ - udsscanwindow.ui + udsscanwindow.ui \ + isotp_interpreterwindow.ui DISTFILES += diff --git a/can_structs.h b/can_structs.h index e9668bd..7a4fc6e 100644 --- a/can_structs.h +++ b/can_structs.h @@ -29,7 +29,7 @@ public: }; //the same as the CANFrame struct but with arbitrary data size. -struct ISOTP_FRAME +struct ISOTP_MESSAGE { public: int ID; diff --git a/isotp_decoder.cpp b/isotp_decoder.cpp new file mode 100644 index 0000000..93e1ec3 --- /dev/null +++ b/isotp_decoder.cpp @@ -0,0 +1,160 @@ +#include "isotp_decoder.h" + +ISOTP_DECODER::ISOTP_DECODER(const QVector *frames, QObject *parent) + : QObject(parent) +{ + modelFrames = frames; + useExtendedAddressing = false; +} + +void ISOTP_DECODER::setExtendedAddressing(bool mode) +{ + useExtendedAddressing = mode; +} + +//remember, negative numbers are special -1 = all frames deleted, -2 = totally new set of frames. +void ISOTP_DECODER::updatedFrames(int numFrames) +{ + if (numFrames == -1) //all frames deleted. Kill the display + { + } + else if (numFrames == -2) //all new set of frames. Reset + { + for (int i = 0; i < modelFrames->length(); i++) processFrame(modelFrames->at(i)); + } + else //just got some new frames. See if they are relevant. + { + for (int i = modelFrames->count() - numFrames; i < modelFrames->count(); i++) + { + processFrame(modelFrames->at(i)); + } + } +} + +void ISOTP_DECODER::processFrame(const CANFrame &frame) +{ + uint64_t ID = frame.ID; + int frameType; + int frameLen; + int ln; + int offset; + ISOTP_MESSAGE msg; + ISOTP_MESSAGE *pMsg; + + frameType = 0; + frameLen = 0; + + if (useExtendedAddressing) + { + ID = ID << 8; + ID += frame.data[0]; + frameType = frame.data[1] >> 4; + frameLen = frame.data[1] & 0xF; + } + else + { + frameType = frame.data[0] >> 4; + frameLen = frame.data[0] & 0xF; + } + + switch(frameType) + { + case 0: //single frame message + checkNeedFlush(ID); + + if (frameLen == 0) return; //length of zero isn't valid. + if (frameLen > 6 && useExtendedAddressing) return; //impossible + if (frameLen > 7) return; + + msg.bus = frame.bus; + msg.extended = frame.extended; + msg.ID = ID; + msg.isReceived = frame.isReceived; + msg.len = frameLen; + msg.data.reserve(frameLen); + msg.timestamp = frame.timestamp; + if (useExtendedAddressing) for (int j = 0; j < frameLen; j++) msg.data.append(frame.data[j+2]); + else for (int j = 0; j < frameLen; j++) msg.data.append(frame.data[j+1]); + qDebug() << "Emitting single frame ISOTP message"; + emit newISOMessage(msg); + break; + case 1: //first frame of a multi-frame message + checkNeedFlush(ID); + msg.bus = frame.bus; + msg.extended = frame.extended; + msg.ID = ID; + msg.timestamp = frame.timestamp; + msg.isReceived = frame.isReceived; + frameLen = frameLen << 8; + if (useExtendedAddressing) + { + frameLen += frame.data[2]; + frameLen = frameLen & 0xFFF; + msg.len = frameLen; + msg.data.reserve(frameLen); + for (int j = 0; j < 5; j++) msg.data.append(frame.data[3 + j]); + } + else + { + frameLen += frame.data[1]; + frameLen = frameLen & 0xFFF; + msg.len = frameLen; + msg.data.reserve(frameLen); + for (int j = 0; j < 6; j++) msg.data.append(frame.data[2 + j]); + } + messageBuffer.append(msg); + break; + case 2: //subsequent frames for multi-frame messages + pMsg = NULL; + for (int i = 0; i < messageBuffer.length(); i++) + { + if (messageBuffer[i].ID == ID) + { + pMsg = &messageBuffer[i]; + break; + } + } + if (!pMsg) return; + ln = pMsg->len - pMsg->data.count(); + offset = pMsg->data.count(); + if (useExtendedAddressing) + { + if (ln > 6) ln = 6; + for (int j = 0; j < ln; j++) pMsg->data.append(frame.data[j+2]); + } + else + { + if (ln > 7) ln = 7; + for (int j = 0; j < ln; j++) pMsg->data.append(frame.data[j+1]); + } + if (pMsg->len <= pMsg->data.count()) + { + qDebug() << "Emitting multiframe ISOTP message"; + emit newISOMessage(*pMsg); + } + break; + case 3: //flow control messages -ignored for now + break; + } +} + +void ISOTP_DECODER::checkNeedFlush(uint64_t ID) +{ + for (int i = 0; i < messageBuffer.length(); i++) + { + if (messageBuffer[i].ID == ID) + { + //warning... this code will work for direct signals as emit turns into a function call + //and thus the other side will have time to do its processing before control returns + //and we delete the message on our side. But, if this code were used cross thread the + //emit would be a queued message instead and control would immediately return + //here and then the message would be deleted before being delivered. + //To fix that the message would have to be passed by value instead which I'd like to avoid. + //Thus, don't use this across threads unless you like to debug strange issues. + qDebug() << "Flushing a partial frame"; + emit newISOMessage(messageBuffer[i]); + messageBuffer.removeAt(i); + return; + } + } +} diff --git a/isotp_decoder.h b/isotp_decoder.h new file mode 100644 index 0000000..51d130c --- /dev/null +++ b/isotp_decoder.h @@ -0,0 +1,31 @@ +#ifndef ISOTP_DECODER_H +#define ISOTP_DECODER_H + +#include +#include +#include "can_structs.h" + +class ISOTP_DECODER : public QObject +{ + Q_OBJECT + +public: + explicit ISOTP_DECODER(const QVector *frames, QObject *parent = 0); + void setExtendedAddressing(bool mode); + +public slots: + void updatedFrames(int); + +signals: + void newISOMessage(ISOTP_MESSAGE &msg); + +private: + QList messageBuffer; + const QVector *modelFrames; + bool useExtendedAddressing; + + void processFrame(const CANFrame &frame); + void checkNeedFlush(uint64_t ID); +}; + +#endif // ISOTP_DECODER_H diff --git a/isotp_interpreterwindow.cpp b/isotp_interpreterwindow.cpp new file mode 100644 index 0000000..14a4cd8 --- /dev/null +++ b/isotp_interpreterwindow.cpp @@ -0,0 +1,97 @@ +#include "isotp_interpreterwindow.h" +#include "ui_isotp_interpreterwindow.h" +#include "mainwindow.h" + +ISOTP_InterpreterWindow::ISOTP_InterpreterWindow(const QVector *frames, QWidget *parent) : + QDialog(parent), + ui(new Ui::ISOTP_InterpreterWindow) +{ + ui->setupUi(this); + modelFrames = frames; + + decoder = new ISOTP_DECODER(modelFrames); + + connect(MainWindow::getReference(), &MainWindow::framesUpdated, decoder, &ISOTP_DECODER::updatedFrames); + connect(decoder, &ISOTP_DECODER::newISOMessage, this, &ISOTP_InterpreterWindow::newISOMessage); + + QStringList headers; + headers << "Timestamp" << "ID" << "Bus" << "Dir" << "Length" << "Data"; + ui->tableIsoFrames->setColumnCount(6); + ui->tableIsoFrames->setColumnWidth(0, 100); + ui->tableIsoFrames->setColumnWidth(1, 50); + ui->tableIsoFrames->setColumnWidth(2, 50); + ui->tableIsoFrames->setColumnWidth(3, 50); + ui->tableIsoFrames->setColumnWidth(4, 75); + ui->tableIsoFrames->setColumnWidth(5, 200); + ui->tableIsoFrames->setHorizontalHeaderLabels(headers); + QHeaderView *HorzHdr = ui->tableIsoFrames->horizontalHeader(); + HorzHdr->setStretchLastSection(true); +} + +ISOTP_InterpreterWindow::~ISOTP_InterpreterWindow() +{ + delete decoder; + delete ui; +} + +void ISOTP_InterpreterWindow::showEvent(QShowEvent* event) +{ + QDialog::showEvent(event); + readSettings(); + decoder->updatedFrames(-2); +} + +void ISOTP_InterpreterWindow::closeEvent(QCloseEvent *event) +{ + Q_UNUSED(event); + writeSettings(); +} + +void ISOTP_InterpreterWindow::readSettings() +{ + QSettings settings; + if (settings.value("Main/SaveRestorePositions", false).toBool()) + { + resize(settings.value("ISODecodeWindow/WindowSize", this->size()).toSize()); + move(settings.value("ISODecodeWindow/WindowPos", QPoint(50, 50)).toPoint()); + } +} + +void ISOTP_InterpreterWindow::writeSettings() +{ + QSettings settings; + + if (settings.value("Main/SaveRestorePositions", false).toBool()) + { + settings.setValue("ISODecodeWindow/WindowSize", size()); + settings.setValue("ISODecodeWindow/WindowPos", pos()); + } +} + +void ISOTP_InterpreterWindow::newISOMessage(ISOTP_MESSAGE &msg) +{ + int rowNum; + QString tempString; + + messages.append(msg); + + rowNum = ui->tableIsoFrames->rowCount(); + ui->tableIsoFrames->insertRow(rowNum); + + ui->tableIsoFrames->setItem(rowNum, 0, new QTableWidgetItem(QString::number(msg.timestamp))); + ui->tableIsoFrames->setItem(rowNum, 1, new QTableWidgetItem(QString::number(msg.ID, 16))); + ui->tableIsoFrames->setItem(rowNum, 2, new QTableWidgetItem(QString::number(msg.bus))); + if (msg.isReceived) ui->tableIsoFrames->setItem(rowNum, 3, new QTableWidgetItem("Rx")); + else ui->tableIsoFrames->setItem(rowNum, 3, new QTableWidgetItem("Tx")); + ui->tableIsoFrames->setItem(rowNum, 4, new QTableWidgetItem(QString::number(msg.len))); + + for (int i = 0; i < msg.data.length(); i++) + { + tempString.append(Utility::formatNumber(msg.data[i])); + tempString.append(" "); + } + ui->tableIsoFrames->setItem(rowNum, 5, new QTableWidgetItem(tempString)); + +} + + diff --git a/isotp_interpreterwindow.h b/isotp_interpreterwindow.h new file mode 100644 index 0000000..a8992ba --- /dev/null +++ b/isotp_interpreterwindow.h @@ -0,0 +1,36 @@ +#ifndef ISOTP_INTERPRETERWINDOW_H +#define ISOTP_INTERPRETERWINDOW_H + +#include +#include "can_structs.h" +#include "isotp_decoder.h" + +namespace Ui { +class ISOTP_InterpreterWindow; +} + +class ISOTP_InterpreterWindow : public QDialog +{ + Q_OBJECT + +public: + explicit ISOTP_InterpreterWindow(const QVector *frames, QWidget *parent = 0); + ~ISOTP_InterpreterWindow(); + void showEvent(QShowEvent*); + +private slots: + void newISOMessage(ISOTP_MESSAGE &msg); + +private: + Ui::ISOTP_InterpreterWindow *ui; + ISOTP_DECODER *decoder; + + const QVector *modelFrames; + QVector messages; + + void closeEvent(QCloseEvent *event); + void readSettings(); + void writeSettings(); +}; + +#endif // ISOTP_INTERPRETERWINDOW_H diff --git a/isotp_interpreterwindow.ui b/isotp_interpreterwindow.ui new file mode 100644 index 0000000..eb92d78 --- /dev/null +++ b/isotp_interpreterwindow.ui @@ -0,0 +1,48 @@ + + + ISOTP_InterpreterWindow + + + + 0 + 0 + 792 + 571 + + + + ISO-TP Decoder + + + + + + Interpreted Messages Overview + + + + + + + + + + Data Details of Selected Message + + + + + + + + + + Interpret Data as UDS + + + + + + + + diff --git a/mainwindow.cpp b/mainwindow.cpp index 1782687..5426539 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -98,6 +98,7 @@ MainWindow::MainWindow(QWidget *parent) : dbcFileWindow = NULL; fuzzingWindow = NULL; udsScanWindow = NULL; + isoWindow = NULL; dbcHandler = new DBCHandler; bDirty = false; inhibitFilterUpdate = false; @@ -140,6 +141,7 @@ MainWindow::MainWindow(QWidget *parent) : connect(ui->actionDBC_File_Manager, &QAction::triggered, this, &MainWindow::showDBCFileWindow); connect(ui->actionFuzzing, &QAction::triggered, this, &MainWindow::showFuzzingWindow); connect(ui->actionUDS_Scanner, &QAction::triggered, this, &MainWindow::showUDSScanWindow); + connect(ui->actionISO_TP_Decoder, &QAction::triggered, this, &MainWindow::showISOInterpreterWindow); lbStatusConnected.setText(tr("Not connected")); updateFileStatus(); @@ -274,6 +276,12 @@ MainWindow::~MainWindow() delete udsScanWindow; } + if (isoWindow) + { + isoWindow->close(); + delete isoWindow; + } + delete elapsedTime; delete ui; @@ -299,6 +307,7 @@ void MainWindow::exitApp() if (dbcFileWindow) dbcFileWindow->close(); if (fuzzingWindow) fuzzingWindow->close(); if (udsScanWindow) udsScanWindow->close(); + if (isoWindow) isoWindow->close(); this->close(); } @@ -884,6 +893,18 @@ void MainWindow::showFrameDataAnalysis() frameInfoWindow->show(); } +void MainWindow::showISOInterpreterWindow() +{ + if (!isoWindow) + { + if (!useFiltered) + isoWindow = new ISOTP_InterpreterWindow(model->getListReference()); + else + isoWindow = new ISOTP_InterpreterWindow(model->getFilteredListReference()); + } + isoWindow->show(); +} + void MainWindow::showFrameSenderWindow() { if (!frameSenderWindow) diff --git a/mainwindow.h b/mainwindow.h index bbe6416..fc8115f 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -25,6 +25,7 @@ #include "dbcloadsavewindow.h" #include "fuzzingwindow.h" #include "udsscanwindow.h" +#include "isotp_interpreterwindow.h" namespace Ui { class MainWindow; @@ -65,6 +66,7 @@ private slots: void showDBCFileWindow(); void showFuzzingWindow(); void showUDSScanWindow(); + void showISOInterpreterWindow(); void exitApp(); void handleSaveDecoded(); void changeBaudRates(); @@ -141,6 +143,7 @@ private: DBCLoadSaveWindow *dbcFileWindow; FuzzingWindow *fuzzingWindow; UDSScanWindow *udsScanWindow; + ISOTP_InterpreterWindow *isoWindow; //various private storage QLabel lbStatusConnected; diff --git a/mainwindow.ui b/mainwindow.ui index 4e7a167..fafd407 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -210,6 +210,7 @@ + @@ -387,6 +388,11 @@ UDS Scanner + + + ISO-TP Decoder + + diff --git a/udsscanwindow.ui b/udsscanwindow.ui index f0e1c2a..0c157bf 100644 --- a/udsscanwindow.ui +++ b/udsscanwindow.ui @@ -11,7 +11,7 @@ - Dialog + UDS Scanner From 8a886ddcc905a18cf9c3a4f123555d6201e1ebf9 Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Mon, 14 Mar 2016 21:57:43 -0400 Subject: [PATCH 16/26] More implementation of iso-tp interpreter. Mostly just defines. --- can_structs.h | 3 +- isotp_decoder.cpp | 31 ++++++++++++ isotp_decoder.h | 44 +++++++++++++++++ isotp_interpreterwindow.cpp | 59 +++++++++++++++++++++- isotp_interpreterwindow.h | 2 + isotp_interpreterwindow.ui | 97 ++++++++++++++++++++++++++++++++----- 6 files changed, 223 insertions(+), 13 deletions(-) diff --git a/can_structs.h b/can_structs.h index 7a4fc6e..0326947 100644 --- a/can_structs.h +++ b/can_structs.h @@ -36,7 +36,8 @@ public: int bus; bool extended; bool isReceived; - int len; + int len; //# of bytes this message should have (as reported) + int actualSize; //# we actually got QVector data; uint64_t timestamp; }; diff --git a/isotp_decoder.cpp b/isotp_decoder.cpp index 93e1ec3..08d0a4d 100644 --- a/isotp_decoder.cpp +++ b/isotp_decoder.cpp @@ -1,5 +1,36 @@ #include "isotp_decoder.h" +//in order descriptions based on the list above. But, just in order one after the other +QString UDS_SERVICE_DESCRIPT[] = +{ + "Diagnostic session control", + "Reset ECU", + "Clear diagnostic trouble codes", + "Read diagnostic trouble codes", + "Read data by ID", + "Read data by address", + "Read scaling data by ID", + "Request security access", + "Communication control", + "Read data by ID periodically", + "Create dynamic data ID", + "Write data by ID", + "Input/Output control (force)", + "Call a service routine", + "Request data download (from client to server)", + "Request data upload (from server to client)", + "Transfer data", + "Request that data transfer cease", + "Request file transfer", + "Write data by address", + "Tester is present", + "Read or write comm timing parameters", + "Secured data transmission", + "Control DTC settings", + "Request start/stop transmission on event", + "Control comm link" +}; + ISOTP_DECODER::ISOTP_DECODER(const QVector *frames, QObject *parent) : QObject(parent) { diff --git a/isotp_decoder.h b/isotp_decoder.h index 51d130c..a170f36 100644 --- a/isotp_decoder.h +++ b/isotp_decoder.h @@ -5,6 +5,50 @@ #include #include "can_structs.h" +enum OBDII_FUNCTS +{ + UDS_OBDII_SHOW_CURRENT = 1, + UDS_OBDII_SHOW_FREEZE = 2, + UDS_OBDII_SHOW_STORED_DTC = 3, + UDS_OBDII_CLEAR_DTC = 4, + UDS_OBDII_TEST_O2 = 5, + UDS_OBDII_TEST_RESULTS = 6, + UDS_OBDII_SHOW_PENDING_DTC = 7, + UDS_OBDII_CONTROL_DEVICES = 8, + UDS_OBDII_VEH_INFO = 9, + UDS_OBDII_PERM_DTC = 0xA +}; + +enum UDS_FUNCTS +{ + UDS_DIAG_CONTROL=0x10, + UDS_ECU_RESET=0x11, + UDS_CLEAR_DIAG=0x14, + UDS_READ_DTC=0x19, + UDS_READ_BY_ID=0x22, + UDS_READ_BY_ADDR=0x23, + UDS_READ_SCALING_ID=0x24, + UDS_SECURITY_ACCESS=0x27, + UDS_COMM_CTRL=0x28, + UDS_READ_DATA_ID_PERIODIC=0x2A, + UDS_DYNAMIC_DATA_DEFINE=0x2C, + UDS_WRITE_BY_ID=0x2E, + UDS_IO_CTRL=0x2F, + UDS_ROUTINE_CTRL=0x31, + UDS_REQUEST_DOWNLOAD=0x34, + UDS_REQUEST_UPLOAD=0x35, + UDS_TRANSFER_DATA=0x36, + UDS_REQ_TRANS_EXIT=0x37, + UDS_REQ_FILE_TRANS=0x38, + UDS_WRITE_BY_ADDR=0x3D, + UDS_TESTER_PRESENT=0x3E, + UDS_ACCESS_TIMING=0x83, + UDS_SECURED_DATA_TRANS=0x84, + UDS_CTRL_DTC_SETTINGS=0x85, + UDS_RESPONSE_ON_EVENT=0x86, + UDS_RESPONSE_LINK_CTRL=0x87 +}; + class ISOTP_DECODER : public QObject { Q_OBJECT diff --git a/isotp_interpreterwindow.cpp b/isotp_interpreterwindow.cpp index 14a4cd8..6d3c95e 100644 --- a/isotp_interpreterwindow.cpp +++ b/isotp_interpreterwindow.cpp @@ -11,9 +11,12 @@ ISOTP_InterpreterWindow::ISOTP_InterpreterWindow(const QVector *frames decoder = new ISOTP_DECODER(modelFrames); + connect(MainWindow::getReference(), &MainWindow::framesUpdated, this, &ISOTP_InterpreterWindow::updatedFrames); connect(MainWindow::getReference(), &MainWindow::framesUpdated, decoder, &ISOTP_DECODER::updatedFrames); connect(decoder, &ISOTP_DECODER::newISOMessage, this, &ISOTP_InterpreterWindow::newISOMessage); + connect(ui->tableIsoFrames, &QTableWidget::itemSelectionChanged, this, &ISOTP_InterpreterWindow::showDetailView); + QStringList headers; headers << "Timestamp" << "ID" << "Bus" << "Dir" << "Length" << "Data"; ui->tableIsoFrames->setColumnCount(6); @@ -68,11 +71,65 @@ void ISOTP_InterpreterWindow::writeSettings() } } +void ISOTP_InterpreterWindow::updatedFrames(int numFrames) +{ + if (numFrames == -1) //all frames deleted. Kill the display + { + messages.clear(); + ui->tableIsoFrames->clear(); + } + else if (numFrames == -2) //all new set of frames. Reset + { + messages.clear(); + ui->tableIsoFrames->clear(); + } + else //just got some new frames. See if they are relevant. + { + } +} + +void ISOTP_InterpreterWindow::showDetailView() +{ + QString buildString; + ISOTP_MESSAGE *msg; + int rowNum = ui->tableIsoFrames->currentRow(); + + ui->txtFrameDetails->clear(); + if (rowNum == -1) return; + + msg = &messages[rowNum]; + + if (msg->len != msg->data.length()) + { + buildString.append("Message didn't have the correct number of bytes.\rExpected " + + QString::number(msg->len) + " got " + + QString::number(msg->data.length()) + "\r\r"); + } + + buildString.append(tr("Raw Payload: ")); + for (int i = 0; i < messages[rowNum].data.count(); i++) + { + buildString.append(Utility::formatNumber(messages[rowNum].data[i])); + buildString.append(" "); + } + buildString.append("\r\r"); + + //if (ui->cb->isChecked()) + //{ + + //} + + ui->txtFrameDetails->setText(buildString); + +} + void ISOTP_InterpreterWindow::newISOMessage(ISOTP_MESSAGE &msg) { int rowNum; QString tempString; + if ((msg.len != msg.data.count()) && !ui->cbShowIncomplete->isChecked()) return; + messages.append(msg); rowNum = ui->tableIsoFrames->rowCount(); @@ -85,7 +142,7 @@ void ISOTP_InterpreterWindow::newISOMessage(ISOTP_MESSAGE &msg) else ui->tableIsoFrames->setItem(rowNum, 3, new QTableWidgetItem("Tx")); ui->tableIsoFrames->setItem(rowNum, 4, new QTableWidgetItem(QString::number(msg.len))); - for (int i = 0; i < msg.data.length(); i++) + for (int i = 0; i < msg.data.count(); i++) { tempString.append(Utility::formatNumber(msg.data[i])); tempString.append(" "); diff --git a/isotp_interpreterwindow.h b/isotp_interpreterwindow.h index a8992ba..3818e76 100644 --- a/isotp_interpreterwindow.h +++ b/isotp_interpreterwindow.h @@ -20,6 +20,8 @@ public: private slots: void newISOMessage(ISOTP_MESSAGE &msg); + void showDetailView(); + void updatedFrames(int); private: Ui::ISOTP_InterpreterWindow *ui; diff --git a/isotp_interpreterwindow.ui b/isotp_interpreterwindow.ui index eb92d78..3219b39 100644 --- a/isotp_interpreterwindow.ui +++ b/isotp_interpreterwindow.ui @@ -7,7 +7,7 @@ 0 0 792 - 571 + 725 @@ -25,21 +25,96 @@ - + - Data Details of Selected Message + Show incomplete and/or corrupted messages - - - - - - Interpret Data as UDS - - + + + + + + + Data Details of Selected Message + + + + + + + + + Interpret As: + + + + + + + Raw + + + true + + + + + + + UDS + + + + + + + OBDII + + + + + + + + + + + + + + + + IDs to Decode + + + + + + + + + + + + All + + + + + + + None + + + + + + + + From ed238440a46609c38c30fab8cf6219b8c52cce62 Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Thu, 24 Mar 2016 21:19:43 -0400 Subject: [PATCH 17/26] Stability improvements --- canframemodel.cpp | 36 +++++++++++++++++++++++++----------- canframemodel.h | 2 +- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/canframemodel.cpp b/canframemodel.cpp index dc0216d..fe54877 100644 --- a/canframemodel.cpp +++ b/canframemodel.cpp @@ -6,7 +6,11 @@ int CANFrameModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); - if (filteredFrames.data()) return filteredFrames.count(); + if (filteredFrames.data()) + { + int rows = filteredFrames.count(); + return rows; + } //just in case somehow data is invalid which I have seen before. //But, this should not happen so issue a debugging message too @@ -16,7 +20,9 @@ int CANFrameModel::rowCount(const QModelIndex &parent) const int CANFrameModel::totalFrameCount() { - return frames.count(); + int count; + count = frames.count(); + return count; } int CANFrameModel::columnCount(const QModelIndex &index) const @@ -28,8 +34,8 @@ int CANFrameModel::columnCount(const QModelIndex &index) const CANFrameModel::CANFrameModel(QObject *parent) : QAbstractTableModel(parent) { - frames.reserve(10000000); //yes, I'm preallocating 10 million entries in this list. I don't think anyone will exceed this. - filteredFrames.reserve(10000000); + frames.reserve(50000000); //yes, preallocating a huge number of frames. + filteredFrames.reserve(50000000); //the goal is to prevent a reallocation from ever happening dbcHandler = NULL; interpretFrames = false; overwriteDups = false; @@ -80,6 +86,7 @@ void CANFrameModel::setInterpetMode(bool mode) void CANFrameModel::normalizeTiming() { + mutex.lock(); if (frames.count() == 0) return; timeOffset = frames[0].timestamp; for (int i = 0; i < frames.count(); i++) @@ -92,11 +99,14 @@ void CANFrameModel::normalizeTiming() filteredFrames[i].timestamp -= timeOffset; } this->endResetModel(); + mutex.unlock(); } void CANFrameModel::setOverwriteMode(bool mode) { + beginResetModel(); overwriteDups = mode; + endResetModel(); } void CANFrameModel::setFilterState(int ID, bool state) @@ -125,6 +135,7 @@ void CANFrameModel::recalcOverwrite() int lastUnique = 0; bool found; + mutex.lock(); beginResetModel(); for (int i = 1; i < frames.count(); i++) { @@ -148,6 +159,7 @@ void CANFrameModel::recalcOverwrite() while (frames.count() > lastUnique) frames.removeLast(); filteredFrames.clear(); + filteredFrames.reserve(50000000); for (int i = 0; i < frames.count(); i++) { @@ -158,6 +170,7 @@ void CANFrameModel::recalcOverwrite() } endResetModel(); + mutex.unlock(); } QVariant CANFrameModel::data(const QModelIndex &index, int role) const @@ -332,11 +345,9 @@ void CANFrameModel::addFrame(const CANFrame &frame, bool autoRefresh = false) } void CANFrameModel::sendRefresh() -{ - mutex.lock(); +{ qDebug() << "Sending mass refresh"; - QVector tempContainer; - beginResetModel(); + QVector tempContainer; int count = frames.count(); for (int i = 0; i < count; i++) { @@ -345,8 +356,11 @@ void CANFrameModel::sendRefresh() tempContainer.append(frames[i]); } } + mutex.lock(); + beginResetModel(); filteredFrames.clear(); filteredFrames.append(tempContainer); + filteredFrames.reserve(50000000); lastUpdateNumFrames = filteredFrames.count(); endResetModel(); mutex.unlock(); @@ -381,14 +395,12 @@ void CANFrameModel::sendBulkRefresh(int num) if (!overwriteDups) { - mutex.lock(); - if (num > filteredFrames.count()) num = filteredFrames.count(); + //if (num > filteredFrames.count()) num = filteredFrames.count(); //qDebug() << "From " << (filteredFrames.count() - num) << " to " << (filteredFrames.count() - 1); //beginInsertRows(QModelIndex(), filteredFrames.count() - num, filteredFrames.count() - 1); //endInsertRows(); beginResetModel(); endResetModel(); - mutex.unlock(); } else { @@ -404,6 +416,8 @@ void CANFrameModel::clearFrames() frames.clear(); filteredFrames.clear(); filters.clear(); + frames.reserve(50000000); + filteredFrames.reserve(50000000); this->endResetModel(); lastUpdateNumFrames = 0; mutex.unlock(); diff --git a/canframemodel.h b/canframemodel.h index 764c073..7e93007 100644 --- a/canframemodel.h +++ b/canframemodel.h @@ -21,7 +21,7 @@ public: QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; int columnCount(const QModelIndex &) const; - int totalFrameCount(); + int totalFrameCount(); void addFrame(const CANFrame &, bool); void sendRefresh(); From 7b0556bba764e4e305921d28a8ae3f1c6f220bed Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Thu, 24 Mar 2016 22:17:40 -0400 Subject: [PATCH 18/26] Fix for crash in flowview when the clear button is pressed on the main screen. --- flowviewwindow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/flowviewwindow.cpp b/flowviewwindow.cpp index d4fe69d..424925e 100644 --- a/flowviewwindow.cpp +++ b/flowviewwindow.cpp @@ -656,6 +656,7 @@ void FlowViewWindow::updatePosition(bool forward) void FlowViewWindow::updateGraphLocation() { + if (frameCache.count() == 0) return; int start = currentPosition - 5; if (start < 0) start = 0; int end = currentPosition + 5; From 4694d9f37c123f4d62516fd6937dce2a6b75b0ee Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Sat, 26 Mar 2016 22:15:50 -0400 Subject: [PATCH 19/26] Renamed isotp class to ISOTP_HANDLER as it will soon gain support for sending ISO-TP frames as well. Fixed up a couple of bugs. Added more to the UDS code and descriptions. --- SavvyCAN.pro | 228 +++++++++++++++--------------- isotp_decoder.cpp | 191 ------------------------- isotp_decoder.h | 75 ---------- isotp_handler.cpp | 274 ++++++++++++++++++++++++++++++++++++ isotp_handler.h | 39 +++++ isotp_interpreterwindow.cpp | 6 +- isotp_interpreterwindow.h | 4 +- mainwindow.cpp | 1 + 8 files changed, 433 insertions(+), 385 deletions(-) delete mode 100644 isotp_decoder.cpp delete mode 100644 isotp_decoder.h create mode 100644 isotp_handler.cpp create mode 100644 isotp_handler.h diff --git a/SavvyCAN.pro b/SavvyCAN.pro index acc556d..1b59288 100644 --- a/SavvyCAN.pro +++ b/SavvyCAN.pro @@ -1,114 +1,114 @@ -#------------------------------------------------- -# -# Project created by QtCreator 2015-04-25T22:57:44 -# -#------------------------------------------------- - -QT += core gui - -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets serialport printsupport qml - -CONFIG(release, debug|release):DEFINES += QT_NO_DEBUG_OUTPUT - -CONFIG += c++11 qscintilla2 - -TARGET = SavvyCAN -TEMPLATE = app - - -SOURCES += main.cpp\ - mainwindow.cpp \ - canframemodel.cpp \ - utility.cpp \ - qcustomplot.cpp \ - graphingwindow.cpp \ - frameinfowindow.cpp \ - newgraphdialog.cpp \ - frameplaybackwindow.cpp \ - serialworker.cpp \ - candatagrid.cpp \ - flowviewwindow.cpp \ - framesenderwindow.cpp \ - dbchandler.cpp \ - dbcmaineditor.cpp \ - dbcsignaleditor.cpp \ - framefileio.cpp \ - filecomparatorwindow.cpp \ - mainsettingsdialog.cpp \ - firmwareuploaderwindow.cpp \ - discretestatewindow.cpp \ - connectionwindow.cpp \ - scriptingwindow.cpp \ - scriptcontainer.cpp \ - canfilter.cpp \ - rangestatewindow.cpp \ - dbc_classes.cpp \ - dbcloadsavewindow.cpp \ - fuzzingwindow.cpp \ - udsscanwindow.cpp \ - can_structs.cpp \ - isotp_interpreterwindow.cpp \ - isotp_decoder.cpp - -HEADERS += mainwindow.h \ - can_structs.h \ - canframemodel.h \ - utility.h \ - qcustomplot.h \ - graphingwindow.h \ - frameinfowindow.h \ - newgraphdialog.h \ - frameplaybackwindow.h \ - serialworker.h \ - candatagrid.h \ - flowviewwindow.h \ - framesenderwindow.h \ - can_trigger_structs.h \ - dbc_classes.h \ - dbchandler.h \ - dbcmaineditor.h \ - dbcsignaleditor.h \ - framefileio.h \ - config.h \ - filecomparatorwindow.h \ - mainsettingsdialog.h \ - firmwareuploaderwindow.h \ - discretestatewindow.h \ - connectionwindow.h \ - scriptingwindow.h \ - scriptcontainer.h \ - canfilter.h \ - rangestatewindow.h \ - dbcloadsavewindow.h \ - fuzzingwindow.h \ - udsscanwindow.h \ - isotp_interpreterwindow.h \ - isotp_decoder.h - -FORMS += mainwindow.ui \ - graphingwindow.ui \ - frameinfowindow.ui \ - newgraphdialog.ui \ - frameplaybackwindow.ui \ - candatagrid.ui \ - flowviewwindow.ui \ - framesenderwindow.ui \ - dbcmaineditor.ui \ - dbcsignaleditor.ui \ - filecomparatorwindow.ui \ - mainsettingsdialog.ui \ - firmwareuploaderwindow.ui \ - discretestatewindow.ui \ - connectionwindow.ui \ - scriptingwindow.ui \ - rangestatewindow.ui \ - dbcloadsavewindow.ui \ - fuzzingwindow.ui \ - udsscanwindow.ui \ - isotp_interpreterwindow.ui - -DISTFILES += - -RESOURCES += \ - icons.qrc \ - images.qrc +#------------------------------------------------- +# +# Project created by QtCreator 2015-04-25T22:57:44 +# +#------------------------------------------------- + +QT += core gui + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets serialport printsupport qml + +CONFIG(release, debug|release):DEFINES += QT_NO_DEBUG_OUTPUT + +CONFIG += c++11 qscintilla2 + +TARGET = SavvyCAN +TEMPLATE = app + + +SOURCES += main.cpp\ + mainwindow.cpp \ + canframemodel.cpp \ + utility.cpp \ + qcustomplot.cpp \ + graphingwindow.cpp \ + frameinfowindow.cpp \ + newgraphdialog.cpp \ + frameplaybackwindow.cpp \ + serialworker.cpp \ + candatagrid.cpp \ + flowviewwindow.cpp \ + framesenderwindow.cpp \ + dbchandler.cpp \ + dbcmaineditor.cpp \ + dbcsignaleditor.cpp \ + framefileio.cpp \ + filecomparatorwindow.cpp \ + mainsettingsdialog.cpp \ + firmwareuploaderwindow.cpp \ + discretestatewindow.cpp \ + connectionwindow.cpp \ + scriptingwindow.cpp \ + scriptcontainer.cpp \ + canfilter.cpp \ + rangestatewindow.cpp \ + dbc_classes.cpp \ + dbcloadsavewindow.cpp \ + fuzzingwindow.cpp \ + udsscanwindow.cpp \ + can_structs.cpp \ + isotp_interpreterwindow.cpp \ + isotp_handler.cpp + +HEADERS += mainwindow.h \ + can_structs.h \ + canframemodel.h \ + utility.h \ + qcustomplot.h \ + graphingwindow.h \ + frameinfowindow.h \ + newgraphdialog.h \ + frameplaybackwindow.h \ + serialworker.h \ + candatagrid.h \ + flowviewwindow.h \ + framesenderwindow.h \ + can_trigger_structs.h \ + dbc_classes.h \ + dbchandler.h \ + dbcmaineditor.h \ + dbcsignaleditor.h \ + framefileio.h \ + config.h \ + filecomparatorwindow.h \ + mainsettingsdialog.h \ + firmwareuploaderwindow.h \ + discretestatewindow.h \ + connectionwindow.h \ + scriptingwindow.h \ + scriptcontainer.h \ + canfilter.h \ + rangestatewindow.h \ + dbcloadsavewindow.h \ + fuzzingwindow.h \ + udsscanwindow.h \ + isotp_interpreterwindow.h \ + isotp_handler.h + +FORMS += mainwindow.ui \ + graphingwindow.ui \ + frameinfowindow.ui \ + newgraphdialog.ui \ + frameplaybackwindow.ui \ + candatagrid.ui \ + flowviewwindow.ui \ + framesenderwindow.ui \ + dbcmaineditor.ui \ + dbcsignaleditor.ui \ + filecomparatorwindow.ui \ + mainsettingsdialog.ui \ + firmwareuploaderwindow.ui \ + discretestatewindow.ui \ + connectionwindow.ui \ + scriptingwindow.ui \ + rangestatewindow.ui \ + dbcloadsavewindow.ui \ + fuzzingwindow.ui \ + udsscanwindow.ui \ + isotp_interpreterwindow.ui + +DISTFILES += + +RESOURCES += \ + icons.qrc \ + images.qrc diff --git a/isotp_decoder.cpp b/isotp_decoder.cpp deleted file mode 100644 index 08d0a4d..0000000 --- a/isotp_decoder.cpp +++ /dev/null @@ -1,191 +0,0 @@ -#include "isotp_decoder.h" - -//in order descriptions based on the list above. But, just in order one after the other -QString UDS_SERVICE_DESCRIPT[] = -{ - "Diagnostic session control", - "Reset ECU", - "Clear diagnostic trouble codes", - "Read diagnostic trouble codes", - "Read data by ID", - "Read data by address", - "Read scaling data by ID", - "Request security access", - "Communication control", - "Read data by ID periodically", - "Create dynamic data ID", - "Write data by ID", - "Input/Output control (force)", - "Call a service routine", - "Request data download (from client to server)", - "Request data upload (from server to client)", - "Transfer data", - "Request that data transfer cease", - "Request file transfer", - "Write data by address", - "Tester is present", - "Read or write comm timing parameters", - "Secured data transmission", - "Control DTC settings", - "Request start/stop transmission on event", - "Control comm link" -}; - -ISOTP_DECODER::ISOTP_DECODER(const QVector *frames, QObject *parent) - : QObject(parent) -{ - modelFrames = frames; - useExtendedAddressing = false; -} - -void ISOTP_DECODER::setExtendedAddressing(bool mode) -{ - useExtendedAddressing = mode; -} - -//remember, negative numbers are special -1 = all frames deleted, -2 = totally new set of frames. -void ISOTP_DECODER::updatedFrames(int numFrames) -{ - if (numFrames == -1) //all frames deleted. Kill the display - { - } - else if (numFrames == -2) //all new set of frames. Reset - { - for (int i = 0; i < modelFrames->length(); i++) processFrame(modelFrames->at(i)); - } - else //just got some new frames. See if they are relevant. - { - for (int i = modelFrames->count() - numFrames; i < modelFrames->count(); i++) - { - processFrame(modelFrames->at(i)); - } - } -} - -void ISOTP_DECODER::processFrame(const CANFrame &frame) -{ - uint64_t ID = frame.ID; - int frameType; - int frameLen; - int ln; - int offset; - ISOTP_MESSAGE msg; - ISOTP_MESSAGE *pMsg; - - frameType = 0; - frameLen = 0; - - if (useExtendedAddressing) - { - ID = ID << 8; - ID += frame.data[0]; - frameType = frame.data[1] >> 4; - frameLen = frame.data[1] & 0xF; - } - else - { - frameType = frame.data[0] >> 4; - frameLen = frame.data[0] & 0xF; - } - - switch(frameType) - { - case 0: //single frame message - checkNeedFlush(ID); - - if (frameLen == 0) return; //length of zero isn't valid. - if (frameLen > 6 && useExtendedAddressing) return; //impossible - if (frameLen > 7) return; - - msg.bus = frame.bus; - msg.extended = frame.extended; - msg.ID = ID; - msg.isReceived = frame.isReceived; - msg.len = frameLen; - msg.data.reserve(frameLen); - msg.timestamp = frame.timestamp; - if (useExtendedAddressing) for (int j = 0; j < frameLen; j++) msg.data.append(frame.data[j+2]); - else for (int j = 0; j < frameLen; j++) msg.data.append(frame.data[j+1]); - qDebug() << "Emitting single frame ISOTP message"; - emit newISOMessage(msg); - break; - case 1: //first frame of a multi-frame message - checkNeedFlush(ID); - msg.bus = frame.bus; - msg.extended = frame.extended; - msg.ID = ID; - msg.timestamp = frame.timestamp; - msg.isReceived = frame.isReceived; - frameLen = frameLen << 8; - if (useExtendedAddressing) - { - frameLen += frame.data[2]; - frameLen = frameLen & 0xFFF; - msg.len = frameLen; - msg.data.reserve(frameLen); - for (int j = 0; j < 5; j++) msg.data.append(frame.data[3 + j]); - } - else - { - frameLen += frame.data[1]; - frameLen = frameLen & 0xFFF; - msg.len = frameLen; - msg.data.reserve(frameLen); - for (int j = 0; j < 6; j++) msg.data.append(frame.data[2 + j]); - } - messageBuffer.append(msg); - break; - case 2: //subsequent frames for multi-frame messages - pMsg = NULL; - for (int i = 0; i < messageBuffer.length(); i++) - { - if (messageBuffer[i].ID == ID) - { - pMsg = &messageBuffer[i]; - break; - } - } - if (!pMsg) return; - ln = pMsg->len - pMsg->data.count(); - offset = pMsg->data.count(); - if (useExtendedAddressing) - { - if (ln > 6) ln = 6; - for (int j = 0; j < ln; j++) pMsg->data.append(frame.data[j+2]); - } - else - { - if (ln > 7) ln = 7; - for (int j = 0; j < ln; j++) pMsg->data.append(frame.data[j+1]); - } - if (pMsg->len <= pMsg->data.count()) - { - qDebug() << "Emitting multiframe ISOTP message"; - emit newISOMessage(*pMsg); - } - break; - case 3: //flow control messages -ignored for now - break; - } -} - -void ISOTP_DECODER::checkNeedFlush(uint64_t ID) -{ - for (int i = 0; i < messageBuffer.length(); i++) - { - if (messageBuffer[i].ID == ID) - { - //warning... this code will work for direct signals as emit turns into a function call - //and thus the other side will have time to do its processing before control returns - //and we delete the message on our side. But, if this code were used cross thread the - //emit would be a queued message instead and control would immediately return - //here and then the message would be deleted before being delivered. - //To fix that the message would have to be passed by value instead which I'd like to avoid. - //Thus, don't use this across threads unless you like to debug strange issues. - qDebug() << "Flushing a partial frame"; - emit newISOMessage(messageBuffer[i]); - messageBuffer.removeAt(i); - return; - } - } -} diff --git a/isotp_decoder.h b/isotp_decoder.h deleted file mode 100644 index a170f36..0000000 --- a/isotp_decoder.h +++ /dev/null @@ -1,75 +0,0 @@ -#ifndef ISOTP_DECODER_H -#define ISOTP_DECODER_H - -#include -#include -#include "can_structs.h" - -enum OBDII_FUNCTS -{ - UDS_OBDII_SHOW_CURRENT = 1, - UDS_OBDII_SHOW_FREEZE = 2, - UDS_OBDII_SHOW_STORED_DTC = 3, - UDS_OBDII_CLEAR_DTC = 4, - UDS_OBDII_TEST_O2 = 5, - UDS_OBDII_TEST_RESULTS = 6, - UDS_OBDII_SHOW_PENDING_DTC = 7, - UDS_OBDII_CONTROL_DEVICES = 8, - UDS_OBDII_VEH_INFO = 9, - UDS_OBDII_PERM_DTC = 0xA -}; - -enum UDS_FUNCTS -{ - UDS_DIAG_CONTROL=0x10, - UDS_ECU_RESET=0x11, - UDS_CLEAR_DIAG=0x14, - UDS_READ_DTC=0x19, - UDS_READ_BY_ID=0x22, - UDS_READ_BY_ADDR=0x23, - UDS_READ_SCALING_ID=0x24, - UDS_SECURITY_ACCESS=0x27, - UDS_COMM_CTRL=0x28, - UDS_READ_DATA_ID_PERIODIC=0x2A, - UDS_DYNAMIC_DATA_DEFINE=0x2C, - UDS_WRITE_BY_ID=0x2E, - UDS_IO_CTRL=0x2F, - UDS_ROUTINE_CTRL=0x31, - UDS_REQUEST_DOWNLOAD=0x34, - UDS_REQUEST_UPLOAD=0x35, - UDS_TRANSFER_DATA=0x36, - UDS_REQ_TRANS_EXIT=0x37, - UDS_REQ_FILE_TRANS=0x38, - UDS_WRITE_BY_ADDR=0x3D, - UDS_TESTER_PRESENT=0x3E, - UDS_ACCESS_TIMING=0x83, - UDS_SECURED_DATA_TRANS=0x84, - UDS_CTRL_DTC_SETTINGS=0x85, - UDS_RESPONSE_ON_EVENT=0x86, - UDS_RESPONSE_LINK_CTRL=0x87 -}; - -class ISOTP_DECODER : public QObject -{ - Q_OBJECT - -public: - explicit ISOTP_DECODER(const QVector *frames, QObject *parent = 0); - void setExtendedAddressing(bool mode); - -public slots: - void updatedFrames(int); - -signals: - void newISOMessage(ISOTP_MESSAGE &msg); - -private: - QList messageBuffer; - const QVector *modelFrames; - bool useExtendedAddressing; - - void processFrame(const CANFrame &frame); - void checkNeedFlush(uint64_t ID); -}; - -#endif // ISOTP_DECODER_H diff --git a/isotp_handler.cpp b/isotp_handler.cpp new file mode 100644 index 0000000..0fdac38 --- /dev/null +++ b/isotp_handler.cpp @@ -0,0 +1,274 @@ +#include "isotp_handler.h" + +CODE_STRUCT OBDII_FUNCTS[] = +{ + {1, "UDS_OBDII_SHOW_CURRENT", "OBDII - Show current data"}, + {2, "UDS_OBDII_SHOW_FREEZE", "OBDII - Show freeze data"}, + {3, "UDS_OBDII_SHOW_STORED_DTC", "OBDII - Show stored DTC codes"}, + {4, "UDS_OBDII_CLEAR_DTC", "OBDII - Clear current DTC codes"}, + {5, "UDS_OBDII_TEST_O2", "OBDII - O2 sensor testing"}, + {6, "UDS_OBDII_TEST_RESULTS", "OBDII - Show emissions testing results"}, + {7, "UDS_OBDII_SHOW_PENDING_DTC", "OBDII - Show pending DTC codes"}, + {8, "UDS_OBDII_CONTROL_DEVICES", "OBDII - Control vehicle devices"}, + {9, "UDS_OBDII_VEH_INFO", "OBDII - Retrieve vehicle information"}, + {0xA, "UDS_OBDII_PERM_DTC", "OBDII - Show permanent DTC codes"}, + {0xFF, "UDS_UNKNOWN", "Unknown OBDII code - Likely proprietary"} +}; + +CODE_STRUCT UDS_FUNCS[] = +{ + {0x10, "UDS_DIAG_CONTROL", "Diagnostic session control"}, + {0x11, "UDS_ECU_RESET", "Reset ECU"}, + {0x14, "UDS_CLEAR_DIAG", "Clear diagnostic trouble codes"}, + {0x19, "UDS_READ_DTC", "Read diagnostic trouble codes"}, + {0x22, "UDS_READ_BY_ID", "Read data by ID"}, + {0x23, "UDS_READ_BY_ADDR", "Read data by address"}, + {0x24, "UDS_READ_SCALING_ID", "Read scaling data by ID"}, + {0x27, "UDS_SECURITY_ACCESS", "Request security access"}, + {0x28, "UDS_COMM_CTRL", "Communication control"}, + {0x2A, "UDS_READ_DATA_ID_PERIODIC", "Read data by ID periodically"}, + {0x2C, "UDS_DYNAMIC_DATA_DEFINE", "Create dynamic data ID"}, + {0x2E, "UDS_WRITE_BY_ID", "Write data by ID"}, + {0x2F, "UDS_IO_CTRL", "Input/Output control (force)"}, + {0x31, "UDS_ROUTINE_CTRL", "Call a service routine"}, + {0x34, "UDS_REQUEST_DOWNLOAD", "Request data download (from PC to ECU)"}, + {0x35, "UDS_REQUEST_UPLOAD", "Request data upload (from ECU to PC)"}, + {0x36, "UDS_TRANSFER_DATA", "Transfer data"}, + {0x37, "UDS_REQ_TRANS_EXIT", "Request that data transfer cease"}, + {0x38, "UDS_REQ_FILE_TRANS", "Request file transfer"}, + {0x3D, "UDS_WRITE_BY_ADDR", "Write data by address"}, + {0x3E, "UDS_TESTER_PRESENT", "Tester is present"}, + {0x83, "UDS_ACCESS_TIMING", "Read or write comm timing parameters"}, + {0x84, "UDS_SECURED_DATA_TRANS", "Secured data transmission"}, + {0x85, "UDS_CTRL_DTC_SETTINGS", "Control DTC settings"}, + {0x86, "UDS_RESPONSE_ON_EVENT", "Request start/stop transmission on event"}, + {0x87, "UDS_RESPONSE_LINK_CTRL", "Control comm link"}, + {0xFF, "UDS_UNKNOWN_CODE", "Unknown, likely proprietary UDS function code"} +}; + +CODE_STRUCT UDS_NEG_RESPONSE[] = +{ + {0x10, "UDS_NEG_GENERAL_REJECT", "General rejection (no other codes matched)"}, + {0x11, "UDS_NEG_SERVICE_NOTSUPP", "ECU does not support this service code"}, + {0x12, "UDS_NEG_SUBFUNCT_NOTSUPP", "ECU does not support the requested sub function"}, + {0x13, "UDS_NEG_INVALID_FORMAT", "Invalid request length or format error"}, + {0x14, "UDS_NEG_RESPONSE_TOOLONG", "Response would be too long to send"}, + {0x21, "UDS_NEG_BUSY", "ECU is busy. Try again later"}, + {0x22, "UDS_NEG_COND_INCORR", "A prereq. condition was not met"}, + {0x24, "UDS_NEG_REQ_SEQ_ERR", "Invalid sequence of requests"}, + {0x25, "UDS_NEG_SUBNET_NORESP", "ECU tried to gateway request but response timed out"}, + {0x26, "UDS_NEG_FAILURE", "A failure (indicated in a DTC) is preventing a reply"}, + {0x31, "UDS_NEG_REQ_OUTOFRANGE", "A parameter is outside of the valid range"}, + {0x33, "UDS_NEG_SECURITY_DENIED", "Security access was denied. (invalid seq or ECU not unlocked?)"}, + {0x35, "UDS_NEG_INVALID_KEY", "Key passed was invalid. Failure counter has been incremented."}, + {0x36, "UDS_NEG_EXCEED_ATTEMPTS", "Key failed too many times. ECU security access locked out"}, + {0x37, "UDS_NEG_TIMEDELAY", "Security access too soon after last attempt"}, + {0x38, "UDS_NEG_EXT_SECUR_1", "Extended security failure code 1"}, + {0x39, "UDS_NEG_EXT_SECUR_2", "Extended security failure code 2"}, + {0x3A, "UDS_NEG_EXT_SECUR_3", "Extended security failure code 3"}, + {0x3B, "UDS_NEG_EXT_SECUR_4", "Extended security failure code 4"}, + {0x3C, "UDS_NEG_EXT_SECUR_5", "Extended security failure code 5"}, + {0x3D, "UDS_NEG_EXT_SECUR_6", "Extended security failure code 6"}, + {0x3E, "UDS_NEG_EXT_SECUR_7", "Extended security failure code 7"}, + {0x3F, "UDS_NEG_EXT_SECUR_8", "Extended security failure code 8"}, + {0x40, "UDS_NEG_EXT_SECUR_9", "Extended security failure code 9"}, + {0x41, "UDS_NEG_EXT_SECUR_10", "Extended security failure code 10"}, + {0x42, "UDS_NEG_EXT_SECUR_11", "Extended security failure code 11"}, + {0x43, "UDS_NEG_EXT_SECUR_12", "Extended security failure code 12"}, + {0x44, "UDS_NEG_EXT_SECUR_13", "Extended security failure code 13"}, + {0x45, "UDS_NEG_EXT_SECUR_14", "Extended security failure code 14"}, + {0x46, "UDS_NEG_EXT_SECUR_15", "Extended security failure code 15"}, + {0x47, "UDS_NEG_EXT_SECUR_16", "Extended security failure code 16"}, + {0x48, "UDS_NEG_EXT_SECUR_17", "Extended security failure code 17"}, + {0x49, "UDS_NEG_EXT_SECUR_18", "Extended security failure code 18"}, + {0x4A, "UDS_NEG_EXT_SECUR_19", "Extended security failure code 19"}, + {0x4B, "UDS_NEG_EXT_SECUR_20", "Extended security failure code 20"}, + {0x4C, "UDS_NEG_EXT_SECUR_21", "Extended security failure code 21"}, + {0x4D, "UDS_NEG_EXT_SECUR_22", "Extended security failure code 22"}, + {0x4E, "UDS_NEG_EXT_SECUR_23", "Extended security failure code 23"}, + {0x4F, "UDS_NEG_EXT_SECUR_24", "Extended security failure code 24"}, + {0x70, "UDS_NEG_UPLOAD_DOWNLOAD", "Fault when attempting to start upload/download"}, + {0x71, "UDS_NEG_TRX_SUSPENDED", "Transfer aborting due to a fault"}, + {0x72, "UDS_NEG_GEN_PROGRAMMING", "Fault while attempting to write to ECU memory"}, + {0x73, "UDS_NEG_WRONG_BLOCK_SEQ", "Invalid sequence value detected during transfer"}, + {0x78, "UDS_NEG_RESP_PENDING", "Request successful but ECU still busy - Response pending"}, + {0x7E, "UDS_NEG_SUBFUNCT_CURRSESS", "ECU does not support this subfunction in current session type"}, + {0x7F, "UDS_NEG_SERVICE_CURRSESS", "ECU does not support this service in current session type"}, + {0x81, "UDS_NEG_RPM_TOOHIGH", "RPM is too high to execute request"}, + {0x82, "UDS_NEG_RPM_TOOLOW", "RPM is too low to execute request"}, + {0x83, "UDS_NEG_ENGINE_RUNNING", "Cannot execute request while engine is running"}, + {0x84, "UDS_NEG_ENGINE_NOTRUNNING", "Cannot execute request while engine is off"}, + {0x85, "UDS_NEG_ENG_RUNTIME_LOW", "Cannot execute request until engine has run for longer"}, + {0x86, "UDS_NEG_TEMPERATURE_HIGH", "Cannot execute request until temperature is lower"}, + {0x87, "UDS_NEG_TEMPERATURE_LOW", "Cannot execute request until temperature is higher"}, + {0x88, "UDS_NEG_SPEED_HIGH", "Cannot execute request until vehicle slows down"}, + {0x89, "UDS_NEG_SPEED_LOW", "Cannot execute request until vehicle is going faster"}, + {0x8A, "UDS_NEG_PEDAL_HIGH", "Cannot execute request until throttle is lower"}, + {0x8B, "UDS_NEG_PEDAL_LOW", "Cannot execute request until throttle is higher"}, + {0x8C, "UDS_NEG_NOT_NEUTRAL", "Cannot execute request until transmission is in neutral"}, + {0x8D, "UDS_NEG_NOT_INGEAR", "Cannot execute request until vehicle is in gear"}, + {0x8F, "UDS_NEG_BRAKE_NOTPRESSED", "Cannot execute request until brake pedal is pressed (Hold down)"}, + {0x90, "UDS_NEG_NOT_PARK", "Cannot execute request until vehicle is in park"}, + {0x91, "UDS_NEG_CLUTCH_LOCKED", "Cannot execute request while clutch is locked"}, + {0x92, "UDS_NEG_VOLTAGE_HIGH", "Cannot execute request until voltage is lower"}, + {0x93, "UDS_NEG_VOLTAGE_LOW", "Cannot execute request until voltage is higher"}, +}; + +ISOTP_HANDLER::ISOTP_HANDLER(const QVector *frames, QObject *parent) + : QObject(parent) +{ + modelFrames = frames; + useExtendedAddressing = false; +} + +void ISOTP_HANDLER::setExtendedAddressing(bool mode) +{ + useExtendedAddressing = mode; +} + +//remember, negative numbers are special -1 = all frames deleted, -2 = totally new set of frames. +void ISOTP_HANDLER::updatedFrames(int numFrames) +{ + if (numFrames == -1) //all frames deleted. Kill the display + { + } + else if (numFrames == -2) //all new set of frames. Reset + { + for (int i = 0; i < modelFrames->length(); i++) processFrame(modelFrames->at(i)); + } + else //just got some new frames. See if they are relevant. + { + for (int i = modelFrames->count() - numFrames; i < modelFrames->count(); i++) + { + processFrame(modelFrames->at(i)); + } + } +} + +void ISOTP_HANDLER::processFrame(const CANFrame &frame) +{ + uint64_t ID = frame.ID; + int frameType; + int frameLen; + int ln; + int offset; + ISOTP_MESSAGE msg; + ISOTP_MESSAGE *pMsg; + + frameType = 0; + frameLen = 0; + + if (useExtendedAddressing) + { + ID = ID << 8; + ID += frame.data[0]; + frameType = frame.data[1] >> 4; + frameLen = frame.data[1] & 0xF; + } + else + { + frameType = frame.data[0] >> 4; + frameLen = frame.data[0] & 0xF; + } + + switch(frameType) + { + case 0: //single frame message + checkNeedFlush(ID); + + if (frameLen == 0) return; //length of zero isn't valid. + if (frameLen > 6 && useExtendedAddressing) return; //impossible + if (frameLen > 7) return; + + msg.bus = frame.bus; + msg.extended = frame.extended; + msg.ID = ID; + msg.isReceived = frame.isReceived; + msg.len = frameLen; + msg.data.reserve(frameLen); + msg.timestamp = frame.timestamp; + if (useExtendedAddressing) for (int j = 0; j < frameLen; j++) msg.data.append(frame.data[j+2]); + else for (int j = 0; j < frameLen; j++) msg.data.append(frame.data[j+1]); + qDebug() << "Emitting single frame ISOTP message"; + emit newISOMessage(msg); + break; + case 1: //first frame of a multi-frame message + checkNeedFlush(ID); + msg.bus = frame.bus; + msg.extended = frame.extended; + msg.ID = ID; + msg.timestamp = frame.timestamp; + msg.isReceived = frame.isReceived; + frameLen = frameLen << 8; + if (useExtendedAddressing) + { + frameLen += frame.data[2]; + frameLen = frameLen & 0xFFF; + msg.len = frameLen; + msg.data.reserve(frameLen); + for (int j = 0; j < 5; j++) msg.data.append(frame.data[3 + j]); + } + else + { + frameLen += frame.data[1]; + frameLen = frameLen & 0xFFF; + msg.len = frameLen; + msg.data.reserve(frameLen); + for (int j = 0; j < 6; j++) msg.data.append(frame.data[2 + j]); + } + messageBuffer.append(msg); + break; + case 2: //subsequent frames for multi-frame messages + pMsg = NULL; + for (int i = 0; i < messageBuffer.length(); i++) + { + if (messageBuffer[i].ID == ID) + { + pMsg = &messageBuffer[i]; + break; + } + } + if (!pMsg) return; + ln = pMsg->len - pMsg->data.count(); + offset = pMsg->data.count(); + if (useExtendedAddressing) + { + if (ln > 6) ln = 6; + for (int j = 0; j < ln; j++) pMsg->data.append(frame.data[j+2]); + } + else + { + if (ln > 7) ln = 7; + for (int j = 0; j < ln; j++) pMsg->data.append(frame.data[j+1]); + } + if (pMsg->len <= pMsg->data.count()) + { + qDebug() << "Emitting multiframe ISOTP message"; + emit newISOMessage(*pMsg); + } + break; + case 3: //flow control messages -ignored for now + break; + } +} + +void ISOTP_HANDLER::checkNeedFlush(uint64_t ID) +{ + for (int i = 0; i < messageBuffer.length(); i++) + { + if (messageBuffer[i].ID == ID) + { + //warning... this code will work for direct signals as emit turns into a function call + //and thus the other side will have time to do its processing before control returns + //and we delete the message on our side. But, if this code were used cross thread the + //emit would be a queued message instead and control would immediately return + //here and then the message would be deleted before being delivered. + //To fix that the message would have to be passed by value instead which I'd like to avoid. + //Thus, don't use this across threads unless you like to debug strange issues. + qDebug() << "Flushing a partial frame"; + emit newISOMessage(messageBuffer[i]); + messageBuffer.removeAt(i); + return; + } + } +} diff --git a/isotp_handler.h b/isotp_handler.h new file mode 100644 index 0000000..00c2cb8 --- /dev/null +++ b/isotp_handler.h @@ -0,0 +1,39 @@ +#ifndef ISOTP_DECODER_H +#define ISOTP_DECODER_H + +#include +#include +#include +#include "can_structs.h" + +struct CODE_STRUCT +{ + int code; + QString shortDesc; + QString longDesc; +}; + +class ISOTP_HANDLER : public QObject +{ + Q_OBJECT + +public: + explicit ISOTP_HANDLER(const QVector *frames, QObject *parent = 0); + void setExtendedAddressing(bool mode); + +public slots: + void updatedFrames(int); + +signals: + void newISOMessage(ISOTP_MESSAGE &msg); + +private: + QList messageBuffer; + const QVector *modelFrames; + bool useExtendedAddressing; + + void processFrame(const CANFrame &frame); + void checkNeedFlush(uint64_t ID); +}; + +#endif // ISOTP_DECODER_H diff --git a/isotp_interpreterwindow.cpp b/isotp_interpreterwindow.cpp index 6d3c95e..778a2d8 100644 --- a/isotp_interpreterwindow.cpp +++ b/isotp_interpreterwindow.cpp @@ -9,11 +9,11 @@ ISOTP_InterpreterWindow::ISOTP_InterpreterWindow(const QVector *frames ui->setupUi(this); modelFrames = frames; - decoder = new ISOTP_DECODER(modelFrames); + decoder = new ISOTP_HANDLER(modelFrames); connect(MainWindow::getReference(), &MainWindow::framesUpdated, this, &ISOTP_InterpreterWindow::updatedFrames); - connect(MainWindow::getReference(), &MainWindow::framesUpdated, decoder, &ISOTP_DECODER::updatedFrames); - connect(decoder, &ISOTP_DECODER::newISOMessage, this, &ISOTP_InterpreterWindow::newISOMessage); + connect(MainWindow::getReference(), &MainWindow::framesUpdated, decoder, &ISOTP_HANDLER::updatedFrames); + connect(decoder, &ISOTP_HANDLER::newISOMessage, this, &ISOTP_InterpreterWindow::newISOMessage); connect(ui->tableIsoFrames, &QTableWidget::itemSelectionChanged, this, &ISOTP_InterpreterWindow::showDetailView); diff --git a/isotp_interpreterwindow.h b/isotp_interpreterwindow.h index 3818e76..ff099e1 100644 --- a/isotp_interpreterwindow.h +++ b/isotp_interpreterwindow.h @@ -3,7 +3,7 @@ #include #include "can_structs.h" -#include "isotp_decoder.h" +#include "isotp_handler.h" namespace Ui { class ISOTP_InterpreterWindow; @@ -25,7 +25,7 @@ private slots: private: Ui::ISOTP_InterpreterWindow *ui; - ISOTP_DECODER *decoder; + ISOTP_HANDLER *decoder; const QVector *modelFrames; QVector messages; diff --git a/mainwindow.cpp b/mainwindow.cpp index 5426539..e2e41dd 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -103,6 +103,7 @@ MainWindow::MainWindow(QWidget *parent) : bDirty = false; inhibitFilterUpdate = false; rxFrames = 0; + framesPerSec = 0; model->setDBCHandler(dbcHandler); From 7e20bcfd37351c2b369672a789a1fe773f2899c6 Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Mon, 9 May 2016 21:12:41 -0400 Subject: [PATCH 20/26] Added a bit of error handling to file loading routines. It will now most likely tell you that you picked the wrong format instead of crashing. Updated version number to match new functionality. --- config.h | 2 +- framefileio.cpp | 359 +++++++++++++++++++++++++++++------------------- 2 files changed, 218 insertions(+), 143 deletions(-) diff --git a/config.h b/config.h index 08e40d0..92b7d9f 100644 --- a/config.h +++ b/config.h @@ -1,7 +1,7 @@ #ifndef CONFIG #define CONFIG -#define VERSION 152 +#define VERSION 154 //try to keep this in sync. //SavvyCAN will complain if you connect a GVRET board with a revision diff --git a/framefileio.cpp b/framefileio.cpp index 211b23e..0418009 100644 --- a/framefileio.cpp +++ b/framefileio.cpp @@ -1,5 +1,6 @@ #include "framefileio.h" +#include #include FrameFileIO::FrameFileIO() @@ -159,7 +160,13 @@ bool FrameFileIO::loadFrameFile(QString &fileName, QVector* frameCache fileName = fileList[fileList.length() - 1]; return true; } - else return false; + else + { + QMessageBox msgBox; + msgBox.setText("File load completed with errors.\r\nPerhaps you selected the wrong file type?"); + msgBox.exec(); + return false; + } } return false; } @@ -175,6 +182,7 @@ bool FrameFileIO::loadVehicleSpyFile(QString filename, QVector *frames QByteArray line; int lineCounter = 0; bool pastHeader = false; + bool foundErrors = false; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { @@ -189,6 +197,8 @@ bool FrameFileIO::loadVehicleSpyFile(QString filename, QVector *frames if (lineCounter == 2) pastHeader = true; } + if (inFile->atEnd()) foundErrors = true; + while (!inFile->atEnd()) { lineCounter++; if (lineCounter > 100) @@ -199,30 +209,33 @@ bool FrameFileIO::loadVehicleSpyFile(QString filename, QVector *frames line = inFile->readLine().simplified().toUpper(); QList tokens = line.split(','); - thisFrame.bus = 0; - thisFrame.timestamp = tokens[1].toDouble() * 1000000.0; - if (tokens[5].startsWith("T")) thisFrame.isReceived = false; - else thisFrame.isReceived = true; - thisFrame.ID = tokens[9].toInt(NULL, 16); - if (tokens[11].startsWith("T")) thisFrame.extended = true; - else thisFrame.extended = false; - - thisFrame.len = 0; - for (int i = 0; i < 8; i++) + if (tokens.length() > 20) { - if (tokens[12 + i].length() > 0) - { - thisFrame.data[i] = tokens[12 + i].toInt(NULL, 16); - thisFrame.len++; - } - else break; - } + thisFrame.bus = 0; + thisFrame.timestamp = tokens[1].toDouble() * 1000000.0; + if (tokens[5].startsWith("T")) thisFrame.isReceived = false; + else thisFrame.isReceived = true; + thisFrame.ID = tokens[9].toInt(NULL, 16); + if (tokens[11].startsWith("T")) thisFrame.extended = true; + else thisFrame.extended = false; - frames->append(thisFrame); + thisFrame.len = 0; + for (int i = 0; i < 8; i++) + { + if (tokens[12 + i].length() > 0) + { + thisFrame.data[i] = tokens[12 + i].toInt(NULL, 16); + thisFrame.len++; + } + else break; + } + frames->append(thisFrame); + } + else foundErrors = true; } inFile->close(); delete inFile; - return true; + return !foundErrors; } bool FrameFileIO::saveVehicleSpyFile(QString filename, const QVector *frames) @@ -258,6 +271,7 @@ bool FrameFileIO::loadCRTDFile(QString filename, QVector* frames) CANFrame thisFrame; QByteArray line; int lineCounter = 0; + bool foundErrors = false; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { @@ -279,44 +293,48 @@ bool FrameFileIO::loadCRTDFile(QString filename, QVector* frames) { QList tokens = line.split(' '); int multiplier; - 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 + if (tokens.length() > 3) { - multiplier = 1; //special case. Assume no decimal means microseconds - } - //qDebug() << "decimal places " << decimalPlaces; - thisFrame.timestamp = (int64_t)(tokens[0].toDouble() * multiplier); - char firstChar = tokens[1].left(1)[0]; - if (firstChar == 'R' || firstChar == 'T') - { - thisFrame.ID = tokens[2].toInt(NULL, 16); - if (tokens[1] == "R29" || tokens[1] == "T29") thisFrame.extended = true; - else thisFrame.extended = false; - if (firstChar == 'T') thisFrame.isReceived = false; - else thisFrame.isReceived = true; - thisFrame.bus = 0; - thisFrame.len = tokens.length() - 3; - for (int d = 0; d < thisFrame.len; d++) + 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 { - if (tokens[d + 3] != "") + multiplier = 1; //special case. Assume no decimal means microseconds + } + //qDebug() << "decimal places " << decimalPlaces; + thisFrame.timestamp = (int64_t)(tokens[0].toDouble() * multiplier); + char firstChar = tokens[1].left(1)[0]; + if (firstChar == 'R' || firstChar == 'T') + { + thisFrame.ID = tokens[2].toInt(NULL, 16); + if (tokens[1] == "R29" || tokens[1] == "T29") thisFrame.extended = true; + else thisFrame.extended = false; + if (firstChar == 'T') thisFrame.isReceived = false; + else thisFrame.isReceived = true; + thisFrame.bus = 0; + thisFrame.len = tokens.length() - 3; + for (int d = 0; d < thisFrame.len; d++) { - thisFrame.data[d] = tokens[d + 3].toInt(NULL, 16); + if (tokens[d + 3] != "") + { + thisFrame.data[d] = tokens[d + 3].toInt(NULL, 16); + } + else thisFrame.data[d] = 0; } - else thisFrame.data[d] = 0; + frames->append(thisFrame); } - frames->append(thisFrame); } + else foundErrors = true; } } inFile->close(); delete inFile; - return true; + return !foundErrors; } bool FrameFileIO::saveCRTDFile(QString filename, const QVector* frames) @@ -380,6 +398,7 @@ bool FrameFileIO::loadNativeCSVFile(QString filename, QVector* frames) int fileVersion = 1; long long timeStamp = Utility::GetTimeMS(); int lineCounter = 0; + bool foundErrors = false; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { @@ -402,47 +421,52 @@ bool FrameFileIO::loadNativeCSVFile(QString filename, QVector* frames) if (line.length() > 2) { QList tokens = line.split(','); - if (tokens[0].length() > 3) + if (tokens.length() >= 6) { - long long temp = tokens[0].right(10).toLongLong(); - thisFrame.timestamp = temp; - } - else - { - timeStamp += 5; - thisFrame.timestamp = timeStamp; - } + if (tokens[0].length() > 3) + { + long long temp = tokens[0].right(10).toLongLong(); + thisFrame.timestamp = temp; + } + else + { + timeStamp += 5; + thisFrame.timestamp = timeStamp; + } - thisFrame.ID = tokens[1].toInt(NULL, 16); - if (tokens[2].toUpper().contains("TRUE")) thisFrame.extended = 1; - else thisFrame.extended = 0; + thisFrame.ID = tokens[1].toInt(NULL, 16); + if (tokens[2].toUpper().contains("TRUE")) thisFrame.extended = 1; + else thisFrame.extended = 0; - if (fileVersion == 1) - { - thisFrame.isReceived = true; - thisFrame.bus = tokens[3].toInt(); - thisFrame.len = tokens[4].toInt(); - for (int c = 0; c < 8; c++) thisFrame.data[c] = 0; - for (int d = 0; d < thisFrame.len; d++) - thisFrame.data[d] = tokens[5 + d].toInt(NULL, 16); - } - else if (fileVersion == 2) - { - if (tokens[3].at(0) == 'R') thisFrame.isReceived = true; - else thisFrame.isReceived = false; - thisFrame.bus = tokens[4].toInt(); - thisFrame.len = tokens[5].toInt(); - for (int c = 0; c < 8; c++) thisFrame.data[c] = 0; - for (int d = 0; d < thisFrame.len; d++) - thisFrame.data[d] = tokens[6 + d].toInt(NULL, 16); - } + if (fileVersion == 1) + { + thisFrame.isReceived = true; + thisFrame.bus = tokens[3].toInt(); + thisFrame.len = tokens[4].toInt(); + for (int c = 0; c < 8; c++) thisFrame.data[c] = 0; + for (int d = 0; d < thisFrame.len; d++) + thisFrame.data[d] = tokens[5 + d].toInt(NULL, 16); + } + else if (fileVersion == 2) + { + if (tokens[3].at(0) == 'R') thisFrame.isReceived = true; + else thisFrame.isReceived = false; + thisFrame.bus = tokens[4].toInt(); + thisFrame.len = tokens[5].toInt(); + if (thisFrame.len + 6 > tokens.length()) thisFrame.len = tokens.length() - 6; + for (int c = 0; c < 8; c++) thisFrame.data[c] = 0; + for (int d = 0; d < thisFrame.len; d++) + thisFrame.data[d] = tokens[6 + d].toInt(NULL, 16); + } - frames->append(thisFrame); + frames->append(thisFrame); + } + else foundErrors = true; } } inFile->close(); delete inFile; - return true; + return !foundErrors; } bool FrameFileIO::saveNativeCSVFile(QString filename, const QVector* frames) @@ -510,6 +534,7 @@ bool FrameFileIO::loadGenericCSVFile(QString filename, QVector* frames QByteArray line; long long timeStamp = Utility::GetTimeMS(); int lineCounter = 0; + bool foundErrors = false; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { @@ -545,10 +570,11 @@ bool FrameFileIO::loadGenericCSVFile(QString filename, QVector* frames frames->append(thisFrame); } + else foundErrors = true; } inFile->close(); delete inFile; - return true; + return !foundErrors; } //4f5,ff 34 23 45 24 e4 @@ -631,6 +657,7 @@ bool FrameFileIO::loadLogFile(QString filename, QVector* frames) QByteArray line; uint64_t timeStamp = Utility::GetTimeMS(); int lineCounter = 0; + bool foundErrors = false; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { @@ -653,24 +680,30 @@ bool FrameFileIO::loadLogFile(QString filename, QVector* frames) if (line.length() > 1) { QList tokens = line.split(' '); - QList timeToks = tokens[0].split(':'); - timeStamp = (timeToks[0].toInt() * (1000ul * 1000ul * 60ul * 60ul)) + (timeToks[1].toInt() * (1000ul * 1000ul * 60ul)) + if (tokens.length() >= 6) + { + QList timeToks = tokens[0].split(':'); + timeStamp = (timeToks[0].toInt() * (1000ul * 1000ul * 60ul * 60ul)) + (timeToks[1].toInt() * (1000ul * 1000ul * 60ul)) + (timeToks[2].toInt() * (1000ul * 1000ul)) + (timeToks[3].toInt() * 100ul); - thisFrame.timestamp = timeStamp; - if (tokens[1].at(0) == 'R') thisFrame.isReceived = true; - else thisFrame.isReceived = false; - thisFrame.ID = tokens[3].right(tokens[3].length() - 2).toInt(NULL, 16); - if (tokens[4] == "s") thisFrame.extended = false; - else thisFrame.extended = true; - thisFrame.bus = tokens[2].toInt() - 1; - thisFrame.len = tokens[5].toInt(); - for (int d = 0; d < thisFrame.len; d++) thisFrame.data[d] = tokens[d + 6].toInt(NULL, 16); - } - frames->append(thisFrame); + thisFrame.timestamp = timeStamp; + if (tokens[1].at(0) == 'R') thisFrame.isReceived = true; + else thisFrame.isReceived = false; + thisFrame.ID = tokens[3].right(tokens[3].length() - 2).toInt(NULL, 16); + if (tokens[4] == "s") thisFrame.extended = false; + else thisFrame.extended = true; + thisFrame.bus = tokens[2].toInt() - 1; + thisFrame.len = tokens[5].toInt(); + if (thisFrame.len > 8) thisFrame.len = 8; + if (thisFrame.len < 0) thisFrame.len = 0; + for (int d = 0; d < thisFrame.len; d++) thisFrame.data[d] = tokens[d + 6].toInt(NULL, 16); + frames->append(thisFrame); + } + else foundErrors = true; + } } inFile->close(); delete inFile; - return true; + return !foundErrors; } bool FrameFileIO::saveLogFile(QString filename, const QVector* frames) @@ -744,6 +777,7 @@ bool FrameFileIO::loadIXXATFile(QString filename, QVector* frames) QByteArray line; uint64_t timeStamp = Utility::GetTimeMS(); int lineCounter = 0; + bool foundErrors = false; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { @@ -765,27 +799,48 @@ bool FrameFileIO::loadIXXATFile(QString filename, QVector* frames) if (line.length() > 1) { QList tokens = line.split(','); - QString timePortion = unQuote(tokens[0]); - QStringList timeToks = timePortion.split(':'); - timeStamp = (timeToks[0].toInt() * (1000ul * 1000ul * 60ul * 60ul)) + (timeToks[1].toInt() * (1000ul * 1000ul * 60ul)) + if (line.length() >= 5) + { + QString timePortion = unQuote(tokens[0]); + QStringList timeToks = timePortion.split(':'); + if (timeToks.length() >= 3) + { + timeStamp = (timeToks[0].toInt() * (1000ul * 1000ul * 60ul * 60ul)) + (timeToks[1].toInt() * (1000ul * 1000ul * 60ul)) + (timeToks[2].toDouble() * (1000.0 * 1000.0)); - thisFrame.timestamp = timeStamp; - thisFrame.ID = unQuote(tokens[1]).toInt(NULL, 16); - if (unQuote(tokens[2]).toUpper().at(0) == 'S') thisFrame.extended = false; - else thisFrame.extended = true; + } + else + { + timeStamp = 0; + foundErrors = true; + } + thisFrame.timestamp = timeStamp; + thisFrame.ID = unQuote(tokens[1]).toInt(NULL, 16); + QString tempStr = unQuote(tokens[2]).toUpper(); + if (tempStr.length() > 0) + { + if (tempStr.at(0) == 'S') thisFrame.extended = false; + else thisFrame.extended = true; + } + else + { + thisFrame.extended = false; + foundErrors = true; + } - thisFrame.isReceived = true; - thisFrame.bus = 0; + thisFrame.isReceived = true; + thisFrame.bus = 0; - QStringList dataToks = unQuote(tokens[4]).simplified().split(' '); - thisFrame.len = dataToks.length(); - for (int d = 0; d < thisFrame.len; d++) thisFrame.data[d] = dataToks[d].toInt(NULL, 16); + QStringList dataToks = unQuote(tokens[4]).simplified().split(' '); + thisFrame.len = dataToks.length(); + for (int d = 0; d < thisFrame.len; d++) thisFrame.data[d] = dataToks[d].toInt(NULL, 16); + frames->append(thisFrame); + } + else foundErrors = true; } - frames->append(thisFrame); } inFile->close(); delete inFile; - return true; + return !foundErrors; } bool FrameFileIO::saveIXXATFile(QString filename, const QVector* frames) @@ -852,6 +907,7 @@ bool FrameFileIO::loadCANDOFile(QString filename, QVector* frames) QByteArray data; int timeOffset = 0; uint64_t lastTimeStamp = 0; + bool foundErrors = false; if (!inFile->open(QIODevice::ReadOnly)) { @@ -887,7 +943,7 @@ bool FrameFileIO::loadCANDOFile(QString filename, QVector* frames) timeOffset += 60000000ul; } lastTimeStamp = thisFrame.timestamp; - thisFrame.ID = ((unsigned char)data[3] * 256 + (unsigned char)data[2]) & 0x7FF; + thisFrame.ID = (((unsigned char)data[3] & 0x0F) * 256 + (unsigned char)data[2]) & 0x7FF; thisFrame.len = (unsigned char)data[3] >> 4; if (thisFrame.len <= 8 && thisFrame.ID <= 0x7FF) @@ -895,11 +951,12 @@ bool FrameFileIO::loadCANDOFile(QString filename, QVector* frames) for (int d = 0; d < thisFrame.len; d++) thisFrame.data[d] = (unsigned char)data[4 + d]; frames->append(thisFrame); } + else foundErrors = true; } inFile->close(); delete inFile; - return true; + return !foundErrors; } bool FrameFileIO::saveCANDOFile(QString filename, const QVector* frames) @@ -973,6 +1030,7 @@ bool FrameFileIO::loadMicrochipFile(QString filename, QVector* frames) bool inComment = false; long long timeStamp; int lineCounter = 0; + bool foundErrors = false; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { @@ -1002,24 +1060,31 @@ bool FrameFileIO::loadMicrochipFile(QString filename, QVector* frames) if (!inComment) { QList tokens = line.split(';'); - timeStamp = tokens[0].toInt() * 1000; - thisFrame.timestamp = timeStamp; - if (tokens[1].at(0) == 'R') thisFrame.isReceived = true; - else thisFrame.isReceived = false; - thisFrame.ID = Utility::ParseStringToNum(tokens[2]); - if (thisFrame.ID <= 0x7FF) thisFrame.extended = false; - else thisFrame.extended = true; - thisFrame.bus = 0; - thisFrame.len = tokens[3].toInt(); - for (int d = 0; d < thisFrame.len; d++) thisFrame.data[d] = (unsigned char)Utility::ParseStringToNum(tokens[4 + d]); - frames->append(thisFrame); + if (tokens.length() >= 4) + { + timeStamp = tokens[0].toInt() * 1000; + thisFrame.timestamp = timeStamp; + if (tokens[1].at(0) == 'R') thisFrame.isReceived = true; + else thisFrame.isReceived = false; + thisFrame.ID = Utility::ParseStringToNum(tokens[2]); + if (thisFrame.ID <= 0x7FF) thisFrame.extended = false; + else thisFrame.extended = true; + thisFrame.bus = 0; + thisFrame.len = tokens[3].toInt(); + if (thisFrame.len > 8) thisFrame.len = 8; + if (thisFrame.len < 0) thisFrame.len = 0; + if (thisFrame.len + 4 > tokens.length()) thisFrame.len = tokens.length() - 4; + for (int d = 0; d < thisFrame.len; d++) thisFrame.data[d] = (unsigned char)Utility::ParseStringToNum(tokens[4 + d]); + frames->append(thisFrame); + } + else foundErrors = true; } } } } inFile->close(); delete inFile; - return true; + return !foundErrors; } /* @@ -1122,6 +1187,7 @@ bool FrameFileIO::loadTraceFile(QString filename, QVector* frames) QByteArray line; long long timeStamp = 0; int lineCounter = 0; + bool foundErrors = false; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { @@ -1148,31 +1214,36 @@ bool FrameFileIO::loadTraceFile(QString filename, QVector* frames) else { QList tokens = line.split('\t'); + if (tokens.length() > 3) + { + QList timestampToks = tokens[1].split(':'); - QList timestampToks = tokens[1].split(':'); + timeStamp = timestampToks[0].toInt() * 1000000ul * 60 * 60; + timeStamp += timestampToks[1].toInt() * 1000000ul * 60; + timeStamp += timestampToks[2].toInt() * 1000000ul; + timeStamp += timestampToks[3].toInt() * 100; - timeStamp = timestampToks[0].toInt() * 1000000ul * 60 * 60; - timeStamp += timestampToks[1].toInt() * 1000000ul * 60; - timeStamp += timestampToks[2].toInt() * 1000000ul; - timeStamp += timestampToks[3].toInt() * 100; + thisFrame.timestamp = timeStamp; - thisFrame.timestamp = timeStamp; - - thisFrame.ID = tokens[2].toLong(NULL, 16); - if (thisFrame.ID <= 0x7FF) thisFrame.extended = false; - else thisFrame.extended = true; - thisFrame.bus = 0; - thisFrame.len = tokens[3].toInt(); - - QList dataToks = tokens[4].split(' '); - for (int d = 0; d < thisFrame.len; d++) thisFrame.data[d] = (unsigned char)dataToks[d].toInt(NULL, 16); - frames->append(thisFrame); + thisFrame.ID = tokens[2].toLong(NULL, 16); + if (thisFrame.ID <= 0x7FF) thisFrame.extended = false; + else thisFrame.extended = true; + thisFrame.bus = 0; + thisFrame.len = tokens[3].toInt(); + if (thisFrame.len < 0) thisFrame.len = 0; + if (thisFrame.len > 8) thisFrame.len = 8; + QList dataToks = tokens[4].split(' '); + if (thisFrame.len > dataToks.length()) thisFrame.len = dataToks.length(); + for (int d = 0; d < thisFrame.len; d++) thisFrame.data[d] = (unsigned char)dataToks[d].toInt(NULL, 16); + frames->append(thisFrame); + } + else foundErrors = true; } } } inFile->close(); delete inFile; - return true; + return !foundErrors; } bool FrameFileIO::saveTraceFile(QString filename, const QVector * frames) @@ -1266,5 +1337,9 @@ bool FrameFileIO::saveTraceFile(QString filename, const QVector * fram QString FrameFileIO::unQuote(QString inStr) { - return inStr.split('\"')[1]; + QStringList temp; + temp = inStr.split('\"'); + if (temp.length() >= 3) + return temp[1]; + return QString(""); } From 4282f6b25c7ac8747a1522cb66b9f52975cf37a6 Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Tue, 10 May 2016 22:38:16 -0400 Subject: [PATCH 21/26] *Very* drastic changes to the graphing system. Now all graphs are done very much like DBC signals. Quite a bit has changed. This commit compiles but the results are all funky. Just committing to give a good reference point during debugging. Don't use compiles from this revision. Wait young Jedi... --- config.h | 2 +- dbc_classes.cpp | 1 + graphingwindow.cpp | 435 +++++++-------------------------------------- graphingwindow.h | 5 +- newgraphdialog.cpp | 236 +++++++++++------------- newgraphdialog.h | 9 +- newgraphdialog.ui | 129 +++++++++----- 7 files changed, 264 insertions(+), 553 deletions(-) diff --git a/config.h b/config.h index 92b7d9f..33378ea 100644 --- a/config.h +++ b/config.h @@ -1,7 +1,7 @@ #ifndef CONFIG #define CONFIG -#define VERSION 154 +#define VERSION 155 //try to keep this in sync. //SavvyCAN will complain if you connect a GVRET board with a revision diff --git a/dbc_classes.cpp b/dbc_classes.cpp index 8885b3f..03d4bc5 100644 --- a/dbc_classes.cpp +++ b/dbc_classes.cpp @@ -180,6 +180,7 @@ bool DBC_SIGNAL::processAsDouble(const CANFrame &frame, double &outValue) endResult = ((double)result * factor) + bias; result = (int64_t)endResult; } + /*TODO: It should be noted that the below floating point has not even been tested. For shame! Test it!*/ else if (valType == SP_FLOAT) { //The theory here is that we force the integer signal code to treat this as diff --git a/graphingwindow.cpp b/graphingwindow.cpp index afe9c4d..e5bbf64 100644 --- a/graphingwindow.cpp +++ b/graphingwindow.cpp @@ -685,33 +685,24 @@ void GraphingWindow::saveDefinitions() QList::iterator iter; for (iter = graphParams.begin(); iter != graphParams.end(); ++iter) { + outFile->write("X,"); outFile->write(QString::number(iter->ID, 16).toUtf8()); outFile->putChar(','); - if (iter->isDBCSignal) - { - outFile->putChar('S'); - outFile->putChar(','); - outFile->write(iter->signal.toUtf8()); - outFile->putChar(','); - } - else - { - outFile->write(QString::number(iter->mask, 16).toUtf8()); - outFile->putChar(','); - outFile->write(QString::number(iter->startByte).toUtf8()); - outFile->putChar(','); - outFile->write(QString::number(iter->endByte).toUtf8()); - outFile->putChar(','); - if (iter->isSigned) outFile->putChar('Y'); - else outFile->putChar('N'); - outFile->putChar(','); - outFile->write(QString::number(iter->bias).toUtf8()); - outFile->putChar(','); - outFile->write(QString::number(iter->scale).toUtf8()); - outFile->putChar(','); - outFile->write(QString::number(iter->stride).toUtf8()); - outFile->putChar(','); - } + outFile->write(QString::number(iter->mask, 16).toUtf8()); + outFile->putChar(','); + outFile->write(QString::number(iter->startBit).toUtf8()); + outFile->putChar(','); + outFile->write(QString::number(iter->numBits).toUtf8()); + outFile->putChar(','); + if (iter->isSigned) outFile->putChar('Y'); + else outFile->putChar('N'); + outFile->putChar(','); + outFile->write(QString::number(iter->bias).toUtf8()); + outFile->putChar(','); + outFile->write(QString::number(iter->scale).toUtf8()); + outFile->putChar(','); + outFile->write(QString::number(iter->stride).toUtf8()); + outFile->putChar(','); outFile->write(QString::number(iter->color.red()).toUtf8()); outFile->putChar(','); outFile->write(QString::number(iter->color.green()).toUtf8()); @@ -729,7 +720,6 @@ void GraphingWindow::loadDefinitions() { QString filename; QFileDialog dialog; - bool dbcMissing = false; QStringList filters; filters.append(QString(tr("Graph definition (*.gdf)"))); @@ -755,66 +745,33 @@ void GraphingWindow::loadDefinitions() if (line.length() > 2) { GraphParams gp; - QList tokens = line.split(','); - gp.isDBCSignal = false; - gp.ID = tokens[0].toInt(NULL, 16); - if (tokens[1] == "S") + QList tokens = line.split(','); + + if (tokens[0] == "X") { - gp.isDBCSignal = true; - //tokens[2] is the signal name. Need to use the message ID and this name to look it up - DBC_MESSAGE *msg = dbcHandler->getFileByIdx(0)->messageHandler->findMsgByID(gp.ID); - if (msg != NULL) - { - DBC_SIGNAL *sig = msg->sigHandler->findSignalByName(tokens[2]); - if (sig == NULL) dbcMissing = true; - gp.signal = tokens[2]; - } - else - { - gp.signal = ""; - dbcMissing = true; - } - } - else - { - gp.mask = tokens[1].toULongLong(NULL, 16); - qDebug() << gp.mask; - gp.startByte = tokens[2].toInt(); - gp.endByte = tokens[3].toInt(); - if (tokens[4] == "Y") gp.isSigned = true; + gp.ID = tokens[1].toInt(NULL, 16); + gp.mask = tokens[2].toULongLong(NULL, 16); + gp.startBit = tokens[3].toInt(); + gp.numBits = tokens[4].toInt(); + if (tokens[5] == "Y") gp.isSigned = true; else gp.isSigned = false; - gp.bias = tokens[5].toFloat(); - gp.scale = tokens[6].toFloat(); - gp.stride = tokens[7].toInt(); - } - if (!gp.isDBCSignal) - { - gp.color.setRed(tokens[8].toInt()); - gp.color.setGreen(tokens[9].toInt()); - gp.color.setBlue(tokens[10].toInt()); - if (tokens.length() > 11) - gp.graphName = tokens[11]; + gp.bias = tokens[6].toFloat(); + gp.scale = tokens[7].toFloat(); + gp.stride = tokens[8].toInt(); + + gp.color.setRed(tokens[9].toInt()); + gp.color.setGreen(tokens[10].toInt()); + gp.color.setBlue(tokens[11].toInt()); + if (tokens.length() > 12) + gp.graphName = tokens[12]; else gp.graphName = QString(); + createGraph(gp, true); } - else - { - gp.color.setRed(tokens[3].toInt()); - gp.color.setGreen(tokens[4].toInt()); - gp.color.setBlue(tokens[5].toInt()); - gp.graphName = tokens[6]; - } - createGraph(gp, true); } } inFile->close(); - if (dbcMissing) - { - QMessageBox msg; - msg.setText("One or more graphs could not be loaded\r\nbecause the signal could not be found.\r\nPerhaps you forgot to load\r\nthe DBC file?"); - msg.exec(); - } } } @@ -851,133 +808,16 @@ void GraphingWindow::addNewGraph() void GraphingWindow::appendToGraph(GraphParams ¶ms, CANFrame &frame) { int64_t tempVal; //64 bit temp value. - if (params.isDBCSignal) + tempVal = Utility::processIntegerSignal(frame.data, params.startBit, params.numBits, params.intelFormat, params.isSigned) & params.mask; + if (secondsMode) { - double tempValue; - DBC_MESSAGE *msg = dbcHandler->getFileByIdx(0)->messageHandler->findMsgByID(params.ID); - DBC_SIGNAL *sig = NULL; - if (msg) sig = msg->sigHandler->findSignalByName(params.signal); - if (sig == NULL) return; - //if the given signal was found and successfully processed in this frame then add it to the graph - if (sig->processAsDouble(frame, tempValue)) - { - //qDebug() << "tempValue: " << tempValue; - if (secondsMode) - { - params.x.append((double)(frame.timestamp) / 1000000.0 - params.xbias); - } - else - { - params.x.append(frame.timestamp - params.xbias); - } - params.y.append(tempValue); - } + params.x.append((double)(frame.timestamp) / 1000000.0 - params.xbias); } else { - if (params.endByte == -1 || params.startByte == params.endByte) - { - tempVal = (frame.data[params.startByte] & params.mask); - if (params.isSigned && tempVal > 127) - { - tempVal = tempVal - 256; - } - if (secondsMode) - { - params.x.append((double)(frame.timestamp) / 1000000.0 - params.xbias); - } - else - { - params.x.append(frame.timestamp - params.xbias); - } - params.y.append((tempVal * params.scale) + params.bias); - } - else if (params.endByte > params.startByte) //big endian - { - float tempValue; - int64_t tempValInt; - int numBytes = (params.endByte - params.startByte) + 1; - int64_t shiftRef = 1 << (numBytes * 8); - uint64_t maskShifter; - uint8_t tempByte; - tempValInt = 0; - int64_t expon = 1; - maskShifter = params.mask; - for (int c = 0; c < numBytes; c++) - { - tempByte = frame.data[params.endByte - c]; - tempByte &= maskShifter; - tempValInt += (tempByte * expon); - expon *= 256; - maskShifter = maskShifter >> 8; - } - - tempValInt &= params.mask; - - int64_t twocompPoint = params.mask; - if (shiftRef < twocompPoint || twocompPoint == -1) twocompPoint = shiftRef; - //qDebug() << "two comp point: " << twocompPoint; - if (params.isSigned && tempValInt > ((twocompPoint / 2))) - { - tempValInt = tempValInt - twocompPoint; - } - - tempValue = (float)tempValInt; - - if (secondsMode) - { - params.x.append((double)(frame.timestamp) / 1000000.0 - params.xbias); - } - else - { - params.x.append(frame.timestamp - params.xbias); - } - - params.y.append((tempValue * params.scale) + params.bias); - } - else //little endian - { - float tempValue; - int64_t tempValInt; - int numBytes = (params.startByte - params.endByte) + 1; - int64_t shiftRef = 1 << (numBytes * 8); - uint64_t maskShifter; - uint8_t tempByte; - tempValInt = 0; - int64_t expon = 1; - maskShifter = params.mask; - for (int c = 0; c < numBytes; c++) - { - tempByte = frame.data[params.endByte + c]; - tempByte &= maskShifter; - tempValInt += tempByte * expon; - expon *= 256; - maskShifter = maskShifter >> 8; - } - tempValInt &= params.mask; - - int64_t twocompPoint = params.mask; - if (shiftRef < twocompPoint || twocompPoint == -1) twocompPoint = shiftRef; - //qDebug() << "two comp point: " << twocompPoint; - if (params.isSigned && tempValInt > ((twocompPoint / 2))) - { - tempValInt = tempValInt - twocompPoint; - } - - tempValue = (float)tempValInt; - - if (secondsMode) - { - params.x.append((double)(frame.timestamp) / 1000000.0 - params.xbias); - } - else - { - params.x.append(frame.timestamp - params.xbias); - } - - params.y.append((tempValue * params.scale) + params.bias); - } + params.x.append(frame.timestamp - params.xbias); } + params.y.append((tempVal * params.scale) + params.bias); params.ref->setData(params.x,params.y); } @@ -988,23 +828,15 @@ void GraphingWindow::createGraph(GraphParams ¶ms, bool createGraphParam) float yminval=10000000.0, ymaxval = -1000000.0; float xminval=10000000000.0, xmaxval = -10000000000.0; GraphParams *refParam = ¶ms; - DBC_MESSAGE *msg = NULL; - DBC_SIGNAL *sig = NULL; + int sBit, bits; + bool intelFormat, isSigned; - if (params.isDBCSignal) - { - msg = dbcHandler->getFileByIdx(0)->messageHandler->findMsgByID(params.ID); - if (msg) sig = msg->sigHandler->findSignalByName(params.signal); - if (sig == NULL) return; - qDebug() << "New signal graph: " << params.signal <<" in ID:" << params.ID; - } - else - { - qDebug() << "New Graph ID: " << params.ID; - qDebug() << "Start byte: " << params.startByte; - qDebug() << "End Byte: " << params.endByte; - qDebug() << "Mask: " << params.mask; - } + qDebug() << "New Graph ID: " << params.ID; + qDebug() << "Start bit: " << params.startBit; + qDebug() << "Data length: " << params.numBits; + qDebug() << "Intel Mode: " << params.intelFormat; + qDebug() << "Signed: " << params.isSigned; + qDebug() << "Mask: " << params.mask; frameCache.clear(); for (int i = 0; i < modelFrames->count(); i++) @@ -1020,163 +852,28 @@ void GraphingWindow::createGraph(GraphParams ¶ms, bool createGraphParam) params.x.fill(0, numEntries); params.y.fill(0, numEntries); - if (params.isDBCSignal) + sBit = params.startBit; + bits = params.numBits; + intelFormat = params.intelFormat; + isSigned = params.isSigned; + + for (int j = 0; j < numEntries; j++) { - double tempValue; - int l = 0; - for (int j = 0; j < numEntries; j++) + tempVal = Utility::processIntegerSignal(frameCache[j /** params.stride*/].data, sBit, bits, intelFormat, isSigned) & params.mask; + //qDebug() << tempVal; + if (secondsMode) { - //if the given signal was found and successfully processed in this frame then add it to the graph - if (sig->processAsDouble(frameCache[j], tempValue)) - { - //qDebug() << "tempValue: " << tempValue; - if (secondsMode) - { - params.x[l] = (double)(frameCache[j].timestamp) / 1000000.0; - } - else - { - params.x[l] = frameCache[j].timestamp; - } - params.y[l] = tempValue; - if (params.y[l] < yminval) yminval = params.y[l]; - if (params.y[l] > ymaxval) ymaxval = params.y[l]; - if (params.x[l] < xminval) xminval = params.x[l]; - if (params.x[l] > xmaxval) xmaxval = params.x[l]; - l++; - } + params.x[j] = (double)(frameCache[j].timestamp) / 1000000.0; } - params.x.resize(l); - params.y.resize(l); - params.x.squeeze(); - params.y.squeeze(); - } - else - { - - if (params.endByte == -1 || params.startByte == params.endByte) + else { - for (int j = 0; j < numEntries; j++) - { - tempVal = (frameCache[j * params.stride].data[params.startByte] & params.mask); - if (params.isSigned && tempVal > 127) - { - tempVal = tempVal - 256; - } - if (secondsMode) - { - params.x[j] = (double)(frameCache[j].timestamp) / 1000000.0; - } - else - { - params.x[j] = frameCache[j].timestamp; - } - params.y[j] = (tempVal * params.scale) + params.bias; - if (params.y[j] < yminval) yminval = params.y[j]; - if (params.y[j] > ymaxval) ymaxval = params.y[j]; - if (params.x[j] < xminval) xminval = params.x[j]; - if (params.x[j] > xmaxval) xmaxval = params.x[j]; - } - } - else if (params.endByte > params.startByte) //big endian - { - float tempValue; - int64_t tempValInt; - int numBytes = (params.endByte - params.startByte) + 1; - int64_t shiftRef = (uint64_t)1 << (numBytes * 8); - uint64_t maskShifter; - uint8_t tempByte; - for (int j = 0; j < numEntries; j++) - { - tempValInt = 0; - int64_t expon = 1; - maskShifter = params.mask; - for (int c = 0; c < numBytes; c++) - { - tempByte = frameCache[j * params.stride].data[params.endByte - c]; - tempByte &= maskShifter; - tempValInt += (tempByte * expon); - expon *= 256; - maskShifter = maskShifter >> 8; - } - - tempValInt &= params.mask; - - int64_t twocompPoint = params.mask; - if (shiftRef < twocompPoint || twocompPoint == -1) twocompPoint = shiftRef; - //qDebug() << "two comp point: " << twocompPoint; - if (params.isSigned && tempValInt > ((twocompPoint / 2))) - { - tempValInt = tempValInt - twocompPoint; - } - - tempValue = (float)tempValInt; - - if (secondsMode) - { - params.x[j] = (double)(frameCache[j].timestamp) / 1000000.0; - } - else - { - params.x[j] = frameCache[j].timestamp; - } - - params.y[j] = (tempValue * params.scale) + params.bias; - if (params.y[j] < yminval) yminval = params.y[j]; - if (params.y[j] > ymaxval) ymaxval = params.y[j]; - if (params.x[j] < xminval) xminval = params.x[j]; - if (params.x[j] > xmaxval) xmaxval = params.x[j]; - } - } - else //little endian - { - float tempValue; - int64_t tempValInt; - int numBytes = (params.startByte - params.endByte) + 1; - int64_t shiftRef = (uint64_t)1 << (numBytes * 8); - uint64_t maskShifter; - uint8_t tempByte; - for (int j = 0; j < numEntries; j++) - { - tempValInt = 0; - int64_t expon = 1; - maskShifter = params.mask; - for (int c = 0; c < numBytes; c++) - { - tempByte = frameCache[j * params.stride].data[params.endByte + c]; - tempByte &= maskShifter; - tempValInt += tempByte * expon; - expon *= 256; - maskShifter = maskShifter >> 8; - } - tempValInt &= params.mask; - - int64_t twocompPoint = params.mask; - if (shiftRef < twocompPoint || twocompPoint == -1) twocompPoint = shiftRef; - //qDebug() << "two comp point: " << twocompPoint; - if (params.isSigned && tempValInt > ((twocompPoint / 2))) - { - tempValInt = tempValInt - twocompPoint; - } - - tempValue = (float)tempValInt; - - if (secondsMode) - { - params.x[j] = (double)(frameCache[j].timestamp) / 1000000.0; - } - else - { - params.x[j] = frameCache[j].timestamp; - } - - params.y[j] = (tempValue * params.scale) + params.bias; - if (params.y[j] < yminval) yminval = params.y[j]; - if (params.y[j] > ymaxval) ymaxval = params.y[j]; - if (params.x[j] < xminval) xminval = params.x[j]; - if (params.x[j] > xmaxval) xmaxval = params.x[j]; - } + params.x[j] = frameCache[j].timestamp; } + params.y[j] = (tempVal * params.scale) + params.bias; + if (params.y[j] < yminval) yminval = params.y[j]; + if (params.y[j] > ymaxval) ymaxval = params.y[j]; + if (params.x[j] < xminval) xminval = params.x[j]; + if (params.x[j] > xmaxval) xmaxval = params.x[j]; } params.xbias = 0; @@ -1193,8 +890,8 @@ void GraphingWindow::createGraph(GraphParams ¶ms, bool createGraphParam) if (params.graphName == NULL || params.graphName.length() == 0) { - params.graphName = QString("0x") + QString::number(params.ID, 16) + ":" + QString::number(params.startByte); - if ((params.endByte != -1) && (params.endByte != params.startByte)) params.graphName += "-" + QString::number(params.endByte); + params.graphName = QString("0x") + QString::number(params.ID, 16) + ":" + QString::number(params.startBit); + params.graphName += "-" + QString::number(params.numBits); } ui->graphingView->graph()->setName(params.graphName); ui->graphingView->graph()->setProperty("id", params.ID); diff --git a/graphingwindow.h b/graphingwindow.h index 80c8d4f..4887243 100644 --- a/graphingwindow.h +++ b/graphingwindow.h @@ -15,7 +15,8 @@ class GraphParams { public: uint32_t ID; - int startByte, endByte; + int startBit, numBits; + bool intelFormat; bool isSigned; uint64_t mask; float bias; @@ -24,8 +25,6 @@ public: QColor color; QCPGraph *ref; QString graphName; - bool isDBCSignal; //is this graph actually a DBC signal? If so the below pointer should be filled out - QString signal; //the below stuff is used for internal purposes only - code should be refactored so these can be private QVector x, y; double xbias; diff --git a/newgraphdialog.cpp b/newgraphdialog.cpp index 409e2f9..5f23f27 100644 --- a/newgraphdialog.cpp +++ b/newgraphdialog.cpp @@ -24,9 +24,13 @@ NewGraphDialog::NewGraphDialog(DBCHandler *handler, QWidget *parent) : ui->colorSwatch->setPalette(p); connect(ui->cbMessages, SIGNAL(currentIndexChanged(int)), this, SLOT(loadSignals(int))); - connect(ui->cbSignals, SIGNAL(currentIndexChanged(int)), this, SLOT(fillFormFromSignal(int))); - connect(ui->rbSignalGraph, SIGNAL(toggled(bool)), this, SLOT(setSignalActive(bool))); - connect(ui->rbStandardGraph, SIGNAL(toggled(bool)), this, SLOT(setStandardActive(bool))); + connect(ui->gridData, SIGNAL(gridClicked(int,int)), this, SLOT(bitfieldClicked(int,int))); + connect(ui->txtDataLen, SIGNAL(textChanged(QString)), this, SLOT(handleDataLenUpdate())); + connect(ui->cbIntel, SIGNAL(toggled(bool)), this, SLOT(drawBitfield())); + connect(ui->btnCopySignal, SIGNAL(clicked(bool)), this, SLOT(copySignalToParamsUI())); + + startBit = 0; + dataLen = 1; loadMessages(); } @@ -36,6 +40,12 @@ NewGraphDialog::~NewGraphDialog() delete ui; } +void NewGraphDialog::showEvent(QShowEvent* event) +{ + QDialog::showEvent(event); + loadMessages(); +} + void NewGraphDialog::addButtonClicked() { accept(); @@ -59,91 +69,47 @@ void NewGraphDialog::clearParams() ui->txtScale->clear(); ui->txtStride->clear(); ui->txtName->clear(); - ui->txtData->clear(); - ui->rbStandardGraph->setChecked(true); - setStandardActive(true); - } void NewGraphDialog::setParams(GraphParams ¶ms) { - if (params.isDBCSignal) - { - clearParams(); - setSignalActive(true); - //loadMessages(); + ui->txtBias->setText(QString::number(params.bias)); + ui->txtMask->setText(Utility::formatNumber(params.mask)); + ui->txtScale->setText(QString::number(params.scale)); + ui->txtStride->setText(QString::number(params.stride)); + ui->cbSigned->setChecked(params.isSigned); - } - else - { - setStandardActive(true); - ui->txtBias->setText(QString::number(params.bias)); - ui->txtMask->setText(Utility::formatNumber(params.mask)); - ui->txtScale->setText(QString::number(params.scale)); - ui->txtStride->setText(QString::number(params.stride)); - ui->cbSigned->setChecked(params.isSigned); - - if (params.endByte > -1) - { - ui->txtData->setText(QString::number(params.startByte) + "-" + QString::number(params.endByte)); - } - else - { - ui->txtData->setText(QString::number(params.startByte)); - } - } + startBit = params.startBit; + dataLen = params.numBits; ui->txtID->setText(Utility::formatNumber(params.ID)); ui->txtName->setText(params.graphName); QPalette p = ui->colorSwatch->palette(); p.setColor(QPalette::Button, params.color); ui->colorSwatch->setPalette(p); + + drawBitfield(); } void NewGraphDialog::getParams(GraphParams ¶ms) { - params.isDBCSignal = ui->rbSignalGraph->isChecked(); params.color = ui->colorSwatch->palette().button().color(); params.graphName = ui->txtName->text(); - if (params.isDBCSignal) - { - params.signal = ui->cbSignals->currentText(); - params.ID = Utility::ParseStringToNum(ui->txtID->text()); - params.bias = 0; - params.isSigned = false; - params.mask = 0; - params.scale = 1; - params.bias = 0; - params.stride = 1; + params.ID = Utility::ParseStringToNum(ui->txtID->text()); + params.bias = ui->txtBias->text().toFloat(); + params.isSigned = ui->cbSigned->isChecked(); + params.mask = Utility::ParseStringToNum(ui->txtMask->text()); + params.scale = ui->txtScale->text().toFloat(); + params.stride = Utility::ParseStringToNum(ui->txtStride->text()); - } - else { - params.ID = Utility::ParseStringToNum(ui->txtID->text()); - params.bias = ui->txtBias->text().toFloat(); - params.isSigned = ui->cbSigned->isChecked(); - params.mask = Utility::ParseStringToNum(ui->txtMask->text()); - params.scale = ui->txtScale->text().toFloat(); - params.stride = Utility::ParseStringToNum(ui->txtStride->text()); - params.signal = ""; + params.startBit = startBit; + params.numBits = dataLen; - QStringList values = ui->txtData->text().split('-'); - params.startByte = -1; - params.endByte = -1; - if (values.count() > 0) - { - params.startByte = values[0].toInt(); - if (values.count() > 1) - { - params.endByte = values[1].toInt(); - } - } - - //now catch stupidity and bring it to defaults - if (params.mask == 0) params.mask = 0xFFFFFFFF; - if (fabs(params.scale) < 0.00000001) params.scale = 1.0f; - if (params.stride < 1) params.stride = 1; - } + //now catch stupidity and bring it to defaults + if (params.mask == 0) params.mask = 0xFFFFFFFF; + if (fabs(params.scale) < 0.00000001) params.scale = 1.0f; + if (params.stride < 1) params.stride = 1; } void NewGraphDialog::loadMessages() @@ -174,73 +140,77 @@ void NewGraphDialog::loadSignals(int idx) } } -void NewGraphDialog::fillFormFromSignal(int idx) +void NewGraphDialog::bitfieldClicked(int x,int y) +{ + int bit = (y * 8 + (7-x)); + int res; + + qDebug() << "Clicked bit: " << bit; + startBit = bit; + drawBitfield(); +} + +void NewGraphDialog::drawBitfield() +{ + int64_t bitField = 0; + int endBit, sBit; + + bitField |= 1ull << (startBit); //make the start bit a different color to set it apart + ui->gridData->setReference((unsigned char *)&bitField, false); + + if (ui->cbIntel->isChecked()) + { + endBit = startBit + dataLen - 1; + if (startBit < 0) startBit = 0; + if (endBit > 63) endBit = 63; + for (int y = startBit; y <= endBit; y++) + { + bitField |= 1ull << y; + } + } + else //big endian / motorola format + { + //much more irritating than the intel version... + int size = dataLen; + sBit = startBit; + while (size > 0) + { + bitField |= 1ull << sBit; + size--; + if ((sBit % 8) == 0) sBit += 15; + else sBit--; + if (sBit > 63) sBit = 63; + } + } + + ui->gridData->updateData((unsigned char *)&bitField, true); +} + +void NewGraphDialog::handleDataLenUpdate() +{ + dataLen = ui->txtDataLen->text().toInt(); + if (dataLen < 1) dataLen = 1; + if (dataLen > 63) dataLen = 63; + drawBitfield(); +} + +void NewGraphDialog::copySignalToParamsUI() { - Q_UNUSED(idx); - GraphParams params; DBC_MESSAGE *msg = dbcHandler->getFileByIdx(0)->messageHandler->findMsgByName(ui->cbMessages->currentText()); - - if (msg == NULL) return; - + if (!msg) return; DBC_SIGNAL *sig = msg->sigHandler->findSignalByName(ui->cbSignals->currentText()); + if (!sig) return; - if (sig == NULL) return; - - params.graphName = sig->name; - params.ID = msg->ID; - //params.bias = sig->bias; - //params.scale = sig->factor; - //params.stride = 1; - //params.mask = (1 << (sig->signalSize)) - 1; - //if (sig->valType == SIGNED_INT) params.isSigned = true; - //else params.isSigned = false; - params.color = ui->colorSwatch->palette().color(QPalette::Button); - /* - if (sig->intelByteOrder) - { - //for this ordering the byte order is reserved and starting byte - //will be the higher value - params.endByte = sig->startBit / 8; - params.startByte = (sig->startBit + sig->signalSize - 1) / 8; - } - else - { - //for this ordering it goes in normal numerical order - params.startByte = sig->startBit / 8; - params.endByte = (sig->startBit + sig->signalSize - 1) / 8; - } - */ - setParams(params); -} - -void NewGraphDialog::setSignalActive(bool state) -{ - if (!state) return; - ui->rbSignalGraph->setChecked(true); - ui->rbStandardGraph->setChecked(false); - ui->cbMessages->setEnabled(true); - ui->cbSignals->setEnabled(true); - ui->cbSigned->setEnabled(false); - ui->txtBias->setEnabled(false); - ui->txtData->setEnabled(false); - ui->txtID->setEnabled(false); - ui->txtMask->setEnabled(false); - ui->txtScale->setEnabled(false); - ui->txtStride->setEnabled(false); -} - -void NewGraphDialog::setStandardActive(bool state) -{ - if (!state) return; - ui->rbStandardGraph->setChecked(true); - ui->rbSignalGraph->setChecked(false); - ui->cbMessages->setEnabled(false); - ui->cbSignals->setEnabled(false); - ui->cbSigned->setEnabled(true); - ui->txtBias->setEnabled(true); - ui->txtData->setEnabled(true); - ui->txtID->setEnabled(true); - ui->txtMask->setEnabled(true); - ui->txtScale->setEnabled(true); - ui->txtStride->setEnabled(true); + startBit = sig->startBit; + ui->txtBias->setText(QString::number(sig->bias)); + ui->txtDataLen->setText(QString::number(sig->signalSize)); + ui->txtID->setText(Utility::formatNumber(msg->ID)); + ui->txtMask->setText("0xFFFFFFFF"); + ui->txtName->setText(sig->name); + ui->txtScale->setText(QString::number(sig->factor)); + ui->txtStride->setText("1"); + ui->cbIntel->setChecked(sig->intelByteOrder); + if (sig->valType == SIGNED_INT) ui->cbSigned->setChecked(true); + else ui->cbSigned->setChecked(false); + drawBitfield(); } diff --git a/newgraphdialog.h b/newgraphdialog.h index 67fe5f0..907a03c 100644 --- a/newgraphdialog.h +++ b/newgraphdialog.h @@ -16,6 +16,7 @@ class NewGraphDialog : public QDialog public: explicit NewGraphDialog(DBCHandler *handler, QWidget *parent = 0); ~NewGraphDialog(); + void showEvent(QShowEvent*); void getParams(GraphParams &); void setParams(GraphParams &); void clearParams(); @@ -25,13 +26,15 @@ private slots: void colorSwatchClick(); void loadMessages(); void loadSignals(int idx); - void fillFormFromSignal(int idx); - void setSignalActive(bool); - void setStandardActive(bool); + void bitfieldClicked(int,int); + void handleDataLenUpdate(); + void drawBitfield(); + void copySignalToParamsUI(); private: Ui::NewGraphDialog *ui; DBCHandler *dbcHandler; + int startBit, dataLen; }; #endif // NEWGRAPHDIALOG_H diff --git a/newgraphdialog.ui b/newgraphdialog.ui index dcb7f12..0111816 100644 --- a/newgraphdialog.ui +++ b/newgraphdialog.ui @@ -6,8 +6,8 @@ 0 0 - 492 - 340 + 681 + 640 @@ -15,31 +15,7 @@ - - - - - Standard Graph - - - true - - - - - - - DBC Signal Graph - - - false - - - - - - - + @@ -55,28 +31,25 @@ - Data: + Start Bit: - - - - + Signed: - + - + QFrame::NoFrame @@ -86,47 +59,47 @@ - + - + Bias: - + - + Scale - + - + Stride - + - + Color - + true @@ -139,7 +112,7 @@ - + Add this graph @@ -156,6 +129,46 @@ + + + + + 0 + 290 + + + + + 150 + 150 + + + + + + + + Intel Format + + + + + + + + + + + + + + Data Len: + + + + + + @@ -214,12 +227,40 @@ + + + + Copy Signal Parameters + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + CANDataGrid + QWidget +
candatagrid.h
+ 1 +
+
From b0902c8f50f4d16f2d9af6c09216048f12f05d3f Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Wed, 11 May 2016 20:38:33 -0400 Subject: [PATCH 22/26] Fixed errors within last commit. Graphing works pretty well now with the new system. --- graphingwindow.cpp | 2 +- newgraphdialog.cpp | 2 ++ utility.h | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/graphingwindow.cpp b/graphingwindow.cpp index e5bbf64..3c45263 100644 --- a/graphingwindow.cpp +++ b/graphingwindow.cpp @@ -859,7 +859,7 @@ void GraphingWindow::createGraph(GraphParams ¶ms, bool createGraphParam) for (int j = 0; j < numEntries; j++) { - tempVal = Utility::processIntegerSignal(frameCache[j /** params.stride*/].data, sBit, bits, intelFormat, isSigned) & params.mask; + tempVal = Utility::processIntegerSignal(frameCache[j * params.stride].data, sBit, bits, intelFormat, isSigned); //& params.mask; //qDebug() << tempVal; if (secondsMode) { diff --git a/newgraphdialog.cpp b/newgraphdialog.cpp index 5f23f27..f92ee8b 100644 --- a/newgraphdialog.cpp +++ b/newgraphdialog.cpp @@ -78,6 +78,7 @@ void NewGraphDialog::setParams(GraphParams ¶ms) ui->txtScale->setText(QString::number(params.scale)); ui->txtStride->setText(QString::number(params.stride)); ui->cbSigned->setChecked(params.isSigned); + ui->cbIntel->setChecked(params.intelFormat); startBit = params.startBit; dataLen = params.numBits; @@ -99,6 +100,7 @@ void NewGraphDialog::getParams(GraphParams ¶ms) params.ID = Utility::ParseStringToNum(ui->txtID->text()); params.bias = ui->txtBias->text().toFloat(); params.isSigned = ui->cbSigned->isChecked(); + params.intelFormat = ui->cbIntel->isChecked(); params.mask = Utility::ParseStringToNum(ui->txtMask->text()); params.scale = ui->txtScale->text().toFloat(); params.stride = Utility::ParseStringToNum(ui->txtStride->text()); diff --git a/utility.h b/utility.h index 0479172..d5614e8 100644 --- a/utility.h +++ b/utility.h @@ -178,7 +178,7 @@ public: * actual signal. This extends the sign bits out so that the integer result reads as the proper negative * value. We dont need to do any of this if the sign bit wasn't set. */ - int64_t signedMask = ~((1ULL << sigSize) - 1); + uint64_t signedMask = ~((1ULL << sigSize) - 1); result = (-1LL & signedMask) | result; } } From 4fd437a44ad7faea94b89cab7d93e0151311b5e5 Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Wed, 11 May 2016 21:46:14 -0400 Subject: [PATCH 23/26] Fixes and addition of code to be able to load old graph definitions. It's kind of broken when loading old standard graph definitions though. Otherwise graphing seems pretty good. --- graphingwindow.cpp | 117 ++++++++++++++++++++++++++++++++++++++++++++- newgraphdialog.cpp | 2 +- 2 files changed, 116 insertions(+), 3 deletions(-) diff --git a/graphingwindow.cpp b/graphingwindow.cpp index 3c45263..0deecd5 100644 --- a/graphingwindow.cpp +++ b/graphingwindow.cpp @@ -748,7 +748,7 @@ void GraphingWindow::loadDefinitions() QList tokens = line.split(','); - if (tokens[0] == "X") + if (tokens[0] == "X") //newest format based around signals { gp.ID = tokens[1].toInt(NULL, 16); gp.mask = tokens[2].toULongLong(NULL, 16); @@ -769,6 +769,119 @@ void GraphingWindow::loadDefinitions() gp.graphName = QString(); createGraph(gp, true); } + else //one of the two older formats then + { + gp.ID = tokens[0].toInt(NULL, 16); + if (tokens[1] == "S") //old signal based graph definition + { + //tokens[2] is the signal name. Need to use the message ID and this name to look it up + DBC_MESSAGE *msg = dbcHandler->getFileByIdx(0)->messageHandler->findMsgByID(gp.ID); + if (msg != NULL) + { + DBC_SIGNAL *sig = msg->sigHandler->findSignalByName(tokens[2]); + if (sig) + { + gp.mask = 0xFFFFFFFF; + gp.bias = sig->bias; + gp.color.setRed(tokens[3].toInt()); + gp.color.setGreen(tokens[4].toInt()); + gp.color.setBlue(tokens[5].toInt()); + gp.graphName = sig->name; + gp.intelFormat = sig->intelByteOrder; + if (sig->valType == SIGNED_INT) gp.isSigned = true; + else gp.isSigned = false; + gp.numBits = sig->signalSize; + gp.scale = sig->factor; + gp.startBit = sig->startBit; + gp.stride = 1; + createGraph(gp, true); + } + } + } + else //old standard graph definition + { + //hard part - this all changed drastically + //the difference between intel and motorola format is whether + //start is larger than end byte or not. + uint64_t oldMask = tokens[1].toULongLong(NULL, 16); + int oldStart = tokens[2].toInt(); + int oldEnd = tokens[3].toInt(); + + if (oldEnd > oldStart) //motorola / big endian - hell... + { + gp.intelFormat = false; + //for now just naively use the entire bytes called for. + gp.startBit = 8 * oldStart + 7; + gp.numBits = (oldEnd - oldStart + 1) * 8; + } + else if (oldStart > oldEnd) //intel / little endian - easiest of multi-byte types + { + //have to find both ends. start bit is somewhere in oldEnd and last bit is somewhere in + //oldStart. + + gp.intelFormat = true; + + //start by setting a safe default if nothing else pans out. + gp.startBit = 8 * oldEnd; + + int numBytes = oldStart - oldEnd + 1; + gp.numBits = numBytes * 8; + + for (int b = 0; b < 8; b++) + { + if (oldMask & (1 << b)) + { + gp.startBit = (8 * oldEnd) + b; + break; + } + } + + for (int c = 7; c >= 0; c--) + { + if ( oldMask & (1<<(((numBytes - 1) * 8) + c)) ) + { + gp.numBits -= (7-c); + break; + } + } + } + else //within a single byte - easier than the above two by a bit - always use intel format for this + { + gp.intelFormat = true; + oldMask = oldMask & 0xFF; //only this part matters + //for intel format we give startbit as the lowest bit number in the signal + //we can find that by going backward from bit 0 to 7 and picking the first bit that is 1. + //that's our start bit (+ 8*oldStart) + //set default first in case the rest falls through + gp.startBit = 8 * oldStart; + gp.numBits = 8; + for (int b = 0; b < 8; b++) + { + if (oldMask & (1 << b)) + { + gp.startBit = 8 * oldStart + b; + gp.numBits = 8 - b; + break; + } + } + } + + //the rest is easy stuff + if (tokens[4] == "Y") gp.isSigned = true; + else gp.isSigned = false; + gp.bias = tokens[5].toFloat(); + gp.scale = tokens[6].toFloat(); + gp.stride = tokens[7].toInt(); + gp.color.setRed(tokens[8].toInt()); + gp.color.setGreen(tokens[9].toInt()); + gp.color.setBlue(tokens[10].toInt()); + if (tokens.length() > 11) + gp.graphName = tokens[11]; + else + gp.graphName = QString(); + createGraph(gp, true); + } + } } } inFile->close(); @@ -808,7 +921,7 @@ void GraphingWindow::addNewGraph() void GraphingWindow::appendToGraph(GraphParams ¶ms, CANFrame &frame) { int64_t tempVal; //64 bit temp value. - tempVal = Utility::processIntegerSignal(frame.data, params.startBit, params.numBits, params.intelFormat, params.isSigned) & params.mask; + tempVal = Utility::processIntegerSignal(frame.data, params.startBit, params.numBits, params.intelFormat, params.isSigned); //& params.mask; if (secondsMode) { params.x.append((double)(frame.timestamp) / 1000000.0 - params.xbias); diff --git a/newgraphdialog.cpp b/newgraphdialog.cpp index f92ee8b..79da03e 100644 --- a/newgraphdialog.cpp +++ b/newgraphdialog.cpp @@ -82,7 +82,7 @@ void NewGraphDialog::setParams(GraphParams ¶ms) startBit = params.startBit; dataLen = params.numBits; - + ui->txtDataLen->setText(QString::number(dataLen)); ui->txtID->setText(Utility::formatNumber(params.ID)); ui->txtName->setText(params.graphName); QPalette p = ui->colorSwatch->palette(); From 58f62f896b6b4e9999e48c4be0b99fa51ab17475 Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Tue, 31 May 2016 18:51:08 -0400 Subject: [PATCH 24/26] Basically do nothing commit to fix up encoding for a couple of files. --- SavvyCAN.pro | 228 +++++++++++----------- docs/make.bat | 526 +++++++++++++++++++++++++------------------------- 2 files changed, 377 insertions(+), 377 deletions(-) diff --git a/SavvyCAN.pro b/SavvyCAN.pro index 1b59288..6040390 100644 --- a/SavvyCAN.pro +++ b/SavvyCAN.pro @@ -1,114 +1,114 @@ -#------------------------------------------------- -# -# Project created by QtCreator 2015-04-25T22:57:44 -# -#------------------------------------------------- - -QT += core gui - -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets serialport printsupport qml - -CONFIG(release, debug|release):DEFINES += QT_NO_DEBUG_OUTPUT - -CONFIG += c++11 qscintilla2 - -TARGET = SavvyCAN -TEMPLATE = app - - -SOURCES += main.cpp\ - mainwindow.cpp \ - canframemodel.cpp \ - utility.cpp \ - qcustomplot.cpp \ - graphingwindow.cpp \ - frameinfowindow.cpp \ - newgraphdialog.cpp \ - frameplaybackwindow.cpp \ - serialworker.cpp \ - candatagrid.cpp \ - flowviewwindow.cpp \ - framesenderwindow.cpp \ - dbchandler.cpp \ - dbcmaineditor.cpp \ - dbcsignaleditor.cpp \ - framefileio.cpp \ - filecomparatorwindow.cpp \ - mainsettingsdialog.cpp \ - firmwareuploaderwindow.cpp \ - discretestatewindow.cpp \ - connectionwindow.cpp \ - scriptingwindow.cpp \ - scriptcontainer.cpp \ - canfilter.cpp \ - rangestatewindow.cpp \ - dbc_classes.cpp \ - dbcloadsavewindow.cpp \ - fuzzingwindow.cpp \ - udsscanwindow.cpp \ - can_structs.cpp \ - isotp_interpreterwindow.cpp \ - isotp_handler.cpp - -HEADERS += mainwindow.h \ - can_structs.h \ - canframemodel.h \ - utility.h \ - qcustomplot.h \ - graphingwindow.h \ - frameinfowindow.h \ - newgraphdialog.h \ - frameplaybackwindow.h \ - serialworker.h \ - candatagrid.h \ - flowviewwindow.h \ - framesenderwindow.h \ - can_trigger_structs.h \ - dbc_classes.h \ - dbchandler.h \ - dbcmaineditor.h \ - dbcsignaleditor.h \ - framefileio.h \ - config.h \ - filecomparatorwindow.h \ - mainsettingsdialog.h \ - firmwareuploaderwindow.h \ - discretestatewindow.h \ - connectionwindow.h \ - scriptingwindow.h \ - scriptcontainer.h \ - canfilter.h \ - rangestatewindow.h \ - dbcloadsavewindow.h \ - fuzzingwindow.h \ - udsscanwindow.h \ - isotp_interpreterwindow.h \ - isotp_handler.h - -FORMS += mainwindow.ui \ - graphingwindow.ui \ - frameinfowindow.ui \ - newgraphdialog.ui \ - frameplaybackwindow.ui \ - candatagrid.ui \ - flowviewwindow.ui \ - framesenderwindow.ui \ - dbcmaineditor.ui \ - dbcsignaleditor.ui \ - filecomparatorwindow.ui \ - mainsettingsdialog.ui \ - firmwareuploaderwindow.ui \ - discretestatewindow.ui \ - connectionwindow.ui \ - scriptingwindow.ui \ - rangestatewindow.ui \ - dbcloadsavewindow.ui \ - fuzzingwindow.ui \ - udsscanwindow.ui \ - isotp_interpreterwindow.ui - -DISTFILES += - -RESOURCES += \ - icons.qrc \ - images.qrc +#------------------------------------------------- +# +# Project created by QtCreator 2015-04-25T22:57:44 +# +#------------------------------------------------- + +QT += core gui + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets serialport printsupport qml + +CONFIG(release, debug|release):DEFINES += QT_NO_DEBUG_OUTPUT + +CONFIG += c++11 qscintilla2 + +TARGET = SavvyCAN +TEMPLATE = app + + +SOURCES += main.cpp\ + mainwindow.cpp \ + canframemodel.cpp \ + utility.cpp \ + qcustomplot.cpp \ + graphingwindow.cpp \ + frameinfowindow.cpp \ + newgraphdialog.cpp \ + frameplaybackwindow.cpp \ + serialworker.cpp \ + candatagrid.cpp \ + flowviewwindow.cpp \ + framesenderwindow.cpp \ + dbchandler.cpp \ + dbcmaineditor.cpp \ + dbcsignaleditor.cpp \ + framefileio.cpp \ + filecomparatorwindow.cpp \ + mainsettingsdialog.cpp \ + firmwareuploaderwindow.cpp \ + discretestatewindow.cpp \ + connectionwindow.cpp \ + scriptingwindow.cpp \ + scriptcontainer.cpp \ + canfilter.cpp \ + rangestatewindow.cpp \ + dbc_classes.cpp \ + dbcloadsavewindow.cpp \ + fuzzingwindow.cpp \ + udsscanwindow.cpp \ + can_structs.cpp \ + isotp_interpreterwindow.cpp \ + isotp_handler.cpp + +HEADERS += mainwindow.h \ + can_structs.h \ + canframemodel.h \ + utility.h \ + qcustomplot.h \ + graphingwindow.h \ + frameinfowindow.h \ + newgraphdialog.h \ + frameplaybackwindow.h \ + serialworker.h \ + candatagrid.h \ + flowviewwindow.h \ + framesenderwindow.h \ + can_trigger_structs.h \ + dbc_classes.h \ + dbchandler.h \ + dbcmaineditor.h \ + dbcsignaleditor.h \ + framefileio.h \ + config.h \ + filecomparatorwindow.h \ + mainsettingsdialog.h \ + firmwareuploaderwindow.h \ + discretestatewindow.h \ + connectionwindow.h \ + scriptingwindow.h \ + scriptcontainer.h \ + canfilter.h \ + rangestatewindow.h \ + dbcloadsavewindow.h \ + fuzzingwindow.h \ + udsscanwindow.h \ + isotp_interpreterwindow.h \ + isotp_handler.h + +FORMS += mainwindow.ui \ + graphingwindow.ui \ + frameinfowindow.ui \ + newgraphdialog.ui \ + frameplaybackwindow.ui \ + candatagrid.ui \ + flowviewwindow.ui \ + framesenderwindow.ui \ + dbcmaineditor.ui \ + dbcsignaleditor.ui \ + filecomparatorwindow.ui \ + mainsettingsdialog.ui \ + firmwareuploaderwindow.ui \ + discretestatewindow.ui \ + connectionwindow.ui \ + scriptingwindow.ui \ + rangestatewindow.ui \ + dbcloadsavewindow.ui \ + fuzzingwindow.ui \ + udsscanwindow.ui \ + isotp_interpreterwindow.ui + +DISTFILES += + +RESOURCES += \ + icons.qrc \ + images.qrc diff --git a/docs/make.bat b/docs/make.bat index da8b07a..22da6bc 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -1,263 +1,263 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source -set I18NSPHINXOPTS=%SPHINXOPTS% source -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% - set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. texinfo to make Texinfo files - echo. gettext to make PO message catalogs - echo. changes to make an overview over all changed/added/deprecated items - echo. xml to make Docutils-native XML files - echo. pseudoxml to make pseudoxml-XML files for display purposes - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - echo. coverage to run coverage check of the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - - -REM Check if sphinx-build is available and fallback to Python version if any -%SPHINXBUILD% 2> nul -if errorlevel 9009 goto sphinx_python -goto sphinx_ok - -:sphinx_python - -set SPHINXBUILD=python -m sphinx.__init__ -%SPHINXBUILD% 2> nul -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -:sphinx_ok - - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\SavvyCAN.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\SavvyCAN.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdf" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "latexpdfja" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - cd %BUILDDIR%/latex - make all-pdf-ja - cd %~dp0 - echo. - echo.Build finished; the PDF files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "texinfo" ( - %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. - goto end -) - -if "%1" == "gettext" ( - %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The message catalogs are in %BUILDDIR%/locale. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -if "%1" == "coverage" ( - %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage - if errorlevel 1 exit /b 1 - echo. - echo.Testing of coverage in the sources finished, look at the ^ -results in %BUILDDIR%/coverage/python.txt. - goto end -) - -if "%1" == "xml" ( - %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The XML files are in %BUILDDIR%/xml. - goto end -) - -if "%1" == "pseudoxml" ( - %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. - goto end -) - -:end +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source +set I18NSPHINXOPTS=%SPHINXOPTS% source +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 2> nul +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\SavvyCAN.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\SavvyCAN.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end From be203ffef356328469e074185aef98d2d5dc65ab Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Tue, 31 May 2016 19:55:20 -0400 Subject: [PATCH 25/26] Previous commit with merge of fixes from another user added code in the wrong place for this branch and I didn't catch it. Fixed now. --- mainwindow.cpp | 10 ++++++++-- serialworker.cpp | 12 ------------ 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/mainwindow.cpp b/mainwindow.cpp index e2e41dd..7590f1d 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -515,8 +515,14 @@ void MainWindow::filterClearAll() void MainWindow::tickGUIUpdate() { - framesPerSec += rxFrames * 1000 / elapsedTime->elapsed() - (framesPerSec / 4); - elapsedTime->restart(); + int elapsed = elapsedTime->elapsed(); + if(elapsed) { + framesPerSec += rxFrames * 1000 / elapsed - (framesPerSec / 4); + elapsedTime->restart(); + } + else + framesPerSec = 0; + model->sendBulkRefresh(rxFrames); ui->lbNumFrames->setText(QString::number(model->rowCount())); diff --git a/serialworker.cpp b/serialworker.cpp index fdf5f4c..61a0b5f 100644 --- a/serialworker.cpp +++ b/serialworker.cpp @@ -478,18 +478,6 @@ void SerialWorker::handleTick() } } - int elapsed = elapsedTime->elapsed(); - if(elapsed) { - framesPerSec += gotFrames * 1000 / elapsed - (framesPerSec / 4); - elapsedTime->restart(); - } - else - framesPerSec = 0; - - emit frameUpdateTick(framesPerSec / 4, gotFrames); //sends stats to interested parties - canModel->sendBulkRefresh(gotFrames); - gotFrames = 0; - if (doValidation && serial && serial->isOpen()) sendCommValidation(); } From 9e37ded7cb7faadcba869564aa017d3a0de8c7fb Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Sun, 5 Jun 2016 22:08:47 -0400 Subject: [PATCH 26/26] Fixes for many issues in flowview, graphing window, and frame playback. In FlowView: - Drastically improved functionality of live mode. It actually works now - Fixed several crash bugs - Program didn't used to pick up new frames properly. This is now corrected. In Graphing window: - Fixed dynamic updates to work properly even under heavy bus loads In frame playback: - Fixed a bug where single stepping through frames didn't send them over canbus --- SavvyCAN.pro | 11 ++++-- config.h | 2 +- deltamodel.cpp | 36 ++++++++++++++++++++ deltamodel.h | 25 ++++++++++++++ deltawindow.cpp | 14 ++++++++ deltawindow.h | 22 ++++++++++++ deltawindow.ui | 24 +++++++++++++ flowviewwindow.cpp | 74 +++++++++++++++++++++++++++++++---------- flowviewwindow.h | 2 ++ frameplaybackwindow.cpp | 4 +++ graphingwindow.cpp | 12 +++++-- 11 files changed, 202 insertions(+), 24 deletions(-) create mode 100644 deltamodel.cpp create mode 100644 deltamodel.h create mode 100644 deltawindow.cpp create mode 100644 deltawindow.h create mode 100644 deltawindow.ui diff --git a/SavvyCAN.pro b/SavvyCAN.pro index 6040390..07fc638 100644 --- a/SavvyCAN.pro +++ b/SavvyCAN.pro @@ -48,7 +48,9 @@ SOURCES += main.cpp\ udsscanwindow.cpp \ can_structs.cpp \ isotp_interpreterwindow.cpp \ - isotp_handler.cpp + isotp_handler.cpp \ + deltawindow.cpp \ + deltamodel.cpp HEADERS += mainwindow.h \ can_structs.h \ @@ -83,7 +85,9 @@ HEADERS += mainwindow.h \ fuzzingwindow.h \ udsscanwindow.h \ isotp_interpreterwindow.h \ - isotp_handler.h + isotp_handler.h \ + deltawindow.h \ + deltamodel.h FORMS += mainwindow.ui \ graphingwindow.ui \ @@ -105,7 +109,8 @@ FORMS += mainwindow.ui \ dbcloadsavewindow.ui \ fuzzingwindow.ui \ udsscanwindow.ui \ - isotp_interpreterwindow.ui + isotp_interpreterwindow.ui \ + deltawindow.ui DISTFILES += diff --git a/config.h b/config.h index 33378ea..12accfa 100644 --- a/config.h +++ b/config.h @@ -1,7 +1,7 @@ #ifndef CONFIG #define CONFIG -#define VERSION 155 +#define VERSION 156 //try to keep this in sync. //SavvyCAN will complain if you connect a GVRET board with a revision diff --git a/deltamodel.cpp b/deltamodel.cpp new file mode 100644 index 0000000..98f88fa --- /dev/null +++ b/deltamodel.cpp @@ -0,0 +1,36 @@ +#include "deltamodel.h" + +DeltaModel::DeltaModel(QObject *parent) + : QAbstractTableModel(parent) +{ +} + +QVariant DeltaModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + // FIXME: Implement me! +} + +int DeltaModel::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) + return 0; + + // FIXME: Implement me! +} + +int DeltaModel::columnCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) + return 0; + + // FIXME: Implement me! +} + +QVariant DeltaModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + // FIXME: Implement me! + return QVariant(); +} diff --git a/deltamodel.h b/deltamodel.h new file mode 100644 index 0000000..c0444d8 --- /dev/null +++ b/deltamodel.h @@ -0,0 +1,25 @@ +#ifndef DELTAMODEL_H +#define DELTAMODEL_H + +#include + +class DeltaModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + explicit DeltaModel(QObject *parent = 0); + + // Header: + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + // Basic functionality: + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +private: +}; + +#endif // DELTAMODEL_H \ No newline at end of file diff --git a/deltawindow.cpp b/deltawindow.cpp new file mode 100644 index 0000000..8a1b3c0 --- /dev/null +++ b/deltawindow.cpp @@ -0,0 +1,14 @@ +#include "deltawindow.h" +#include "ui_deltawindow.h" + +DeltaWindow::DeltaWindow(QWidget *parent) : + QDialog(parent), + ui(new Ui::DeltaWindow) +{ + ui->setupUi(this); +} + +DeltaWindow::~DeltaWindow() +{ + delete ui; +} diff --git a/deltawindow.h b/deltawindow.h new file mode 100644 index 0000000..e9c30cf --- /dev/null +++ b/deltawindow.h @@ -0,0 +1,22 @@ +#ifndef DELTAWINDOW_H +#define DELTAWINDOW_H + +#include + +namespace Ui { +class DeltaWindow; +} + +class DeltaWindow : public QDialog +{ + Q_OBJECT + +public: + explicit DeltaWindow(QWidget *parent = 0); + ~DeltaWindow(); + +private: + Ui::DeltaWindow *ui; +}; + +#endif // DELTAWINDOW_H diff --git a/deltawindow.ui b/deltawindow.ui new file mode 100644 index 0000000..3a67a5e --- /dev/null +++ b/deltawindow.ui @@ -0,0 +1,24 @@ + + + DeltaWindow + + + + 0 + 0 + 978 + 654 + + + + Dialog + + + + + + + + + + diff --git a/flowviewwindow.cpp b/flowviewwindow.cpp index 424925e..f443d49 100644 --- a/flowviewwindow.cpp +++ b/flowviewwindow.cpp @@ -158,9 +158,9 @@ void FlowViewWindow::writeSettings() /* * Keyboard shortcuts to allow for quick work without needing to move around a mouse. - * E = resume or pause playback - * Q = go back one frame - * W = go forward one frame + * R = resume or pause playback + * T = go back one frame + * Y = go forward one frame */ bool FlowViewWindow::eventFilter(QObject *obj, QEvent *event) { @@ -371,27 +371,64 @@ void FlowViewWindow::updatedFrames(int numFrames) else //just got some new frames. See if they are relevant. { if (numFrames > modelFrames->count()) return; - int refID = frameCache[0].ID; + int refID; + if (frameCache.count() > 0) refID = frameCache[0].ID; + else refID = 0; bool needRefresh = false; for (int i = modelFrames->count() - numFrames; i < modelFrames->count(); i++) { thisFrame = modelFrames->at(i); + + if (!foundID.contains(thisFrame.ID)) + { + foundID.append(thisFrame.ID); + QListWidgetItem* item = new QListWidgetItem(Utility::formatNumber(thisFrame.ID), ui->listFrameID); + } + if (thisFrame.ID == refID) { frameCache.append(thisFrame); - if (ui->cbLiveMode->checkState() == Qt::Checked) + + for (int k = 0; k < 8; k++) { - currentPosition = frameCache.count() - 1; + if (ui->cbTimeGraph->isChecked()) + { + if (secondsMode){ + x[k].append((double)(thisFrame.timestamp) / 1000000.0); + } + else + { + x[k].append(thisFrame.timestamp); + } + } + else + { + x[k].append(x[k].count()); + } + y[k].append(thisFrame.data[k]); needRefresh = true; } - } + } + } + if (ui->cbLiveMode->checkState() == Qt::Checked) + { + currentPosition = frameCache.count() - 1; + memcpy(currBytes, frameCache.at(currentPosition).data, 8); + memcpy(refBytes, currBytes, 8); + } if (needRefresh) { - updateDataView(); + for (int k = 0; k < 8; k++) + { + graphRef[k]->setData(x[k], y[k]); + } + ui->graphView->replot(); + updateDataView(); if (ui->cbSync->checkState() == Qt::Checked) emit sendCenterTimeID(frameCache[currentPosition].ID, frameCache[currentPosition].timestamp / 1000000.0); } } + updateFrameLabel(); } void FlowViewWindow::removeAllGraphs() @@ -409,7 +446,10 @@ void FlowViewWindow::createGraph(int byteNum) int numEntries = frameCache.count(); - QVector x(numEntries), y(numEntries); + x[byteNum].clear(); + y[byteNum].clear(); + x[byteNum].resize(numEntries); + y[byteNum].resize(numEntries); for (int j = 0; j < numEntries; j++) { @@ -418,26 +458,26 @@ void FlowViewWindow::createGraph(int byteNum) if (graphByTime) { if (secondsMode){ - x[j] = (double)(frameCache[j].timestamp) / 1000000.0; + x[byteNum][j] = (double)(frameCache[j].timestamp) / 1000000.0; } else { - x[j] = frameCache[j].timestamp; + x[byteNum][j] = frameCache[j].timestamp; } } else { - x[j] = j; + x[byteNum][j] = j; } - y[j] = tempVal; - if (y[j] < minval) minval = y[j]; - if (y[j] > maxval) maxval = y[j]; + y[byteNum][j] = tempVal; + if (y[byteNum][j] < minval) minval = y[byteNum][j]; + if (y[byteNum][j] > maxval) maxval = y[byteNum][j]; } - ui->graphView->addGraph(); + graphRef[byteNum] = ui->graphView->addGraph(); ui->graphView->graph()->setName(QString("Graph %1").arg(ui->graphView->graphCount()-1)); - ui->graphView->graph()->setData(x,y); + ui->graphView->graph()->setData(x[byteNum],y[byteNum]); ui->graphView->graph()->setLineStyle(QCPGraph::lsLine); //connect points with lines QPen graphPen; graphPen.setColor(graphColors[byteNum]); diff --git a/flowviewwindow.h b/flowviewwindow.h index 78f5b71..165a13e 100644 --- a/flowviewwindow.h +++ b/flowviewwindow.h @@ -55,6 +55,8 @@ private: bool playbackForward; static const QColor graphColors[8]; bool secondsMode; + QVector x[8], y[8]; + QCPGraph *graphRef[8]; void refreshIDList(); void updateFrameLabel(); diff --git a/frameplaybackwindow.cpp b/frameplaybackwindow.cpp index 765f482..be6dae7 100644 --- a/frameplaybackwindow.cpp +++ b/frameplaybackwindow.cpp @@ -400,10 +400,12 @@ void FramePlaybackWindow::btnLoadLive() void FramePlaybackWindow::btnBackOneClick() { + sendingBuffer.clear(); playbackTimer->stop(); //pushing this button halts automatic playback playbackActive = false; updatePosition(false); + emit sendFrameBatch(&sendingBuffer); } void FramePlaybackWindow::btnPauseClick() @@ -450,9 +452,11 @@ void FramePlaybackWindow::btnPlayClick() void FramePlaybackWindow::btnFwdOneClick() { + sendingBuffer.clear(); playbackTimer->stop(); playbackActive = false; updatePosition(true); + emit sendFrameBatch(&sendingBuffer); } void FramePlaybackWindow::changePlaybackSpeed(int newSpeed) diff --git a/graphingwindow.cpp b/graphingwindow.cpp index 0deecd5..6aff9ec 100644 --- a/graphingwindow.cpp +++ b/graphingwindow.cpp @@ -142,6 +142,7 @@ void GraphingWindow::updatedFrames(int numFrames) } else //just got some new frames. See if they are relevant. { + bool appendedToGraph = false; if (numFrames > modelFrames->count()) return; for (int i = modelFrames->count() - numFrames; i < modelFrames->count(); i++) { @@ -151,10 +152,17 @@ void GraphingWindow::updatedFrames(int numFrames) if (graphParams[j].ID == thisFrame.ID) { appendToGraph(graphParams[j], thisFrame); + appendedToGraph = true; } } } - ui->graphingView->replot(); + if (appendedToGraph) { + for (int j = 0; j < graphParams.count(); j++) + { + graphParams[j].ref->setData(graphParams[j].x, graphParams[j].y); + } + ui->graphingView->replot(); + } } } @@ -931,8 +939,6 @@ void GraphingWindow::appendToGraph(GraphParams ¶ms, CANFrame &frame) params.x.append(frame.timestamp - params.xbias); } params.y.append((tempVal * params.scale) + params.bias); - - params.ref->setData(params.x,params.y); } void GraphingWindow::createGraph(GraphParams ¶ms, bool createGraphParam)