#include "mainwindow.h" #include "ui_mainwindow.h" #include "can_structs.h" #include #include #include #include "canframemodel.h" #include "utility.h" #include "serialworker.h" /* This first order of business is to attempt to gain feature parity (roughly) with GVRET-PC so that this can replace it as a cross platform solution. Here are things yet to do - Ability to use programmatic frame sending interface (only I use it and it's complicated but helpful) Things that were planned for the GVRET-PC project but never completed. Single / Multi state - The goal is to find bits that change based on toggles or discrete state items (shifters, etc) Range state - Find things that range like accelerator pedal inputs, road speed, tach, etc fuzzy scope - Try to find potential places where a given value might be stored - offer guesses and the program tries to find candidates for you Things currently broken or in need of attention: (push these over to github issues as soon as you can) */ QString MainWindow::loadedFileName = ""; MainWindow *MainWindow::selfRef = NULL; MainWindow *MainWindow::getReference() { return selfRef; } MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); selfRef = this; this->setWindowTitle("Savvy CAN V" + QString::number(VERSION)); model = new CANFrameModel(); ui->canFramesView->setModel(model); QStringList headers; headers << "Timestamp" << "ID" << "Ext" << "Bus" << "Len" << "Data"; //model->setHorizontalHeaderLabels(headers); ui->canFramesView->setColumnWidth(0, 110); ui->canFramesView->setColumnWidth(1, 70); ui->canFramesView->setColumnWidth(2, 40); ui->canFramesView->setColumnWidth(3, 40); ui->canFramesView->setColumnWidth(4, 40); ui->canFramesView->setColumnWidth(5, 275); QHeaderView *HorzHdr = ui->canFramesView->horizontalHeader(); HorzHdr->setStretchLastSection(true); //causes the data column to automatically fill the tableview //enabling the below line kills performance in every way imaginable. Left here as a warning. Do not do this. //ui->canFramesView->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); ports = QSerialPortInfo::availablePorts(); for (int i = 0; i < ports.count(); i++) { ui->cbSerialPorts->addItem(ports[i].portName()); } ui->cbSpeed1->addItem(tr("Disabled")); ui->cbSpeed1->addItem(tr("125000")); ui->cbSpeed1->addItem(tr("250000")); ui->cbSpeed1->addItem(tr("500000")); ui->cbSpeed1->addItem(tr("1000000")); ui->cbSpeed1->addItem(tr("33333")); ui->cbSpeed2->addItem(tr("Disabled")); ui->cbSpeed2->addItem(tr("125000")); ui->cbSpeed2->addItem(tr("250000")); ui->cbSpeed2->addItem(tr("500000")); ui->cbSpeed2->addItem(tr("1000000")); ui->cbSpeed2->addItem(tr("33333")); worker = new SerialWorker(model); worker->moveToThread(&serialWorkerThread); connect(&serialWorkerThread, &QThread::finished, worker, &QObject::deleteLater); connect(this, &MainWindow::sendSerialPort, worker, &SerialWorker::setSerialPort, Qt::QueuedConnection); connect(worker, &SerialWorker::frameUpdateTick, this, &MainWindow::gotFrames, Qt::QueuedConnection); connect(this, &MainWindow::updateBaudRates, worker, &SerialWorker::updateBaudRates, Qt::QueuedConnection); connect(this, &MainWindow::sendCANFrame, worker, &SerialWorker::sendFrame, Qt::QueuedConnection); connect(worker, &SerialWorker::connectionSuccess, this, &MainWindow::connectionSucceeded, Qt::QueuedConnection); connect(worker, &SerialWorker::connectionFailure, this, &MainWindow::connectionFailed, Qt::QueuedConnection); connect(worker, &SerialWorker::deviceInfo, this, &MainWindow::gotDeviceInfo, Qt::QueuedConnection); connect(this, &MainWindow::closeSerialPort, worker, &SerialWorker::closeSerialPort, Qt::QueuedConnection); connect(this, &MainWindow::startFrameCapturing, worker, &SerialWorker::startFrameCapture); connect(this, &MainWindow::stopFrameCapturing, worker, &SerialWorker::stopFrameCapture); serialWorkerThread.start(); serialWorkerThread.setPriority(QThread::TimeCriticalPriority); graphingWindow = NULL; frameInfoWindow = NULL; playbackWindow = NULL; flowViewWindow = NULL; frameSenderWindow = NULL; dbcMainEditor = NULL; comparatorWindow = NULL; dbcHandler = new DBCHandler; bDirty = false; model->setDBCHandler(dbcHandler); connect(ui->btnConnect, SIGNAL(clicked(bool)), this, SLOT(connButtonPress())); connect(ui->actionOpen_Log_File, SIGNAL(triggered(bool)), this, SLOT(handleLoadFile())); connect(ui->actionGraph_Dta, SIGNAL(triggered(bool)), this, SLOT(showGraphingWindow())); connect(ui->actionFrame_Data_Analysis, SIGNAL(triggered(bool)), this, SLOT(showFrameDataAnalysis())); connect(ui->btnClearFrames, SIGNAL(clicked(bool)), this, SLOT(clearFrames())); connect(ui->actionSave_Log_File, SIGNAL(triggered(bool)), this, SLOT(handleSaveFile())); connect(ui->action_Playback, SIGNAL(triggered(bool)), this, SLOT(showPlaybackWindow())); connect(ui->btnBaudSet, SIGNAL(clicked(bool)), this, SLOT(changeBaudRates())); connect(ui->actionFlow_View, SIGNAL(triggered(bool)), this, SLOT(showFlowViewWindow())); connect(ui->action_Custom, SIGNAL(triggered(bool)), this, SLOT(showFrameSenderWindow())); connect(ui->actionLoad_DBC_File, SIGNAL(triggered(bool)), this, SLOT(handleLoadDBC())); connect(ui->actionSave_DBC_File, SIGNAL(triggered(bool)), this, SLOT(handleSaveDBC())); connect(ui->canFramesView, SIGNAL(clicked(QModelIndex)), this, SLOT(gridClicked(QModelIndex))); connect(ui->cbInterpret, SIGNAL(toggled(bool)), this, SLOT(interpretToggled(bool))); connect(ui->cbOverwrite, SIGNAL(toggled(bool)), this, SLOT(overwriteToggled(bool))); connect(ui->actionEdit_Messages_Signals, SIGNAL(triggered(bool)), this, SLOT(showDBCEditor())); connect(ui->btnCaptureToggle, SIGNAL(clicked(bool)), this, SLOT(toggleCapture())); connect(ui->actionExit_Application, SIGNAL(triggered(bool)), this, SLOT(exitApp())); connect(ui->actionFuzzy_Scope, SIGNAL(triggered(bool)), this, SLOT(showFuzzyScopeWindow())); connect(ui->actionRange_State, SIGNAL(triggered(bool)), this, SLOT(showRangeWindow())); connect(ui->actionSave_Decoded_Frames, SIGNAL(triggered(bool)), this, SLOT(handleSaveDecoded())); connect(ui->actionSingle_Multi_State, SIGNAL(triggered(bool)), this, SLOT(showSingleMultiWindow())); connect(ui->actionFile_Comparison, SIGNAL(triggered(bool)), this, SLOT(showComparisonWindow())); connect(ui->btnNormalize, SIGNAL(clicked(bool)), this, SLOT(normalizeTiming())); lbStatusConnected.setText(tr("Not connected")); updateFileStatus(); lbStatusDatabase.setText(tr("No DBC database loaded")); ui->statusBar->addWidget(&lbStatusConnected); ui->statusBar->addWidget(&lbStatusFilename); ui->statusBar->addWidget(&lbStatusDatabase); ui->lbFPS->setText("0"); ui->lbNumFrames->setText("0"); 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; model->addFrame(temp, true); normalRowHeight = ui->canFramesView->rowHeight(0); qDebug() << "normal row height = " << normalRowHeight; model->clearFrames(); } MainWindow::~MainWindow() { serialWorkerThread.quit(); serialWorkerThread.wait(); if (graphingWindow) { graphingWindow->close(); delete graphingWindow; } if (frameInfoWindow) { frameInfoWindow->close(); delete frameInfoWindow; } if (playbackWindow) { playbackWindow->close(); delete playbackWindow; } if (flowViewWindow) { flowViewWindow->close(); delete flowViewWindow; } if (frameSenderWindow) { frameSenderWindow->close(); delete frameSenderWindow; } if (comparatorWindow) { comparatorWindow->close(); delete comparatorWindow; } if (dbcMainEditor) { dbcMainEditor->close(); delete dbcMainEditor; } delete ui; delete dbcHandler; model->clearFrames(); delete model; } void MainWindow::closeEvent(QCloseEvent *event) { exitApp(); } void MainWindow::gridClicked(QModelIndex idx) { if (ui->canFramesView->rowHeight(idx.row()) > normalRowHeight) { ui->canFramesView->setRowHeight(idx.row(), normalRowHeight); } else { ui->canFramesView->resizeRowToContents(idx.row()); } } void MainWindow::interpretToggled(bool state) { model->setInterpetMode(state); } void MainWindow::overwriteToggled(bool state) { model->setOverwriteMode(state); } //most of the work is handled elsewhere. Need only to update the # of frames //and maybe auto scroll void MainWindow::gotFrames(int FPS, int framesSinceLastUpdate) { ui->lbNumFrames->setText(QString::number(model->rowCount())); if (ui->cbAutoScroll->isChecked()) ui->canFramesView->scrollToBottom(); ui->lbFPS->setText(QString::number(FPS)); if (framesSinceLastUpdate > 0) { bDirty = true; emit framesUpdated(framesSinceLastUpdate); //anyone care that frames were updated? } } 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())); } } void MainWindow::clearFrames() { ui->canFramesView->scrollToTop(); model->clearFrames(); ui->lbNumFrames->setText(QString::number(model->rowCount())); bDirty = false; loadedFileName = ""; updateFileStatus(); emit framesUpdated(-1); } void MainWindow::normalizeTiming() { model->normalizeTiming(); } void MainWindow::changeBaudRates() { int Speed1 = 0, Speed2 = 0; Speed1 = ui->cbSpeed1->currentText().toInt(); Speed2 = ui->cbSpeed2->currentText().toInt(); emit updateBaudRates(Speed1, Speed2); } void MainWindow::handleLoadFile() { QString filename; QFileDialog dialog(this); bool result = false; QStringList filters; filters.append(QString(tr("CRTD Logs (*.txt)"))); filters.append(QString(tr("GVRET Logs (*.csv)"))); filters.append(QString(tr("Generic ID/Data CSV (*.csv)"))); filters.append(QString(tr("BusMaster Log (*.log)"))); filters.append(QString(tr("Microchip Log (*.log)"))); dialog.setFileMode(QFileDialog::ExistingFile); dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); if (dialog.exec() == QDialog::Accepted) { filename = dialog.selectedFiles()[0]; ui->canFramesView->scrollToTop(); model->clearFrames(); if (dialog.selectedNameFilter() == filters[0]) result = FrameFileIO::loadCRTDFile(filename, model->getListReference()); if (dialog.selectedNameFilter() == filters[1]) result = FrameFileIO::loadNativeCSVFile(filename, model->getListReference()); if (dialog.selectedNameFilter() == filters[2]) result = FrameFileIO::loadGenericCSVFile(filename, model->getListReference()); if (dialog.selectedNameFilter() == filters[3]) result = FrameFileIO::loadLogFile(filename, model->getListReference()); if (dialog.selectedNameFilter() == filters[4]) result = FrameFileIO::loadMicrochipFile(filename, model->getListReference()); if (result) { QStringList fileList = filename.split('/'); loadedFileName = fileList[fileList.length() - 1]; model->sendRefresh(); ui->lbNumFrames->setText(QString::number(model->rowCount())); if (ui->cbAutoScroll->isChecked()) ui->canFramesView->scrollToBottom(); updateFileStatus(); emit framesUpdated(-2); } } } void MainWindow::handleSaveFile() { QString filename; QFileDialog dialog(this); bool result = false; QStringList filters; filters.append(QString(tr("CRTD Logs (*.txt)"))); filters.append(QString(tr("GVRET Logs (*.csv)"))); filters.append(QString(tr("Generic ID/Data CSV (*.csv)"))); filters.append(QString(tr("BusMaster Log (*.log)"))); filters.append(QString(tr("Microchip Log (*.log)"))); dialog.setFileMode(QFileDialog::AnyFile); dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); dialog.setAcceptMode(QFileDialog::AcceptSave); if (dialog.exec() == QDialog::Accepted) { QVector *frames = model->getListReference(); filename = dialog.selectedFiles()[0]; if (dialog.selectedNameFilter() == filters[0]) result = FrameFileIO::saveCRTDFile(filename, frames); if (dialog.selectedNameFilter() == filters[1]) result = FrameFileIO::saveNativeCSVFile(filename, frames); if (dialog.selectedNameFilter() == filters[2]) result = FrameFileIO::saveGenericCSVFile(filename, frames); if (dialog.selectedNameFilter() == filters[3]) result = FrameFileIO::saveLogFile(filename, frames); if (dialog.selectedNameFilter() == filters[4]) result = FrameFileIO::saveMicrochipFile(filename, frames); if (result) { QStringList fileList = filename.split('/'); loadedFileName = fileList[fileList.length() - 1]; updateFileStatus(); } } } void MainWindow::handleLoadDBC() { QString filename; QFileDialog dialog(this); QStringList filters; filters.append(QString(tr("DBC File (*.dbc)"))); 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. dbcHandler->loadDBCFile(filename); //dbcHandler->listDebugging(); QStringList fileList = filename.split('/'); lbStatusDatabase.setText(fileList[fileList.length() - 1] + tr(" loaded.")); } } void MainWindow::handleSaveDBC() { QString filename; QFileDialog dialog(this); QStringList filters; filters.append(QString(tr("DBC File (*.dbc)"))); dialog.setFileMode(QFileDialog::AnyFile); dialog.setNameFilters(filters); dialog.setViewMode(QFileDialog::Detail); dialog.setAcceptMode(QFileDialog::AcceptSave); if (dialog.exec() == QDialog::Accepted) { filename = dialog.selectedFiles()[0]; dbcHandler->saveDBCFile(filename); QStringList fileList = filename.split('/'); lbStatusDatabase.setText(fileList[fileList.length() - 1] + tr(" loaded.")); } } void MainWindow::handleSaveDecoded() { 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]; saveDecodedTextFile(filename); } } void MainWindow::saveDecodedTextFile(QString filename) { QFile *outFile = new QFile(filename); QVector *frames = model->getListReference(); 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: 0x") + QString::number(thisFrame.ID, 16).toUpper().rightJustified(8, '0'); 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 (int temp = 0; temp < thisFrame.len; temp++) { builderString += QString::number(thisFrame.data[temp], 16).toUpper().rightJustified(2, '0') + " "; } builderString += "\n"; outFile->write(builderString.toUtf8()); builderString = ""; if (dbcHandler != NULL) { DBC_MESSAGE *msg = dbcHandler->findMsgByID(thisFrame.ID); if (msg != NULL) { for (int j = 0; j < msg->msgSignals.length(); j++) { builderString.append("\t" + dbcHandler->processSignal(thisFrame, msg->msgSignals.at(j))); builderString.append("\n"); } } builderString.append("\n"); outFile->write(builderString.toUtf8()); } } outFile->close(); } void MainWindow::connButtonPress() { if (!isConnected) { for (int x = 0; x < ports.count(); x++) { if (ports.at(x).portName() == ui->cbSerialPorts->currentText()) { emit sendSerialPort(&ports[x]); lbStatusConnected.setText(tr("Attempting to connect to port ") + ports[x].portName()); return; } } } else { emit closeSerialPort(); isConnected = false; lbStatusConnected.setText(tr("Not Connected")); ui->btnConnect->setText(tr("Connect to GVRET")); } } void MainWindow::toggleCapture() { allowCapture = !allowCapture; if (allowCapture) { ui->btnCaptureToggle->setText("Suspend Capturing"); emit startFrameCapturing(); } else { ui->btnCaptureToggle->setText("Restart Capturing"); emit stopFrameCapturing(); } } void MainWindow::connectionSucceeded(int baud0, int baud1) { lbStatusConnected.setText(tr("Connected to GVRET")); //finally, find the baud rate in the list of rates or //add it to the bottom if needed (that'll be weird though... int idx = ui->cbSpeed1->findText(QString::number(baud0)); if (idx > -1) ui->cbSpeed1->setCurrentIndex(idx); else { ui->cbSpeed1->addItem(QString::number(baud0)); ui->cbSpeed1->setCurrentIndex(ui->cbSpeed1->count() - 1); } idx = ui->cbSpeed2->findText(QString::number(baud1)); if (idx > -1) ui->cbSpeed2->setCurrentIndex(idx); else { ui->cbSpeed2->addItem(QString::number(baud1)); ui->cbSpeed2->setCurrentIndex(ui->cbSpeed2->count() - 1); } //ui->btnConnect->setEnabled(false); ui->btnConnect->setText(tr("Disconnect from GVRET")); isConnected = true; } void MainWindow::connectionFailed() { lbStatusConnected.setText(tr("Failed to connect!")); } 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); } void MainWindow::gotDeviceInfo(int build, int swCAN) { QString str = tr("Connected to GVRET ") + QString::number(build); if (swCAN == 1) str += "(SW)"; lbStatusConnected.setText(str); } void MainWindow::showGraphingWindow() { if (!graphingWindow) graphingWindow = new GraphingWindow(model->getListReference()); graphingWindow->show(); } void MainWindow::showFrameDataAnalysis() { //only create an instance of the object if we dont have one. Otherwise just display the existing one. if (!frameInfoWindow) { frameInfoWindow = new FrameInfoWindow(model->getListReference()); } frameInfoWindow->show(); } void MainWindow::showFrameSenderWindow() { if (!frameSenderWindow) frameSenderWindow = new FrameSenderWindow(model->getListReference()); frameSenderWindow->show(); } void MainWindow::showPlaybackWindow() { if (!playbackWindow) { playbackWindow = new FramePlaybackWindow(model->getListReference(), worker); //connect signal to signal to pass the signal through to whoever is handling the slot //connect(playbackWindow, SIGNAL(sendCANFrame(const CANFrame*,int)), this, SIGNAL(sendCANFrame(const CANFrame*,int))); } playbackWindow->show(); } void MainWindow::showComparisonWindow() { if (!comparatorWindow) { comparatorWindow = new FileComparatorWindow(); } comparatorWindow->show(); } void MainWindow::showSingleMultiWindow() { //not done yet } void MainWindow::showRangeWindow() { //not done yet } void MainWindow::showFuzzyScopeWindow() { //not done yet } void MainWindow::exitApp() { if (graphingWindow) graphingWindow->close(); if (frameInfoWindow) frameInfoWindow->close(); if (playbackWindow) playbackWindow->close(); if (flowViewWindow) flowViewWindow->close(); if (frameSenderWindow) frameSenderWindow->close(); if (dbcMainEditor) dbcMainEditor->close(); this->close(); } void MainWindow::showFlowViewWindow() { if (!flowViewWindow) { flowViewWindow = new FlowViewWindow(model->getListReference()); } flowViewWindow->show(); } void MainWindow::showDBCEditor() { if (!dbcMainEditor) { dbcMainEditor = new DBCMainEditor(dbcHandler); } dbcMainEditor->show(); }