Frame Info Window. Made Frame Info Window able to have the two sides resized relative to each other
825 lines
31 KiB
C++
825 lines
31 KiB
C++
#include "frameinfowindow.h"
|
|
#include "ui_frameinfowindow.h"
|
|
#include "mainwindow.h"
|
|
#include "helpwindow.h"
|
|
#include <QtDebug>
|
|
#include <vector>
|
|
#include "filterutility.h"
|
|
#include "qcpaxistickerhex.h"
|
|
|
|
const QColor FrameInfoWindow::byteGraphColors[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
|
|
QPen FrameInfoWindow::bytePens[8];
|
|
|
|
const int numIntervalHistBars = 20;
|
|
|
|
FrameInfoWindow::FrameInfoWindow(const QVector<CANFrame> *frames, QWidget *parent) :
|
|
QDialog(parent),
|
|
ui(new Ui::FrameInfoWindow)
|
|
{
|
|
ui->setupUi(this);
|
|
setWindowFlags(Qt::Window);
|
|
|
|
readSettings();
|
|
|
|
modelFrames = frames;
|
|
|
|
// Using lambda expression to strip away the possible filter label before passing the ID to updateDetailsWindow
|
|
connect(ui->listFrameID, &QListWidget::currentTextChanged,
|
|
[this](QString itemText)
|
|
{
|
|
FrameInfoWindow::updateDetailsWindow(FilterUtility::getId(itemText));
|
|
} );
|
|
|
|
connect(MainWindow::getReference(), &MainWindow::framesUpdated, this, &FrameInfoWindow::updatedFrames);
|
|
connect(ui->btnSave, &QAbstractButton::clicked, this, &FrameInfoWindow::saveDetails);
|
|
|
|
ui->splitter->setStretchFactor(0, 1); //idx, stretch factor
|
|
ui->splitter->setStretchFactor(1, 4); //goal is to make right hand side larger by default
|
|
|
|
for (int i = 0; i < 8; i++)
|
|
{
|
|
graphByte[i] = new QCustomPlot();
|
|
setupByteGraph(graphByte[i], i);
|
|
ui->gridLower->addWidget(graphByte[i], i / 4, i & 3);
|
|
}
|
|
|
|
ui->gridUpper->addWidget(new QLabel("Heatmap"), 0, 0);
|
|
heatmap = new CANDataGrid();
|
|
heatmap->setMode(GridMode::HEAT_VIEW);
|
|
ui->gridUpper->addWidget(heatmap, 1, 0);
|
|
|
|
ui->gridUpper->addWidget(new QLabel("Bit Histogram"), 0, 1);
|
|
graphHistogram = new QCustomPlot();
|
|
ui->gridUpper->addWidget(graphHistogram, 1, 1);
|
|
|
|
ui->gridUpper->setRowMinimumHeight(0, 20);
|
|
ui->gridUpper->setRowStretch(0, 1);
|
|
ui->gridUpper->setRowStretch(1, 10);
|
|
|
|
graphHistogram->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectAxes |
|
|
QCP::iSelectLegend | QCP::iSelectPlottables);
|
|
|
|
graphHistogram->xAxis->setRange(0, 63);
|
|
graphHistogram->yAxis->setRange(0, 100);
|
|
QSharedPointer<QCPAxisTickerLog> graphHistoLogTicker(new QCPAxisTickerLog);
|
|
graphHistogram->yAxis->setTicker(graphHistoLogTicker);
|
|
graphHistogram->yAxis2->setTicker(graphHistoLogTicker);
|
|
graphHistogram->yAxis->setNumberFormat("eb"); // e = exponential, b = beautiful decimal powers
|
|
graphHistogram->yAxis->setNumberPrecision(0); //log ticker always picks powers of 10 so no need or use for precision
|
|
|
|
graphHistogram->axisRect()->setupFullAxesBox();
|
|
graphHistogram->setBufferDevicePixelRatio(1);
|
|
|
|
graphHistogram->xAxis->setLabel("Bits");
|
|
graphHistogram->yAxis->setLabel("Instances");
|
|
|
|
graphHistogram->legend->setVisible(false);
|
|
|
|
ui->timeHistogram->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectAxes |
|
|
QCP::iSelectLegend | QCP::iSelectPlottables);
|
|
|
|
ui->timeHistogram->xAxis->setRange(0, numIntervalHistBars);
|
|
ui->timeHistogram->yAxis->setRange(0, 100);
|
|
QSharedPointer<QCPAxisTickerLog> logTicker(new QCPAxisTickerLog);
|
|
ui->timeHistogram->yAxis->setTicker(logTicker);
|
|
ui->timeHistogram->yAxis2->setTicker(logTicker);
|
|
ui->timeHistogram->yAxis->setScaleType(QCPAxis::stLogarithmic);
|
|
ui->timeHistogram->yAxis->setNumberFormat("eb"); // e = exponential, b = beautiful decimal powers
|
|
ui->timeHistogram->yAxis->setNumberPrecision(0); //log ticker always picks powers of 10 so no need or use for precision
|
|
ui->timeHistogram->axisRect()->setupFullAxesBox();
|
|
|
|
ui->timeHistogram->xAxis->setLabel("Interval (ms)");
|
|
ui->timeHistogram->yAxis->setLabel("Occurrences");
|
|
|
|
ui->timeHistogram->legend->setVisible(false);
|
|
ui->timeHistogram->setBufferDevicePixelRatio(1);
|
|
|
|
if (useOpenGL)
|
|
{
|
|
graphHistogram->setAntialiasedElements(QCP::aeAll);
|
|
graphHistogram->setOpenGl(true);
|
|
ui->timeHistogram->setAntialiasedElements(QCP::aeAll);
|
|
ui->timeHistogram->setOpenGl(true);
|
|
}
|
|
else
|
|
{
|
|
graphHistogram->setOpenGl(false);
|
|
graphHistogram->setAntialiasedElements(QCP::aeNone);
|
|
ui->timeHistogram->setOpenGl(false);
|
|
ui->timeHistogram->setAntialiasedElements(QCP::aeNone);
|
|
}
|
|
|
|
// Prevent annoying accidental horizontal scrolling when filter list is populated with long interpreted message names
|
|
ui->listFrameID->horizontalScrollBar()->setEnabled(false);
|
|
|
|
installEventFilter(this);
|
|
|
|
for (int i = 0; i < 8; i++)
|
|
{
|
|
bytePens[i].setColor(byteGraphColors[i]);
|
|
bytePens[i].setWidth(1);
|
|
}
|
|
|
|
dbcHandler = DBCHandler::getReference();
|
|
}
|
|
|
|
void FrameInfoWindow::setupByteGraph(QCustomPlot *plot, int num)
|
|
{
|
|
plot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectAxes |
|
|
QCP::iSelectLegend | QCP::iSelectPlottables);
|
|
|
|
plot->xAxis->setRange(0, 63);
|
|
plot->yAxis->setRange(0, 265);
|
|
if (useHexTicker)
|
|
{
|
|
QSharedPointer<QCPAxisTickerHex> hexTicker(new QCPAxisTickerHex);
|
|
plot->yAxis->setTicker(hexTicker);
|
|
}
|
|
plot->axisRect()->setupFullAxesBox();
|
|
|
|
plot->xAxis->setLabel("Time [" + QString::number(num) + "]");
|
|
//if (useHexTicker) plot->yAxis->setLabel("Value (HEX)");
|
|
//else plot->yAxis->setLabel("Value (Dec)");
|
|
plot->yAxis->setLabel("");
|
|
|
|
plot->legend->setVisible(false);
|
|
plot->setBufferDevicePixelRatio(1);
|
|
|
|
if (useOpenGL)
|
|
{
|
|
plot->setAntialiasedElements(QCP::aeAll);
|
|
plot->setOpenGl(true);
|
|
}
|
|
else
|
|
{
|
|
plot->setOpenGl(false);
|
|
plot->setAntialiasedElements(QCP::aeNone);
|
|
}
|
|
}
|
|
|
|
void FrameInfoWindow::showEvent(QShowEvent* event)
|
|
{
|
|
QDialog::showEvent(event);
|
|
readSettings();
|
|
refreshIDList();
|
|
if (ui->listFrameID->count() > 0)
|
|
{
|
|
updateDetailsWindow(FilterUtility::getId(ui->listFrameID->item(0)));
|
|
ui->listFrameID->setCurrentRow(0);
|
|
}
|
|
}
|
|
|
|
bool FrameInfoWindow::eventFilter(QObject *obj, QEvent *event)
|
|
{
|
|
if (event->type() == QEvent::KeyRelease) {
|
|
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
|
|
switch (keyEvent->key())
|
|
{
|
|
case Qt::Key_F1:
|
|
HelpWindow::getRef()->showHelp("framedetails.md");
|
|
break;
|
|
}
|
|
return true;
|
|
} else {
|
|
// standard event processing
|
|
return QObject::eventFilter(obj, event);
|
|
}
|
|
}
|
|
|
|
FrameInfoWindow::~FrameInfoWindow()
|
|
{
|
|
removeEventFilter(this);
|
|
delete ui;
|
|
}
|
|
|
|
void FrameInfoWindow::closeEvent(QCloseEvent *event)
|
|
{
|
|
Q_UNUSED(event)
|
|
writeSettings();
|
|
emit rejected();
|
|
}
|
|
|
|
void FrameInfoWindow::readSettings()
|
|
{
|
|
QSettings settings;
|
|
|
|
if (settings.value("Main/FilterLabeling", false).toBool())
|
|
{
|
|
ui->listFrameID->setMinimumWidth(250);
|
|
}
|
|
else
|
|
{
|
|
ui->listFrameID->setMinimumWidth(120);
|
|
}
|
|
|
|
if (settings.value("Main/SaveRestorePositions", false).toBool())
|
|
{
|
|
resize(settings.value("FrameInfo/WindowSize", QSize(794, 694)).toSize());
|
|
move(Utility::constrainedWindowPos(settings.value("FrameInfo/WindowPos", QPoint(50, 50)).toPoint()));
|
|
}
|
|
useOpenGL = settings.value("Main/UseOpenGL", false).toBool();
|
|
useHexTicker = settings.value("InfoCompare/GraphHex", false).toBool();
|
|
}
|
|
|
|
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
|
|
{
|
|
//qDebug() << "Delete all frames in Info Window";
|
|
ui->listFrameID->clear();
|
|
ui->treeDetails->clear();
|
|
foundID.clear();
|
|
refreshIDList();
|
|
}
|
|
else if (numFrames == -2) //all new set of frames. Reset
|
|
{
|
|
//qDebug() << "All new set of frames in Info Window";
|
|
ui->listFrameID->clear();
|
|
ui->treeDetails->clear();
|
|
foundID.clear();
|
|
refreshIDList();
|
|
if (ui->listFrameID->count() > 0)
|
|
{
|
|
updateDetailsWindow(FilterUtility::getId(ui->listFrameID->item(0)));
|
|
ui->listFrameID->setCurrentRow(0);
|
|
}
|
|
}
|
|
else //just got some new frames. See if they are relevant.
|
|
{
|
|
//qDebug() << "Got frames in Info Window";
|
|
if (numFrames > modelFrames->count()) return;
|
|
|
|
unsigned int currID = 0;
|
|
if (ui->listFrameID->currentItem())
|
|
currID = static_cast<unsigned int>(FilterUtility::getIdAsInt(ui->listFrameID->currentItem()));
|
|
bool thisID = false;
|
|
for (int x = modelFrames->count() - numFrames; x < modelFrames->count(); x++)
|
|
{
|
|
CANFrame thisFrame = modelFrames->at(x);
|
|
int32_t id = static_cast<int32_t>(thisFrame.frameId());
|
|
if (!foundID.contains(id))
|
|
{
|
|
foundID.append(id);
|
|
FilterUtility::createFilterItem(id, ui->listFrameID);
|
|
}
|
|
|
|
if (currID == modelFrames->at(x).frameId())
|
|
{
|
|
thisID = true;
|
|
break;
|
|
}
|
|
}
|
|
if (thisID)
|
|
{
|
|
//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());
|
|
}
|
|
//default is to sort in ascending order
|
|
ui->listFrameID->sortItems();
|
|
ui->lblUniqueID->setText(")" + QString::number(ui->listFrameID->count()) + tr(" unique ids)"));
|
|
}
|
|
}
|
|
|
|
void FrameInfoWindow::updateDetailsWindow(QString newID)
|
|
{
|
|
int targettedID;
|
|
int minLen, maxLen, thisLen;
|
|
int64_t avgInterval;
|
|
int64_t minInterval;
|
|
int64_t maxInterval;
|
|
int64_t thisInterval;
|
|
int minData[8];
|
|
int maxData[8];
|
|
int dataHistogram[256][8];
|
|
int bitfieldHistogram[64];
|
|
QVector<double> histGraphX, histGraphY;
|
|
QVector<double> byteGraphX, byteGraphY[8];
|
|
QVector<double> timeGraphX, timeGraphY;
|
|
QHash<QString, QHash<QString, int>> signalInstances;
|
|
double maxY = -1000.0;
|
|
uint8_t changedBits[8];
|
|
uint8_t referenceBits[8];
|
|
uint8_t heatVals[512];
|
|
|
|
//these two used by bitflip heatmap functionality
|
|
uint8_t refByte[8];
|
|
double bitFlipHeat[64];
|
|
|
|
QTreeWidgetItem *baseNode, *dataBase, *histBase, *tempItem;
|
|
|
|
if (modelFrames->count() == 0) return;
|
|
|
|
targettedID = static_cast<int>(Utility::ParseStringToNum(newID));
|
|
|
|
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.frameId() == static_cast<uint32_t>(targettedID)) frameCache.append(thisFrame);
|
|
}
|
|
|
|
if (frameCache.count() == 0) return; //nothing to do if there are no frames!
|
|
|
|
const unsigned char *data = reinterpret_cast<const unsigned char *>(frameCache.at(0).payload().constData());
|
|
int dataLen = frameCache.at(0).payload().length();
|
|
|
|
ui->treeDetails->clear();
|
|
|
|
if (frameCache.count() == 0) return;
|
|
|
|
baseNode = new QTreeWidgetItem();
|
|
baseNode->setText(0, QString("ID: ") + newID );
|
|
|
|
if (frameCache[0].hasExtendedFrameFormat()) //if these frames seem to be extended then try for J1939 decoding
|
|
{
|
|
// ------- 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;
|
|
|
|
tempItem = new QTreeWidgetItem();
|
|
tempItem->setText(0, tr("J1939 decoding"));
|
|
baseNode->addChild(tempItem);
|
|
|
|
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(static_cast<uint64_t>(jid.dest)));
|
|
baseNode->addChild(tempItem);
|
|
}
|
|
tempItem = new QTreeWidgetItem();
|
|
tempItem->setText(0, tr(" SRC: ") + Utility::formatNumber(static_cast<uint64_t>(jid.src)));
|
|
baseNode->addChild(tempItem);
|
|
|
|
tempItem = new QTreeWidgetItem();
|
|
tempItem->setText(0, tr(" PGN: ") + Utility::formatNumber(static_cast<uint64_t>(jid.pgn)) + "(" + QString::number(jid.pgn) + ")");
|
|
baseNode->addChild(tempItem);
|
|
|
|
tempItem = new QTreeWidgetItem();
|
|
tempItem->setText(0, tr(" PF: ") + Utility::formatNumber(static_cast<uint64_t>(jid.pf)));
|
|
baseNode->addChild(tempItem);
|
|
|
|
tempItem = new QTreeWidgetItem();
|
|
tempItem->setText(0, tr(" PS: ") + Utility::formatNumber(static_cast<uint64_t>(jid.ps)));
|
|
baseNode->addChild(tempItem);
|
|
|
|
// ------- GMLAN 29bit decoding ----------
|
|
tempItem = new QTreeWidgetItem();
|
|
tempItem->setText(0, tr("GMLAN 29bit decoding"));
|
|
baseNode->addChild(tempItem);
|
|
|
|
tempItem = new QTreeWidgetItem();
|
|
tempItem->setText(0, tr(" Priority bits: ") + Utility::formatNumber( (uint64_t)FilterUtility::getGMLanPriorityBits(targettedID)));
|
|
baseNode->addChild(tempItem);
|
|
tempItem = new QTreeWidgetItem();
|
|
tempItem->setText(0, tr(" Arbitration Id: ") + Utility::formatNumber( (uint64_t)FilterUtility::getGMLanArbitrationId(targettedID)));
|
|
baseNode->addChild(tempItem);
|
|
tempItem = new QTreeWidgetItem();
|
|
tempItem->setText(0, tr(" Sender Id: ") + Utility::formatNumber( (uint64_t)FilterUtility::getGMLanSenderId(targettedID)));
|
|
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;
|
|
bitFlipHeat[j] = 0;
|
|
}
|
|
signalInstances.clear();
|
|
|
|
data = reinterpret_cast<const unsigned char *>(frameCache.at(0).payload().constData());
|
|
dataLen = frameCache.at(0).payload().length();
|
|
|
|
for (int c = 0; c < dataLen; c++)
|
|
{
|
|
changedBits[c] = 0;
|
|
referenceBits[c] = data[c];
|
|
refByte[c] = data[c];
|
|
//qDebug() << referenceBits[c];
|
|
}
|
|
|
|
std::vector<int64_t> sortedIntervals;
|
|
int64_t intervalSum = 0;
|
|
|
|
DBC_MESSAGE *msg = dbcHandler->findMessageForFilter(targettedID, nullptr);
|
|
|
|
//then find all data points
|
|
for (int j = 0; j < frameCache.count(); j++)
|
|
{
|
|
data = reinterpret_cast<const unsigned char *>(frameCache.at(j).payload().constData());
|
|
dataLen = frameCache.at(j).payload().length();
|
|
|
|
byteGraphX.append(j);
|
|
for (int bytcnt = 0; bytcnt < dataLen; bytcnt++)
|
|
{
|
|
byteGraphY[bytcnt].append(data[bytcnt]);
|
|
}
|
|
|
|
if (j != 0)
|
|
{
|
|
//TODO - we try the interval whichever way doesn't go negative. But, we should probably sort the frame list before
|
|
//starting so that the intervals are all correct.
|
|
if (frameCache[j].timeStamp().microSeconds() > frameCache[j-1].timeStamp().microSeconds())
|
|
thisInterval = (frameCache[j].timeStamp().microSeconds() - frameCache[j-1].timeStamp().microSeconds());
|
|
else
|
|
thisInterval = (frameCache[j-1].timeStamp().microSeconds() - frameCache[j].timeStamp().microSeconds());
|
|
|
|
sortedIntervals.push_back(thisInterval);
|
|
intervalSum += thisInterval;
|
|
if (thisInterval > maxInterval) maxInterval = thisInterval;
|
|
if (thisInterval < minInterval) minInterval = thisInterval;
|
|
avgInterval += thisInterval;
|
|
}
|
|
thisLen = dataLen;
|
|
if (thisLen > maxLen) maxLen = thisLen;
|
|
if (thisLen < minLen) minLen = thisLen;
|
|
for (int c = 0; c < thisLen; c++)
|
|
{
|
|
unsigned char dat = 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 (refByte[c] != dat) //if this byte doesn't match the value it last had
|
|
{
|
|
uint8_t newBits = refByte[c] ^ dat; //get changed bits since last ref
|
|
for (int l = 0; l < 8; l++)
|
|
{
|
|
if (newBits & (1 << l)) bitFlipHeat[(c * 8) + l]++;
|
|
}
|
|
refByte[c] = dat; //set the reference to this current byte now that processing is done
|
|
}
|
|
}
|
|
|
|
//Search every signal in the selected message and give output of the range the signal took and
|
|
//how many messages contained each discrete value.
|
|
if (msg)
|
|
{
|
|
int numSignals = msg->sigHandler->getCount();
|
|
for (int i = 0; i < numSignals; i++)
|
|
{
|
|
DBC_SIGNAL *sig = msg->sigHandler->findSignalByIdx(i);
|
|
if (sig)
|
|
{
|
|
if (sig->isSignalInMessage(frameCache.at(j)))
|
|
{
|
|
QString sigVal;
|
|
if (sig->processAsText(frameCache.at(j), sigVal, false))
|
|
{
|
|
signalInstances[sig->name][sigVal] = signalInstances[sig->name][sigVal] + 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Divide all the bit flip heat values by the number of frames to get a ratio
|
|
for (int j = 0; j < 64; j++) bitFlipHeat[j] /= (double)frameCache.count();
|
|
|
|
std::sort(sortedIntervals.begin(), sortedIntervals.end());
|
|
int64_t intervalStdDiv = 0, intervalPctl5 = 0, intervalPctl95 = 0, intervalMean = 0, intervalVariance = 0;
|
|
|
|
int maxTimeCounter = -1;
|
|
if (sortedIntervals.size() > 0)
|
|
{
|
|
intervalMean = intervalSum / sortedIntervals.size();
|
|
|
|
for(int l = 0; l < static_cast<int>(sortedIntervals.size()); l++) {
|
|
intervalVariance += ((sortedIntervals[l] - intervalMean) * (sortedIntervals[l] - intervalMean));
|
|
}
|
|
|
|
intervalVariance /= sortedIntervals.size();
|
|
intervalStdDiv = static_cast<int>(sqrt(intervalVariance));
|
|
|
|
intervalPctl5 = sortedIntervals[static_cast<unsigned int>(floor(0.05 * sortedIntervals.size()))];
|
|
intervalPctl95 = sortedIntervals[static_cast<unsigned int>(floor(0.95 * sortedIntervals.size()))];
|
|
|
|
uint64_t step = static_cast<unsigned int>(ceil((maxInterval - minInterval) / numIntervalHistBars));
|
|
qDebug() << "Step: " << step << " minInt: " << minInterval << " maxInt: " << maxInterval;
|
|
unsigned int index = 0;
|
|
int counter = 0;
|
|
for(int l = 0; l <= numIntervalHistBars; l++) {
|
|
int64_t currentMax = maxInterval - ((numIntervalHistBars - l) * step); // avoid missing the biggest value due to rounding errors
|
|
qDebug() << "CurrentMax: " << currentMax;
|
|
while(index < sortedIntervals.size()) {
|
|
if(sortedIntervals[index] <= currentMax) {
|
|
counter++;
|
|
index++;
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
timeGraphX.append(currentMax / 1000.0);
|
|
timeGraphY.append(counter);
|
|
if(counter > maxTimeCounter) maxTimeCounter = counter;
|
|
counter = 0;
|
|
}
|
|
}
|
|
|
|
if (frameCache.count() > 1)
|
|
avgInterval = avgInterval / (frameCache.count() - 1);
|
|
else avgInterval = 0;
|
|
|
|
//now that data processing is done, create all of our output
|
|
|
|
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.0) + "ms");
|
|
baseNode->addChild(tempItem);
|
|
tempItem = new QTreeWidgetItem();
|
|
tempItem->setText(0, tr("Minimum inter-frame interval: ") + QString::number(minInterval / 1000.0) + "ms");
|
|
baseNode->addChild(tempItem);
|
|
tempItem = new QTreeWidgetItem();
|
|
tempItem->setText(0, tr("Maximum inter-frame interval: ") + QString::number(maxInterval / 1000.0) + "ms");
|
|
baseNode->addChild(tempItem);
|
|
tempItem = new QTreeWidgetItem();
|
|
tempItem->setText(0, tr("Inter-frame interval variation: ") + QString::number((maxInterval - minInterval) / 1000.0) + "ms");
|
|
baseNode->addChild(tempItem);
|
|
tempItem = new QTreeWidgetItem();
|
|
tempItem->setText(0, tr("Interval standard deviation: ") + QString::number(intervalStdDiv / 1000.0) + "ms");
|
|
baseNode->addChild(tempItem);
|
|
tempItem = new QTreeWidgetItem();
|
|
tempItem->setText(0, tr("Minimum range to fit 90% of inter-frame intervals: ") + QString::number((intervalPctl95 - intervalPctl5) / 1000.0) + "ms");
|
|
baseNode->addChild(tempItem);
|
|
|
|
//display accumulated data for all the bytes in the message
|
|
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((unsigned int)minData[c]) + tr(" to ") + Utility::formatNumber((unsigned int)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(static_cast<uint8_t>(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);
|
|
histGraphX.append(c);
|
|
histGraphY.append(bitfieldHistogram[c]);
|
|
if (bitfieldHistogram[c] > maxY) maxY = bitfieldHistogram[c];
|
|
}
|
|
baseNode->addChild(dataBase);
|
|
|
|
//heat map output
|
|
dataBase = new QTreeWidgetItem();
|
|
dataBase->setText(0, tr("Bitchange Heatmap"));
|
|
memset(heatVals, 0, 512); //always clear the array before populating it.
|
|
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(bitFlipHeat[c] * 100.0, 'f', 2));
|
|
|
|
dataBase->addChild(tempItem);
|
|
histGraphX.append(c);
|
|
histGraphY.append(bitfieldHistogram[c]);
|
|
if (bitfieldHistogram[c] > maxY) maxY = bitfieldHistogram[c];
|
|
uint8_t heat = bitFlipHeat[c] * 255;
|
|
if ((heat < 1) && (bitFlipHeat[c] > 0.0001)) heat = 1; //make sure any little bit of heat causes at least some output
|
|
qDebug() << "Heat for bit " << c << " is " << heat;
|
|
heatVals[c] = heat;
|
|
}
|
|
baseNode->addChild(dataBase);
|
|
heatmap->setHeat(heatVals);
|
|
|
|
QHash<QString, QHash<QString, int>>::const_iterator it = signalInstances.constBegin();
|
|
while (it != signalInstances.constEnd()) {
|
|
dataBase = new QTreeWidgetItem();
|
|
dataBase->setText(0, it.key());
|
|
QHash<QString,int>::const_iterator itVal = signalInstances[it.key()].constBegin();
|
|
while (itVal != signalInstances[it.key()].constEnd())
|
|
{
|
|
tempItem = new QTreeWidgetItem();
|
|
tempItem->setText(0, itVal.key() + ": " + QString::number(itVal.value()));
|
|
dataBase->addChild(tempItem);
|
|
++itVal;
|
|
}
|
|
baseNode->addChild(dataBase);
|
|
++it;
|
|
}
|
|
|
|
ui->treeDetails->insertTopLevelItem(0, baseNode);
|
|
|
|
graphHistogram->clearGraphs();
|
|
graphHistogram->addGraph();
|
|
graphHistogram->graph()->setData(histGraphX, histGraphY);
|
|
graphHistogram->graph()->setLineStyle(QCPGraph::lsStepLeft); //connect points with lines
|
|
QBrush graphBrush;
|
|
graphBrush.setColor(Qt::red);
|
|
graphBrush.setStyle(Qt::SolidPattern);
|
|
graphHistogram->graph()->setPen(Qt::NoPen);
|
|
graphHistogram->graph()->setBrush(graphBrush);
|
|
graphHistogram->yAxis->setRange(0.8, maxY * 1.2);
|
|
graphHistogram->yAxis->setScaleType(QCPAxis::stLogarithmic);
|
|
graphHistogram->axisRect()->setupFullAxesBox();
|
|
graphHistogram->replot();
|
|
|
|
for (int graphs = 0; graphs < 8; graphs++)
|
|
{
|
|
graphByte[graphs]->clearGraphs();
|
|
graphRef[graphs] = graphByte[graphs]->addGraph();
|
|
graphByte[graphs]->graph()->setData(byteGraphX, byteGraphY[graphs]);
|
|
graphByte[graphs]->graph()->setPen(bytePens[graphs]);
|
|
graphByte[graphs]->xAxis->setRange(0, byteGraphX.count());
|
|
graphByte[graphs]->replot();
|
|
}
|
|
|
|
ui->timeHistogram->clearGraphs();
|
|
ui->timeHistogram->addGraph();
|
|
ui->timeHistogram->graph()->setData(timeGraphX, timeGraphY);
|
|
ui->timeHistogram->graph()->setLineStyle(QCPGraph::lsStepLeft); //connect points with lines
|
|
//QBrush graphBrush;
|
|
graphBrush.setColor(Qt::red);
|
|
graphBrush.setStyle(Qt::SolidPattern);
|
|
ui->timeHistogram->graph()->setPen(Qt::NoPen);
|
|
ui->timeHistogram->graph()->setBrush(graphBrush);
|
|
//ui->timeHistogram->yAxis->setRange(0, maxTimeCounter * 1.1);
|
|
//ui->timeHistogram->xAxis->setRange(minInterval / 1000.0, maxInterval / 1000.0); //graph is in ms while intervals are in us
|
|
ui->timeHistogram->axisRect()->setupFullAxesBox();
|
|
ui->timeHistogram->rescaleAxes();
|
|
ui->timeHistogram->replot();
|
|
}
|
|
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++)
|
|
{
|
|
CANFrame thisFrame = modelFrames->at(i);
|
|
id = (int)thisFrame.frameId();
|
|
if (!foundID.contains(id))
|
|
{
|
|
foundID.append(id);
|
|
FilterUtility::createFilterItem(id, ui->listFrameID);
|
|
}
|
|
}
|
|
//default is to sort in ascending order
|
|
ui->listFrameID->sortItems();
|
|
ui->lblUniqueID->setText("(" + QString::number(ui->listFrameID->count()) + tr(" unique ids)"));
|
|
}
|
|
|
|
void FrameInfoWindow::saveDetails()
|
|
{
|
|
QString filename;
|
|
QFileDialog dialog(this);
|
|
QSettings settings;
|
|
|
|
QStringList filters;
|
|
filters.append(QString(tr("Text File (*.txt)")));
|
|
|
|
dialog.setFileMode(QFileDialog::AnyFile);
|
|
dialog.setNameFilters(filters);
|
|
dialog.setViewMode(QFileDialog::Detail);
|
|
dialog.setAcceptMode(QFileDialog::AcceptSave);
|
|
dialog.setDirectory(settings.value("FrameInfo/LoadSaveDirectory", dialog.directory().path()).toString());
|
|
|
|
if (dialog.exec() == QDialog::Accepted)
|
|
{
|
|
settings.setValue("FrameInfo/LoadSaveDirectory", dialog.directory().path());
|
|
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(FilterUtility::getId(ui->listFrameID->item(i)));
|
|
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 );
|
|
}
|
|
|
|
|