diff --git a/README.md b/README.md index 18a6712..ff088d2 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,6 +50,9 @@ http://www.qcustomplot.com/ However, this source code is integrated into the source for SavvyCAN and one isn't required to download it separately. +This project will soon require 5.6.0 or higher because of an upcoming dependency on QSerialBus but currently +should compile with QT 5.5 + Instructions for compiling with Ubuntu: sudo apt-get install qt5-default qtdeclarative5-dev libqt5serialport5-dev libudev-dev diff --git a/SavvyCAN.pro b/SavvyCAN.pro index 8d64e4d..07fc638 100644 --- a/SavvyCAN.pro +++ b/SavvyCAN.pro @@ -46,7 +46,11 @@ SOURCES += main.cpp\ dbcloadsavewindow.cpp \ fuzzingwindow.cpp \ udsscanwindow.cpp \ - can_structs.cpp + can_structs.cpp \ + isotp_interpreterwindow.cpp \ + isotp_handler.cpp \ + deltawindow.cpp \ + deltamodel.cpp HEADERS += mainwindow.h \ can_structs.h \ @@ -79,7 +83,11 @@ HEADERS += mainwindow.h \ rangestatewindow.h \ dbcloadsavewindow.h \ fuzzingwindow.h \ - udsscanwindow.h + udsscanwindow.h \ + isotp_interpreterwindow.h \ + isotp_handler.h \ + deltawindow.h \ + deltamodel.h FORMS += mainwindow.ui \ graphingwindow.ui \ @@ -100,7 +108,9 @@ FORMS += mainwindow.ui \ rangestatewindow.ui \ dbcloadsavewindow.ui \ fuzzingwindow.ui \ - udsscanwindow.ui + udsscanwindow.ui \ + isotp_interpreterwindow.ui \ + deltawindow.ui DISTFILES += diff --git a/can_structs.h b/can_structs.h index e9668bd..0326947 100644 --- a/can_structs.h +++ b/can_structs.h @@ -29,14 +29,15 @@ public: }; //the same as the CANFrame struct but with arbitrary data size. -struct ISOTP_FRAME +struct ISOTP_MESSAGE { public: int ID; 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/canframemodel.cpp b/canframemodel.cpp index 98385cf..fe54877 100644 --- a/canframemodel.cpp +++ b/canframemodel.cpp @@ -6,12 +6,23 @@ int CANFrameModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); - 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 + qDebug() << "Invalid data for filteredFrames. Returning 0."; + return 0; } int CANFrameModel::totalFrameCount() { - return frames.count(); + int count; + count = frames.count(); + return count; } int CANFrameModel::columnCount(const QModelIndex &index) const @@ -23,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; @@ -75,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++) @@ -87,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) @@ -120,6 +135,7 @@ void CANFrameModel::recalcOverwrite() int lastUnique = 0; bool found; + mutex.lock(); beginResetModel(); for (int i = 1; i < frames.count(); i++) { @@ -143,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++) { @@ -153,6 +170,7 @@ void CANFrameModel::recalcOverwrite() } endResetModel(); + mutex.unlock(); } QVariant CANFrameModel::data(const QModelIndex &index, int role) const @@ -327,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++) { @@ -340,8 +356,11 @@ void CANFrameModel::sendRefresh() tempContainer.append(frames[i]); } } + mutex.lock(); + beginResetModel(); filteredFrames.clear(); - filteredFrames = tempContainer; + filteredFrames.append(tempContainer); + filteredFrames.reserve(50000000); lastUpdateNumFrames = filteredFrames.count(); endResetModel(); mutex.unlock(); @@ -376,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 { @@ -399,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(); diff --git a/config.h b/config.h index 8030d77..12accfa 100644 --- a/config.h +++ b/config.h @@ -1,7 +1,7 @@ #ifndef CONFIG #define CONFIG -#define VERSION 151 +#define VERSION 156 //try to keep this in sync. //SavvyCAN will complain if you connect a GVRET board with a revision 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; 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/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/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 - - - diff --git a/flowviewwindow.cpp b/flowviewwindow.cpp index d4fe69d..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]); @@ -656,6 +696,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; 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/framefileio.cpp b/framefileio.cpp index b1192ea..2e96149 100644 --- a/framefileio.cpp +++ b/framefileio.cpp @@ -1,5 +1,6 @@ #include "framefileio.h" +#include #include #include @@ -163,7 +164,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; } @@ -179,6 +186,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)) { @@ -193,6 +201,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) @@ -203,30 +213,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) @@ -262,6 +275,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)) { @@ -283,44 +297,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) @@ -384,6 +402,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)) { @@ -406,47 +425,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) @@ -514,6 +538,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)) { @@ -549,10 +574,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 @@ -635,6 +661,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)) { @@ -657,24 +684,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) @@ -748,6 +781,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)) { @@ -769,27 +803,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) @@ -856,6 +911,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)) { @@ -891,7 +947,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) @@ -899,11 +955,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) @@ -977,6 +1034,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)) { @@ -1006,24 +1064,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; } /* @@ -1126,6 +1191,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)) { @@ -1152,31 +1218,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) @@ -1348,5 +1419,9 @@ bool FrameFileIO::loadCanDumpFile(QString filename, QVector* frames) QString FrameFileIO::unQuote(QString inStr) { - return inStr.split('\"')[1]; + QStringList temp; + temp = inStr.split('\"'); + if (temp.length() >= 3) + return temp[1]; + return QString(""); } 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++) { 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/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); diff --git a/graphingwindow.cpp b/graphingwindow.cpp index afe9c4d..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(); + } } } @@ -685,33 +693,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 +728,6 @@ void GraphingWindow::loadDefinitions() { QString filename; QFileDialog dialog; - bool dbcMissing = false; QStringList filters; filters.append(QString(tr("Graph definition (*.gdf)"))); @@ -755,66 +753,146 @@ 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") //newest format based around signals { - 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 + else //one of the two older formats then { - gp.color.setRed(tokens[3].toInt()); - gp.color.setGreen(tokens[4].toInt()); - gp.color.setBlue(tokens[5].toInt()); - gp.graphName = tokens[6]; + 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); + } } - 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,135 +929,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.ref->setData(params.x,params.y); + params.y.append((tempVal * params.scale) + params.bias); } void GraphingWindow::createGraph(GraphParams ¶ms, bool createGraphParam) @@ -988,23 +947,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 +971,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 +1009,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/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 new file mode 100644 index 0000000..778a2d8 --- /dev/null +++ b/isotp_interpreterwindow.cpp @@ -0,0 +1,154 @@ +#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_HANDLER(modelFrames); + + connect(MainWindow::getReference(), &MainWindow::framesUpdated, this, &ISOTP_InterpreterWindow::updatedFrames); + 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); + + 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::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(); + 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.count(); 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..ff099e1 --- /dev/null +++ b/isotp_interpreterwindow.h @@ -0,0 +1,38 @@ +#ifndef ISOTP_INTERPRETERWINDOW_H +#define ISOTP_INTERPRETERWINDOW_H + +#include +#include "can_structs.h" +#include "isotp_handler.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); + void showDetailView(); + void updatedFrames(int); + +private: + Ui::ISOTP_InterpreterWindow *ui; + ISOTP_HANDLER *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..3219b39 --- /dev/null +++ b/isotp_interpreterwindow.ui @@ -0,0 +1,123 @@ + + + ISOTP_InterpreterWindow + + + + 0 + 0 + 792 + 725 + + + + ISO-TP Decoder + + + + + + Interpreted Messages Overview + + + + + + + + + + Show incomplete and/or corrupted messages + + + + + + + + + + + Data Details of Selected Message + + + + + + + + + Interpret As: + + + + + + + Raw + + + true + + + + + + + UDS + + + + + + + OBDII + + + + + + + + + + + + + + + + IDs to Decode + + + + + + + + + + + + All + + + + + + + None + + + + + + + + + + + + + + diff --git a/mainwindow.cpp b/mainwindow.cpp index 71bba18..7590f1d 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); @@ -98,9 +98,12 @@ MainWindow::MainWindow(QWidget *parent) : dbcFileWindow = NULL; fuzzingWindow = NULL; udsScanWindow = NULL; + isoWindow = NULL; dbcHandler = new DBCHandler; bDirty = false; inhibitFilterUpdate = false; + rxFrames = 0; + framesPerSec = 0; model->setDBCHandler(dbcHandler); @@ -139,6 +142,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(); @@ -150,6 +154,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 +277,14 @@ MainWindow::~MainWindow() delete udsScanWindow; } + if (isoWindow) + { + isoWindow->close(); + delete isoWindow; + } + + delete elapsedTime; + delete ui; delete dbcHandler; model->clearFrames(); @@ -289,6 +308,7 @@ void MainWindow::exitApp() if (dbcFileWindow) dbcFileWindow->close(); if (fuzzingWindow) fuzzingWindow->close(); if (udsScanWindow) udsScanWindow->close(); + if (isoWindow) isoWindow->close(); this->close(); } @@ -493,20 +513,36 @@ 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() { + 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())); 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) @@ -864,6 +900,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 cf99eba..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(); @@ -75,6 +77,7 @@ private slots: void gridDoubleClicked(QModelIndex); void interpretToggled(bool); void overwriteToggled(bool); + void tickGUIUpdate(); void toggleCapture(); void normalizeTiming(); void updateFilterList(); @@ -83,7 +86,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 +101,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 +115,10 @@ private: QThread serialWorkerThread; SerialWorker *worker; QByteArray inputBuffer; + QTimer updateTimer; + QTime *elapsedTime; + int framesPerSec; + int rxFrames; bool inhibitFilterUpdate; bool useHex; bool allowCapture; @@ -135,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/newgraphdialog.cpp b/newgraphdialog.cpp index 409e2f9..79da03e 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,49 @@ 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(); - - } - 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)); - } - } + 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); + ui->cbIntel->setChecked(params.intelFormat); + 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(); 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.intelFormat = ui->cbIntel->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 +142,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 +
+
diff --git a/serialworker.cpp b/serialworker.cpp index 85c7ec6..61a0b5f 100644 --- a/serialworker.cpp +++ b/serialworker.cpp @@ -12,15 +12,15 @@ 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; targetID = -1; + txTimestampBasis = QDateTime::currentMSecsSinceEpoch(); + readSettings(); } @@ -45,9 +45,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(); @@ -118,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; @@ -148,6 +150,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) @@ -157,12 +164,13 @@ 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; //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; @@ -233,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; @@ -316,7 +325,9 @@ void SerialWorker::procRXChar(unsigned char c) { buildFrame->isReceived = true; canModel->addFrame(*buildFrame, false); - gotFrames++; + //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); } } @@ -324,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) { @@ -411,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; @@ -447,17 +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(); } diff --git a/serialworker.h b/serialworker.h index 2b71266..1658c04 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); @@ -71,14 +71,13 @@ private: bool doValidation; bool gotValidated; bool isAutoRestart; + bool continuousTimeSync; 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; @@ -87,6 +86,8 @@ private: bool can0Enabled, can1Enabled; int deviceBuildNum; int deviceSingleWireMode; + uint64_t txTimestampBasis; + uint32_t buildTimeBasis; void procRXChar(unsigned char); void sendCommValidation(); diff --git a/udsscanwindow.cpp b/udsscanwindow.cpp index 37c9d07..7398477 100644 --- a/udsscanwindow.cpp +++ b/udsscanwindow.cpp @@ -10,10 +10,13 @@ UDSScanWindow::UDSScanWindow(const QVector *frames, QWidget *parent) : modelFrames = frames; + currentlyRunning = false; + waitTimer = new QTimer; - waitTimer->setInterval(500); + 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); @@ -32,10 +35,19 @@ UDSScanWindow::~UDSScanWindow() void UDSScanWindow::scanUDS() { + if (currentlyRunning) + { + waitTimer->stop(); + sendingFrames.clear(); + currentlyRunning = false; + ui->btnScan->setText("Start Scan"); + } + ui->listResults->clear(); sendingFrames.clear(); CANFrame frame; + int typ, id; int startID, endID; startID = Utility::ParseStringToNum(ui->txtStartID->text()); endID = Utility::ParseStringToNum(ui->txtEndID->text()); @@ -43,9 +55,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; @@ -71,14 +109,14 @@ void UDSScanWindow::scanUDS() waitTimer->start(); currIdx = -1; + currentlyRunning = true; + ui->btnScan->setText("Abort Scan"); 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; if (numFrames == -1) //all frames deleted. We don't care { } @@ -87,30 +125,81 @@ void UDSScanWindow::updatedFrames(int numFrames) } else //just got some new frames. See if they are relevant. { - if (numFrames > modelFrames->count()) return; - for (int i = modelFrames->count() - numFrames; i < modelFrames->count(); i++) + } +} + +//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; + thisFrame = modelFrames->at(i); + id = thisFrame.ID; + + if ((id == (sentFrame.ID + offset)) || ui->cbAllowAdaptiveOffset->isChecked()) { - thisFrame = modelFrames->at(i); - id = thisFrame.ID; - if (id >= 0x7E8 && id <= 0x7EF) + int temp = thisFrame.data[0] >> 4; + if (temp == 0) //single frame reply (maybe) { - 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); - ui->listResults->addItem(result); - sendNextMsg(); + 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; + } + } + + 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(); + } } 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]); + 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); sendNextMsg(); @@ -122,9 +211,12 @@ void UDSScanWindow::sendNextMsg() if (currIdx < sendingFrames.count()) { emit sendCANFrame(&sendingFrames[currIdx], sendingFrames[currIdx].bus); + waitTimer->start(); } else { waitTimer->stop(); + ui->btnScan->setText("Start Scan"); + currentlyRunning = false; } } diff --git a/udsscanwindow.h b/udsscanwindow.h index 39b06d5..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(); @@ -31,6 +32,7 @@ private: QTimer *waitTimer; QList sendingFrames; int currIdx = 0; + bool currentlyRunning; void sendNextMsg(); }; diff --git a/udsscanwindow.ui b/udsscanwindow.ui index 14aff43..0c157bf 100644 --- a/udsscanwindow.ui +++ b/udsscanwindow.ui @@ -11,7 +11,7 @@ - Dialog + UDS Scanner @@ -64,6 +64,33 @@ + + + + Reply Offset: + + + + + + + -128 + + + 128 + + + 8 + + + + + + + Allow adaptive reply offset + + + 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; } }