From 77b6cae6fc43329b70afec7cd2e04f796132647d Mon Sep 17 00:00:00 2001 From: Chris Whiteford Date: Fri, 15 Dec 2023 11:14:46 -0500 Subject: [PATCH 01/27] Support higher time resolution CANserver format CANserver firmware 2.2.0 will be adding a new logging format that support us resolution for timestamps of received frames. --- framefileio.cpp | 81 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 28 deletions(-) diff --git a/framefileio.cpp b/framefileio.cpp index 1922431e..5ea46caa 100644 --- a/framefileio.cpp +++ b/framefileio.cpp @@ -4791,7 +4791,7 @@ bool FrameFileIO::isCANServerFile(QString filename) { //qDebug() << "header: " << headerData; //We have enough bytes to make up our header. Lets check if it matches - if (headerData == "CANSERVER_v2_CANSERVER") + if (headerData == "CANSERVER_v2_CANSERVER" || headerData == "CANSERVER_v3_CANSERVER") { isMatch = true; } @@ -4833,6 +4833,7 @@ bool FrameFileIO::loadCANServerFile(QString filename, QVector* frames) //Skip the header bool fileIsGood = false; + uint8_t logVersion = 1; QByteArray headerData = inFile->read(22); if (headerData.length() == 22) { @@ -4840,6 +4841,12 @@ bool FrameFileIO::loadCANServerFile(QString filename, QVector* frames) //We have enough bytes to make up our header. Lets check if it matches if (headerData == "CANSERVER_v2_CANSERVER") { + logVersion = 1; + fileIsGood = true; + } + else if (headerData == "CANSERVER_v3_CANSERVER") + { + logVersion = 2; fileIsGood = true; } } @@ -4864,7 +4871,13 @@ bool FrameFileIO::loadCANServerFile(QString filename, QVector* frames) { if (possibleHeaderData == "ANSERVER_v2_CANSERVER" ) { - //We found another header. Lets just ignore things and move on + //We found another header (in this case a version 1 format). Lets just ignore things and move on + logVersion = 1; + } + else if (possibleHeaderData == "ANSERVER_v3_CANSERVER" ) + { + //We found another header (in this case a version 2 format). Lets just ignore things and move on + logVersion = 2; } else { @@ -4873,10 +4886,10 @@ bool FrameFileIO::loadCANServerFile(QString filename, QVector* frames) } } } - else if (data[0] == 0xCD) + else if ((logVersion == 1 && data[0] == 0xCD) || (logVersion == 2 && data[0] == 0xD0)) { - //Log mark message - + //Log mark message (the same format in v1 or v2. Just a different identifier byte) + //Read in the size of the mark uint8_t markSize[1]; inFile->read((char*)&markSize, 1); @@ -4897,9 +4910,9 @@ bool FrameFileIO::loadCANServerFile(QString filename, QVector* frames) frames->append(markFrame); */ } - else if (data[0] == 0xCE) + else if ((logVersion == 1 && data[0] == 0xCE) || (logVersion == 2 && data[0] == 0xA0)) { - //Timesync message + //Timesync message (v1 log format) framedata_U timesyncvalue; ::memset(timesyncvalue.data, 0, sizeof(timesyncvalue.data)); inFile->read(timesyncvalue.data, 8); @@ -4908,39 +4921,54 @@ bool FrameFileIO::loadCANServerFile(QString filename, QVector* frames) //qDebug() << "Found a time sync message: " << timesyncvalue.u64; timeStampBase = timesyncvalue.u64; } - else if (data[0] == 0xCF) + else if ((logVersion == 1 && data[0] == 0xCF) || (logVersion == 2 && (data[0] & 0xF0) == 0xB0)) { - if (frameCounter++ > 100) + // Can frame format (either a v1 or a v2) + if (frameCounter++ > 10000) { qApp->processEvents(); frameCounter = 0; } - - //CAN Frame + uint8_t frameheaderdata[5]; inFile->read((char*)frameheaderdata, 5); - //The frame time offset is how long since the last time sync message (now in ms) this frame is - uint8_t byte1 = frameheaderdata[0]; - uint8_t byte2 = frameheaderdata[1]; - uint16_t frametimeoffset = ((uint16_t)byte2 << 8) | byte1; - - byte1 = frameheaderdata[2]; - byte2 = frameheaderdata[3]; - uint16_t messageId = ((uint16_t)byte2 << 8) | byte1; - - uint8_t framelength = frameheaderdata[4] & 0x0F; - uint8_t busid = frameheaderdata[4] >> 4; + uint32_t frametimeoffset = 0; + if (logVersion == 1) + { + //The frame time offset is how long since the last time sync message (now in ms) this frame is + frametimeoffset = ((uint16_t)frameheaderdata[1] << 8) | frameheaderdata[0]; + frametimeoffset *= 1000; + } + else if (logVersion == 2) + { + frametimeoffset = (data[0] & 0x0F) + (frameheaderdata[0] << 4) + (frameheaderdata[1] << 12) + (((frameheaderdata[2] & 0xF8) >> 3) << 20); + } + + uint16_t messageId = 0; + if (logVersion == 1) + { + messageId = ((uint16_t)frameheaderdata[3] << 8) | frameheaderdata[2]; + } + else if (logVersion == 2) + { + messageId = ((frameheaderdata[2] & 0x07) << 8) + (frameheaderdata[3] & 0xFF); + } + + uint8_t framelength = (frameheaderdata[4] & 0x0F); + uint8_t busid = (frameheaderdata[4] & 0xF0) >> 4; framelength = framelength > 8 ? 8 : framelength; - //Now that we have a length lets read those bytes ::memset(framedata.data, 0, sizeof(framedata.data)); - inFile->read(framedata.data, framelength); + + if (framelength > 0) + { + inFile->read(framedata.data, framelength); + } //Setup the frame object and populate it - CANFrame thisFrame; thisFrame.setFrameType(QCanBusFrame::DataFrame); @@ -4950,11 +4978,8 @@ bool FrameFileIO::loadCANServerFile(QString filename, QVector* frames) uint64_t frameTime = timeStampBase; uint64_t frameoffset = frametimeoffset; - frameoffset *= 1000; frameTime += frameoffset; - //lastFrameTime = frameTime; - thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, frameTime)); QByteArray bytes(framelength,0); From f911ab00d5309b3cd904d4f0c8513f7c2c147d65 Mon Sep 17 00:00:00 2001 From: Chris Whiteford Date: Fri, 15 Dec 2023 11:42:31 -0500 Subject: [PATCH 02/27] Calculate the default row height correctly Ensure that the temporary row that is added to the view on startup is sized correctly before storing that height as the default. Then use the calculated row height to set the default height for all added rows --- mainwindow.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mainwindow.cpp b/mainwindow.cpp index b2718bd6..8b14149f 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -204,6 +204,7 @@ MainWindow::MainWindow(QWidget *parent) : temp.isReceived = true; temp.setTimeStamp(QCanBusFrame::TimeStamp(0, 100000000)); model->addFrame(temp, true); + ui->canFramesView->resizeRowToContents(0); // Resize the row to fit the contents so we get a proper height value qApp->processEvents(); tickGUIUpdate(); //force a GUI refresh so that the row exists to measure normalRowHeight = ui->canFramesView->rowHeight(0); @@ -211,6 +212,8 @@ MainWindow::MainWindow(QWidget *parent) : qDebug() << "normal row height = " << normalRowHeight; model->clearFrames(); + ui->canFramesView->verticalHeader()->setDefaultSectionSize(normalRowHeight); // Set the default height for all rows to the height that was calculated + //connect(CANConManager::getInstance(), CANConManager::connectionStatusUpdated, this, MainWindow::connectionStatusUpdated); connect(CANConManager::getInstance(), SIGNAL(connectionStatusUpdated(int)), this, SLOT(connectionStatusUpdated(int))); From c3521449fd662bb85c4bfc92acf64e9cb06b55e0 Mon Sep 17 00:00:00 2001 From: Charles Galpin Date: Sun, 24 Dec 2023 10:45:56 -0500 Subject: [PATCH 03/27] add save as, reload, and save log buttons to scriptingwindow --- .vscode/settings.json | 8 ++++ scriptingwindow.cpp | 89 +++++++++++++++++++++++++++++++++++++++++++ scriptingwindow.h | 3 ++ ui/scriptingwindow.ui | 42 +++++++++++++++++--- 4 files changed, 137 insertions(+), 5 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..936eb860 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "files.associations": { + "thread": "cpp", + "*.tcc": "cpp", + "chrono": "cpp", + "algorithm": "cpp" + } +} \ No newline at end of file diff --git a/scriptingwindow.cpp b/scriptingwindow.cpp index 1f24dee6..ea254c37 100644 --- a/scriptingwindow.cpp +++ b/scriptingwindow.cpp @@ -41,8 +41,11 @@ ScriptingWindow::ScriptingWindow(const QVector *frames, QWidget *paren connect(ui->btnRecompile, &QAbstractButton::pressed, this, &ScriptingWindow::recompileScript); connect(ui->btnRemoveScript, &QAbstractButton::pressed, this, &ScriptingWindow::deleteCurrentScript); connect(ui->btnRevertScript, &QAbstractButton::pressed, this, &ScriptingWindow::revertScript); + connect(ui->btnReloadScript, &QAbstractButton::pressed, this, &ScriptingWindow::reloadScript); connect(ui->btnSaveScript, &QAbstractButton::pressed, this, &ScriptingWindow::saveScript); + connect(ui->btnSaveAsScript, &QAbstractButton::pressed, this, &ScriptingWindow::saveAsScript); connect(ui->btnClearLog, &QAbstractButton::pressed, this, &ScriptingWindow::clickedLogClear); + connect(ui->btnSaveLog, &QAbstractButton::pressed, this, &ScriptingWindow::saveLog); connect(ui->listLoadedScripts, &QListWidget::currentRowChanged, this, &ScriptingWindow::changeCurrentScript); connect(ui->tableVariables, SIGNAL(cellChanged(int,int)), this, SLOT(updatedValue(int, int))); @@ -301,6 +304,21 @@ void ScriptingWindow::refreshSourceWindow() } void ScriptingWindow::saveScript() +{ + QFile *outFile = new QFile(currentScript->filePath); + + if (!outFile->open(QIODevice::WriteOnly | QIODevice::Text)) + { + delete outFile; + return; + } + outFile->write(editor->toPlainText().toUtf8()); + currentScript->scriptText = editor->toPlainText(); + outFile->close(); + delete outFile; +} + +void ScriptingWindow::saveAsScript() { QString filename; QFileDialog dialog(this); @@ -358,6 +376,38 @@ void ScriptingWindow::revertScript() } } +void ScriptingWindow::reloadScript() +{ + QMessageBox msgBox; + msgBox.setText("Are you sure you'd like to reload from disk?"); + msgBox.setInformativeText("Really do it?"); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::Cancel); + int ret = msgBox.exec(); + switch (ret) + { + case QMessageBox::Yes: + { + // get the latest version from disk and set in the editor/state + QFile scriptFile(currentScript->filePath); + if (scriptFile.open(QIODevice::ReadOnly | QIODevice::Text)) + { + QString contents = scriptFile.readAll(); + scriptFile.close(); + editor->setPlainText(contents); + currentScript->scriptText = contents; + currentScript->compileScript(); + } + break; + } + case QMessageBox::No: + break; + default: + // should never be reached + break; + } +} + void ScriptingWindow::recompileScript() { if (currentScript) @@ -373,6 +423,45 @@ void ScriptingWindow::clickedLogClear() elapsedTime.start(); } +void ScriptingWindow::saveLog() +{ + QString filename; + QFileDialog dialog(this); + QSettings settings; + + QStringList filters; + filters.append(QString(tr("Log File (*.log)"))); + + dialog.setDirectory(settings.value("ScriptingWindow/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 += ".log"; + if (dialog.selectedNameFilter() == filters[0]) + { + QFile *outFile = new QFile(filename); + + if (!outFile->open(QIODevice::WriteOnly | QIODevice::Text)) + { + delete outFile; + return; + } + int c = ui->listLog ->count(); + for (int row = 0; row < c; row++) { + outFile->write(ui->listLog->item(row)->data(Qt::DisplayRole).toString().toUtf8() + "\n"); + } + outFile->close(); + delete outFile; + settings.setValue("ScriptingWindow/LoadSaveDirectory", dialog.directory().path()); + } + } +} + void ScriptingWindow::log(QString text) { ScriptContainer *cont = qobject_cast(sender()); diff --git a/scriptingwindow.h b/scriptingwindow.h index 156a72e2..55770998 100644 --- a/scriptingwindow.h +++ b/scriptingwindow.h @@ -37,7 +37,9 @@ private slots: void deleteCurrentScript(); void refreshSourceWindow(); void saveScript(); + void saveAsScript(); void revertScript(); + void reloadScript(); void recompileScript(); void changeCurrentScript(); void newFrames(const CANConnection*, const QVector&); @@ -49,6 +51,7 @@ private slots: void closeEvent(QCloseEvent *event); void readSettings(); void writeSettings(); + void saveLog(); bool eventFilter(QObject *obj, QEvent *event); Ui::ScriptingWindow *ui; diff --git a/ui/scriptingwindow.ui b/ui/scriptingwindow.ui index 35c0b9ed..5623804f 100644 --- a/ui/scriptingwindow.ui +++ b/ui/scriptingwindow.ui @@ -108,6 +108,13 @@ + + + + Sa&ve As + + + @@ -115,6 +122,13 @@ + + + + R&eload + + + @@ -151,11 +165,26 @@ - - - Clear Log &Window - - + + + Qt::AlignCenter + + 10, 0, 10, 0 + + + + Clear Log &Window + + + + + + + Save L&og + + + + @@ -169,10 +198,13 @@ listLoadedScripts tableVariables btnSaveScript + btnSaveAsScript btnRevertScript + btnReloadScript btnRecompile cbAutoScroll btnClearLog + btnSaveLog From 43821feb2ba947c08ea0215e7b271c413e355760 Mon Sep 17 00:00:00 2001 From: Charles Galpin Date: Sun, 24 Dec 2023 10:55:37 -0500 Subject: [PATCH 04/27] exclude .vscode from git --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6cb0bf65..13068cdd 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,4 @@ Makefile SavvyCAN.pro.qtds .xcode SavvyCAN.xcodeproj +.vscode From 0a1a72fe7fc405bb3e72c17603ea304dfc18152d Mon Sep 17 00:00:00 2001 From: Charles Galpin Date: Sun, 24 Dec 2023 10:56:58 -0500 Subject: [PATCH 05/27] Delete .vscode/settings.json unintentional commit --- .vscode/settings.json | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 936eb860..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "files.associations": { - "thread": "cpp", - "*.tcc": "cpp", - "chrono": "cpp", - "algorithm": "cpp" - } -} \ No newline at end of file From b77b876b46268073e54daab41f6816c9a97c9f0b Mon Sep 17 00:00:00 2001 From: Miklos Marton Date: Thu, 18 Jan 2024 16:53:13 +0100 Subject: [PATCH 06/27] Do not add extra newline on the DBC decoded message texts --- dbc/dbc_classes.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dbc/dbc_classes.cpp b/dbc/dbc_classes.cpp index 09b7cb1b..da44f804 100644 --- a/dbc/dbc_classes.cpp +++ b/dbc/dbc_classes.cpp @@ -76,12 +76,16 @@ QString DBC_SIGNAL::processSignalTree(const CANFrame &frame) if (sig->processAsText(frame, sigString)) { qDebug() << "Returned value: " << sigString; + if (!build.isEmpty() && !sigString.isEmpty()) + build.append("\n"); build.append(sigString); - build.append("\n"); if (sig->isMultiplexor) { qDebug() << "Spelunkin!"; - build.append(sig->processSignalTree(frame)); + auto subTreeString = sig->processSignalTree(frame); + if (!build.isEmpty() && !subTreeString.isEmpty()) + build.append("\n"); + build.append(subTreeString); } } } From 3bab2ec63af0acf718fd34c2c5d56d5f826cb1da Mon Sep 17 00:00:00 2001 From: Alexey Esaulenko Date: Thu, 18 Jan 2024 12:42:12 +0400 Subject: [PATCH 07/27] Update required C++ standard to C++17 (needed by [[fallthrough]] statements) --- SavvyCAN.pro | 2 +- connections/lawicel_serial.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/SavvyCAN.pro b/SavvyCAN.pro index 3b782a9d..def354a4 100644 --- a/SavvyCAN.pro +++ b/SavvyCAN.pro @@ -8,7 +8,7 @@ QT = core gui printsupport qml serialbus serialport widgets help network opengl CONFIG(release, debug|release):DEFINES += QT_NO_DEBUG_OUTPUT -CONFIG += c++11 +CONFIG += c++17 CONFIG += NO_UNIT_TESTS DEFINES += QCUSTOMPLOT_USE_OPENGL diff --git a/connections/lawicel_serial.cpp b/connections/lawicel_serial.cpp index 0b502f88..34b4fd4f 100644 --- a/connections/lawicel_serial.cpp +++ b/connections/lawicel_serial.cpp @@ -551,6 +551,7 @@ void LAWICELSerial::readSerialData() break; case 'b': buildFrame.setBitrateSwitch(true); //BRS enabled + [[fallthrough]]; case 'd': //standard fd frame, BRS disabled //tIIILDD buildFrame.setFlexibleDataRateFormat(true); @@ -581,6 +582,7 @@ void LAWICELSerial::readSerialData() break; case 'B': buildFrame.setBitrateSwitch(true); //BRS enabled + [[fallthrough]]; case 'D': //extended fd frame //TIIIIIIIILDD. buildFrame.setFlexibleDataRateFormat(true); From 87b87745c466f45e3e75dd1b0e88c2ffc14b6245 Mon Sep 17 00:00:00 2001 From: Alexey Esaulenko Date: Thu, 18 Jan 2024 10:57:27 +0400 Subject: [PATCH 08/27] FirmwareUploader: fix memory leaks --- firmwareuploaderwindow.cpp | 73 ++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/firmwareuploaderwindow.cpp b/firmwareuploaderwindow.cpp index 05028be1..0a6be758 100644 --- a/firmwareuploaderwindow.cpp +++ b/firmwareuploaderwindow.cpp @@ -133,38 +133,38 @@ void FirmwareUploaderWindow::timerElapsed() void FirmwareUploaderWindow::sendFirmwareChunk() { - CANFrame *output = new CANFrame; + CANFrame output; int firmwareLocation = currentSendingPosition * 4; int xorByte = 0; - output->setExtendedFrameFormat(false); + output.setExtendedFrameFormat(false); QByteArray bytes(7,0); - output->bus = bus; - output->setFrameId(baseAddress + 0x16); - output->payload()[0] = currentSendingPosition & 0xFF; - output->payload()[1] = (currentSendingPosition >> 8) & 0xFF; - output->payload()[2] = firmwareData[firmwareLocation++]; - output->payload()[3] = firmwareData[firmwareLocation++]; - output->payload()[4] = firmwareData[firmwareLocation++]; - output->payload()[5] = firmwareData[firmwareLocation++]; - for (int i = 0; i < 6; i++) xorByte = xorByte ^ static_cast(output->payload()[i]); - output->payload()[6] = xorByte; - output->setPayload(bytes); - CANConManager::getInstance()->sendFrame(*output); + output.bus = bus; + output.setFrameId(baseAddress + 0x16); + output.payload()[0] = currentSendingPosition & 0xFF; + output.payload()[1] = (currentSendingPosition >> 8) & 0xFF; + output.payload()[2] = firmwareData[firmwareLocation++]; + output.payload()[3] = firmwareData[firmwareLocation++]; + output.payload()[4] = firmwareData[firmwareLocation++]; + output.payload()[5] = firmwareData[firmwareLocation++]; + for (int i = 0; i < 6; i++) xorByte ^= static_cast(output.payload()[i]); + output.payload()[6] = xorByte; + output.setPayload(bytes); + CANConManager::getInstance()->sendFrame(output); timer->start(); } void FirmwareUploaderWindow::sendFirmwareEnding() { - CANFrame *output = new CANFrame; - output->setExtendedFrameFormat(false); - output->bus = bus; + CANFrame output; + output.setExtendedFrameFormat(false); + output.bus = bus; QByteArray bytes(4,0); - output->setFrameId(baseAddress + 0x30); - output->payload()[3] = 0xC0; - output->payload()[2] = 0xDE; - output->payload()[1] = 0xFA; - output->payload()[0] = 0xDE; - output->setPayload(bytes); + output.setFrameId(baseAddress + 0x30); + output.payload()[3] = 0xC0; + output.payload()[2] = 0xDE; + output.payload()[1] = 0xFA; + output.payload()[0] = 0xDE; + output.setPayload(bytes); //sendCANFrame(output, bus); } @@ -182,12 +182,12 @@ void FirmwareUploaderWindow::handleStartStopTransfer() qDebug() << "Base address: " + QString::number(baseAddress); CANConManager::getInstance()->addTargettedFrame(bus, baseAddress + 0x10, 0x7FF, this); CANConManager::getInstance()->addTargettedFrame(bus, baseAddress + 0x20, 0x7FF, this); - CANFrame *output = new CANFrame; - output->setExtendedFrameFormat(false); + CANFrame output; + output.setExtendedFrameFormat(false); QByteArray bytes(8,0); - output->bus = bus; - output->setFrameId(baseAddress); - output->setFrameType(QCanBusFrame::DataFrame); + output.bus = bus; + output.setFrameId(baseAddress); + output.setFrameType(QCanBusFrame::DataFrame); bytes[0] = 0xEF; bytes[1] = 0xBE; @@ -197,8 +197,8 @@ void FirmwareUploaderWindow::handleStartStopTransfer() bytes[5] = (token >> 8) & 0xFF; bytes[6] = (token >> 16) & 0xFF; bytes[7] = (token >> 24) & 0xFF; - output->setPayload(bytes); - CANConManager::getInstance()->sendFrame(*output); + output.setPayload(bytes); + CANConManager::getInstance()->sendFrame(output); } else //stop anything in process { @@ -209,7 +209,6 @@ void FirmwareUploaderWindow::handleStartStopTransfer() void FirmwareUploaderWindow::handleLoadFile() { - QString filename; QFileDialog dialog; QStringList filters; @@ -221,7 +220,7 @@ void FirmwareUploaderWindow::handleLoadFile() if (dialog.exec() == QDialog::Accepted) { - filename = dialog.selectedFiles()[0]; + QString filename = dialog.selectedFiles().constFirst(); loadBinaryFile(filename); } @@ -232,22 +231,20 @@ void FirmwareUploaderWindow::loadBinaryFile(QString filename) if (transferInProgress) handleStartStopTransfer(); - QFile *inFile = new QFile(filename); + QFile inFile(filename); - if (!inFile->open(QIODevice::ReadOnly)) + if (!inFile.open(QIODevice::ReadOnly)) { - delete inFile; return; } - firmwareData = inFile->readAll(); + firmwareData = inFile.readAll(); currentSendingPosition = 0; firmwareSize = firmwareData.length(); updateProgress(); - inFile->close(); - delete inFile; + inFile.close(); } From 9e1d10b467f73b5c8ac861729775034a9d194586 Mon Sep 17 00:00:00 2001 From: Alexey Esaulenko Date: Thu, 18 Jan 2024 16:10:37 +0400 Subject: [PATCH 09/27] FirmwareUpdater: fix populating CAN frames frame.payload() returns a copy (!) of data, so assignig to it has no effect --- firmwareuploaderwindow.cpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/firmwareuploaderwindow.cpp b/firmwareuploaderwindow.cpp index 0a6be758..bc234e24 100644 --- a/firmwareuploaderwindow.cpp +++ b/firmwareuploaderwindow.cpp @@ -140,14 +140,14 @@ void FirmwareUploaderWindow::sendFirmwareChunk() QByteArray bytes(7,0); output.bus = bus; output.setFrameId(baseAddress + 0x16); - output.payload()[0] = currentSendingPosition & 0xFF; - output.payload()[1] = (currentSendingPosition >> 8) & 0xFF; - output.payload()[2] = firmwareData[firmwareLocation++]; - output.payload()[3] = firmwareData[firmwareLocation++]; - output.payload()[4] = firmwareData[firmwareLocation++]; - output.payload()[5] = firmwareData[firmwareLocation++]; - for (int i = 0; i < 6; i++) xorByte ^= static_cast(output.payload()[i]); - output.payload()[6] = xorByte; + bytes[0] = currentSendingPosition & 0xFF; + bytes[1] = (currentSendingPosition >> 8) & 0xFF; + bytes[2] = firmwareData[firmwareLocation++]; + bytes[3] = firmwareData[firmwareLocation++]; + bytes[4] = firmwareData[firmwareLocation++]; + bytes[5] = firmwareData[firmwareLocation++]; + for (int i = 0; i < 6; i++) xorByte ^= static_cast(bytes[i]); + bytes[6] = xorByte; output.setPayload(bytes); CANConManager::getInstance()->sendFrame(output); timer->start(); @@ -160,10 +160,10 @@ void FirmwareUploaderWindow::sendFirmwareEnding() output.bus = bus; QByteArray bytes(4,0); output.setFrameId(baseAddress + 0x30); - output.payload()[3] = 0xC0; - output.payload()[2] = 0xDE; - output.payload()[1] = 0xFA; - output.payload()[0] = 0xDE; + bytes[3] = (char)0xC0; + bytes[2] = (char)0xDE; + bytes[1] = (char)0xFA; + bytes[0] = (char)0xDE; output.setPayload(bytes); //sendCANFrame(output, bus); } @@ -189,10 +189,10 @@ void FirmwareUploaderWindow::handleStartStopTransfer() output.setFrameId(baseAddress); output.setFrameType(QCanBusFrame::DataFrame); - bytes[0] = 0xEF; - bytes[1] = 0xBE; - bytes[2] = 0xAD; - bytes[3] = 0xDE; + bytes[0] = (char)0xEF; + bytes[1] = (char)0xBE; + bytes[2] = (char)0xAD; + bytes[3] = (char)0xDE; bytes[4] = token & 0xFF; bytes[5] = (token >> 8) & 0xFF; bytes[6] = (token >> 16) & 0xFF; From 78b34afc9acb0be84e6596f452cd2c907e2af1b2 Mon Sep 17 00:00:00 2001 From: Alexey Esaulenko Date: Thu, 18 Jan 2024 11:00:15 +0400 Subject: [PATCH 10/27] Minor: fix Frame Info window layout (same Widget names produced warning during compilation) --- ui/frameinfowindow.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/frameinfowindow.ui b/ui/frameinfowindow.ui index 0b613946..375c63e7 100644 --- a/ui/frameinfowindow.ui +++ b/ui/frameinfowindow.ui @@ -120,7 +120,7 @@ - + From 578894600a75a0592285523bd7f76dbf9c0cf2ac Mon Sep 17 00:00:00 2001 From: Alexey Esaulenko Date: Thu, 18 Jan 2024 11:29:47 +0400 Subject: [PATCH 11/27] Access to the CANBus parameters only through setters/getters Also removed explicit copy constructor, in such trivial cases compiler can create all needed copy/move stubs automatically --- connections/canbus.cpp | 27 +++++++++-------------- connections/canbus.h | 33 +++++++++++++++-------------- connections/serialbusconnection.cpp | 15 ++++++------- 3 files changed, 34 insertions(+), 41 deletions(-) diff --git a/connections/canbus.cpp b/connections/canbus.cpp index 3779e86e..0623baa8 100644 --- a/connections/canbus.cpp +++ b/connections/canbus.cpp @@ -13,21 +13,13 @@ CANBus::CANBus() } -CANBus::CANBus(const CANBus& pBus) : - speed(pBus.speed), - listenOnly(pBus.listenOnly), - singleWire(pBus.singleWire), - active(pBus.active), - canFD(pBus.canFD), - dataRate(pBus.dataRate){} - - bool CANBus::operator==(const CANBus& bus) const{ return speed == bus.speed && listenOnly == bus.listenOnly && singleWire == bus.singleWire && active == bus.active && - canFD == bus.canFD; + canFD == bus.canFD && + dataRate == bus.dataRate; } void CANBus::setSpeed(int newSpeed){ @@ -55,11 +47,11 @@ void CANBus::setCanFD(bool mode){ canFD = mode; } -int CANBus::getSpeed(){ +int CANBus::getSpeed() const { return speed; } -int CANBus::getDataRate(){ +int CANBus::getDataRate() const { return dataRate; } @@ -68,29 +60,30 @@ void CANBus::setDataRate(int newSpeed){ dataRate = newSpeed; } -bool CANBus::isListenOnly(){ +bool CANBus::isListenOnly() const { return listenOnly; } -bool CANBus::isSingleWire(){ +bool CANBus::isSingleWire() const { return singleWire; } -bool CANBus::isActive(){ +bool CANBus::isActive() const { return active; } -bool CANBus::isCanFD(){ +bool CANBus::isCanFD() const { return canFD; } -QDataStream& operator<<( QDataStream & pStream, const CANBus& pCanBus ) +QDataStream& operator<<(QDataStream & pStream, const CANBus& pCanBus) { pStream << pCanBus.speed; pStream << pCanBus.listenOnly; pStream << pCanBus.singleWire; pStream << pCanBus.active; + // FIXME CANFD settings missing return pStream; } diff --git a/connections/canbus.h b/connections/canbus.h index ff3d8878..61fc122b 100644 --- a/connections/canbus.h +++ b/connections/canbus.h @@ -5,13 +5,6 @@ class CANBus { -public: - CANBus(); - CANBus(const CANBus&); - bool operator==(const CANBus&) const; - CANBus& operator=(const CANBus& other) = default; - //virtual ~CANBus(){} - int speed; bool listenOnly; bool singleWire; @@ -19,28 +12,36 @@ class CANBus bool canFD; int dataRate; + friend QDataStream& operator<<(QDataStream & pStream, const CANBus& pCanBus); + friend QDataStream& operator>>(QDataStream & pStream, CANBus& pCanBus); +public: + CANBus(); + + bool operator==(const CANBus&) const; + void setSpeed(int); // new speed void setListenOnly(bool); //bool for whether to only listen void setSingleWire(bool); //bool for whether to use single wire mode void setActive(bool); //whether this bus should be enabled or not. void setCanFD(bool); // enable or disable CANFD support - int getSpeed(); - int getDataRate(); void setDataRate(int newSpeed); - bool isListenOnly(); - bool isSingleWire(); - bool isActive(); - bool isCanFD(); + + int getSpeed() const; + int getDataRate() const; + bool isListenOnly() const; + bool isSingleWire() const; + bool isActive() const; + bool isCanFD() const; }; -QDataStream& operator<<( QDataStream & pStream, const CANBus& pCanBus ); -QDataStream & operator>>(QDataStream & pStream, CANBus& pCanBus); +QDataStream& operator<<(QDataStream & pStream, const CANBus& pCanBus); +QDataStream& operator>>(QDataStream & pStream, CANBus& pCanBus); Q_DECLARE_METATYPE(CANBus); struct BusData { CANBus mBus; - bool mConfigured; + bool mConfigured = {}; QVector mTargettedFrames; }; diff --git a/connections/serialbusconnection.cpp b/connections/serialbusconnection.cpp index a5ec860e..8ebebbfc 100644 --- a/connections/serialbusconnection.cpp +++ b/connections/serialbusconnection.cpp @@ -94,7 +94,7 @@ void SerialBusConnection::piSetBusSettings(int pBusIdx, CANBus bus) setBusConfig(0, bus); /* if bus is not active we are done */ - if(!bus.active) + if(!bus.isActive()) return; /* set configuration */ @@ -105,10 +105,10 @@ void SerialBusConnection::piSetBusSettings(int pBusIdx, CANBus bus) //You cannot set the speed of a socketcan interface, it has to be set with console commands. //But, you can probabaly set the speed of many of the other serialbus devices so go ahead and try - mDev_p->setConfigurationParameter(QCanBusDevice::BitRateKey, bus.speed); - mDev_p->setConfigurationParameter(QCanBusDevice::CanFdKey, bus.canFD); + mDev_p->setConfigurationParameter(QCanBusDevice::BitRateKey, bus.getSpeed()); + mDev_p->setConfigurationParameter(QCanBusDevice::CanFdKey, bus.isCanFD()); - if(bus.listenOnly) + if(bus.isListenOnly()) sbusconfig |= EN_SILENT_MODE; mDev_p->setConfigurationParameter(QCanBusDevice::UserKey, sbusconfig); @@ -188,6 +188,7 @@ void SerialBusConnection::framesReceived() /* check frame */ //if (recFrame.payload().length() <= 8) { + if (true) { CANFrame* frame_p = getQueue().get(); if(frame_p) { frame_p->setPayload(recFrame.payload()); @@ -206,7 +207,7 @@ void SerialBusConnection::framesReceived() frame_p->setTimeStamp(recFrame.timeStamp()); frame_p->setFrameType(recFrame.frameType()); frame_p->setError(recFrame.error()); - /* If recorded frame has a local echo, it is a Tx message, and thus should not be marked as Rx */ + /* If recorded frame has a local echo, it is a Tx message, and thus should not be marked as Rx */ frame_p->isReceived = !recFrame.hasLocalEcho(); if (useSystemTime) { @@ -218,11 +219,9 @@ void SerialBusConnection::framesReceived() /* enqueue frame */ getQueue().queue(); - //} -#if 0 + } else qDebug() << "can't get a frame, ERROR"; -#endif } } } From fbfe55c90505064e5c53e0381863ad6686da3086 Mon Sep 17 00:00:00 2001 From: Alexey Esaulenko Date: Thu, 18 Jan 2024 11:52:22 +0400 Subject: [PATCH 12/27] GraphingWindow: don't use size_t as indexes for Qt containers --- re/graphingwindow.cpp | 46 ++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/re/graphingwindow.cpp b/re/graphingwindow.cpp index f2a41e76..860aa7f4 100644 --- a/re/graphingwindow.cpp +++ b/re/graphingwindow.cpp @@ -779,12 +779,12 @@ void GraphingWindow::saveSpreadsheet() QList::iterator iter; double xMin = std::numeric_limits::max(), xMax = std::numeric_limits::min(); - size_t maxCount = 0; - size_t numGraphs = graphParams.length(); + int maxCount = 0; + int numGraphs = graphParams.length(); for (auto && graph : graphParams) { xMin = std::min(xMin, graph.x[0]); xMax = std::max(xMax, graph.x[graph.x.count() - 1]); - maxCount = std::max(maxCount, static_cast(graph.x.count())); + maxCount = std::max(maxCount, graph.x.count()); } qDebug() << "xMin: " << xMin; qDebug() << "xMax: " << xMax; @@ -795,8 +795,6 @@ void GraphingWindow::saveSpreadsheet() double xSize = xMax - xMin; double sliceSize = xSize / ((double)maxCount); double equivValue = sliceSize / 100.0; - double currentX; - double value; QList indices; indices.reserve(numGraphs); @@ -808,14 +806,14 @@ void GraphingWindow::saveSpreadsheet() } outFile->write("\n"); - for (size_t j = 1; j < (maxCount - 1); j++) + for (int j = 1; j < (maxCount - 1); j++) { - currentX = xMin + (j * sliceSize); + double currentX = xMin + (j * sliceSize); qDebug() << "X: " << currentX; outFile->write(QString::number(currentX, 'f').toUtf8()); - for (size_t k = 0; k < numGraphs; k++) + for (int k = 0; k < numGraphs; k++) { - value = 0.0; + double value = 0.0; // move cursor to last sample before currentX while (graphParams[k].x[indices[k]+1] < currentX) @@ -849,7 +847,7 @@ void GraphingWindow::saveSpreadsheet() else { // find index, where x >= currentX - size_t cursor = indices[k]; + int cursor = indices[k]; double span = graphParams[k].x[cursor+1] - graphParams[k].x[cursor]; double progress = (currentX - graphParams[k].x[cursor]) / span; Q_ASSERT(progress >= 0.0 && progress <= 1.0); @@ -1325,10 +1323,8 @@ void GraphingWindow::createGraph(GraphParams ¶ms, bool createGraphParam) double yminval=10000000.0, ymaxval = -1000000.0; double xminval=10000000000.0, xmaxval = -10000000000.0; GraphParams *refParam = ¶ms; - int sBit, bits; - bool intelFormat, isSigned; QString tempStr; - double x,y; + double x{}, y{}; qDebug() << "New Graph ID: " << params.ID; qDebug() << "Start bit: " << params.startBit; @@ -1365,10 +1361,10 @@ void GraphingWindow::createGraph(GraphParams ¶ms, bool createGraphParam) //params.x.fill(0, numEntries); //params.y.fill(0, numEntries); - sBit = params.startBit; - bits = params.numBits; - intelFormat = params.intelFormat; - isSigned = params.isSigned; + int sBit = params.startBit; + int bits = params.numBits; + bool intelFormat = params.intelFormat; + bool isSigned = params.isSigned; for (int j = 0; j < numEntries; j++) { @@ -1558,16 +1554,16 @@ void GraphingWindow::createGraph(GraphParams ¶ms, bool createGraphParam) void GraphingWindow::moveLegend() { qDebug() << "moveLegend"; - if (QAction* contextAction = qobject_cast(sender())) // make sure this slot is really called by a context menu action, so it carries the data we need - { - bool ok; - int dataInt = contextAction->data().toInt(&ok); - if (ok) + if (QAction* contextAction = qobject_cast(sender())) // make sure this slot is really called by a context menu action, so it carries the data we need { - ui->graphingView->axisRect()->insetLayout()->setInsetAlignment(0, (Qt::Alignment)dataInt); - ui->graphingView->replot(); + bool ok; + int dataInt = contextAction->data().toInt(&ok); + if (ok) + { + ui->graphingView->axisRect()->insetLayout()->setInsetAlignment(0, (Qt::Alignment)dataInt); + ui->graphingView->replot(); + } } - } } GraphParams::GraphParams() From 97d9f68bb789104f9b15052f3df8e33c0b9e4caf Mon Sep 17 00:00:00 2001 From: Alexey Esaulenko Date: Thu, 18 Jan 2024 12:44:16 +0400 Subject: [PATCH 13/27] CANConnection constructor: use correct order of members in the initialization list --- connections/canconnection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connections/canconnection.cpp b/connections/canconnection.cpp index 74e4fb87..9eac8176 100644 --- a/connections/canconnection.cpp +++ b/connections/canconnection.cpp @@ -13,11 +13,11 @@ CANConnection::CANConnection(QString pPort, int pQueueLen, bool pUseThread) : mNumBuses(pNumBuses), + mSerialSpeed(pSerialSpeed), mQueue(), mPort(pPort), mDriver(pDriver), mType(pType), - mSerialSpeed(pSerialSpeed), mIsCapSuspended(false), mStatus(CANCon::NOT_CONNECTED), mStarted(false), From 3e85c74cfa9473fa576298019cb90a7554654933 Mon Sep 17 00:00:00 2001 From: Alexey Esaulenko Date: Thu, 18 Jan 2024 12:52:25 +0400 Subject: [PATCH 14/27] Minor: Utility::formatTimestamp() suppress compiler warning --- utility.h | 1 + 1 file changed, 1 insertion(+) diff --git a/utility.h b/utility.h index 01abc3da..88bbb318 100644 --- a/utility.h +++ b/utility.h @@ -190,6 +190,7 @@ class Utility return (double)timestamp / 1000000.0; break; } + return QVariant(); // should never happen } //parses the input string to grab as much of it as possible while staying alpha numeric From 0b652b8da1efff3a02412da453ea5912d029514c Mon Sep 17 00:00:00 2001 From: Alexey Esaulenko Date: Thu, 18 Jan 2024 14:35:02 +0400 Subject: [PATCH 15/27] Minor: fix memory leak on GraphingWindow file operations --- re/graphingwindow.cpp | 154 ++++++++++++++++++++---------------------- re/newgraphdialog.cpp | 1 - 2 files changed, 75 insertions(+), 80 deletions(-) diff --git a/re/graphingwindow.cpp b/re/graphingwindow.cpp index 860aa7f4..d2f25812 100644 --- a/re/graphingwindow.cpp +++ b/re/graphingwindow.cpp @@ -39,7 +39,7 @@ GraphingWindow::GraphingWindow(const QVector *frames, QWidget *parent) if (Utility::timeStyle == TS_CLOCK) { - QSharedPointer timeTicker(new QCPAxisTickerTime); + QSharedPointer timeTicker = QSharedPointer::create(); timeTicker->setTimeFormat("%h:%m:%s.%z"); ui->graphingView->xAxis->setTicker(timeTicker); } @@ -571,7 +571,7 @@ void GraphingWindow::removeSelectedGraph() int idx = -1; for (int i = 0; i < graphParams.count(); i++) { - if (graphParams[i].ref == ui->graphingView->selectedGraphs().first()) + if (graphParams[i].ref == ui->graphingView->selectedGraphs().constFirst()) { idx = i; break; @@ -590,7 +590,7 @@ void GraphingWindow::removeSelectedGraph() graphParams.removeAt(idx); - ui->graphingView->removeGraph(ui->graphingView->selectedGraphs().first()); + ui->graphingView->removeGraph(ui->graphingView->selectedGraphs().constFirst()); if (graphParams.count() == 0) needScaleSetup = true; @@ -605,7 +605,7 @@ void GraphingWindow::editSelectedGraph() int idx = -1; for (int i = 0; i < graphParams.count(); i++) { - if (graphParams[i].ref == ui->graphingView->selectedGraphs().first()) + if (graphParams[i].ref == ui->graphingView->selectedGraphs().constFirst()) { idx = i; break; @@ -701,7 +701,6 @@ void GraphingWindow::contextMenuRequest(QPoint pos) void GraphingWindow::saveGraphs() { - QString filename; QFileDialog dialog(this); QSettings settings; @@ -718,7 +717,7 @@ void GraphingWindow::saveGraphs() if (dialog.exec() == QDialog::Accepted) { - filename = dialog.selectedFiles()[0]; + QString filename = dialog.selectedFiles().constFirst(); settings.setValue("Graphing/LoadSaveDirectory", dialog.directory().path()); if (dialog.selectedNameFilter() == filters[0]) @@ -741,7 +740,6 @@ void GraphingWindow::saveGraphs() void GraphingWindow::saveSpreadsheet() { - QString filename; QFileDialog dialog(this); QSettings settings; @@ -756,14 +754,14 @@ void GraphingWindow::saveSpreadsheet() if (dialog.exec() == QDialog::Accepted) { - filename = dialog.selectedFiles()[0]; + QString filename = dialog.selectedFiles().constFirst(); settings.setValue("Graphing/LoadSaveDirectory", dialog.directory().path()); if (!filename.contains('.')) filename += ".csv"; - QFile *outFile = new QFile(filename); + QFile outFile(filename); - if (!outFile->open(QIODevice::WriteOnly | QIODevice::Text)) + if (!outFile.open(QIODevice::WriteOnly | QIODevice::Text)) return; /* @@ -798,19 +796,19 @@ void GraphingWindow::saveSpreadsheet() QList indices; indices.reserve(numGraphs); - outFile->write("TimeStamp"); + outFile.write("TimeStamp"); for (auto && graph : graphParams) { indices.append(0); - outFile->putChar(','); - outFile->write(graph.graphName.toUtf8()); + outFile.putChar(','); + outFile.write(graph.graphName.toUtf8()); } - outFile->write("\n"); + outFile.write("\n"); for (int j = 1; j < (maxCount - 1); j++) { double currentX = xMin + (j * sliceSize); qDebug() << "X: " << currentX; - outFile->write(QString::number(currentX, 'f').toUtf8()); + outFile.write(QString::number(currentX, 'f').toUtf8()); for (int k = 0; k < numGraphs; k++) { double value = 0.0; @@ -854,20 +852,19 @@ void GraphingWindow::saveSpreadsheet() value = Utility::Lerp(graphParams[k].y[cursor], graphParams[k].y[cursor+1], progress); qDebug() << "Span: " << span << " Prog: " << progress << " Value: " << value; } - outFile->putChar(','); - outFile->write(QString::number(value).toUtf8()); + outFile.putChar(','); + outFile.write(QString::number(value).toUtf8()); } - outFile->write("\n"); + outFile.write("\n"); } - outFile->close(); + outFile.close(); } } void GraphingWindow::saveDefinitions() { - QString filename; QFileDialog dialog(this); QSettings settings; @@ -882,79 +879,78 @@ void GraphingWindow::saveDefinitions() if (dialog.exec() == QDialog::Accepted) { - filename = dialog.selectedFiles()[0]; + QString filename = dialog.selectedFiles().constFirst(); settings.setValue("Graphing/LoadSaveDirectory", dialog.directory().path()); if (!filename.contains('.')) filename += ".gdf"; - QFile *outFile = new QFile(filename); + QFile outFile(filename); - if (!outFile->open(QIODevice::WriteOnly | QIODevice::Text)) + if (!outFile.open(QIODevice::WriteOnly | QIODevice::Text)) return; QList::iterator iter; for (iter = graphParams.begin(); iter != graphParams.end(); ++iter) { - outFile->write("Z,"); - outFile->write(QString::number(iter->ID, 16).toUtf8()); - outFile->putChar(','); - outFile->write(QString::number(iter->mask, 16).toUtf8()); - outFile->putChar(','); - if (iter->intelFormat) outFile->write(QString::number(iter->startBit).toUtf8()); - else outFile->write(QString::number(iter->startBit * -1).toUtf8()); - outFile->putChar(','); - outFile->write(QString::number(iter->numBits).toUtf8()); - outFile->putChar(','); - if (iter->isSigned) outFile->putChar('Y'); - else outFile->putChar('N'); - outFile->putChar(','); - outFile->write(QString::number(iter->bias).toUtf8()); - outFile->putChar(','); - outFile->write(QString::number(iter->scale).toUtf8()); - outFile->putChar(','); - outFile->write(QString::number(iter->stride).toUtf8()); - outFile->putChar(','); - outFile->write(QString::number(iter->bus).toUtf8()); - outFile->putChar(','); - outFile->write(QString::number(iter->lineColor.red()).toUtf8()); - outFile->putChar(','); - outFile->write(QString::number(iter->lineColor.green()).toUtf8()); - outFile->putChar(','); - outFile->write(QString::number(iter->lineColor.blue()).toUtf8()); - outFile->putChar(','); - outFile->write(iter->graphName.toUtf8()); - outFile->putChar(','); - outFile->write(QString::number(iter->fillColor.red()).toUtf8()); - outFile->putChar(','); - outFile->write(QString::number(iter->fillColor.green()).toUtf8()); - outFile->putChar(','); - outFile->write(QString::number(iter->fillColor.blue()).toUtf8()); - outFile->putChar(','); - outFile->write(QString::number(iter->fillColor.alpha()).toUtf8()); - outFile->putChar(','); - if (iter->drawOnlyPoints) outFile->putChar('Y'); - else outFile->putChar('N'); - outFile->putChar(','); - outFile->write(QString::number(iter->pointType).toUtf8()); - outFile->putChar(','); - outFile->write(QString::number(iter->lineWidth).toUtf8()); + outFile.write("Z,"); + outFile.write(QString::number(iter->ID, 16).toUtf8()); + outFile.putChar(','); + outFile.write(QString::number(iter->mask, 16).toUtf8()); + outFile.putChar(','); + if (iter->intelFormat) outFile.write(QString::number(iter->startBit).toUtf8()); + else outFile.write(QString::number(iter->startBit * -1).toUtf8()); + outFile.putChar(','); + outFile.write(QString::number(iter->numBits).toUtf8()); + outFile.putChar(','); + if (iter->isSigned) outFile.putChar('Y'); + else outFile.putChar('N'); + outFile.putChar(','); + outFile.write(QString::number(iter->bias).toUtf8()); + outFile.putChar(','); + outFile.write(QString::number(iter->scale).toUtf8()); + outFile.putChar(','); + outFile.write(QString::number(iter->stride).toUtf8()); + outFile.putChar(','); + outFile.write(QString::number(iter->bus).toUtf8()); + outFile.putChar(','); + outFile.write(QString::number(iter->lineColor.red()).toUtf8()); + outFile.putChar(','); + outFile.write(QString::number(iter->lineColor.green()).toUtf8()); + outFile.putChar(','); + outFile.write(QString::number(iter->lineColor.blue()).toUtf8()); + outFile.putChar(','); + outFile.write(iter->graphName.toUtf8()); + outFile.putChar(','); + outFile.write(QString::number(iter->fillColor.red()).toUtf8()); + outFile.putChar(','); + outFile.write(QString::number(iter->fillColor.green()).toUtf8()); + outFile.putChar(','); + outFile.write(QString::number(iter->fillColor.blue()).toUtf8()); + outFile.putChar(','); + outFile.write(QString::number(iter->fillColor.alpha()).toUtf8()); + outFile.putChar(','); + if (iter->drawOnlyPoints) outFile.putChar('Y'); + else outFile.putChar('N'); + outFile.putChar(','); + outFile.write(QString::number(iter->pointType).toUtf8()); + outFile.putChar(','); + outFile.write(QString::number(iter->lineWidth).toUtf8()); if (iter->associatedSignal) { - outFile->putChar(','); - outFile->write(iter->associatedSignal->parentMessage->name.toUtf8()); - outFile->putChar(','); - outFile->write(iter->associatedSignal->name.toUtf8()); + outFile.putChar(','); + outFile.write(iter->associatedSignal->parentMessage->name.toUtf8()); + outFile.putChar(','); + outFile.write(iter->associatedSignal->name.toUtf8()); } - outFile->write("\n"); + outFile.write("\n"); } - outFile->close(); + outFile.close(); } } void GraphingWindow::loadDefinitions() { - QString filename; QFileDialog dialog; QSettings settings; @@ -971,17 +967,17 @@ void GraphingWindow::loadDefinitions() if (dialog.exec() == QDialog::Accepted) { - filename = dialog.selectedFiles()[0]; + QString filename = dialog.selectedFiles().constFirst(); settings.setValue("Graphing/LoadSaveDirectory", dialog.directory().path()); - QFile *inFile = new QFile(filename); + QFile inFile(filename); QByteArray line; - if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) + if (!inFile.open(QIODevice::ReadOnly | QIODevice::Text)) return; - while (!inFile->atEnd()) { - line = inFile->readLine().simplified(); + while (!inFile.atEnd()) { + line = inFile.readLine().simplified(); if (line.length() > 2) { GraphParams gp; @@ -1203,7 +1199,7 @@ void GraphingWindow::loadDefinitions() } } } - inFile->close(); + inFile.close(); } } diff --git a/re/newgraphdialog.cpp b/re/newgraphdialog.cpp index 6b759f49..62f73c66 100644 --- a/re/newgraphdialog.cpp +++ b/re/newgraphdialog.cpp @@ -361,7 +361,6 @@ void NewGraphDialog::loadNodes() void NewGraphDialog::loadMessages(int idx) { int numFiles = 0; - DBC_MESSAGE *msg; ui->cbMessages->clear(); if (dbcHandler == nullptr) return; if ((numFiles = dbcHandler->getFileCount()) == 0) return; From d0c508c0ea8b33f8e31013df2eca213f71c2ae22 Mon Sep 17 00:00:00 2001 From: Alexey Esaulenko Date: Thu, 18 Jan 2024 15:02:44 +0400 Subject: [PATCH 16/27] FrameSender: fix memory corruption while writing to the QCanBusFrame with empty payload --- framesenderwindow.cpp | 2 -- re/flowviewwindow.cpp | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/framesenderwindow.cpp b/framesenderwindow.cpp index 73162e56..a6e867cc 100644 --- a/framesenderwindow.cpp +++ b/framesenderwindow.cpp @@ -1054,8 +1054,6 @@ void FrameSenderWindow::processCellChange(int line, int col) } break; case ST_COLS::SENDTAB_COL_DATA: //Data bytes - for (int i = 0; i < 8; i++) sendingData[line].payload().data()[i] = 0; - #if QT_VERSION >= QT_VERSION_CHECK( 5, 14, 0 ) tokens = ui->tableSender->item(line, ST_COLS::SENDTAB_COL_DATA)->text().split(" ", Qt::SkipEmptyParts); #else diff --git a/re/flowviewwindow.cpp b/re/flowviewwindow.cpp index c51960b1..77c088f8 100644 --- a/re/flowviewwindow.cpp +++ b/re/flowviewwindow.cpp @@ -851,7 +851,7 @@ void FlowViewWindow::updatePosition(bool forward) int maxVal = qMin(chunk * 8 + 8, frameCache.at(currentPosition).payload().length()); for (int i = chunk * 8; i < maxVal; i++) { - unsigned char thisByte = static_cast(frameCache.at(currentPosition).payload().data()[i]); + unsigned char thisByte = static_cast(frameCache.at(currentPosition).payload()[i]); cngByte = currBytes[i] ^ thisByte; changedBits |= (uint64_t)cngByte << (8ull * (i & 7)); } From f98ee0a34dc6527f197939e105053040e364435a Mon Sep 17 00:00:00 2001 From: Alexey Esaulenko Date: Thu, 18 Jan 2024 15:13:48 +0400 Subject: [PATCH 17/27] SocketCANd: fix memory leak: string can be accessed after object destruction --- connections/socketcand.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/connections/socketcand.cpp b/connections/socketcand.cpp index 83fb144a..6ef89755 100644 --- a/connections/socketcand.cpp +++ b/connections/socketcand.cpp @@ -219,8 +219,8 @@ void SocketCANd::connectDevice() void SocketCANd::deviceConnected(int busNum) { sendDebug("Opening CAN on Kayak Device!"); - const char* openCanCmd = ("< open " + hostCanIDs[busNum] + " >").toStdString().c_str(); - sendStringToTCP(openCanCmd, busNum); + QString openCanCmd("< open " % hostCanIDs[busNum] % " >"); + sendStringToTCP(openCanCmd.toUtf8().data(), busNum); QCoreApplication::processEvents(); } @@ -383,8 +383,6 @@ void SocketCANd::procRXData(QString data, int busNum) mTimer.stop(); mTimer.start(); } - QByteArray output; - switch (rx_state.at(busNum)) { case IDLE: From 2b5aa1f111e1bde02f3a34185b948ddc2046682e Mon Sep 17 00:00:00 2001 From: Alexey Esaulenko Date: Thu, 18 Jan 2024 15:25:42 +0400 Subject: [PATCH 18/27] Minor: DBCComparatorWindow: sanity checks to fix gcc warnings --- re/dbccomparatorwindow.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/re/dbccomparatorwindow.cpp b/re/dbccomparatorwindow.cpp index e9b87b27..f8eb0bd9 100644 --- a/re/dbccomparatorwindow.cpp +++ b/re/dbccomparatorwindow.cpp @@ -222,8 +222,8 @@ void DBCComparatorWindow::calculateDetails() ui->treeDetails->addTopLevelItem(msgDiffRoot); - QTreeWidgetItem *msgItem; - QTreeWidgetItem *sigTemp; + QTreeWidgetItem *msgItem {}; + QTreeWidgetItem *sigTemp {}; for (int i = 0; i < firstDBC->messageHandler->getCount(); i++) { @@ -257,7 +257,8 @@ void DBCComparatorWindow::calculateDetails() msgItem->setText(0, msgName + " (" + Utility::formatCANID(thisMsg->ID) + ")"); sigDiffTwo->addChild(msgItem); } - msgItem->addChild(missingSigItem); + if (msgItem) + msgItem->addChild(missingSigItem); } else //signal exists on both sides. See if it as changed position or length { @@ -301,7 +302,8 @@ void DBCComparatorWindow::calculateDetails() sigTemp->setText(0, msgName + " (" + Utility::formatCANID(thisMsg->ID) + ")"); sigModifiedRoot->addChild(sigTemp); } - sigTemp->addChild(sigItem); + if (sigTemp) + sigTemp->addChild(sigItem); } } } From f634cd9e6cc5086bddabf69e81627f006d076b90 Mon Sep 17 00:00:00 2001 From: Alexey Esaulenko Date: Thu, 18 Jan 2024 18:42:06 +0400 Subject: [PATCH 19/27] Minor: SnifferWindow: fix deprecated constant --- re/sniffer/snifferwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/re/sniffer/snifferwindow.cpp b/re/sniffer/snifferwindow.cpp index e0ecfb87..f8145b14 100644 --- a/re/sniffer/snifferwindow.cpp +++ b/re/sniffer/snifferwindow.cpp @@ -181,7 +181,7 @@ void SnifferWindow::notchTick() } else { - ui->lblNotch->setBackgroundRole(QPalette::Background); + ui->lblNotch->setBackgroundRole(QPalette::Window); ui->lblNotch->repaint(); //qDebug() << "Tock"; } From cee34173eefafbbd41d9b62f990b7011791db02a Mon Sep 17 00:00:00 2001 From: Alexey Esaulenko Date: Thu, 18 Jan 2024 18:06:45 +0400 Subject: [PATCH 20/27] ConnectionWindow: declare debug output connection as unique to prevent duplicates --- connections/connectionwindow.cpp | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/connections/connectionwindow.cpp b/connections/connectionwindow.cpp index 639d531f..5e4df7aa 100644 --- a/connections/connectionwindow.cpp +++ b/connections/connectionwindow.cpp @@ -79,12 +79,12 @@ ConnectionWindow::ConnectionWindow(QWidget *parent) : //Need to make sure it tries to share the address in case there are //multiple instances of SavvyCAN running. rxBroadcastGVRET->bind(QHostAddress::AnyIPv4, 17222, QAbstractSocket::ShareAddress); - connect(rxBroadcastGVRET, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams())); + connect(rxBroadcastGVRET, &QUdpSocket::readyRead, this, &ConnectionWindow::readPendingDatagrams); //Doing the same for socketcand/kayak hosts: rxBroadcastKayak = new QUdpSocket(this); rxBroadcastKayak->bind(QHostAddress::AnyIPv4, 42000, QAbstractSocket::ShareAddress); - connect(rxBroadcastKayak, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams())); + connect(rxBroadcastKayak, &QUdpSocket::readyRead, this, &ConnectionWindow::readPendingDatagrams); } @@ -236,12 +236,12 @@ void ConnectionWindow::consoleEnableChanged(bool checked) { CANConnection* conn_p = connModel->getAtIdx(selIdx); if (checked) { //enable console - connect(conn_p, SIGNAL(debugOutput(QString)), this, SLOT(getDebugText(QString))); - connect(this, SIGNAL(sendDebugData(QByteArray)), conn_p, SLOT(debugInput(QByteArray))); + connect(conn_p, &CANConnection::debugOutput, this, &ConnectionWindow::getDebugText, Qt::UniqueConnection); + connect(this, &ConnectionWindow::sendDebugData, conn_p, &CANConnection::debugInput, Qt::UniqueConnection); } else { //turn it off - disconnect(conn_p, SIGNAL(debugOutput(QString)), nullptr, nullptr); - disconnect(this, SIGNAL(sendDebugData(QByteArray)), conn_p, SLOT(debugInput(QByteArray))); + disconnect(conn_p, &CANConnection::debugOutput, nullptr, nullptr); + disconnect(this, &ConnectionWindow::sendDebugData, conn_p, &CANConnection::debugInput); } } @@ -454,8 +454,8 @@ void ConnectionWindow::currentRowChanged(const QModelIndex ¤t, const QMode int selIdx = current.row(); CANConnection* prevConn = connModel->getAtIdx(previous.row()); if(prevConn != nullptr) - disconnect(prevConn, SIGNAL(debugOutput(QString)), nullptr, nullptr); - disconnect(this, SIGNAL(sendDebugData(QByteArray)), nullptr, nullptr); + disconnect(prevConn, &CANConnection::debugOutput, nullptr, nullptr); + disconnect(this, &ConnectionWindow::sendDebugData, nullptr, nullptr); /* set parameters */ if (selIdx == -1) { @@ -472,7 +472,7 @@ void ConnectionWindow::currentRowChanged(const QModelIndex ¤t, const QMode if(!conn_p) return; //because this might have already been setup during the initial setup so tear that one down and then create the normal one. - //disconnect(conn_p, SIGNAL(debugOutput(QString)), 0, 0); + //disconnect(conn_p, &CANConnection::debugOutput, 0, 0); numBuses = conn_p->getNumBuses(); int numB = ui->tabBuses->count(); @@ -485,8 +485,8 @@ void ConnectionWindow::currentRowChanged(const QModelIndex ¤t, const QMode populateBusDetails(0); if (ui->ckEnableConsole->isChecked()) { - connect(conn_p, SIGNAL(debugOutput(QString)), this, SLOT(getDebugText(QString))); - connect(this, SIGNAL(sendDebugData(QByteArray)), conn_p, SLOT(debugInput(QByteArray))); + connect(conn_p, &CANConnection::debugOutput, this, &ConnectionWindow::getDebugText, Qt::UniqueConnection); + connect(this, &ConnectionWindow::sendDebugData, conn_p, &CANConnection::debugInput, Qt::UniqueConnection); } } } @@ -524,12 +524,11 @@ CANConnection* ConnectionWindow::create(CANCon::type pTye, QString pPortName, QS if(conn_p) { /* connect signal */ - connect(conn_p, SIGNAL(status(CANConStatus)), - this, SLOT(connectionStatus(CANConStatus))); + connect(conn_p, &CANConnection::status, this, &ConnectionWindow::connectionStatus); if (ui->ckEnableConsole->isChecked()) { //set up the debug console to operate if we've selected it. Doing so here allows debugging right away during set up - connect(conn_p, SIGNAL(debugOutput(QString)), this, SLOT(getDebugText(QString))); + connect(conn_p, &CANConnection::debugOutput, this, &ConnectionWindow::getDebugText, Qt::UniqueConnection); } /*TODO add return value and checks */ conn_p->start(); From 75f9efa4b499031970a4b709a5335a9ea2d4af7b Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Tue, 6 Feb 2024 20:22:42 -0500 Subject: [PATCH 21/27] Update CANalyzer ASC loader to accept CANFD messages as well --- framefileio.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/framefileio.cpp b/framefileio.cpp index 5ea46caa..f7e86589 100644 --- a/framefileio.cpp +++ b/framefileio.cpp @@ -1680,12 +1680,16 @@ bool FrameFileIO::isCanalyzerASC(QString filename) //0.001371 CANFD 1 Rx 171 A0066_Unknown 0 0 8 8 00 00 07 76 23 00 00 00 230199 119 220000 62e7 46500250 460a0250 20011736 20010205 //Version 16.0.0 //0.008600 1 358 Rx d 8 04 03 50 01 F0 40 54 2C Length = 233910 BitCount = 121 ID = 856 -//Time bus id dir ? len databytes (Ver 8.0) -//Time type bus dir ID ? ? length length bytes then many values of unknown type (ver 8.1) -//Time type bus dir ID SignalName ? ? length length bytes then many values of unknown type (ver 9.0) -//Time bus id dir ? len databytes additional crap (ver 16.0) -//So it seems the file format is not entirely different based on version but rather on some other -//settings... Fun! +//Version 17.3.0 +//0.001684 CANFD 1 Rx 65b 1 0 d 32 f1 01 00 00 50 00 00 00 00 00 00 00 00 00 00 40 00 88 f5 77 17 78 00 00 c0 03 00 00 00 00 00 00 213797 356 303000 11729 46500250 4b140250 20011736 2001040d + +//Time bus id dir ? len databytes (Ver 8.0) +//Time bus id dir ? len databytes (addl info) (ver 16.0) +//Time type bus dir ID ? ? length length (bytes) (many values of unknown type) (ver 8.1) +//Time type bus dir ID SignalName ? ? length length bytes (then many values of unknown type)(ver 9.0) +//Time Type Bus Dir ID ? ? (length) (Real Length) (bytes) (many values of unknown type) (Ver 17.3) +//0 1 2 3 4 5 6 7 8 9 10 +//This seems like a rather eclectic mix. It's almost arbitrary! bool FrameFileIO::loadCanalyzerASC(QString filename, QVector* frames) { QFile *inFile = new QFile(filename); @@ -1736,7 +1740,7 @@ bool FrameFileIO::loadCanalyzerASC(QString filename, QVector* frames) } } if (inHeader) continue; - if (line.length() > 2) + if (line.length() > 2 && !line.startsWith("//")) { tokens = line.simplified().split(' '); @@ -1766,7 +1770,7 @@ bool FrameFileIO::loadCanalyzerASC(QString filename, QVector* frames) int payloadLen = tokens[8].toInt(); qDebug() << "PayloadLen: " << payloadLen << " Tokens: " << tokens; QByteArray bytes(payloadLen, 0); - if (payloadLen > 8) + if (payloadLen > 64) { qDebug() << "Payload length too long. Original line: " << line; return false; From a7dd9041854cd93f63e9b3370604d9add2cc5c4a Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Mon, 12 Feb 2024 21:20:54 -0500 Subject: [PATCH 22/27] Partial implementation of a more generic and multi-threaded version of the frame sender. Now a simplified interface is attached to the main window as well. Doesn't necessarily work just yet... --- SavvyCAN.pro | 2 + can_trigger_structs.h | 6 +- framesenderobject.cpp | 453 +++++++++++++++++++++++++++++ framesenderobject.h | 81 ++++++ framesenderwindow.cpp | 41 ++- mainwindow.cpp | 198 +++++++++++++ mainwindow.h | 20 +- ui/framesenderwindow.ui | 2 +- ui/mainwindow.ui | 623 ++++++++++++++++++++-------------------- 9 files changed, 1103 insertions(+), 323 deletions(-) create mode 100644 framesenderobject.cpp create mode 100644 framesenderobject.h diff --git a/SavvyCAN.pro b/SavvyCAN.pro index def354a4..5512e427 100644 --- a/SavvyCAN.pro +++ b/SavvyCAN.pro @@ -26,6 +26,7 @@ SOURCES += main.cpp\ connections/lawicel_serial.cpp \ connections/mqtt_bus.cpp \ dbc/dbcnodeduplicateeditor.cpp \ + framesenderobject.cpp \ mqtt/qmqtt_client.cpp \ mqtt/qmqtt_client_p.cpp \ mqtt/qmqtt_frame.cpp \ @@ -113,6 +114,7 @@ HEADERS += mainwindow.h \ connections/mqtt_bus.h \ dbc/dbcnodeduplicateeditor.h \ dbc/dbcnoderebaseeditor.h \ + framesenderobject.h \ mqtt/qmqtt.h \ mqtt/qmqtt_client.h \ mqtt/qmqtt_client_p.h \ diff --git a/can_trigger_structs.h b/can_trigger_structs.h index 32ef0ef8..177df4f6 100644 --- a/can_trigger_structs.h +++ b/can_trigger_structs.h @@ -4,6 +4,7 @@ #include "can_structs.h" #include +#include enum TriggerMask { @@ -38,6 +39,7 @@ class Trigger //If ID is -1 then this is the temporary storage register. This is a shadow //register used to accumulate the results of a multi operation modifier. //if ID is -2 then this is a look up of our own data bytes stored in the class data. +//Of course, if the ID is positive then we grab bytes or signals from newest message with that ID class ModifierOperand { public: @@ -45,6 +47,7 @@ class ModifierOperand int bus; int databyte; bool notOper; //should a bitwise NOT be applied to this prior to doing the actual calculation? + QString signalName; //if ID is positive and there is text in here then we'll look up the signal and use its value }; //list of operations that can be done between the two operands @@ -73,7 +76,8 @@ class ModifierOp class Modifier { public: - int destByte; + int destByte; //if -1 then target one of this ID's signals instead + QString signalName; QList operations; }; diff --git a/framesenderobject.cpp b/framesenderobject.cpp new file mode 100644 index 00000000..92127474 --- /dev/null +++ b/framesenderobject.cpp @@ -0,0 +1,453 @@ +#include "framesenderobject.h" +#include "mainwindow.h" + +FrameSenderObject::FrameSenderObject(const QVector *frames) +{ + mThread_p = new QThread(); + + statusCounter = 0; + modelFrames = frames; + dbcHandler = DBCHandler::getReference(); +} + +FrameSenderObject::~FrameSenderObject() +{ + mThread_p->quit(); + mThread_p->wait(); + delete mThread_p; +} + +void FrameSenderObject::piStart() +{ + sendingTimer = new QTimer(); + sendingTimer->setTimerType(Qt::PreciseTimer); + sendingTimer->setInterval(1); + + connect(sendingTimer, &QTimer::timeout, this, &FrameSenderObject::timerTriggered); +} + +void FrameSenderObject::piStop() +{ + sendingTimer->stop(); + delete sendingTimer; +} + +void FrameSenderObject::initialize() +{ + if( mThread_p && (mThread_p != QThread::currentThread()) ) + { + /* move ourself to the thread */ + moveToThread(mThread_p); /*TODO handle errors */ + /* connect started() */ + connect(mThread_p, SIGNAL(started()), this, SLOT(initialize())); + /* start the thread */ + mThread_p->start(QThread::HighPriority); + return; + } + + /* set started flag */ + //mStarted = true; + + connect(MainWindow::getReference(), SIGNAL(framesUpdated(int)), this, SLOT(updatedFrames(int))); + + /* in multithread case, this will be called before entering thread event loop */ + return piStart(); + +} + +void FrameSenderObject::finalize() +{ + /* 1) execute in mThread_p context */ + if( mThread_p && (mThread_p != QThread::currentThread()) ) + { + /* if thread is finished, it means we call this function for the second time so we can leave */ + if( !mThread_p->isFinished() ) + { + /* we need to call piStop() */ + QMetaObject::invokeMethod(this, "finalize", + Qt::BlockingQueuedConnection); + /* 3) stop thread */ + mThread_p->quit(); + if(!mThread_p->wait()) { + qDebug() << "can't stop thread"; + } + } + return; + } + + /* 2) call piStop in mThread context */ + return piStop(); +} + +void FrameSenderObject::startSending() +{ + /* make sure we execute in mThread context */ + if( mThread_p && (mThread_p != QThread::currentThread()) ) { + QMetaObject::invokeMethod(this, "startSending", + Qt::BlockingQueuedConnection); + return; + } + + sendingElapsed.start(); + sendingTimer->start(); +} + +void FrameSenderObject::stopSending() +{ + /* make sure we execute in mThread context */ + if( mThread_p && (mThread_p != QThread::currentThread()) ) { + QMetaObject::invokeMethod(this, "stopPlayback", + Qt::BlockingQueuedConnection); + return; + } + + sendingTimer->stop(); //pushing this button halts automatic playback + //emit statusUpdate(currentPosition); +} + +void FrameSenderObject::addSendRecord(FrameSendData record) +{ + /* make sure we execute in mThread context */ + if( mThread_p && (mThread_p != QThread::currentThread()) ) { + QMetaObject::invokeMethod(this, "addSendRecord", + Qt::BlockingQueuedConnection, + Q_ARG(FrameSendData, record)); + return; + } + sendingData.append(record); +} + +void FrameSenderObject::removeSendRecord(int idx) +{ + /* make sure we execute in mThread context */ + if( mThread_p && (mThread_p != QThread::currentThread()) ) { + QMetaObject::invokeMethod(this, "removeSendRecord", + Qt::BlockingQueuedConnection, + Q_ARG(int, idx)); + return; + } + sendingData.removeAt(idx); +} + +/* + * Note: Getting a reference to an item in a QList is a dangerous thing. If anyone + * or anything happens to reallocate the list you're screwed. So, call this, do the + * work, and drop the reference as soon as possible. +*/ +FrameSendData *FrameSenderObject::getSendRecordRef(int idx) +{ + if (idx < 0 || idx >= sendingData.count()) return nullptr; + return &sendingData[idx]; +} + +/// +/// Called every millisecond to set the system update figures and send frames if necessary. +/// +/// +/// +/// +void FrameSenderObject::timerTriggered() +{ + FrameSendData *sendData; + Trigger *trigger; + + sendingList.clear(); + if(mutex.tryLock()) + { + //get elapsed microseconds since last tick (in case timer skips or is otherwise inaccurate, though there are no guarantees about elapsed timer either) + quint64 elapsed = sendingElapsed.nsecsElapsed() / 1000; + if (elapsed == 0) elapsed = 1; + sendingElapsed.start(); + sendingLastTimeStamp += elapsed; + //qDebug() << playbackLastTimeStamp; + //qDebug() << "El: " << elapsed; + statusCounter++; + for (int i = 0; i < sendingData.count(); i++) + { + sendData = &sendingData[i]; + if (!sendData->enabled) + { + if (sendData->triggers.count() > 0) + { + for (int j = 0; j < sendData->triggers.count(); j++) //resetting currCount when line is disabled + { + sendData->triggers[j].currCount = 0; + } + } + continue; //abort any processing on this if it is not enabled. + } + if (sendData->triggers.count() == 0) + { + qDebug() << "No triggers to process"; + break; + } + for (int j = 0; j < sendData->triggers.count(); j++) + { + trigger = &sendData->triggers[j]; + if (trigger->currCount >= trigger->maxCount) continue; //don't process if we've sent max frames we were supposed to + if (!trigger->readyCount) continue; //don't tick if not ready to tick + //is it time to fire? + trigger->msCounter += elapsed; //gives proper tracking even if timer doesn't fire as fast as it should + if (trigger->msCounter >= trigger->milliseconds) + { + trigger->msCounter -= trigger->milliseconds; + sendData->count++; + trigger->currCount++; + doModifiers(i); + //updateGridRow(i); + //qDebug() << "About to try to send a frame"; + //CANConManager::getInstance()->sendFrame(sendingData[i]); + sendingList.append(sendingData[i]); //queue it instead of immediate sending + if (trigger->ID > 0) trigger->readyCount = false; //reset flag if this is a timed ID trigger + } + } + } + + //if we have any frames to send after the above then send as a batch + if (sendingList.count() > 0) CANConManager::getInstance()->sendFrames(sendingList); + mutex.unlock(); + } + else + { + qDebug() << "framesenderobject::handleTick() couldn't get mutex, elapsed is: " << sendingElapsed.elapsed(); + } +} + +void FrameSenderObject::buildFrameCache() +{ + CANFrame thisFrame; + frameCache.clear(); + for (int i = 0; i < modelFrames->count(); i++) + { + thisFrame = modelFrames->at(i); + if (!frameCache.contains(thisFrame.frameId())) + { + frameCache.insert(thisFrame.frameId(), thisFrame); + } + else + { + frameCache[thisFrame.frameId()] = thisFrame; + } + } +} + +//remember, negative numbers are special -1 = all frames deleted, -2 = totally new set of frames. +void FrameSenderObject::updatedFrames(int numFrames) +{ + CANFrame thisFrame; + if (numFrames == -1) //all frames deleted. + { + } + else if (numFrames == -2) //all new set of frames. + { + buildFrameCache(); + } + else //just got some new frames. See if they are relevant. + { + if (numFrames > modelFrames->count()) return; + qDebug() << "New frames in sender window"; + //run through the supposedly new frames in order + for (int i = modelFrames->count() - numFrames; i < modelFrames->count(); i++) + { + thisFrame = modelFrames->at(i); + if (!frameCache.contains(thisFrame.frameId())) + { + frameCache.insert(thisFrame.frameId(), thisFrame); + } + else + { + frameCache[thisFrame.frameId()] = thisFrame; + } + processIncomingFrame(&thisFrame); + } + } +} + +void FrameSenderObject::processIncomingFrame(CANFrame *frame) +{ + for (int sd = 0; sd < sendingData.count(); sd++) + { + if (sendingData[sd].triggers.count() == 0) continue; + bool passedChecks = true; + for (int trig = 0; trig < sendingData[sd].triggers.count(); trig++) + { + Trigger *thisTrigger = &sendingData[sd].triggers[trig]; + //need to ensure that this trigger is actually related to frames incoming. + //Otherwise ignore it here. Only frames that have BUS and/or ID set as trigger will work here. + if (!(thisTrigger->triggerMask & (TriggerMask::TRG_BUS | TriggerMask::TRG_ID) ) ) continue; + + passedChecks = true; + //qDebug() << "Trigger ID: " << thisTrigger->ID; + //qDebug() << "Frame ID: " << frame->frameId(); + + //Check to see if we have a bus trigger condition and if so does it match + if (thisTrigger->bus != frame->bus && (thisTrigger->triggerMask & TriggerMask::TRG_BUS) ) + passedChecks = false; + + //check to see if we have an ID trigger condition and if so does it match + if ((thisTrigger->triggerMask & TriggerMask::TRG_ID) && (uint32_t)thisTrigger->ID != frame->frameId() ) + passedChecks = false; + + //check to see if we're limiting the trigger by max count and have we reached that count? + if ( (thisTrigger->triggerMask & TriggerMask::TRG_COUNT) && (thisTrigger->currCount >= thisTrigger->maxCount) ) + passedChecks = false; + + //if the above passed then are we triggering not only on ID but also signal? + if (passedChecks && (thisTrigger->triggerMask & (TriggerMask::TRG_SIGNAL | TriggerMask::TRG_ID) ) ) + { + bool sigCheckPassed = false; + DBC_MESSAGE *msg = dbcHandler->findMessage(thisTrigger->ID); + if (msg) + { + DBC_SIGNAL *sig = msg->sigHandler->findSignalByName(thisTrigger->sigName); + if (sig) + { + //first of all, is this signal really in this message we got? + if (sig->isSignalInMessage(*frame)) sigCheckPassed = true; + //if it was and we're also filtering on value then try that next + if (sigCheckPassed && (thisTrigger->triggerMask & TriggerMask::TRG_SIGVAL)) + { + double sigval = 0.0; + if (sig->processAsDouble(*frame, sigval)) + { + if (abs(sigval - thisTrigger->sigValueDbl) > 0.001) + { + sigCheckPassed = false; + } + } + else sigCheckPassed = false; + } + } + else sigCheckPassed = false; + } + else sigCheckPassed = false; + passedChecks &= sigCheckPassed; //passedChecks can only be true if both are + } + + //if all the above says it's OK then we'll go ahead and send that message. + //If a message that comes through here has a MS value then we use it as a delay + //after the check passes. This allows for delaying the sending of the frame if that + //is required. Otherwise, just send it immediately. + if (passedChecks) + { + if (thisTrigger->milliseconds == 0) //immediate reply + { + thisTrigger->currCount++; + sendingData[sd].count++; + doModifiers(sd); + //updateGridRow(sd); + CANConManager::getInstance()->sendFrame(sendingData[sd]); + } + else //delayed sending frame + { + thisTrigger->readyCount = true; + } + } + } + } +} + + +/// +/// given an index into the sendingData list we run the modifiers that it has set up +/// +/// The index into the sendingData list +void FrameSenderObject::doModifiers(int idx) +{ + int shadowReg = 0; //shadow register we use to accumulate results + int first=0, second=0; + + FrameSendData *sendData = &sendingData[idx]; + Modifier *mod; + ModifierOp op; + + if (sendData->modifiers.count() == 0) return; //if no modifiers just leave right now + + //qDebug() << "Executing mods"; + + for (int i = 0; i < sendData->modifiers.count(); i++) + { + mod = &sendData->modifiers[i]; + for (int j = 0; j < mod->operations.count(); j++) + { + op = mod->operations.at(j); + if (op.first.ID == -1) + { + first = shadowReg; + } + else first = fetchOperand(idx, op.first); + second = fetchOperand(idx, op.second); + switch (op.operation) + { + case ADDITION: + shadowReg = first + second; + break; + case AND: + shadowReg = first & second; + break; + case DIVISION: + shadowReg = first / second; + break; + case MULTIPLICATION: + shadowReg = first * second; + break; + case OR: + shadowReg = first | second; + break; + case SUBTRACTION: + shadowReg = first - second; + break; + case XOR: + shadowReg = first ^ second; + break; + case MOD: + shadowReg = first % second; + } + } + //Finally, drop the result into the proper data byte + QByteArray newArr(sendData->payload()); + newArr[mod->destByte] = (char) shadowReg; + sendData->setPayload(newArr); + } +} + +int FrameSenderObject::fetchOperand(int idx, ModifierOperand op) +{ + CANFrame *tempFrame = nullptr; + if (op.ID == 0) //numeric constant + { + if (op.notOper) return ~op.databyte; + else return op.databyte; + } + else if (op.ID == -2) //fetch data from a data byte within the output frame + { + if (op.notOper) return ~((unsigned char)sendingData.at(idx).payload()[op.databyte]); + else return (unsigned char)sendingData.at(idx).payload()[op.databyte]; + } + else //look up external data byte + { + tempFrame = lookupFrame(op.ID, op.bus); + if (tempFrame != nullptr) + { + if (op.notOper) return ~((unsigned char)tempFrame->payload()[op.databyte]); + else return (unsigned char)tempFrame->payload()[op.databyte]; + } + else return 0; + } +} + +/// +/// Try to find the most recent frame given the input criteria +/// +/// The ID to find +/// Which bus to look on (-1 if you don't care) +/// +CANFrame* FrameSenderObject::lookupFrame(int ID, int bus) +{ + if (!frameCache.contains(ID)) return nullptr; + + if (bus == -1 || frameCache[ID].bus == bus) return &frameCache[ID]; + + return nullptr; +} + diff --git a/framesenderobject.h b/framesenderobject.h new file mode 100644 index 00000000..47a5800f --- /dev/null +++ b/framesenderobject.h @@ -0,0 +1,81 @@ +#ifndef FRAMESENDEROBJECT_H +#define FRAMESENDEROBJECT_H + +#include +#include +#include +#include +#include +#include +#include "can_structs.h" +#include "connections/canconmanager.h" +#include "can_trigger_structs.h" +#include "dbc/dbchandler.h" + +class FrameSenderObject : public QObject +{ + Q_OBJECT + +public: + FrameSenderObject(const QVector *frames); + ~FrameSenderObject(); + +public slots: + /** + * @brief start the device. This calls piStarted + * @note starts the working thread if required (piStart is called in the working thread context) + */ + void initialize(); + + /** + * @brief stop the device. This calls piStop + * @note if a working thread is used, piStop is called before exiting the working thread + */ + void finalize(); + + void startSending(); + void stopSending(); + + void addSendRecord(FrameSendData record); + void removeSendRecord(int idx); + FrameSendData *getSendRecordRef(int idx); + +signals: + +private slots: + void timerTriggered(); + void updatedFrames(int); + +private: + QList sendingList; + int currentPosition; + QTimer *sendingTimer; + QElapsedTimer sendingElapsed; + quint64 sendingLastTimeStamp; + int statusCounter; + QList sendingData; + QThread* mThread_p; + QHash frameCache; //hash with frame ID as the key and the most recent frame as the value + const QVector *modelFrames; + bool inhibitChanged = false; + QMutex mutex; + DBCHandler *dbcHandler; + + void doModifiers(int); + int fetchOperand(int, ModifierOperand); + CANFrame* lookupFrame(int, int); + void buildFrameCache(); + void processIncomingFrame(CANFrame *frame); + + /** + * @brief starts the device + */ + void piStart(); + + /** + * @brief stops the device + */ + void piStop(); +}; + +#endif // FRAMESENDEROBJECT_H diff --git a/framesenderwindow.cpp b/framesenderwindow.cpp index a6e867cc..cd4b32f6 100644 --- a/framesenderwindow.cpp +++ b/framesenderwindow.cpp @@ -63,8 +63,8 @@ void FrameSenderWindow::setupGrid() ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_EXT, 50); ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_REM, 50); ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_DATA, 220); - ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_TRIGGER, 220); - ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_MODS, 220); + ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_TRIGGER, 270); + ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_MODS, 270); ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_COUNT, 80); ui->tableSender->setHorizontalHeaderLabels(headers); } @@ -693,6 +693,12 @@ void FrameSenderWindow::processModifierText(int line) //Example line: //d0 = D0 + 1,d1 = id:0x200:d3 + id:0x200:d4 AND 0xF0 - Original version //D0=D0+1,D1=ID:0x200:D3+ID:0x200:D4&0xF0 + + //[BMS_TargetVoltage]=45 would set the value of signal BMS_TargetVoltage to 45 + + //[BMS_TargetVoltage]=[0x234:BMS_CurrentVoltage] + 4 would instead grab the value + //of BMS_CurrentVoltage from ID 0x234, add 4 to it, and set BMS_TargetVoltage to that value. + //This is certainly much harder to parse than the trigger definitions. //the left side of the = has to be D0 to D7. After that there is a string of //data. Spaces used to be required but no longer are. This makes parsing harder but data entry easier @@ -710,22 +716,33 @@ void FrameSenderWindow::processModifierText(int line) Modifier thisMod; thisMod.destByte = 0; - QString leftSide = Utility::grabAlphaNumeric(mods[i]); - if (leftSide.startsWith("D") && leftSide.length() == 2) + QRegularExpression regex; + QRegularExpressionMatch match; + + regex.setPattern("^\\[(\\w+)]=(\\w+)"); + match = regex.match(mods[i]); + if (match.hasMatch()) { - thisMod.destByte = leftSide.right(1).toInt(); + thisMod.destByte = -1; + thisMod.signalName = match.captured(1); + mods[i] = match.captured(2); thisMod.operations.clear(); } else { - qDebug() << "Something wrong with lefthand val"; - continue; - } - if (!(Utility::grabOperation(mods[i]) == "=")) - { - qDebug() << "Err: No = after lefthand val"; - continue; + QString leftSide = Utility::grabAlphaNumeric(mods[i]); + if (leftSide.startsWith("D") && leftSide.length() == 2) + { + thisMod.destByte = leftSide.right(1).toInt(); + thisMod.operations.clear(); + if (!(Utility::grabOperation(mods[i]) == "=")) + { + qDebug() << "Err: No = after lefthand val"; + continue; + } + } } + abort = false; token = Utility::grabAlphaNumeric(mods[i]); diff --git a/mainwindow.cpp b/mainwindow.cpp index 8b14149f..bcc65cf7 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -164,6 +164,8 @@ MainWindow::MainWindow(QWidget *parent) : connect(ui->btnExpandAll, &QAbstractButton::clicked, this, &MainWindow::expandAllRows); connect(ui->btnCollapseAll, &QAbstractButton::clicked, this, &MainWindow::collapseAllRows); + connect(ui->tableSimpleSender, SIGNAL(cellChanged(int,int)), this, SLOT(onSenderCellChanged(int,int))); + lbStatusConnected.setText(tr("Connected to 0 buses")); lbHelp.setText(tr("Press F1 on any screen for help")); lbHelp.setAlignment(Qt::AlignCenter); @@ -227,12 +229,35 @@ MainWindow::MainWindow(QWidget *parent) : ui->actionMotorControlConfig->setVisible(false); ui->actionSingle_Multi_State_2->setVisible(false); + QStringList headers; + headers << "En" << "Bus" << "ID" << "Len" << "Ext" << "Rem" << "Data" + << "Interval" << "Count"; + ui->tableSimpleSender->setColumnCount(9); + ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_EN, 70); + ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_BUS, 70); + ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_ID, 70); + ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_LEN, 70); + ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_EXT, 70); + ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_REM, 70); + ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_DATA, 300); + ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_INTERVAL, 100); + ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_COUNT, 100); + ui->tableSimpleSender->setHorizontalHeaderLabels(headers); + + createSenderRow(); + + frameSender = new FrameSenderObject(model->getListReference()); + + frameSender->initialize(); //creates the thread and sets things up + frameSender->startSending(); //start the timer in the object so enabled things can send + installEventFilter(this); } MainWindow::~MainWindow() { updateTimer.stop(); + frameSender->stopSending(); killEmAll(); //Ride the lightning delete ui; delete model; @@ -438,6 +463,174 @@ void MainWindow::writeSettings() } } +void MainWindow::onSenderCellChanged(int row, int col) +{ + if (inhibitSenderChanged) return; + qDebug() << "onCellChanged"; + if (row == ui->tableSimpleSender->rowCount() - 1) + { + createSenderRow(); + } + + processSenderCellChange(row, col); +} + +void MainWindow::processSenderCellChange(int line, int col) +{ + qDebug() << "processSenderCellChange"; + FrameSendData *tempData; + QStringList tokens; + int tempVal; + + int numBuses = CANConManager::getInstance()->getNumBuses(); + QByteArray arr; + + tempData = frameSender->getSendRecordRef(line); + + if (!tempData) + { + FrameSendData dat; + frameSender->addSendRecord(dat); + tempData = frameSender->getSendRecordRef(line); + } + + if (!tempData) + { + qDebug() << "No data to modify in processSenderCellChange. This is a bug!"; + return; + } + + switch (col) + { + case SIMP_COL::SC_COL_EN: //Enable check box + if (ui->tableSimpleSender->item(line, 0)->checkState() == Qt::Checked) + { + tempData->enabled = true; + } + else tempData->enabled = false; + qDebug() << "Setting enabled to " << tempData->enabled; + break; + case SIMP_COL::SC_COL_BUS: //Bus designation + tempVal = Utility::ParseStringToNum(ui->tableSimpleSender->item(line, SIMP_COL::SC_COL_BUS)->text()); + if (tempVal < -1) tempVal = -1; + if (tempVal >= numBuses) tempVal = numBuses - 1; + tempData->bus = tempVal; + qDebug() << "Setting bus to " << tempVal; + break; + case SIMP_COL::SC_COL_ID: //ID field + tempVal = Utility::ParseStringToNum(ui->tableSimpleSender->item(line, SIMP_COL::SC_COL_ID)->text()); + if (tempVal < 0) tempVal = 0; + if (tempVal > 0x7FFFFFFF) tempVal = 0x7FFFFFFF; + tempData->setFrameId(tempVal); + if (tempData->frameId() > 0x7FF) { + tempData->setExtendedFrameFormat(true); + ui->tableSimpleSender->blockSignals(true); + ui->tableSimpleSender->item(line, ST_COLS::SENDTAB_COL_EXT)->setCheckState(Qt::Checked); + ui->tableSimpleSender->blockSignals(false); + } + qDebug() << "setting ID to " << tempVal; + break; + case SIMP_COL::SC_COL_LEN: + tempVal = Utility::ParseStringToNum(ui->tableSimpleSender->item(line, SIMP_COL::SC_COL_LEN)->text()); + if (tempVal < 0) tempVal = 0; + if (tempVal > 8) tempVal = 8; + arr.resize(tempVal); + tempData->setPayload(arr); + break; + case SIMP_COL::SC_COL_EXT: + if (ui->tableSimpleSender->item(line, SIMP_COL::SC_COL_EXT)->checkState() == Qt::Checked) { + tempData->setExtendedFrameFormat(true); + } else { + tempData->setExtendedFrameFormat(false); + } + break; + case SIMP_COL::SC_COL_REM: + if (ui->tableSimpleSender->item(line, SIMP_COL::SC_COL_REM)->checkState() == Qt::Checked) { + tempData->setFrameType(QCanBusFrame::RemoteRequestFrame); + } else { + tempData->setFrameType(QCanBusFrame::DataFrame); + } + break; + case SIMP_COL::SC_COL_DATA: //Data bytes + for (int i = 0; i < 8; i++) tempData->payload().data()[i] = 0; + +#if QT_VERSION >= QT_VERSION_CHECK( 5, 14, 0 ) + tokens = ui->tableSimpleSender->item(line, ST_COLS::SENDTAB_COL_DATA)->text().split(" ", Qt::SkipEmptyParts); +#else + tokens = ui->tableSender->item(line, ST_COLS::SENDTAB_COL_DATA)->text().split(" ", QString::SkipEmptyParts); +#endif + arr.clear(); + arr.reserve(tokens.count()); + for (int j = 0; j < tokens.count(); j++) + { + arr.append((uint8_t)Utility::ParseStringToNum(tokens[j])); + } + tempData->setPayload(arr); + break; + case SIMP_COL::SC_COL_INTERVAL: //interval in ms + + QString trigger = ui->tableSimpleSender->item(line, ST_COLS::SENDTAB_COL_TRIGGER)->text().toUpper(); + + Trigger thisTrigger; + thisTrigger.bus = -1; //-1 means we don't care which + thisTrigger.ID = -1; //the rest of these being -1 means nothing has changed it + thisTrigger.maxCount = -1; + thisTrigger.milliseconds = -1; + thisTrigger.currCount = 0; + thisTrigger.msCounter = 0; + thisTrigger.triggerMask = 0; + thisTrigger.readyCount = true; + + tempData->triggers.clear(); + tempData->triggers.reserve(1); + + if (trigger != "") + { + thisTrigger.milliseconds = Utility::ParseStringToNum(trigger); + thisTrigger.triggerMask |= TriggerMask::TRG_MS; + } + + if (thisTrigger.milliseconds < 1) thisTrigger.milliseconds = 1; + + tempData->triggers.append(thisTrigger); + + break; + } +} + + +void MainWindow::createSenderRow() +{ + int row = ui->tableSimpleSender->rowCount(); + ui->tableSimpleSender->insertRow(row); + + QTableWidgetItem *item = new QTableWidgetItem(); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(Qt::Unchecked); + inhibitSenderChanged = true; + ui->tableSimpleSender->setItem(row, SIMP_COL::SC_COL_EN, item); + + item = new QTableWidgetItem(); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(Qt::Unchecked); + ui->tableSimpleSender->setItem(row, SIMP_COL::SC_COL_EXT, item); + + item = new QTableWidgetItem(); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(Qt::Unchecked); + ui->tableSimpleSender->setItem(row, SIMP_COL::SC_COL_REM, item); + + for (int i = 1; i <= SIMP_COL::SC_COL_COUNT; i++) + { + if (i != SIMP_COL::SC_COL_EXT && i != SIMP_COL::SC_COL_REM) { + item = new QTableWidgetItem(""); + ui->tableSimpleSender->setItem(row, i, item); + } + } + + inhibitSenderChanged = false; +} + void MainWindow::updateConnectionSettings(QString connectionType, QString port, int speed0, int speed1) { Q_UNUSED(connectionType); @@ -1371,6 +1564,11 @@ CANFrameModel* MainWindow::getCANFrameModel() return model; } + +/* + * All functions past this point set up the various other windows that can be opened +*/ + void MainWindow::showSettingsDialog() { if (!settingsDialog) diff --git a/mainwindow.h b/mainwindow.h index dbc7cdfc..29daf73e 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -10,7 +10,7 @@ #include "framefileio.h" #include "dbc/dbchandler.h" #include "bus_protocols/isotp_handler.h" - +#include "framesenderobject.h" #include "re/graphingwindow.h" #include "re/frameinfowindow.h" #include "frameplaybackwindow.h" @@ -40,6 +40,19 @@ class ConnectionWindow; class ISOTP_InterpreterWindow; class ScriptingWindow; +enum SIMP_COL +{ + SC_COL_EN = 0, + SC_COL_BUS = 1, + SC_COL_ID = 2, + SC_COL_LEN = 3, + SC_COL_EXT = 4, + SC_COL_REM = 5, + SC_COL_DATA = 6, + SC_COL_INTERVAL = 7, + SC_COL_COUNT = 8, +}; + namespace Ui { class MainWindow; } @@ -114,6 +127,7 @@ private slots: void filterClearAll(); void headerClicked (int logicalIndex); void DBCSettingsUpdated(); + void onSenderCellChanged(int, int); public slots: void gotFrames(int); @@ -142,6 +156,7 @@ public slots: QByteArray inputBuffer; QTimer updateTimer; QElapsedTimer *elapsedTime; + FrameSenderObject *frameSender; int framesPerSec; int rxFrames; bool inhibitFilterUpdate; @@ -151,6 +166,7 @@ public slots: bool CSVAbsTime; bool bDirty; //have frames been added or subtracted since the last save/load? bool useFiltered; //should sub-windows use the unfiltered or filtered frames list? + bool inhibitSenderChanged; bool continuousLogging; int continuousLogFlushCounter; @@ -211,6 +227,8 @@ public slots: bool eventFilter(QObject *obj, QEvent *event); void manageRowExpansion(); void disableAutoRowExpansion(); + void createSenderRow(); + void processSenderCellChange(int line, int col); }; #endif // MAINWINDOW_H diff --git a/ui/framesenderwindow.ui b/ui/framesenderwindow.ui index 4e027bdc..0f0bebf7 100644 --- a/ui/framesenderwindow.ui +++ b/ui/framesenderwindow.ui @@ -6,7 +6,7 @@ 0 0 - 1008 + 1350 611 diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index 3b719915..80af4a8f 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -6,8 +6,8 @@ 0 0 - 800 - 682 + 1177 + 717 @@ -26,340 +26,347 @@ SavvyCAN - - - - - - 11 - false - - - - true - - - QAbstractItemView::ScrollPerPixel - - - false - - - + - + - - - true - - - Remote Connection Active: - - - Qt::AlignCenter - - - - - - - true - - - - 1000 - 50 - - - - - 150 - 50 - - - - - false - - - - 8 - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - true - - - - - - - - - - - - 255 - 0 - 0 - - - - - - - - - 255 - 0 - 0 - - - - - - - - - 120 - 120 - 120 - - - - - - + 11 - true - true + false - - - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - Total Frames Captured: - - - - - - - - 0 - 0 - - - - - - - - - - - - 0 - 0 - - - - Frames Per Second: - - - - - - - - 0 - 0 - - - - - - - - - - - Suspend Capturing - - - false - - - - - - - Normalize Frame Timing - - - - - - - Clear Frames - - - - - - - Keep Filters When Clearing - - - - - - - Auto Scroll Window - - - - - - + true - - Overwrite Mode - - - - - - - Qt::Horizontal - - - - - - - Interpret Frames - - - - - - - Expand All Rows - - - - - - - Collapse All Rows - - - - - - - Bus Filtering: - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - 175 - 40 - - - - QListView::LeftToRight + + QAbstractItemView::ScrollPerPixel - - - - - - Frame Filtering: - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - 0 - 160 - - - - - 175 - 16777215 - - - + false - + + + + + true + + + Remote Connection Active: + + + Qt::AlignCenter + + + + + + + true + + + + 1000 + 50 + + + + + 150 + 50 + + + + + false + + + + 8 + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + true + + + - + + + + + + + + 255 + 0 + 0 + + + + + + + + + 255 + 0 + 0 + + + + + + + + + 120 + 120 + 120 + + + + + + + + + 11 + true + true + + + + + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + - All + Total Frames Captured: + + + + + + + + 0 + 0 + + + + + + + + + + + + 0 + 0 + + + + Frames Per Second: + + + + + + + + 0 + 0 + + + + + + + + + + + Suspend Capturing + + + false + + + + + + + Normalize Frame Timing + + + + + + + Clear Frames - + - None + Keep Filters When Clearing + + + + Auto Scroll Window + + + + + + + true + + + Overwrite Mode + + + + + + + Qt::Horizontal + + + + + + + Interpret Frames + + + + + + + Expand All Rows + + + + + + + Collapse All Rows + + + + + + + Bus Filtering: + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 175 + 40 + + + + QListView::LeftToRight + + + + + + + Frame Filtering: + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 0 + 160 + + + + + 175 + 16777215 + + + + false + + + + + + + + + All + + + + + + + None + + + + + + + + @@ -367,7 +374,7 @@ 0 0 - 800 + 1177 26 From d5a94a1ebee8479edbdfdc2c477e470bf8dfcc1d Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Tue, 13 Feb 2024 21:52:52 -0500 Subject: [PATCH 23/27] Sender at bottom of main window is now operational. --- framesenderobject.cpp | 18 +++++++++------- mainwindow.cpp | 36 +++++++++++++++++++------------- mainwindow.h | 11 +++++----- ui/mainwindow.ui | 48 +++++++++++++++++++++++-------------------- 4 files changed, 64 insertions(+), 49 deletions(-) diff --git a/framesenderobject.cpp b/framesenderobject.cpp index 92127474..f9eb4688 100644 --- a/framesenderobject.cpp +++ b/framesenderobject.cpp @@ -154,12 +154,15 @@ void FrameSenderObject::timerTriggered() sendingList.clear(); if(mutex.tryLock()) { - //get elapsed microseconds since last tick (in case timer skips or is otherwise inaccurate, though there are no guarantees about elapsed timer either) - quint64 elapsed = sendingElapsed.nsecsElapsed() / 1000; + /* + * Requested tick interval was 1ms but the actual interval could be wildly different. So, we track + * by counting microseconds and accumulating. This creates decent stability even for long intervals + */ + quint64 elapsed = sendingElapsed.nsecsElapsed() / 1000ul; if (elapsed == 0) elapsed = 1; sendingElapsed.start(); sendingLastTimeStamp += elapsed; - //qDebug() << playbackLastTimeStamp; + //qDebug() << sendingLastTimeStamp; //qDebug() << "El: " << elapsed; statusCounter++; for (int i = 0; i < sendingData.count(); i++) @@ -178,19 +181,20 @@ void FrameSenderObject::timerTriggered() } if (sendData->triggers.count() == 0) { - qDebug() << "No triggers to process"; + //qDebug() << "No triggers to process"; break; } for (int j = 0; j < sendData->triggers.count(); j++) { trigger = &sendData->triggers[j]; - if (trigger->currCount >= trigger->maxCount) continue; //don't process if we've sent max frames we were supposed to + //if ( (trigger->currCount >= trigger->maxCount) || (trigger->maxCount == -1) ) continue; //don't process if we've sent max frames we were supposed to if (!trigger->readyCount) continue; //don't tick if not ready to tick //is it time to fire? trigger->msCounter += elapsed; //gives proper tracking even if timer doesn't fire as fast as it should - if (trigger->msCounter >= trigger->milliseconds) + //qDebug() << trigger->msCounter; + if (trigger->msCounter >= (trigger->milliseconds * 1000)) { - trigger->msCounter -= trigger->milliseconds; + trigger->msCounter -= (trigger->milliseconds * 1000); sendData->count++; trigger->currCount++; doModifiers(i); diff --git a/mainwindow.cpp b/mainwindow.cpp index bcc65cf7..9e6fea3a 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -230,13 +230,12 @@ MainWindow::MainWindow(QWidget *parent) : ui->actionSingle_Multi_State_2->setVisible(false); QStringList headers; - headers << "En" << "Bus" << "ID" << "Len" << "Ext" << "Rem" << "Data" + headers << "En" << "Bus" << "ID" << "Ext" << "Rem" << "Data" << "Interval" << "Count"; - ui->tableSimpleSender->setColumnCount(9); + ui->tableSimpleSender->setColumnCount(8); ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_EN, 70); ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_BUS, 70); ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_ID, 70); - ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_LEN, 70); ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_EXT, 70); ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_REM, 70); ui->tableSimpleSender->setColumnWidth(SIMP_COL::SC_COL_DATA, 300); @@ -489,7 +488,12 @@ void MainWindow::processSenderCellChange(int line, int col) if (!tempData) { + qDebug() << "Need to set up a new entry in senders"; FrameSendData dat; + dat.enabled = false; + dat.count = 0; + dat.frameCount = 0; + dat.bus = 0; frameSender->addSendRecord(dat); tempData = frameSender->getSendRecordRef(line); } @@ -530,13 +534,6 @@ void MainWindow::processSenderCellChange(int line, int col) } qDebug() << "setting ID to " << tempVal; break; - case SIMP_COL::SC_COL_LEN: - tempVal = Utility::ParseStringToNum(ui->tableSimpleSender->item(line, SIMP_COL::SC_COL_LEN)->text()); - if (tempVal < 0) tempVal = 0; - if (tempVal > 8) tempVal = 8; - arr.resize(tempVal); - tempData->setPayload(arr); - break; case SIMP_COL::SC_COL_EXT: if (ui->tableSimpleSender->item(line, SIMP_COL::SC_COL_EXT)->checkState() == Qt::Checked) { tempData->setExtendedFrameFormat(true); @@ -555,9 +552,9 @@ void MainWindow::processSenderCellChange(int line, int col) for (int i = 0; i < 8; i++) tempData->payload().data()[i] = 0; #if QT_VERSION >= QT_VERSION_CHECK( 5, 14, 0 ) - tokens = ui->tableSimpleSender->item(line, ST_COLS::SENDTAB_COL_DATA)->text().split(" ", Qt::SkipEmptyParts); + tokens = ui->tableSimpleSender->item(line, SIMP_COL::SC_COL_DATA)->text().split(" ", Qt::SkipEmptyParts); #else - tokens = ui->tableSender->item(line, ST_COLS::SENDTAB_COL_DATA)->text().split(" ", QString::SkipEmptyParts); + tokens = ui->tableSimpleSender->item(line, SIMP_COL::SC_COL_DATA)->text().split(" ", QString::SkipEmptyParts); #endif arr.clear(); arr.reserve(tokens.count()); @@ -569,7 +566,7 @@ void MainWindow::processSenderCellChange(int line, int col) break; case SIMP_COL::SC_COL_INTERVAL: //interval in ms - QString trigger = ui->tableSimpleSender->item(line, ST_COLS::SENDTAB_COL_TRIGGER)->text().toUpper(); + QString trigger = ui->tableSimpleSender->item(line, SIMP_COL::SC_COL_INTERVAL)->text().toUpper(); Trigger thisTrigger; thisTrigger.bus = -1; //-1 means we don't care which @@ -598,7 +595,6 @@ void MainWindow::processSenderCellChange(int line, int col) } } - void MainWindow::createSenderRow() { int row = ui->tableSimpleSender->rowCount(); @@ -1018,6 +1014,18 @@ void MainWindow::tickGUIUpdate() } } + //refresh the count for all the frame senders + FrameSendData *tempData; + int numRows = ui->tableSimpleSender->rowCount(); + for (int i = 0; i < numRows; i++) + { + tempData = frameSender->getSendRecordRef(i); + if (tempData) + { + ui->tableSimpleSender->item(i, SIMP_COL::SC_COL_COUNT)->setText(QString::number( tempData->count )); + } + } + rxFrames = 0; //} } diff --git a/mainwindow.h b/mainwindow.h index 29daf73e..31df02c1 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -45,12 +45,11 @@ enum SIMP_COL SC_COL_EN = 0, SC_COL_BUS = 1, SC_COL_ID = 2, - SC_COL_LEN = 3, - SC_COL_EXT = 4, - SC_COL_REM = 5, - SC_COL_DATA = 6, - SC_COL_INTERVAL = 7, - SC_COL_COUNT = 8, + SC_COL_EXT = 3, + SC_COL_REM = 4, + SC_COL_DATA = 5, + SC_COL_INTERVAL = 6, + SC_COL_COUNT = 7, }; namespace Ui { diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index 80af4a8f..8828f21b 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -26,27 +26,34 @@ SavvyCAN - + - + - - - - 11 - false - - - - true - - - QAbstractItemView::ScrollPerPixel - - - false - - + + + + + + 11 + false + + + + true + + + QAbstractItemView::ScrollPerPixel + + + false + + + + + + + @@ -364,9 +371,6 @@ - - - From b363917a3e80965671812c650e89b68af76ff173 Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Mon, 18 Mar 2024 21:13:02 -0400 Subject: [PATCH 24/27] Fix issue where graph windows would be corrupted if a new file was loaded while graphs were visible. --- re/graphingwindow.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/re/graphingwindow.cpp b/re/graphingwindow.cpp index d2f25812..80b32d5a 100644 --- a/re/graphingwindow.cpp +++ b/re/graphingwindow.cpp @@ -1333,7 +1333,8 @@ void GraphingWindow::createGraph(GraphParams ¶ms, bool createGraphParam) for (int i = 0; i < modelFrames->count(); i++) { CANFrame thisFrame = modelFrames->at(i); - if (thisFrame.frameId() == params.ID && thisFrame.frameType() == QCanBusFrame::DataFrame && ( ( params.bus == -1) || (params.bus == thisFrame.bus) ) ) frameCache.append(thisFrame); + if ( (thisFrame.frameId() == params.ID) && (thisFrame.frameType() == QCanBusFrame::DataFrame) + && ( ( params.bus == -1) || (params.bus == thisFrame.bus) ) ) frameCache.append(thisFrame); } //to fix weirdness where a graph that has no data won't be able to be edited, selected, or deleted properly @@ -1352,6 +1353,8 @@ void GraphingWindow::createGraph(GraphParams ¶ms, bool createGraphParam) int numEntries = frameCache.count() / params.stride; if (numEntries < 1) numEntries = 1; //could happen if stride is larger than frame count + params.x.clear(); + params.y.clear(); params.x.reserve(numEntries); params.y.reserve(numEntries); //params.x.fill(0, numEntries); From 3018f8426adcfe069f8b868b7376723a4f6058a1 Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Mon, 18 Mar 2024 21:48:27 -0400 Subject: [PATCH 25/27] Some tweaking to try to make the filters area of the main screen a little wider --- ui/mainwindow.ui | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index 8828f21b..72d9cec1 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -48,6 +48,9 @@ false + + false + @@ -327,20 +330,20 @@ - + 0 0 - 0 + 300 160 - 175 + 1000 16777215 From 77f889e4d765d82242f200f4c1187f99a67eff9e Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Mon, 18 Mar 2024 21:52:22 -0400 Subject: [PATCH 26/27] Fix signal viewer so that it handles multiplexed signals properly --- signalviewerwindow.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/signalviewerwindow.cpp b/signalviewerwindow.cpp index f21c7f9c..c31f0fb3 100644 --- a/signalviewerwindow.cpp +++ b/signalviewerwindow.cpp @@ -98,15 +98,18 @@ void SignalViewerWindow::processFrame(CANFrame &frame) if (!sig) return; if (sig->parentMessage->ID == frame.frameId()) { - if (sig->processAsText(frame, sigString, false)) //if true we could interpret the signal so update it in the list + if (sig->isSignalInMessage(frame)) //filter out multiplexed signals that aren't in this message. { - QTableWidgetItem *item = ui->tableViewer->item(i, VALUE_COL); - if (!item) + if (sig->processAsText(frame, sigString, false)) //if true we could interpret the signal so update it in the list { - item = new QTableWidgetItem(sigString); - ui->tableViewer->setItem(i, VALUE_COL, item); + QTableWidgetItem *item = ui->tableViewer->item(i, VALUE_COL); + if (!item) + { + item = new QTableWidgetItem(sigString); + ui->tableViewer->setItem(i, VALUE_COL, item); + } + else item->setText(sigString); } - else item->setText(sigString); } } } From 0447ec1435e98420f9afaca0a5e55728e02278e0 Mon Sep 17 00:00:00 2001 From: Collin Kidder Date: Thu, 28 Mar 2024 19:07:21 -0400 Subject: [PATCH 27/27] Update readme --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e4cc5cae..cdcdbb59 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SavvyCAN Qt based cross platform canbus tool -(C) 2015-2021 EVTV and Collin Kidder +(C) 2015-2024 Collin Kidder A Qt5 based cross platform tool which can be used to load, save, and capture canbus frames. This tool is designed to help with visualization, reverse engineering, debugging, and @@ -17,9 +17,6 @@ The CANDue board must be running the GVRET firmware which can also be found within the collin80 repos. It is now possible to use any Qt SerialBus driver (socketcan, Vector, PeakCAN, TinyCAN). -There may, however, be some loss of some functionality as -some functions of SavvyCAN are designed for use directly with the -EVTVDue and CANDue 2.0 boards. It should, however, be noted that use of a capture device is not required to make use of this program. It can load and save in several formats: @@ -50,8 +47,9 @@ to download it separately. This project requires 5.14.0 or higher because of a dependency on QtSerialBus and other new additions to Qt. -NOTE: Qt6 currently lacks support for QtSerialBus and many other Qt sub-features. At this time you cannot -use Qt6 to compile SavvyCAN. Support for Qt6 should be possible around Qt6.2. +NOTE: As the code in this master branch sits, it will not properly compile with QT6. +However, there is a QT6WIP branch that should successfully compile. The QT6WIP branch may not +function entirely properly yet. ## Instructions for compiling: