- 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
1058 lines
35 KiB
C++
1058 lines
35 KiB
C++
#include "mainwindow.h"
|
|
#include "ui_mainwindow.h"
|
|
#include "can_structs.h"
|
|
#include <QDateTime>
|
|
#include <QFileDialog>
|
|
#include <QtSerialPort/QSerialPortInfo>
|
|
#include "connections/canconmanager.h"
|
|
#include "connections/connectionwindow.h"
|
|
#include "helpwindow.h"
|
|
#include "utility.h"
|
|
#include "filterutility.h"
|
|
|
|
/*
|
|
Some notes on things I'd like to put into the program but haven't put on github (yet)
|
|
|
|
Allow scripts to read/write signals from DBC files
|
|
allow scripts to load DBC files in support of the script - maybe the graphing system too.
|
|
*/
|
|
|
|
QString MainWindow::loadedFileName = "";
|
|
MainWindow *MainWindow::selfRef = nullptr;
|
|
|
|
MainWindow *MainWindow::getReference()
|
|
{
|
|
return selfRef;
|
|
}
|
|
|
|
MainWindow::MainWindow(QWidget *parent) :
|
|
QMainWindow(parent),
|
|
ui(new Ui::MainWindow)
|
|
{
|
|
ui->setupUi(this);
|
|
qRegisterMetaTypeStreamOperators<QVector<QString>>();
|
|
qRegisterMetaTypeStreamOperators<QVector<int>>();
|
|
|
|
useHex = true;
|
|
|
|
selfRef = this;
|
|
|
|
this->setWindowTitle("Savvy CAN V" + QString::number(VERSION));
|
|
|
|
model = new CANFrameModel(this); // set parent to mainwindow to prevent canframemodel to change thread (might be done by setModel but just in case)
|
|
|
|
QSortFilterProxyModel* proxyModel = new QSortFilterProxyModel;
|
|
proxyModel->setSourceModel(model);
|
|
|
|
ui->canFramesView->setModel(proxyModel);
|
|
|
|
settingsDialog = new MainSettingsDialog(); //instantiate the settings dialog so it can initialize settings if this is the first run or the config file was deleted.
|
|
settingsDialog->updateSettings(); //write out all the settings. If this is the first run it'll write defaults out.
|
|
|
|
readSettings();
|
|
|
|
//QHeaderView *verticalHeader = ui->canFramesView->verticalHeader();
|
|
//verticalHeader->setSectionResizeMode(QHeaderView::Fixed);
|
|
//verticalHeader->setDefaultSectionSize(10);
|
|
QHeaderView *HorzHdr = ui->canFramesView->horizontalHeader();
|
|
HorzHdr->setStretchLastSection(true); //causes the data column to automatically fill the tableview
|
|
connect(HorzHdr, SIGNAL(sectionClicked(int)), this, SLOT(headerClicked(int)));
|
|
|
|
graphingWindow = nullptr;
|
|
frameInfoWindow = nullptr;
|
|
playbackWindow = nullptr;
|
|
flowViewWindow = nullptr;
|
|
frameSenderWindow = nullptr;
|
|
dbcMainEditor = nullptr;
|
|
comparatorWindow = nullptr;
|
|
settingsDialog = nullptr;
|
|
firmwareUploaderWindow = nullptr;
|
|
discreteStateWindow = nullptr;
|
|
connectionWindow = nullptr;
|
|
scriptingWindow = nullptr;
|
|
rangeWindow = nullptr;
|
|
dbcFileWindow = nullptr;
|
|
fuzzingWindow = nullptr;
|
|
udsScanWindow = nullptr;
|
|
motorctrlConfigWindow = nullptr;
|
|
isoWindow = nullptr;
|
|
snifferWindow = nullptr;
|
|
bisectWindow = nullptr;
|
|
signalViewerWindow = nullptr;
|
|
temporalGraphWindow = nullptr;
|
|
dbcHandler = DBCHandler::getReference();
|
|
bDirty = false;
|
|
inhibitFilterUpdate = false;
|
|
rxFrames = 0;
|
|
framesPerSec = 0;
|
|
continuousLogging = false;
|
|
continuousLogFlushCounter = 0;
|
|
|
|
connect(ui->actionSetup, SIGNAL(triggered(bool)), SLOT(showConnectionSettingsWindow()));
|
|
connect(ui->actionOpen_Log_File, &QAction::triggered, this, &MainWindow::handleLoadFile);
|
|
connect(ui->actionGraph_Dta, &QAction::triggered, this, &MainWindow::showGraphingWindow);
|
|
connect(ui->actionFrame_Data_Analysis, &QAction::triggered, this, &MainWindow::showFrameDataAnalysis);
|
|
connect(ui->btnClearFrames, &QAbstractButton::clicked, this, &MainWindow::clearFrames);
|
|
connect(ui->actionSave_Log_File, &QAction::triggered, this, &MainWindow::handleSaveFile);
|
|
connect(ui->actionSave_Filtered_Log_File, &QAction::triggered, this, &MainWindow::handleSaveFilteredFile);
|
|
connect(ui->actionLoad_Filter_Definition, &QAction::triggered, this, &MainWindow::handleLoadFilters);
|
|
connect(ui->actionSave_Filter_Definition, &QAction::triggered, this, &MainWindow::handleSaveFilters);
|
|
connect(ui->action_Playback, &QAction::triggered, this, &MainWindow::showPlaybackWindow);
|
|
connect(ui->actionFlow_View, &QAction::triggered, this, &MainWindow::showFlowViewWindow);
|
|
connect(ui->action_Custom, &QAction::triggered, this, &MainWindow::showFrameSenderWindow);
|
|
connect(ui->canFramesView, &QAbstractItemView::pressed, this, &MainWindow::gridClicked);
|
|
connect(ui->canFramesView, &QAbstractItemView::doubleClicked, this, &MainWindow::gridDoubleClicked);
|
|
connect(ui->cbInterpret, &QAbstractButton::toggled, this, &MainWindow::interpretToggled);
|
|
connect(ui->cbOverwrite, &QAbstractButton::toggled, this, &MainWindow::overwriteToggled);
|
|
connect(ui->btnCaptureToggle, &QAbstractButton::clicked, this, &MainWindow::toggleCapture);
|
|
connect(ui->actionExit_Application, &QAction::triggered, this, &MainWindow::exitApp);
|
|
connect(ui->actionFuzzy_Scope, &QAction::triggered, this, &MainWindow::showFuzzyScopeWindow);
|
|
connect(ui->actionRange_State_2, &QAction::triggered, this, &MainWindow::showRangeWindow);
|
|
connect(ui->actionSave_Decoded_Frames, &QAction::triggered, this, &MainWindow::handleSaveDecoded);
|
|
connect(ui->actionSingle_Multi_State_2, &QAction::triggered, this, &MainWindow::showSingleMultiWindow);
|
|
connect(ui->actionFile_Comparison, &QAction::triggered, this, &MainWindow::showComparisonWindow);
|
|
connect(ui->actionScripting_INterface, &QAction::triggered, this, &MainWindow::showScriptingWindow);
|
|
connect(ui->btnNormalize, &QAbstractButton::clicked, this, &MainWindow::normalizeTiming);
|
|
connect(ui->actionPreferences, &QAction::triggered, this, &MainWindow::showSettingsDialog);
|
|
connect(model, &CANFrameModel::updatedFiltersList, this, &MainWindow::updateFilterList);
|
|
connect(ui->listFilters, &QListWidget::itemChanged, this, &MainWindow::filterListItemChanged);
|
|
connect(ui->btnFilterAll, &QAbstractButton::clicked, this, &MainWindow::filterSetAll);
|
|
connect(ui->btnFilterNone, &QAbstractButton::clicked, this, &MainWindow::filterClearAll);
|
|
connect(ui->actionFirmware_Update, &QAction::triggered, this, &MainWindow::showFirmwareUploaderWindow);
|
|
connect(ui->actionDBC_File_Manager, &QAction::triggered, this, &MainWindow::showDBCFileWindow);
|
|
connect(ui->actionFuzzing, &QAction::triggered, this, &MainWindow::showFuzzingWindow);
|
|
connect(ui->actionUDS_Scanner, &QAction::triggered, this, &MainWindow::showUDSScanWindow);
|
|
connect(ui->actionISO_TP_Decoder, &QAction::triggered, this, &MainWindow::showISOInterpreterWindow);
|
|
connect(ui->actionSniffer, &QAction::triggered, this, &MainWindow::showSnifferWindow);
|
|
connect(ui->actionMotorControlConfig, &QAction::triggered, this, &MainWindow::showMCConfigWindow);
|
|
connect(ui->actionCapture_Bisector, &QAction::triggered, this, &MainWindow::showBisectWindow);
|
|
connect(ui->actionSignal_Viewer, &QAction::triggered, this, &MainWindow::showSignalViewer);
|
|
connect(ui->actionSave_Continuous_Logfile, &QAction::triggered, this, &MainWindow::handleContinousLogging);
|
|
connect(ui->actionTemporal_Graph, &QAction::triggered, this, &MainWindow::showTemporalGraphWindow);
|
|
|
|
connect(CANConManager::getInstance(), &CANConManager::framesReceived, model, &CANFrameModel::addFrames);
|
|
|
|
lbStatusConnected.setText(tr("Connected to 0 buses"));
|
|
updateFileStatus();
|
|
//lbStatusDatabase.setText(tr("No DBC database loaded"));
|
|
ui->statusBar->addWidget(&lbStatusConnected);
|
|
ui->statusBar->addWidget(&lbStatusFilename);
|
|
//ui->statusBar->addWidget(&lbStatusDatabase);
|
|
ui->lblRemoteConn->setVisible(false);
|
|
ui->lineRemoteKey->setVisible(false);
|
|
|
|
ui->lbFPS->setText("0");
|
|
ui->lbNumFrames->setText("0");
|
|
|
|
// Prevent annoying accidental horizontal scrolling when filter list is populated with long interpreted message names
|
|
ui->listFilters->horizontalScrollBar()->setEnabled(false);
|
|
|
|
connect(&updateTimer, &QTimer::timeout, this, &MainWindow::tickGUIUpdate);
|
|
updateTimer.setInterval(250);
|
|
updateTimer.start();
|
|
|
|
elapsedTime = new QTime;
|
|
elapsedTime->start();
|
|
|
|
isConnected = false;
|
|
allowCapture = true;
|
|
|
|
//create a temporary frame to be able to capture the correct
|
|
//default height of an item in the table. Need to do this in case
|
|
//of scaling or font differences between different computers.
|
|
CANFrame temp;
|
|
temp.bus = 0;
|
|
temp.ID = 0x100;
|
|
temp.len = 0;
|
|
temp.extended = false;
|
|
temp.isReceived = true;
|
|
temp.remote = false;
|
|
temp.timestamp = 100000000;
|
|
model->addFrame(temp, true);
|
|
qApp->processEvents();
|
|
tickGUIUpdate(); //force a GUI refresh so that the row exists to measure
|
|
normalRowHeight = ui->canFramesView->rowHeight(0);
|
|
if (normalRowHeight == 0) normalRowHeight = 30; //should not be necessary but provides a sane number if something stupid happened.
|
|
qDebug() << "normal row height = " << normalRowHeight;
|
|
model->clearFrames();
|
|
|
|
//connect(CANConManager::getInstance(), CANConManager::connectionStatusUpdated, this, MainWindow::connectionStatusUpdated);
|
|
connect(CANConManager::getInstance(), SIGNAL(connectionStatusUpdated(int)), this, SLOT(connectionStatusUpdated(int)));
|
|
|
|
//Automatically create the connection window so it can be updated even if we never opened it.
|
|
connectionWindow = new ConnectionWindow();
|
|
connect(this, SIGNAL(suspendCapturing(bool)), connectionWindow, SLOT(setSuspendAll(bool)));
|
|
|
|
//these either are unfinished/not working or are not for general use. But,they exist
|
|
//so if you want to enable them and play with them then go for it.
|
|
ui->actionFirmware_Update->setVisible(false);
|
|
ui->actionMotorControlConfig->setVisible(false);
|
|
ui->actionSingle_Multi_State_2->setVisible(false);
|
|
|
|
installEventFilter(this);
|
|
}
|
|
|
|
MainWindow::~MainWindow()
|
|
{
|
|
updateTimer.stop();
|
|
killEmAll(); //Ride the lightning
|
|
delete ui;
|
|
delete model;
|
|
delete elapsedTime;
|
|
delete dbcHandler;
|
|
}
|
|
|
|
//kill every sub window that could be open. At the moment a hard coded list
|
|
//but eventually each window should be registered and be able to be iterated.
|
|
void MainWindow::killEmAll()
|
|
{
|
|
killWindow(graphingWindow);
|
|
killWindow(frameInfoWindow);
|
|
killWindow(playbackWindow);
|
|
killWindow(flowViewWindow);
|
|
killWindow(frameSenderWindow);
|
|
killWindow(comparatorWindow);
|
|
killWindow(dbcMainEditor);
|
|
killWindow(settingsDialog);
|
|
killWindow(discreteStateWindow);
|
|
killWindow(scriptingWindow);
|
|
killWindow(rangeWindow);
|
|
killWindow(dbcFileWindow);
|
|
killWindow(fuzzingWindow);
|
|
killWindow(udsScanWindow);
|
|
killWindow(isoWindow);
|
|
killWindow(snifferWindow);
|
|
killWindow(bisectWindow);
|
|
killWindow(firmwareUploaderWindow);
|
|
killWindow(motorctrlConfigWindow);
|
|
killWindow(signalViewerWindow);
|
|
killWindow(connectionWindow);
|
|
killWindow(temporalGraphWindow);
|
|
}
|
|
|
|
//forcefully close the window, kill it, and salt the earth
|
|
//note, for some stupid reason this function causes a seg fault
|
|
//it seems that when it runs just before the program closes it'll
|
|
//fault out when trying to close the connection window. I assume
|
|
//this could be because that window has long running threads open and doesn't
|
|
//close quickly or maybe cleanly. Investigate.
|
|
void MainWindow::killWindow(QDialog *win)
|
|
{
|
|
if (win)
|
|
{
|
|
win->close();
|
|
delete win;
|
|
win = nullptr;
|
|
}
|
|
}
|
|
|
|
void MainWindow::exitApp()
|
|
{
|
|
this->close();
|
|
QApplication::quit(); //forces the whole application to terminate when the main window is closed
|
|
}
|
|
|
|
void MainWindow::closeEvent(QCloseEvent *event)
|
|
{
|
|
Q_UNUSED(event);
|
|
removeEventFilter(this);
|
|
writeSettings();
|
|
exitApp();
|
|
}
|
|
|
|
bool MainWindow::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("mainscreen.html");
|
|
return true;
|
|
break;
|
|
}
|
|
return false;
|
|
} else {
|
|
// standard event processing
|
|
return QObject::eventFilter(obj, event);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
void MainWindow::updateSettings()
|
|
{
|
|
readUpdateableSettings();
|
|
emit settingsUpdated();
|
|
}
|
|
|
|
void MainWindow::readSettings()
|
|
{
|
|
QSettings settings;
|
|
if (settings.value("Main/SaveRestorePositions", false).toBool())
|
|
{
|
|
resize(settings.value("Main/WindowSize", QSize(800, 750)).toSize());
|
|
move(settings.value("Main/WindowPos", QPoint(100, 100)).toPoint());
|
|
ui->canFramesView->setColumnWidth(0, settings.value("Main/TimeColumn", 150).toUInt()); //time stamp
|
|
ui->canFramesView->setColumnWidth(1, settings.value("Main/IDColumn", 70).toUInt()); //frame ID
|
|
ui->canFramesView->setColumnWidth(2, settings.value("Main/ExtColumn", 40).toUInt()); //extended
|
|
ui->canFramesView->setColumnWidth(3, settings.value("Main/RemColumn", 40).toUInt()); //remote
|
|
ui->canFramesView->setColumnWidth(4, settings.value("Main/DirColumn", 40).toUInt()); //direction
|
|
ui->canFramesView->setColumnWidth(5, settings.value("Main/BusColumn", 40).toUInt()); //bus
|
|
ui->canFramesView->setColumnWidth(6, settings.value("Main/LengthColumn", 40).toUInt()); //length
|
|
ui->canFramesView->setColumnWidth(7, settings.value("Main/AsciiColumn", 50).toUInt()); //ascii
|
|
//ui->canFramesView->setColumnWidth(8, settings.value("Main/DataColumn", 225).toUInt()); //data
|
|
}
|
|
if (settings.value("Main/AutoScroll", false).toBool())
|
|
{
|
|
ui->cbAutoScroll->setChecked(true);
|
|
}
|
|
int fontSize = settings.value("Main/FontSize", 9).toUInt();
|
|
QFont newFont = QFont(ui->canFramesView->font());
|
|
newFont.setPointSize(fontSize);
|
|
ui->canFramesView->setFont(newFont);
|
|
|
|
readUpdateableSettings();
|
|
}
|
|
|
|
void MainWindow::readUpdateableSettings()
|
|
{
|
|
QSettings settings;
|
|
useHex = settings.value("Main/UseHex", true).toBool();
|
|
model->setHexMode(useHex);
|
|
Utility::decimalMode = !useHex;
|
|
secondsMode = settings.value("Main/TimeSeconds", false).toBool();
|
|
model->setSecondsMode(secondsMode);
|
|
useSystemClock = settings.value("Main/TimeClock", false).toBool();
|
|
model->setSysTimeMode(useSystemClock);
|
|
useFiltered = settings.value("Main/UseFiltered", false).toBool();
|
|
model->setTimeFormat(settings.value("Main/TimeFormat", "MMM-dd HH:mm:ss.zzz").toString());
|
|
|
|
if (settings.value("Main/FilterLabeling", false).toBool())
|
|
ui->listFilters->setMaximumWidth(250);
|
|
else
|
|
ui->listFilters->setMaximumWidth(175);
|
|
updateFilterList();
|
|
}
|
|
|
|
|
|
void MainWindow::writeSettings()
|
|
{
|
|
QSettings settings;
|
|
|
|
if (settings.value("Main/SaveRestorePositions", false).toBool())
|
|
{
|
|
settings.setValue("Main/WindowSize", size());
|
|
settings.setValue("Main/WindowPos", pos());
|
|
settings.setValue("Main/TimeColumn", ui->canFramesView->columnWidth(0));
|
|
settings.setValue("Main/IDColumn", ui->canFramesView->columnWidth(1));
|
|
settings.setValue("Main/ExtColumn", ui->canFramesView->columnWidth(2));
|
|
settings.setValue("Main/RemColumn", ui->canFramesView->columnWidth(3));
|
|
settings.setValue("Main/DirColumn", ui->canFramesView->columnWidth(4));
|
|
settings.setValue("Main/BusColumn", ui->canFramesView->columnWidth(5));
|
|
settings.setValue("Main/LengthColumn", ui->canFramesView->columnWidth(6));
|
|
settings.setValue("Main/AsciiColumn", ui->canFramesView->columnWidth(7));
|
|
//settings.setValue("Main/DataColumn", ui->canFramesView->columnWidth(8));
|
|
}
|
|
}
|
|
|
|
void MainWindow::updateConnectionSettings(QString connectionType, QString port, int speed0, int speed1)
|
|
{
|
|
Q_UNUSED(connectionType);
|
|
Q_UNUSED(port);
|
|
Q_UNUSED(speed0);
|
|
Q_UNUSED(speed1);
|
|
//connType = connectionType;
|
|
//portName = port;
|
|
|
|
//canSpeed0 = speed0;
|
|
//canSpeed1 = speed1;
|
|
if (isConnected)
|
|
{
|
|
//emit updateBaudRates(speed0, speed1);
|
|
}
|
|
}
|
|
|
|
void MainWindow::headerClicked(int logicalIndex)
|
|
{
|
|
//ui->canFramesView->sortByColumn(logicalIndex);
|
|
model->sortByColumn(logicalIndex);
|
|
}
|
|
|
|
void MainWindow::gridClicked(QModelIndex idx)
|
|
{
|
|
//qDebug() << "Grid Clicked";
|
|
if (ui->canFramesView->rowHeight(idx.row()) > normalRowHeight)
|
|
{
|
|
ui->canFramesView->setRowHeight(idx.row(), normalRowHeight);
|
|
}
|
|
else {
|
|
ui->canFramesView->resizeRowToContents(idx.row());
|
|
}
|
|
}
|
|
|
|
void MainWindow::gridDoubleClicked(QModelIndex idx)
|
|
{
|
|
//qDebug() << "Grid double clicked";
|
|
//grab ID and timestamp and send them away
|
|
CANFrame frame = model->getListReference()->at(idx.row());
|
|
emit sendCenterTimeID(frame.ID, frame.timestamp / 1000000.0);
|
|
}
|
|
|
|
void MainWindow::interpretToggled(bool state)
|
|
{
|
|
model->setInterpretMode(state);
|
|
//ui->canFramesView->resizeRowsToContents(); //a VERY costly operation!
|
|
}
|
|
|
|
void MainWindow::overwriteToggled(bool state)
|
|
{
|
|
if (state)
|
|
{
|
|
QMessageBox::StandardButton confirmDialog;
|
|
confirmDialog = QMessageBox::question(this, "Danger Will Robinson", "Enabling Overwrite mode will\ndelete your captured frames\nand replace them with one\nframe per ID.\n\nAre you ready to do that?",
|
|
QMessageBox::Yes|QMessageBox::No);
|
|
if (confirmDialog == QMessageBox::Yes)
|
|
{
|
|
model->setOverwriteMode(true);
|
|
}
|
|
else ui->cbOverwrite->setCheckState(Qt::Unchecked);
|
|
}
|
|
else
|
|
{
|
|
model->setOverwriteMode(false);
|
|
}
|
|
}
|
|
|
|
void MainWindow::updateFilterList()
|
|
{
|
|
if (model == nullptr) return;
|
|
const QMap<int, bool> *filters = model->getFiltersReference();
|
|
if (filters == nullptr) return;
|
|
|
|
qDebug() << "updateFilterList called on MainWindow";
|
|
|
|
ui->listFilters->clear();
|
|
|
|
if (filters->isEmpty()) return;
|
|
|
|
QMap<int, bool>::const_iterator filterIter;
|
|
for (filterIter = filters->begin(); filterIter != filters->end(); ++filterIter)
|
|
{
|
|
QListWidgetItem *thisItem = FilterUtility::createCheckableFilterItem(filterIter.key(), filterIter.value(), ui->listFilters);
|
|
}
|
|
}
|
|
|
|
void MainWindow::filterListItemChanged(QListWidgetItem *item)
|
|
{
|
|
if (inhibitFilterUpdate) return;
|
|
//qDebug() << item->text();
|
|
|
|
// strip away possible filter label
|
|
int ID = FilterUtility::getIdAsInt(item);
|
|
bool isSet = false;
|
|
if (item->checkState() == Qt::Checked) isSet = true;
|
|
|
|
model->setFilterState(ID, isSet);
|
|
}
|
|
|
|
void MainWindow::filterSetAll()
|
|
{
|
|
inhibitFilterUpdate = true;
|
|
for (int i = 0; i < ui->listFilters->count(); i++)
|
|
{
|
|
ui->listFilters->item(i)->setCheckState(Qt::Checked);
|
|
}
|
|
inhibitFilterUpdate = false;
|
|
model->setAllFilters(true);
|
|
}
|
|
|
|
void MainWindow::filterClearAll()
|
|
{
|
|
inhibitFilterUpdate = true;
|
|
for (int i = 0; i < ui->listFilters->count(); i++)
|
|
{
|
|
ui->listFilters->item(i)->setCheckState(Qt::Unchecked);
|
|
}
|
|
inhibitFilterUpdate = false;
|
|
model->setAllFilters(false);
|
|
}
|
|
|
|
void MainWindow::tickGUIUpdate()
|
|
{
|
|
rxFrames = model->sendBulkRefresh();
|
|
//if(rxFrames>0)
|
|
//{
|
|
int elapsed = elapsedTime->elapsed();
|
|
if(elapsed) {
|
|
framesPerSec = (framesPerSec + (rxFrames * 1000 / elapsed)) / 2;
|
|
elapsedTime->restart();
|
|
}
|
|
else
|
|
framesPerSec = 0;
|
|
|
|
ui->lbNumFrames->setText(QString::number(model->rowCount()));
|
|
if (rxFrames > 0 && /*allowCapture && */ ui->cbAutoScroll->isChecked()) ui->canFramesView->scrollToBottom();
|
|
ui->lbFPS->setText(QString::number(framesPerSec));
|
|
if (rxFrames > 0)
|
|
{
|
|
bDirty = true;
|
|
emit framesUpdated(rxFrames); //anyone care that frames were updated?
|
|
}
|
|
|
|
if (model->needsFilterRefresh()) updateFilterList();
|
|
|
|
if (continuousLogging)
|
|
{
|
|
const QVector<CANFrame> *modelFrames = model->getListReference();
|
|
FrameFileIO::writeContinuousNative(modelFrames, modelFrames->count() - rxFrames);
|
|
|
|
continuousLogFlushCounter++;
|
|
if ((continuousLogFlushCounter % 3) == 0)
|
|
{
|
|
if (ui->lblContMsg->text().length() > 2)
|
|
{
|
|
ui->lblContMsg->setText("");
|
|
}
|
|
else
|
|
{
|
|
ui->lblContMsg->setText("CONTINUOUS LOGGING");
|
|
}
|
|
}
|
|
if (continuousLogFlushCounter > 8)
|
|
{
|
|
continuousLogFlushCounter = 0;
|
|
FrameFileIO::flushContinuousNative();
|
|
}
|
|
}
|
|
|
|
rxFrames = 0;
|
|
//}
|
|
}
|
|
|
|
void MainWindow::gotFrames(int framesSinceLastUpdate)
|
|
{
|
|
rxFrames += framesSinceLastUpdate;
|
|
emit frameUpdateRapid(framesSinceLastUpdate);
|
|
}
|
|
|
|
void MainWindow::addFrameToDisplay(CANFrame &frame, bool autoRefresh = false)
|
|
{
|
|
model->addFrame(frame, autoRefresh);
|
|
if (autoRefresh)
|
|
{
|
|
if (ui->cbAutoScroll->isChecked()) ui->canFramesView->scrollToBottom();
|
|
ui->lbNumFrames->setText(QString::number(model->rowCount()));
|
|
}
|
|
}
|
|
|
|
//A sub-window is sending us a center on timestamp and ID signal
|
|
//try to find the relevant frame in the list and focus on it.
|
|
void MainWindow::gotCenterTimeID(int32_t ID, double timestamp)
|
|
{
|
|
int idx = model->getIndexFromTimeID(ID, timestamp);
|
|
if (idx > -1)
|
|
{
|
|
ui->canFramesView->selectRow(idx);
|
|
}
|
|
}
|
|
|
|
void MainWindow::clearFrames()
|
|
{
|
|
ui->canFramesView->scrollToTop();
|
|
model->clearFrames();
|
|
CANConManager::getInstance()->resetTimeBasis();
|
|
ui->lbNumFrames->setText(QString::number(model->rowCount()));
|
|
bDirty = false;
|
|
loadedFileName = "";
|
|
updateFileStatus();
|
|
emit framesUpdated(-1);
|
|
}
|
|
|
|
void MainWindow::normalizeTiming()
|
|
{
|
|
model->normalizeTiming();
|
|
emit framesUpdated(-2); //claim an all new set of frames because every frame was updated.
|
|
}
|
|
|
|
void MainWindow::handleLoadFile()
|
|
{
|
|
QString filename;
|
|
QVector<CANFrame> tempFrames;
|
|
|
|
QMessageBox::StandardButton confirmDialog;
|
|
|
|
bool loadResult = FrameFileIO::loadFrameFile(filename, &tempFrames);
|
|
|
|
if (!loadResult)
|
|
{
|
|
confirmDialog = QMessageBox::question(this, "Error Loading", "Do you want to salvage what could be loaded?",
|
|
QMessageBox::Yes|QMessageBox::No);
|
|
if (confirmDialog == QMessageBox::Yes) {
|
|
loadResult = true;
|
|
}
|
|
}
|
|
|
|
if (loadResult)
|
|
{
|
|
ui->canFramesView->scrollToTop();
|
|
model->clearFrames();
|
|
model->insertFrames(tempFrames);
|
|
loadedFileName = filename;
|
|
model->recalcOverwrite();
|
|
ui->lbNumFrames->setText(QString::number(model->rowCount()));
|
|
if (ui->cbAutoScroll->isChecked()) ui->canFramesView->scrollToBottom();
|
|
|
|
updateFileStatus();
|
|
emit framesUpdated(-1);
|
|
}
|
|
}
|
|
|
|
void MainWindow::handleSaveFile()
|
|
{
|
|
QString filename;
|
|
|
|
if (FrameFileIO::saveFrameFile(filename, model->getListReference()))
|
|
{
|
|
loadedFileName = filename;
|
|
updateFileStatus();
|
|
}
|
|
}
|
|
|
|
void MainWindow::handleContinousLogging()
|
|
{
|
|
continuousLogging = !continuousLogging;
|
|
|
|
if (continuousLogging)
|
|
{
|
|
ui->actionSave_Continuous_Logfile->setText(tr("Cease Continuous Logging"));
|
|
FrameFileIO::openContinuousNative();
|
|
}
|
|
else
|
|
{
|
|
ui->actionSave_Continuous_Logfile->setText(tr("Start Continuous Logging"));
|
|
ui->lblContMsg->setText("");
|
|
FrameFileIO::closeContinuousNative();
|
|
}
|
|
}
|
|
|
|
void MainWindow::handleSaveFilteredFile()
|
|
{
|
|
QString filename;
|
|
|
|
if (FrameFileIO::saveFrameFile(filename, model->getFilteredListReference()))
|
|
{
|
|
loadedFileName = filename;
|
|
updateFileStatus();
|
|
}
|
|
}
|
|
|
|
void MainWindow::handleSaveFilters()
|
|
{
|
|
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]) model->saveFilterFile(filename);
|
|
settings.setValue("Filters/LoadSaveDirectory", dialog.directory().path());
|
|
}
|
|
}
|
|
|
|
void MainWindow::handleLoadFilters()
|
|
{
|
|
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.
|
|
model->loadFilterFile(filename);
|
|
settings.setValue("Filters/LoadSaveDirectory", dialog.directory().path());
|
|
}
|
|
}
|
|
|
|
void MainWindow::handleSaveDecoded()
|
|
{
|
|
QString filename;
|
|
QFileDialog dialog(this);
|
|
QSettings settings;
|
|
|
|
QStringList filters;
|
|
filters.append(QString(tr("Text File (*.txt)")));
|
|
|
|
dialog.setDirectory(settings.value("FileIO/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 += ".txt";
|
|
saveDecodedTextFile(filename);
|
|
settings.setValue("FileIO/LoadSaveDirectory", dialog.directory().path());
|
|
}
|
|
}
|
|
|
|
void MainWindow::saveDecodedTextFile(QString filename)
|
|
{
|
|
QFile *outFile = new QFile(filename);
|
|
const QVector<CANFrame> *frames = model->getFilteredListReference();
|
|
|
|
if (!outFile->open(QIODevice::WriteOnly | QIODevice::Text))
|
|
return;
|
|
/*
|
|
Time: 205.173000 ID: 0x20E Std Bus: 0 Len: 8
|
|
Data Bytes: 88 10 00 13 BB 00 06 00
|
|
SignalName Value
|
|
*/
|
|
for (int c = 0; c < frames->count(); c++)
|
|
{
|
|
CANFrame thisFrame = frames->at(c);
|
|
QString builderString;
|
|
builderString += tr("Time: ") + QString::number((thisFrame.timestamp / 1000000.0), 'f', 6);
|
|
builderString += tr(" ID: ") + Utility::formatCANID(thisFrame.ID, thisFrame.extended);
|
|
if (thisFrame.extended) builderString += tr(" Ext ");
|
|
else builderString += tr(" Std ");
|
|
builderString += tr("Bus: ") + QString::number(thisFrame.bus);
|
|
builderString += " Len: " + QString::number(thisFrame.len) + "\n";
|
|
outFile->write(builderString.toUtf8());
|
|
|
|
builderString = tr("Data Bytes: ");
|
|
for (unsigned int temp = 0; temp < thisFrame.len; temp++)
|
|
{
|
|
builderString += Utility::formatNumber(thisFrame.data[temp]) + " ";
|
|
}
|
|
builderString += "\n";
|
|
outFile->write(builderString.toUtf8());
|
|
|
|
builderString = "";
|
|
if (dbcHandler != nullptr)
|
|
{
|
|
DBC_MESSAGE *msg = dbcHandler->findMessage(thisFrame);
|
|
if (msg != nullptr)
|
|
{
|
|
for (int j = 0; j < msg->sigHandler->getCount(); j++)
|
|
{
|
|
|
|
QString temp;
|
|
if (msg->sigHandler->findSignalByIdx(j)->processAsText(thisFrame, temp))
|
|
{
|
|
builderString.append("\t" + temp);
|
|
builderString.append("\n");
|
|
}
|
|
}
|
|
}
|
|
builderString.append("\n");
|
|
outFile->write(builderString.toUtf8());
|
|
}
|
|
}
|
|
outFile->close();
|
|
}
|
|
|
|
void MainWindow::toggleCapture()
|
|
{
|
|
allowCapture = !allowCapture;
|
|
if (allowCapture)
|
|
ui->btnCaptureToggle->setText("Suspend Capturing");
|
|
else
|
|
ui->btnCaptureToggle->setText("Restart Capturing");
|
|
|
|
emit suspendCapturing(!allowCapture);
|
|
}
|
|
|
|
void MainWindow::connectionStatusUpdated(int conns)
|
|
{
|
|
lbStatusConnected.setText(tr("Connected to ") + QString::number(conns) + tr(" buses"));
|
|
}
|
|
|
|
void MainWindow::updateFileStatus()
|
|
{
|
|
QString output;
|
|
if (model->rowCount() == 0)
|
|
{
|
|
output = tr("No file loaded");
|
|
}
|
|
else
|
|
{
|
|
if (loadedFileName.length() > 2)
|
|
{
|
|
output = loadedFileName + " loaded";
|
|
}
|
|
else
|
|
{
|
|
output = tr("No file loaded");
|
|
}
|
|
|
|
if (bDirty)
|
|
{
|
|
output += " (X)";
|
|
}
|
|
}
|
|
lbStatusFilename.setText(output);
|
|
}
|
|
|
|
CANFrameModel* MainWindow::getCANFrameModel()
|
|
{
|
|
return model;
|
|
}
|
|
|
|
void MainWindow::showSettingsDialog()
|
|
{
|
|
if (!settingsDialog)
|
|
{
|
|
settingsDialog = new MainSettingsDialog();
|
|
connect (settingsDialog, SIGNAL(updatedSettings()), this, SLOT(readUpdateableSettings()));
|
|
}
|
|
settingsDialog->show();
|
|
}
|
|
|
|
//always gets unfiltered list. You ask for the graphs so there is no need to send filtered frames
|
|
void MainWindow::showGraphingWindow()
|
|
{
|
|
if (!graphingWindow) {
|
|
graphingWindow = new GraphingWindow(model->getListReference());
|
|
connect(graphingWindow, SIGNAL(sendCenterTimeID(int32_t,double)), this, SLOT(gotCenterTimeID(int32_t,double)));
|
|
connect(this, SIGNAL(sendCenterTimeID(int32_t,double)), graphingWindow, SLOT(gotCenterTimeID(int32_t,double)));
|
|
}
|
|
|
|
if (flowViewWindow) //connect the two external windows together
|
|
{
|
|
connect(graphingWindow, SIGNAL(sendCenterTimeID(int32_t,double)), flowViewWindow, SLOT(gotCenterTimeID(int32_t,double)));
|
|
connect(flowViewWindow, SIGNAL(sendCenterTimeID(int32_t,double)), graphingWindow, SLOT(gotCenterTimeID(int32_t,double)));
|
|
}
|
|
graphingWindow->show();
|
|
}
|
|
|
|
void MainWindow::showTemporalGraphWindow()
|
|
{
|
|
//only create an instance of the object if we dont have one. Otherwise just display the existing one.
|
|
if (!temporalGraphWindow)
|
|
{
|
|
if (!useFiltered)
|
|
temporalGraphWindow = new TemporalGraphWindow(model->getListReference());
|
|
else
|
|
temporalGraphWindow = new TemporalGraphWindow(model->getFilteredListReference());
|
|
}
|
|
temporalGraphWindow->show();
|
|
}
|
|
|
|
void MainWindow::showFrameDataAnalysis()
|
|
{
|
|
//only create an instance of the object if we dont have one. Otherwise just display the existing one.
|
|
if (!frameInfoWindow)
|
|
{
|
|
if (!useFiltered)
|
|
frameInfoWindow = new FrameInfoWindow(model->getListReference());
|
|
else
|
|
frameInfoWindow = new FrameInfoWindow(model->getFilteredListReference());
|
|
}
|
|
frameInfoWindow->show();
|
|
}
|
|
|
|
void MainWindow::showISOInterpreterWindow()
|
|
{
|
|
if (!isoWindow)
|
|
{
|
|
if (!useFiltered)
|
|
isoWindow = new ISOTP_InterpreterWindow(model->getListReference());
|
|
else
|
|
isoWindow = new ISOTP_InterpreterWindow(model->getFilteredListReference());
|
|
}
|
|
isoWindow->show();
|
|
}
|
|
|
|
void MainWindow::showSnifferWindow()
|
|
{
|
|
if (!snifferWindow)
|
|
snifferWindow = new SnifferWindow(this);
|
|
snifferWindow->show();
|
|
}
|
|
|
|
void MainWindow::showBisectWindow()
|
|
{
|
|
if (!bisectWindow)
|
|
{
|
|
bisectWindow = new BisectWindow(model->getListReference());
|
|
}
|
|
bisectWindow->show();
|
|
}
|
|
|
|
void MainWindow::showFrameSenderWindow()
|
|
{
|
|
if (!frameSenderWindow)
|
|
{
|
|
if (!useFiltered)
|
|
frameSenderWindow = new FrameSenderWindow(model->getListReference());
|
|
else
|
|
frameSenderWindow = new FrameSenderWindow(model->getFilteredListReference());
|
|
}
|
|
frameSenderWindow->show();
|
|
}
|
|
|
|
void MainWindow::showPlaybackWindow()
|
|
{
|
|
if (!playbackWindow)
|
|
{
|
|
if (!useFiltered)
|
|
playbackWindow = new FramePlaybackWindow(model->getListReference());
|
|
else
|
|
playbackWindow = new FramePlaybackWindow(model->getFilteredListReference());
|
|
}
|
|
playbackWindow->show();
|
|
}
|
|
|
|
void MainWindow::showFirmwareUploaderWindow()
|
|
{
|
|
if (!firmwareUploaderWindow)
|
|
{
|
|
firmwareUploaderWindow = new FirmwareUploaderWindow(model->getListReference());
|
|
}
|
|
firmwareUploaderWindow->show();
|
|
}
|
|
|
|
void MainWindow::showComparisonWindow()
|
|
{
|
|
if (!comparatorWindow)
|
|
{
|
|
comparatorWindow = new FileComparatorWindow();
|
|
}
|
|
comparatorWindow->show();
|
|
}
|
|
|
|
void MainWindow::showSingleMultiWindow()
|
|
{
|
|
if (!discreteStateWindow)
|
|
{
|
|
discreteStateWindow = new DiscreteStateWindow(model->getListReference());
|
|
}
|
|
discreteStateWindow->show();
|
|
}
|
|
|
|
void MainWindow::showFuzzingWindow()
|
|
{
|
|
if (!fuzzingWindow)
|
|
{
|
|
fuzzingWindow = new FuzzingWindow(model->getListReference());
|
|
}
|
|
fuzzingWindow->show();
|
|
}
|
|
|
|
void MainWindow::showMCConfigWindow()
|
|
{
|
|
if (!motorctrlConfigWindow)
|
|
{
|
|
motorctrlConfigWindow = new MotorControllerConfigWindow(model->getListReference());
|
|
//connect(motorctrlConfigWindow, SIGNAL(sendCANFrame(const CANFrame*,int)), worker, SLOT(sendFrame(const CANFrame*,int)));
|
|
//connect(motorctrlConfigWindow, SIGNAL(sendFrameBatch(const QList<CANFrame>*)), worker, SLOT(sendFrameBatch(const QList<CANFrame>*)));
|
|
}
|
|
motorctrlConfigWindow->show();
|
|
}
|
|
|
|
void MainWindow::showUDSScanWindow()
|
|
{
|
|
if (!udsScanWindow)
|
|
{
|
|
udsScanWindow = new UDSScanWindow(model->getListReference());
|
|
}
|
|
udsScanWindow->show();
|
|
}
|
|
|
|
void MainWindow::showScriptingWindow()
|
|
{
|
|
if (!scriptingWindow)
|
|
{
|
|
scriptingWindow = new ScriptingWindow(model->getListReference());
|
|
}
|
|
scriptingWindow->show();
|
|
}
|
|
|
|
void MainWindow::showRangeWindow()
|
|
{
|
|
if (!rangeWindow)
|
|
{
|
|
rangeWindow = new RangeStateWindow(model->getListReference());
|
|
}
|
|
rangeWindow->show();
|
|
}
|
|
|
|
void MainWindow::showFuzzyScopeWindow()
|
|
{
|
|
//not done yet
|
|
}
|
|
|
|
void MainWindow::showFlowViewWindow()
|
|
{
|
|
if (!flowViewWindow)
|
|
{
|
|
if (!useFiltered)
|
|
flowViewWindow = new FlowViewWindow(model->getListReference());
|
|
else
|
|
flowViewWindow = new FlowViewWindow(model->getFilteredListReference());
|
|
connect(flowViewWindow, SIGNAL(sendCenterTimeID(int32_t,double)), this, SLOT(gotCenterTimeID(int32_t,double)));
|
|
connect(this, SIGNAL(sendCenterTimeID(int32_t,double)), flowViewWindow, SLOT(gotCenterTimeID(int32_t,double)));
|
|
}
|
|
|
|
if (graphingWindow)
|
|
{
|
|
connect(graphingWindow, SIGNAL(sendCenterTimeID(int32_t,double)), flowViewWindow, SLOT(gotCenterTimeID(int32_t,double)));
|
|
connect(flowViewWindow, SIGNAL(sendCenterTimeID(int32_t,double)), graphingWindow, SLOT(gotCenterTimeID(int32_t,double)));
|
|
}
|
|
|
|
flowViewWindow->show();
|
|
}
|
|
|
|
void MainWindow::showDBCFileWindow()
|
|
{
|
|
if (!dbcFileWindow)
|
|
{
|
|
dbcFileWindow = new DBCLoadSaveWindow(model->getListReference());
|
|
}
|
|
dbcFileWindow->show();
|
|
}
|
|
|
|
void MainWindow::showSignalViewer()
|
|
{
|
|
if (!signalViewerWindow)
|
|
{
|
|
if (!useFiltered)
|
|
signalViewerWindow = new SignalViewerWindow(model->getListReference());
|
|
else
|
|
signalViewerWindow = new SignalViewerWindow(model->getFilteredListReference());
|
|
}
|
|
signalViewerWindow->show();
|
|
}
|
|
|
|
void MainWindow::showConnectionSettingsWindow()
|
|
{
|
|
if (!connectionWindow)
|
|
{
|
|
connectionWindow = new ConnectionWindow();
|
|
}
|
|
connectionWindow->show();
|
|
}
|