Reorganized source code
This commit is contained in:
@@ -0,0 +1,346 @@
|
||||
#include "discretestatewindow.h"
|
||||
#include "ui_discretestatewindow.h"
|
||||
#include "mainwindow.h"
|
||||
|
||||
DiscreteStateWindow::DiscreteStateWindow(const QVector<CANFrame> *frames, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::DiscreteStateWindow)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
modelFrames = frames;
|
||||
operatingState = DWStates::IDLE;
|
||||
|
||||
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()
|
||||
{
|
||||
timer->stop();
|
||||
|
||||
for (int i = 0; i < stateFrames.count(); i++)
|
||||
{
|
||||
stateFrames[i]->clear();
|
||||
delete(stateFrames[i]);
|
||||
}
|
||||
|
||||
delete timer;
|
||||
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.
|
||||
{
|
||||
if (numFrames > modelFrames->count()) return;
|
||||
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);
|
||||
|
||||
readSettings();
|
||||
|
||||
qDebug() << "DiscreteState show event was processed";
|
||||
}
|
||||
|
||||
void DiscreteStateWindow::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
writeSettings();
|
||||
}
|
||||
|
||||
void DiscreteStateWindow::readSettings()
|
||||
{
|
||||
QSettings settings;
|
||||
if (settings.value("Main/SaveRestorePositions", false).toBool())
|
||||
{
|
||||
resize(settings.value("DiscreteState/WindowSize", QSize(400, 300)).toSize());
|
||||
move(settings.value("DiscreteState/WindowPos", QPoint(50, 50)).toPoint());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void DiscreteStateWindow::writeSettings()
|
||||
{
|
||||
QSettings settings;
|
||||
|
||||
if (settings.value("Main/SaveRestorePositions", false).toBool())
|
||||
{
|
||||
settings.setValue("DiscreteState/WindowSize", size());
|
||||
settings.setValue("DiscreteState/WindowPos", pos());
|
||||
}
|
||||
}
|
||||
|
||||
void DiscreteStateWindow::updateStateLabel()
|
||||
{
|
||||
QPalette pal;
|
||||
switch(operatingState)
|
||||
{
|
||||
case DWStates::IDLE:
|
||||
ui->lblStatus->setText("IDLE");
|
||||
pal = ui->lblStatus->palette();
|
||||
pal.setColor(QPalette::WindowText, Qt::red);
|
||||
ui->lblStatus->setPalette(pal);
|
||||
break;
|
||||
case DWStates::COUNTDOWN_SIGNAL:
|
||||
ui->lblStatus->setText(QString::number(ticksUntilStateChange * 0.1, 'f', 1) + "...");
|
||||
pal = ui->lblStatus->palette();
|
||||
pal.setColor(QPalette::WindowText, Qt::blue);
|
||||
ui->lblStatus->setPalette(pal);
|
||||
break;
|
||||
case DWStates::COUNTDOWN_WAITING:
|
||||
ui->lblStatus->setText("Wait....");
|
||||
pal = ui->lblStatus->palette();
|
||||
pal.setColor(QPalette::WindowText, Qt::red);
|
||||
ui->lblStatus->setPalette(pal);
|
||||
break;
|
||||
case DWStates::GETTING_SIGNAL:
|
||||
ui->lblStatus->setText("Go to state " + QString::number(currToggleState + 1));
|
||||
pal = ui->lblStatus->palette();
|
||||
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()
|
||||
{
|
||||
if (isRealtime)
|
||||
{
|
||||
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 = stateFrames.count() - 1; i > 0; i--)
|
||||
{
|
||||
stateFrames[i]->clear();
|
||||
stateFrames.removeAt(i);
|
||||
}
|
||||
|
||||
for (int j = 0; j <= numToggleStates; j++)
|
||||
{
|
||||
stateFrames.append(new QVector<CANFrame>());
|
||||
}
|
||||
|
||||
timer->start();
|
||||
}
|
||||
else
|
||||
{
|
||||
calculateResults();
|
||||
}
|
||||
}
|
||||
|
||||
void DiscreteStateWindow::handleTick()
|
||||
{
|
||||
switch (operatingState)
|
||||
{
|
||||
case DWStates::IDLE:
|
||||
break;
|
||||
case DWStates::COUNTDOWN_SIGNAL:
|
||||
ticksUntilStateChange--;
|
||||
if (ticksUntilStateChange == 0)
|
||||
{
|
||||
ticksUntilStateChange = ticksPerStateChange;
|
||||
operatingState = DWStates::GETTING_SIGNAL;
|
||||
}
|
||||
break;
|
||||
case DWStates::COUNTDOWN_WAITING:
|
||||
ticksUntilStateChange--;
|
||||
if (ticksUntilStateChange == 0)
|
||||
{
|
||||
ticksUntilStateChange = ticksPerStateChange;
|
||||
currIteration++;
|
||||
if (currIteration > numIterations)
|
||||
{
|
||||
operatingState = DWStates::IDLE;
|
||||
timer->stop();
|
||||
calculateResults();
|
||||
}
|
||||
else operatingState = DWStates::COUNTDOWN_SIGNAL;
|
||||
}
|
||||
break;
|
||||
case DWStates::GETTING_SIGNAL:
|
||||
ticksUntilStateChange--;
|
||||
if (ticksUntilStateChange == 0)
|
||||
{
|
||||
ticksUntilStateChange = ticksPerStateChange;
|
||||
operatingState = DWStates::COUNTDOWN_WAITING;
|
||||
currToggleState++;
|
||||
if (currToggleState > numToggleStates) currToggleState = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
updateStateLabel();
|
||||
}
|
||||
|
||||
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<int, bool>::const_iterator it;
|
||||
QList<CANFrame> 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<int> values;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
#ifndef DISCRETESTATEWINDOW_H
|
||||
#define DISCRETESTATEWINDOW_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QTimer>
|
||||
#include "can_structs.h"
|
||||
|
||||
namespace Ui {
|
||||
class DiscreteStateWindow;
|
||||
}
|
||||
|
||||
namespace DWStates {
|
||||
enum DiscreteWindowState
|
||||
{
|
||||
IDLE,
|
||||
COUNTDOWN_SIGNAL,
|
||||
COUNTDOWN_WAITING,
|
||||
GETTING_SIGNAL,
|
||||
DONE
|
||||
};
|
||||
}
|
||||
|
||||
using namespace DWStates;
|
||||
class DiscreteStateWindow : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DiscreteStateWindow(const QVector<CANFrame> *frames, QWidget *parent = 0);
|
||||
~DiscreteStateWindow();
|
||||
void showEvent(QShowEvent*);
|
||||
|
||||
private slots:
|
||||
void updatedFrames(int);
|
||||
void handleStartButton();
|
||||
void handleTick();
|
||||
void typeChanged();
|
||||
|
||||
private:
|
||||
Ui::DiscreteStateWindow *ui;
|
||||
const QVector<CANFrame> *modelFrames;
|
||||
QList< QVector<CANFrame> *> stateFrames;
|
||||
QTimer *timer;
|
||||
DiscreteWindowState operatingState;
|
||||
int ticksUntilStateChange;
|
||||
int ticksPerStateChange;
|
||||
int numToggleStates;
|
||||
int currToggleState;
|
||||
int numIterations;
|
||||
int currIteration;
|
||||
bool isRealtime;
|
||||
QHash<int, bool> idFilters;
|
||||
|
||||
void refreshFilterList();
|
||||
void closeEvent(QCloseEvent *event);
|
||||
void readSettings();
|
||||
void writeSettings();
|
||||
void updateStateLabel();
|
||||
void calculateResults();
|
||||
};
|
||||
|
||||
#endif // DISCRETESTATEWINDOW_H
|
||||
@@ -0,0 +1,352 @@
|
||||
#include "filecomparatorwindow.h"
|
||||
#include "ui_filecomparatorwindow.h"
|
||||
|
||||
#include <QSettings>
|
||||
|
||||
FileComparatorWindow::FileComparatorWindow(QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::FileComparatorWindow)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
connect(ui->btnInterestedFile, SIGNAL(clicked(bool)), this, SLOT(loadInterestedFile()));
|
||||
connect(ui->btnLoadRefFile, SIGNAL(clicked(bool)), this, SLOT(loadReferenceFile()));
|
||||
connect(ui->btnSaveDetails, SIGNAL(clicked(bool)), this, SLOT(saveDetails()));
|
||||
connect(ui->btnClear, SIGNAL(clicked(bool)), this, SLOT(clearReference()));
|
||||
|
||||
ui->lblFirstFile->setText("");
|
||||
ui->lblRefFrames->setText("0");
|
||||
}
|
||||
|
||||
FileComparatorWindow::~FileComparatorWindow()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void FileComparatorWindow::showEvent(QShowEvent *)
|
||||
{
|
||||
readSettings();
|
||||
}
|
||||
|
||||
void FileComparatorWindow::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
writeSettings();
|
||||
}
|
||||
|
||||
void FileComparatorWindow::readSettings()
|
||||
{
|
||||
QSettings settings;
|
||||
if (settings.value("Main/SaveRestorePositions", false).toBool())
|
||||
{
|
||||
resize(settings.value("FileComparator/WindowSize", QSize(720, 631)).toSize());
|
||||
move(settings.value("FileComparator/WindowPos", QPoint(50, 50)).toPoint());
|
||||
}
|
||||
}
|
||||
|
||||
void FileComparatorWindow::writeSettings()
|
||||
{
|
||||
QSettings settings;
|
||||
|
||||
if (settings.value("Main/SaveRestorePositions", false).toBool())
|
||||
{
|
||||
settings.setValue("FileComparator/WindowSize", size());
|
||||
settings.setValue("FileComparator/WindowPos", pos());
|
||||
}
|
||||
}
|
||||
|
||||
void FileComparatorWindow::loadInterestedFile()
|
||||
{
|
||||
interestedFrames.clear();
|
||||
QString resultingFileName;
|
||||
if (FrameFileIO::loadFrameFile(resultingFileName, &interestedFrames))
|
||||
{
|
||||
ui->lblFirstFile->setText(resultingFileName);
|
||||
interestedFilename = resultingFileName;
|
||||
if (interestedFrames.count() > 0 && referenceFrames.count() > 0) calculateDetails();
|
||||
}
|
||||
}
|
||||
|
||||
void FileComparatorWindow::loadReferenceFile()
|
||||
{
|
||||
//secondFileFrames.clear();
|
||||
QString resultingFileName;
|
||||
if (FrameFileIO::loadFrameFile(resultingFileName, &referenceFrames))
|
||||
{
|
||||
ui->lblRefFrames->setText(QString::number(referenceFrames.length()));
|
||||
if (interestedFrames.count() > 0 && referenceFrames.count() > 0) calculateDetails();
|
||||
}
|
||||
}
|
||||
|
||||
void FileComparatorWindow::clearReference()
|
||||
{
|
||||
referenceFrames.clear();
|
||||
ui->treeDetails->clear();
|
||||
}
|
||||
|
||||
void FileComparatorWindow::calculateDetails()
|
||||
{
|
||||
QMap<int, FrameData> interestedIDs;
|
||||
QMap<int, FrameData> referenceIDs;
|
||||
QTreeWidgetItem *interestedOnlyBase, *referenceOnlyBase = NULL, *sharedBase, *bitmapBaseInterested, *bitmapBaseReference = NULL;
|
||||
QTreeWidgetItem *valuesBase, *detail, *sharedItem, *valuesInterested, *valuesReference = NULL;
|
||||
uint64_t tmp;
|
||||
|
||||
bool uniqueInterested = ui->ckUniqueToInterested->isChecked();
|
||||
|
||||
ui->treeDetails->clear();
|
||||
|
||||
interestedOnlyBase = new QTreeWidgetItem();
|
||||
interestedOnlyBase->setText(0,"IDs found only in " + interestedFilename);
|
||||
if (!uniqueInterested)
|
||||
{
|
||||
referenceOnlyBase = new QTreeWidgetItem();
|
||||
referenceOnlyBase->setText(0, "IDs found only in reference frames");
|
||||
}
|
||||
sharedBase = new QTreeWidgetItem();
|
||||
sharedBase->setText(0,"IDs found in both places");
|
||||
|
||||
//first we have to fill out the data structures to get ready to do the report
|
||||
for (int x = 0; x < interestedFrames.count(); x++)
|
||||
{
|
||||
CANFrame frame = interestedFrames.at(x);
|
||||
if (interestedIDs.contains(frame.ID)) //if we saw this ID before then add to the QList in there
|
||||
{
|
||||
for (unsigned int y = 0; y < frame.len; y++)
|
||||
{
|
||||
interestedIDs[frame.ID].values[y][frame.data[y]]++;
|
||||
tmp = frame.data[y];
|
||||
tmp = tmp << (8 * y);
|
||||
interestedIDs[frame.ID].bitmap |= tmp;
|
||||
//qDebug() << "bitmap: " << QString::number(interestedIDs[frame.ID].bitmap, 16);
|
||||
}
|
||||
}
|
||||
else //never seen this ID before so add one
|
||||
{
|
||||
FrameData *newData = new FrameData();
|
||||
newData->ID = frame.ID;
|
||||
newData->dataLen = frame.len;
|
||||
//it would be possible to implement a constructor for FrameData
|
||||
//that sets the bitmap and values to zero. That would be cleaner and better.
|
||||
newData->bitmap = 0;
|
||||
for (int x = 0; x < 8; x++)
|
||||
{
|
||||
for (int y = 0; y < 256; y++)
|
||||
{
|
||||
newData->values[x][y] = 0;
|
||||
}
|
||||
}
|
||||
//memset(newData->values, 0, 256 * 8);
|
||||
for (unsigned int y = 0; y < frame.len; y++)
|
||||
{
|
||||
newData->values[y][frame.data[y]] = 1;
|
||||
tmp = frame.data[y];
|
||||
tmp = tmp << (8 * y);
|
||||
newData->bitmap |= tmp;
|
||||
//qDebug() << "bitmap: " << QString::number(newData->bitmap, 16);
|
||||
}
|
||||
interestedIDs.insert(frame.ID, *newData);
|
||||
}
|
||||
}
|
||||
|
||||
for (int x = 0; x < referenceFrames.count(); x++)
|
||||
{
|
||||
CANFrame frame = referenceFrames.at(x);
|
||||
if (referenceIDs.contains(frame.ID)) //if we saw this ID before then add to the QList in there
|
||||
{
|
||||
for (int y = 0; y < frame.len; y++)
|
||||
{
|
||||
referenceIDs[frame.ID].values[y][frame.data[y]]++;
|
||||
tmp = frame.data[y];
|
||||
tmp = tmp << (8 * y);
|
||||
referenceIDs[frame.ID].bitmap |= tmp;
|
||||
//qDebug() << "bitmap: " << QString::number(referenceIDs[frame.ID].bitmap, 16);
|
||||
}
|
||||
}
|
||||
else //never seen this ID before so add one
|
||||
{
|
||||
FrameData *newData = new FrameData();
|
||||
newData->ID = frame.ID;
|
||||
newData->dataLen = frame.len;
|
||||
newData->bitmap = 0;
|
||||
for (int x = 0; x < 8; x++)
|
||||
{
|
||||
for (int y = 0; y < 256; y++)
|
||||
{
|
||||
newData->values[x][y] = 0;
|
||||
}
|
||||
}
|
||||
//memset(newData->values, 0, 256 * 8);
|
||||
for (unsigned int y = 0; y < frame.len; y++)
|
||||
{
|
||||
newData->values[y][frame.data[y]] = 1;
|
||||
tmp = frame.data[y];
|
||||
tmp = tmp << (8 * y);
|
||||
newData->bitmap |= tmp;
|
||||
//qDebug() << "bitmap: " << QString::number(newData->bitmap, 16);
|
||||
}
|
||||
referenceIDs.insert(frame.ID, *newData);
|
||||
}
|
||||
}
|
||||
|
||||
//now we iterate through the IDs within both files and see which are unique to one file and which
|
||||
//are shared
|
||||
bool interestedHadUnique = false;
|
||||
QMap<int, FrameData>::iterator i;
|
||||
for (i = interestedIDs.begin(); i != interestedIDs.end(); ++i)
|
||||
{
|
||||
int keyone = i.key();
|
||||
if (!referenceIDs.contains(keyone))
|
||||
{
|
||||
valuesBase = new QTreeWidgetItem();
|
||||
valuesBase->setText(0, QString::number(keyone, 16));
|
||||
interestedOnlyBase->addChild(valuesBase);
|
||||
}
|
||||
else //ID was in both files
|
||||
{
|
||||
interestedHadUnique = false;
|
||||
sharedItem = new QTreeWidgetItem();
|
||||
sharedItem->setText(0, Utility::formatHexNum(keyone));
|
||||
//if the ID was in both files then we can use the data accumulated above in bitmap
|
||||
//and values to figure out what has changed between the two files
|
||||
|
||||
FrameData interested = interestedIDs[keyone];
|
||||
FrameData reference = referenceIDs[keyone];
|
||||
|
||||
bitmapBaseInterested = new QTreeWidgetItem();
|
||||
bitmapBaseInterested->setText(0, "Bits set only in " + interestedFilename);
|
||||
if (!uniqueInterested)
|
||||
{
|
||||
bitmapBaseReference = new QTreeWidgetItem();
|
||||
bitmapBaseReference->setText(0, "Bits set only in reference frames");
|
||||
}
|
||||
sharedItem->addChild(bitmapBaseInterested);
|
||||
if (!uniqueInterested) sharedItem->addChild(bitmapBaseReference);
|
||||
|
||||
uint64_t interestedBits = interested.bitmap;
|
||||
uint64_t referenceBits = reference.bitmap;
|
||||
|
||||
//first up, which bits were set in one file but not the other
|
||||
for (int b = 0; b < (8 * interested.dataLen); b++)
|
||||
{
|
||||
detail = new QTreeWidgetItem();
|
||||
detail->setText(0, QString::number(b) + " (" + QString::number(b / 8) + ":" + QString::number(b % 8) + ")");
|
||||
if ( (interestedBits & 1) && !(referenceBits & 1) )
|
||||
{
|
||||
bitmapBaseInterested->addChild(detail);
|
||||
interestedHadUnique = true;
|
||||
}
|
||||
else if ( !(interestedBits & 1) && (referenceBits & 1) )
|
||||
{
|
||||
if (!uniqueInterested) bitmapBaseReference->addChild(detail);
|
||||
}
|
||||
//qDebug() << b << " " << QString::number(interestedBits, 16) << " " << QString::number(referenceBits, 16);
|
||||
interestedBits = interestedBits >> 1;
|
||||
referenceBits = referenceBits >> 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < qMax(interested.dataLen, reference.dataLen); i++)
|
||||
{
|
||||
valuesBase = new QTreeWidgetItem();
|
||||
valuesBase->setText(0, "Byte " + QString::number(i));
|
||||
sharedItem->addChild(valuesBase);
|
||||
valuesInterested = new QTreeWidgetItem();
|
||||
valuesInterested->setText(0, "Values found only in " + interestedFilename);
|
||||
if (!uniqueInterested)
|
||||
{
|
||||
valuesReference = new QTreeWidgetItem();
|
||||
valuesReference->setText(0, "Values found only in reference frames");
|
||||
}
|
||||
valuesBase->addChild(valuesInterested);
|
||||
if (!uniqueInterested) valuesBase->addChild(valuesReference);
|
||||
for (int j = 0; j < 256; j++)
|
||||
{
|
||||
detail = new QTreeWidgetItem();
|
||||
detail->setText(0, Utility::formatHexNum(j));
|
||||
if ((interested.values[i][j] > 0) && (reference.values[i][j] == 0) )
|
||||
{
|
||||
valuesInterested->addChild(detail);
|
||||
interestedHadUnique = true;
|
||||
}
|
||||
if ((reference.values[i][j] > 0) && (interested.values[i][j] == 0) )
|
||||
{
|
||||
if (!uniqueInterested) valuesReference->addChild(detail);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (interestedHadUnique || !uniqueInterested) sharedBase->addChild(sharedItem);
|
||||
}
|
||||
}
|
||||
|
||||
if (!uniqueInterested)
|
||||
{
|
||||
QMap<int, FrameData>::iterator itwo;
|
||||
for (itwo = referenceIDs.begin(); itwo != referenceIDs.end(); ++itwo)
|
||||
{
|
||||
int keytwo = itwo.key();
|
||||
if (!interestedIDs.contains(keytwo))
|
||||
{
|
||||
valuesBase = new QTreeWidgetItem();
|
||||
valuesBase->setText(0, Utility::formatHexNum(keytwo));
|
||||
referenceOnlyBase->addChild(valuesBase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ui->treeDetails->addTopLevelItem(interestedOnlyBase);
|
||||
if (!uniqueInterested) ui->treeDetails->addTopLevelItem(referenceOnlyBase);
|
||||
ui->treeDetails->addTopLevelItem(sharedBase);
|
||||
|
||||
//ui->treeDetails->setSortingEnabled(true);
|
||||
//ui->treeDetails->sortByColumn(0, Qt::AscendingOrder);
|
||||
|
||||
QSettings settings;
|
||||
if (settings.value("InfoCompare/AutoExpand", false).toBool())
|
||||
{
|
||||
ui->treeDetails->expandAll();
|
||||
}
|
||||
}
|
||||
|
||||
void FileComparatorWindow::saveDetails()
|
||||
{
|
||||
QString filename;
|
||||
QFileDialog dialog(this);
|
||||
|
||||
QStringList filters;
|
||||
filters.append(QString(tr("Text File (*.txt)")));
|
||||
|
||||
dialog.setFileMode(QFileDialog::AnyFile);
|
||||
dialog.setNameFilters(filters);
|
||||
dialog.setViewMode(QFileDialog::Detail);
|
||||
dialog.setAcceptMode(QFileDialog::AcceptSave);
|
||||
|
||||
if (dialog.exec() == QDialog::Accepted)
|
||||
{
|
||||
filename = dialog.selectedFiles()[0];
|
||||
if (!filename.contains('.')) filename += ".txt";
|
||||
QFile *outFile = new QFile(filename);
|
||||
|
||||
if (!outFile->open(QIODevice::WriteOnly | QIODevice::Text))
|
||||
return;
|
||||
|
||||
QTreeWidget *tree = ui->treeDetails;
|
||||
|
||||
|
||||
QTreeWidgetItemIterator it(tree);
|
||||
while (*it) {
|
||||
QTreeWidgetItem *item = *it;
|
||||
QString itemText = item->text(0);
|
||||
while (item->parent())
|
||||
{
|
||||
outFile->write(" ");
|
||||
item = item->parent();
|
||||
}
|
||||
outFile->write(itemText.toUtf8() + "\n");
|
||||
++it;
|
||||
}
|
||||
|
||||
outFile->close();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#ifndef FILECOMPARATORWINDOW_H
|
||||
#define FILECOMPARATORWINDOW_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QDebug>
|
||||
#include <QTreeWidget>
|
||||
#include "framefileio.h"
|
||||
#include "can_structs.h"
|
||||
#include "utility.h"
|
||||
|
||||
namespace Ui {
|
||||
class FileComparatorWindow;
|
||||
}
|
||||
|
||||
struct FrameData
|
||||
{
|
||||
int ID;
|
||||
int dataLen;
|
||||
uint64_t bitmap;
|
||||
int values[8][256]; //first index is the data byte, second is # of times we saw that value
|
||||
};
|
||||
|
||||
class FileComparatorWindow : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FileComparatorWindow(QWidget *parent = 0);
|
||||
~FileComparatorWindow();
|
||||
|
||||
private slots:
|
||||
void loadInterestedFile();
|
||||
void loadReferenceFile();
|
||||
void clearReference();
|
||||
void saveDetails();
|
||||
|
||||
private:
|
||||
Ui::FileComparatorWindow *ui;
|
||||
QVector<CANFrame> interestedFrames;
|
||||
QVector<CANFrame> referenceFrames;
|
||||
QString interestedFilename;
|
||||
|
||||
void calculateDetails();
|
||||
void showEvent(QShowEvent *);
|
||||
void closeEvent(QCloseEvent *event);
|
||||
void readSettings();
|
||||
void writeSettings();
|
||||
};
|
||||
|
||||
#endif // FILECOMPARATORWINDOW_H
|
||||
@@ -0,0 +1,733 @@
|
||||
#include "flowviewwindow.h"
|
||||
#include "ui_flowviewwindow.h"
|
||||
#include "mainwindow.h"
|
||||
|
||||
const QColor FlowViewWindow::graphColors[8] = {Qt::blue, Qt::green, Qt::black, Qt::red, //0 1 2 3
|
||||
Qt::gray, Qt::yellow, Qt::cyan, Qt::darkMagenta}; //4 5 6 7
|
||||
|
||||
|
||||
FlowViewWindow::FlowViewWindow(const QVector<CANFrame> *frames, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::FlowViewWindow)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
readSettings();
|
||||
|
||||
modelFrames = frames;
|
||||
|
||||
playbackTimer = new QTimer();
|
||||
|
||||
currentPosition = 0;
|
||||
playbackActive = false;
|
||||
playbackForward = true;
|
||||
|
||||
memset(refBytes, 0, 8);
|
||||
memset(currBytes, 0, 8);
|
||||
memset(triggerValues, -1, sizeof(int) * 8);
|
||||
|
||||
//ui->graphView->setInteractions();
|
||||
|
||||
ui->graphView->xAxis->setRange(0, 8);
|
||||
ui->graphView->yAxis->setRange(-10, 265); //run range a bit outside possible number so they aren't plotted in a hard to see place
|
||||
ui->graphView->axisRect()->setupFullAxesBox();
|
||||
|
||||
QCPItemText *textLabel = new QCPItemText(ui->graphView);
|
||||
ui->graphView->addItem(textLabel);
|
||||
textLabel->setPositionAlignment(Qt::AlignTop|Qt::AlignHCenter);
|
||||
textLabel->position->setType(QCPItemPosition::ptAxisRectRatio);
|
||||
textLabel->position->setCoords(0.5, .5);
|
||||
textLabel->setText("+");
|
||||
textLabel->setFont(QFont(font().family(), 16)); // make font a bit larger
|
||||
textLabel->setPen(QPen(Qt::black)); // show black border around text
|
||||
|
||||
ui->graphView->xAxis->setLabel("Time Axis");
|
||||
ui->graphView->yAxis->setLabel("Value Axis");
|
||||
QFont legendFont = font();
|
||||
legendFont.setPointSize(10);
|
||||
QFont legendSelectedFont = font();
|
||||
legendSelectedFont.setPointSize(12);
|
||||
legendSelectedFont.setBold(true);
|
||||
ui->graphView->legend->setFont(legendFont);
|
||||
ui->graphView->legend->setSelectedFont(legendSelectedFont);
|
||||
ui->graphView->legend->setSelectableParts(QCPLegend::spItems); // legend box shall not be selectable, only legend items
|
||||
//ui->graphView->xAxis->setAutoSubTicks(false);
|
||||
//ui->graphView->xAxis->setAutoTicks(false);
|
||||
ui->graphView->xAxis->setAutoTickStep(false);
|
||||
ui->graphView->xAxis->setAutoSubTicks(false);
|
||||
ui->graphView->xAxis->setNumberFormat("gb");
|
||||
ui->graphView->xAxis->setTickStep(5.0);
|
||||
ui->graphView->xAxis->setSubTickCount(0);
|
||||
|
||||
connect(ui->btnBackOne, SIGNAL(clicked(bool)), this, SLOT(btnBackOneClick()));
|
||||
connect(ui->btnPause, SIGNAL(clicked(bool)), this, SLOT(btnPauseClick()));
|
||||
connect(ui->btnReverse, SIGNAL(clicked(bool)), this, SLOT(btnReverseClick()));
|
||||
connect(ui->btnStop, SIGNAL(clicked(bool)), this, SLOT(btnStopClick()));
|
||||
connect(ui->btnPlay, SIGNAL(clicked(bool)), this, SLOT(btnPlayClick()));
|
||||
connect(ui->btnForwardOne, SIGNAL(clicked(bool)), this, SLOT(btnFwdOneClick()));
|
||||
connect(ui->spinPlayback, SIGNAL(valueChanged(int)), this, SLOT(changePlaybackSpeed(int)));
|
||||
connect(ui->cbLoopPlayback, SIGNAL(clicked(bool)), this, SLOT(changeLooping(bool)));
|
||||
connect(ui->listFrameID, SIGNAL(currentTextChanged(QString)), this, SLOT(changeID(QString)));
|
||||
connect(playbackTimer, SIGNAL(timeout()), this, SLOT(timerTriggered()));
|
||||
connect(ui->graphView, SIGNAL(plottableDoubleClick(QCPAbstractPlottable*,QMouseEvent*)), this, SLOT(plottableDoubleClick(QCPAbstractPlottable*,QMouseEvent*)));
|
||||
connect(ui->txtTrigger0, SIGNAL(textEdited(QString)), this, SLOT(updateTriggerValues()));
|
||||
connect(ui->txtTrigger1, SIGNAL(textEdited(QString)), this, SLOT(updateTriggerValues()));
|
||||
connect(ui->txtTrigger2, SIGNAL(textEdited(QString)), this, SLOT(updateTriggerValues()));
|
||||
connect(ui->txtTrigger3, SIGNAL(textEdited(QString)), this, SLOT(updateTriggerValues()));
|
||||
connect(ui->txtTrigger4, SIGNAL(textEdited(QString)), this, SLOT(updateTriggerValues()));
|
||||
connect(ui->txtTrigger5, SIGNAL(textEdited(QString)), this, SLOT(updateTriggerValues()));
|
||||
connect(ui->txtTrigger6, SIGNAL(textEdited(QString)), this, SLOT(updateTriggerValues()));
|
||||
connect(ui->txtTrigger7, SIGNAL(textEdited(QString)), this, SLOT(updateTriggerValues()));
|
||||
|
||||
connect(MainWindow::getReference(), SIGNAL(framesUpdated(int)), this, SLOT(updatedFrames(int)));
|
||||
|
||||
ui->graphView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(ui->graphView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenuRequestGraph(QPoint)));
|
||||
ui->flowView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(ui->flowView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenuRequestFlow(QPoint)));
|
||||
|
||||
playbackTimer->setInterval(ui->spinPlayback->value()); //set the timer to the default value of the control
|
||||
}
|
||||
|
||||
void FlowViewWindow::showEvent(QShowEvent* event)
|
||||
{
|
||||
QDialog::showEvent(event);
|
||||
|
||||
installEventFilter(this);
|
||||
|
||||
readSettings();
|
||||
|
||||
refreshIDList();
|
||||
if (ui->listFrameID->count() > 0)
|
||||
{
|
||||
changeID(ui->listFrameID->item(0)->text());
|
||||
ui->listFrameID->setCurrentRow(0);
|
||||
}
|
||||
updateFrameLabel();
|
||||
qDebug() << "FlowView show event was processed";
|
||||
}
|
||||
|
||||
FlowViewWindow::~FlowViewWindow()
|
||||
{
|
||||
removeEventFilter(this);
|
||||
|
||||
delete ui;
|
||||
|
||||
playbackTimer->stop();
|
||||
delete playbackTimer;
|
||||
}
|
||||
|
||||
void FlowViewWindow::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
writeSettings();
|
||||
}
|
||||
|
||||
void FlowViewWindow::readSettings()
|
||||
{
|
||||
QSettings settings;
|
||||
if (settings.value("Main/SaveRestorePositions", false).toBool())
|
||||
{
|
||||
resize(settings.value("FlowView/WindowSize", QSize(1078, 621)).toSize());
|
||||
move(settings.value("FlowView/WindowPos", QPoint(50, 50)).toPoint());
|
||||
}
|
||||
|
||||
if (settings.value("FlowView/AutoRef", false).toBool())
|
||||
{
|
||||
ui->cbAutoRef->setChecked(true);
|
||||
}
|
||||
|
||||
if (settings.value("FlowView/UseTimestamp", false).toBool())
|
||||
{
|
||||
ui->cbTimeGraph->setChecked(true);
|
||||
}
|
||||
|
||||
secondsMode = settings.value("Main/TimeSeconds", false).toBool();
|
||||
}
|
||||
|
||||
void FlowViewWindow::writeSettings()
|
||||
{
|
||||
QSettings settings;
|
||||
|
||||
if (settings.value("Main/SaveRestorePositions", false).toBool())
|
||||
{
|
||||
settings.setValue("FlowView/WindowSize", size());
|
||||
settings.setValue("FlowView/WindowPos", pos());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Keyboard shortcuts to allow for quick work without needing to move around a mouse.
|
||||
* R = resume or pause playback
|
||||
* T = go back one frame
|
||||
* Y = go forward one frame
|
||||
*/
|
||||
bool FlowViewWindow::eventFilter(QObject *obj, QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::KeyRelease) {
|
||||
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
|
||||
switch (keyEvent->key())
|
||||
{
|
||||
case Qt::Key_R:
|
||||
if (playbackActive) btnPauseClick();
|
||||
else btnPlayClick();
|
||||
break;
|
||||
case Qt::Key_T:
|
||||
btnBackOneClick();
|
||||
break;
|
||||
case Qt::Key_Y:
|
||||
btnFwdOneClick();
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
// standard event processing
|
||||
return QObject::eventFilter(obj, event);
|
||||
}
|
||||
}
|
||||
|
||||
void FlowViewWindow::updateTriggerValues()
|
||||
{
|
||||
triggerValues[0] = Utility::ParseStringToNum(ui->txtTrigger0->text());
|
||||
triggerValues[1] = Utility::ParseStringToNum(ui->txtTrigger1->text());
|
||||
triggerValues[2] = Utility::ParseStringToNum(ui->txtTrigger2->text());
|
||||
triggerValues[3] = Utility::ParseStringToNum(ui->txtTrigger3->text());
|
||||
triggerValues[4] = Utility::ParseStringToNum(ui->txtTrigger4->text());
|
||||
triggerValues[5] = Utility::ParseStringToNum(ui->txtTrigger5->text());
|
||||
triggerValues[6] = Utility::ParseStringToNum(ui->txtTrigger6->text());
|
||||
triggerValues[7] = Utility::ParseStringToNum(ui->txtTrigger7->text());
|
||||
}
|
||||
|
||||
void FlowViewWindow::plottableDoubleClick(QCPAbstractPlottable* plottable, QMouseEvent* event)
|
||||
{
|
||||
int id = 0;
|
||||
//apply transforms to get the X axis value where we double clicked
|
||||
double coord = plottable->keyAxis()->pixelToCoord(event->localPos().x());
|
||||
if (frameCache.count() > 0) id = frameCache[0].ID;
|
||||
if (secondsMode) emit sendCenterTimeID(id, coord);
|
||||
else emit sendCenterTimeID(id, coord / 1000000.0);
|
||||
}
|
||||
|
||||
void FlowViewWindow::gotCenterTimeID(int32_t ID, double timestamp)
|
||||
{
|
||||
uint64_t t_stamp;
|
||||
|
||||
t_stamp = timestamp * 1000000l;
|
||||
|
||||
qDebug() << "timestamp: " << t_stamp;
|
||||
|
||||
changeID(QString::number(ID)); //to be sure we're focused on the proper ID
|
||||
|
||||
for (int j = 0; j < ui->listFrameID->count(); j++)
|
||||
{
|
||||
int thisNum = Utility::ParseStringToNum(ui->listFrameID->item(j)->text());
|
||||
if (thisNum == ID)
|
||||
{
|
||||
ui->listFrameID->setCurrentRow(j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int bestIdx = -1;
|
||||
for (int i = 0; i < frameCache.count(); i++)
|
||||
{
|
||||
if (frameCache[i].timestamp > t_stamp)
|
||||
{
|
||||
bestIdx = i - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
qDebug() << "Best index " << bestIdx;
|
||||
if (bestIdx > -1)
|
||||
{
|
||||
currentPosition = bestIdx;
|
||||
if (ui->cbAutoRef->isChecked())
|
||||
{
|
||||
memcpy(refBytes, currBytes, 8);
|
||||
}
|
||||
|
||||
memcpy(currBytes, frameCache.at(currentPosition).data, 8);
|
||||
|
||||
updateDataView();
|
||||
}
|
||||
}
|
||||
|
||||
void FlowViewWindow::contextMenuRequestFlow(QPoint pos)
|
||||
{
|
||||
QMenu *menu = new QMenu(this);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
menu->addAction(tr("Save image to file"), this, SLOT(saveFileFlow()));
|
||||
|
||||
menu->popup(ui->flowView->mapToGlobal(pos));
|
||||
}
|
||||
|
||||
void FlowViewWindow::contextMenuRequestGraph(QPoint pos)
|
||||
{
|
||||
QMenu *menu = new QMenu(this);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
menu->addAction(tr("Save image to file"), this, SLOT(saveFileGraph()));
|
||||
|
||||
menu->popup(ui->graphView->mapToGlobal(pos));
|
||||
}
|
||||
|
||||
void FlowViewWindow::saveFileGraph()
|
||||
{
|
||||
QString filename;
|
||||
QFileDialog dialog(this);
|
||||
|
||||
QStringList filters;
|
||||
filters.append(QString(tr("PDF Files (*.pdf)")));
|
||||
filters.append(QString(tr("PNG Files (*.png)")));
|
||||
filters.append(QString(tr("JPEG Files (*.jpg)")));
|
||||
|
||||
dialog.setFileMode(QFileDialog::AnyFile);
|
||||
dialog.setNameFilters(filters);
|
||||
dialog.setViewMode(QFileDialog::Detail);
|
||||
dialog.setAcceptMode(QFileDialog::AcceptSave);
|
||||
|
||||
if (dialog.exec() == QDialog::Accepted)
|
||||
{
|
||||
filename = dialog.selectedFiles()[0];
|
||||
|
||||
if (dialog.selectedNameFilter() == filters[0])
|
||||
{
|
||||
if (!filename.contains('.')) filename += ".pdf";
|
||||
ui->graphView->savePdf(filename, true, 0, 0);
|
||||
}
|
||||
if (dialog.selectedNameFilter() == filters[1])
|
||||
{
|
||||
if (!filename.contains('.')) filename += ".png";
|
||||
ui->graphView->savePng(filename, 1024, 768);
|
||||
}
|
||||
if (dialog.selectedNameFilter() == filters[2])
|
||||
{
|
||||
if (!filename.contains('.')) filename += ".jpg";
|
||||
ui->graphView->saveJpg(filename, 1024, 768);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FlowViewWindow::saveFileFlow()
|
||||
{
|
||||
QString filename;
|
||||
QFileDialog dialog(this);
|
||||
|
||||
QStringList filters;
|
||||
filters.append(QString(tr("PNG Files (*.png)")));
|
||||
filters.append(QString(tr("JPEG Files (*.jpg)")));
|
||||
|
||||
dialog.setFileMode(QFileDialog::AnyFile);
|
||||
dialog.setNameFilters(filters);
|
||||
dialog.setViewMode(QFileDialog::Detail);
|
||||
dialog.setAcceptMode(QFileDialog::AcceptSave);
|
||||
|
||||
if (dialog.exec() == QDialog::Accepted)
|
||||
{
|
||||
filename = dialog.selectedFiles()[0];
|
||||
|
||||
if (dialog.selectedNameFilter() == filters[0])
|
||||
{
|
||||
if (!filename.contains('.')) filename += ".png";
|
||||
ui->flowView->saveImage(filename, 1024, 768);
|
||||
}
|
||||
if (dialog.selectedNameFilter() == filters[1])
|
||||
{
|
||||
if (!filename.contains('.')) filename += ".jpg";
|
||||
ui->flowView->saveImage(filename, 1024, 768);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FlowViewWindow::updatedFrames(int numFrames)
|
||||
{
|
||||
CANFrame thisFrame;
|
||||
if (numFrames == -1) //all frames deleted. Kill the display
|
||||
{
|
||||
ui->listFrameID->clear();
|
||||
foundID.clear();
|
||||
currentPosition = 0;
|
||||
refreshIDList();
|
||||
updateFrameLabel();
|
||||
removeAllGraphs();
|
||||
memset(refBytes, 0, 8);
|
||||
memset(currBytes, 0, 8);
|
||||
updateDataView();
|
||||
}
|
||||
else if (numFrames == -2) //all new set of frames. Reset
|
||||
{
|
||||
ui->listFrameID->clear();
|
||||
foundID.clear();
|
||||
currentPosition = 0;
|
||||
refreshIDList();
|
||||
if (ui->listFrameID->count() > 0)
|
||||
{
|
||||
changeID(ui->listFrameID->item(0)->text());
|
||||
ui->listFrameID->setCurrentRow(0);
|
||||
}
|
||||
updateFrameLabel();
|
||||
}
|
||||
else //just got some new frames. See if they are relevant.
|
||||
{
|
||||
if (numFrames > modelFrames->count()) return;
|
||||
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);
|
||||
|
||||
for (int k = 0; k < 8; k++)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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()
|
||||
{
|
||||
ui->graphView->clearGraphs();
|
||||
ui->graphView->replot();
|
||||
}
|
||||
|
||||
void FlowViewWindow::createGraph(int byteNum)
|
||||
{
|
||||
int tempVal;
|
||||
float minval=1000000, maxval = -100000;
|
||||
|
||||
bool graphByTime = ui->cbTimeGraph->isChecked();
|
||||
|
||||
int numEntries = frameCache.count();
|
||||
|
||||
x[byteNum].clear();
|
||||
y[byteNum].clear();
|
||||
x[byteNum].resize(numEntries);
|
||||
y[byteNum].resize(numEntries);
|
||||
|
||||
for (int j = 0; j < numEntries; j++)
|
||||
{
|
||||
tempVal = frameCache[j].data[byteNum];
|
||||
|
||||
if (graphByTime)
|
||||
{
|
||||
if (secondsMode){
|
||||
x[byteNum][j] = (double)(frameCache[j].timestamp) / 1000000.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
x[byteNum][j] = frameCache[j].timestamp;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
x[byteNum][j] = j;
|
||||
}
|
||||
|
||||
y[byteNum][j] = tempVal;
|
||||
if (y[byteNum][j] < minval) minval = y[byteNum][j];
|
||||
if (y[byteNum][j] > maxval) maxval = y[byteNum][j];
|
||||
}
|
||||
|
||||
graphRef[byteNum] = ui->graphView->addGraph();
|
||||
ui->graphView->graph()->setName(QString("Graph %1").arg(ui->graphView->graphCount()-1));
|
||||
ui->graphView->graph()->setData(x[byteNum],y[byteNum]);
|
||||
ui->graphView->graph()->setLineStyle(QCPGraph::lsLine); //connect points with lines
|
||||
QPen graphPen;
|
||||
graphPen.setColor(graphColors[byteNum]);
|
||||
graphPen.setWidth(1);
|
||||
ui->graphView->graph()->setPen(graphPen);
|
||||
ui->graphView->axisRect()->setupFullAxesBox();
|
||||
}
|
||||
|
||||
void FlowViewWindow::refreshIDList()
|
||||
{
|
||||
int id;
|
||||
for (int i = 0; i < modelFrames->count(); i++)
|
||||
{
|
||||
id = modelFrames->at(i).ID;
|
||||
if (!foundID.contains(id))
|
||||
{
|
||||
foundID.append(id);
|
||||
QListWidgetItem* item = new QListWidgetItem(Utility::formatNumber(id), ui->listFrameID);
|
||||
}
|
||||
}
|
||||
//default is to sort in ascending order
|
||||
ui->listFrameID->sortItems();
|
||||
}
|
||||
|
||||
void FlowViewWindow::updateFrameLabel()
|
||||
{
|
||||
ui->lblNumFrames->setText(QString::number(currentPosition) + tr(" of ") + QString::number(frameCache.count()));
|
||||
}
|
||||
|
||||
void FlowViewWindow::changeID(QString newID)
|
||||
{
|
||||
//parse the ID and then load up the frame cache with just messages with that ID.
|
||||
int id = Utility::ParseStringToNum(newID);
|
||||
frameCache.clear();
|
||||
|
||||
if (modelFrames->count() == 0) return;
|
||||
|
||||
playbackTimer->stop();
|
||||
playbackActive = false;
|
||||
for (int x = 0; x < modelFrames->count(); x++)
|
||||
{
|
||||
CANFrame thisFrame = modelFrames->at(x);
|
||||
if (thisFrame.ID == id)
|
||||
{
|
||||
for (int j = thisFrame.len; j < 8; j++) thisFrame.data[j] = 0;
|
||||
frameCache.append(thisFrame);
|
||||
}
|
||||
}
|
||||
currentPosition = 0;
|
||||
|
||||
if (frameCache.count() == 0) return;
|
||||
|
||||
removeAllGraphs();
|
||||
for (int c = 0; c < frameCache.at(0).len; c++)
|
||||
{
|
||||
createGraph(c);
|
||||
}
|
||||
|
||||
updateGraphLocation();
|
||||
|
||||
memcpy(currBytes, frameCache.at(currentPosition).data, 8);
|
||||
memcpy(refBytes, currBytes, 8);
|
||||
|
||||
updateDataView();
|
||||
}
|
||||
|
||||
void FlowViewWindow::btnBackOneClick()
|
||||
{
|
||||
playbackTimer->stop(); //pushing this button halts automatic playback
|
||||
playbackActive = false;
|
||||
|
||||
updatePosition(false);
|
||||
updateDataView();
|
||||
}
|
||||
|
||||
void FlowViewWindow::btnPauseClick()
|
||||
{
|
||||
playbackActive = false;
|
||||
playbackTimer->stop();
|
||||
updateDataView();
|
||||
}
|
||||
|
||||
void FlowViewWindow::btnReverseClick()
|
||||
{
|
||||
playbackActive = true;
|
||||
playbackForward = false;
|
||||
playbackTimer->start();
|
||||
}
|
||||
|
||||
void FlowViewWindow::btnStopClick()
|
||||
{
|
||||
playbackTimer->stop(); //pushing this button halts automatic playback
|
||||
playbackActive = false;
|
||||
currentPosition = 0;
|
||||
|
||||
memcpy(currBytes, frameCache.at(currentPosition).data, 8);
|
||||
memcpy(refBytes, currBytes, 8);
|
||||
|
||||
updateFrameLabel();
|
||||
updateDataView();
|
||||
}
|
||||
|
||||
void FlowViewWindow::btnPlayClick()
|
||||
{
|
||||
playbackActive = true;
|
||||
playbackForward = true;
|
||||
playbackTimer->start();
|
||||
}
|
||||
|
||||
void FlowViewWindow::btnFwdOneClick()
|
||||
{
|
||||
playbackTimer->stop();
|
||||
playbackActive = false;
|
||||
updatePosition(true);
|
||||
updateDataView();
|
||||
}
|
||||
|
||||
void FlowViewWindow::changePlaybackSpeed(int newSpeed)
|
||||
{
|
||||
playbackTimer->setInterval(newSpeed);
|
||||
}
|
||||
|
||||
void FlowViewWindow::changeLooping(bool check)
|
||||
{
|
||||
Q_UNUSED(check);
|
||||
}
|
||||
|
||||
void FlowViewWindow::timerTriggered()
|
||||
{
|
||||
if (!playbackActive || (ui->cbLiveMode->checkState() == Qt::Checked))
|
||||
{
|
||||
playbackTimer->stop();
|
||||
return;
|
||||
}
|
||||
if (playbackForward)
|
||||
{
|
||||
updatePosition(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
updatePosition(false);
|
||||
}
|
||||
|
||||
updateDataView();
|
||||
|
||||
if (!ui->cbLoopPlayback->isChecked())
|
||||
{
|
||||
if (currentPosition == 0) playbackActive = false;
|
||||
if (currentPosition == (frameCache.count() - 1)) playbackActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
void FlowViewWindow::updateDataView()
|
||||
{
|
||||
|
||||
ui->txtCurr1->setText(Utility::formatNumber(currBytes[0]));
|
||||
ui->txtCurr2->setText(Utility::formatNumber(currBytes[1]));
|
||||
ui->txtCurr3->setText(Utility::formatNumber(currBytes[2]));
|
||||
ui->txtCurr4->setText(Utility::formatNumber(currBytes[3]));
|
||||
ui->txtCurr5->setText(Utility::formatNumber(currBytes[4]));
|
||||
ui->txtCurr6->setText(Utility::formatNumber(currBytes[5]));
|
||||
ui->txtCurr7->setText(Utility::formatNumber(currBytes[6]));
|
||||
ui->txtCurr8->setText(Utility::formatNumber(currBytes[7]));
|
||||
|
||||
ui->txtRef1->setText(Utility::formatNumber(refBytes[0]));
|
||||
ui->txtRef2->setText(Utility::formatNumber(refBytes[1]));
|
||||
ui->txtRef3->setText(Utility::formatNumber(refBytes[2]));
|
||||
ui->txtRef4->setText(Utility::formatNumber(refBytes[3]));
|
||||
ui->txtRef5->setText(Utility::formatNumber(refBytes[4]));
|
||||
ui->txtRef6->setText(Utility::formatNumber(refBytes[5]));
|
||||
ui->txtRef7->setText(Utility::formatNumber(refBytes[6]));
|
||||
ui->txtRef8->setText(Utility::formatNumber(refBytes[7]));
|
||||
|
||||
ui->flowView->setReference(refBytes, false);
|
||||
ui->flowView->updateData(currBytes, true);
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
if (currBytes[i] == triggerValues[i])
|
||||
{
|
||||
playbackActive = false;
|
||||
playbackTimer->stop();
|
||||
break; //once any trigger is hit we can stop looking
|
||||
}
|
||||
}
|
||||
|
||||
updateGraphLocation();
|
||||
|
||||
updateFrameLabel();
|
||||
|
||||
}
|
||||
|
||||
void FlowViewWindow::updatePosition(bool forward)
|
||||
{
|
||||
|
||||
if (forward)
|
||||
{
|
||||
if (currentPosition < (frameCache.count() - 1)) currentPosition++;
|
||||
else if (ui->cbLoopPlayback->isChecked()) currentPosition = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (currentPosition > 0) currentPosition--;
|
||||
else if (ui->cbLoopPlayback->isChecked()) currentPosition = frameCache.count() - 1;
|
||||
}
|
||||
|
||||
if (ui->cbAutoRef->isChecked())
|
||||
{
|
||||
memcpy(refBytes, currBytes, 8);
|
||||
}
|
||||
|
||||
memcpy(currBytes, frameCache.at(currentPosition).data, 8);
|
||||
|
||||
if (ui->cbSync->checkState() == Qt::Checked) emit sendCenterTimeID(frameCache[currentPosition].ID, frameCache[currentPosition].timestamp / 1000000.0);
|
||||
}
|
||||
|
||||
void FlowViewWindow::updateGraphLocation()
|
||||
{
|
||||
if (frameCache.count() == 0) return;
|
||||
int start = currentPosition - 5;
|
||||
if (start < 0) start = 0;
|
||||
int end = currentPosition + 5;
|
||||
if (end >= frameCache.count()) end = frameCache.count() - 1;
|
||||
if (ui->cbTimeGraph->isChecked())
|
||||
{
|
||||
if (secondsMode)
|
||||
{
|
||||
ui->graphView->xAxis->setRange(frameCache[start].timestamp / 1000000.0, frameCache[end].timestamp / 1000000.0);
|
||||
ui->graphView->xAxis->setTickStep((frameCache[end].timestamp - frameCache[start].timestamp)/ 3000000.0);
|
||||
ui->graphView->xAxis->setSubTickCount(0);
|
||||
ui->graphView->xAxis->setNumberFormat("f");
|
||||
ui->graphView->xAxis->setNumberPrecision(6);
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->graphView->xAxis->setRange(frameCache[start].timestamp, frameCache[end].timestamp);
|
||||
ui->graphView->xAxis->setTickStep((frameCache[end].timestamp - frameCache[start].timestamp)/ 3.0);
|
||||
ui->graphView->xAxis->setSubTickCount(0);
|
||||
ui->graphView->xAxis->setNumberFormat("f");
|
||||
ui->graphView->xAxis->setNumberPrecision(0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->graphView->xAxis->setRange(start, end);
|
||||
ui->graphView->xAxis->setTickStep(5.0);
|
||||
ui->graphView->xAxis->setSubTickCount(0);
|
||||
ui->graphView->xAxis->setNumberFormat("gb");
|
||||
}
|
||||
|
||||
ui->graphView->replot();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
#ifndef FLOWVIEWWINDOW_H
|
||||
#define FLOWVIEWWINDOW_H
|
||||
|
||||
#include <QDialog>
|
||||
#include "qcustomplot.h"
|
||||
#include "can_structs.h"
|
||||
|
||||
namespace Ui {
|
||||
class FlowViewWindow;
|
||||
}
|
||||
|
||||
class FlowViewWindow : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FlowViewWindow(const QVector<CANFrame> *frames, QWidget *parent = 0);
|
||||
~FlowViewWindow();
|
||||
void showEvent(QShowEvent*);
|
||||
|
||||
private slots:
|
||||
void btnBackOneClick();
|
||||
void btnPauseClick();
|
||||
void btnReverseClick();
|
||||
void btnStopClick();
|
||||
void btnPlayClick();
|
||||
void btnFwdOneClick();
|
||||
void changePlaybackSpeed(int newSpeed);
|
||||
void changeLooping(bool check);
|
||||
void timerTriggered();
|
||||
void changeID(QString);
|
||||
void updatedFrames(int);
|
||||
void contextMenuRequestFlow(QPoint pos);
|
||||
void contextMenuRequestGraph(QPoint pos);
|
||||
void saveFileFlow();
|
||||
void saveFileGraph();
|
||||
void plottableDoubleClick(QCPAbstractPlottable* plottable, QMouseEvent* event);
|
||||
void gotCenterTimeID(int32_t ID, double timestamp);
|
||||
void updateTriggerValues();
|
||||
|
||||
signals:
|
||||
void sendCenterTimeID(int32_t ID, double timestamp);
|
||||
|
||||
private:
|
||||
Ui::FlowViewWindow *ui;
|
||||
QList<int> foundID;
|
||||
QList<CANFrame> frameCache;
|
||||
const QVector<CANFrame> *modelFrames;
|
||||
unsigned char refBytes[8];
|
||||
unsigned char currBytes[8];
|
||||
int triggerValues[8];
|
||||
int currentPosition;
|
||||
QTimer *playbackTimer;
|
||||
bool playbackActive;
|
||||
bool playbackForward;
|
||||
static const QColor graphColors[8];
|
||||
bool secondsMode;
|
||||
QVector<double> x[8], y[8];
|
||||
QCPGraph *graphRef[8];
|
||||
|
||||
void refreshIDList();
|
||||
void updateFrameLabel();
|
||||
void updatePosition(bool forward);
|
||||
void updateDataView();
|
||||
void removeAllGraphs();
|
||||
void createGraph(int);
|
||||
void updateGraphLocation();
|
||||
void closeEvent(QCloseEvent *event);
|
||||
void readSettings();
|
||||
void writeSettings();
|
||||
bool eventFilter(QObject *obj, QEvent *event);
|
||||
};
|
||||
|
||||
#endif // FLOWVIEWWINDOW_H
|
||||
@@ -0,0 +1,398 @@
|
||||
#include "frameinfowindow.h"
|
||||
#include "ui_frameinfowindow.h"
|
||||
#include "mainwindow.h"
|
||||
#include <QtDebug>
|
||||
|
||||
FrameInfoWindow::FrameInfoWindow(const QVector<CANFrame> *frames, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::FrameInfoWindow)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
readSettings();
|
||||
|
||||
modelFrames = frames;
|
||||
|
||||
connect(ui->listFrameID, &QListWidget::currentTextChanged, this, &FrameInfoWindow::updateDetailsWindow);
|
||||
connect(MainWindow::getReference(), &MainWindow::framesUpdated, this, &FrameInfoWindow::updatedFrames);
|
||||
connect(ui->btnSave, &QAbstractButton::clicked, this, &FrameInfoWindow::saveDetails);
|
||||
|
||||
}
|
||||
|
||||
void FrameInfoWindow::showEvent(QShowEvent* event)
|
||||
{
|
||||
QDialog::showEvent(event);
|
||||
readSettings();
|
||||
refreshIDList();
|
||||
if (ui->listFrameID->count() > 0)
|
||||
{
|
||||
updateDetailsWindow(ui->listFrameID->item(0)->text());
|
||||
ui->listFrameID->setCurrentRow(0);
|
||||
}
|
||||
}
|
||||
|
||||
FrameInfoWindow::~FrameInfoWindow()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void FrameInfoWindow::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
writeSettings();
|
||||
}
|
||||
|
||||
void FrameInfoWindow::readSettings()
|
||||
{
|
||||
QSettings settings;
|
||||
if (settings.value("Main/SaveRestorePositions", false).toBool())
|
||||
{
|
||||
resize(settings.value("FrameInfo/WindowSize", QSize(794, 494)).toSize());
|
||||
move(settings.value("FrameInfo/WindowPos", QPoint(50, 50)).toPoint());
|
||||
}
|
||||
}
|
||||
|
||||
void FrameInfoWindow::writeSettings()
|
||||
{
|
||||
QSettings settings;
|
||||
|
||||
if (settings.value("Main/SaveRestorePositions", false).toBool())
|
||||
{
|
||||
settings.setValue("FrameInfo/WindowSize", size());
|
||||
settings.setValue("FrameInfo/WindowPos", pos());
|
||||
}
|
||||
}
|
||||
|
||||
//remember, negative numbers are special -1 = all frames deleted, -2 = totally new set of frames.
|
||||
void FrameInfoWindow::updatedFrames(int numFrames)
|
||||
{
|
||||
if (numFrames == -1) //all frames deleted. Kill the display
|
||||
{
|
||||
ui->listFrameID->clear();
|
||||
ui->treeDetails->clear();
|
||||
refreshIDList();
|
||||
}
|
||||
else if (numFrames == -2) //all new set of frames. Reset
|
||||
{
|
||||
refreshIDList();
|
||||
if (ui->listFrameID->count() > 0)
|
||||
{
|
||||
updateDetailsWindow(ui->listFrameID->item(0)->text());
|
||||
ui->listFrameID->setCurrentRow(0);
|
||||
}
|
||||
}
|
||||
else //just got some new frames. See if they are relevant.
|
||||
{
|
||||
if (numFrames > modelFrames->count()) return;
|
||||
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++)
|
||||
{
|
||||
if (currID == modelFrames->at(x).ID)
|
||||
{
|
||||
foundID = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (foundID)
|
||||
{
|
||||
//the problem here is that it'll blast us out of the details as soon as this
|
||||
//happens. The only way to do this properly is to actually traverse
|
||||
//the details structure and change the text. We don't do that yet.
|
||||
//so, the line is commented out. If people need to see the updated
|
||||
//data they can click another ID and back and it'll be OK
|
||||
|
||||
//updateDetailsWindow(ui->listFrameID->currentItem()->text());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FrameInfoWindow::updateDetailsWindow(QString newID)
|
||||
{
|
||||
int targettedID;
|
||||
int minLen, maxLen, thisLen;
|
||||
int avgInterval;
|
||||
int minInterval;
|
||||
int maxInterval;
|
||||
int thisInterval;
|
||||
int minData[8];
|
||||
int maxData[8];
|
||||
int dataHistogram[256][8];
|
||||
int bitfieldHistogram[64];
|
||||
uint8_t changedBits[8];
|
||||
uint8_t referenceBits[8];
|
||||
QTreeWidgetItem *baseNode, *dataBase, *histBase, *tempItem;
|
||||
|
||||
targettedID = Utility::ParseStringToNum(newID);
|
||||
|
||||
if (modelFrames->count() == 0) return;
|
||||
|
||||
qDebug() << "Started update details window with id " << targettedID;
|
||||
|
||||
avgInterval = 0;
|
||||
|
||||
if (targettedID > -1)
|
||||
{
|
||||
|
||||
frameCache.clear();
|
||||
for (int i = 0; i < modelFrames->count(); i++)
|
||||
{
|
||||
CANFrame thisFrame = modelFrames->at(i);
|
||||
if (thisFrame.ID == targettedID) frameCache.append(thisFrame);
|
||||
}
|
||||
|
||||
ui->treeDetails->clear();
|
||||
|
||||
if (frameCache.count() == 0) return;
|
||||
|
||||
baseNode = new QTreeWidgetItem();
|
||||
baseNode->setText(0, QString("ID: ") + newID );
|
||||
|
||||
if (frameCache[0].extended) //if these frames seem to be extended then try for J1939 decoding
|
||||
{
|
||||
J1939ID jid;
|
||||
jid.src = targettedID & 0xFF;
|
||||
jid.priority = targettedID >> 26;
|
||||
jid.pgn = (targettedID >> 8) & 0x3FFFF; //18 bits
|
||||
jid.pf = (targettedID >> 16) & 0xFF;
|
||||
jid.ps = (targettedID >> 8) & 0xFF;
|
||||
|
||||
if (jid.pf > 0xEF)
|
||||
{
|
||||
jid.isBroadcast = true;
|
||||
jid.dest = 0xFFFF;
|
||||
tempItem = new QTreeWidgetItem();
|
||||
tempItem->setText(0, tr("Broadcast Frame"));
|
||||
baseNode->addChild(tempItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
jid.dest = jid.ps;
|
||||
tempItem = new QTreeWidgetItem();
|
||||
tempItem->setText(0, tr("Destination ID: ") + Utility::formatNumber(jid.dest));
|
||||
baseNode->addChild(tempItem);
|
||||
}
|
||||
tempItem = new QTreeWidgetItem();
|
||||
tempItem->setText(0, tr("SRC: ") + Utility::formatNumber(jid.src));
|
||||
baseNode->addChild(tempItem);
|
||||
|
||||
tempItem = new QTreeWidgetItem();
|
||||
tempItem->setText(0, tr("PGN: ") + Utility::formatNumber(jid.pgn));
|
||||
baseNode->addChild(tempItem);
|
||||
|
||||
tempItem = new QTreeWidgetItem();
|
||||
tempItem->setText(0, tr("PF: ") + Utility::formatNumber(jid.pf));
|
||||
baseNode->addChild(tempItem);
|
||||
|
||||
tempItem = new QTreeWidgetItem();
|
||||
tempItem->setText(0, tr("PS: ") + Utility::formatNumber(jid.ps));
|
||||
baseNode->addChild(tempItem);
|
||||
}
|
||||
|
||||
tempItem = new QTreeWidgetItem();
|
||||
tempItem->setText(0, tr("# of frames: ") + QString::number(frameCache.count(),10));
|
||||
baseNode->addChild(tempItem);
|
||||
|
||||
//clear out all the counters and accumulators
|
||||
minLen = 8;
|
||||
maxLen = 0;
|
||||
minInterval = 0x7FFFFFFF;
|
||||
maxInterval = 0;
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
minData[i] = 256;
|
||||
maxData[i] = -1;
|
||||
for (int k = 0; k < 256; k++) dataHistogram[k][i] = 0;
|
||||
}
|
||||
for (int j = 0; j < 64; j++) bitfieldHistogram[j] = 0;
|
||||
|
||||
for (int c = 0; c < 8; c++)
|
||||
{
|
||||
changedBits[c] = 0;
|
||||
referenceBits[c] = frameCache.at(0).data[c];
|
||||
qDebug() << referenceBits[c];
|
||||
}
|
||||
|
||||
//then find all data points
|
||||
for (int j = 0; j < frameCache.count(); j++)
|
||||
{
|
||||
if (j != 0)
|
||||
{
|
||||
thisInterval = (frameCache[j].timestamp - frameCache[j-1].timestamp);
|
||||
if (thisInterval > maxInterval) maxInterval = thisInterval;
|
||||
if (thisInterval < minInterval) minInterval = thisInterval;
|
||||
avgInterval += thisInterval;
|
||||
}
|
||||
thisLen = frameCache.at(j).len;
|
||||
if (thisLen > maxLen) maxLen = thisLen;
|
||||
if (thisLen < minLen) minLen = thisLen;
|
||||
for (int c = 0; c < thisLen; c++)
|
||||
{
|
||||
unsigned char dat = frameCache.at(j).data[c];
|
||||
if (minData[c] > dat) minData[c] = dat;
|
||||
if (maxData[c] < dat) maxData[c] = dat;
|
||||
dataHistogram[dat][c]++; //add one to count for this
|
||||
for (int l = 0; l < 8; l++)
|
||||
{
|
||||
int bit = dat & (1 << l);
|
||||
if (bit == (1 << l))
|
||||
{
|
||||
bitfieldHistogram[c * 8 + l]++;
|
||||
}
|
||||
}
|
||||
changedBits[c] |= referenceBits[c] ^ dat;
|
||||
}
|
||||
}
|
||||
|
||||
if (frameCache.count() > 1)
|
||||
avgInterval = avgInterval / (frameCache.count() - 1);
|
||||
else avgInterval = 0;
|
||||
|
||||
tempItem = new QTreeWidgetItem();
|
||||
|
||||
if (minLen < maxLen)
|
||||
tempItem->setText(0, tr("Data Length: ") + QString::number(minLen) + tr(" to ") + QString::number(maxLen));
|
||||
else
|
||||
tempItem->setText(0, tr("Data Length: ") + QString::number(minLen));
|
||||
|
||||
baseNode->addChild(tempItem);
|
||||
|
||||
tempItem = new QTreeWidgetItem();
|
||||
tempItem->setText(0, tr("Average inter-frame interval: ") + QString::number(avgInterval / 1000.0f) + "ms");
|
||||
baseNode->addChild(tempItem);
|
||||
tempItem = new QTreeWidgetItem();
|
||||
tempItem->setText(0, tr("Minimum inter-frame interval: ") + QString::number(minInterval / 1000.0f) + "ms");
|
||||
baseNode->addChild(tempItem);
|
||||
tempItem = new QTreeWidgetItem();
|
||||
tempItem->setText(0, tr("Maximum inter-frame interval: ") + QString::number(maxInterval / 1000.0f) + "ms");
|
||||
baseNode->addChild(tempItem);
|
||||
tempItem = new QTreeWidgetItem();
|
||||
tempItem->setText(0, tr("Inter-frame interval variation: ") + QString::number((maxInterval - minInterval) / 1000.0f) + "ms");
|
||||
baseNode->addChild(tempItem);
|
||||
|
||||
for (int c = 0; c < maxLen; c++)
|
||||
{
|
||||
dataBase = new QTreeWidgetItem();
|
||||
histBase = new QTreeWidgetItem();
|
||||
|
||||
dataBase->setText(0, tr("Data Byte ") + QString::number(c));
|
||||
baseNode->addChild(dataBase);
|
||||
|
||||
tempItem = new QTreeWidgetItem();
|
||||
QString builder;
|
||||
builder = tr("Changed bits: 0x") + QString::number(changedBits[c], 16) + " (" + Utility::formatByteAsBinary(changedBits[c]) + ")";
|
||||
tempItem->setText(0, builder);
|
||||
dataBase->addChild(tempItem);
|
||||
|
||||
tempItem = new QTreeWidgetItem();
|
||||
tempItem->setText(0, tr("Range: ") + Utility::formatNumber(minData[c]) + tr(" to ") + Utility::formatNumber(maxData[c]));
|
||||
dataBase->addChild(tempItem);
|
||||
histBase->setText(0, tr("Histogram"));
|
||||
dataBase->addChild(histBase);
|
||||
|
||||
for (int d = 0; d < 256; d++)
|
||||
{
|
||||
if (dataHistogram[d][c] > 0)
|
||||
{
|
||||
tempItem = new QTreeWidgetItem();
|
||||
tempItem->setText(0, QString::number(d) + "/0x" + QString::number(d, 16) +" (" + Utility::formatByteAsBinary(d) +") -> " + QString::number(dataHistogram[d][c]));
|
||||
histBase->addChild(tempItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dataBase = new QTreeWidgetItem();
|
||||
dataBase->setText(0, tr("Bitfield Histogram"));
|
||||
for (int c = 0; c < 8 * maxLen; c++)
|
||||
{
|
||||
tempItem = new QTreeWidgetItem();
|
||||
tempItem->setText(0, QString::number(c) + " (Byte " + QString::number(c / 8) + " Bit "
|
||||
+ QString::number(c % 8) + ") :" + QString::number(bitfieldHistogram[c]));
|
||||
|
||||
dataBase->addChild(tempItem);
|
||||
}
|
||||
baseNode->addChild(dataBase);
|
||||
|
||||
ui->treeDetails->insertTopLevelItem(0, baseNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
|
||||
QSettings settings;
|
||||
if (settings.value("InfoCompare/AutoExpand", false).toBool())
|
||||
{
|
||||
ui->treeDetails->expandAll();
|
||||
}
|
||||
}
|
||||
|
||||
void FrameInfoWindow::refreshIDList()
|
||||
{
|
||||
int id;
|
||||
for (int i = 0; i < modelFrames->count(); i++)
|
||||
{
|
||||
id = modelFrames->at(i).ID;
|
||||
if (!foundID.contains(id))
|
||||
{
|
||||
foundID.append(id);
|
||||
ui->listFrameID->addItem(Utility::formatNumber(id));
|
||||
}
|
||||
}
|
||||
//default is to sort in ascending order
|
||||
ui->listFrameID->sortItems();
|
||||
ui->lblFrameID->setText(tr("Frame IDs: (") + QString::number(ui->listFrameID->count()) + tr(" unique ids)"));
|
||||
}
|
||||
|
||||
void FrameInfoWindow::saveDetails()
|
||||
{
|
||||
QString filename;
|
||||
QFileDialog dialog(this);
|
||||
|
||||
QStringList filters;
|
||||
filters.append(QString(tr("Text File (*.txt)")));
|
||||
|
||||
dialog.setFileMode(QFileDialog::AnyFile);
|
||||
dialog.setNameFilters(filters);
|
||||
dialog.setViewMode(QFileDialog::Detail);
|
||||
dialog.setAcceptMode(QFileDialog::AcceptSave);
|
||||
|
||||
if (dialog.exec() == QDialog::Accepted)
|
||||
{
|
||||
filename = dialog.selectedFiles()[0];
|
||||
if (!filename.contains('.')) filename += ".txt";
|
||||
if (dialog.selectedNameFilter() == filters[0])
|
||||
{
|
||||
QFile *outFile = new QFile(filename);
|
||||
|
||||
if (!outFile->open(QIODevice::WriteOnly | QIODevice::Text))
|
||||
{
|
||||
delete outFile;
|
||||
return;
|
||||
}
|
||||
|
||||
//go through all IDs, recalculate the data, and then save it to file
|
||||
for (int i = 0; i < ui->listFrameID->count(); i++)
|
||||
{
|
||||
updateDetailsWindow(ui->listFrameID->item(i)->text());
|
||||
dumpNode(ui->treeDetails->invisibleRootItem(), outFile, 0);
|
||||
outFile->write("\n\n");
|
||||
}
|
||||
|
||||
outFile->close();
|
||||
delete outFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FrameInfoWindow::dumpNode(QTreeWidgetItem* item, QFile *file, int indent)
|
||||
{
|
||||
for (int i = 0; i < indent; i++) file->write("\t");
|
||||
file->write(item->text(0).toUtf8());
|
||||
file->write("\n");
|
||||
for( int i = 0; i < item->childCount(); ++i )
|
||||
dumpNode( item->child(i), file, indent + 1 );
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef FRAMEINFOWINDOW_H
|
||||
#define FRAMEINFOWINDOW_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QFile>
|
||||
#include <QListWidget>
|
||||
#include <QTreeWidget>
|
||||
#include "can_structs.h"
|
||||
|
||||
namespace Ui {
|
||||
class FrameInfoWindow;
|
||||
}
|
||||
|
||||
class FrameInfoWindow : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FrameInfoWindow(const QVector<CANFrame> *frames, QWidget *parent = 0);
|
||||
~FrameInfoWindow();
|
||||
void showEvent(QShowEvent*);
|
||||
|
||||
private slots:
|
||||
void updateDetailsWindow(QString);
|
||||
void updatedFrames(int);
|
||||
void saveDetails();
|
||||
|
||||
private:
|
||||
Ui::FrameInfoWindow *ui;
|
||||
|
||||
QList<int> foundID;
|
||||
QList<CANFrame> frameCache;
|
||||
const QVector<CANFrame> *modelFrames;
|
||||
|
||||
void refreshIDList();
|
||||
void closeEvent(QCloseEvent *event);
|
||||
void readSettings();
|
||||
void writeSettings();
|
||||
void dumpNode(QTreeWidgetItem* item, QFile *file, int indent);
|
||||
};
|
||||
|
||||
#endif // FRAMEINFOWINDOW_H
|
||||
@@ -0,0 +1,445 @@
|
||||
#include "fuzzingwindow.h"
|
||||
#include "ui_fuzzingwindow.h"
|
||||
#include "utility.h"
|
||||
#include <QDebug>
|
||||
#include "mainwindow.h"
|
||||
#include "connections/canconmanager.h"
|
||||
|
||||
FuzzingWindow::FuzzingWindow(const QVector<CANFrame> *frames, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::FuzzingWindow)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
modelFrames = frames;
|
||||
|
||||
fuzzTimer = new QTimer();
|
||||
|
||||
connect(ui->btnStartStop, &QPushButton::clicked, this, &FuzzingWindow::toggleFuzzing);
|
||||
connect(ui->btnAllFilters, &QPushButton::clicked, this, &FuzzingWindow::setAllFilters);
|
||||
connect(ui->btnNoFilters, &QPushButton::clicked, this, &FuzzingWindow::clearAllFilters);
|
||||
connect(fuzzTimer, &QTimer::timeout, this, &FuzzingWindow::timerTriggered);
|
||||
connect(ui->spinTiming, SIGNAL(valueChanged(int)), this, SLOT(changePlaybackSpeed(int)));
|
||||
connect(ui->listID, &QListWidget::itemChanged, this, &FuzzingWindow::idListChanged);
|
||||
connect(ui->spinBytes, SIGNAL(valueChanged(int)), this, SLOT(changedNumDataBytes(int)));
|
||||
connect(ui->bitfield, SIGNAL(gridClicked(int,int)), this, SLOT(bitfieldClicked(int,int)));
|
||||
|
||||
connect(MainWindow::getReference(), SIGNAL(framesUpdated(int)), this, SLOT(updatedFrames(int)));
|
||||
|
||||
refreshIDList();
|
||||
|
||||
currentlyFuzzing = false;
|
||||
|
||||
for (int j = 0; j < 64; j++) bitGrid[j] = 1;
|
||||
numBits = 64;
|
||||
bitAccum = 0;
|
||||
redrawGrid();
|
||||
|
||||
fuzzTimer->setInterval(ui->spinTiming->value());
|
||||
|
||||
int numBuses = CANConManager::getInstance()->getNumBuses();
|
||||
for (int n = 0; n < numBuses; n++) ui->cbBuses->addItem(QString::number(n));
|
||||
ui->cbBuses->addItem(tr("All"));
|
||||
|
||||
}
|
||||
|
||||
FuzzingWindow::~FuzzingWindow()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void FuzzingWindow::updatedFrames(int numFrames)
|
||||
{
|
||||
CANFrame thisFrame;
|
||||
int id;
|
||||
if (numFrames == -1) //all frames deleted. Kill the display
|
||||
{
|
||||
ui->listID->clear();
|
||||
foundIDs.clear();
|
||||
refreshIDList();
|
||||
}
|
||||
else if (numFrames == -2) //all new set of frames. Reset
|
||||
{
|
||||
ui->listID->clear();
|
||||
foundIDs.clear();
|
||||
refreshIDList();
|
||||
}
|
||||
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++)
|
||||
{
|
||||
id = modelFrames->at(i).ID;
|
||||
if (!foundIDs.contains(id))
|
||||
{
|
||||
foundIDs.append(id);
|
||||
selectedIDs.append(id);
|
||||
QListWidgetItem *thisItem = new QListWidgetItem();
|
||||
thisItem->setText(Utility::formatNumber(id));
|
||||
thisItem->setFlags(thisItem->flags() | Qt::ItemIsUserCheckable);
|
||||
thisItem->setCheckState(Qt::Checked);
|
||||
ui->listID->addItem(thisItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FuzzingWindow::changePlaybackSpeed(int newSpeed)
|
||||
{
|
||||
fuzzTimer->setInterval(newSpeed);
|
||||
}
|
||||
|
||||
void FuzzingWindow::changedNumDataBytes(int newVal)
|
||||
{
|
||||
qDebug() << "new num bytes: " << newVal;
|
||||
int byt;
|
||||
for (int i = 0; i < 64; i++)
|
||||
{
|
||||
byt = i / 8;
|
||||
if (byt >= newVal)
|
||||
{
|
||||
bitGrid[i] = 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (bitGrid[i] == 3) bitGrid[i] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
redrawGrid();
|
||||
}
|
||||
|
||||
void FuzzingWindow::timerTriggered()
|
||||
{
|
||||
CANFrame thisFrame;
|
||||
sendingBuffer.clear();
|
||||
int buses = ui->cbBuses->currentIndex();
|
||||
for (int count = 0; count < ui->spinBurst->value(); count++)
|
||||
{
|
||||
thisFrame.ID = currentID;
|
||||
for (int i = 0; i < 8; i++) thisFrame.data[i] = currentBytes[i];
|
||||
if (currentID > 0x7FF) thisFrame.extended = true;
|
||||
else thisFrame.extended = false;
|
||||
thisFrame.bus = 0; //hard coded for now. TODO: do not hard code
|
||||
thisFrame.len = ui->spinBytes->value();
|
||||
|
||||
if (buses < (ui->cbBuses->count() - 1))
|
||||
{
|
||||
thisFrame.bus = buses;
|
||||
sendingBuffer.append(thisFrame);
|
||||
}
|
||||
else //fuzz all the buses! HACK THE PLANET! Er, something...
|
||||
{
|
||||
for (int j = 0; j < ui->cbBuses->count() - 1; j++)
|
||||
{
|
||||
thisFrame.bus = j;
|
||||
sendingBuffer.append(thisFrame);
|
||||
}
|
||||
}
|
||||
|
||||
calcNextID();
|
||||
calcNextBitPattern();
|
||||
numSentFrames++;
|
||||
}
|
||||
CANConManager::getInstance()->sendFrames(sendingBuffer);
|
||||
ui->lblNumFrames->setText("# of sent frames: " + QString::number(numSentFrames));
|
||||
}
|
||||
|
||||
void FuzzingWindow::clearAllFilters()
|
||||
{
|
||||
for (int i = 0; i < ui->listID->count(); i++)
|
||||
{
|
||||
ui->listID->item(i)->setCheckState(Qt::Unchecked);
|
||||
}
|
||||
}
|
||||
|
||||
void FuzzingWindow::setAllFilters()
|
||||
{
|
||||
for (int i = 0; i < ui->listID->count(); i++)
|
||||
{
|
||||
ui->listID->item(i)->setCheckState(Qt::Checked);
|
||||
}
|
||||
}
|
||||
|
||||
void FuzzingWindow::calcNextID()
|
||||
{
|
||||
if (seqIDScan)
|
||||
{
|
||||
if (rangeIDSelect)
|
||||
{
|
||||
currentID++;
|
||||
if (currentID > endID) currentID = startID;
|
||||
}
|
||||
else //IDs by filter. So, select the first filter
|
||||
{
|
||||
currentIdx++;
|
||||
if (currentIdx >= selectedIDs.length()) currentIdx = 0;
|
||||
currentID = selectedIDs[currentIdx];
|
||||
qDebug() << "idx id: " << currentID;
|
||||
}
|
||||
}
|
||||
else //random IDs
|
||||
{
|
||||
if (rangeIDSelect)
|
||||
{
|
||||
int range = endID - startID;
|
||||
if (range != 0) currentID = startID + qrand() % range;
|
||||
else currentID = startID;
|
||||
}
|
||||
else //IDs by filter so pick a random selected ID from the filter list
|
||||
{
|
||||
currentIdx = qrand() % selectedIDs.length();
|
||||
currentID = selectedIDs[currentIdx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FuzzingWindow::calcNextBitPattern()
|
||||
{
|
||||
uint64_t accum;
|
||||
|
||||
switch (bitSequenceType)
|
||||
{
|
||||
case BitSequenceType::Random:
|
||||
int thisBit;
|
||||
for (int byt = 0; byt < ui->spinBytes->value(); byt++)
|
||||
{
|
||||
currentBytes[byt] = 0;
|
||||
for (int bit = 0; bit < 8; bit++)
|
||||
{
|
||||
thisBit = bitGrid[byt * 8 + bit];
|
||||
if (thisBit == 1)
|
||||
{
|
||||
if ((qrand() % 2) == 1) currentBytes[byt] |= (1 << bit);
|
||||
}
|
||||
if (thisBit == 2) currentBytes[byt] |= (1 << bit);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case BitSequenceType::Sequential:
|
||||
bitAccum++;
|
||||
bitAccum &= ((1 << numBits) - 1);
|
||||
accum = bitAccum;
|
||||
for (int byt = 0; byt < ui->spinBytes->value(); byt++)
|
||||
{
|
||||
currentBytes[byt] = 0;
|
||||
for (int bit = 0; bit < 8; bit++)
|
||||
{
|
||||
thisBit = bitGrid[byt * 8 + bit];
|
||||
if (thisBit == 1)
|
||||
{
|
||||
if (accum & 1) currentBytes[byt] |= (1 << bit);
|
||||
accum >>= 1;
|
||||
}
|
||||
if (thisBit == 2) currentBytes[byt] |= (1 << bit);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case BitSequenceType::Sweeping:
|
||||
qDebug() << "Start " << bitAccum;
|
||||
accum = bitAccum;
|
||||
|
||||
int offset;
|
||||
for (int i = 1; i < 64; i++)
|
||||
{
|
||||
offset = (i + bitAccum) % 64;
|
||||
if (bitGrid[offset] == 1)
|
||||
{
|
||||
bitAccum = offset;
|
||||
qDebug() << "End " << bitAccum;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int byt = 0; byt < ui->spinBytes->value(); byt++)
|
||||
{
|
||||
currentBytes[byt] = 0;
|
||||
for (int bit = 0; bit < 8; bit++)
|
||||
{
|
||||
thisBit = bitGrid[byt * 8 + bit];
|
||||
if (thisBit == 1 && (byt * 8 + bit) == bitAccum)
|
||||
{
|
||||
currentBytes[byt] |= (1 << bit);
|
||||
}
|
||||
if (thisBit == 2) currentBytes[byt] |= (1 << bit);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void FuzzingWindow::toggleFuzzing()
|
||||
{
|
||||
if (currentlyFuzzing) //stop it then
|
||||
{
|
||||
ui->btnStartStop->setText("Start Fuzzing");
|
||||
currentlyFuzzing = false;
|
||||
fuzzTimer->stop();
|
||||
}
|
||||
else //start it then
|
||||
{
|
||||
ui->btnStartStop->setText("Stop Fuzzing");
|
||||
currentlyFuzzing = true;
|
||||
|
||||
startID = Utility::ParseStringToNum(ui->txtStartID->text());
|
||||
endID = Utility::ParseStringToNum(ui->txtEndID->text());
|
||||
|
||||
seqIDScan = ui->rbSequentialID->isChecked();
|
||||
rangeIDSelect = ui->rbRangeIDSel->isChecked();
|
||||
if (ui->rbSequentialBits->isChecked()) bitSequenceType = BitSequenceType::Sequential;
|
||||
if (ui->rbRandomBits->isChecked())
|
||||
{
|
||||
bitSequenceType = BitSequenceType::Random;
|
||||
bitAccum = 0;
|
||||
}
|
||||
if (ui->rbSweep->isChecked())
|
||||
{
|
||||
bitSequenceType = BitSequenceType::Sweeping;
|
||||
for (int i = 0; i < 64; i++)
|
||||
{
|
||||
if (bitGrid[i] == 1)
|
||||
{
|
||||
bitAccum = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
numSentFrames = 0;
|
||||
|
||||
if (seqIDScan)
|
||||
{
|
||||
if (rangeIDSelect)
|
||||
{
|
||||
currentID = startID;
|
||||
}
|
||||
else //IDs by filter. So, select the first filter
|
||||
{
|
||||
currentIdx = 0;
|
||||
currentID = selectedIDs[currentIdx];
|
||||
}
|
||||
}
|
||||
else //random IDs
|
||||
{
|
||||
if (rangeIDSelect)
|
||||
{
|
||||
int range = endID - startID;
|
||||
if (range != 0) currentID = startID + qrand() % range;
|
||||
else currentID = startID;
|
||||
}
|
||||
else //IDs by filter so pick a random selected ID from the filter list
|
||||
{
|
||||
currentIdx = qrand() % selectedIDs.length();
|
||||
currentID = selectedIDs[currentIdx];
|
||||
}
|
||||
}
|
||||
|
||||
calcNextBitPattern();
|
||||
|
||||
fuzzTimer->start();
|
||||
}
|
||||
}
|
||||
|
||||
void FuzzingWindow::refreshIDList()
|
||||
{
|
||||
ui->listID->clear();
|
||||
foundIDs.clear();
|
||||
|
||||
int id;
|
||||
for (int i = 0; i < modelFrames->count(); i++)
|
||||
{
|
||||
id = modelFrames->at(i).ID;
|
||||
if (!foundIDs.contains(id))
|
||||
{
|
||||
foundIDs.append(id);
|
||||
selectedIDs.append(id);
|
||||
QListWidgetItem *thisItem = new QListWidgetItem();
|
||||
thisItem->setText(Utility::formatNumber(id));
|
||||
thisItem->setFlags(thisItem->flags() | Qt::ItemIsUserCheckable);
|
||||
thisItem->setCheckState(Qt::Checked);
|
||||
ui->listID->addItem(thisItem);
|
||||
}
|
||||
}
|
||||
//default is to sort in ascending order
|
||||
ui->listID->sortItems();
|
||||
}
|
||||
|
||||
void FuzzingWindow::idListChanged(QListWidgetItem *item)
|
||||
{
|
||||
int id = Utility::ParseStringToNum(item->text());
|
||||
if (item->checkState() == Qt::Checked)
|
||||
{
|
||||
if (!selectedIDs.contains(id))
|
||||
{
|
||||
qDebug() << "adding " << id << " to list of selected IDs";
|
||||
selectedIDs.append(id);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "removing " << id << " from the list of selected ids";
|
||||
selectedIDs.removeOne(id);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
bitGrid stores the state of all 64 bits.
|
||||
The grid is capable of showing the following colors:
|
||||
White = not used (left as 0)
|
||||
Gray = past the end of the valid bits (because of # of data bytes requested)
|
||||
Green = fuzz it
|
||||
black = always keep it set to 1
|
||||
*/
|
||||
void FuzzingWindow::bitfieldClicked(int x, int y)
|
||||
{
|
||||
qDebug() << "X: " << x << " Y: " << y;
|
||||
int bit = (7 - x) + (y * 8);
|
||||
if (bitGrid[bit] == 3) return; //naughty!
|
||||
bitGrid[bit]++;
|
||||
if (bitGrid[bit] > 2) bitGrid[bit] = 0;
|
||||
|
||||
redrawGrid();
|
||||
}
|
||||
|
||||
void FuzzingWindow::redrawGrid()
|
||||
{
|
||||
//now update the bits in the bitfield control
|
||||
uint8_t refBytes[8];
|
||||
uint8_t dataBytes[8];
|
||||
uint8_t usedBytes[8];
|
||||
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
refBytes[j] = 0;
|
||||
dataBytes[j] = 0;
|
||||
usedBytes[j] = 0;
|
||||
}
|
||||
|
||||
numBits = 0;
|
||||
|
||||
for (int i = 0; i < 64; i++)
|
||||
{
|
||||
int byt = i / 8;
|
||||
int bit = i % 8;
|
||||
switch (bitGrid[i])
|
||||
{
|
||||
case 0: //white, keep this bit off always
|
||||
break;
|
||||
case 1: //Green, fuzz this bit
|
||||
dataBytes[byt] |= (1 << bit);
|
||||
numBits++;
|
||||
break;
|
||||
case 2: //black, bit always set
|
||||
dataBytes[byt] |= (1 << bit);
|
||||
refBytes[byt] |= (1 << bit);
|
||||
break;
|
||||
case 3: //gray, this bit doesn't exist
|
||||
usedBytes[byt] |= (1 << bit);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ui->bitfield->setUsed(usedBytes, false);
|
||||
ui->bitfield->setReference(refBytes, false);
|
||||
ui->bitfield->updateData(dataBytes, true);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
#ifndef FUZZINGWINDOW_H
|
||||
#define FUZZINGWINDOW_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QListWidget>
|
||||
#include <QTimer>
|
||||
#include "can_structs.h"
|
||||
|
||||
namespace Ui {
|
||||
class FuzzingWindow;
|
||||
}
|
||||
|
||||
namespace BitSequenceType
|
||||
{
|
||||
enum
|
||||
{
|
||||
Sequential,
|
||||
Sweeping,
|
||||
Random
|
||||
};
|
||||
}
|
||||
|
||||
class FuzzingWindow : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FuzzingWindow(const QVector<CANFrame> *frames, QWidget *parent = 0);
|
||||
~FuzzingWindow();
|
||||
|
||||
signals:
|
||||
void sendCANFrame(const CANFrame *);
|
||||
void sendFrameBatch(const QList<CANFrame> *);
|
||||
|
||||
private slots:
|
||||
void changePlaybackSpeed(int newSpeed);
|
||||
void timerTriggered();
|
||||
void clearAllFilters();
|
||||
void setAllFilters();
|
||||
void toggleFuzzing();
|
||||
void idListChanged(QListWidgetItem *item);
|
||||
void bitfieldClicked(int, int);
|
||||
void changedNumDataBytes(int newVal);
|
||||
void updatedFrames(int numFrames);
|
||||
|
||||
private:
|
||||
Ui::FuzzingWindow *ui;
|
||||
const QVector<CANFrame> *modelFrames;
|
||||
QTimer *fuzzTimer;
|
||||
QList<int> foundIDs;
|
||||
QList<int> selectedIDs;
|
||||
QList<CANFrame> sendingBuffer;
|
||||
int startID, endID, currentID, currentIdx;
|
||||
bool seqIDScan, rangeIDSelect;
|
||||
int bitSequenceType;
|
||||
bool currentlyFuzzing;
|
||||
uint8_t currentBytes[8];
|
||||
uint8_t bitGrid[64];
|
||||
uint8_t numBits;
|
||||
uint64_t bitAccum;
|
||||
int numSentFrames;
|
||||
|
||||
void refreshIDList();
|
||||
void calcNextID();
|
||||
void calcNextBitPattern();
|
||||
void redrawGrid();
|
||||
};
|
||||
|
||||
#endif // FUZZINGWINDOW_H
|
||||
@@ -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<CANFrame> *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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#ifndef ISOTP_DECODER_H
|
||||
#define ISOTP_DECODER_H
|
||||
|
||||
#include <Qt>
|
||||
#include <QObject>
|
||||
#include <QDebug>
|
||||
#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<CANFrame> *frames, QObject *parent = 0);
|
||||
void setExtendedAddressing(bool mode);
|
||||
|
||||
public slots:
|
||||
void updatedFrames(int);
|
||||
|
||||
signals:
|
||||
void newISOMessage(ISOTP_MESSAGE &msg);
|
||||
|
||||
private:
|
||||
QList<ISOTP_MESSAGE> messageBuffer;
|
||||
const QVector<CANFrame> *modelFrames;
|
||||
bool useExtendedAddressing;
|
||||
|
||||
void processFrame(const CANFrame &frame);
|
||||
void checkNeedFlush(uint64_t ID);
|
||||
};
|
||||
|
||||
#endif // ISOTP_DECODER_H
|
||||
@@ -0,0 +1,154 @@
|
||||
#include "isotp_interpreterwindow.h"
|
||||
#include "ui_isotp_interpreterwindow.h"
|
||||
#include "mainwindow.h"
|
||||
|
||||
ISOTP_InterpreterWindow::ISOTP_InterpreterWindow(const QVector<CANFrame> *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));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#ifndef ISOTP_INTERPRETERWINDOW_H
|
||||
#define ISOTP_INTERPRETERWINDOW_H
|
||||
|
||||
#include <QDialog>
|
||||
#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<CANFrame> *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<CANFrame> *modelFrames;
|
||||
QVector<ISOTP_MESSAGE> messages;
|
||||
|
||||
void closeEvent(QCloseEvent *event);
|
||||
void readSettings();
|
||||
void writeSettings();
|
||||
};
|
||||
|
||||
#endif // ISOTP_INTERPRETERWINDOW_H
|
||||
@@ -0,0 +1,430 @@
|
||||
#include "rangestatewindow.h"
|
||||
#include "ui_rangestatewindow.h"
|
||||
#include "mainwindow.h"
|
||||
#include "utility.h"
|
||||
|
||||
RangeStateWindow::RangeStateWindow(const QVector<CANFrame> *frames, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::RangeStateWindow)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
modelFrames = frames;
|
||||
|
||||
ui->graphSignal->xAxis->setRange(0, 8);
|
||||
ui->graphSignal->yAxis->setRange(-10, 265); //run range a bit outside possible number so they aren't plotted in a hard to see place
|
||||
ui->graphSignal->xAxis->setVisible(false);
|
||||
ui->graphSignal->yAxis->setVisible(false);
|
||||
|
||||
//ui->graphSignal->axisRect()->setupFullAxesBox();
|
||||
|
||||
ui->cbSignalMode->addItem(tr("Big Endian"));
|
||||
ui->cbSignalMode->addItem(tr("Little Endian"));
|
||||
ui->cbSignalMode->addItem(tr("Try Both"));
|
||||
|
||||
ui->cbSignedMode->addItem(tr("Signed Value"));
|
||||
ui->cbSignedMode->addItem(tr("Unsigned Value"));
|
||||
ui->cbSignedMode->addItem(tr("Try Both"));
|
||||
|
||||
//lambda expressions used here because these are tiny functions that don't have any reason to be full named functions
|
||||
//unfortunately the fact that valueChanged is overloaded makes the syntax here HORRIBLE. That sucks.
|
||||
connect(ui->spinMaxSigSize, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
|
||||
[=](int newVal)
|
||||
{
|
||||
if (newVal < ui->spinMinSigSize->value()) ui->spinMinSigSize->setValue(newVal);
|
||||
});
|
||||
|
||||
connect(ui->spinMinSigSize, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
|
||||
[=](int newVal)
|
||||
{
|
||||
if (newVal > ui->spinMaxSigSize->value()) ui->spinMaxSigSize->setValue(newVal);
|
||||
});
|
||||
|
||||
connect(ui->btnAllFilter, &QAbstractButton::clicked,
|
||||
[=]()
|
||||
{
|
||||
for (int i = 0; i < ui->listFilter->count(); i++)
|
||||
{
|
||||
QListWidgetItem *item = ui->listFilter->item(i);
|
||||
item->setCheckState(Qt::Checked);
|
||||
idFilters[Utility::ParseStringToNum(item->text())] = true;
|
||||
}
|
||||
});
|
||||
|
||||
connect(ui->btnNoneFilter, &QAbstractButton::clicked,
|
||||
[=]()
|
||||
{
|
||||
for (int i = 0; i < ui->listFilter->count(); i++)
|
||||
{
|
||||
QListWidgetItem *item = ui->listFilter->item(i);
|
||||
item->setCheckState(Qt::Unchecked);
|
||||
idFilters[Utility::ParseStringToNum(item->text())] = false;
|
||||
}
|
||||
});
|
||||
|
||||
connect(ui->listFilter, &QListWidget::itemChanged,
|
||||
[=](QListWidgetItem *item)
|
||||
{
|
||||
bool isChecked = false;
|
||||
int id = Utility::ParseStringToNum(item->text());
|
||||
if (item->checkState() == Qt::Checked) isChecked = true;
|
||||
idFilters[id] = isChecked;
|
||||
});
|
||||
|
||||
connect(ui->btnRecalc, &QAbstractButton::clicked, this, &RangeStateWindow::recalcButton);
|
||||
connect(MainWindow::getReference(), SIGNAL(framesUpdated(int)), this, SLOT(updatedFrames(int)));
|
||||
connect(ui->listCandidates, &QListWidget::currentRowChanged, this, &RangeStateWindow::clickedSignalList);
|
||||
}
|
||||
|
||||
RangeStateWindow::~RangeStateWindow()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void RangeStateWindow::showEvent(QShowEvent* event)
|
||||
{
|
||||
QDialog::showEvent(event);
|
||||
|
||||
readSettings();
|
||||
|
||||
refreshFilterList();
|
||||
}
|
||||
|
||||
void RangeStateWindow::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
writeSettings();
|
||||
}
|
||||
|
||||
void RangeStateWindow::readSettings()
|
||||
{
|
||||
QSettings settings;
|
||||
if (settings.value("Main/SaveRestorePositions", false).toBool())
|
||||
{
|
||||
resize(settings.value("RangeStateView/WindowSize", QSize(765, 615)).toSize());
|
||||
move(settings.value("RangeStateView/WindowPos", QPoint(50, 50)).toPoint());
|
||||
}
|
||||
}
|
||||
|
||||
void RangeStateWindow::writeSettings()
|
||||
{
|
||||
QSettings settings;
|
||||
|
||||
if (settings.value("Main/SaveRestorePositions", false).toBool())
|
||||
{
|
||||
settings.setValue("RangeStateView/WindowSize", size());
|
||||
settings.setValue("RangeStateView/WindowPos", pos());
|
||||
}
|
||||
}
|
||||
|
||||
void RangeStateWindow::updatedFrames(int numFrames)
|
||||
{
|
||||
CANFrame thisFrame;
|
||||
if (numFrames == -1) //all frames deleted. We don't need to do a thing on this window but erase everything in the filters section
|
||||
{
|
||||
ui->listFilter->clear();
|
||||
idFilters.clear();
|
||||
}
|
||||
else if (numFrames == -2) //all new set of frames. Reset
|
||||
{
|
||||
refreshFilterList();
|
||||
}
|
||||
else //just got some new frames. See if we need to update the filters list. Otherwise nothing to do - no recalc happens until the button is pressed
|
||||
{
|
||||
if (numFrames > modelFrames->count()) return;
|
||||
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->listFilter);
|
||||
listItem->setFlags(listItem->flags() | Qt::ItemIsUserCheckable); // set checkable flag
|
||||
listItem->setCheckState(Qt::Checked); //default all filters to be set active
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RangeStateWindow::refreshFilterList()
|
||||
{
|
||||
int id;
|
||||
|
||||
idFilters.clear();
|
||||
ui->listFilter->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->listFilter);
|
||||
listItem->setFlags(listItem->flags() | Qt::ItemIsUserCheckable); // set checkable flag
|
||||
listItem->setCheckState(Qt::Checked); //default all filters to be set active
|
||||
}
|
||||
}
|
||||
|
||||
ui->listFilter->sortItems();
|
||||
}
|
||||
|
||||
void RangeStateWindow::recalcButton()
|
||||
{
|
||||
QHash<int, bool>::iterator iter;
|
||||
int id;
|
||||
|
||||
ui->listCandidates->clear();
|
||||
foundSignals.clear();
|
||||
|
||||
for (iter = idFilters.begin(); iter != idFilters.end(); ++iter)
|
||||
{
|
||||
if (iter.value() == true)
|
||||
{
|
||||
qDebug() << "Processing for ID: " << iter.key();
|
||||
//so, we're supposed to process this frame ID. We'll need to create a frame cache for it
|
||||
frameCache.clear();
|
||||
frameCache.reserve(modelFrames->count()); //block allocate more than enough space
|
||||
id = iter.key();
|
||||
for (int j = 0; j < modelFrames->count(); j++)
|
||||
{
|
||||
if (modelFrames->at(j).ID == id) frameCache.append(modelFrames->at(j));
|
||||
}
|
||||
//now we've got a list with all the same ID. Time to send it off for processing
|
||||
signalsFactory();
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "Found " << foundSignals.count() << " signals total.";
|
||||
}
|
||||
|
||||
/*
|
||||
* Uses the settings exposed to the user to generate a set of candidate signals that should be checked.
|
||||
* The user could specify signal sizes, granularity, endian type and we generate all the permutations from there
|
||||
* Should process from max to min and stop when a valid signal is found (at least as an option) to declutter a bit.
|
||||
* Mostly what we're interested in is the largest signal that matches
|
||||
*/
|
||||
void RangeStateWindow::signalsFactory()
|
||||
{
|
||||
int minSig = ui->spinMinSigSize->value();
|
||||
int maxSig = ui->spinMaxSigSize->value();
|
||||
int granularity = ui->spinGranularity->value();
|
||||
int sigType = ui->cbSignalMode->currentIndex() + 1;
|
||||
int signedType = ui->cbSignedMode->currentIndex() + 1;
|
||||
int maxBits = frameCache.at(0).len * 8;
|
||||
int sens = ui->slideSensitivity->value();
|
||||
|
||||
for (int sigSize = maxSig; sigSize >= minSig; sigSize--)
|
||||
{
|
||||
for (int startBit = 0; startBit < maxBits; startBit += granularity)
|
||||
{
|
||||
if (sigType & 1)
|
||||
{
|
||||
if (signedType & 1) processSignal(startBit, sigSize, sens, true, true);
|
||||
if (signedType & 2) processSignal(startBit, sigSize, sens, true, false);
|
||||
}
|
||||
if (sigType & 2)
|
||||
{
|
||||
if (signedType & 1) processSignal(startBit, sigSize, sens, false, true);
|
||||
if (signedType & 2) processSignal(startBit, sigSize, sens, false, false);
|
||||
}
|
||||
//have to try both types even with 8 bit and smaller signals
|
||||
//because they could cross byte boundaries. Could check whether they
|
||||
//do and not try both types if it is impossible.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Given the signal we generate the relevant data and figure out whether this signal seems to be a smooth range signal
|
||||
*/
|
||||
bool RangeStateWindow::processSignal(int startBit, int bitLength, int sensitivity, bool bigEndian, bool isSigned)
|
||||
{
|
||||
qDebug() << "";
|
||||
qDebug() << "S:" << startBit << " B:" << bitLength << " Sens:" << sensitivity << " Big E:" << bigEndian << " Signed: " << isSigned;
|
||||
|
||||
QVector<int> scaledVals;
|
||||
QVector<int> diff1;
|
||||
QVector<int> diff2;
|
||||
int64_t valu;
|
||||
int64_t highestValue = -1000000000000LL;
|
||||
int64_t lowestValue = 1000000000000LL;
|
||||
double multiplier;
|
||||
int numFrames = frameCache.count();
|
||||
|
||||
scaledVals.reserve(frameCache.count());
|
||||
diff1.reserve(frameCache.count() - 1);
|
||||
diff2.reserve(frameCache.count() - 2);
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < numFrames; i++)
|
||||
{
|
||||
valu = Utility::processIntegerSignal(frameCache.at(i).data, startBit, bitLength, !bigEndian, isSigned);
|
||||
if (valu < lowestValue) lowestValue = valu;
|
||||
if (valu > highestValue) highestValue = valu;
|
||||
}
|
||||
|
||||
qDebug() << "Min: " << lowestValue << " Max: " << highestValue;
|
||||
|
||||
if (lowestValue == highestValue) return false; //a signal that never changes is worthless and not a range signal
|
||||
|
||||
//multiplier is calculated such that the range of the signal is scaled down to the value of sensitivity.
|
||||
//so, if we have a sensitivity of 50 then the range is scaled so that it is 50
|
||||
//this has the effect of smoothing the data a bit by completely removing the fine detail. It's irrelevant to
|
||||
//an algorithm like this anyway and just gets in the way.
|
||||
//Also, we're going to offset by lowest value so that everything is based at a value of 0 as the low end.
|
||||
//All of this drastically modifies the actual signal but that's OK, we're looking for patterns here not
|
||||
//being faithful to the signal (what a dirty cheater)
|
||||
int range = highestValue - lowestValue;
|
||||
multiplier = (double)sensitivity / (double)range;
|
||||
|
||||
for (i = 0; i < numFrames; i++) scaledVals.append((int)((Utility::processIntegerSignal(frameCache.at(i).data, startBit, bitLength, !bigEndian, isSigned) - lowestValue) * multiplier));
|
||||
|
||||
for (i = 1; i < numFrames; i++)
|
||||
{
|
||||
valu = scaledVals[i-1] - scaledVals[i];
|
||||
diff1.append(valu);
|
||||
}
|
||||
|
||||
for (i = 1; i < diff1.count(); i++)
|
||||
{
|
||||
valu = diff1[i-1] - diff1[i];
|
||||
diff2.append(valu);
|
||||
}
|
||||
|
||||
//now the differences are all stored so let's go through and see if they seem to suggest a ramping sort of signal or not.
|
||||
//for a first test lets let through any signal where the acceleration values are all much smaller than the signal range
|
||||
bool isGood = true;
|
||||
int comparisonValue = Utility::Lerp(sensitivity / 5, sensitivity / 40, (sensitivity - 10) / 240.0);
|
||||
qDebug() << "range: " << sensitivity << " comparisonvalue: " << comparisonValue;
|
||||
int overValues = 0;
|
||||
for (i = 0; i < diff2.count(); i++)
|
||||
{
|
||||
if (abs(diff1[i]) > comparisonValue) overValues++;
|
||||
}
|
||||
int maxOvers = Utility::Lerp(4, numFrames / 50.0, 1.0 - ((sensitivity -10) / 240.0));
|
||||
qDebug() << "Max Overs: " << maxOvers;
|
||||
if (overValues > maxOvers) isGood = false;
|
||||
qDebug() << "Is this signal good: " << isGood << " Num overs: " << overValues;
|
||||
if (isGood)
|
||||
{
|
||||
//createGraph(scaledVals);
|
||||
QString temp;
|
||||
temp = "ID: " + QString::number(frameCache.at(0).ID, 16) + " startBit: " + QString::number(startBit) + " len: " + QString::number(bitLength);
|
||||
int64_t foundSig;
|
||||
foundSig = frameCache.at(0).ID;
|
||||
foundSig += (int64_t)startBit << 32;
|
||||
foundSig += (int64_t)bitLength << 40;
|
||||
|
||||
if (isSigned)
|
||||
{
|
||||
temp += " Signed";
|
||||
foundSig += (int64_t)1 << 48;
|
||||
}
|
||||
else
|
||||
{
|
||||
temp += " Unsigned";
|
||||
}
|
||||
|
||||
if (bigEndian)
|
||||
{
|
||||
temp += " BigEndian";
|
||||
foundSig += (int64_t)1 << 49;
|
||||
}
|
||||
else
|
||||
{
|
||||
temp += " LittleEndian";
|
||||
}
|
||||
|
||||
ui->listCandidates->addItem(temp);
|
||||
foundSignals.append(foundSig);
|
||||
}
|
||||
return isGood;
|
||||
}
|
||||
|
||||
//graphs the vector such that the X axis is just the index into the vector and Y is perfectly graphed within the window
|
||||
void RangeStateWindow::createGraph(QVector<int> values)
|
||||
{
|
||||
|
||||
int tempVal;
|
||||
float minval=1000000, maxval = -100000;
|
||||
|
||||
int numEntries = values.count();
|
||||
|
||||
ui->graphSignal->clearGraphs();
|
||||
|
||||
QVector<double> x(numEntries), y(numEntries);
|
||||
|
||||
for (int j = 0; j < numEntries; j++)
|
||||
{
|
||||
tempVal = values[j];
|
||||
|
||||
x[j] = j;
|
||||
|
||||
y[j] = tempVal;
|
||||
if (y[j] < minval) minval = y[j];
|
||||
if (y[j] > maxval) maxval = y[j];
|
||||
}
|
||||
|
||||
qDebug() << "YMin: " << minval << " YMax: " << maxval;
|
||||
|
||||
double ymin, ymax;
|
||||
|
||||
ymin = minval;
|
||||
|
||||
if (ymin < 0) ymin *= 1.2;
|
||||
else ymin *= 0.8;
|
||||
|
||||
ymax = maxval;
|
||||
if (ymax < 0) ymax * 0.8;
|
||||
else ymax *= 1.2;
|
||||
|
||||
if (fabs(ymin) < 0.01) ymin -= (ymax / 60.0);
|
||||
if (fabs(ymax) < 0.01) ymax -= (ymin / 60.0);
|
||||
|
||||
qDebug() << "YFm: " << ymin << " YFM: " << ymax;
|
||||
|
||||
ui->graphSignal->addGraph();
|
||||
ui->graphSignal->graph()->setName("Graph");
|
||||
ui->graphSignal->graph()->setData(x,y);
|
||||
ui->graphSignal->graph()->setLineStyle(QCPGraph::lsLine); //connect points with lines
|
||||
QPen graphPen;
|
||||
graphPen.setColor(Qt::black);
|
||||
graphPen.setWidth(1);
|
||||
ui->graphSignal->graph()->setPen(graphPen);
|
||||
ui->graphSignal->xAxis->setRange(0, numEntries);
|
||||
ui->graphSignal->yAxis->setRange(ymin, ymax);
|
||||
ui->graphSignal->replot();
|
||||
}
|
||||
|
||||
void RangeStateWindow::clickedSignalList(int idx)
|
||||
{
|
||||
if (idx == -1) return; //just in case...
|
||||
|
||||
int id, startBit, bitLength;
|
||||
bool isSigned = false, isBigEndian = false;
|
||||
int64_t valu;
|
||||
|
||||
valu = foundSignals.at(idx);
|
||||
id = valu & 0xFFFFFFFFULL;
|
||||
startBit = (valu >> 32) & 0xFF;
|
||||
bitLength = (valu >> 40) & 0xFF;
|
||||
if (valu & (1LL << 48)) isSigned = true;
|
||||
if (valu & (1LL << 49)) isBigEndian = true;
|
||||
|
||||
qDebug() << "I:" << id << " sb:" << startBit << " len:" << bitLength << " signed:" << isSigned << " big:" << isBigEndian;
|
||||
|
||||
frameCache.clear();
|
||||
frameCache.reserve(modelFrames->count()); //block allocate more than enough space
|
||||
|
||||
for (int j = 0; j < modelFrames->count(); j++)
|
||||
{
|
||||
if (modelFrames->at(j).ID == id) frameCache.append(modelFrames->at(j));
|
||||
}
|
||||
|
||||
int numFrames = frameCache.count();
|
||||
QVector<int> values;
|
||||
values.reserve(numFrames);
|
||||
for (int i = 0; i < numFrames; i++) values.append((int)((Utility::processIntegerSignal(frameCache.at(i).data, startBit, bitLength, !isBigEndian, isSigned))));
|
||||
createGraph(values);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#ifndef RANGESTATEWINDOW_H
|
||||
#define RANGESTATEWINDOW_H
|
||||
|
||||
#include <QDialog>
|
||||
#include "can_structs.h"
|
||||
|
||||
namespace Ui {
|
||||
class RangeStateWindow;
|
||||
}
|
||||
|
||||
class RangeStateWindow : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit RangeStateWindow(const QVector<CANFrame> *frames, QWidget *parent = 0);
|
||||
~RangeStateWindow();
|
||||
void showEvent(QShowEvent*);
|
||||
|
||||
private slots:
|
||||
void updatedFrames(int);
|
||||
void recalcButton();
|
||||
void clickedSignalList(int idx);
|
||||
|
||||
private:
|
||||
Ui::RangeStateWindow *ui;
|
||||
const QVector<CANFrame> *modelFrames;
|
||||
QVector<CANFrame> frameCache;
|
||||
QList<int64_t> foundSignals;
|
||||
QHash<int, bool> idFilters;
|
||||
|
||||
void refreshFilterList();
|
||||
void closeEvent(QCloseEvent *event);
|
||||
void readSettings();
|
||||
void writeSettings();
|
||||
void signalsFactory();
|
||||
bool processSignal(int startBit, int bitLength, int sensitivity, bool bigEndian, bool isSigned);
|
||||
void createGraph(QVector<int> values);
|
||||
};
|
||||
|
||||
#endif // RANGESTATEWINDOW_H
|
||||
@@ -0,0 +1,228 @@
|
||||
#include "udsscanwindow.h"
|
||||
#include "ui_udsscanwindow.h"
|
||||
#include "mainwindow.h"
|
||||
#include "connections/canconmanager.h"
|
||||
|
||||
UDSScanWindow::UDSScanWindow(const QVector<CANFrame> *frames, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::UDSScanWindow)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
modelFrames = frames;
|
||||
|
||||
currentlyRunning = false;
|
||||
|
||||
waitTimer = new QTimer;
|
||||
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);
|
||||
|
||||
int numBuses = CANConManager::getInstance()->getNumBuses();
|
||||
for (int n = 0; n < numBuses; n++) ui->cbBuses->addItem(QString::number(n));
|
||||
ui->cbBuses->addItem(tr("All"));
|
||||
}
|
||||
|
||||
UDSScanWindow::~UDSScanWindow()
|
||||
{
|
||||
delete ui;
|
||||
waitTimer->stop();
|
||||
delete waitTimer;
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
int buses = ui->cbBuses->currentIndex();
|
||||
|
||||
//start out by sending tester present to every address to see if anyone replies
|
||||
for (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 < ui->cbBuses->count()- 1)
|
||||
{
|
||||
frame.bus = buses;
|
||||
sendingFrames.append(frame);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int c = 0; c < ui->cbBuses->count() - 1; c++)
|
||||
{
|
||||
frame.bus = c;
|
||||
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;
|
||||
frame.extended = false;
|
||||
frame.data[0] = 2;
|
||||
frame.data[1] = 0x10;
|
||||
frame.data[2] = typ;
|
||||
frame.data[3] = 0;frame.data[4] = 0;frame.data[5] = 0;
|
||||
frame.data[6] = 0;frame.data[7] = 0;
|
||||
|
||||
if (buses < ui->cbBuses->count()- 1)
|
||||
{
|
||||
frame.bus = buses;
|
||||
sendingFrames.append(frame);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int c = 0; c < ui->cbBuses->count() - 1; c++)
|
||||
{
|
||||
frame.bus = c;
|
||||
sendingFrames.append(frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (numFrames == -1) //all frames deleted. We don't care
|
||||
{
|
||||
}
|
||||
else if (numFrames == -2) //all new set of frames. Don't care.
|
||||
{
|
||||
}
|
||||
else //just got some new frames. See if they are relevant.
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
//Updates here are nearly once per millisecond if there is heavy traffic. That's more like it!
|
||||
//TODO: I really doubt this works anymore with the new connection system. This breaks the UDS scanner for now! ;(
|
||||
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())
|
||||
{
|
||||
int temp = thisFrame.data[0] >> 4;
|
||||
if (temp == 0) //single frame reply (maybe)
|
||||
{
|
||||
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 = "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();
|
||||
}
|
||||
|
||||
void UDSScanWindow::sendNextMsg()
|
||||
{
|
||||
currIdx++;
|
||||
if (currIdx < sendingFrames.count())
|
||||
{
|
||||
CANConManager::getInstance()->sendFrame(sendingFrames[currIdx]);
|
||||
waitTimer->start();
|
||||
}
|
||||
else
|
||||
{
|
||||
waitTimer->stop();
|
||||
ui->btnScan->setText("Start Scan");
|
||||
currentlyRunning = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#ifndef UDSSCANWINDOW_H
|
||||
#define UDSSCANWINDOW_H
|
||||
|
||||
#include "can_structs.h"
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
namespace Ui {
|
||||
class UDSScanWindow;
|
||||
}
|
||||
|
||||
class UDSScanWindow : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit UDSScanWindow(const QVector<CANFrame> *frames, QWidget *parent = 0);
|
||||
~UDSScanWindow();
|
||||
|
||||
private slots:
|
||||
void updatedFrames(int numFrames);
|
||||
void rapidFrames(int numFrames);
|
||||
void scanUDS();
|
||||
void timeOut();
|
||||
|
||||
private:
|
||||
Ui::UDSScanWindow *ui;
|
||||
const QVector<CANFrame> *modelFrames;
|
||||
QTimer *waitTimer;
|
||||
QList<CANFrame> sendingFrames;
|
||||
int currIdx = 0;
|
||||
bool currentlyRunning;
|
||||
|
||||
void sendNextMsg();
|
||||
};
|
||||
|
||||
#endif // UDSSCANWINDOW_H
|
||||
Reference in New Issue
Block a user