Files
giesCANne/frameplaybackwindow.cpp
T
mjuhanne 428b19c5c4 - Save DBC file configuration when changed, and restore it when restarting the application (no need for manual entry everytime app is launched)
- Add GMLAN 29bit filtering feature. In addition to J1939 matching, it is now possible to match DBC messages using only the Arbitration Id (bits 14-26 of CAN Address), since  Priority id (higher bits) and Sender Id (lowest 13 bits) are not relevant when interpreting the CAN message content. This allows to use the excellent GM Global A DBC files on https://github.com/commaai/opendbc. Just remember to select the "GMLAN" matching criteria on DBC File manager.
- Add "Filter labeling" feature. Using loaded DBC files, it matches the filter Ids with DBC messages and shows them in filter windows. Labeling works in the main window (no need to set "Frame interpret" on, since this is a separate feature), as well as in Flow view, Frame data analysis, Playback and Fuzzzing windows. This makes it so much easier to concentrate on those CAN messages that matter. To use this feature, select global "Label filters using messages from DBC files" on Main Configuration page, and then enable the feature on individual DBC files (on DBC file manager) as needed. This is needed if there's overlap in CAN addresses between DBC files, and because the filter labeler cannot tell apart anymore which Filter ID is associated to which bus, it might label them incorrectly. But usually it's ok to just enable the labeling with all the DBC files.
- In Frame data analysis window, in addition to J1939 decoding show also GMLAN (Priority bits, Arbitration Id, Sender Id) decoding.
- FilterUtility static class used for centralized ListWidgetItem creation and for other helper functions
- DBC handler: To allow setting bus numbers even before connection is configured, do not enforce "valid" bus numbers
2019-08-11 23:16:02 +03:00

638 lines
20 KiB
C++

#include "frameplaybackwindow.h"
#include "ui_frameplaybackwindow.h"
#include <QDebug>
#include <QFileDialog>
#include <QMenu>
#include <QSettings>
#include "connections/canconmanager.h"
#include "helpwindow.h"
#include "filterutility.h"
/*
* Notes about new functionality:
* The system has a sequence of files or captured data to play back. It starts at the first item in the list
* and goes through all of the frames. When it gets to the end it sees if there is a loop count for this file.
* If so it increments the loop count and goes back to the beginning. If not it goes to the next file in the list.
* At the end of the list it either stops (if looping is off) or goes back to the first item in the list.
* All items in the list have their own frame cache and filters
*
* So, to do this we'll have to track which item we're on and which frame within that item. As we go through
* items it should highlight the row we're on
*
*/
FramePlaybackWindow::FramePlaybackWindow(const QVector<CANFrame> *frames, QWidget *parent) :
QDialog(parent),
ui(new Ui::FramePlaybackWindow)
{
ui->setupUi(this);
setWindowFlags(Qt::Window);
int numBuses = CANConManager::getInstance()->getNumBuses();
for (int n = 0; n < numBuses; n++) ui->comboCANBus->addItem(QString::number(n));
ui->comboCANBus->addItem(tr("All"));
ui->comboCANBus->addItem(tr("From File"));
ui->comboCANBus->setCurrentIndex(0);
playbackObject.initialize();
playbackObject.setNumBuses(numBuses);
readSettings();
modelFrames = frames;
currentSeqItem = nullptr;
currentSeqNum = -1;
currentPosition = 0;
forward = true;
isPlaying = false;
updateFrameLabel();
connect(ui->btnStepBack, &QAbstractButton::clicked, this, &FramePlaybackWindow::btnBackOneClick);
connect(ui->btnPause, &QAbstractButton::clicked, this, &FramePlaybackWindow::btnPauseClick);
connect(ui->btnPlayReverse, &QAbstractButton::clicked, this, &FramePlaybackWindow::btnReverseClick);
connect(ui->btnStop, &QAbstractButton::clicked, this, &FramePlaybackWindow::btnStopClick);
connect(ui->btnPlay, &QAbstractButton::clicked, this, &FramePlaybackWindow::btnPlayClick);
connect(ui->btnStepForward, &QAbstractButton::clicked, this, &FramePlaybackWindow::btnFwdOneClick);
connect(ui->btnSelectAll, &QAbstractButton::clicked, this, &FramePlaybackWindow::btnSelectAllClick);
connect(ui->btnSelectNone, &QAbstractButton::clicked, this, &FramePlaybackWindow::btnSelectNoneClick);
connect(ui->btnDelete, &QAbstractButton::clicked, this, &FramePlaybackWindow::btnDeleteCurrSeq);
connect(ui->spinPlaySpeed, SIGNAL(valueChanged(int)), this, SLOT(changePlaybackSpeed(int)));
connect(ui->spinBurstSpeed, SIGNAL(valueChanged(int)), this, SLOT(changeBurstRate(int)));
//connect(ui->cbLoop, SIGNAL(clicked(bool)), this, SLOT(changeLooping(bool)));
connect(ui->comboCANBus, SIGNAL(currentIndexChanged(int)), this, SLOT(changeSendingBus(int)));
connect(ui->listID, &QListWidget::itemChanged, this, &FramePlaybackWindow::changeIDFiltering);
connect(ui->btnLoadFile, &QAbstractButton::clicked, this, &FramePlaybackWindow::btnLoadFile);
connect(ui->btnLoadLive, &QAbstractButton::clicked, this, &FramePlaybackWindow::btnLoadLive);
connect(ui->tblSequence, &QTableWidget::cellPressed, this, &FramePlaybackWindow::seqTableCellClicked);
connect(ui->tblSequence, &QTableWidget::cellChanged, this, &FramePlaybackWindow::seqTableCellChanged);
connect(ui->btnLoadFilters, &QAbstractButton::clicked, this, &FramePlaybackWindow::loadFilters);
connect(ui->btnSaveFilters, &QAbstractButton::clicked, this, &FramePlaybackWindow::saveFilters);
connect(ui->cbOriginalTiming, &QCheckBox::toggled, this, &FramePlaybackWindow::useOrigTimingClicked);
connect(&playbackObject, &FramePlaybackObject::EndOfFrameCache, this, &FramePlaybackWindow::EndOfFrameCache);
connect(&playbackObject, &FramePlaybackObject::statusUpdate, this, &FramePlaybackWindow::getStatusUpdate);
ui->listID->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->listID, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenuFilters(QPoint)));
playbackObject.setPlaybackInterval(ui->spinPlaySpeed->value());
// Prevent annoying accidental horizontal scrolling when filter list is populated with long interpreted message names
ui->listID->horizontalScrollBar()->setEnabled(false);
QStringList headers;
headers << "Source" << "Loops";
ui->tblSequence->setColumnCount(2);
ui->tblSequence->setColumnWidth(0, 260);
ui->tblSequence->setColumnWidth(1, 80);
ui->tblSequence->setHorizontalHeaderLabels(headers);
}
FramePlaybackWindow::~FramePlaybackWindow()
{
delete ui;
}
void FramePlaybackWindow::showEvent(QShowEvent *)
{
readSettings();
installEventFilter(this);
int numBuses = CANConManager::getInstance()->getNumBuses();
ui->comboCANBus->clear();
for (int n = 0; n < numBuses; n++) ui->comboCANBus->addItem(QString::number(n));
ui->comboCANBus->addItem(tr("All"));
ui->comboCANBus->addItem(tr("From File"));
ui->comboCANBus->setCurrentIndex(0);
playbackObject.initialize();
playbackObject.setNumBuses(numBuses);
}
void FramePlaybackWindow::closeEvent(QCloseEvent *event)
{
Q_UNUSED(event);
removeEventFilter(this);
writeSettings();
}
bool FramePlaybackWindow::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("playbackwindow.html");
break;
}
return true;
} else {
// standard event processing
return QObject::eventFilter(obj, event);
}
return false;
}
void FramePlaybackWindow::readSettings()
{
QSettings settings;
if (settings.value("Main/SaveRestorePositions", false).toBool())
{
resize(settings.value("Playback/WindowSize", QSize(742, 606)).toSize());
move(settings.value("Playback/WindowPos", QPoint(50, 50)).toPoint());
}
if (settings.value("Playback/AutoLoop", false).toBool())
{
ui->cbLoop->setChecked(true);
}
ui->spinPlaySpeed->setValue(settings.value("Playback/DefSpeed", 5).toInt());
ui->comboCANBus->setCurrentIndex(settings.value("Playback/SendingBus", 0).toInt());
calculateWhichBus();
}
void FramePlaybackWindow::writeSettings()
{
QSettings settings;
if (settings.value("Main/SaveRestorePositions", false).toBool())
{
settings.setValue("Playback/WindowSize", size());
settings.setValue("Playback/WindowPos", pos());
}
}
void FramePlaybackWindow::contextMenuFilters(QPoint pos)
{
QMenu *menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose);
menu->addAction(tr("Save filter definition to file"), this, SLOT(saveFilters()));
menu->addAction(tr("Load filter definition from file"), this, SLOT(loadFilters()));
menu->popup(ui->listID->mapToGlobal(pos));
}
void FramePlaybackWindow::saveFilters()
{
QString filename;
QFileDialog dialog(this);
QSettings settings;
QStringList filters;
filters.append(QString(tr("Filter list (*.ftl)")));
dialog.setDirectory(settings.value("Filters/LoadSaveDirectory", dialog.directory().path()).toString());
dialog.setFileMode(QFileDialog::AnyFile);
dialog.setNameFilters(filters);
dialog.setViewMode(QFileDialog::Detail);
dialog.setAcceptMode(QFileDialog::AcceptSave);
if (dialog.exec() == QDialog::Accepted)
{
filename = dialog.selectedFiles()[0];
if (!filename.contains('.')) filename += ".ftl";
if (dialog.selectedNameFilter() == filters[0])
{
QFile *outFile = new QFile(filename);
if (!outFile->open(QIODevice::WriteOnly | QIODevice::Text))
return;
for (int c = 0; c < ui->listID->count(); c++)
{
outFile->write(QString::number(FilterUtility::getId(ui->listID->item(c)).toInt(nullptr, 16), 16).toUtf8());
outFile->putChar(',');
if (ui->listID->item(c)->checkState() == Qt::Checked) outFile->putChar('T');
else outFile->putChar('F');
outFile->write("\n");
}
outFile->close();
dialog.setDirectory(settings.value("Filters/LoadSaveDirectory", dialog.directory().path()).toString());
}
}
}
void FramePlaybackWindow::loadFilters()
{
QString filename;
QFileDialog dialog(this);
QSettings settings;
QStringList filters;
filters.append(QString(tr("Filter List (*.ftl)")));
dialog.setDirectory(settings.value("Filters/LoadSaveDirectory", dialog.directory().path()).toString());
dialog.setFileMode(QFileDialog::ExistingFile);
dialog.setNameFilters(filters);
dialog.setViewMode(QFileDialog::Detail);
if (dialog.exec() == QDialog::Accepted)
{
filename = dialog.selectedFiles()[0];
//right now there is only one file type that can be loaded here so just do it.
QFile *inFile = new QFile(filename);
QByteArray line;
int ID;
bool checked = false;
if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text))
return;
btnSelectNoneClick();
while (!inFile->atEnd()) {
line = inFile->readLine().simplified();
if (line.length() > 2)
{
QList<QByteArray> tokens = line.split(',');
ID = tokens[0].toInt(nullptr, 16);
if (tokens[1].toUpper() == "T") checked = true;
else checked = false;
if (checked)
{
QHash<int,bool>::iterator it;
for (it = currentSeqItem->idFilters.begin(); it != currentSeqItem->idFilters.end(); ++it)
{
if (it.key() == ID)
{
it.value() = true;
}
}
for (int c = 0; c < ui->listID->count(); c++)
{
QListWidgetItem *item = ui->listID->item(c);
if (FilterUtility::getId(item).toInt(nullptr, 16) == ID)
{
item->setCheckState(Qt::Checked);
}
}
}
}
}
inFile->close();
dialog.setDirectory(settings.value("Filters/LoadSaveDirectory", dialog.directory().path()).toString());
}
}
void FramePlaybackWindow::refreshIDList()
{
if (currentSeqNum < 0 || currentSeqItem == nullptr)
{
ui->listID->clear();
return;
}
ui->tblSequence->setCurrentCell(currentSeqNum, 0);
//this signal would otherwise be called constantly as we do this routine. Turn it off temporarily
disconnect(ui->listID, &QListWidget::itemChanged, this, &FramePlaybackWindow::changeIDFiltering);
ui->listID->clear();
QHash<int, bool>::Iterator filterIter;
for (filterIter = currentSeqItem->idFilters.begin(); filterIter != currentSeqItem->idFilters.end(); ++filterIter)
{
QListWidgetItem* listItem = FilterUtility::createCheckableFilterItem(filterIter.key(), filterIter.value(), ui->listID);
}
//default is to sort in ascending order
ui->listID->sortItems();
connect(ui->listID, &QListWidget::itemChanged, this, &FramePlaybackWindow::changeIDFiltering);
}
void FramePlaybackWindow::calculateWhichBus()
{
int idx = ui->comboCANBus->currentIndex();
int maxIdx = ui->comboCANBus->count() - 1;
if (maxIdx == 0) maxIdx = 2;
int out = idx;
qDebug() << idx << "***" << maxIdx;
if (idx == (maxIdx - 1) ) out = -1;
if (idx == maxIdx) out = -2;
if (idx < 0) out = 0;
playbackObject.setSendingBus(out);
}
void FramePlaybackWindow::EndOfFrameCache()
{
if (forward)
{
currentSeqNum++; //go forward in the sequence
if (currentSeqNum == seqItems.count()) //are we at the end of the sequence?
{
//reset the loop figures for each sequence entry
for (int i = 0; i < seqItems.count(); i++) seqItems[i].currentLoopCount = 0;
currentSeqNum = 0;
if (ui->cbLoop->isChecked()) //go back to beginning if we're looping the sequence
{
}
else //not looping so stop playback entirely
{
isPlaying = false;
playbackObject.stopPlayback();
}
}
currentSeqItem = &seqItems[currentSeqNum];
playbackObject.setSequenceObject(currentSeqItem);
if (isPlaying) playbackObject.startPlaybackForward();
ui->tblSequence->setCurrentCell(currentSeqNum, 0);
}
else
{
currentSeqNum--; //go backward in the sequence
if (currentSeqNum == -1) //are we trying to go past the beginning?
{
//reset the loop figures for each sequence entry
for (int i = 0; i < seqItems.count(); i++) seqItems[i].currentLoopCount = 0;
currentSeqNum = seqItems.count() - 1;
if (ui->cbLoop->isChecked()) //go back to the last sequence entry if we're looping
{
}
else //not looping so stop playback entirely
{
isPlaying = false;
playbackObject.stopPlayback();
}
}
currentSeqItem = &seqItems[currentSeqNum];
playbackObject.setSequenceObject(currentSeqItem);
if (isPlaying) playbackObject.startPlaybackBackward();
ui->tblSequence->setCurrentCell(currentSeqNum, 0);
}
}
void FramePlaybackWindow::getStatusUpdate(int frameNum)
{
currentPosition = frameNum;
updateFrameLabel();
}
void FramePlaybackWindow::updateFrameLabel()
{
int row = currentSeqNum;
if (row != ui->tblSequence->currentRow())
{
ui->tblSequence->setCurrentCell(row, 0);
}
if (row == -1)
{
ui->lblCurrPlayback->setText("");
ui->lblPosition->setText("");
return;
}
ui->lblCurrPlayback->setText(currentSeqItem->filename);
ui->lblPosition->setText(QString::number(currentPosition) + tr(" of ") + QString::number(seqItems[row].data.count()));
}
void FramePlaybackWindow::seqTableCellClicked(int row, int col)
{
qDebug() << "Row: " << QString::number(row) << " Col: " << QString::number(col);
if (currentSeqNum != row)
{
currentSeqNum = row;
currentSeqItem = &seqItems[row];
refreshIDList();
}
}
void FramePlaybackWindow::seqTableCellChanged(int row,int col)
{
if (col == 1) //don't save any changes to the first column (0)
{
seqItems[row].maxLoops = Utility::ParseStringToNum(ui->tblSequence->item(row, col)->text());
if (seqItems[row].maxLoops < 1) seqItems[row].maxLoops = 1;
}
}
void FramePlaybackWindow::fillIDHash(SequenceItem &item)
{
int id;
item.idFilters.clear();
for (int i = 0; i < item.data.count(); i++)
{
id = item.data[i].ID;
if (!item.idFilters.contains(id))
{
item.idFilters.insert(id, true);
}
}
}
void FramePlaybackWindow::useOrigTimingClicked()
{
if (ui->cbOriginalTiming->isChecked())
{
ui->spinBurstSpeed->setEnabled(false);
ui->spinPlaySpeed->setEnabled(false);
playbackObject.setUseOriginalTiming(true);
}
else
{
ui->spinBurstSpeed->setEnabled(true);
ui->spinPlaySpeed->setEnabled(true);
playbackObject.setUseOriginalTiming(false);
}
}
void FramePlaybackWindow::btnDeleteCurrSeq()
{
if (currentSeqNum == -1) return;
playbackObject.stopPlayback();
seqItems.removeAt(currentSeqNum);
ui->tblSequence->removeRow(currentSeqNum);
if (seqItems.count() > 0)
{
currentSeqNum = 0;
currentSeqItem = &seqItems[currentSeqNum];
}
else
{
currentSeqNum = -1;
currentSeqItem = nullptr;
}
refreshIDList();
updateFrameLabel();
}
void FramePlaybackWindow::btnLoadFile()
{
QString filename;
SequenceItem item;
if (FrameFileIO::loadFrameFile(filename, &item.data))
{
qSort(item.data); //sort by timestamp to be sure it's in order
QStringList fileList = filename.split('/');
item.filename = fileList[fileList.length() - 1];
item.currentLoopCount = 0;
item.maxLoops = 1;
fillIDHash(item);
if (ui->tblSequence->currentRow() == -1)
{
ui->tblSequence->setCurrentCell(0,0);
}
seqItems.append(item);
int row = ui->tblSequence->rowCount();
ui->tblSequence->insertRow(row);
ui->tblSequence->setItem(row, 0, new QTableWidgetItem(item.filename));
ui->tblSequence->setItem(row, 1, new QTableWidgetItem(QString::number(item.maxLoops)));
qDebug() << currentSeqNum;
if (currentSeqNum == -1)
{
currentSeqNum = 0;
currentSeqItem = &seqItems[0];
playbackObject.setSequenceObject(currentSeqItem);
}
refreshIDList();
updateFrameLabel();
}
}
void FramePlaybackWindow::btnLoadLive()
{
SequenceItem item;
item.filename = "<CAPTURED DATA>";
item.currentLoopCount = 0;
item.maxLoops = 1;
item.data = QVector<CANFrame>(*modelFrames); //create a copy of the current frames from the main view
qSort(item.data); //be sure it's all in time based order
fillIDHash(item);
if (ui->tblSequence->currentRow() == -1)
{
ui->tblSequence->setCurrentCell(0,0);
}
seqItems.append(item);
int row = ui->tblSequence->rowCount();
ui->tblSequence->insertRow(row);
ui->tblSequence->setItem(row, 0, new QTableWidgetItem(item.filename));
ui->tblSequence->setItem(row, 1, new QTableWidgetItem(QString::number(item.maxLoops)));
if (currentSeqNum == -1)
{
currentSeqNum = 0;
currentSeqItem = &seqItems[0];
playbackObject.setSequenceObject(currentSeqItem);
}
refreshIDList();
updateFrameLabel();
btnStopClick();
}
void FramePlaybackWindow::btnBackOneClick()
{
forward = false;
isPlaying = false;
playbackObject.stepPlaybackBackward();
}
void FramePlaybackWindow::btnPauseClick()
{
isPlaying = false;
playbackObject.pausePlayback();
}
void FramePlaybackWindow::btnReverseClick()
{
forward = false;
isPlaying = true;
playbackObject.startPlaybackBackward();
updateFrameLabel();
}
void FramePlaybackWindow::btnStopClick()
{
isPlaying = false;
playbackObject.stopPlayback();
if (seqItems.count() > 0)
{
currentSeqNum = 0;
currentSeqItem = &seqItems[currentSeqNum];
}
else {
currentSeqNum = -1;
currentSeqItem = nullptr;
}
if (ui->tblSequence->rowCount() > 0)
{
ui->tblSequence->setCurrentCell(0, 0);
refreshIDList();
}
}
void FramePlaybackWindow::btnPlayClick()
{
forward = true;
isPlaying = true;
playbackObject.startPlaybackForward();
}
void FramePlaybackWindow::btnFwdOneClick()
{
forward = true;
isPlaying = false;
playbackObject.stepPlaybackForward();
}
void FramePlaybackWindow::changePlaybackSpeed(int newSpeed)
{
playbackObject.setPlaybackInterval(newSpeed);
}
void FramePlaybackWindow::changeBurstRate(int burst)
{
playbackObject.setPlaybackBurst(burst);
}
void FramePlaybackWindow::changeLooping(bool check)
{
Q_UNUSED(check);
}
void FramePlaybackWindow::changeSendingBus(int newIdx)
{
Q_UNUSED(newIdx);
calculateWhichBus();
}
void FramePlaybackWindow::changeIDFiltering(QListWidgetItem *item)
{
qDebug() << "Changed ID filter " << item->text() << " : " << item->checkState();
int ID = FilterUtility::getIdAsInt(item);
currentSeqItem->idFilters[ID] = (item->checkState() == Qt::Checked) ? true : false;
}
void FramePlaybackWindow::btnSelectAllClick()
{
for (int i = 0; i < ui->listID->count(); i++)
{
QListWidgetItem *item = ui->listID->item(i);
item->setCheckState(Qt::Checked);
currentSeqItem->idFilters[Utility::ParseStringToNum(item->text())] = true;
}
}
void FramePlaybackWindow::btnSelectNoneClick()
{
for (int i = 0; i < ui->listID->count(); i++)
{
QListWidgetItem *item = ui->listID->item(i);
item->setCheckState(Qt::Unchecked);
currentSeqItem->idFilters[Utility::ParseStringToNum(item->text())] = false;
}
}