diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 59de349..1b16305 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,18 +8,18 @@ on: jobs: buildlinux: name: Linux x64 - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 steps: - name: Prepare Environment run: | - sudo apt-get install libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-randr0 + sudo apt-get install libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-randr0 libxkbcommon-x11-0 - name: Prepare Qt Libraries - uses: jurplel/install-qt-action@v2 + uses: jurplel/install-qt-action@v3 - name: Clone - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Compile run: | @@ -29,18 +29,18 @@ jobs: - name: Package run: | make INSTALL_ROOT=appdir install - wget -c -nv "https://github.com/probonopd/linuxdeployqt/releases/download/5/linuxdeployqt-5-x86_64.AppImage" - chmod a+x linuxdeployqt-5-x86_64.AppImage - ./linuxdeployqt-5-x86_64.AppImage appdir/usr/share/applications/SavvyCAN.desktop -appimage -extra-plugins=iconengines,platformthemes/libqgtk3.so,canbus + wget -c -nv "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage" + chmod a+x linuxdeployqt-continuous-x86_64.AppImage + ./linuxdeployqt-continuous-x86_64.AppImage appdir/usr/share/applications/SavvyCAN.desktop -appimage -extra-plugins=iconengines,platformthemes/libqgtk3.so,canbus - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: SavvyCAN-Linux_x64.AppImage - path: SavvyCAN-x86_64.AppImage + path: SavvyCAN-*x86_64.AppImage buildmacos: name: macOS x64 - runs-on: macos-10.15 + runs-on: macos-12 steps: - name: Prepare macOS Environment @@ -49,7 +49,7 @@ jobs: brew link qt5 --force - name: Clone - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Compile run: | @@ -62,7 +62,7 @@ jobs: cp -R help/* SavvyCAN.app/Contents/MacOS/help macdeployqt SavvyCAN.app -dmg - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: SavvyCAN-macOS_x64.dmg path: SavvyCAN.dmg @@ -73,10 +73,10 @@ jobs: steps: - name: Prepare Qt Libraries - uses: jurplel/install-qt-action@v2 + uses: jurplel/install-qt-action@v3 - name: Clone - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Compile shell: cmd @@ -113,7 +113,7 @@ jobs: mkdir package/canbus copy "${Env:Qt5_Dir}/plugins/canbus/*.*" package/canbus/ - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: SavvyCAN-Windows_x64 path: package @@ -124,12 +124,12 @@ jobs: needs: [buildwindows, buildmacos, buildlinux] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v3 - name: Display structure of downloaded files - run: zip -r SavvyCAN_CIBuild.zip SavvyCAN-Windows_x64 + run: zip -r SavvyCAN-Windows_x64_CIBuild.zip SavvyCAN-Windows_x64 - uses: "marvinpinto/action-automatic-releases@latest" with: repo_token: "${{ secrets.GITHUB_TOKEN }}" @@ -137,8 +137,8 @@ jobs: prerelease: true title: "Development Build" files: | - SavvyCAN-Linux_x64.AppImage/SavvyCAN-x86_64.AppImage - SavvyCAN_CIBuild.zip + SavvyCAN-Linux_x64.AppImage/SavvyCAN-*x86_64.AppImage + SavvyCAN-Windows_x64_CIBuild.zip SavvyCAN-macOS_x64.dmg/SavvyCAN.dmg notify: @@ -155,6 +155,8 @@ jobs: ) release_msg="${release_msg//$'\n'/'-'}" release_msg="${release_msg//$'\r'/'-'}" + release_msg="${release_msg//$'('/'-'}" + release_msg="${release_msg//$')'/'-'}" echo $release_msg echo "::set-output name=msg::$release_msg" diff --git a/.gitignore b/.gitignore index 66d90e6..13068cd 100644 --- a/.gitignore +++ b/.gitignore @@ -30,9 +30,19 @@ build/ *.out *.app *.user +SavvyCAN # Generated files **/moc_* **/ui_*.h **/qrc_*.cpp SavvyCAN.pro.user.* +.qmake.stash +Makefile + +# Editor files +*.code-workspace +SavvyCAN.pro.qtds +.xcode +SavvyCAN.xcodeproj +.vscode diff --git a/README.md b/README.md index 7cb84a6..0574fef 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 +Qt based cross platform canbus tool +(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 @@ -10,16 +10,13 @@ Please use the "Discussions" tab here on GitHub to ask questions and interact wi Requires a resolution of at least 1024x768. Fully multi-monitor capable. Works on 4K monitors as well. -You are highly recommended to use the +You are highly recommended to use the [CANDue board from EVTV](http://store.evtv.me/proddetail.php?prod=ArduinoDueCANBUS&cat=23). 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: @@ -43,15 +40,16 @@ from the Qt installer. Uses QCustomPlot available at: -http://www.qcustomplot.com/ +http://www.qcustomplot.com/ -However, this source code is integrated into the source for SavvyCAN and one isn't required +However, this source code is integrated into the source for SavvyCAN and one isn't required to download it separately. -This project requires 5.14.0 or higher because of a dependency on QtSerialBus and other new additions to Qt. +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 does compile with QT6. Support for QT6 is approximately "beta" quality. Most all functions should work, please send issues if found. + +It appears that the current binary build for MacOS requires at least MacOS 10.15 ## Instructions for compiling: @@ -75,6 +73,8 @@ Now run SavvyCAN ./SavvyCAN ``` +On linux systems you can run `./install.sh` to create a desktop shortcut. + ### Compiling in debug mode for additional information ```sh @@ -100,7 +100,7 @@ when you installed Qt. ### What to do if `qmake` fails with error `Project ERROR: Unknown module(s) in QT: qml serialbus help` on Ubuntu? : -`sudo apt install libqt5serialbus5-dev qtdeclarative5-dev qttools5-dev` +`sudo apt install libqt5serialbus5-dev libqt5serialport5-dev qtdeclarative5-dev qttools5-dev` ### Used Items Requiring Attribution @@ -115,4 +115,3 @@ signal by juli from the Noun Project signal by yudi from the Noun Project Death by Adrien Coquet from the Noun Project - diff --git a/SavvyCAN.pro b/SavvyCAN.pro index 9df479e..5512e42 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 @@ -20,7 +20,13 @@ QMAKE_INFO_PLIST = Info.plist.template ICON = icons/SavvyIcon.icns SOURCES += main.cpp\ + canbridgewindow.cpp \ + connections/canlogserver.cpp \ + connections/canserver.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 \ @@ -38,6 +44,7 @@ SOURCES += main.cpp\ mainwindow.cpp \ canframemodel.cpp \ simplecrypt.cpp \ + triggerdialog.cpp \ utility.cpp \ qcustomplot.cpp \ frameplaybackwindow.cpp \ @@ -67,6 +74,7 @@ SOURCES += main.cpp\ dbc/dbcmaineditor.cpp \ dbc/dbcnodeeditor.cpp \ dbc/dbcsignaleditor.cpp \ + dbc/dbcnoderebaseeditor.cpp \ re/discretestatewindow.cpp \ re/filecomparatorwindow.cpp \ re/flowviewwindow.cpp \ @@ -97,9 +105,16 @@ SOURCES += main.cpp\ HEADERS += mainwindow.h \ can_structs.h \ + canbridgewindow.h \ canframemodel.h \ + connections/canlogserver.h \ + connections/canserver.h \ + connections/lawicel_serial.h \ connections/socketcand.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 \ @@ -122,6 +137,7 @@ HEADERS += mainwindow.h \ qcpaxistickerhex.h \ re/dbccomparatorwindow.h \ simplecrypt.h \ + triggerdialog.h \ utility.h \ qcustomplot.h \ frameplaybackwindow.h \ @@ -183,11 +199,15 @@ HEADERS += mainwindow.h \ pcaplite.h FORMS += ui/candatagrid.ui \ + triggerdialog.ui \ + ui/canbridgewindow.ui \ + ui/dbcnodeduplicateeditor.ui \ ui/dbccomparatorwindow.ui \ ui/dbcmessageeditor.ui \ ui/connectionwindow.ui \ ui/dbcloadsavewindow.ui \ ui/dbcmaineditor.ui \ + ui/dbcnoderebaseeditor.ui \ ui/dbcsignaleditor.ui \ ui/dbcnodeeditor.ui \ ui/discretestatewindow.ui \ @@ -210,7 +230,7 @@ FORMS += ui/candatagrid.ui \ ui/udsscanwindow.ui \ ui/bisectwindow.ui \ ui/signalviewerwindow.ui \ - helpwindow.ui \ + ui/helpwindow.ui \ ui/newconnectiondialog.ui \ ui/temporalgraphwindow.ui diff --git a/bisectwindow.cpp b/bisectwindow.cpp index 4a78520..21822ad 100644 --- a/bisectwindow.cpp +++ b/bisectwindow.cpp @@ -25,7 +25,14 @@ BisectWindow::BisectWindow(const QVector *frames, QWidget *parent) : connect(ui->slidePercentage, &QSlider::sliderReleased, this, &BisectWindow::updatePercentText); connect(ui->editFrameNumber, &QLineEdit::editingFinished, this, &BisectWindow::updateFrameNumSlider); connect(ui->editPercentage, &QLineEdit::editingFinished, this, &BisectWindow::updatePercentSlider); + connect(ui->rbFrameNumber, &QRadioButton::toggled, this, &BisectWindow::updateSectionsText); + connect(ui->rbPercentage, &QRadioButton::toggled, this, &BisectWindow::updateSectionsText); + connect(ui->rbBusNum, &QRadioButton::toggled, this, &BisectWindow::updateSectionsText); + connect(ui->rbIDRange, &QRadioButton::toggled, this, &BisectWindow::updateSectionsText); + installEventFilter(this); + + updateSectionsText(); } BisectWindow::~BisectWindow() @@ -61,6 +68,31 @@ bool BisectWindow::eventFilter(QObject *obj, QEvent *event) return false; } +void BisectWindow::updateSectionsText() +{ + if (ui->rbBusNum->isChecked()) + { + ui->rbLowerSection->setText("Only this bus"); + ui->rbUpperSection->setText("Not this bus"); + } + if (ui->rbFrameNumber->isChecked()) + { + ui->rbLowerSection->setText("Up to this frame number"); + ui->rbUpperSection->setText("After this frame number"); + } + if (ui->rbIDRange->isChecked()) + { + ui->rbLowerSection->setText("Inside the ID range"); + ui->rbUpperSection->setText("Outside the ID range"); + } + if (ui->rbPercentage->isChecked()) + { + ui->rbLowerSection->setText("Up to this percentage into the file"); + ui->rbUpperSection->setText("After this percentage into the file"); + } + +} + void BisectWindow::refreshIDList() { int id; @@ -112,11 +144,12 @@ void BisectWindow::handleCalculateButton() { splitFrames.clear(); bool saveLower = ui->rbLowerSection->isChecked(); - int targetFrameNum; + int targetFrameNum = 0; if (ui->rbFrameNumber->isChecked() || ui->rbPercentage->isChecked()) { if (ui->rbFrameNumber->isChecked()) targetFrameNum = ui->slideFrameNumber->value(); - else targetFrameNum = modelFrames->count() * ui->slidePercentage->value() / 10000; + else targetFrameNum = modelFrames->count() * (ui->slidePercentage->value() / 10000.0); + qDebug() << "Target frame num " << targetFrameNum; if (saveLower) { for (int i = 0; i < targetFrameNum; i++) splitFrames.append(modelFrames->at(i)); @@ -132,7 +165,29 @@ void BisectWindow::handleCalculateButton() uint32_t upperID = Utility::ParseStringToNum2(ui->cbIDUpper->currentText()); for (int i = 0; i < modelFrames->count(); i++) { - if (modelFrames->at(i).frameId() >= lowerID && modelFrames->at(i).frameId() <= upperID) splitFrames.append(modelFrames->at(i)); + if (modelFrames->at(i).frameId() >= lowerID && modelFrames->at(i).frameId() <= upperID) + { + if (saveLower) splitFrames.append(modelFrames->at(i)); + } + else + { + if (!saveLower) splitFrames.append(modelFrames->at(i)); + } + } + } + else if (ui->rbBusNum->isChecked()) + { + int targetBus = Utility::ParseStringToNum(ui->editBusNum->text()); + for (int i = 0; i < modelFrames->count(); i++) + { + if (modelFrames->at(i).bus == targetBus) + { + if (saveLower) splitFrames.append(modelFrames->at(i)); + } + else + { + if (!saveLower) splitFrames.append(modelFrames->at(i)); + } } } refreshFrameNumbers(); diff --git a/bisectwindow.h b/bisectwindow.h index a308428..52af811 100644 --- a/bisectwindow.h +++ b/bisectwindow.h @@ -30,6 +30,7 @@ private slots: void updatePercentSlider(); void updateFrameNumText(); void updatePercentText(); + void updateSectionsText(); private: Ui::BisectWindow *ui; diff --git a/bus_protocols/isotp_handler.cpp b/bus_protocols/isotp_handler.cpp index 249a550..1553da4 100644 --- a/bus_protocols/isotp_handler.cpp +++ b/bus_protocols/isotp_handler.cpp @@ -249,7 +249,7 @@ void ISOTP_HANDLER::processFrame(const CANFrame &frame) messageBuffer.insert(msg.frameId(), msg); //The sending ID is set to the last ID we used to send from this class which is //very likely to be correct. But, caution, there is a chance that it isn't. Beware. - if (issueFlowMsgs && lastSenderID > 0) + if (issueFlowMsgs && lastSenderID > 0 && lastSenderBus==static_cast(frame.bus)) { CANFrame outFrame; outFrame.bus = lastSenderBus; diff --git a/can_trigger_structs.h b/can_trigger_structs.h index ce58d91..177df4f 100644 --- a/can_trigger_structs.h +++ b/can_trigger_structs.h @@ -4,6 +4,17 @@ #include "can_structs.h" #include +#include + +enum TriggerMask +{ + TRG_ID = 1, + TRG_MS = 2, + TRG_COUNT = 4, + TRG_BUS = 8, + TRG_SIGNAL = 16, + TRG_SIGVAL = 32, +}; //Stores a single trigger. class Trigger @@ -16,6 +27,10 @@ public: int maxCount; //max # of these frames to trigger for int currCount; //how many we have triggered for so far. int bus; //which bus to monitor (-1 if we aren't picky) + QString sigName; //which signal to trigger on + int64_t sigValueInt; //value to trigger on (integer) + double sigValueDbl; //value to trigger on (floating point) + uint32_t triggerMask; }; //referece for a source location for a single modifier. @@ -24,6 +39,7 @@ public: //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: @@ -31,6 +47,7 @@ public: 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 @@ -59,7 +76,8 @@ public: 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/canbridgewindow.cpp b/canbridgewindow.cpp new file mode 100644 index 0000000..cb4ffb4 --- /dev/null +++ b/canbridgewindow.cpp @@ -0,0 +1,143 @@ +#include "canbridgewindow.h" +#include "helpwindow.h" +#include "qevent.h" +#include "ui_canbridgewindow.h" +#include "filterutility.h" +#include "mainwindow.h" + +CANBridgeWindow::CANBridgeWindow(const QVector *frames, QWidget *parent) : + QDialog(parent), + ui(new Ui::CANBridgeWindow) +{ + ui->setupUi(this); + + modelFrames = frames; + + side1BusNum = 0; + side2BusNum = 0; + + connect(ui->cbSide1, &QComboBox::currentTextChanged, this, &CANBridgeWindow::recalcSides); + connect(ui->cbSide2, &QComboBox::currentTextChanged, this, &CANBridgeWindow::recalcSides); + connect(MainWindow::getReference(), &MainWindow::framesUpdated, this, &CANBridgeWindow::updatedFrames); + connect(ui->listSide1, &QListWidget::itemChanged, + [this] (QListWidgetItem *item) + { + bool checked = item->checkState() == Qt::Checked ? true:false; + int IDval = FilterUtility::getIdAsInt(item); + qDebug() << "ID " << IDval << " set to " << checked; + foundIDSide1[IDval] = checked; + }); + connect(ui->listSide2, &QListWidget::itemChanged, + [this] (QListWidgetItem *item) + { + bool checked = item->checkState() == Qt::Checked ? true:false; + int IDval = FilterUtility::getIdAsInt(item); + foundIDSide2[IDval] = checked; + }); +} + +CANBridgeWindow::~CANBridgeWindow() +{ + delete ui; +} + + +void CANBridgeWindow::showEvent(QShowEvent* event) +{ + QDialog::showEvent(event); + //stuff to do every time this form is shown. + ui->cbSide1->clear(); + ui->cbSide2->clear(); + int numBuses = CANConManager::getInstance()->getNumBuses(); + for (int i = 0; i < numBuses; i++) + { + ui->cbSide1->addItem(QString::number(i)); + ui->cbSide2->addItem(QString::number(i)); + } +} + +bool CANBridgeWindow::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::KeyRelease) { + QKeyEvent *keyEvent = static_cast(event); + switch (keyEvent->key()) + { + case Qt::Key_F1: + HelpWindow::getRef()->showHelp("canbridge.md"); + break; + } + return true; + } else { + // standard event processing + return QObject::eventFilter(obj, event); + } +} + +void CANBridgeWindow::recalcSides() +{ + side1BusNum = ui->cbSide1->currentText().toInt(); + side2BusNum = ui->cbSide2->currentText().toInt(); +} + + +void CANBridgeWindow::updatedFrames(int numFrames) +{ + bool addedSide1 = false; + bool addedSide2 = false; + + if (numFrames == -1) //all frames deleted. Kill the display + { + } + else if (numFrames == -2) //all new set of frames. Reset + { + } + else //just got some new frames. See if they are relevant. + { + if (numFrames > modelFrames->count()) return; + + for (int x = modelFrames->count() - numFrames; x < modelFrames->count(); x++) + { + CANFrame thisFrame = modelFrames->at(x); + int32_t id = static_cast(thisFrame.frameId()); + + if (thisFrame.bus == side1BusNum) + { + if (!foundIDSide1.contains(id)) + { + foundIDSide1.insert(id, true); + FilterUtility::createCheckableFilterItem(id, true, ui->listSide1); + addedSide1 = true; + } + if (ui->ckEnableSide1->isChecked()) //if we're enabled to forward traffic on this bus to the other one + { + if (foundIDSide1[id]) //and the checkbox for this particular ID is checked + { + thisFrame.bus = side2BusNum; + CANConManager::getInstance()->sendFrame(thisFrame); + } + } + } + else if (thisFrame.bus == side2BusNum) + { + if (!foundIDSide2.contains(id)) + { + foundIDSide2.insert(id, true); + FilterUtility::createCheckableFilterItem(id, true, ui->listSide2); + addedSide2 = true; + } + if (ui->ckEnableSide2->isChecked()) //if we're enabled to forward traffic on this bus to the other one + { + if (foundIDSide2[id]) //and the checkbox for this particular ID is checked + { + thisFrame.bus = side1BusNum; + CANConManager::getInstance()->sendFrame(thisFrame); + } + } + } + } + //default is to sort in ascending order + if (addedSide1) ui->listSide1->sortItems(); + if (addedSide2) ui->listSide2->sortItems(); + } +} + diff --git a/canbridgewindow.h b/canbridgewindow.h new file mode 100644 index 0000000..40304a2 --- /dev/null +++ b/canbridgewindow.h @@ -0,0 +1,38 @@ +#ifndef CANBRIDGEWINDOW_H +#define CANBRIDGEWINDOW_H + +#include +#include "connections/canconmanager.h" + +namespace Ui { +class CANBridgeWindow; +} + +class CANBridgeWindow : public QDialog +{ + Q_OBJECT + +public: + explicit CANBridgeWindow(const QVector *frames, QWidget *parent = nullptr); + ~CANBridgeWindow(); + void showEvent(QShowEvent*); + + +private slots: + void updatedFrames(int); + void recalcSides(); + +private: + Ui::CANBridgeWindow *ui; + const QVector *modelFrames; + QMap foundIDSide1; + QMap foundIDSide2; + int side1BusNum; + int side2BusNum; + + void processIncomingFrame(CANFrame *frame); + bool eventFilter(QObject *obj, QEvent *event); + +}; + +#endif // CANBRIDGEWINDOW_H diff --git a/candatagrid.cpp b/candatagrid.cpp index 53e29f7..bb8cf56 100644 --- a/candatagrid.cpp +++ b/candatagrid.cpp @@ -6,6 +6,57 @@ #include #include +//The program used to generate new colors every time the grid was displayed but that's ugly +//and disorienting. Instead use this list colors I've picked because they're "bright". +//It may need a bit of reorganizing to keep like colors apart and feel free to add some more +//if you want. The program will wrap around if the number of signals in a single message exceeds +//this amount of colors. +//Colors here were generated with: https://colordesigner.io/random-color-generator set to random +//hue bright colors and 40 total colors. I clicked until I kind of liked it. +QVector signalColors = +{ + QColor(101, 104, 159), + QColor(237, 35, 109), + QColor(216, 53, 221), + QColor(37, 252, 220), + QColor(8, 160, 53), + QColor(226, 111, 11), + QColor(216, 59, 2), + QColor(249, 246, 27), + QColor(214, 21, 34), + QColor(178, 219, 74), + QColor(214, 55, 71), + QColor(80, 198, 206), + QColor(83, 214, 201), + QColor(35, 210, 237), + QColor(25, 170, 105), + QColor(110, 221, 55), + QColor(30, 38, 181), + QColor(79, 124, 209), + QColor(7, 91, 175), + QColor(14, 234, 208), + QColor(125, 45, 168), + QColor(176, 211, 80), + QColor(255, 206, 48), + QColor(202, 226, 93), + QColor(201, 99, 232), + QColor(13, 72, 135), + QColor(32, 45, 219), + QColor(197, 26, 216), + QColor(119, 15, 216), + QColor(97, 18, 150), + QColor(242, 16, 242), + QColor(177, 5, 196), + QColor(214, 10, 102), + QColor(9, 50, 155), + QColor(201, 38, 76), + QColor(202, 234, 72), + QColor(59, 191, 57), + QColor(0, 25, 170), + QColor(7, 186, 93), + QColor(29, 211, 147) +}; + CANDataGrid::CANDataGrid(QWidget *parent) : QWidget(parent), ui(new Ui::CANDataGrid) @@ -14,13 +65,17 @@ CANDataGrid::CANDataGrid(QWidget *parent) : gridMode = GridMode::CHANGED_BITS; - memset(data, 0, 8); - memset(refData, 0, 8); - memset(usedData, 0, 8); - memset(usedSignalNum, -1, 64); + memset(data, 0, 64); + memset(refData, 0, 64); + memset(usedData, 0, 64); + for (int j = 0; j < 512; j++) usedSignalNum[j] = -1; + bytesToDraw = 8; //default to the old behavior for (int x = 0; x < 8; x++) - for (int y = 0; y < 8; y++) - textStates[x][y] = GridTextState::NORMAL; + for (int y = 0; y < 64; y++) + { + textStates[y][x] = GridTextState::NORMAL; + heatData[y * 8 + x] = 0; + } blackBrush = QBrush(Qt::black); whiteBrush = QBrush(Qt::white); @@ -29,6 +84,29 @@ CANDataGrid::CANDataGrid(QWidget *parent) : greenHashBrush = QBrush(QColor(0, 0xB6, 0), Qt::BDiagPattern); blackHashBrush = QBrush(QColor(0, 0, 0), Qt::FDiagPattern); grayBrush = QBrush(QColor(230,230,230)); + xOffset = 0; + yOffset = 0; + + //generate the palette + + /* //this one looks sort of like a fire. It was my first idea but never really got to where I liked it + for(int x = 1; x < 256; x++) + { + int lum = x * 2; + if (lum < 40) lum = 40; + if (lum > 250) lum = 250; + fire[x] = QColor::fromHsl((int)(x / 3.5), 255 - ((256 - x) / 10), lum); + } + */ + + for(int x = 1; x < 256; x++) + { + int hue = 256 - x; + fire[x] = QColor::fromHsl(hue, 255, 127); + } + + //the 0th entry should always be black and really stand out from the rest + fire[0] = Qt::black; } CANDataGrid::~CANDataGrid() @@ -46,6 +124,12 @@ void CANDataGrid::setMode(GridMode mode) gridMode = mode; } +void CANDataGrid::setBytesToDraw(int num) +{ + bytesToDraw = num; + //this->update(); +} + void CANDataGrid::mousePressEvent(QMouseEvent *event) { QPoint clickedPoint = event->pos(); @@ -55,33 +139,58 @@ void CANDataGrid::mousePressEvent(QMouseEvent *event) clickedPoint -= upperLeft; if (clickedPoint.x() < 0 || clickedPoint.y() < 0) { - //qDebug() << "Clicked outside the grid you wanker"; + //qDebug() << "Clicked outside the grid"; return; } int x = clickedPoint.x() / gridSize.x(); int y = clickedPoint.y() / gridSize.y(); - //qDebug() << "Grid square clicked " << x << " " << y; - emit gridClicked(x,y); + qDebug() << "Grid square clicked " << x << " " << y; + int bitClicked = gridToBitPosition(x, y); + //this control is the ultimate authority on which bit is at which grid so what we're going to do now + //is return the actual bit so everyone else doesn't have to try to calculate it. When someone clicks + //what the parent GUI really cares about is which bit that was. + emit gridClicked(bitClicked); + } + if (event->button() == Qt::MiddleButton) //cycle through the various grid layouts for frame sizes + { + //qDebug() << "Middle Button"; + if (bytesToDraw < 9) bytesToDraw = 16; + else if (bytesToDraw < 31) bytesToDraw = 32; + else if (bytesToDraw < 63) bytesToDraw = 64; + else if (bytesToDraw > 32) bytesToDraw = 8; + this->update(); + } + + //right click is exactly like left click but the emitted signal is different so that + //external code can differentiate which button was pressed + if (event->button() == Qt::RightButton) + { + clickedPoint -= upperLeft; + if (clickedPoint.x() < 0 || clickedPoint.y() < 0) + { + return; + } + int x = clickedPoint.x() / gridSize.x(); + int y = clickedPoint.y() / gridSize.y(); + int bitClicked = gridToBitPosition(x, y); + emit gridRightClicked(bitClicked); } } -void CANDataGrid::setCellTextState(int x, int y, GridTextState state) +void CANDataGrid::setCellTextState(int bitPos, GridTextState state) { - if (x < 0) return; - if (x > 7) return; - if (y < 0) return; - if (y > 7) return; - textStates[x][y] = state; + //textStates has two dimensions but they are NOT X and Y and don't necessarily correspond to X and Y in the grid + int byte = bitPos / 8; + int bit = bitPos & 7; + textStates[byte][bit] = state; this->update(); } -GridTextState CANDataGrid::getCellTextState(int x, int y) +GridTextState CANDataGrid::getCellTextState(int bitPos) { - if (x < 0) return GridTextState::NORMAL; - if (x > 7) return GridTextState::NORMAL; - if (y < 0) return GridTextState::NORMAL; - if (y > 7) return GridTextState::NORMAL; - return textStates[x][y]; + int byte = bitPos / 8; + int bit = bitPos & 7; + return textStates[byte][bit]; } void CANDataGrid::setSignalNames(int sigIdx, const QString sigName) @@ -98,20 +207,19 @@ void CANDataGrid::clearSignalNames() { signalNames.clear(); signalNames.resize(40); - signalColors.clear(); } void CANDataGrid::setUsedSignalNum(int bit, int signal) { if (bit < 0) return; - if (bit > 63) return; + if (bit > 511) return; usedSignalNum[bit] = signal; } int CANDataGrid::getUsedSignalNum(int bit) { if (bit < 0) return 0; - if (bit > 63) return 0; + if (bit > 511) return 0; return usedSignalNum[bit]; } @@ -120,11 +228,17 @@ void CANDataGrid::paintEvent(QPaintEvent *event) Q_UNUSED(event); paintCommonBeginning(); - if (gridMode == GridMode::CHANGED_BITS) paintChangedBits(); - if (gridMode == GridMode::SIGNAL_VIEW) paintSignalView(); + paintGridCells(); paintCommonEnding(); } +/* + * CAN-FD causes the need to support these sizes: 8, 12, 16, 20, 24, 32, 48, 64 bytes. + * It's probably OK to ignore 12 and just go straight from 8 to 16 where each cell is subdivided in half along the X axis + * Then 12 is just 16 minus some bits that never can get used. Then jump to 32 drawn cells from there. That would be also + * subdividing along the Y axis. Obviously, as before, 24 is just 32 but with unusable bits. Lastly, subdivide X yet again + * so now it's in quarters. This allows for 64 bits (48 is likewise just 64 with unusable bits) +*/ void CANDataGrid::paintCommonBeginning() { int x; @@ -132,29 +246,36 @@ void CANDataGrid::paintCommonBeginning() painter = new QPainter(this); viewport = painter->viewport(); - xSpan = viewport.right() - viewport.left(); - ySpan = viewport.bottom() - viewport.top(); + neededXDivisions = 8; + neededYDivisions = 8; - qDebug() << "XSpan" << xSpan << " YSpan " << ySpan; + int textRestrict = qMax(neededXDivisions, neededYDivisions); - xSector = xSpan / 9; - ySector = ySpan / 9; - - //the whole thing is broken up into 81 chunks which are broken up - //into the 8x8 grid of bits in the bottom right and the other parts - //taken up by helper text - - if (gridMode == GridMode::CHANGED_BITS) + if (bytesToDraw > 8) { - bigTextSize = qMin(xSector, ySector) * 0.6; - smallTextSize = qMin(xSector, ySector) * 0.5; + neededXDivisions = 16; } - if (gridMode == GridMode::SIGNAL_VIEW) + if (bytesToDraw > 16) { - bigTextSize = qMin(xSector, ySector) * 0.5; - smallTextSize = qMin(xSector, ySector) * 0.25; + neededXDivisions = 16; + neededYDivisions = 16; } - sigNameTextSize = qMin(xSector, ySector) * 0.19; + if (bytesToDraw > 32) + { + neededXDivisions = 32; + } + + if (gridMode != GridMode::SIGNAL_VIEW) + { + bigTextSize = qMin(viewport.size().height(), viewport.size().width()) / (textRestrict * 3.25); + smallTextSize = qMin(viewport.size().height(), viewport.size().width()) / (textRestrict * 4.0); + } + else + { + bigTextSize = qMin(viewport.size().height(), viewport.size().width()) / (textRestrict * 3.50); + smallTextSize = qMin(viewport.size().height(), viewport.size().width()) / (textRestrict * 5.5); + } + sigNameTextSize = qMin(viewport.size().height(), viewport.size().width()) / (textRestrict * 5.0); painter->setPen(QPen(QApplication::palette().color(QPalette::Text))); mainFont.setPixelSize(bigTextSize); @@ -165,41 +286,60 @@ void CANDataGrid::paintCommonBeginning() sigNameFont.setPixelSize(sigNameTextSize); smallMetric = new QFontMetrics(sigNameFont); + largeMetric = new QFontMetrics(mainFont); + xOffset = smallMetric->maxWidth(); + yOffset = smallMetric->height(); + + xSpan = viewport.right() - viewport.left() - xOffset; + ySpan = viewport.bottom() - viewport.top() - yOffset; + + qDebug() << "XSpan" << xSpan << " YSpan " << ySpan; + + xSector = xSpan / neededXDivisions; + ySector = ySpan / neededYDivisions; + + qDebug() << "XSector " << xSector << " YSector " << ySector; + + nearX = viewport.left() + xOffset; + nearY = viewport.top() + yOffset; + farX = nearX + xSector * neededXDivisions; + farY = nearY + ySector * neededYDivisions; + + //painter->setFont(boldFont); painter->setFont(smallFont); - painter->drawText(QRect(viewport.left(), viewport.top(), xSector, ySector), Qt::AlignCenter, "BITS ->"); - painter->setFont(boldFont); - for (x = 0; x < 8; x++) + //draw grid by doing vertical and horizontal lines. This is not needed normally but helps when developing new code. Only uncomment for testing +/* + for (int y = 0; y <= neededYDivisions; y++) { - painter->drawText(QRect(viewport.left() + (x+1) * xSector, viewport.top(), xSector, ySector), Qt::AlignCenter, QString::number(7-x)); + painter->drawLine(nearX, nearY + (y * ySector), farX, nearY + (y * ySector) ); } - //for (y = 0; y < 8; y++) - //{ - // painter->drawText(QRect(viewport.left() + xSector, viewport.top() + ySector * (y + 2), xSector, ySector), Qt::AlignCenter, QString::number(y)); - //} - painter->drawText(viewport.left() + 4, viewport.top() + ySector * 3, "B"); - painter->drawText(viewport.left() + 4, viewport.top() + ySector * 4, "Y"); - painter->drawText(viewport.left() + 4, viewport.top() + ySector * 5, "T"); - painter->drawText(viewport.left() + 4, viewport.top() + ySector * 6, "E"); - painter->drawText(viewport.left() + 4, viewport.top() + ySector * 7, "S"); + for (int x = 0; x <= neededXDivisions; x++) + { + painter->drawLine(nearX + (x * xSector), nearY, nearX + (x * xSector), farY); + } +*/ + for (x = 0; x < neededXDivisions; x++) + { + int num = (neededXDivisions - 1) - x; + num = num & 7; + painter->drawText(QRect(nearX + (x * xSector), viewport.top(), xSector, viewport.top() + smallMetric->height()), Qt::AlignCenter, QString::number(num)); + } - painter->drawText(QRect(viewport.left() + (xSector / 4.0), viewport.top() + ySector * 1, xSector, ySector), Qt::AlignCenter, "0"); - painter->drawText(QRect(viewport.left() + (xSector / 4.0), viewport.top() + ySector * 2, xSector, ySector), Qt::AlignCenter, "1"); - painter->drawText(QRect(viewport.left() + (xSector / 4.0), viewport.top() + ySector * 3, xSector, ySector), Qt::AlignCenter, "2"); - painter->drawText(QRect(viewport.left() + (xSector / 4.0), viewport.top() + ySector * 4, xSector, ySector), Qt::AlignCenter, "3"); - painter->drawText(QRect(viewport.left() + (xSector / 4.0), viewport.top() + ySector * 5, xSector, ySector), Qt::AlignCenter, "4"); - painter->drawText(QRect(viewport.left() + (xSector / 4.0), viewport.top() + ySector * 6, xSector, ySector), Qt::AlignCenter, "5"); - painter->drawText(QRect(viewport.left() + (xSector / 4.0), viewport.top() + ySector * 7, xSector, ySector), Qt::AlignCenter, "6"); - painter->drawText(QRect(viewport.left() + (xSector / 4.0), viewport.top() + ySector * 8, xSector, ySector), Qt::AlignCenter, "7"); + int skip = neededXDivisions / 8; + for (int y = 0; y < neededYDivisions; y++) + { + painter->drawText(QRect(viewport.left() + 2, nearY + (ySector * y), xOffset, ySector), Qt::AlignCenter, QString::number(y * skip)); + } painter->setPen(QPen(Qt::black)); painter->setFont(mainFont); } -void CANDataGrid::paintChangedBits() +void CANDataGrid::paintGridCells() { int x, y, bit; unsigned char prevByte, thisByte; @@ -212,50 +352,69 @@ void CANDataGrid::paintChangedBits() //bits that used to be set but now are unset are red, bits that used to be unset but now are set //are green - for (y = 0; y < 8; y++) + for (y = 0; y < neededYDivisions; y++) { - thisByte = data[y]; - prevByte = refData[y]; - for (x = 0; x < 8; x++) + for (x = 0; x < neededXDivisions; x++) { - bit = (y * 8) + (7 - x); + int byteIdx = (y * (neededXDivisions / 8) + (x / 8)); + thisByte = data[byteIdx]; + prevByte = refData[byteIdx]; + int bitIdx = ((neededXDivisions - 1) - x) & 7; + bit = (byteIdx * 8) + bitIdx; thisBit = false; prevBit = false; - if ((thisByte & (1 << (7-x))) == (1 << (7-x))) thisBit = true; - if ((prevByte & (1 << (7-x))) == (1 << (7-x))) prevBit = true; + if ((thisByte & (1 << bitIdx)) == (1 << bitIdx)) thisBit = true; + if ((prevByte & (1 << bitIdx)) == (1 << bitIdx)) prevBit = true; - if (thisBit) + if (gridMode == GridMode::HEAT_VIEW) { - if (prevBit) - { - painter->setBrush(blackBrush); - } - else - { - painter->setBrush(greenBrush); - } + painter->setBrush(QBrush(fire[heatData[bit]])); } else { - if (prevBit) + if (thisBit) { - painter->setBrush(redBrush); + if (prevBit) + { + if ((signalColors.count() > 0) && (gridMode == GridMode::SIGNAL_VIEW)) painter->setBrush(blackHashBrush); + else painter->setBrush(blackBrush); + } + else + { + if ((signalColors.count() > 0) && (gridMode == GridMode::SIGNAL_VIEW)) painter->setBrush(greenHashBrush); + else painter->setBrush(greenBrush); + } } else { - usedSigNum = -1; - if ((usedData[y] & (1 << (7-x))) == (1 << (7-x))) + if (prevBit) { - grayBrush = QBrush(QColor(0xB6, 0xB6, 0xB6), Qt::BDiagPattern); - painter->setBrush(grayBrush); + painter->setBrush(redBrush); + } + else + { + usedSigNum = -1; + if ((usedData[byteIdx] & (1 << bitIdx)) == (1 << bitIdx)) + { + if (gridMode == GridMode::SIGNAL_VIEW) usedSigNum = getUsedSignalNum(bit); + if (usedSigNum == -1) + { + grayBrush = QBrush(QColor(0xB6, 0xB6, 0xB6), Qt::BDiagPattern); + painter->setBrush(grayBrush); + } + else + { + int idx = usedSigNum % signalColors.length(); //can only use as many colors as have been defined + painter->setBrush(QBrush(signalColors[idx])); + } + } + else painter->setBrush(whiteBrush); } - else painter->setBrush(whiteBrush); } } - //painter->fillRect(viewport.left() + (x+2) * xSector, viewport.top() + (y+2) * ySector, xSector, ySector, redBrush); - painter->drawRect(viewport.left() + (x+1) * xSector, viewport.top() + (y+1) * ySector, xSector, ySector); - switch (textStates[x][y]) + painter->drawRect(nearX + (x * xSector), nearY + (y * ySector), xSector, ySector); + switch (textStates[byteIdx][bitIdx]) { case GridTextState::NORMAL: if (thisBit && prevBit) painter->setPen(QPen(Qt::gray)); @@ -275,151 +434,78 @@ void CANDataGrid::paintChangedBits() //change style of bit number output for current signal //if (thisBit) painter->setFont(boldFont); // else painter->setFont(mainFont); - painter->setFont(smallFont); - - painter->drawText(viewport.left() + (x+1) * xSector + (xSector / 3), viewport.top() + (y + 2) * ySector - (ySector * 0.4), QString::number(bit)); - - painter->setFont(mainFont); - painter->setPen(QPen(Qt::black)); - } - } -} - -void CANDataGrid::paintSignalView() -{ - int x, y, bit; - unsigned char prevByte, thisByte; - bool thisBit, prevBit; - int usedSigNum; - QString prevSigName; - - //if this is true then generate unique colors for each signal - if ((signalColors.count() == 0) && (signalNames.count() > 0)) - { - qDebug() << "Generating colors"; - signalColors.resize(signalNames.count()); - for (int i = 0; i < signalNames.count(); i++) - { - QColor newColor; - while (newColor.saturation() < 40) - newColor.setRgb(QRandomGenerator::global()->bounded(160) + 60,QRandomGenerator::global()->bounded(160) + 60, QRandomGenerator::global()->bounded(160) + 60); - qDebug() << newColor; - signalColors[i] = newColor; - } - } - - for (y = 0; y < 8; y++) - { - thisByte = data[y]; - prevByte = refData[y]; - for (x = 0; x < 8; x++) - { - bit = (y * 8) + (7 - x); - thisBit = false; - prevBit = false; - if ((thisByte & (1 << (7-x))) == (1 << (7-x))) thisBit = true; - if ((prevByte & (1 << (7-x))) == (1 << (7-x))) prevBit = true; - - if (thisBit) - { - if (prevBit) - { - if (signalColors.count() > 0) painter->setBrush(blackHashBrush); - else painter->setBrush(blackBrush); - } - else - { - if (signalColors.count() > 0) painter->setBrush(greenHashBrush); - else painter->setBrush(greenBrush); - } - } + //painter->setFont(smallFont); + if (gridMode != GridMode::SIGNAL_VIEW) + painter->drawText(QRect(nearX + (x * xSector), nearY + (y * ySector), xSector, ySector), Qt::AlignCenter, QString::number(bit)); //center center of grid else - { - if (prevBit) - { - painter->setBrush(redBrush); - } - else - { - usedSigNum = -1; - if ((usedData[y] & (1 << (7-x))) == (1 << (7-x))) - { - usedSigNum = getUsedSignalNum(bit); - if (usedSigNum == -1) - { - grayBrush = QBrush(QColor(0xB6, 0xB6, 0xB6), Qt::BDiagPattern); - painter->setBrush(grayBrush); - } - else painter->setBrush(QBrush(signalColors[usedSigNum])); - } - else painter->setBrush(whiteBrush); - } - } - - //painter->fillRect(viewport.left() + (x+2) * xSector, viewport.top() + (y+2) * ySector, xSector, ySector, redBrush); - painter->drawRect(viewport.left() + (x+1) * xSector, viewport.top() + (y+1) * ySector, xSector, ySector); - switch (textStates[x][y]) - { - case GridTextState::NORMAL: - //if (thisBit && prevBit) painter->setPen(QPen(Qt::gray)); - /*else*/ painter->setPen(QPen(Qt::black)); - painter->setFont(mainFont); - break; - case GridTextState::BOLD_BLUE: - painter->setPen(QPen(Qt::blue)); - painter->setFont(boldFont); - break; - case GridTextState::INVERT: - painter->setFont(mainFont); - QColor brushColor = painter->brush().color(); - painter->setPen(QColor(255-brushColor.red(), 255-brushColor.green(), 255-brushColor.blue())); - break; - } - //change style of bit number output for current signal - //if (thisBit) painter->setFont(boldFont); - // else painter->setFont(mainFont); - painter->setFont(smallFont); - - painter->drawText(viewport.left() + (x+1) * xSector + (xSector / 8), viewport.top() + (y + 2) * ySector - (ySector * 0.7), QString::number(bit)); + painter->drawText(QRect(nearX + (x * xSector), nearY + (y * ySector), xSector, ySector), Qt::AlignLeft, QString::number(bit)); //upper left of grid painter->setFont(mainFont); painter->setPen(QPen(Qt::black)); } } - //now if signal names are loaded we'll go through all the bits again and try to label over top of the grid - if (signalNames.count() > 0) + + /* + * now if signal names are loaded we'll go through all the bits again and try to label over top of the grid + * We already have a big bitmap that tells us which signals occupy which bits so every time there is a new + * signal look ahead to see if there's room in the row to just run the signal name through as long as needed. + */ + if ( (signalNames.count() > 0) && (gridMode == GridMode::SIGNAL_VIEW) ) { painter->setFont(sigNameFont); - for (y = 0; y < 8; y++) + + for (y = 0; y < neededYDivisions; y++) { - for (x = 0; x < 8; x++) + for (x = 0; x < neededXDivisions; x++) { - bit = (y * 8) + (7 - x); + int byteIdx = (y * (neededXDivisions / 8) + (x / 8)); + int bitIdx = ((neededXDivisions - 1) - x) & 7; + bit = (byteIdx * 8) + bitIdx; usedSigNum = -1; - if ((usedData[y] & (1 << (7-x))) == (1 << (7-x))) + if ((usedData[byteIdx] & (1 << bitIdx)) == (1 << bitIdx)) { usedSigNum = getUsedSignalNum(bit); - if (prevSigName != signalNames[usedSigNum]) + if ((usedSigNum > -1) && (prevSigName != signalNames[usedSigNum]) ) { prevSigName = signalNames[usedSigNum]; int textWidth = smallMetric->horizontalAdvance(prevSigName); - if (textWidth > xSector) //signal name is too long for a single cell. Try to wrap it + int usableWidth = getSignalRowRun(usedSigNum, bit); + qDebug() << "Width this row: " << usableWidth; + usableWidth *= xSector; + + if (textWidth > usableWidth) //signal name is too long for space we've got on this row. Try to wrap it { int numAvgChars = xSector / smallMetric->averageCharWidth(); - painter->drawText(viewport.left() + (x+1) * xSector, viewport.top() + (y + 2) * ySector - (ySector * 0.4), prevSigName.left(numAvgChars - 1)); + painter->drawText(nearX + x * xSector + 5, nearY + (y * ySector) + smallMetric->height() * 1.6, prevSigName.left(numAvgChars - 1)); QString remainder = prevSigName.mid(numAvgChars - 1, -1); textWidth = smallMetric->horizontalAdvance(prevSigName); if (textWidth > xSector) { - painter->drawText(viewport.left() + (x+1) * xSector, viewport.top() + (y + 2) * ySector - (ySector * 0.2), remainder.left(numAvgChars - 1)); + painter->drawText(nearX + x * xSector + 12, nearY + (y * ySector) + smallMetric->height() * 2.6, remainder.left(numAvgChars - 1)); + } + else painter->drawText(nearX + x * xSector + 12, nearY + (y * ySector) + smallMetric->height() * 2.6, remainder); + } + else + { + //first see if we even have enough room to use a bigger font and use that if so. Otherwise stick with the normal font + textWidth = largeMetric->horizontalAdvance(prevSigName); + if (textWidth < usableWidth) + { + painter->setFont(mainFont); + QSize size = QSize(usableWidth, ySector - smallMetric->height() * 1.0); + painter->drawText(QRect(nearX + x * xSector, nearY + (y * ySector) + smallMetric->height() * 1.0, size.width(), size.height()), Qt::AlignCenter, prevSigName); + painter->setFont(sigNameFont); + } + else + { + QSize size = QSize(usableWidth, ySector - smallMetric->height() * 1.0); + painter->drawText(QRect(nearX + x * xSector, nearY + (y * ySector) + smallMetric->height() * 1.0, size.width(), size.height()), Qt::AlignCenter, prevSigName); } - else painter->drawText(viewport.left() + (x+1) * xSector, viewport.top() + (y + 2) * ySector - (ySector * 0.2), remainder); } - else painter->drawText(viewport.left() + (x+1) * xSector, viewport.top() + (y + 2) * ySector - (ySector * 0.4), prevSigName); } } } @@ -427,17 +513,62 @@ void CANDataGrid::paintSignalView() } } +//starting at the given coords, go right / increment X until either we hit the end of the signal or the end of the row, whichever is first. +//return how many grid cells that was. The part that sucks is that bits aren't really in "bit" order so you can't just increment the bit number +int CANDataGrid::getSignalRowRun(int sigNum, int startBit) +{ + qDebug() << "SigNum: " << sigNum << " startBit " << startBit; + int width = 0; + QPoint gridPt = getGridPointFromBitPosition(startBit); + int x = gridPt.x(); + int y = gridPt.y(); + for (int xx = x; xx < neededXDivisions; xx++) + { + int bit = gridToBitPosition(xx, y); + if (usedSignalNum[bit] == sigNum) width++; + else return width; + } + + return width; +} + void CANDataGrid::paintCommonEnding() { - upperLeft.setX(viewport.left() + 1 * xSector); - upperLeft.setY(viewport.top() + 1 * ySector); + //these are used to make it easy to figure out which grid has been clicked on during mousedown events + upperLeft.setX(nearX); + upperLeft.setY(nearY); gridSize.setX(xSector); gridSize.setY(ySector); + //and we don't need these anymore after we're done drawing delete painter; delete smallMetric; } +//given a grid cell we return which bit position that is within the CAN frame. +int CANDataGrid::gridToBitPosition(int x, int y) +{ + int byteIdx = (y * (neededXDivisions / 8) + (x / 8)); + int bitIdx = ((neededXDivisions - 1) - x) & 7; + int bit = (byteIdx * 8) + bitIdx; + return bit; +} + +//inverse of above. Given a bit position we calculate where that would be in our grid +QPoint CANDataGrid::getGridPointFromBitPosition(int bitPos) +{ + int tempBit = bitPos; + //neededXDivisions tells us how many bits are on a single line. + //From there we can easily determine which Y row we're in with simple division + int y = bitPos / neededXDivisions; + //x is more complicated because the bits go in a sort of stairstep pattern 7654321076543210 + tempBit = (bitPos - (neededXDivisions * y)); + int x = tempBit & 0xF8; //get the byte offset in the line + x = x + (7- (tempBit & 7)); //reverse the bits in the byte + + return QPoint(x, y); +} + void CANDataGrid::saveImage(QString filename, int width, int height) { Q_UNUSED(width); //currently unused but I want to use them in the future @@ -462,20 +593,38 @@ void CANDataGrid::saveImage(QString filename, int width, int height) pixmap.save(filename); //QT will automatically pick the file format given the extension } +//these next three functions will copy the needed number of bytes from the passed buffer but you'd better have a large enough buffer or they'll get junk +//this probably won't crash the program but it would yield some really strange output. This is only really an issue for CAN-FD traffic. Make sure you +//have large enough buffers! void CANDataGrid::setReference(unsigned char *newRef, bool bUpdate = true) { - memcpy(refData, newRef, 8); + int bytesToTransfer = (bytesToDraw + 7) & 0xF8; //force copying in 8 byte increments + memcpy(refData, newRef, bytesToTransfer); + //clear all data past that point just to be sure we don't have garbage left over + if (bytesToTransfer < 64) memset(refData + bytesToTransfer, 0, 64 - bytesToTransfer); if (bUpdate) this->update(); } void CANDataGrid::updateData(unsigned char *newData, bool bUpdate = true) { - memcpy(data, newData, 8); //on a 64 bit processor this is probably optimized to a single instruction + int bytesToTransfer = (bytesToDraw + 7) & 0xF8; //force copying in 8 byte increments + memcpy(data, newData, bytesToTransfer); + //clear all data past that point just to be sure we don't have garbage left over + if (bytesToTransfer < 64) memset(refData + bytesToTransfer, 0, 64 - bytesToTransfer); if (bUpdate) this->update(); } void CANDataGrid::setUsed(unsigned char *newData, bool bUpdate = false) { - memcpy(usedData, newData, 8); + int bytesToTransfer = (bytesToDraw + 7) & 0xF8; //force copying in 8 byte increments + memcpy(usedData, newData, bytesToTransfer); + //clear all data past that point just to be sure we don't have garbage left over + if (bytesToTransfer < 64) memset(refData + bytesToTransfer, 0, 64 - bytesToTransfer); if (bUpdate) this->update(); } + +void CANDataGrid::setHeat(unsigned char *newData) +{ + memcpy(heatData, newData, 512); + this->update(); +} diff --git a/candatagrid.h b/candatagrid.h index 6ae159d..623e80f 100644 --- a/candatagrid.h +++ b/candatagrid.h @@ -22,6 +22,8 @@ class CANDataGrid; * * Now the control also tracks which signal is using which bit. The graphical representation does exist now but some tweaking is probably * still needed. Also, it seems like it is necessary to allow for a variety of modes. + * + * And now, CAN-FD added as the cherry on top! It's a mess but really, all this functionality is handy to have. */ enum GridTextState @@ -33,10 +35,13 @@ enum GridTextState enum GridMode { - CHANGED_BITS, - SIGNAL_VIEW + CHANGED_BITS, //traditional view, bunch of bits we color to show what's set and how the bits have changed over time + SIGNAL_VIEW, //special view for DBC window where we draw the signals in the bits they take up + HEAT_VIEW //another special mode where each bit is given a heat level and we color each bit based on that. Use refData for storage of those values }; +extern QVector signalColors; + class CANDataGrid : public QWidget { Q_OBJECT @@ -44,35 +49,39 @@ class CANDataGrid : public QWidget public: explicit CANDataGrid(QWidget *parent = 0); ~CANDataGrid(); - void paintEvent(QPaintEvent *event); + void paintEvent(QPaintEvent *event) override; void setReference(unsigned char *, bool); void updateData(unsigned char *, bool); void setUsed(unsigned char *, bool); + void setHeat(unsigned char *); void saveImage(QString filename, int width, int height); - void setCellTextState(int x, int y, GridTextState state); - GridTextState getCellTextState(int x, int y); + void setCellTextState(int bitPos, GridTextState state); + GridTextState getCellTextState(int bitPos); void setUsedSignalNum(int bit, int signal); void setSignalNames(int sigIdx, const QString sigName); void clearSignalNames(); int getUsedSignalNum(int bit); GridMode getMode(); void setMode(GridMode mode); + void setBytesToDraw(int num); protected: void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE; signals: - void gridClicked(int x,int y); + void gridClicked(int bitClicked); + void gridRightClicked(int bitClicked); private: Ui::CANDataGrid *ui; - unsigned char refData[8]; - unsigned char data[8]; - unsigned char usedData[8]; - int usedSignalNum[64]; //so we can specify which signal claims this bit + int bytesToDraw; + unsigned char refData[64]; + unsigned char data[64]; + unsigned char usedData[64]; + unsigned char heatData[512]; + int usedSignalNum[512]; //so we can specify which signal claims this bit QVector signalNames; - QVector signalColors; - GridTextState textStates[8][8]; + GridTextState textStates[64][8]; //first dimension is bytes, second is bits QPoint upperLeft, gridSize; GridMode gridMode; QBrush blackBrush, whiteBrush, redBrush, greenBrush, grayBrush; @@ -84,16 +93,25 @@ private: int xSector; int ySector; double bigTextSize, smallTextSize, sigNameTextSize; + int xOffset; + int yOffset; + int farX, farY, nearX, nearY; + int neededXDivisions; + int neededYDivisions; QFont mainFont; QFont smallFont; QFont boldFont; QFont sigNameFont; QFontMetrics *smallMetric; + QFontMetrics *largeMetric; + QColor fire[256]; - void paintChangedBits(); - void paintSignalView(); + void paintGridCells(); void paintCommonBeginning(); - void paintCommonEnding(); + void paintCommonEnding(); + int gridToBitPosition(int x, int y); + QPoint getGridPointFromBitPosition(int bitPos); + int getSignalRowRun(int sigNum, int startBit); }; #endif // CANDATAGRID_H diff --git a/canframemodel.cpp b/canframemodel.cpp index e053044..ef2ccf8 100644 --- a/canframemodel.cpp +++ b/canframemodel.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "utility.h" CANFrameModel::~CANFrameModel() @@ -45,31 +46,48 @@ int CANFrameModel::columnCount(const QModelIndex &index) const CANFrameModel::CANFrameModel(QObject *parent) : QAbstractTableModel(parent) { - + int maxFramesDefault; if (QSysInfo::WordSize > 32) { qDebug() << "64 bit OS detected. Requesting a large preallocation"; - preallocSize = 10000000; + maxFramesDefault = 10000000; } else //if compiling for 32 bit you can't ask for gigabytes of preallocation so tone it down. { qDebug() << "32 bit OS detected. Requesting a much restricted prealloc"; - preallocSize = 2000000; + maxFramesDefault = 2000000; } + QSettings settings; + preallocSize = settings.value("Main/MaximumFrames", maxFramesDefault).toInt(); + + //Each CANFrame object takes up 56 bytes and we're allocating two arrays here so take the + //# of pre-alloc frames and multiply by 112 to get the RAM usage. This is around 1GiB for the default. + + //the goal is to prevent a reallocation from ever happening frames.reserve(preallocSize); - filteredFrames.reserve(preallocSize); //the goal is to prevent a reallocation from ever happening + //this is pretty wasteful. We're storing all frames twice. It may be better for filteredFrames to be a list of pointers. + //Pointers take up 8 bytes instead of 56 so this is quite a savings for RAM usage. But, then filteredFrames would + //work differently from frames above so the two could not be used interchangeably. Still think about what can be done. + filteredFrames.reserve(preallocSize); dbcHandler = DBCHandler::getReference(); interpretFrames = false; overwriteDups = false; + filtersPersistDuringClear = false; useHexMode = true; - timeSeconds = false; + timeStyle = TS_MICROS; timeOffset = 0; needFilterRefresh = false; lastUpdateNumFrames = 0; timeFormat = "MMM-dd HH:mm:ss.zzz"; sortDirAsc = false; + bytesPerLine = 8; +} + +void CANFrameModel::setBytesPerLine(int bpl) +{ + bytesPerLine = bpl; } void CANFrameModel::setHexMode(bool mode) @@ -83,32 +101,13 @@ void CANFrameModel::setHexMode(bool mode) } } -void CANFrameModel::setSecondsMode(bool mode) +void CANFrameModel::setTimeStyle(TimeStyle newStyle) { - if (Utility::secondsMode != mode) + if (timeStyle != newStyle) { this->beginResetModel(); - Utility::secondsMode = mode; - this->endResetModel(); - } -} - -void CANFrameModel::setSysTimeMode(bool mode) -{ - if (Utility::sysTimeMode != mode) - { - this->beginResetModel(); - Utility::sysTimeMode = mode; - this->endResetModel(); - } -} - -void CANFrameModel::setMillisMode(bool mode) -{ - if (Utility::millisMode != mode) - { - this->beginResetModel(); - Utility::millisMode = mode; + timeStyle = newStyle; + Utility::timeStyle = newStyle; this->endResetModel(); } } @@ -133,6 +132,7 @@ bool CANFrameModel::getInterpretMode() void CANFrameModel::setTimeFormat(QString format) { Utility::timeFormat = format; + timeFormat = format; beginResetModel(); //reset model to show new time format endResetModel(); } @@ -195,6 +195,11 @@ void CANFrameModel::setOverwriteMode(bool mode) endResetModel(); } +void CANFrameModel::setClearMode(bool mode) +{ + filtersPersistDuringClear = mode; +} + void CANFrameModel::setFilterState(unsigned int ID, bool state) { if (!filters.contains(ID)) return; @@ -224,11 +229,11 @@ void CANFrameModel::setAllFilters(bool state) * quicksort on the columns and interpret the columns numerically. But, correct or not, this implementation is quite fast * and sorts the columns properly. */ -uint64_t CANFrameModel::getCANFrameVal(int row, Column col) +uint64_t CANFrameModel::getCANFrameVal(QVector *frames, int row, Column col) { uint64_t temp = 0; - if (row >= frames.count()) return 0; - CANFrame frame = frames[row]; + if (row >= frames->count()) return 0; + CANFrame frame = frames->at(row); switch (col) { case Column::TimeStamp: @@ -252,7 +257,7 @@ uint64_t CANFrameModel::getCANFrameVal(int row, Column col) return static_cast(frame.payload().length()); case Column::ASCII: //sort both the same for now case Column::Data: - for (int i = 0; i < frame.payload().length(); i++) temp += (static_cast(frame.payload()[i]) << (56 - (8 * i))); + for (int i = 0; i < std::min(frame.payload().length(), 8); i++) temp += (static_cast(frame.payload()[i]) << (56 - (8 * i))); //qDebug() << temp; return temp; case Column::NUM_COLUMN: @@ -267,18 +272,18 @@ void CANFrameModel::qSortCANFrameAsc(QVector *frames, Column column, i qDebug() << "Lower " << lowerBound << " Upper" << upperBound; if (lowerBound < upperBound) { - uint64_t piv = getCANFrameVal(lowerBound + (upperBound - lowerBound) / 2, column); + uint64_t piv = getCANFrameVal(frames, lowerBound + (upperBound - lowerBound) / 2, column); i = lowerBound - 1; j = upperBound + 1; for (;;){ do { i++; - } while ((i < upperBound) && getCANFrameVal(i, column) < piv); + } while ((i < upperBound) && getCANFrameVal(frames, i, column) < piv); do { j--; - } while ((j > lowerBound) && getCANFrameVal(j, column) > piv); + } while ((j > lowerBound) && getCANFrameVal(frames, j, column) > piv); if (i < j) { CANFrame temp = frames->at(i); frames->replace(i, frames->at(j)); @@ -298,18 +303,18 @@ void CANFrameModel::qSortCANFrameDesc(QVector *frames, Column column, qDebug() << "Lower " << lowerBound << " Upper" << upperBound; if (lowerBound < upperBound) { - uint64_t piv = getCANFrameVal(lowerBound + (upperBound - lowerBound) / 2, column); + uint64_t piv = getCANFrameVal(frames, lowerBound + (upperBound - lowerBound) / 2, column); i = lowerBound - 1; j = upperBound + 1; for (;;){ do { i++; - } while ((i < upperBound) && getCANFrameVal(i, column) > piv); + } while ((i < upperBound) && getCANFrameVal(frames, i, column) > piv); do { j--; - } while ((j > lowerBound) && getCANFrameVal(j, column) < piv); + } while ((j > lowerBound) && getCANFrameVal(frames, j, column) < piv); if (i < j) { CANFrame temp = frames->at(i); frames->replace(i, frames->at(j)); @@ -326,11 +331,13 @@ void CANFrameModel::qSortCANFrameDesc(QVector *frames, Column column, void CANFrameModel::sortByColumn(int column) { sortDirAsc = !sortDirAsc; - //beginResetModel(); - if (sortDirAsc) qSortCANFrameAsc(&frames, Column(column), 0, frames.count()-1); - else qSortCANFrameDesc(&frames, Column(column), 0, frames.count()-1); - //endResetModel(); - sendRefresh(); + if (sortDirAsc) qSortCANFrameAsc(&filteredFrames, Column(column), 0, filteredFrames.count()-1); + else qSortCANFrameDesc(&filteredFrames, Column(column), 0, filteredFrames.count()-1); + + mutex.lock(); + beginResetModel(); + endResetModel(); + mutex.unlock(); } //End of custom sorting code @@ -353,33 +360,38 @@ void CANFrameModel::recalcOverwrite() idAugmented = frame.frameId(); idAugmented = idAugmented + (frame.bus << 29ull); - if (!overWriteFrames.contains(idAugmented)) + if (filters[frame.frameId()] && busFilters[frame.bus]) { - frame.timedelta = 0; - frame.frameCount = 1; - overWriteFrames.insert(idAugmented, frame); - } - else - { - frame.timedelta = frame.timeStamp().microSeconds() - overWriteFrames[idAugmented].timeStamp().microSeconds(); - frame.frameCount = overWriteFrames[idAugmented].frameCount + 1; - overWriteFrames[idAugmented] = frame; + if (!overWriteFrames.contains(idAugmented)) + { + frame.timedelta = 0; + frame.frameCount = 1; + overWriteFrames.insert(idAugmented, frame); + } + else + { + frame.timedelta = frame.timeStamp().microSeconds() - overWriteFrames[idAugmented].timeStamp().microSeconds(); + frame.frameCount = overWriteFrames[idAugmented].frameCount + 1; + overWriteFrames[idAugmented] = frame; + } } } //Then replace the old list of frames with just the unique list - frames.clear(); - frames.append(overWriteFrames.values().toVector()); + //frames.clear(); + //frames.append(overWriteFrames.values().toVector()); + //frames.reserve(preallocSize); filteredFrames.clear(); + filteredFrames.append(overWriteFrames.values().toVector()); filteredFrames.reserve(preallocSize); - for (int i = 0; i < frames.count(); i++) + /*for (int i = 0; i < frames.count(); i++) { if (filters[frames[i].frameId()] && busFilters[frames[i].bus]) { filteredFrames.append(frames[i]); } - } + }*/ endResetModel(); mutex.unlock(); @@ -403,7 +415,7 @@ QVariant CANFrameModel::data(const QModelIndex &index, int role) const const unsigned char *data = reinterpret_cast(thisFrame.payload().constData()); int dataLen = thisFrame.payload().count(); - if (role == Qt::BackgroundColorRole) + if (role == Qt::BackgroundRole) { if (dbcHandler != nullptr && interpretFrames && !ignoreDBCColors) { @@ -436,7 +448,7 @@ QVariant CANFrameModel::data(const QModelIndex &index, int role) const } } - if (role == Qt::TextColorRole) + if (role == Qt::ForegroundRole) { if (dbcHandler != nullptr && interpretFrames && !ignoreDBCColors) { @@ -456,7 +468,7 @@ QVariant CANFrameModel::data(const QModelIndex &index, int role) const //Reformatting the output a bit with custom code if (overwriteDups) { - if (timeSeconds) return QString::number(thisFrame.timedelta / 1000000.0, 'f', 5); + if (timeStyle == TS_SECONDS) return QString::number(thisFrame.timedelta / 1000000.0, 'f', 5); return QString::number(thisFrame.timedelta); } else ts = Utility::formatTimestamp(thisFrame.timeStamp().microSeconds()); @@ -495,6 +507,7 @@ QVariant CANFrameModel::data(const QModelIndex &index, int role) const if (byt < 0x20) byt = 0x2E; //dot character if (byt > 0x7E) byt = 0x2E; tempString.append(QString::fromUtf8(&byt, 1)); + if (!((i+1) % bytesPerLine) && (i != (dataLen - 1))) tempString.append("\n"); } } if (thisFrame.frameType() == QCanBusFrame::ErrorFrame) @@ -512,7 +525,8 @@ QVariant CANFrameModel::data(const QModelIndex &index, int role) const { if (useHexMode) tempString.append( QString::number(data[i], 16).toUpper().rightJustified(2, '0')); else tempString.append(QString::number(data[i], 10)); - tempString.append(" "); + if (!((i+1) % bytesPerLine) && (i != (dataLen - 1))) tempString.append("\n"); + else tempString.append(" "); } if (thisFrame.frameType() == thisFrame.ErrorFrame) { @@ -672,32 +686,52 @@ void CANFrameModel::addFrame(const CANFrame& frame, bool autoRefresh = false) if (!overwriteDups) { - frames.append(tempFrame); - if (filters[tempFrame.frameId()] && busFilters[tempFrame.bus]) + try { - if (autoRefresh) beginInsertRows(QModelIndex(), filteredFrames.count(), filteredFrames.count()); - tempFrame.frameCount = 1; - filteredFrames.append(tempFrame); - if (autoRefresh) endInsertRows(); + frames.append(tempFrame); + + if (filters[tempFrame.frameId()] && busFilters[tempFrame.bus]) + { + if (autoRefresh) beginInsertRows(QModelIndex(), filteredFrames.count(), filteredFrames.count()); + tempFrame.frameCount = 1; + filteredFrames.append(tempFrame); + if (autoRefresh) endInsertRows(); + } + } + catch (const std::exception& ex) + { + qDebug() << "addFrame failed to append. App is probably going to crash. frames.length(): " << frames.length() << " Exception: " << ex.what(); } } else //yes, overwrite dups { bool found = false; - for (int i = 0; i < frames.count(); i++) +// for (int i = 0; i < frames.count(); i++) +// { +// if ( (frames[i].frameId() == tempFrame.frameId()) && (frames[i].bus == tempFrame.bus) ) +// { +// tempFrame.frameCount = frames[i].frameCount + 1; +// tempFrame.timedelta = tempFrame.timeStamp().microSeconds() - frames[i].timeStamp().microSeconds(); +// frames.replace(i, tempFrame); +// found = true; +// break; +// } +// } + for (int i = 0; i < filteredFrames.count(); i++) { - if ( (frames[i].frameId() == tempFrame.frameId()) && (frames[i].bus == tempFrame.bus) ) + if ( (filteredFrames[i].frameId() == tempFrame.frameId()) && (filteredFrames[i].bus == tempFrame.bus) ) { - tempFrame.frameCount = frames[i].frameCount + 1; - tempFrame.timedelta = tempFrame.timeStamp().microSeconds() - frames[i].timeStamp().microSeconds(); - frames.replace(i, tempFrame); + tempFrame.frameCount = filteredFrames[i].frameCount + 1; + tempFrame.timedelta = tempFrame.timeStamp().microSeconds() - filteredFrames[i].timeStamp().microSeconds(); + filteredFrames.replace(i, tempFrame); found = true; break; } } + frames.append(tempFrame); if (!found) { - frames.append(tempFrame); + //frames.append(tempFrame); if (filters[tempFrame.frameId()] && busFilters[tempFrame.bus]) { if (autoRefresh) beginInsertRows(QModelIndex(), filteredFrames.count(), filteredFrames.count()); @@ -727,6 +761,24 @@ void CANFrameModel::addFrame(const CANFrame& frame, bool autoRefresh = false) void CANFrameModel::addFrames(const CANConnection*, const QVector& pFrames) { + if(frames.length() > frames.capacity() * 0.99) + { + mutex.lock(); + qDebug() << "Frames count: " << frames.length() << " of " << frames.capacity() << " capacity, removing first " << (int)(frames.capacity() * 0.05) << " frames"; + frames.remove(0, (int)(frames.capacity() * 0.05)); + qDebug() << "Frames removed, new count: " << frames.length(); + mutex.unlock(); + } + + if(filteredFrames.length() > filteredFrames.capacity() * 0.99) + { + mutex.lock(); + qDebug() << "filteredFrames count: " << filteredFrames.length() << " of " << filteredFrames.capacity() << " capacity, removing first " << (int)(filteredFrames.capacity() * 0.05) << " frames"; + filteredFrames.remove(0, (int)(filteredFrames.capacity() * 0.05)); + qDebug() << "filteredFrames removed, new count: " << filteredFrames.length(); + mutex.unlock(); + } + foreach(const CANFrame& frame, pFrames) { addFrame(frame); @@ -740,25 +792,33 @@ void CANFrameModel::addFrames(const CANConnection*, const QVector& pFr void CANFrameModel::sendRefresh() { - qDebug() << "Sending mass refresh"; - QVector tempContainer; - int count = frames.count(); - for (int i = 0; i < count; i++) - { - if (filters[frames[i].frameId()] && busFilters[frames[i].bus]) - { - tempContainer.append(frames[i]); - } - } - mutex.lock(); - beginResetModel(); - filteredFrames.clear(); - filteredFrames.reserve(preallocSize); - filteredFrames.append(tempContainer); + qDebug() << "Sending mass refresh"; - lastUpdateNumFrames = 0; - endResetModel(); - mutex.unlock(); + if(overwriteDups) + { + recalcOverwrite(); + } + else + { + QVector tempContainer; + int count = frames.count(); + for (int i = 0; i < count; i++) + { + if (filters[frames[i].frameId()] && busFilters[frames[i].bus]) + { + tempContainer.append(frames[i]); + } + } + + mutex.lock(); + beginResetModel(); + filteredFrames.clear(); + filteredFrames.append(tempContainer); + filteredFrames.reserve(preallocSize); + lastUpdateNumFrames = 0; + endResetModel(); + mutex.unlock(); + } } void CANFrameModel::sendRefresh(int pos) @@ -776,9 +836,9 @@ int CANFrameModel::sendBulkRefresh() if (lastUpdateNumFrames <= 0) return 0; if (lastUpdateNumFrames == 0 && !overwriteDups) return 0; - if (filteredFrames.count() == 0) return 0; + //if (filteredFrames.count() == 0) return 0; - qDebug() << "Bulk refresh of " << lastUpdateNumFrames; + //qDebug() << "Bulk refresh of " << lastUpdateNumFrames; beginResetModel(); endResetModel(); @@ -795,8 +855,11 @@ void CANFrameModel::clearFrames() this->beginResetModel(); frames.clear(); filteredFrames.clear(); - filters.clear(); - busFilters.clear(); + if(filtersPersistDuringClear == false) + { + filters.clear(); + busFilters.clear(); + } frames.reserve(preallocSize); filteredFrames.reserve(preallocSize); this->endResetModel(); diff --git a/canframemodel.h b/canframemodel.h index edb65b5..6a6bc54 100644 --- a/canframemodel.h +++ b/canframemodel.h @@ -9,6 +9,7 @@ #include "can_structs.h" #include "dbc/dbchandler.h" #include "connections/canconnection.h" +#include "utility.h" enum class Column { TimeStamp = 0, ///< The timestamp when the frame was transmitted or received @@ -46,14 +47,14 @@ public: bool getInterpretMode(); void setOverwriteMode(bool); void setHexMode(bool); - void setSysTimeMode(bool); - void setMillisMode(bool mode); + void setClearMode(bool mode); + void setTimeStyle(TimeStyle newStyle); void setIgnoreDBCColors(bool mode); void setFilterState(unsigned int ID, bool state); void setBusFilterState(unsigned int BusID, bool state); void setAllFilters(bool state); - void setSecondsMode(bool); void setTimeFormat(QString); + void setBytesPerLine(int bpl); void loadFilterFile(QString filename); void saveFilterFile(QString filename); void normalizeTiming(); @@ -77,7 +78,7 @@ signals: private: void qSortCANFrameAsc(QVector* frames, Column column, int lowerBound, int upperBound); void qSortCANFrameDesc(QVector* frames, Column column, int lowerBound, int upperBound); - uint64_t getCANFrameVal(int row, Column col); + uint64_t getCANFrameVal(QVector *frames, int row, Column col); bool any_filters_are_configured(void); bool any_busfilters_are_configured(void); @@ -89,16 +90,17 @@ private: QMutex mutex; bool interpretFrames; //should we use the dbcHandler? bool overwriteDups; //should we display all frames or only the newest for each ID? + bool filtersPersistDuringClear; QString timeFormat; + TimeStyle timeStyle; bool useHexMode; - bool timeSeconds; - bool useSystemTime; bool needFilterRefresh; bool ignoreDBCColors; int64_t timeOffset; int lastUpdateNumFrames; uint32_t preallocSize; bool sortDirAsc; + int bytesPerLine; }; diff --git a/config.h b/config.h index 99412e5..7849af8 100644 --- a/config.h +++ b/config.h @@ -1,7 +1,7 @@ #ifndef CONFIG #define CONFIG -#define VERSION 207 +#define VERSION 213 //try to keep this in sync. //SavvyCAN will complain if you connect a GVRET board with a revision diff --git a/connections/canbus.cpp b/connections/canbus.cpp index 24238f7..0623baa 100644 --- a/connections/canbus.cpp +++ b/connections/canbus.cpp @@ -4,25 +4,22 @@ CANBus::CANBus() { - speed = 250000; + speed = 500000; listenOnly = false; singleWire = false; active = false; + canFD = false; + dataRate = 2000000; } -CANBus::CANBus(const CANBus& pBus) : - speed(pBus.speed), - listenOnly(pBus.listenOnly), - singleWire(pBus.singleWire), - active(pBus.active) {} - - bool CANBus::operator==(const CANBus& bus) const{ return speed == bus.speed && listenOnly == bus.listenOnly && singleWire == bus.singleWire && - active == bus.active; + active == bus.active && + canFD == bus.canFD && + dataRate == bus.dataRate; } void CANBus::setSpeed(int newSpeed){ @@ -45,29 +42,48 @@ void CANBus::setActive(bool mode){ active = mode; } -int CANBus::getSpeed(){ +void CANBus::setCanFD(bool mode){ + //qDebug() << "CANBUS setCanFD = " << mode; + canFD = mode; +} + +int CANBus::getSpeed() const { return speed; } -bool CANBus::isListenOnly(){ +int CANBus::getDataRate() const { + return dataRate; +} + +void CANBus::setDataRate(int newSpeed){ + //qDebug() << "CANBUS SetSpeed = " << newSpeed; + dataRate = newSpeed; +} + +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() 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 b127059..61fc122 100644 --- a/connections/canbus.h +++ b/connections/canbus.h @@ -5,37 +5,43 @@ 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; bool active; //is this bus turned on? + 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. - int getSpeed(); - bool isListenOnly(); - bool isSingleWire(); - bool isActive(); + void setCanFD(bool); // enable or disable CANFD support + void setDataRate(int newSpeed); + + 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/canconconst.h b/connections/canconconst.h index 7f479c1..0f7ba9c 100644 --- a/connections/canconconst.h +++ b/connections/canconconst.h @@ -20,6 +20,9 @@ namespace CANCon { REMOTE, KAYAK, MQTT, + LAWICEL, + CANSERVER, + CANLOGSERVER, NONE }; } diff --git a/connections/canconfactory.cpp b/connections/canconfactory.cpp index 1fc461c..ae2bc6b 100644 --- a/connections/canconfactory.cpp +++ b/connections/canconfactory.cpp @@ -4,22 +4,34 @@ #include "gvretserial.h" #include "mqtt_bus.h" #include "socketcand.h" +#include "lawicel_serial.h" +#include "canserver.h" +#include "canlogserver.h" using namespace CANCon; -CANConnection* CanConFactory::create(type pType, QString pPortName, QString pDriverName) +CANConnection* CanConFactory::create(type pType, QString pPortName, QString pDriverName, int pSerialSpeed, int pBusSpeed, bool pCanFd, int pDataRate) { switch(pType) { case SERIALBUS: return new SerialBusConnection(pPortName, pDriverName); case GVRET_SERIAL: + if(pPortName.contains(".") && !pPortName.contains("tty") && !pPortName.contains("serial")) + return new GVRetSerial(pPortName, true); + else return new GVRetSerial(pPortName, false); case REMOTE: return new GVRetSerial(pPortName, true); //it's a special case of GVRET connected over TCP/IP so it uses the same class + case LAWICEL: + return new LAWICELSerial(pPortName, pSerialSpeed, pBusSpeed, pCanFd, pDataRate); case KAYAK: return new SocketCANd(pPortName); case MQTT: return new MQTT_BUS(pPortName); + case CANSERVER: + return new CANserver(pPortName); + case CANLOGSERVER: + return new CanLogServer(pPortName); default: {} } diff --git a/connections/canconfactory.h b/connections/canconfactory.h index 668568e..ecf453d 100644 --- a/connections/canconfactory.h +++ b/connections/canconfactory.h @@ -7,7 +7,7 @@ class CanConFactory { public: - static CANConnection* create(CANCon::type, QString pPortName, QString pDriverName); + static CANConnection* create(CANCon::type, QString pPortName, QString pDriverName, int pSerialSpeed, int pBusSpeed, bool pCanFd, int pDataRate); }; #endif // CANCONFACTORY_H diff --git a/connections/canconmanager.cpp b/connections/canconmanager.cpp index fe97759..9496b9e 100644 --- a/connections/canconmanager.cpp +++ b/connections/canconmanager.cpp @@ -69,7 +69,9 @@ void CANConManager::remove(CANConnection* pConn_p) void CANConManager::replace(int idx, CANConnection* pConn_p) { + CANConnection *original = mConns[idx]; mConns.replace(idx, pConn_p); + delete original; original = NULL; } //Get total number of buses currently registered with the program @@ -204,7 +206,6 @@ bool CANConManager::sendFrame(const CANFrame& pFrame) { int busBase = 0; CANFrame workingFrame = pFrame; - CANFrame *txFrame; if (mConns.count() == 0) { @@ -228,10 +229,7 @@ bool CANConManager::sendFrame(const CANFrame& pFrame) workingFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, mElapsedTimer.nsecsElapsed() / 1000)); //workingFrame.timestamp -= mTimestampBasis; } - txFrame = conn->getQueue().get(); - QCoreApplication::processEvents(); - *txFrame = workingFrame; - conn->getQueue().queue(); + return conn->sendFrame(workingFrame); } busBase += conn->getNumBuses(); diff --git a/connections/canconnection.cpp b/connections/canconnection.cpp index ef6e176..9eac817 100644 --- a/connections/canconnection.cpp +++ b/connections/canconnection.cpp @@ -5,10 +5,15 @@ CANConnection::CANConnection(QString pPort, QString pDriver, CANCon::type pType, + int pSerialSpeed, + int pBusSpeed, + bool pCanFd, + int pDataRate, int pNumBuses, int pQueueLen, bool pUseThread) : mNumBuses(pNumBuses), + mSerialSpeed(pSerialSpeed), mQueue(), mPort(pPort), mDriver(pDriver), @@ -34,6 +39,10 @@ CANConnection::CANConnection(QString pPort, mBusData[i].mConfigured = false; } + if (pBusSpeed > 0) mBusData[0].mBus.setSpeed(pBusSpeed); + mBusData[0].mBus.setCanFD(pCanFd); + if (pDataRate > 0) mBusData[0].mBus.setDataRate(pDataRate); + /* if needed, create a thread and move ourself into it */ if(pUseThread) { mThread_p = new QThread(); @@ -168,6 +177,14 @@ bool CANConnection::sendFrame(const CANFrame& pFrame) return ret; } + CANFrame *txFrame; + txFrame = getQueue().get(); + if (txFrame) + { + *txFrame = pFrame; + } + getQueue().queue(); + return piSendFrame(pFrame); } @@ -245,11 +262,11 @@ CANCon::type CANConnection::getType() { CANCon::status CANConnection::getStatus() { - return (CANCon::status) mStatus.load(); + return (CANCon::status) mStatus.loadRelaxed(); } void CANConnection::setStatus(CANCon::status pStatus) { - mStatus.store(pStatus); + mStatus.storeRelaxed(pStatus); } bool CANConnection::isCapSuspended() { @@ -288,7 +305,12 @@ bool CANConnection::addTargettedFrame(int pBusId, uint32_t ID, uint32_t mask, QO target.id = ID; target.mask = mask; target.observer = receiver; - mBusData[pBusId].mTargettedFrames.append(target); + if (pBusId > -1) + mBusData[pBusId].mTargettedFrames.append(target); + else + { + for (int i = 0; i < mBusData.count(); i++) mBusData[i].mTargettedFrames.append(target); + } return true; } @@ -339,7 +361,10 @@ void CANConnection::checkTargettedFrame(CANFrame &frame) //qDebug() << "Got frame with ID " << frame.ID << " on bus " << frame.bus; if (mBusData.count() == 0) return; - if (mBusData[frame.bus].mTargettedFrames.length() == 0) return; + int bus = frame.bus; + if (bus > (mBusData.length() - 1)) bus = mBusData.length() - 1; + + if (mBusData[bus].mTargettedFrames.length() == 0) return; foreach (const CANFltObserver filt, mBusData[frame.bus].mTargettedFrames) { //qDebug() << "Checking filter with id " << filt.id << " mask " << filt.mask; diff --git a/connections/canconnection.h b/connections/canconnection.h index 38d1160..fde8296 100644 --- a/connections/canconnection.h +++ b/connections/canconnection.h @@ -21,13 +21,19 @@ protected: * @param pPort: string containing port name * @param pDriver: string containing driver name - Really only used for SerialBus connections * @param pType: the type of connection @ref CANCon::type + * @param pSerialSpeed: for devices with variable serial speed this is that speed. + * @param pBusSpeed: set an initial speed when opening this connection * @param pNumBuses: the number of buses the device has * @param pQueueLen: the length of the lock free queue to use * @param pUseThread: if set to true, object will be execute in a dedicated thread */ CANConnection(QString pPort, QString pDriver, - CANCon::type pType, + CANCon::type pType, + int pSerialSpeed, + int pBusSpeed, + bool pCanFd, + int pDataRate, int pNumBuses, int pQueueLen, bool pUseThread); @@ -201,6 +207,7 @@ protected: int mNumBuses; //protected to allow connected device to figure out how many buses are available QVector mBusData; bool mConsoleOutput; //send debugging info to the console? + int mSerialSpeed; //determine if the passed frame is part of a filter or not. void checkTargettedFrame(CANFrame &frame); diff --git a/connections/canconnectionmodel.cpp b/connections/canconnectionmodel.cpp index aba9c7c..c014f55 100644 --- a/connections/canconnectionmodel.cpp +++ b/connections/canconnectionmodel.cpp @@ -86,6 +86,9 @@ QVariant CANConnectionModel::data(const QModelIndex &index, int role) const case CANCon::SERIALBUS: return "SerialBus"; case CANCon::GVRET_SERIAL: return "GVRET"; case CANCon::KAYAK: return "socketcand"; + case CANCon::LAWICEL: return "LAWICEL"; + case CANCon::CANSERVER: return "CANserver"; + case CANCon::CANLOGSERVER: return "CanLogServer"; default: {} } else qDebug() << "Tried to show connection type but connection was nullptr"; diff --git a/connections/canlogserver.cpp b/connections/canlogserver.cpp new file mode 100644 index 0000000..78a2bc5 --- /dev/null +++ b/connections/canlogserver.cpp @@ -0,0 +1,186 @@ +#include +#include +#include +#include +#include +#include + +#include "utility.h" +#include "canlogserver.h" + +CanLogServer::CanLogServer(QString serverAddressString) : + CANConnection(serverAddressString, "CanLogserver", CANCon::CANLOGSERVER, 0, 0, false, 0, 1, 4000, true), + m_ptcpSocket(new QTcpSocket(this)) +{ + + qDebug() << "Canlogserver: " << "Constructing new Connection..."; + + CANBus bus_info; + bus_info.setActive(true); + bus_info.setListenOnly(true); + bus_info.setSpeed(500000); + + setBusConfig(0, bus_info); + + // Connect data ready signal + connect(m_ptcpSocket, SIGNAL(readyRead()), this, SLOT(readNetworkData())); + // Connect network connected signal + connect(m_ptcpSocket, SIGNAL(connected()),this, SLOT(networkConnected())); + // Connect network disconnected signal + connect(m_ptcpSocket, SIGNAL(disconnected()),this, SLOT(networkDisconnected())); + // Save address + this->m_qsAddress = serverAddressString; +} + + +CanLogServer::~CanLogServer() +{ + qDebug() << "Canlogserver: " << "Deconstructing Connection..."; + + stop(); + + m_ptcpSocket->close(); + delete m_ptcpSocket; + m_ptcpSocket = NULL; +} + +void CanLogServer::readNetworkData() +{ + // If capture is suspended bail out after reading the bytes from the network. No need to parse them + if(isCapSuspended()) + return; + + while (m_ptcpSocket->canReadLine()) { + // Get a complete line and remove whitespace at the start and at the end of the string + QString data = QString(m_ptcpSocket->readLine()).trimmed(); + // Split to space (obtain "<(time)> ") + QStringList lstData = data.split(" "); + // Expect 3 item in list + if(lstData.size() == 3){ + // Obtain the timestamp. Remove '(', ')' and '.' (logserver send as "(time.fraction)"). + QString qstrTs = lstData[0].remove(QChar('(')).remove(QChar('.')).remove(QChar(')')); + // One bus only. TODO: Use another parameter in connection conf for spcify can bus id to capture. + QString qstrCanId = "0"; + // Split frame + QStringList lstMsg = lstData[2].split("#"); + // Expect 2 item (#) + if(lstMsg.size() == 2){ + // Extract id + QString qstrId = lstMsg[0]; + // Extract payload + QString qstrPayload = lstMsg[1]; + // Support only normal can message. Extended CAN not supported. + if(qstrId.size() <= 4){ + // Prepare the frame + CANFrame* frame_p = getQueue().get(); + // Check for frame existence + if(frame_p){ + // Set frame ID + frame_p->setFrameId(qstrId.toInt(nullptr, 16)); + // Extended frame NOT SUPPORTED + frame_p->setExtendedFrameFormat(0); + // Set bus id + frame_p->bus = qstrCanId.toInt(); + // Set frame type + frame_p->setFrameType(QCanBusFrame::DataFrame); + // Frame is recived + frame_p->isReceived = true; + // Set timestamp + frame_p->setTimeStamp(QCanBusFrame::TimeStamp(0, qstrTs.toULongLong())); + // Set payload + frame_p->setPayload(QByteArray::fromHex(qstrPayload.toUtf8())); + // Elaborate frame + checkTargettedFrame(*frame_p); + /* enqueue frame */ + getQueue().queue(); + } + qDebug() << data << "---" << qstrTs << " - " << qstrId << " + " << qstrPayload; + } + } + } + } +} + +void CanLogServer::networkConnected() +{ + CANConStatus stats; + qDebug() << "networkConnected"; + + setStatus(CANCon::CONNECTED); + stats.conStatus = getStatus(); + stats.numHardwareBuses = 1; + emit status(stats); +} + +void CanLogServer::networkDisconnected() +{ + qDebug() << "networkDisconnected"; +} + +void CanLogServer::piStarted() +{ + this->connectToDevice(); +} + +void CanLogServer::piSuspend(bool pSuspend) +{ + /* update capSuspended */ + setCapSuspended(pSuspend); + + /* flush queue if we are suspended */ + if(isCapSuspended()) + getQueue().flush(); +} + + +void CanLogServer::piStop() +{ + this->disconnectFromDevice(); +} + + +bool CanLogServer::piGetBusSettings(int pBusIdx, CANBus& pBus) +{ + return getBusConfig(pBusIdx, pBus); +} + + +void CanLogServer::piSetBusSettings(int pBusIdx, CANBus bus) +{ + /* sanity checks */ + if( (pBusIdx < 0) || pBusIdx >= getNumBuses()) + return; + + /* copy bus config */ + setBusConfig(pBusIdx, bus); +} + + +bool CanLogServer::piSendFrame(const CANFrame& ) +{ + //We don't support sending frames right now + return true; +} + +void CanLogServer::connectToDevice() +{ + qDebug() << "Canlogserver: " << "Establishing connection to a Canlogserver device..."; + + QUrl url("http://" + m_qsAddress); + qDebug() << "address:" << m_qsAddress; + qDebug() << "Host:" << url.host(); + qDebug() << "Port:" << url.port(); + // Set status at not connected + setStatus(CANCon::NOT_CONNECTED); + // No proxy for connection + m_ptcpSocket->setProxy(QNetworkProxy::NoProxy); + // Connect to log server + m_ptcpSocket->connectToHost(url.host(), url.port()); +} + +void CanLogServer::disconnectFromDevice() +{ + qDebug() << "Canlogserver: " << "Disconnecting..."; + // Close socket + m_ptcpSocket->close(); +} diff --git a/connections/canlogserver.h b/connections/canlogserver.h new file mode 100644 index 0000000..ee7cab5 --- /dev/null +++ b/connections/canlogserver.h @@ -0,0 +1,57 @@ +#ifndef CANLOGSERVER_H +#define CANLOGSERVER_H + +#include + +#include +#include +#include +#include + +/*************/ +#include +/*************/ + +#include "canframemodel.h" +#include "canconnection.h" +#include "canconmanager.h" + +class CanLogServer : public CANConnection +{ + Q_OBJECT + +public: + CanLogServer(QString serverAddress); + virtual ~CanLogServer(); + +// Interface +protected: + virtual void piStarted(); + virtual void piStop(); + virtual void piSetBusSettings(int pBusIdx, CANBus pBus); + virtual bool piGetBusSettings(int pBusIdx, CANBus& pBus); + virtual void piSuspend(bool pSuspend); + virtual bool piSendFrame(const CANFrame&); + +private slots: + void readNetworkData(); + void networkConnected(); + void networkDisconnected(); + +// Utility +private: + void readSettings(); + void connectToDevice(); + void disconnectFromDevice(); + void heartbeat(); + +// Attributes +protected: + QTcpSocket *m_ptcpSocket = nullptr; + QString m_qsAddress; + +// QUdpSocket *_udpClient; + +// QTimer *_heartbeatTimer; +}; +#endif // CANLOGSERVER_H diff --git a/connections/canserver.cpp b/connections/canserver.cpp new file mode 100644 index 0000000..a8ad145 --- /dev/null +++ b/connections/canserver.cpp @@ -0,0 +1,238 @@ +// +// canserver.cpp +// SavvyCAN +// +// Created by Chris Whiteford on 2022-01-21. +// + +#include +#include +#include +#include +#include +#include + +#include "utility.h" +#include "canserver.h" + +CANserver::CANserver(QString serverAddressString) : + CANConnection(serverAddressString, "CANserver", CANCon::CANSERVER, 0, 0, false, 0, 3, 4000, true), + _udpClient(new QUdpSocket(this)), + _heartbeatTimer(new QTimer(this)) +{ + + qDebug() << "CANserver: " << "Constructing new Connection..."; + + CANBus bus_info; + bus_info.setActive(true); + bus_info.setListenOnly(true); + bus_info.setSpeed(500000); + + setBusConfig(0, bus_info); + setBusConfig(1, bus_info); + setBusConfig(2, bus_info); + + // setup udp client + this->_canserverAddress = QHostAddress(serverAddressString); + connect(_udpClient, SIGNAL(readyRead()), this, SLOT(readNetworkData())); + + // setup signal and slot + connect(_heartbeatTimer, SIGNAL(timeout()), this, SLOT(heartbeatTimerSlot())); + _heartbeatTimer->stop(); +} + + +CANserver::~CANserver() +{ + qDebug() << "CANserver: " << "Deconstructing Connection..."; + + stop(); + + delete _heartbeatTimer; _heartbeatTimer = NULL; + delete _udpClient; _udpClient = NULL; +} + +void CANserver::piStarted() +{ + this->connectToDevice(); +} + +void CANserver::piSuspend(bool pSuspend) +{ + /* update capSuspended */ + setCapSuspended(pSuspend); + + /* flush queue if we are suspended */ + if(isCapSuspended()) + getQueue().flush(); +} + + +void CANserver::piStop() +{ + this->disconnectFromDevice(); +} + + +bool CANserver::piGetBusSettings(int pBusIdx, CANBus& pBus) +{ + return getBusConfig(pBusIdx, pBus); +} + + +void CANserver::piSetBusSettings(int pBusIdx, CANBus bus) +{ + /* sanity checks */ + if( (pBusIdx < 0) || pBusIdx >= getNumBuses()) + return; + + /* copy bus config */ + setBusConfig(pBusIdx, bus); +} + + +bool CANserver::piSendFrame(const CANFrame& ) +{ + //We don't support sending frames right now + return true; +} + +void CANserver::connectToDevice() +{ + qDebug() << "CANserver: " << "Establishing UDP connection to a CANserver device..."; + + bool bindResult = _udpClient->bind(1338); + qDebug() << "CANserver: " << "UDP Bind result: " << bindResult; + + heartbeat(); + + qDebug() << "CANserver: " << "Scheduling additional heartbeats..."; + _heartbeatTimer->start(3500); + + setStatus(CANCon::CONNECTED); + + CANConStatus stats; + stats.conStatus = getStatus(); + stats.numHardwareBuses = 3; + emit status(stats); +} + +void CANserver::disconnectFromDevice() +{ + _heartbeatTimer->stop(); + + QByteArray Data; + Data.append("bye"); + QNetworkDatagram datagram(Data, _canserverAddress, 1338); + + qDebug() << "CANserver: " << "Sending bye..."; + _udpClient->writeDatagram(datagram); + + + _udpClient->close(); +} + +void CANserver::heartbeat() +{ + //Send the PANDA hello packet (in this case we are using the v1 version of the protocol because we just want to get ALL the data) + QByteArray Data; + Data.append("hello"); + QNetworkDatagram datagram(Data, _canserverAddress, 1338); + + qDebug() << "CANserver: " << "Sending a heartbeat..."; + _udpClient->writeDatagram(datagram); +} + +void CANserver::readNetworkData() +{ + QByteArray datagram; + do { + datagram.resize(_udpClient->pendingDatagramSize()); + _udpClient->readDatagram(datagram.data(), datagram.size()); + } while (_udpClient->hasPendingDatagrams()); + + + // If capture is suspended bail out after reading the bytes from the network. No need to parse them + if(isCapSuspended()) + return; + + + uint16_t packetCount = datagram.length() / 16; + //qDebug() << "Processing " << packetCount << " packets"; + + //Process this chunk of data + for (int i = 0 ; i < packetCount; i++) + { + uint16_t headerByteLocation = (i*16); + uint16_t dataByteLocation = (i*16) + 8; + + + uint32_t headerData1Byte1 = datagram.at(headerByteLocation + 0) & 0xFF; + uint32_t headerData1Byte2 = datagram.at(headerByteLocation + 1) & 0xFF; + uint32_t headerData1Byte3 = datagram.at(headerByteLocation + 2) & 0xFF; + uint32_t headerData1Byte4 = datagram.at(headerByteLocation + 3) & 0xFF; + + uint32_t headerData2Byte1 = datagram.at(headerByteLocation + 4) & 0xFF; + uint32_t headerData2Byte2 = datagram.at(headerByteLocation + 5) & 0xFF; + uint32_t headerData2Byte3 = datagram.at(headerByteLocation + 6) & 0xFF; + uint32_t headerData2Byte4 = datagram.at(headerByteLocation + 7) & 0xFF; + + + //printf("HB1: %02X, %02X, %02X, %02X\n", headerData1Byte1, headerData1Byte2, headerData1Byte3, headerData1Byte4); + //printf("HB2: %02X, %02X, %02X, %02X\n", headerData2Byte1, headerData2Byte2, headerData2Byte3, headerData2Byte4); + + uint32_t headerData1 = headerData1Byte1; + headerData1 += headerData1Byte2 << 8; + headerData1 += headerData1Byte3 << 16; + headerData1 += headerData1Byte4 << 24; + + uint32_t headerData2 = headerData2Byte1; + headerData2 += headerData2Byte2 << 8; + headerData2 += headerData2Byte3 << 16; + headerData2 += headerData2Byte4 << 24; + + uint32_t frameId = headerData1 >> 21; + uint32_t length = (headerData2 & 0x0F); + uint32_t busId = (headerData2 >> 4); + + //printf("frameId: %02X, busId: %d, length: %d\n", frameId, busId, length); + + CANFrame* frame_p = getQueue().get(); + if(frame_p) + { + frame_p->setFrameId(frameId); + frame_p->setExtendedFrameFormat(0); + + // We need to change the bus id if it is the special CANserver bus id. + // This keeps us from needing to define 15 busses just to get access to our special one + if (busId == 15) + { + busId = 2; + } + frame_p->bus = busId; + + frame_p->setFrameType(QCanBusFrame::DataFrame); + frame_p->isReceived = true; + + frame_p->setTimeStamp(QCanBusFrame::TimeStamp(0, QDateTime::currentMSecsSinceEpoch() * 1000ul)); + + frame_p->setPayload(datagram.mid(dataByteLocation, length)); + + checkTargettedFrame(*frame_p); + + /* enqueue frame */ + getQueue().queue(); + } + } + +} + +void CANserver::heartbeatTimerSlot() +{ + heartbeat(); +} + +void CANserver::readSettings() +{ + QSettings settings; +} diff --git a/connections/canserver.h b/connections/canserver.h new file mode 100644 index 0000000..c7e8f1c --- /dev/null +++ b/connections/canserver.h @@ -0,0 +1,64 @@ +// +// canserver.h +// SavvyCAN +// +// Created by Chris Whiteford on 2022-01-21. +// + +#ifndef canserver_h +#define canserver_h + +#include + +#include +#include +#include +#include + +/*************/ +#include +/*************/ + +#include "canframemodel.h" +#include "canconnection.h" +#include "canconmanager.h" + +class CANserver : public CANConnection +{ + Q_OBJECT + +public: + CANserver(QString serverAddress); + virtual ~CANserver(); + +protected: + + virtual void piStarted(); + virtual void piStop(); + virtual void piSetBusSettings(int pBusIdx, CANBus pBus); + virtual bool piGetBusSettings(int pBusIdx, CANBus& pBus); + virtual void piSuspend(bool pSuspend); + virtual bool piSendFrame(const CANFrame&); + +private slots: + void readNetworkData(); + void heartbeatTimerSlot(); + + +private: + void readSettings(); + + void connectToDevice(); + void disconnectFromDevice(); + + void heartbeat(); + +protected: + QHostAddress _canserverAddress; + + QUdpSocket *_udpClient; + + QTimer *_heartbeatTimer; +}; + +#endif /* canserver_h */ diff --git a/connections/connectionwindow.cpp b/connections/connectionwindow.cpp index ae852e0..5e4df7a 100644 --- a/connections/connectionwindow.cpp +++ b/connections/connectionwindow.cpp @@ -64,6 +64,7 @@ ConnectionWindow::ConnectionWindow(QWidget *parent) : ui->cbBusSpeed->addItem("33333"); ui->cbBusSpeed->addItem("50000"); + ui->cbBusSpeed->addItem("83333"); ui->cbBusSpeed->addItem("100000"); ui->cbBusSpeed->addItem("125000"); ui->cbBusSpeed->addItem("250000"); @@ -78,13 +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); } @@ -109,16 +109,16 @@ void ConnectionWindow::readPendingDatagrams() while(!CANBeaconXml.atEnd() && !CANBeaconXml.hasError()) { CANBeaconXml.readNext(); - if(CANBeaconXml.name() == "CANBeacon" && !CANBeaconXml.isEndElement()) + if(CANBeaconXml.name() == QString("CANBeacon") && !CANBeaconXml.isEndElement()) KayakHost.append(CANBeaconXml.attributes().value("name")); - if(CANBeaconXml.name() == "URL") + if(CANBeaconXml.name() == QString("URL")) KayakHost.append(" (" + CANBeaconXml.readElementText() + ')'); //Kayak can theoretically send multiple busses over one ports //TODO: implement this case in socketcand.cpp - if(CANBeaconXml.name() == "Bus" && !CANBeaconXml.isEndElement()) - KayakBus.append(CANBeaconXml.attributes().value("name") + ","); + if(CANBeaconXml.name() == QString("Bus") && !CANBeaconXml.isEndElement()) + KayakBus.append(CANBeaconXml.attributes().value("name").toUtf8() + ","); } KayakHost = KayakBus.left(KayakBus.length() - 1) + "@" + KayakHost; @@ -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); } } @@ -251,6 +251,10 @@ void ConnectionWindow::handleNewConn() CANCon::type newType; QString newPort; QString newDriver; + int newSerialSpeed; + int newBusSpeed; + bool newCanFd; + int newDataRate; CANConnection *conn; if (thisDialog->exec() == QDialog::Accepted) @@ -258,7 +262,11 @@ void ConnectionWindow::handleNewConn() newType = thisDialog->getConnectionType(); newPort = thisDialog->getPortName(); newDriver = thisDialog->getDriverName(); - conn = create(newType, newPort, newDriver); + newSerialSpeed = thisDialog->getSerialSpeed(); + newBusSpeed = thisDialog->getBusSpeed(); + newCanFd=thisDialog->isCanFd(); + newDataRate = thisDialog->getDataRate(); + conn = create(newType, newPort, newDriver, newSerialSpeed, newBusSpeed, newCanFd, newDataRate); if (conn) { connModel->add(conn); @@ -293,6 +301,8 @@ void ConnectionWindow::handleResetConn() { QString port, driver; CANCon::type type; + int serSpeed, busSpeed, dataRate; + bool canFd; int selIdx = ui->tableConnections->selectionModel()->currentIndex().row(); if (selIdx <0) return; @@ -305,13 +315,18 @@ void ConnectionWindow::handleResetConn() type = conn_p->getType(); port = conn_p->getPort(); driver = conn_p->getDriver(); + serSpeed = 0; //TODO: implement these + busSpeed = 0; + dataRate = 0; + canFd = false; + /* stop and delete connection */ conn_p->stop(); conn_p = nullptr; - conn_p = create(type, port, driver); + conn_p = create(type, port, driver, serSpeed, busSpeed,canFd,dataRate); if (conn_p) connModel->replace(selIdx, conn_p); } @@ -358,6 +373,8 @@ void ConnectionWindow::saveBusSettings() bus.setSpeed(ui->cbBusSpeed->currentText().toInt()); bus.setActive(ui->ckEnable->isChecked()); bus.setListenOnly(ui->ckListenOnly->isChecked()); + bus.setCanFD(ui->canFDEnable->isChecked()); + bus.setDataRate(ui->cbDataRate->currentText().toInt()); conn_p->setBusSettings(offset, bus); } } @@ -374,7 +391,10 @@ void ConnectionWindow::populateBusDetails(int offset) { //bool ret; //int numBuses; - + ui->canFDEnable->setVisible(false); + ui->canFDEnable_label->setVisible(false); + ui->dataRate_label->setVisible(false); + ui->cbDataRate->setVisible(false); CANConnection* conn_p = connModel->getAtIdx(selIdx); CANBus bus; if(!conn_p) return; @@ -389,6 +409,14 @@ void ConnectionWindow::populateBusDetails(int offset) //ui->lblBusNum->setText(QString::number(busBase + offset)); ui->ckListenOnly->setChecked(bus.isListenOnly()); ui->ckEnable->setChecked(bus.isActive()); + if (conn_p->getType() == CANCon::type::SERIALBUS || conn_p->getType() == CANCon::type::LAWICEL) + { + ui->canFDEnable->setVisible(true); + ui->canFDEnable_label->setVisible(true); + ui->canFDEnable->setChecked(bus.isCanFD()); + ui->cbDataRate->setVisible(true); + ui->dataRate_label->setVisible(true); + } bool found = false; for (int i = 0; i < ui->cbBusSpeed->count(); i++) @@ -400,7 +428,19 @@ void ConnectionWindow::populateBusDetails(int offset) break; } } + if (!found) ui->cbBusSpeed->addItem(QString::number(bus.getSpeed())); + found = false; + for (int i = 0; i < ui->cbDataRate->count(); i++) + { + if (bus.getDataRate() == ui->cbDataRate->itemText(i).toInt()) + { + found = true; + ui->cbDataRate->setCurrentIndex(i); + break; + } + } + if (!found) ui->cbDataRate->addItem(QString::number(bus.getDataRate())); } } @@ -411,12 +451,11 @@ void ConnectionWindow::currentTabChanged(int newIdx) void ConnectionWindow::currentRowChanged(const QModelIndex ¤t, const QModelIndex &previous) { - Q_UNUSED(previous); - int selIdx = current.row(); - - disconnect(connModel->getAtIdx(previous.row()), SIGNAL(debugOutput(QString)), 0, 0); - disconnect(this, SIGNAL(sendDebugData(QByteArray)), connModel->getAtIdx(previous.row()), SLOT(debugInput(QByteArray))); + CANConnection* prevConn = connModel->getAtIdx(previous.row()); + if(prevConn != nullptr) + disconnect(prevConn, &CANConnection::debugOutput, nullptr, nullptr); + disconnect(this, &ConnectionWindow::sendDebugData, nullptr, nullptr); /* set parameters */ if (selIdx == -1) { @@ -433,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(); @@ -446,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); } } } @@ -476,21 +515,20 @@ void ConnectionWindow::handleSendText() { emit sendDebugData(bytes); } -CANConnection* ConnectionWindow::create(CANCon::type pTye, QString pPortName, QString pDriver) +CANConnection* ConnectionWindow::create(CANCon::type pTye, QString pPortName, QString pDriver, int pSerialSpeed, int pBusSpeed, bool pCanFd, int pDataRate) { CANConnection* conn_p; /* create connection */ - conn_p = CanConFactory::create(pTye, pPortName, pDriver); + conn_p = CanConFactory::create(pTye, pPortName, pDriver, pSerialSpeed, pBusSpeed, pCanFd, pDataRate); 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(); @@ -501,8 +539,10 @@ CANConnection* ConnectionWindow::create(CANCon::type pTye, QString pPortName, QS void ConnectionWindow::loadConnections() { +#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) qRegisterMetaTypeStreamOperators(); qRegisterMetaTypeStreamOperators>(); +#endif QSettings settings; @@ -516,7 +556,8 @@ void ConnectionWindow::loadConnections() for(int i = 0 ; i < portNames.count() ; i++) { - CANConnection* conn_p = create((CANCon::type)devTypes[i], portNames[i], driverNames[i]); + //TODO: add serial speed and bus speed to this properly. + CANConnection* conn_p = create((CANCon::type)devTypes[i], portNames[i], driverNames[i], 0, 0, false, 0); /* add connection to model */ connModel->add(conn_p); } @@ -534,6 +575,8 @@ void ConnectionWindow::saveConnections() QVector portNames; QVector devTypes; QVector driverNames; + QVector serialSpeeds; + QVector busSpeeds; /* save connections */ foreach(CANConnection* conn_p, conns) diff --git a/connections/connectionwindow.h b/connections/connectionwindow.h index b13f31f..61a94c5 100644 --- a/connections/connectionwindow.h +++ b/connections/connectionwindow.h @@ -65,7 +65,7 @@ private: QVector remoteDeviceIPGVRET; QVector remoteDeviceKayak; - CANConnection* create(CANCon::type pTye, QString pPortName, QString pDriver); + CANConnection* create(CANCon::type pTye, QString pPortName, QString pDriver, int pSerialSpeed, int pBusSpeed, bool pCanFd, int pDataRate); void populateBusDetails(int offset); void loadConnections(); void saveConnections(); diff --git a/connections/gvretserial.cpp b/connections/gvretserial.cpp index 4da0f3a..dc63ae0 100644 --- a/connections/gvretserial.cpp +++ b/connections/gvretserial.cpp @@ -9,7 +9,7 @@ #include "gvretserial.h" GVRetSerial::GVRetSerial(QString portName, bool useTcp) : - CANConnection(portName, "gvret", CANCon::GVRET_SERIAL, 3, 4000, true), + CANConnection(portName, "gvret", CANCon::GVRET_SERIAL, 0, 0, false, 0, 3, 4000, true), mTimer(this), /*NB: set this as parent of timer to manage it from working thread */ useTcp(useTcp) { @@ -485,6 +485,7 @@ void GVRetSerial::serialError(QSerialPort::SerialPortError err) killConnection = true; piStop(); break; +#if QT_VERSION <= QT_VERSION_CHECK( 6, 0, 0 ) case QSerialPort::ParityError: errMessage = "Parity error on serial port"; break; @@ -494,6 +495,7 @@ void GVRetSerial::serialError(QSerialPort::SerialPortError err) case QSerialPort::BreakConditionError: errMessage = "Break error on serial port"; break; +#endif case QSerialPort::WriteError: errMessage = "Write error on serial port"; piStop(); @@ -584,10 +586,11 @@ void GVRetSerial::readSerialData() { c = data.at(i); //qDebug() << c << " " << QString::number(c, 16) << " " << QString(c); - debugBuild = debugBuild % QString::number(c, 16) % " "; + debugBuild = debugBuild % QString::number(c, 16).rightJustified(2,'0') % " "; procRXChar(c); } debugOutput(debugBuild); + //qDebug() << debugBuild; } //Debugging data sent from connection window. Inject it into Comm traffic. @@ -640,7 +643,7 @@ void GVRetSerial::procRXChar(unsigned char c) break; case 9: validationCounter = 10; - //qDebug() << "Got validated"; + qDebug() << "Got validated"; rx_state = IDLE; break; case 12: @@ -653,6 +656,15 @@ void GVRetSerial::procRXChar(unsigned char c) qDebug() << "Got extended buses info reply"; rx_step = 0; break; + case 20: + rx_state = BUILD_FD_FRAME; + rx_step = 0; + break; + case 22: + rx_state = GET_FD_SETTINGS; + rx_step = 0; + qDebug() << "Got FD settings reply"; + break; } break; case BUILD_CAN_FRAME: @@ -702,6 +714,93 @@ void GVRetSerial::procRXChar(unsigned char c) break; default: if (rx_step < buildData.length() + 9) + { + buildData[rx_step - 9] = c; + if (rx_step == buildData.length() + 8) //it's the last data byte so immediately process the frame + { + rx_state = IDLE; + rx_step = 0; + buildFrame.isReceived = true; + buildFrame.setPayload(buildData); + buildFrame.setFrameType(QCanBusFrame::FrameType::DataFrame); + if (!isCapSuspended()) + { + /* get frame from queue */ + CANFrame* frame_p = getQueue().get(); + if(frame_p) { + //qDebug() << "GVRET got frame on bus " << frame_p->bus; + /* copy frame */ + *frame_p = buildFrame; + checkTargettedFrame(buildFrame); + /* enqueue frame */ + getQueue().queue(); + } + else + qDebug() << "can't get a frame, ERROR"; + + //take the time the frame came in and try to resync the time base. + //if (continuousTimeSync) txTimestampBasis = QDateTime::currentMSecsSinceEpoch() - (buildFrame.timestamp / 1000); + } + } + } + else //should never get here! But, just in case, reset the comm + { + rx_state = IDLE; + rx_step = 0; + } + break; + } + rx_step++; + break; + case BUILD_FD_FRAME: + switch (rx_step) + { + case 0: + buildTimestamp = c; + break; + case 1: + buildTimestamp |= (uint)(c << 8); + break; + case 2: + buildTimestamp |= (uint)c << 16; + break; + case 3: + buildTimestamp |= (uint)c << 24; + + buildTimestamp += timeBasis; + if (useSystemTime) + { + buildTimestamp = QDateTime::currentMSecsSinceEpoch() * 1000l; + } + buildFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, buildTimestamp)); + break; + case 4: + buildId = c; + break; + case 5: + buildId |= c << 8; + break; + case 6: + buildId |= c << 16; + break; + case 7: + buildId |= c << 24; + if ((buildId & 1 << 31) == 1u << 31) + { + buildId &= 0x7FFFFFFF; + buildFrame.setExtendedFrameFormat(true); + } + else buildFrame.setExtendedFrameFormat(false); + buildFrame.setFrameId(buildId); + break; + case 8: + buildData.resize(c & 0x3F); + break; + case 9: + buildFrame.bus = c; + break; + default: + if (rx_step < buildData.length() + 10) { buildData[rx_step - 9] = c; } @@ -719,7 +818,7 @@ void GVRetSerial::procRXChar(unsigned char c) if(frame_p) { //qDebug() << "GVRET got frame on bus " << frame_p->bus; /* copy frame */ - *frame_p = buildFrame; + *frame_p = buildFrame; checkTargettedFrame(buildFrame); /* enqueue frame */ getQueue().queue(); @@ -912,6 +1011,8 @@ void GVRetSerial::procRXChar(unsigned char c) emit status(stats); break; + case GET_FD_SETTINGS: + break; case GET_EXT_BUSES: switch (rx_step) { diff --git a/connections/gvretserial.h b/connections/gvretserial.h index 4dc5e47..bee1e23 100644 --- a/connections/gvretserial.h +++ b/connections/gvretserial.h @@ -32,7 +32,9 @@ enum STATE GET_DEVICE_INFO, SET_SINGLEWIRE_MODE, GET_NUM_BUSES, - GET_EXT_BUSES + GET_EXT_BUSES, + BUILD_FD_FRAME, + GET_FD_SETTINGS }; } diff --git a/connections/lawicel_serial.cpp b/connections/lawicel_serial.cpp new file mode 100644 index 0000000..34b4fd4 --- /dev/null +++ b/connections/lawicel_serial.cpp @@ -0,0 +1,690 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "lawicel_serial.h" +#include "utility.h" + +LAWICELSerial::LAWICELSerial(QString portName, int serialSpeed, int lawicelSpeed, bool canFd, int dataRate) : + CANConnection(portName, "LAWICEL", CANCon::LAWICEL,serialSpeed, lawicelSpeed, canFd, dataRate, 3, 4000, true), + mTimer(this) /*NB: set this as parent of timer to manage it from working thread */ +{ + sendDebug("LAWICELSerial()"); + + serial = nullptr; + isAutoRestart = false; + + readSettings(); +} + + +LAWICELSerial::~LAWICELSerial() +{ + stop(); + sendDebug("~LAWICELSerial()"); +} + +void LAWICELSerial::sendDebug(const QString debugText) +{ + qDebug() << debugText; + debugOutput(debugText); +} + +void LAWICELSerial::sendToSerial(const QByteArray &bytes) +{ + if (serial == nullptr) + { + sendDebug("Attempt to write to serial port when it has not been initialized!"); + return; + } + + if (serial && !serial->isOpen()) + { + sendDebug("Attempt to write to serial port when it is not open!"); + return; + } + + QString buildDebug; + buildDebug = "Write to serial -> "; + foreach (int byt, bytes) { + byt = (unsigned char)byt; + buildDebug = buildDebug % QString::number(byt, 16) % " "; + } + sendDebug(buildDebug); + + if (serial) serial->write(bytes); +} + +void LAWICELSerial::piStarted() +{ + connectDevice(); +} + + +void LAWICELSerial::piSuspend(bool pSuspend) +{ + /* update capSuspended */ + setCapSuspended(pSuspend); + + /* flush queue if we are suspended */ + if(isCapSuspended()) + getQueue().flush(); +} + + +void LAWICELSerial::piStop() +{ + mTimer.stop(); + disconnectDevice(); +} + + +bool LAWICELSerial::piGetBusSettings(int pBusIdx, CANBus& pBus) +{ + return getBusConfig(pBusIdx, pBus); +} + + +void LAWICELSerial::piSetBusSettings(int pBusIdx, CANBus bus) +{ + /* sanity checks */ + if( (pBusIdx < 0) || pBusIdx >= getNumBuses()) + return; + + /* copy bus config */ + setBusConfig(pBusIdx, bus); +/* + qDebug() << "About to update bus " << pBusIdx << " on GVRET"; + if (pBusIdx == 0) + { + can0Baud = bus.getSpeed(); + can0Baud |= 0x80000000; + if (bus.isActive()) + { + can0Baud |= 0x40000000; + can0Enabled = true; + } + else can0Enabled = false; + + if (bus.isListenOnly()) + { + can0Baud |= 0x20000000; + can0ListenOnly = true; + } + else can0ListenOnly = false; + } +*/ + if (pBusIdx < 2) { + /* update baud rates */ + QByteArray buffer; + //sendDebug("Got signal to update bauds. 1: " + QString::number((can0Baud & 0xFFFFFFF))); + buffer[0] = (char)0xF1; //start of a command over serial + //sendToSerial(buffer); + } + +} + + +bool LAWICELSerial::piSendFrame(const CANFrame& frame) +{ + QByteArray buffer; + int c; + quint32 ID; + + //qDebug() << "Sending out lawicel frame with id " << frame.ID << " on bus " << frame.bus; + + framesRapid++; + + if (serial == nullptr) return false; + if (serial && !serial->isOpen()) return false; + //if (!isConnected) return false; + + // Doesn't make sense to send an error frame + // to an adapter + if (frame.frameId() & 0x20000000) { + return true; + } + + ID = frame.frameId(); + if (frame.hasExtendedFrameFormat()) ID |= 1u << 31; + + int idx = 0; + QString buildStr; + if(frame.hasFlexibleDataRateFormat()){ + if (frame.hasExtendedFrameFormat()) + { + if (frame.hasBitrateSwitch()) + buildStr = QString::asprintf("B%08X%u", ID, LAWICELSerial::bytes_to_dlc_code(frame.payload().length())); + else + buildStr = QString::asprintf("D%08X%u", ID, LAWICELSerial::bytes_to_dlc_code(frame.payload().length())); + } + else + { + if (frame.hasBitrateSwitch()) + buildStr = QString::asprintf("b%03X%u", ID, LAWICELSerial::bytes_to_dlc_code(frame.payload().length())); + else + buildStr = QString::asprintf("d%03X%u", ID, LAWICELSerial::bytes_to_dlc_code(frame.payload().length())); + } + } + else { + if (frame.hasExtendedFrameFormat()) + { + buildStr = QString::asprintf("T%08X%u", ID, frame.payload().length()); + } + else + { + buildStr = QString::asprintf("t%03X%u", ID, frame.payload().length()); + } + } + foreach (QChar chr, buildStr) + { + buffer[idx] = chr.toLatin1(); + idx++; + } + + for (c = 0; c < frame.payload().length(); c++) + { + QString byt = Utility::formatByteAsHex(frame.payload()[c]); + buffer[idx + (c * 2)] = byt[0].toLatin1(); + buffer[idx + (c * 2) + 1] = byt[1].toLatin1(); + } + buffer[idx + (frame.payload().length() * 2)] = 13; //CR + + sendToSerial(buffer); + + return true; +} + + + +/****************************************************************/ + +void LAWICELSerial::readSettings() +{ + QSettings settings; +} + + +void LAWICELSerial::connectDevice() +{ + QSettings settings; + + /* disconnect device */ + if(serial) + disconnectDevice(); + + /* open new device */ + + qDebug() << "Serial port: " << getPort(); + + serial = new QSerialPort(QSerialPortInfo(getPort())); + if(!serial) { + sendDebug("can't open serial port " + getPort()); + return; + } + sendDebug("Created Serial Port Object"); + + /* connect reading event */ + connect(serial, SIGNAL(readyRead()), this, SLOT(readSerialData())); + connect(serial, SIGNAL(error(QSerialPort::SerialPortError)), this, SLOT(serialError(QSerialPort::SerialPortError))); + + /* configure */ + serial->setBaudRate(mSerialSpeed); + serial->setDataBits(serial->Data8); + + serial->setFlowControl(serial->HardwareControl); + //serial->setFlowControl(serial->NoFlowControl); + if (!serial->open(QIODevice::ReadWrite)) + { + //sendDebug("Error returned during port opening: " + serial->errorString()); + } + else + { + //serial->setDataTerminalReady(true); //Seemingly these two lines used to be needed + //serial->setRequestToSend(true); //But, really both ends should automatically handle these + deviceConnected(); + } +} + +void LAWICELSerial::deviceConnected() +{ + sendDebug("Connecting to LAWICEL Device!"); + + QByteArray output; + + output.clear(); + output.append('C'); //close the bus in case it was already up + output.append(13); + sendToSerial(output); + + output.clear(); + output.append('S'); //configure speed of bus + switch (this->mBusData[0].mBus.getSpeed()) + { + case 10000: + output.append('0'); + break; + case 20000: + output.append('1'); + break; + case 50000: + output.append('2'); + break; + case 100000: + output.append('3'); + break; + case 125000: + output.append('4'); + break; + case 250000: + output.append('5'); + break; + case 500000: + output.append('6'); + break; + case 800000: + output.append('7'); + break; + case 1000000: + output.append('8'); + break; + default: + output.append('6'); + break; + } + + output.append('\x0D'); + + sendToSerial(output); + + output.clear(); + + if (this->canFd){ + switch (this->dataRate) + { + case 1000000: + output.append("Y1"); + break; + case 2000000: + output.append("Y2"); + break; + case 4000000: + output.append("Y4"); + break; + case 5000000: + output.append("Y5"); + break; + default: + output.append("Y2"); + break; + } + + output.append('\x0D'); + sendToSerial(output); + output.clear(); + } + + output.append('O'); //open bus now that we set the speed + output.append(13); + + sendToSerial(output); + + mNumBuses = 1; + setStatus(CANCon::CONNECTED); + CANConStatus stats; + stats.conStatus = getStatus(); + stats.numHardwareBuses = mNumBuses; + mBusData[0].mConfigured = true; + mBusData[0].mBus.setActive(true); + //mBusData[0].mBus.setSpeed(); + emit status(stats); +} + +void LAWICELSerial::disconnectDevice() { + if (serial != nullptr) + { + if (serial->isOpen()) + { + //serial->clear(); + serial->close(); + + } + serial->disconnect(); //disconnect all signals + delete serial; + serial = nullptr; + } + + setStatus(CANCon::NOT_CONNECTED); + CANConStatus stats; + stats.conStatus = getStatus(); + stats.numHardwareBuses = mNumBuses; + emit status(stats); +} + +void LAWICELSerial::serialError(QSerialPort::SerialPortError err) +{ + QString errMessage; + bool killConnection = false; + switch (err) + { + case QSerialPort::NoError: + return; + case QSerialPort::DeviceNotFoundError: + errMessage = "Device not found error on serial"; + killConnection = true; + piStop(); + break; + case QSerialPort::PermissionError: + errMessage = "Permission error on serial port"; + killConnection = true; + piStop(); + break; + case QSerialPort::OpenError: + errMessage = "Open error on serial port"; + killConnection = true; + piStop(); + break; + case QSerialPort::ParityError: + errMessage = "Parity error on serial port"; + break; + case QSerialPort::FramingError: + errMessage = "Framing error on serial port"; + break; + case QSerialPort::BreakConditionError: + errMessage = "Break error on serial port"; + break; + case QSerialPort::WriteError: + errMessage = "Write error on serial port"; + piStop(); + break; + case QSerialPort::ReadError: + errMessage = "Read error on serial port"; + piStop(); + break; + case QSerialPort::ResourceError: + errMessage = "Serial port seems to have disappeared."; + killConnection = true; + piStop(); + break; + case QSerialPort::UnsupportedOperationError: + errMessage = "Unsupported operation on serial port"; + killConnection = true; + break; + case QSerialPort::UnknownError: + errMessage = "Beats me what happened to the serial port."; + killConnection = true; + piStop(); + break; + case QSerialPort::TimeoutError: + errMessage = "Timeout error on serial port"; + killConnection = true; + break; + case QSerialPort::NotOpenError: + errMessage = "The serial port isn't open"; + killConnection = true; + piStop(); + break; + } + /* + if (serial) + { + serial->clearError(); + serial->flush(); + serial->close(); + }*/ + if (errMessage.length() > 1) + { + sendDebug(errMessage); + } + if (killConnection) + { + qDebug() << "Shooting the serial object in the head. It deserves it."; + disconnectDevice(); + } +} + + +void LAWICELSerial::connectionTimeout() +{ + //one second after trying to connect are we actually connected? + if (CANCon::NOT_CONNECTED==getStatus()) //no? + { + //then emit the the failure signal and see if anyone cares + sendDebug("Failed to connect to LAWICEL at that com port"); + + disconnectDevice(); + connectDevice(); + } + else + { + /* start timer */ + connect(&mTimer, SIGNAL(timeout()), this, SLOT(handleTick())); + mTimer.setInterval(250); //tick four times per second + mTimer.setSingleShot(false); //keep ticking + mTimer.start(); + } +} + +void LAWICELSerial::readSerialData() +{ + QByteArray data; + unsigned char c; + QString debugBuild; + CANFrame buildFrame; + QByteArray buildData; + + if (serial) data = serial->readAll(); + + sendDebug("Got data from serial. Len = " % QString::number(data.length())); + for (int i = 0; i < data.length(); i++) + { + c = data.at(i); + //qDebug() << c << " " << QString::number(c, 16) << " " << QString(c); + debugBuild = debugBuild % QString::number(c, 16).rightJustified(2,'0') % " "; + //procRXChar(c); + mBuildLine.append(c); + if (c == 13) //all lawicel commands end in CR + { + qDebug() << "Got CR!"; + + buildFrame.setTimeStamp(QDateTime::currentMSecsSinceEpoch() * 1000l); + switch (mBuildLine[0].toLatin1()) + { + case 't': //standard frame + //tIIILDD + buildFrame.setFrameId(mBuildLine.mid(1, 3).toInt(nullptr, 16)); + buildFrame.isReceived = true; + buildFrame.setFrameType(QCanBusFrame::FrameType::DataFrame); + buildData.resize(mBuildLine.mid(4, 1).toInt()); + for (int c = 0; c < buildData.size(); c++) + { + buildData[c] = mBuildLine.mid(5 + (c*2), 2).toInt(nullptr, 16); + } + buildFrame.setPayload(buildData); + if (!isCapSuspended()) + { + /* get frame from queue */ + CANFrame* frame_p = getQueue().get(); + if(frame_p) { + //qDebug() << "Lawicel got frame on bus " << frame_p->bus; + /* copy frame */ + *frame_p = buildFrame; + checkTargettedFrame(buildFrame); + /* enqueue frame */ + getQueue().queue(); + } + else + qDebug() << "can't get a frame, ERROR"; + } + break; + case 'T': //extended frame + //TIIIIIIIILDD. + buildFrame.setFrameId(mBuildLine.mid(1, 8).toInt(nullptr, 16)); + buildFrame.isReceived = true; + buildFrame.setFrameType(QCanBusFrame::FrameType::DataFrame); + buildFrame.setExtendedFrameFormat(true); + buildData.resize(mBuildLine.mid(9, 1).toInt()); + for (int c = 0; c < buildData.size(); c++) + { + buildData[c] = mBuildLine.mid(10 + (c*2), 2).toInt(nullptr, 16); + } + buildFrame.setPayload(buildData); + if (!isCapSuspended()) + { + /* get frame from queue */ + CANFrame* frame_p = getQueue().get(); + if(frame_p) { + //qDebug() << "Lawicel got frame on bus " << frame_p->bus; + /* copy frame */ + *frame_p = buildFrame; + checkTargettedFrame(buildFrame); + /* enqueue frame */ + getQueue().queue(); + } + else + qDebug() << "can't get a frame, ERROR"; + } + break; + case 'b': + buildFrame.setBitrateSwitch(true); //BRS enabled + [[fallthrough]]; + case 'd': //standard fd frame, BRS disabled + //tIIILDD + buildFrame.setFlexibleDataRateFormat(true); + buildFrame.setFrameId(mBuildLine.mid(1, 3).toInt(nullptr, 16)); + buildFrame.isReceived = true; + buildFrame.setFrameType(QCanBusFrame::FrameType::DataFrame); + buildData.resize(LAWICELSerial::dlc_code_to_bytes(mBuildLine.mid(4, 1).toInt(nullptr, 16))); + for (int c = 0; c < buildData.size(); c++) + { + buildData[c] = mBuildLine.mid(5 + (c*2), 2).toInt(nullptr, 16); + } + buildFrame.setPayload(buildData); + if (!isCapSuspended()) + { + /* get frame from queue */ + CANFrame* frame_p = getQueue().get(); + if(frame_p) { + //qDebug() << "Lawicel got frame on bus " << frame_p->bus; + /* copy frame */ + *frame_p = buildFrame; + checkTargettedFrame(buildFrame); + /* enqueue frame */ + getQueue().queue(); + } + else + qDebug() << "can't get a frame, ERROR"; + } + break; + case 'B': + buildFrame.setBitrateSwitch(true); //BRS enabled + [[fallthrough]]; + case 'D': //extended fd frame + //TIIIIIIIILDD. + buildFrame.setFlexibleDataRateFormat(true); + buildFrame.setBitrateSwitch(true); + buildFrame.setFrameId(mBuildLine.mid(1, 8).toInt(nullptr, 16)); + buildFrame.isReceived = true; + buildFrame.setFrameType(QCanBusFrame::FrameType::DataFrame); + buildFrame.setExtendedFrameFormat(true); + buildData.resize(LAWICELSerial::dlc_code_to_bytes(mBuildLine.mid(4, 1).toInt(nullptr, 16))); + for (int c = 0; c < buildData.size(); c++) + { + buildData[c] = mBuildLine.mid(10 + (c*2), 2).toInt(nullptr, 16); + } + buildFrame.setPayload(buildData); + if (!isCapSuspended()) + { + /* get frame from queue */ + CANFrame* frame_p = getQueue().get(); + if(frame_p) { + //qDebug() << "Lawicel got frame on bus " << frame_p->bus; + /* copy frame */ + *frame_p = buildFrame; + checkTargettedFrame(buildFrame); + /* enqueue frame */ + getQueue().queue(); + } + else + qDebug() << "can't get a frame, ERROR"; + } + break; + } + mBuildLine.clear(); + } + } + debugOutput(debugBuild); + //qDebug() << debugBuild; +} + +//Debugging data sent from connection window. Inject it into Comm traffic. +void LAWICELSerial::debugInput(QByteArray bytes) { + sendToSerial(bytes); +} + +void LAWICELSerial::handleTick() +{ + //qDebug() << "Tick!"; +} + + +// Convert a FDCAN_data_length_code to number of bytes in a message +uint8_t LAWICELSerial::dlc_code_to_bytes(int dlc_code) +{ + if (dlc_code<=8) + return dlc_code; + else{ + switch(dlc_code) + { + case 9: + return 12; + case 10: + return 16; + case 11: + return 20; + case 12: + return 24; + case 13: + return 32; + case 14: + return 48; + case 15: + return 64; + default: + return 0; + } + } +} + +uint8_t LAWICELSerial::bytes_to_dlc_code(uint8_t bytes) +{ + if (bytes<=8) + return bytes; + else{ + switch(bytes) + { + case 12: + return 9; + case 16: + return 10; + case 20: + return 11; + case 24: + return 12; + case 32: + return 13; + case 48: + return 14; + case 64: + return 15; + default: + return 0; + } + } +} + + diff --git a/connections/lawicel_serial.h b/connections/lawicel_serial.h new file mode 100644 index 0000000..3d32a88 --- /dev/null +++ b/connections/lawicel_serial.h @@ -0,0 +1,70 @@ +#ifndef LAWICELSERIAL_H +#define LAWICELSERIAL_H + +#include +#include +#include +#include + +/*************/ +#include +/*************/ + +#include "canframemodel.h" +#include "canconnection.h" +#include "canconmanager.h" + +class LAWICELSerial : public CANConnection +{ + Q_OBJECT + +public: + LAWICELSerial(QString portName, int serialSpeed, int lawicelSpeed, bool canFd, int dataRate); + virtual ~LAWICELSerial(); + +protected: + + virtual void piStarted(); + virtual void piStop(); + virtual void piSetBusSettings(int pBusIdx, CANBus pBus); + virtual bool piGetBusSettings(int pBusIdx, CANBus& pBus); + virtual void piSuspend(bool pSuspend); + virtual bool piSendFrame(const CANFrame&) ; + + void disconnectDevice(); + +public slots: + void debugInput(QByteArray bytes); + +private slots: + void connectDevice(); + void connectionTimeout(); + void readSerialData(); + void serialError(QSerialPort::SerialPortError err); + void deviceConnected(); + void handleTick(); + +private: + void readSettings(); + void rebuildLocalTimeBasis(); + void sendToSerial(const QByteArray &bytes); + void sendDebug(const QString debugText); + uint8_t dlc_code_to_bytes(int dlc_code); + uint8_t bytes_to_dlc_code(uint8_t bytes); + +protected: + QTimer mTimer; + QThread mThread; + QString mBuildLine; + + bool isAutoRestart; + QSerialPort *serial; + int framesRapid; + CANFrame buildFrame; + bool can0Enabled; + bool can0ListenOnly; + bool canFd; + int dataRate; +}; + +#endif // LAWICELSERIAL_H diff --git a/connections/mqtt_bus.cpp b/connections/mqtt_bus.cpp index 6b05be5..4220e34 100644 --- a/connections/mqtt_bus.cpp +++ b/connections/mqtt_bus.cpp @@ -9,7 +9,7 @@ #include "mqtt_bus.h" MQTT_BUS::MQTT_BUS(QString topicName) : - CANConnection(topicName, "mqtt_client", CANCon::MQTT, 1, 4000, true), + CANConnection(topicName, "mqtt_client", CANCon::MQTT, 0, 0, false, 0, 1, 4000, true), mTimer(this) /*NB: set this as parent of timer to manage it from working thread */ { @@ -228,8 +228,8 @@ void MQTT_BUS::piSetBusSettings(int pBusIdx, CANBus bus) bool MQTT_BUS::piSendFrame(const CANFrame& frame) { QByteArray buffer; - int c; - quint32 ID; + //int c; + //quint32 ID; //qDebug() << "Sending out GVRET frame with id " << frame.ID << " on bus " << frame.bus; @@ -278,7 +278,7 @@ void MQTT_BUS::readSettings() void MQTT_BUS::clientMessageReceived(const QMQTT::Message& message) { - uint64_t timeBasis = CANConManager::getInstance()->getTimeBasis(); + //uint64_t timeBasis = CANConManager::getInstance()->getTimeBasis(); /* drop frame if capture is suspended */ @@ -289,9 +289,10 @@ void MQTT_BUS::clientMessageReceived(const QMQTT::Message& message) if(frame_p) { uint32_t frameID = message.topic().split("/")[1].toInt(); - uint64_t timeStamp = message.payload()[0] + (message.payload()[1] << 8) + (message.payload()[2] << 16) + (message.payload()[3] << 24) - + ((uint64_t)message.payload()[4] << 32ull) + ((uint64_t)message.payload()[5] << 40ull) - + ((uint64_t)message.payload()[6] << 48ull) + ((uint64_t)message.payload()[7] << 56ull); + + QByteArray timeStampBytes = message.payload().left(8); + uint64_t timeStamp = qFromLittleEndian(timeStampBytes.data()); + int flags = message.payload()[8]; frame_p->setPayload(message.payload().right(message.payload().count() - 9)); frame_p->bus = 0; diff --git a/connections/newconnectiondialog.cpp b/connections/newconnectiondialog.cpp index db4a721..85163e2 100644 --- a/connections/newconnectiondialog.cpp +++ b/connections/newconnectiondialog.cpp @@ -27,12 +27,23 @@ NewConnectionDialog::NewConnectionDialog(QVector* gvretips, QVectorrbRemote, &QAbstractButton::clicked, this, &NewConnectionDialog::handleConnTypeChanged); connect(ui->rbKayak, &QAbstractButton::clicked, this, &NewConnectionDialog::handleConnTypeChanged); connect(ui->rbMQTT, &QAbstractButton::clicked, this, &NewConnectionDialog::handleConnTypeChanged); + connect(ui->rbLawicel, &QAbstractButton::clicked, this, &NewConnectionDialog::handleConnTypeChanged); + connect(ui->rbCANserver, &QAbstractButton::clicked, this, &NewConnectionDialog::handleConnTypeChanged); + connect(ui->rbCanlogserver, &QAbstractButton::clicked, this, &NewConnectionDialog::handleConnTypeChanged); connect(ui->cbDeviceType, QOverload::of(&QComboBox::currentIndexChanged), this, &NewConnectionDialog::handleDeviceTypeChanged); connect(ui->btnOK, &QPushButton::clicked, this, &NewConnectionDialog::handleCreateButton); ui->lblDeviceType->setHidden(true); ui->cbDeviceType->setHidden(true); + ui->cbCANSpeed->setHidden(true); + ui->cbSerialSpeed->setHidden(true); + ui->lblCANSpeed->setHidden(true); + ui->lblSerialSpeed->setHidden(true); + ui->cbCanFd->setHidden(true); + ui->cbDataRate->setHidden(true); + ui->lblDataRate->setHidden(true); + selectSerial(); qDebug() << "Was passed " << remoteDeviceIPGVRET->count() << " remote GVRET IPs"; @@ -53,9 +64,12 @@ void NewConnectionDialog::handleConnTypeChanged() { if (ui->rbGVRET->isChecked()) selectSerial(); if (ui->rbSocketCAN->isChecked()) selectSocketCan(); + if (ui->rbLawicel->isChecked()) selectLawicel(); if (ui->rbRemote->isChecked()) selectRemote(); if (ui->rbKayak->isChecked()) selectKayak(); if (ui->rbMQTT->isChecked()) selectMQTT(); + if (ui->rbCANserver->isChecked()) selectCANserver(); + if (ui->rbCanlogserver->isChecked()) selectCANlogserver(); } void NewConnectionDialog::handleDeviceTypeChanged() @@ -68,12 +82,72 @@ void NewConnectionDialog::handleDeviceTypeChanged() ui->cbPort->addItem(canDevices[i].name()); } +void NewConnectionDialog::selectLawicel() +{ + ui->lPort->setText("Serial Port:"); + + ui->lblDeviceType->setHidden(true); + ui->cbDeviceType->setHidden(true); + + ui->cbCANSpeed->setHidden(false); + ui->cbSerialSpeed->setHidden(false); + ui->lblCANSpeed->setHidden(false); + ui->lblSerialSpeed->setHidden(false); + ui->cbCanFd->setHidden(false); + ui->cbDataRate->setHidden(false); + ui->lblDataRate->setHidden(false); + + ui->cbPort->clear(); + ports = QSerialPortInfo::availablePorts(); + + for (int i = 0; i < ports.count(); i++) + ui->cbPort->addItem(ports[i].portName()); + + if (ui->cbCANSpeed->count() == 0) + { + ui->cbCANSpeed->addItem("10000"); + ui->cbCANSpeed->addItem("20000"); + ui->cbCANSpeed->addItem("50000"); + ui->cbCANSpeed->addItem("83333"); + ui->cbCANSpeed->addItem("100000"); + ui->cbCANSpeed->addItem("125000"); + ui->cbCANSpeed->addItem("250000"); + ui->cbCANSpeed->addItem("500000"); + ui->cbCANSpeed->addItem("1000000"); + } + if (ui->cbDataRate->count() == 0) + { + ui->cbDataRate->addItem("1000000"); + ui->cbDataRate->addItem("2000000"); + ui->cbDataRate->addItem("4000000"); + ui->cbDataRate->addItem("5000000"); + } + if (ui->cbSerialSpeed->count() == 0) + { + ui->cbSerialSpeed->addItem("115200"); + ui->cbSerialSpeed->addItem("150000"); + ui->cbSerialSpeed->addItem("250000"); + ui->cbSerialSpeed->addItem("500000"); + ui->cbSerialSpeed->addItem("1000000"); + ui->cbSerialSpeed->addItem("2000000"); + ui->cbSerialSpeed->addItem("3000000"); + } + +} + void NewConnectionDialog::selectSerial() { ui->lPort->setText("Serial Port:"); ui->lblDeviceType->setHidden(true); ui->cbDeviceType->setHidden(true); + ui->cbCANSpeed->setHidden(true); + ui->cbSerialSpeed->setHidden(true); + ui->lblCANSpeed->setHidden(true); + ui->lblSerialSpeed->setHidden(true); + ui->cbCanFd->setHidden(true); + ui->cbDataRate->setHidden(true); + ui->lblDataRate->setHidden(true); ui->cbPort->clear(); ports = QSerialPortInfo::availablePorts(); @@ -87,6 +161,13 @@ void NewConnectionDialog::selectSocketCan() ui->lPort->setText("Port:"); ui->lblDeviceType->setHidden(false); ui->cbDeviceType->setHidden(false); + ui->cbCANSpeed->setHidden(true); + ui->cbSerialSpeed->setHidden(true); + ui->lblCANSpeed->setHidden(true); + ui->lblSerialSpeed->setHidden(true); + ui->cbCanFd->setHidden(true); + ui->cbDataRate->setHidden(true); + ui->lblDataRate->setHidden(true); ui->cbDeviceType->clear(); QStringList plugins; @@ -99,8 +180,17 @@ void NewConnectionDialog::selectSocketCan() void NewConnectionDialog::selectRemote() { ui->lPort->setText("IP Address:"); + ui->lblDeviceType->setHidden(true); ui->cbDeviceType->setHidden(true); + ui->cbCANSpeed->setHidden(true); + ui->cbSerialSpeed->setHidden(true); + ui->lblCANSpeed->setHidden(true); + ui->lblSerialSpeed->setHidden(true); + ui->cbCanFd->setHidden(true); + ui->cbDataRate->setHidden(true); + ui->lblDataRate->setHidden(true); + ui->cbPort->clear(); foreach(QString pName, *remoteDeviceIPGVRET) { @@ -111,8 +201,17 @@ void NewConnectionDialog::selectRemote() void NewConnectionDialog::selectKayak() { ui->lPort->setText("Available Bus(ses):"); + ui->lblDeviceType->setHidden(true); ui->cbDeviceType->setHidden(true); + ui->cbCANSpeed->setHidden(true); + ui->cbSerialSpeed->setHidden(true); + ui->lblCANSpeed->setHidden(true); + ui->lblSerialSpeed->setHidden(true); + ui->cbCanFd->setHidden(true); + ui->cbDataRate->setHidden(true); + ui->lblDataRate->setHidden(true); + ui->cbPort->clear(); foreach(QString pName, *remoteBusKayak) { @@ -123,8 +222,51 @@ void NewConnectionDialog::selectKayak() void NewConnectionDialog::selectMQTT() { ui->lPort->setText("Topic Name:"); + ui->lblDeviceType->setHidden(true); ui->cbDeviceType->setHidden(true); + ui->cbCANSpeed->setHidden(true); + ui->cbSerialSpeed->setHidden(true); + ui->lblCANSpeed->setHidden(true); + ui->lblSerialSpeed->setHidden(true); + ui->cbCanFd->setHidden(true); + ui->cbDataRate->setHidden(true); + ui->lblDataRate->setHidden(true); + + ui->cbPort->clear(); +} + +void NewConnectionDialog::selectCANserver() +{ + ui->lPort->setText("CANserver IP Address:"); + + ui->lblDeviceType->setHidden(true); + ui->cbDeviceType->setHidden(true); + ui->cbCANSpeed->setHidden(true); + ui->cbSerialSpeed->setHidden(true); + ui->lblCANSpeed->setHidden(true); + ui->lblSerialSpeed->setHidden(true); + ui->cbCanFd->setHidden(true); + ui->cbDataRate->setHidden(true); + ui->lblDataRate->setHidden(true); + + ui->cbPort->clear(); +} + +void NewConnectionDialog::selectCANlogserver() +{ + ui->lPort->setText("CANlogserver IP Address:"); + + ui->lblDeviceType->setHidden(true); + ui->cbDeviceType->setHidden(true); + ui->cbCANSpeed->setHidden(true); + ui->cbSerialSpeed->setHidden(true); + ui->lblCANSpeed->setHidden(true); + ui->lblSerialSpeed->setHidden(true); + ui->cbCanFd->setHidden(true); + ui->cbDataRate->setHidden(true); + ui->lblDataRate->setHidden(true); + ui->cbPort->clear(); } @@ -148,6 +290,15 @@ void NewConnectionDialog::setPortName(CANCon::type pType, QString pPortName, QSt case CANCon::MQTT: ui->rbMQTT->setChecked(true); break; + case CANCon::LAWICEL: + ui->rbLawicel->setChecked(true); + break; + case CANCon::CANSERVER: + ui->rbCANserver->setChecked(true); + break; + case CANCon::CANLOGSERVER: + ui->rbCanlogserver->setChecked(true); + break; default: {} } @@ -157,6 +308,7 @@ void NewConnectionDialog::setPortName(CANCon::type pType, QString pPortName, QSt switch(pType) { case CANCon::GVRET_SERIAL: + case CANCon::LAWICEL: { int idx = ui->cbPort->findText(pPortName); if( idx<0 ) idx=0; @@ -190,6 +342,12 @@ void NewConnectionDialog::setPortName(CANCon::type pType, QString pPortName, QSt case CANCon::MQTT: ui->cbPort->setCurrentText(pPortName); break; + case CANCon::CANSERVER: + case CANCon::CANLOGSERVER: + { + ui->cbPort->setCurrentText(pPortName); + break; + } default: {} } } @@ -201,9 +359,14 @@ QString NewConnectionDialog::getPortName() case CANCon::SERIALBUS: case CANCon::REMOTE: case CANCon::MQTT: + case CANCon::LAWICEL: return ui->cbPort->currentText(); case CANCon::KAYAK: return ui->cbPort->currentText(); + case CANCon::CANSERVER: + case CANCon::CANLOGSERVER: + return ui->cbPort->currentText(); + default: qDebug() << "getPortName: can't get port"; } @@ -220,6 +383,24 @@ QString NewConnectionDialog::getDriverName() return "N/A"; } +int NewConnectionDialog::getSerialSpeed() +{ + if (getConnectionType() == CANCon::LAWICEL) + { + return ui->cbSerialSpeed->currentText().toInt(); + } + else return 0; +} + +int NewConnectionDialog::getBusSpeed() +{ + if (getConnectionType() == CANCon::LAWICEL) + { + return ui->cbCANSpeed->currentText().toInt(); + } + else return 0; +} + CANCon::type NewConnectionDialog::getConnectionType() { if (ui->rbGVRET->isChecked()) return CANCon::GVRET_SERIAL; @@ -227,6 +408,9 @@ CANCon::type NewConnectionDialog::getConnectionType() if (ui->rbRemote->isChecked()) return CANCon::REMOTE; if (ui->rbKayak->isChecked()) return CANCon::KAYAK; if (ui->rbMQTT->isChecked()) return CANCon::MQTT; + if (ui->rbLawicel->isChecked()) return CANCon::LAWICEL; + if (ui->rbCANserver->isChecked()) return CANCon::CANSERVER; + if (ui->rbCanlogserver->isChecked()) return CANCon::CANLOGSERVER; qDebug() << "getConnectionType: error"; return CANCon::NONE; @@ -237,3 +421,21 @@ bool NewConnectionDialog::isSerialBusAvailable() if (QCanBus::instance()->plugins().count() > 0) return true; return false; } + +int NewConnectionDialog::getDataRate() +{ + if (getConnectionType() == CANCon::LAWICEL) + { + return ui->cbDataRate->currentText().toInt(); + } + else return 0; +} + +bool NewConnectionDialog::isCanFd() + { + if (getConnectionType() == CANCon::LAWICEL) + { + return ui->cbCanFd; + } + else return 0; + } diff --git a/connections/newconnectiondialog.h b/connections/newconnectiondialog.h index 1410b42..387a4fb 100644 --- a/connections/newconnectiondialog.h +++ b/connections/newconnectiondialog.h @@ -24,6 +24,10 @@ public: CANCon::type getConnectionType(); QString getPortName(); QString getDriverName(); + int getSerialSpeed(); + int getBusSpeed(); + bool isCanFd(); + int getDataRate(); public slots: void handleConnTypeChanged(); @@ -43,6 +47,9 @@ private: void selectRemote(); void selectKayak(); void selectMQTT(); + void selectLawicel(); + void selectCANserver(); + void selectCANlogserver(); bool isSerialBusAvailable(); void setPortName(CANCon::type pType, QString pPortName, QString pDriver); }; diff --git a/connections/serialbusconnection.cpp b/connections/serialbusconnection.cpp index 6ea71c3..8ebebbf 100644 --- a/connections/serialbusconnection.cpp +++ b/connections/serialbusconnection.cpp @@ -12,7 +12,7 @@ /***********************************/ SerialBusConnection::SerialBusConnection(QString portName, QString driverName) : - CANConnection(portName, driverName, CANCon::SERIALBUS, 1, 4000, true), + CANConnection(portName, driverName, CANCon::SERIALBUS,0 ,0, false, 0 ,1, 4000, true), mTimer(this) /*NB: set connection as parent of timer to manage it from working thread */ { } @@ -47,6 +47,7 @@ void SerialBusConnection::piStarted() mTimer.setSingleShot(false); //keep ticking mTimer.start(); mBusData[0].mBus.setActive(true); + mBusData[0].mBus.setCanFD(false); mBusData[0].mConfigured = true; } @@ -77,6 +78,8 @@ bool SerialBusConnection::piGetBusSettings(int pBusIdx, CANBus& pBus) void SerialBusConnection::piSetBusSettings(int pBusIdx, CANBus bus) { + quint32 sbusconfig = 0; + //CANConStatus stats; /* sanity checks */ if(0 != pBusIdx) @@ -91,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 */ @@ -102,7 +105,12 @@ 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::BitRateKey, bus.getSpeed()); + mDev_p->setConfigurationParameter(QCanBusDevice::CanFdKey, bus.isCanFD()); + + if(bus.isListenOnly()) + sbusconfig |= EN_SILENT_MODE; + mDev_p->setConfigurationParameter(QCanBusDevice::UserKey, sbusconfig); /* connect device */ if (!mDev_p->connectDevice()) { @@ -180,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()); @@ -198,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) { @@ -210,11 +219,9 @@ void SerialBusConnection::framesReceived() /* enqueue frame */ getQueue().queue(); - //} -#if 0 + } else qDebug() << "can't get a frame, ERROR"; -#endif } } } diff --git a/connections/serialbusconnection.h b/connections/serialbusconnection.h index e430ebd..f65be43 100644 --- a/connections/serialbusconnection.h +++ b/connections/serialbusconnection.h @@ -7,6 +7,21 @@ #include #include +/* +QCanBusDevice::UserKey +0x00000001 - enable silent mode +0x00000002 - enable loopback mode +0x00000004 - disable auto retransmissions +0x00000008 - enable terminator +0x00000010 - enable automatic bus off recovery +*/ + +#define EN_SILENT_MODE 0x00000001 +#define EN_LOOPBACK_MODE 0x00000002 +#define DIS_AUTO_RETRANSMISSIONS 0x00000004 +#define EN_TERMINATOR 0x00000008 +#define EN_AUTOMATIC_BUSOFF_RECOVERY 0x00000010 + class SerialBusConnection : public CANConnection { Q_OBJECT diff --git a/connections/socketcand.cpp b/connections/socketcand.cpp index 9bbddbb..6ef8975 100644 --- a/connections/socketcand.cpp +++ b/connections/socketcand.cpp @@ -10,7 +10,7 @@ #include "socketcand.h" SocketCANd::SocketCANd(QString portName) : - CANConnection(portName, "kayak", CANCon::KAYAK, 1, 4000, true), + CANConnection(portName, "kayak", CANCon::KAYAK, 0, 0, false, 0, 1, 4000, true), mTimer(this) /*NB: set this as parent of timer to manage it from working thread */ { @@ -32,6 +32,7 @@ SocketCANd::SocketCANd(QString portName) : for (int i = 0; i < mNumBuses; i++) { rx_state.append(IDLE); + unprocessedData.append(""); } } @@ -63,7 +64,7 @@ void SocketCANd::sendBytesToTCP(const QByteArray &bytes, int busNum) byt = (unsigned char)byt; buildDebug = buildDebug % QString::number(byt, 16) % " "; } - sendDebug(buildDebug); + //sendDebug(buildDebug); if (tcpClient[busNum]) tcpClient[busNum]->write(bytes); } @@ -76,9 +77,9 @@ void SocketCANd::sendStringToTCP(const char* data, int busNum) return; } - QString buildDebug; - buildDebug = "Send data to " + hostIP.toString() + ":" + QString::number(hostPort) + " -> " + data; - sendDebug(buildDebug); + //QString buildDebug; + //buildDebug = "Send data to " + hostIP.toString() + ":" + QString::number(hostPort) + " -> " + data; + //sendDebug(buildDebug); //qInfo() << buildDebug; if (tcpClient[busNum]) tcpClient[busNum]->write(data); @@ -218,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(); } @@ -249,62 +250,89 @@ void SocketCANd::switchToRawMode(int busNum) QCoreApplication::processEvents(); } -void SocketCANd::decodeFrames(QString data, int busNum) +QString SocketCANd::decodeFrames(QString data, int busNum) { - if (data.indexOf("< frame ") == -1) + if (data.indexOf("<") == -1) + return ""; + else if(data.length() >= 8 && data.indexOf("< frame ") == -1) + return ""; + + int firstIndex = data.indexOf("< frame "); + if(firstIndex > 0) { - qDebug() << "Received datagramm doesn't contain any frame: " << data; - return; + QString framePartial = data.left(firstIndex); + qDebug() << "Received datagramm that starts with fragment (missing '< frame'), this should only occur on startup, removing...: " << framePartial; } - else + QString framePart = data.mid(firstIndex); //remove starting beginning of payload if not < frame > + const QString frameStrConst = framePart.left(framePart.indexOf(">")+1); + QString frameStr = frameStrConst; + QStringList frameParsed = (frameStr.remove(QRegularExpression("^<")).remove(QRegularExpression(">$"))).simplified().split(' '); + + if(frameParsed.length() < 3) { - QString framePart = data.mid(data.indexOf("< frame "), data.length()); //remove starting beginning of payload if not < frame > - const QString frameStrConst = framePart.left(framePart.indexOf(">")+1); - QString frameStr = frameStrConst; - QStringList frameParsed = (frameStr.remove(QRegExp("^<")).remove(QRegExp(">$"))).simplified().split(' '); + //qDebug() << "Received datagramm is an incomplete frame: " << data; - buildFrame.setFrameId(frameParsed[1].toUInt(nullptr, 16)); - buildFrame.bus = busNum; + //ok great, need to leave it in the buffer in case it can be combined with what comes next + //but if there was a fragment that did not have a starting token then we don't want it so only return + //known good data...again this should only happen on startup, but just in case we need to remove it + //so the data buffer doesn't grow uncontrolled. + return framePart; + } - if (buildFrame.frameId() > 0x7FF) buildFrame.setExtendedFrameFormat(true); - else buildFrame.setExtendedFrameFormat(false); + buildFrame.setFrameId(frameParsed[1].toUInt(nullptr, 16)); + buildFrame.bus = busNum; - buildFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, frameParsed[2].toDouble() * 1000000l)); - //buildFrame.len = frameParsed[3].length() * 0.5; - int framelength = frameParsed[3].length() * 0.5; + if (buildFrame.frameId() > 0x7FF) buildFrame.setExtendedFrameFormat(true); + else buildFrame.setExtendedFrameFormat(false); - buildData.resize(framelength); + buildFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, frameParsed[2].toDouble() * 1000000l)); + //buildFrame.len = frameParsed[3].length() * 0.5; - int c; - for (c = 0; c < framelength; c++) - { - bool ok; - unsigned char byteVal = frameParsed[3].mid(c*2, 2).toUInt(&ok, 16); - buildData[c] = byteVal; - } - buildFrame.setPayload(buildData); + int framelength = 0; + + if(frameParsed.length() == 4) + { + framelength = frameParsed[3].length() * 0.5; + } + + QByteArray buildData; + buildData.resize(framelength); + + int c; + for (c = 0; c < framelength; c++) + { + bool ok; + unsigned char byteVal = frameParsed[3].mid(c*2, 2).toUInt(&ok, 16); + buildData[c] = byteVal; + } + buildFrame.setPayload(buildData); // buildFrame.isReceived = true; - if (!isCapSuspended()) - { - /* get frame from queue */ - CANFrame* frame_p = getQueue().get(); - if(frame_p) { - /* copy frame */ - *frame_p = buildFrame; - //frame_p->remote = false; - frame_p->setFrameType(QCanBusFrame::DataFrame); - checkTargettedFrame(buildFrame); - /* enqueue frame */ - getQueue().queue(); - } + if (!isCapSuspended()) + { + /* get frame from queue */ + CANFrame* frame_p = getQueue().get(); + if(frame_p) { + /* copy frame */ + *frame_p = buildFrame; + //frame_p->remote = false; + frame_p->setFrameType(QCanBusFrame::DataFrame); + checkTargettedFrame(buildFrame); + /* enqueue frame */ + getQueue().queue(); } - else - qDebug() << "can't get a frame, capture suspended"; - - if (framePart.length() > frameStrConst.length()) - decodeFrames(framePart.right(framePart.length() - frameStrConst.length()), busNum); } + //else + // qDebug() << "can't get a frame, capture suspended"; + + //take out the data that we just processed and anything that is in front of it + //this should keep broken frames from accumulating at in the data buffer + if (framePart.length() > frameStrConst.length()) + { + return decodeFrames(framePart.right(framePart.length() - frameStrConst.length()), busNum); + } + + return ""; } void SocketCANd::disconnectDevice() { @@ -339,15 +367,15 @@ void SocketCANd::invokeReadTCPData() void SocketCANd::readTCPData(int busNum) { - QByteArray data; + QString data; - if (tcpClient[busNum]) data = tcpClient[busNum]->readAll(); + if (QTcpSocket* socket = tcpClient.value(busNum)) + data = QString(socket->readAll()); //sendDebug("Got data from TCP. Len = " % QString::number(data.length())); //qDebug() << "Received datagramm: " << data; procRXData(data, busNum); } - void SocketCANd::procRXData(QString data, int busNum) { if (data != "") @@ -355,8 +383,6 @@ void SocketCANd::procRXData(QString data, int busNum) mTimer.stop(); mTimer.start(); } - QByteArray output; - switch (rx_state.at(busNum)) { case IDLE: @@ -374,6 +400,7 @@ void SocketCANd::procRXData(QString data, int busNum) { switchToRawMode(busNum); rx_state[busNum] = SWITCHING2RAW; + unprocessedData[busNum].clear(); } else qInfo() << hostCanIDs[busNum] << ": Could not open bus. Host did not respond with ""< ok >"": " << data; break; @@ -383,9 +410,30 @@ void SocketCANd::procRXData(QString data, int busNum) { rx_state[busNum] = RAWMODE; } + else if(data.indexOf("< ok >", 0, Qt::CaseSensitivity::CaseInsensitive) == 0) + { + qDebug() << "Ok found at start of compound message, switching to RAW and decoding immediately"; + rx_state[busNum] = RAWMODE; + unprocessedData[busNum] = decodeFrames(data, busNum); + } + else if(data.indexOf("< ok >", 0, Qt::CaseSensitivity::CaseInsensitive) > 0) + { + qDebug() << "Ok found at in middle of compound message, switching to RAW and decoding immediately"; + rx_state[busNum] = RAWMODE; + unprocessedData[busNum] = decodeFrames(data, busNum); + } break; case RAWMODE: - decodeFrames(data, busNum); + unprocessedData[busNum] = decodeFrames(unprocessedData[busNum] + data, busNum); + + if(unprocessedData[busNum].length() > 128) + { + //the buffer has grown too much we need to clear it out, but what is good logic for that? + //the decodeFrames function strips out datat that doesn't have a '< frame' starting token, and in its + //recursive calling of itself it strips out data that preceedes valid frames, so this should never happen + qDebug() << "busNum: " << busNum << "- " << unprocessedData[busNum].length() << " bytes in unprocessedData, something is wrong, clearing..."; + unprocessedData[busNum].clear(); + } break; case ISOTP: break; diff --git a/connections/socketcand.h b/connections/socketcand.h index b5c9376..06c7a6d 100644 --- a/connections/socketcand.h +++ b/connections/socketcand.h @@ -57,7 +57,7 @@ private slots: void invokeReadTCPData(); void deviceConnected(int busNum); void switchToRawMode(int busNum); - void decodeFrames(QString, int busNum); + QString decodeFrames(QString, int busNum); private: void procRXData(QString, int busNum); @@ -73,9 +73,9 @@ protected: int hostPort; QList hostCanIDs; int framesRapid; - QByteArray buildData; QVarLengthArray rx_state; CANFrame buildFrame; + QVarLengthArray unprocessedData; }; diff --git a/dbc/dbc_classes.cpp b/dbc/dbc_classes.cpp index 5467b4f..da44f80 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); } } } @@ -118,7 +122,7 @@ s 5 47 46 45 44 43 42 41 40 So, the bits are 12, 11, 10, 9, 8, 23, 22, 21. Yes, that's confusing. They now go in reverse value order too. Bit 12 is worth 128, 11 is worth 64, etc until bit 21 is worth 1. */ -bool DBC_SIGNAL::processAsText(const CANFrame &frame, QString &outString, bool outputName) +bool DBC_SIGNAL::processAsText(const CANFrame &frame, QString &outString, bool outputName, bool outputUnit) { int64_t result = 0; bool isSigned = false; @@ -155,7 +159,7 @@ bool DBC_SIGNAL::processAsText(const CANFrame &frame, QString &outString, bool o //a 32 bit single precision float. That's evil incarnate but it is very fast and small //in terms of new code. result = Utility::processIntegerSignal(frame.payload(), startBit, 32, intelByteOrder, false); - endResult = (*((float *)(&result)) * factor) + bias; + endResult = (*((float *)(&result)) * factor) + bias; //look away! This is awful. I don't even know for sure if it works. Should test that. } else //double precision float { @@ -166,11 +170,11 @@ bool DBC_SIGNAL::processAsText(const CANFrame &frame, QString &outString, bool o } //like the above, this is rotten and evil and wrong in so many ways. Force //calculation of a 64 bit integer and then cast it into a double. - result = Utility::processIntegerSignal(frame.payload(), 0, 64, intelByteOrder, false); + result = Utility::processIntegerSignal(frame.payload(), startBit, 64, intelByteOrder, false); endResult = (*((double *)(&result)) * factor) + bias; } - outString = makePrettyOutput(endResult, result, outputName, isInteger); + outString = makePrettyOutput(endResult, result, outputName, isInteger, outputUnit); cachedValue = endResult; return true; } @@ -191,7 +195,7 @@ bool DBC_SIGNAL::getValueString(int64_t intVal, QString &outString) return false; } -QString DBC_SIGNAL::makePrettyOutput(double floatVal, int64_t intVal, bool outputName, bool isInteger) +QString DBC_SIGNAL::makePrettyOutput(double floatVal, int64_t intVal, bool outputName, bool isInteger, bool outputUnit) { QString outputString; @@ -209,11 +213,13 @@ QString DBC_SIGNAL::makePrettyOutput(double floatVal, int64_t intVal, bool outpu break; } } - if (!foundVal) outputString += QString::number(intVal) + unitName; + if (!foundVal) outputString += QString::number(intVal); + if (outputUnit) outputString += unitName; } else //otherwise display the actual number and unit (if it exists) { - outputString += (isInteger ? QString::number(intVal) : QString::number(floatVal)) + unitName; + outputString += (isInteger ? QString::number(intVal) : QString::number(floatVal)); + if (outputUnit) outputString += unitName; } return outputString; } @@ -306,7 +312,7 @@ bool DBC_SIGNAL::processAsDouble(const CANFrame &frame, double &outValue) } //like the above, this is rotten and evil and wrong in so many ways. Force //calculation of a 64 bit integer and then cast it into a double. - result = Utility::processIntegerSignal(frame.payload(), 0, 64, false, false); + result = Utility::processIntegerSignal(frame.payload(), startBit, 64, false, false); endResult = (*((double *)(&result)) * factor) + bias; } cachedValue = endResult; diff --git a/dbc/dbc_classes.h b/dbc/dbc_classes.h index fd085c2..11a7568 100644 --- a/dbc/dbc_classes.h +++ b/dbc/dbc_classes.h @@ -71,6 +71,7 @@ class DBC_NODE public: QString name; QString comment; + QString sourceFileName; QList attributes; DBC_ATTRIBUTE_VALUE *findAttrValByName(QString name); @@ -110,13 +111,14 @@ public: //TODO: this is sloppy. It shouldn't all be public! QList valList; QList multiplexedChildren; DBC_SIGNAL *multiplexParent; + DBC_SIGNAL *self; DBC_SIGNAL(); - bool processAsText(const CANFrame &frame, QString &outString, bool outputName = true); + bool processAsText(const CANFrame &frame, QString &outString, bool outputName = true, bool outputUnit = true); bool processAsInt(const CANFrame &frame, int32_t &outValue); bool processAsDouble(const CANFrame &frame, double &outValue); bool getValueString(int64_t intVal, QString &outString); - QString makePrettyOutput(double floatVal, int64_t intVal, bool outputName = true, bool isInteger = false); + QString makePrettyOutput(double floatVal, int64_t intVal, bool outputName = true, bool isInteger = false, bool outputUnit = true); QString processSignalTree(const CANFrame &frame); DBC_ATTRIBUTE_VALUE *findAttrValByName(QString name); DBC_ATTRIBUTE_VALUE *findAttrValByIdx(int idx); diff --git a/dbc/dbchandler.cpp b/dbc/dbchandler.cpp index 3bbfe01..f55118b 100644 --- a/dbc/dbchandler.cpp +++ b/dbc/dbchandler.cpp @@ -100,8 +100,15 @@ void DBCSignalHandler::sort() DBC_MESSAGE* DBCMessageHandler::findMsgByID(uint32_t id) { if (messages.count() == 0) return nullptr; + DBC_MESSAGE *bestMatch = nullptr; + for (int i = 0; i < messages.count(); i++) { + if ( messages[i].ID == id ) + { + return &messages[i]; + } + if (matchingCriteria == J1939) { // include data page and extended data page in the pgn @@ -112,7 +119,7 @@ DBC_MESSAGE* DBCMessageHandler::findMsgByID(uint32_t id) pgn &= 0x3FF00; if ((messages[i].ID & 0x3FF0000) == (pgn << 8)) { - return &messages[i]; + bestMatch = &messages[i]; } } else @@ -120,7 +127,7 @@ DBC_MESSAGE* DBCMessageHandler::findMsgByID(uint32_t id) // PDU2 format if ((messages[i].ID & 0x3FFFF00) == (pgn << 8)) { - return &messages[i]; + bestMatch = &messages[i]; } } } @@ -129,17 +136,10 @@ DBC_MESSAGE* DBCMessageHandler::findMsgByID(uint32_t id) // Match the bits 14-26 (Arbitration Id) of GMLAN 29bit header uint32_t arbId = id &0x3FFE000; if ( (arbId != 0) && (messages[i].ID & 0x3FFE000) == arbId ) - return &messages[i]; - } - else - { - if ( messages[i].ID == id ) - { - return &messages[i]; - } + bestMatch = &messages[i]; } } - return nullptr; + return bestMatch; } DBC_MESSAGE* DBCMessageHandler::findMsgByIdx(int idx) @@ -177,6 +177,24 @@ DBC_MESSAGE* DBCMessageHandler::findMsgByPartialName(QString name) return nullptr; } +QList DBCMessageHandler::findMsgsByNode(DBC_NODE* node) +{ + QList messagesForNode; + + if (messages.count() == 0) + return messagesForNode; + + for (int i = 0; i < messages.count(); i++) + { + if (messages[i].sender == node) + { + messagesForNode.append(&messages[i]); + } + } + + return messagesForNode; +} + bool DBCMessageHandler::addMessage(DBC_MESSAGE &msg) { messages.append(msg); @@ -192,6 +210,7 @@ bool DBCMessageHandler::removeMessage(DBC_MESSAGE *msg) { messages.removeAt(i); qDebug() << "Removed message at idx " << i; + break; } } return true; @@ -336,7 +355,26 @@ DBC_NODE* DBCFile::findNodeByName(QString name) if (dbc_nodes.length() == 0) return nullptr; for (int i = 0; i < dbc_nodes.length(); i++) { - if (dbc_nodes[i].name.compare(name, Qt::CaseInsensitive) == 0) + if (name.compare(dbc_nodes[i].name, Qt::CaseInsensitive) == 0) + { + return &dbc_nodes[i]; + } + } + return nullptr; +} + +DBC_NODE* DBCFile::findNodeByNameAndComment(QString fullname) +{ + QString nameAndComment; + if (dbc_nodes.length() == 0) return nullptr; + for (int i = 0; i < dbc_nodes.length(); i++) + { + if(dbc_nodes[i].comment.isEmpty()) + nameAndComment = dbc_nodes[i].name; + else + nameAndComment = dbc_nodes[i].name + " - " + dbc_nodes[i].comment; + + if (fullname.compare(nameAndComment, Qt::CaseInsensitive) == 0) { return &dbc_nodes[i]; } @@ -354,6 +392,11 @@ QString DBCFile::getFilename() return fileName; } +QString DBCFile::getFilenameNoExt() +{ + return fileName.split(".dbc")[0]; +} + QString DBCFile::getPath() { return filePath; @@ -403,12 +446,21 @@ void DBCFile::findAttributesByType(DBC_ATTRIBUTE_TYPE typ, QList } } -//there's no external way to clear the flag. It is only cleared when the file is saved by this object. void DBCFile::setDirtyFlag() { isDirty = true; } +//BE CAREFUL HERE. Do not clear the dirty flag unless you're absolutely sure nothing has changed. +//Currently the signal editor clears this flag if the entire undo buffer is emptied but still +//it's possible that signals or messages were deleted or added so this is potentially not that safe +//It would be better if every node, message, and signal had a dirty flag. Then the DBCFile getDirtyFlag +//function could traverse the tree and see if anything is dirty. +void DBCFile::clearDirtyFlag() +{ + isDirty = false; +} + bool DBCFile::getDirtyFlag() { return isDirty; @@ -422,7 +474,7 @@ DBC_MESSAGE* DBCFile::parseMessageLine(QString line) DBC_MESSAGE *msgPtr; qDebug() << "Found a BO line"; - regex.setPattern("^BO\\_ (\\w+) (\\w+) *: (\\w+) (\\w+)"); + regex.setPattern("^BO\\_ (\\w+) ([-\\w]+) *: (\\w+) ([-\\w]+)"); match = regex.match(line); //captured 1 = the ID in decimal //captured 2 = The message name @@ -433,7 +485,7 @@ DBC_MESSAGE* DBCFile::parseMessageLine(QString line) DBC_MESSAGE msg; uint32_t ID = match.captured(1).toULong(); //the ID is always stored in decimal format msg.ID = ID & 0x1FFFFFFFul; - msg.extendedID = (ID & 80000000ul) ? true : false; + msg.extendedID = (ID & 0x80000000ul) ? true : false; msg.name = match.captured(2); msg.len = match.captured(3).toUInt(); msg.sender = findNodeByName(match.captured(4)); @@ -461,7 +513,7 @@ DBC_SIGNAL* DBCFile::parseSignalLine(QString line, DBC_MESSAGE *msg) sig.isMultiplexor = false; qDebug() << "Found a SG line"; - regex.setPattern("^SG\\_ *(\\w+) +M *: *(\\d+)\\|(\\d+)@(\\d+)([\\+|\\-]) \\(([0-9.+\\-eE]+),([0-9.+\\-eE]+)\\) \\[([0-9.+\\-eE]+)\\|([0-9.+\\-eE]+)\\] \\\"(.*)\\\" (.*)"); + regex.setPattern("^SG\\_ *([-\\w]+) +M *: *(\\d+)\\|(\\d+)@(\\d+)([\\+|\\-]) \\(([0-9.+\\-eE]+),([0-9.+\\-eE]+)\\) \\[([0-9.+\\-eE]+)\\|([0-9.+\\-eE]+)\\] \\\"(.*)\\\" (.*)"); match = regex.match(line); if (match.hasMatch()) @@ -472,7 +524,7 @@ DBC_SIGNAL* DBCFile::parseSignalLine(QString line, DBC_MESSAGE *msg) } else { - regex.setPattern("^SG\\_ *(\\w+) +m(\\d+) *: *(\\d+)\\|(\\d+)@(\\d+)([\\+|\\-]) \\(([0-9.+\\-eE]+),([0-9.+\\-eE]+)\\) \\[([0-9.+\\-eE]+)\\|([0-9.+\\-eE]+)\\] \\\"(.*)\\\" (.*)"); + regex.setPattern("^SG\\_ *([-\\w]+) +m(\\d+) *: *(\\d+)\\|(\\d+)@(\\d+)([\\+|\\-]) \\(([0-9.+\\-eE]+),([0-9.+\\-eE]+)\\) \\[([0-9.+\\-eE]+)\\|([0-9.+\\-eE]+)\\] \\\"(.*)\\\" (.*)"); match = regex.match(line); if (match.hasMatch()) { @@ -485,7 +537,7 @@ DBC_SIGNAL* DBCFile::parseSignalLine(QString line, DBC_MESSAGE *msg) } else { - regex.setPattern("^SG\\_ *(\\w+) +m(\\d+)M *: *(\\d+)\\|(\\d+)@(\\d+)([\\+|\\-]) \\(([0-9.+\\-eE]+),([0-9.+\\-eE]+)\\) \\[([0-9.+\\-eE]+)\\|([0-9.+\\-eE]+)\\] \\\"(.*)\\\" (.*)"); + regex.setPattern("^SG\\_ *([-\\w]+) +m(\\d+)M *: *(\\d+)\\|(\\d+)@(\\d+)([\\+|\\-]) \\(([0-9.+\\-eE]+),([0-9.+\\-eE]+)\\) \\[([0-9.+\\-eE]+)\\|([0-9.+\\-eE]+)\\] \\\"(.*)\\\" (.*)"); match = regex.match(line); if (match.hasMatch()) { @@ -499,7 +551,7 @@ DBC_SIGNAL* DBCFile::parseSignalLine(QString line, DBC_MESSAGE *msg) else { qDebug() << "standard signal"; - regex.setPattern("^SG\\_ *(\\w+) *: *(\\d+)\\|(\\d+)@(\\d+)([\\+|\\-]) \\(([0-9.+\\-eE]+),([0-9.+\\-eE]+)\\) \\[([0-9.+\\-eE]+)\\|([0-9.+\\-eE]+)\\] \\\"(.*)\\\" (.*)"); + regex.setPattern("^SG\\_ *([-\\w]+) *: *(\\d+)\\|(\\d+)@(\\d+)([\\+|\\-]) \\(([0-9.+\\-eE]+),([0-9.+\\-eE]+)\\) \\[([0-9.+\\-eE]+)\\|([0-9.+\\-eE]+)\\] \\\"(.*)\\\" (.*)"); match = regex.match(line); sig.isMultiplexed = false; sig.isMultiplexor = false; @@ -591,7 +643,7 @@ bool DBCFile::parseSignalMultiplexValueLine(QString line) QRegularExpressionMatch match; qDebug() << "Found a multiplex definition line"; - regex.setPattern("^SG\\_MUL\\_VAL\\_ (\\d+) (\\w+) (\\w+) (\\d+)\\-(\\d+);"); + regex.setPattern("^SG\\_MUL\\_VAL\\_ (\\d+) ([-\\w]+) ([-\\w]+) (\\d+)\\-(\\d+);"); match = regex.match(line); //captured 1 is message ID //Captured 2 is signal name @@ -612,6 +664,8 @@ bool DBCFile::parseSignalMultiplexValueLine(QString line) //now need to add "thisSignal" to the children multiplexed signals of "parentSignal" parentSignal->multiplexedChildren.append(thisSignal); thisSignal->multiplexParent = parentSignal; + thisSignal->multiplexLowValue = match.captured(4).toInt(); + thisSignal->multiplexHighValue = match.captured(5).toInt(); return true; } } @@ -620,13 +674,51 @@ bool DBCFile::parseSignalMultiplexValueLine(QString line) return false; } +bool DBCFile::parseSignalValueTypeLine(QString line) +{ + QRegularExpression regex; + QRegularExpressionMatch match; + qDebug() << "Found a signal valtype line"; + regex.setPattern("^SIG\\_VALTYPE\\_ *(\\d+) *([-\\w]+) *: *(\\d+);"); + match = regex.match(line); + + // captured 1 is the message id + // captured 2 is the signal name + // captured 3 is the valtype + if (!match.hasMatch()) { return false; } + uint32_t id = match.captured(1).toULong() & 0x1FFFFFFFUL; + + DBC_MESSAGE *msg = messageHandler->findMsgByID(match.captured(1).toULong() & 0x1FFFFFFFUL); + if (msg == nullptr) { return false; } + + DBC_SIGNAL *thisSignal = msg->sigHandler->findSignalByName(match.captured(2)); + if (thisSignal == nullptr) { return false; } + int valType = match.captured(3).toInt(); + + switch (valType) { + case 1: { + thisSignal->valType = SP_FLOAT; + break; + } + case 2: { + thisSignal->valType = DP_FLOAT; + break; + } + default: { + return false; + } + } + return true; +} + + bool DBCFile::parseValueLine(QString line) { QRegularExpression regex; QRegularExpressionMatch match; qDebug() << "Found a value definition line"; - regex.setPattern("^VAL\\_ (\\w+) (\\w+) (.*);"); + regex.setPattern("^VAL\\_ (\\w+) ([-\\w]+) (.*);"); match = regex.match(line); //captured 1 is the ID to match against //captured 2 is the signal name to match against @@ -671,7 +763,7 @@ bool DBCFile::parseAttributeLine(QString line) QRegularExpression regex; QRegularExpressionMatch match; - regex.setPattern("^BA\\_ \\\"*(\\w+)\\\"* BO\\_ (\\d+) \\\"*([#\\w]+)\\\"*"); + regex.setPattern("^BA\\_ \\\"*([-\\w]+)\\\"* BO\\_ (\\d+) \\\"*([#\\w]+)\\\"*"); match = regex.match(line); //captured 1 is the attribute name //captured 2 is the message ID number (frame ID) @@ -682,7 +774,7 @@ bool DBCFile::parseAttributeLine(QString line) DBC_ATTRIBUTE *foundAttr = findAttributeByName(match.captured(1)); if (foundAttr) { - qDebug() << "That attribute does exist"; + qDebug() << "That message attribute does exist"; DBC_MESSAGE *foundMsg = messageHandler->findMsgByID(match.captured(2).toUInt() & 0x1FFFFFFFul); if (foundMsg) { @@ -702,7 +794,7 @@ bool DBCFile::parseAttributeLine(QString line) } } - regex.setPattern("^BA\\_ \\\"*(\\w+)\\\"* SG\\_ (\\d+) \\\"*(\\w+)\\\"* \\\"*([#\\w]+)\\\"*"); + regex.setPattern("^BA\\_ \\\"*([-\\w]+)\\\"* SG\\_ (\\d+) \\\"*([-\\w]+)\\\"* \\\"*([#\\w]+)\\\"*"); match = regex.match(line); //captured 1 is the attribute name //captured 2 is the message ID number (frame ID) @@ -714,7 +806,7 @@ bool DBCFile::parseAttributeLine(QString line) DBC_ATTRIBUTE *foundAttr = findAttributeByName(match.captured(1)); if (foundAttr) { - qDebug() << "That attribute does exist"; + qDebug() << "That signal attribute does exist"; DBC_MESSAGE *foundMsg = messageHandler->findMsgByID(match.captured(2).toUInt() & 0x1FFFFFFFUL); if (foundMsg) { @@ -736,7 +828,7 @@ bool DBCFile::parseAttributeLine(QString line) } } - regex.setPattern("^BA\\_ \\\"*(\\w+)\\\"* BU\\_ \\\"*(\\w+)\\\"* \\\"*([#\\w]+)\\\"*"); + regex.setPattern("^BA\\_ \\\"*([-\\w]+)\\\"* BU\\_ \\\"*([-\\w]+)\\\"* \\\"*([#\\w]+)\\\"*"); match = regex.match(line); //captured 1 is the attribute name //captured 2 is the name of the node @@ -747,7 +839,7 @@ bool DBCFile::parseAttributeLine(QString line) DBC_ATTRIBUTE *foundAttr = findAttributeByName(match.captured(1)); if (foundAttr) { - qDebug() << "That attribute does exist"; + qDebug() << "That node attribute does exist"; DBC_NODE *foundNode = findNodeByName(match.captured(2)); if (foundNode) { @@ -774,7 +866,7 @@ bool DBCFile::parseDefaultAttrLine(QString line) QRegularExpression regex; QRegularExpressionMatch match; - regex.setPattern("^BA\\_DEF\\_DEF\\_ \\\"*(\\w+)\\\"* \\\"*([#\\w]*)\\\"*"); + regex.setPattern("^BA\\_DEF\\_DEF\\_ \\\"*([-\\w]+)\\\"* \\\"*([#\\w]*)\\\"*"); match = regex.match(line); //captured 1 is the name of the attribute //captured 2 is the default value for that attribute @@ -824,6 +916,7 @@ bool DBCFile::loadFile(QString fileName) DBC_ATTRIBUTE attr; int numSigFaults = 0, numMsgFaults = 0; int linesSinceYield = 0; + QString fileBaseName = QFileInfo(fileName).baseName(); bool inMultilineBU = false; @@ -862,6 +955,7 @@ bool DBCFile::loadFile(QString fileName) if (rawLine.startsWith("\t") || rawLine.startsWith(" ")) { DBC_NODE node; + node.sourceFileName = fileBaseName; node.name = line; dbc_nodes.append(node); } @@ -885,6 +979,11 @@ bool DBCFile::loadFile(QString fileName) if (!parseSignalMultiplexValueLine(line)) numSigFaults++; } + if (line.startsWith("SIG_VALTYPE_ ")) //defines a signal value type + { + if (!parseSignalValueTypeLine(line)) numSigFaults++; + } + if (line.startsWith("BU_:")) //line specifies the nodes on this canbus { qDebug() << "Found a BU line"; @@ -901,6 +1000,7 @@ bool DBCFile::loadFile(QString fileName) if (nodeStrings[i].length() > 1) { DBC_NODE node; + node.sourceFileName = fileBaseName; node.name = nodeStrings[i]; dbc_nodes.append(node); } @@ -911,7 +1011,7 @@ bool DBCFile::loadFile(QString fileName) if (line.startsWith("CM_ SG_ ")) { qDebug() << "Found an SG comment line"; - regex.setPattern("^CM\\_ SG\\_ *(\\w+) *(\\w+) *\\\"(.*)\\\";"); + regex.setPattern("^CM\\_ SG\\_ *(\\w+) *([-\\w]+) *\\\"(.*)\\\";"); match = regex.match(line); //captured 1 is the ID to match against to get to the message //captured 2 is the signal name from that message @@ -950,7 +1050,7 @@ bool DBCFile::loadFile(QString fileName) if (line.startsWith("CM_ BU_ ")) { qDebug() << "Found a BU comment line"; - regex.setPattern("^CM\\_ BU\\_ *(\\w+) *\\\"(.*)\\\";"); + regex.setPattern("^CM\\_ BU\\_ *([-\\w]+) *\\\"(.*)\\\";"); match = regex.match(line); //captured 1 is the Node name //captured 2 is the comment itself @@ -1254,7 +1354,7 @@ bool DBCFile::saveFile(QString fileName) int msgNumber = 1; int sigNumber = 1; QFile *outFile = new QFile(fileName); - QString nodesOutput, msgOutput, commentsOutput, valuesOutput; + QString nodesOutput, msgOutput, commentsOutput, valuesOutput, extMultiplexOutput; QString defaultsOutput, attrValOutput; bool hasExtendedMultiplexing = false; @@ -1323,7 +1423,7 @@ bool DBCFile::saveFile(QString fileName) attrValOutput.append("BA_ \"" + val.attrName + "\" BU_ "); switch (val.value.type()) { - case QMetaType::QString: + case QVariant::Type::String: attrValOutput.append("\"" + val.value.toString() + "\";\n"); break; default: @@ -1349,7 +1449,10 @@ bool DBCFile::saveFile(QString fileName) } uint32_t ID = msg->ID; - if (msg->ID > 0x7FF || msg->extendedID) msg->ID += 0x80000000ul; //set bit 31 if this ID is extended. + if (msg->ID > 0x7FF || msg->extendedID) + { + ID += 0x80000000ul; //set bit 31 if this ID is extended. + } msgOutput.append("BO_ " + QString::number(ID) + " " + msg->name + ": " + QString::number(msg->len) + " " + msg->sender->name + "\n"); @@ -1365,7 +1468,7 @@ bool DBCFile::saveFile(QString fileName) attrValOutput.append("BA_ \"" + val.attrName + "\" BO_ " + QString::number(ID) + " "); switch (val.value.type()) { - case QMetaType::QString: + case QVariant::Type::String: attrValOutput.append("\"" + val.value.toString() + "\";\n"); break; default: @@ -1442,7 +1545,7 @@ bool DBCFile::saveFile(QString fileName) attrValOutput.append("BA_ \"" + val.attrName + "\" SG_ " + QString::number(ID) + " " + sig->name + " "); switch (val.value.type()) { - case QMetaType::QString: + case QVariant::Type::String: attrValOutput.append("\"" + val.value.toString() + "\";\n"); break; default: @@ -1469,6 +1572,9 @@ bool DBCFile::saveFile(QString fileName) msgOutput.clear(); //got to reset it after writing } + outFile->write(commentsOutput.toUtf8()); + commentsOutput.clear(); + //Now dump out all of the stored attributes for (int x = 0; x < dbc_attributes.count(); x++) { @@ -1486,6 +1592,8 @@ bool DBCFile::saveFile(QString fileName) case ATTR_TYPE_SIG: msgOutput.append("SG_ "); break; + case ATTR_TYPE_ANY: + break; } msgOutput.append("\"" + dbc_attributes[x].name + "\" "); @@ -1528,6 +1636,8 @@ bool DBCFile::saveFile(QString fileName) defaultsOutput.append("\"" + dbc_attributes[x].enumVals[dbc_attributes[x].defaultValue.toInt()] + "\";\n"); break; case ATTR_INT: + defaultsOutput.append(QString::number(dbc_attributes[x].defaultValue.toLongLong()) + ";\n"); + break; case ATTR_FLOAT: defaultsOutput.append(dbc_attributes[x].defaultValue.toString() + ";\n"); break; @@ -1545,7 +1655,10 @@ bool DBCFile::saveFile(QString fileName) DBC_MESSAGE *msg = messageHandler->findMsgByIdx(x); uint32_t ID = msg->ID; - if (msg->ID > 0x7FF || msg->extendedID) msg->ID += 0x80000000ul; //set bit 31 if this ID is extended. + if (msg->ID > 0x7FF || msg->extendedID) + { + ID += 0x80000000ul; //set bit 31 if this ID is extended. + } for (int s = 0; s < msg->sigHandler->getCount(); s++) { @@ -1554,10 +1667,10 @@ bool DBCFile::saveFile(QString fileName) if (sig->isMultiplexed) { msgOutput.append("SG_MUL_VAL_ " + QString::number(ID) + " "); - msgOutput.append(sig->name + " " + sig->parentMessage->name + " "); + msgOutput.append(sig->name + " " + sig->multiplexParent->name + " "); msgOutput.append(QString::number(sig->multiplexLowValue) + "-" + QString::number(sig->multiplexHighValue) + ";"); msgOutput.append("\n"); - outFile->write(msgOutput.toUtf8()); + extMultiplexOutput.append(msgOutput); msgOutput.clear(); //got to reset it after writing } } @@ -1565,15 +1678,15 @@ bool DBCFile::saveFile(QString fileName) } //now write out all of the accumulated comments and value tables from above - outFile->write(attrValOutput.toUtf8()); outFile->write(defaultsOutput.toUtf8()); - outFile->write(commentsOutput.toUtf8()); + outFile->write(attrValOutput.toUtf8()); outFile->write(valuesOutput.toUtf8()); + outFile->write(extMultiplexOutput.toUtf8()); attrValOutput.clear(); defaultsOutput.clear(); - commentsOutput.clear(); valuesOutput.clear(); + extMultiplexOutput.clear(); outFile->close(); delete outFile; @@ -2140,6 +2253,8 @@ DBC_MESSAGE* DBCHandler::findMessageForFilter(uint32_t id, MatchingCriteria_t * /* * As above, a real shortcut function that searches all files in order to try to find a message with the given name + * This has pitfalls because the same message name can easily exist in multiple dbc files AND nodes in a single file + * By DBC standards only the MSG ID is required to be unique */ DBC_MESSAGE* DBCHandler::findMessage(const QString msgName) { @@ -2153,6 +2268,43 @@ DBC_MESSAGE* DBCHandler::findMessage(const QString msgName) return nullptr; //no match, tough luck, return null } +DBC_MESSAGE* DBCHandler::findMessage(const QString msgName, const QString nodeName, const QString fileNameNoExt) +{ + DBC_MESSAGE *msg = nullptr; + for(int i = 0; i < loadedFiles.count(); i++) + { + DBCFile * file = getFileByIdx(i); + if(file->getFilenameNoExt() == fileNameNoExt) + { + int msgCount = file->messageHandler->getCount(); + for(int f = 0; f < msgCount; f++) + { + msg = file->messageHandler->findMsgByIdx(f); + if (msg && msg->name == msgName && msg->sender->name == nodeName) + return msg; //if it's not null and the node name matches return it + } + } + } + return nullptr; //no match, tough luck, return null +} + +DBC_MESSAGE* DBCHandler::findMessage(const QString msgName, const QString fullyQualifiedNodeName) +{ + QStringList nodeNameParts = fullyQualifiedNodeName.split(Utility::fullyQualifiedNameSeperator); + if(nodeNameParts.count() != 2) + { + qDebug() << "Error parsing fully qualified node name for message search"; + return nullptr; + } + + QString fileNameNoExt = nodeNameParts[0]; + QString nodeName = nodeNameParts[1]; + + DBC_MESSAGE *msg = nullptr; + msg = findMessage(msgName, nodeName, fileNameNoExt); + return msg; +} + int DBCHandler::getFileCount() { return loadedFiles.count(); @@ -2183,18 +2335,19 @@ DBCHandler::DBCHandler() { // Load previously saved DBC file settings QSettings settings; + qDebug() <<"Settings file: " << settings.fileName(); int filecount = settings.value("DBC/FileCount", 0).toInt(); qDebug() << "Previously loaded DBC file count: " << filecount; for (int i=0; isetAssocBus(bus); - MatchingCriteria_t matchingCriteria = (MatchingCriteria_t)settings.value("DBC/MatchingCriteria_" + QString(i),0).toInt(); + MatchingCriteria_t matchingCriteria = (MatchingCriteria_t)settings.value("DBC/MatchingCriteria_" + QString::number(i),0).toInt(); DBC_ATTRIBUTE attr; @@ -2208,7 +2361,7 @@ DBCHandler::DBCHandler() file->dbc_attributes.append(attr); file->messageHandler->setMatchingCriteria(matchingCriteria); - bool filterLabeling = settings.value("DBC/FilterLabeling_" + QString(i),0).toBool(); + int filterLabeling = settings.value("DBC/FilterLabeling_" + QString::number(i),0).toInt(); attr.attrType = ATTR_TYPE_MESSAGE; attr.defaultValue = filterLabeling; attr.enumVals.clear(); diff --git a/dbc/dbchandler.h b/dbc/dbchandler.h index 304a7a8..14f19f8 100644 --- a/dbc/dbchandler.h +++ b/dbc/dbchandler.h @@ -43,6 +43,7 @@ public: DBC_MESSAGE *findMsgByIdx(int idx); DBC_MESSAGE *findMsgByName(QString name); DBC_MESSAGE *findMsgByPartialName(QString name); + QList findMsgsByNode(DBC_NODE *node); bool addMessage(DBC_MESSAGE &msg); bool removeMessage(DBC_MESSAGE *msg); bool removeMessageByIndex(int idx); @@ -72,6 +73,7 @@ public: DBCFile(const DBCFile& cpy); DBCFile& operator=(const DBCFile& cpy); DBC_NODE *findNodeByName(QString name); + DBC_NODE *findNodeByNameAndComment(QString fullname); DBC_NODE *findNodeByIdx(int idx); DBC_ATTRIBUTE *findAttributeByName(QString name, DBC_ATTRIBUTE_TYPE type = ATTR_TYPE_ANY); DBC_ATTRIBUTE *findAttributeByIdx(int idx); @@ -80,11 +82,13 @@ public: bool loadFile(QString); QString getFullFilename(); QString getFilename(); + QString getFilenameNoExt(); QString getPath(); int getAssocBus(); void setAssocBus(int bus); void setDirtyFlag(); bool getDirtyFlag(); + void clearDirtyFlag(); void sort(); DBCMessageHandler *messageHandler; @@ -102,6 +106,7 @@ private: bool parseSignalMultiplexValueLine(QString line); DBC_MESSAGE* parseMessageLine(QString line); bool parseValueLine(QString line); + bool parseSignalValueTypeLine(QString line); bool parseAttributeLine(QString line); bool parseDefaultAttrLine(QString line); }; @@ -118,6 +123,8 @@ public: void swapFiles(int pos1, int pos2); DBC_MESSAGE* findMessage(const CANFrame &frame); DBC_MESSAGE* findMessage(const QString msgName); + DBC_MESSAGE* findMessage(const QString msgName, const QString fullyQualifiedNodeName); + DBC_MESSAGE* findMessage(const QString msgName, const QString nodeName, const QString fileNameNoExt); DBC_MESSAGE* findMessage(uint32_t id); DBC_MESSAGE* findMessageForFilter(uint32_t id, MatchingCriteria_t * matchingCriteria); int getFileCount(); diff --git a/dbc/dbcloadsavewindow.cpp b/dbc/dbcloadsavewindow.cpp index 7136379..8f8e7dd 100644 --- a/dbc/dbcloadsavewindow.cpp +++ b/dbc/dbcloadsavewindow.cpp @@ -108,10 +108,10 @@ void DBCLoadSaveWindow::updateSettings() qDebug() << "Save DBC settings #" << i << " File: " << file->getFullFilename() << "Bus: " << file->getAssocBus() << "MC: " << file->messageHandler->getMatchingCriteria() << "Filter Labeling: " << (file->messageHandler->filterLabeling() ? "enabled" : "disabled"); - settings.setValue("DBC/Filename_" + QString(i), file->getFullFilename()); - settings.setValue("DBC/AssocBus_" + QString(i), file->getAssocBus()); - settings.setValue("DBC/MatchingCriteria_" + QString(i), file->messageHandler->getMatchingCriteria()); - settings.setValue("DBC/FilterLabeling_" + QString(i), file->messageHandler->filterLabeling()); + settings.setValue("DBC/Filename_" + QString::number(i), file->getFullFilename()); + settings.setValue("DBC/AssocBus_" + QString::number(i), file->getAssocBus()); + settings.setValue("DBC/MatchingCriteria_" + QString::number(i), file->messageHandler->getMatchingCriteria()); + settings.setValue("DBC/FilterLabeling_" + QString::number(i), file->messageHandler->filterLabeling() ? 1 : 0); } } emit updatedDBCSettings(); @@ -200,7 +200,7 @@ void DBCLoadSaveWindow::loadFile() DBC_ATTRIBUTE *attr = file->findAttributeByName("matchingcriteria"); QComboBox * mc_item = addMatchingCriteriaCombobox(idx); - if (attr && attr->defaultValue > 0) + if (attr && attr->defaultValue.toInt() > 0) { mc_item->setCurrentIndex(attr->defaultValue.toInt()); } @@ -208,7 +208,7 @@ void DBCLoadSaveWindow::loadFile() attr = file->findAttributeByName("filterlabeling"); QTableWidgetItem *item = new QTableWidgetItem(""); ui->tableFiles->setItem(idx, 3, item); - if (attr && attr->defaultValue > 0) + if (attr && attr->defaultValue.toInt() > 0) { item->setCheckState(Qt::Checked); } diff --git a/dbc/dbcmaineditor.cpp b/dbc/dbcmaineditor.cpp index 28e831a..b48b05c 100644 --- a/dbc/dbcmaineditor.cpp +++ b/dbc/dbcmaineditor.cpp @@ -21,25 +21,35 @@ DBCMainEditor::DBCMainEditor( const QVector *frames, QWidget *parent) dbcHandler = DBCHandler::getReference(); referenceFrames = frames; + ui->treeDBC->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->btnSearch, &QAbstractButton::clicked, this, &DBCMainEditor::handleSearch); connect(ui->lineSearch, &QLineEdit::returnPressed, this, &DBCMainEditor::handleSearch); connect(ui->btnSearchNext, &QAbstractButton::clicked, this, &DBCMainEditor::handleSearchForward); connect(ui->btnSearchPrev, &QAbstractButton::clicked, this, &DBCMainEditor::handleSearchBackward); connect(ui->treeDBC, &QTreeWidget::doubleClicked, this, &DBCMainEditor::onTreeDoubleClicked); + connect(ui->treeDBC, &QTreeWidget::customContextMenuRequested, this, &DBCMainEditor::onTreeContextMenu); connect(ui->treeDBC, &QTreeWidget::currentItemChanged, this, &DBCMainEditor::currentItemChanged); connect(ui->btnDelete, &QAbstractButton::clicked, this, &DBCMainEditor::deleteCurrentTreeItem); - connect(ui->btnNewNode, &QAbstractButton::clicked, this, &DBCMainEditor::newNode); + connect(ui->btnNewNode, &QAbstractButton::clicked, this, QOverload<>::of(&DBCMainEditor::newNode)); connect(ui->btnNewMessage, &QAbstractButton::clicked, this, &DBCMainEditor::newMessage); connect(ui->btnNewSignal, &QAbstractButton::clicked, this, &DBCMainEditor::newSignal); sigEditor = new DBCSignalEditor(this); msgEditor = new DBCMessageEditor(this); nodeEditor = new DBCNodeEditor(this); + nodeRebaseEditor = new DBCNodeRebaseEditor(this); + nodeDuplicateEditor = new DBCNodeDuplicateEditor(this); //all three might potentially change the data stored and force the tree to be updated connect(sigEditor, &DBCSignalEditor::updatedTreeInfo, this, &DBCMainEditor::updatedSignal); connect(msgEditor, &DBCMessageEditor::updatedTreeInfo, this, &DBCMainEditor::updatedMessage); connect(nodeEditor, &DBCNodeEditor::updatedTreeInfo, this, &DBCMainEditor::updatedNode); + connect(nodeRebaseEditor, &DBCNodeRebaseEditor::updatedTreeInfo, this, &DBCMainEditor::updatedMessage); + connect(nodeDuplicateEditor, &DBCNodeDuplicateEditor::updatedTreeInfo, this, &DBCMainEditor::updatedMessage); + connect(nodeDuplicateEditor, &DBCNodeDuplicateEditor::createNode, this, QOverload::of(&DBCMainEditor::newNode)); + connect(nodeDuplicateEditor, &DBCNodeDuplicateEditor::cloneMessageToNode, this, &DBCMainEditor::copyMessageToNode); + connect(nodeDuplicateEditor, &DBCNodeDuplicateEditor::nodeAdded, this, &DBCMainEditor::refreshTree); nodeIcon = QIcon(":/icons/images/node.png"); messageIcon = QIcon(":/icons/images/message.png"); @@ -150,6 +160,7 @@ void DBCMainEditor::writeSettings() void DBCMainEditor::onCustomMenuTree(QPoint point) { + Q_UNUSED(point); QMenu *menu = new QMenu(this); menu->setAttribute(Qt::WA_DeleteOnClose); @@ -205,19 +216,19 @@ void DBCMainEditor::currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem } switch (current->data(0, Qt::UserRole).toInt()) { - case 1: //node + case DBCItemTypes::NODE: //node ui->btnNewNode->setEnabled(true); ui->btnNewMessage->setEnabled(true); ui->btnNewSignal->setEnabled(false); ui->btnDelete->setEnabled(true); break; - case 2: //message + case DBCItemTypes::MESG: //message ui->btnNewNode->setEnabled(true); ui->btnNewMessage->setEnabled(true); ui->btnNewSignal->setEnabled(true); ui->btnDelete->setEnabled(true); break; - case 3: //signal + case DBCItemTypes::SIG: //signal ui->btnNewNode->setEnabled(true); ui->btnNewMessage->setEnabled(true); ui->btnNewSignal->setEnabled(true); @@ -232,7 +243,7 @@ void DBCMainEditor::currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem uint32_t DBCMainEditor::getParentMessageID(QTreeWidgetItem *cell) { - if (cell->data(0, Qt::UserRole) == 2) + if (cell->data(0, Qt::UserRole) == DBCItemTypes::MESG) { return static_cast(Utility::ParseStringToNum(cell->text(0).split(" ")[0])); } @@ -249,7 +260,7 @@ void DBCMainEditor::onTreeDoubleClicked(const QModelIndex &index) Q_UNUSED(index) QTreeWidgetItem* firstCol = ui->treeDBC->currentItem(); - bool ret = false; + //bool ret = false; DBC_MESSAGE *msg; DBC_SIGNAL *sig; DBC_NODE *node; @@ -260,7 +271,7 @@ void DBCMainEditor::onTreeDoubleClicked(const QModelIndex &index) switch (firstCol->data(0, Qt::UserRole).toInt()) { - case 1: //a node + case DBCItemTypes::NODE: //a node idString = firstCol->text(0).split(" ")[0]; node = dbcFile->findNodeByName(idString); nodeEditor->setFileIdx(fileIdx); @@ -268,7 +279,7 @@ void DBCMainEditor::onTreeDoubleClicked(const QModelIndex &index) nodeEditor->refreshView(); nodeEditor->show(); break; - case 2: //a message + case DBCItemTypes::MESG: //a message idString = firstCol->text(0).split(" ")[0]; msgID = static_cast(Utility::ParseStringToNum(idString)); msg = dbcFile->messageHandler->findMsgByID(msgID); @@ -278,7 +289,7 @@ void DBCMainEditor::onTreeDoubleClicked(const QModelIndex &index) msgEditor->refreshView(); msgEditor->show(); //show allows the rest of the forms to keep going break; - case 3: //a signal + case DBCItemTypes::SIG: //a signal msgID = getParentMessageID(firstCol); msg = dbcFile->messageHandler->findMsgByID(msgID); QString nameString = firstCol->text(0); @@ -298,6 +309,74 @@ void DBCMainEditor::onTreeDoubleClicked(const QModelIndex &index) } } +void DBCMainEditor::onTreeContextMenu(const QPoint & pos) +{ + QTreeWidgetItem* firstCol = ui->treeDBC->currentItem(); + QString idString; + + qDebug() << firstCol->data(0, Qt::UserRole) << " - " << firstCol->text(0); + + switch (firstCol->data(0, Qt::UserRole).toInt()) + { + case DBCItemTypes::NODE: //a node + idString = firstCol->text(0).split(" ")[0]; + //node = dbcFile->findNodeByName(idString); + + QAction *actionRebase = new QAction(QIcon(":/Resource/warning32.ico"), tr("Rebase all messages"), this); + actionRebase->setStatusTip(tr("Rebase all messages in node")); + connect(actionRebase, SIGNAL(triggered()), this, SLOT(onRebaseMessages())); + + QAction *actionDupe = new QAction(QIcon(":/Resource/warning32.ico"), tr("Duplicate node"), this); + actionDupe->setStatusTip(tr("Duplicate node and messages")); + connect(actionDupe, SIGNAL(triggered()), this, SLOT(onDuplicateNode())); + + QMenu menu(this); + menu.addAction(actionRebase); + menu.addAction(actionDupe); + + //QPoint pt(pos); + menu.exec( ui->treeDBC->mapToGlobal(pos) ); + break; + } +} + +void DBCMainEditor::onRebaseMessages() +{ + QTreeWidgetItem* firstCol = ui->treeDBC->currentItem(); + DBC_NODE *node; + QString idString; + + idString = firstCol->text(0).split(" ")[0]; + node = dbcFile->findNodeByName(idString); + nodeRebaseEditor->setFileIdx(fileIdx); + nodeRebaseEditor->setNodeRef(node); + if(nodeRebaseEditor->refreshView()) + { + nodeRebaseEditor->setModal(true); + nodeRebaseEditor->show(); + } +} + +void DBCMainEditor::onDuplicateNode() +{ + QTreeWidgetItem* firstCol = ui->treeDBC->currentItem(); + //bool ret = false; + //DBC_MESSAGE *msg; + //DBC_SIGNAL *sig; + //uint32_t msgID; + DBC_NODE *node; + QString idString; + + idString = firstCol->text(0).split(" ")[0]; + node = dbcFile->findNodeByName(idString); + nodeDuplicateEditor->setFileIdx(fileIdx); + nodeDuplicateEditor->setNodeRef(node); + if(nodeDuplicateEditor->refreshView()) + { + nodeDuplicateEditor->setModal(true); + nodeDuplicateEditor->show(); + } +} /* * Recreate the whole tree with pretty icons and custom user roles that give the rest of code an easy way to figure out whether a given tree node @@ -329,7 +408,7 @@ void DBCMainEditor::refreshTree() if (node->comment.count() > 0) nodeInfo.append(" - ").append(node->comment); nodeItem->setText(0, nodeInfo); nodeItem->setIcon(0, nodeIcon); - nodeItem->setData(0, Qt::UserRole, 1); + nodeItem->setData(0, Qt::UserRole, DBCItemTypes::NODE); nodeToItem.insert(node, nodeItem); itemToNode.insert(nodeItem, node); for (int x = 0; x < dbcFile->messageHandler->getCount(); x++) @@ -342,7 +421,7 @@ void DBCMainEditor::refreshTree() if (msg->comment.count() > 0) msgInfo.append(" - ").append(msg->comment); msgItem->setText(0, msgInfo); msgItem->setIcon(0, messageIcon); - msgItem->setData(0, Qt::UserRole, 2); + msgItem->setData(0, Qt::UserRole, DBCItemTypes::MESG); messageToItem.insert(msg, msgItem); itemToMessage.insert(msgItem, msg); for (int i = 0; i < msg->sigHandler->getCount(); i++) @@ -387,7 +466,7 @@ void DBCMainEditor::processSignalToTree(QTreeWidgetItem *parent, DBC_SIGNAL *sig if (sig->isMultiplexor) sigItem->setIcon(0, multiplexorSignalIcon); else if (sig->isMultiplexed) sigItem->setIcon(0, multiplexedSignalIcon); else sigItem->setIcon(0, signalIcon); - sigItem->setData(0, Qt::UserRole, 3); + sigItem->setData(0, Qt::UserRole, DBCItemTypes::SIG); signalToItem.insert(sig, sigItem); itemToSignal.insert(sigItem, sig); if (sig->multiplexedChildren.count() > 0) @@ -432,7 +511,7 @@ void DBCMainEditor::updatedMessage(DBC_MESSAGE *msg) QTreeWidgetItem *newNodeItem = new QTreeWidgetItem(); newNodeItem->setText(0, msg->sender->name); newNodeItem->setIcon(0, nodeIcon); - newNodeItem->setData(0, Qt::UserRole, 1); + newNodeItem->setData(0, Qt::UserRole, DBCItemTypes::NODE); ui->treeDBC->addTopLevelItem(newNodeItem); nodeToItem.insert(msg->sender, newNodeItem); itemToNode.insert(newNodeItem, msg->sender); @@ -458,7 +537,7 @@ void DBCMainEditor::updatedSignal(DBC_SIGNAL *sig) item->setText(0, sigInfo); if (sig->isMultiplexed) { - if (item->parent()->data(0, Qt::UserRole).toInt() == 3) //if our parent is another signal + if (item->parent()->data(0, Qt::UserRole).toInt() == DBCItemTypes::SIG) //if our parent is another signal { QString nameString = item->parent()->text(0); if (nameString.contains("(")) nameString = nameString.split(" ")[1]; @@ -481,17 +560,24 @@ void DBCMainEditor::updatedSignal(DBC_SIGNAL *sig) else qDebug() << "That signal doesn't exist. That's a bug dude."; } -void DBCMainEditor::newNode() +void DBCMainEditor::newNode(QString nodeName) { DBC_NODE node; DBC_NODE *nodePtr; - node.name = "Unnamed" + QString::number(randGen.bounded(50000)); + if(nodeName.isEmpty()) + { + node.name = "Unnamed" + QString::number(randGen.bounded(50000)); + } + else + { + node.name = nodeName; + } dbcFile->dbc_nodes.append(node); nodePtr = dbcFile->findNodeByName(node.name); QTreeWidgetItem *nodeItem = new QTreeWidgetItem(); nodeItem->setText(0, node.name); nodeItem->setIcon(0, nodeIcon); - nodeItem->setData(0, Qt::UserRole, 1); + nodeItem->setData(0, Qt::UserRole, DBCItemTypes::NODE); nodeToItem.insert(nodePtr, nodeItem); itemToNode.insert(nodeItem, nodePtr); ui->treeDBC->addTopLevelItem(nodeItem); @@ -499,6 +585,77 @@ void DBCMainEditor::newNode() dbcFile->setDirtyFlag(); } +void DBCMainEditor::newNode() +{ + newNode(QString()); +} + +void DBCMainEditor::copyMessageToNode(DBC_NODE *parentNode, DBC_MESSAGE *source, uint newMsgId) +{ + DBC_NODE *node = parentNode; + if (!node) node = dbcFile->findNodeByIdx(0); + QTreeWidgetItem *nodeItem = nullptr; + DBC_MESSAGE msg; + DBC_MESSAGE *msgPtr; + + nodeItem = ui->treeDBC->currentItem(); + + msg.name = source->name; + msg.ID = newMsgId; + msg.len = source->len; + msg.bgColor = source->bgColor; + msg.fgColor = source->fgColor; + msg.comment = source->comment; + msg.sender = node; + + DBC_SIGNAL *sigSource; + int sigCount = source->sigHandler->getCount(); + + for(int i=0; isigHandler->findSignalByIdx(i); + + DBC_SIGNAL sig; + + //Does not properly handle multiplexed signals, for now + sig.name = sigSource->name; + sig.bias = sigSource->bias; + sig.isMultiplexed = false; //sigSource->isMultiplexed; + sig.isMultiplexor = false; //sigSource->isMultiplexor; + sig.max = sigSource->max; + sig.min = sigSource->min; + sig.multiplexLowValue = sigSource->multiplexLowValue; + sig.multiplexHighValue = sigSource->multiplexHighValue; + sig.factor = sigSource->factor; + sig.intelByteOrder = sigSource->intelByteOrder; + sig.parentMessage = &msg; + sig.multiplexParent = nullptr; //need to learn about multiplexed signals and track them when copying + sig.receiver = node; + sig.signalSize = sigSource->signalSize; + sig.startBit = sigSource->startBit; + sig.valType = sigSource->valType; + + + sig.parentMessage = &msg; + msg.sigHandler->addSignal(sig); + } + + msg.sigHandler->sort(); + + dbcFile->messageHandler->addMessage(msg); + msgPtr = dbcFile->messageHandler->findMsgByIdx(dbcFile->messageHandler->getCount() - 1); + QTreeWidgetItem *newMsgItem = new QTreeWidgetItem(); + QString msgInfo = Utility::formatCANID(msg.ID) + " " + msg.name; + if (msg.comment.count() > 0) msgInfo.append(" - ").append(msg.comment); + newMsgItem->setText(0, msgInfo); + newMsgItem->setIcon(0, messageIcon); + newMsgItem->setData(0, Qt::UserRole, DBCItemTypes::MESG); + messageToItem.insert(msgPtr, newMsgItem); + itemToMessage.insert(newMsgItem, msgPtr); + nodeItem->addChild(newMsgItem); + //ui->treeDBC->setCurrentItem(newMsgItem); + dbcFile->setDirtyFlag(); +} //create a new message with it's parent being the node we're currently within void DBCMainEditor::newMessage() @@ -508,16 +665,19 @@ void DBCMainEditor::newMessage() nodeItem = ui->treeDBC->currentItem(); int typ = nodeItem->data(0, Qt::UserRole).toInt(); if (!nodeItem) return; //nothing selected! - if (typ == 2) + if (typ == DBCItemTypes::MESG) { msgItem = nodeItem; nodeItem = nodeItem->parent(); } - if (typ == 3) + if (typ == DBCItemTypes::SIG) { msgItem = nodeItem->parent(); nodeItem = msgItem->parent(); } + if (typ == DBCItemTypes::NODE){ + msgItem = nodeItem; + } //if there was a comment this will find the location of the comment and snip it out. QString nodeName = nodeItem->data(0, Qt::DisplayRole).toString().split(" - ")[0]; @@ -533,8 +693,14 @@ void DBCMainEditor::newMessage() DBC_MESSAGE *oldMsg = dbcFile->messageHandler->findMsgByID(msgID); if (oldMsg) { - msg.name = oldMsg->name + QString::number(randGen.bounded(1000)); + msg.name = "Msg" + QString::number(randGen.bounded(500)); msg.ID = oldMsg->ID + 1; + DBC_MESSAGE *overlappedMsg = dbcFile->messageHandler->findMsgByID(msg.ID); + while (overlappedMsg) + { + msg.ID++; + overlappedMsg = dbcFile->messageHandler->findMsgByID(msg.ID); + } msg.len = oldMsg->len; msg.bgColor = oldMsg->bgColor; msg.fgColor = oldMsg->fgColor; @@ -544,14 +710,15 @@ void DBCMainEditor::newMessage() { msg.name = nodeName + "Msg" + QString::number(randGen.bounded(500)); msg.ID = 0; - msg.len = 0; + msg.len = 8; + msg.bgColor = QApplication::palette().color(QPalette::Base); } } else { msg.name = nodeName + "Msg" + QString::number(randGen.bounded(500)); msg.ID = 0; - msg.len = 0; + msg.len = 8; } msg.sender = node; @@ -562,7 +729,7 @@ void DBCMainEditor::newMessage() if (msg.comment.count() > 0) msgInfo.append(" - ").append(msg.comment); newMsgItem->setText(0, msgInfo); newMsgItem->setIcon(0, messageIcon); - newMsgItem->setData(0, Qt::UserRole, 2); + newMsgItem->setData(0, Qt::UserRole, DBCItemTypes::MESG); messageToItem.insert(msgPtr, newMsgItem); itemToMessage.insert(newMsgItem, msgPtr); nodeItem->addChild(newMsgItem); @@ -574,27 +741,37 @@ void DBCMainEditor::newSignal() { QTreeWidgetItem *msgItem = nullptr; QTreeWidgetItem *sigItem = nullptr; + QTreeWidgetItem *parentItem = nullptr; msgItem = ui->treeDBC->currentItem(); + parentItem = msgItem; if (!msgItem) return; //nothing selected! int typ = msgItem->data(0, Qt::UserRole).toInt(); - if (typ == 1) return; //can't add signals to a node! - if (typ == 3) + if (typ == DBCItemTypes::NODE) return; //can't add signals to a node! + if (typ == DBCItemTypes::SIG) { sigItem = msgItem; msgItem = msgItem->parent(); + parentItem = msgItem; + //walk up the tree to find the parent msg + while (msgItem && msgItem->data(0, Qt::UserRole).toInt() != DBCItemTypes::MESG) msgItem = msgItem->parent(); + if (!msgItem) return; //something bad happened. abort. } QString idString = msgItem->text(0).split(" ")[0]; int msgID = static_cast(Utility::ParseStringToNum(idString)); DBC_MESSAGE *msg = dbcFile->messageHandler->findMsgByID(msgID); + if (!msg) return; //null pointers are a bummer. Do not follow them. DBC_SIGNAL sig; DBC_SIGNAL *sigPtr; if (sigItem) { - DBC_SIGNAL *oldSig = msg->sigHandler->findSignalByName(sigItem->text(0).split(" ")[0]); + QString txt = sigItem->text(0); + if (txt.startsWith('(')) txt = txt.split(" ")[1]; //if it was a multiplexed signal we need to ignore that part and still grab sig name + else txt = txt.split(" ")[0]; + DBC_SIGNAL *oldSig = msg->sigHandler->findSignalByName(txt); if (oldSig) { - sig = *oldSig; //copy it all over + sig = *oldSig; sig.name = sig.name + QString::number(randGen.bounded(100)); } else @@ -612,16 +789,15 @@ void DBCMainEditor::newSignal() msg->sigHandler->addSignal(sig); sigPtr = msg->sigHandler->findSignalByIdx(msg->sigHandler->getCount() - 1); QTreeWidgetItem *newSigItem = new QTreeWidgetItem(); - QString sigInfo = sig.name; - if (sig.comment.count() > 0) sigInfo.append(" - ").append(sig.comment); + QString sigInfo = createSignalText(&sig); newSigItem->setText(0, sigInfo); if (sig.isMultiplexed) newSigItem->setIcon(0, multiplexedSignalIcon); else if (sig.isMultiplexor) newSigItem->setIcon(0, multiplexorSignalIcon); else newSigItem->setIcon(0, signalIcon); - newSigItem->setData(0, Qt::UserRole, 3); + newSigItem->setData(0, Qt::UserRole, DBCItemTypes::SIG); signalToItem.insert(sigPtr, newSigItem); itemToSignal.insert(newSigItem, sigPtr); - msgItem->addChild(newSigItem); + parentItem->addChild(newSigItem); ui->treeDBC->setCurrentItem(newSigItem); dbcFile->setDirtyFlag(); } @@ -631,7 +807,7 @@ void DBCMainEditor::deleteCurrentTreeItem() { QTreeWidgetItem *currItem = ui->treeDBC->currentItem(); int typ = currItem->data(0, Qt::UserRole).toInt(); - QString idString; + QString idString, columnText; int msgID; DBC_MESSAGE *msg; DBC_NODE *node; @@ -641,8 +817,9 @@ void DBCMainEditor::deleteCurrentTreeItem() switch (typ) { - case 1: //deleting a node cascades deletion down to messages and signals - node = dbcFile->findNodeByName(currItem->text(0)); + case DBCItemTypes::NODE: //deleting a node cascades deletion down to messages and signals + columnText = currItem->text(0); + node = dbcFile->findNodeByNameAndComment(columnText); if (!node) return; for (int x = 0; x < dbcFile->messageHandler->getCount(); x++) { @@ -668,7 +845,7 @@ void DBCMainEditor::deleteCurrentTreeItem() } break; - case 2: //cascades to removing all signals too. + case DBCItemTypes::MESG: //cascades to removing all signals too. idString = currItem->text(0).split(" ")[0]; msgID = static_cast(Utility::ParseStringToNum(idString)); msg = dbcFile->messageHandler->findMsgByID(msgID); @@ -689,7 +866,7 @@ void DBCMainEditor::deleteCurrentTreeItem() } } break; - case 3: //no cascade, just this one signal. + case DBCItemTypes::SIG: //no cascade, just this one signal. confirmDialog = QMessageBox::question(this, "Really?", "Are you sure you want to delete this signal?", QMessageBox::Yes|QMessageBox::No); if (confirmDialog == QMessageBox::Yes) @@ -702,7 +879,7 @@ void DBCMainEditor::deleteCurrentTreeItem() } else { - qDebug() << "WTF, could not find the signal in the map. That should not happen."; + qDebug() << "Could not find the signal in the map. That should not happen."; } } break; @@ -720,7 +897,21 @@ void DBCMainEditor::deleteNode(DBC_NODE *node) int numItems = dbcFile->messageHandler->getCount(); for (int i = numItems - 1; i > -1; i--) { - if (dbcFile->messageHandler->findMsgByIdx(i)->sender == node) deleteMessage(dbcFile->messageHandler->findMsgByIdx(i)); + DBC_MESSAGE *msg = dbcFile->messageHandler->findMsgByIdx(i); + if (msg->sender == node) deleteMessage(dbcFile->messageHandler->findMsgByIdx(i)); + //also, each signal has a receiver field that references the nodes. It was probably stupid to store + //pointers to node structures in signals but that's how it is currently. Need to iterate over all + //signals in all messages and set the receiver field to Vector__XXX if the old receiver node was this one. + else //still check for signals with receiver set to this node + { + DBC_NODE *unset_node = dbcFile->findNodeByName("Vector__XXX"); + int numSigs = msg->sigHandler->getCount(); + for (int j = numSigs - 1; j > -1; j--) + { + DBC_SIGNAL *sig = msg->sigHandler->findSignalByIdx(j); + if (sig->receiver == node) sig->receiver = unset_node; + } + } } nodeToItem.remove(node); @@ -771,6 +962,6 @@ void DBCMainEditor::deleteSignal(DBC_SIGNAL *sig) itemToSignal.remove(currItem); signalToItem.remove(sig); ui->treeDBC->removeItemWidget(currItem, 0); - delete currItem; + //delete currItem; //already removed by above remove call dbcFile->setDirtyFlag(); } diff --git a/dbc/dbcmaineditor.h b/dbc/dbcmaineditor.h index bfc3acd..75780ea 100644 --- a/dbc/dbcmaineditor.h +++ b/dbc/dbcmaineditor.h @@ -10,12 +10,21 @@ #include "dbcsignaleditor.h" #include "dbcmessageeditor.h" #include "dbcnodeeditor.h" +#include "dbcnoderebaseeditor.h" +#include "dbcnodeduplicateeditor.h" #include "utility.h" namespace Ui { class DBCMainEditor; } +enum DBCItemTypes +{ + NODE = 1, + MESG = 2, + SIG = 3 +}; + class DBCMainEditor : public QDialog { Q_OBJECT @@ -32,6 +41,7 @@ public slots: private slots: void onTreeDoubleClicked(const QModelIndex &index); + void onTreeContextMenu(const QPoint & pos); void currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *prev); void onCustomMenuTree(QPoint); void deleteCurrentTreeItem(); @@ -41,9 +51,13 @@ private slots: void handleSearch(); void handleSearchForward(); void handleSearchBackward(); + void newNode(QString nodeName); void newNode(); + void copyMessageToNode(DBC_NODE *node, DBC_MESSAGE *source, uint newMsgId); void newMessage(); - void newSignal(); + void newSignal(); + void onRebaseMessages(); + void onDuplicateNode(); private: Ui::DBCMainEditor *ui; @@ -52,6 +66,8 @@ private: DBCSignalEditor *sigEditor; DBCMessageEditor *msgEditor; DBCNodeEditor *nodeEditor; + DBCNodeRebaseEditor *nodeRebaseEditor; + DBCNodeDuplicateEditor *nodeDuplicateEditor; DBCFile *dbcFile; int fileIdx; QIcon nodeIcon; diff --git a/dbc/dbcnodeduplicateeditor.cpp b/dbc/dbcnodeduplicateeditor.cpp new file mode 100644 index 0000000..7ea5cd1 --- /dev/null +++ b/dbc/dbcnodeduplicateeditor.cpp @@ -0,0 +1,204 @@ +#include "dbcnodeduplicateeditor.h" +#include "ui_dbcnodeduplicateeditor.h" + +#include +#include +#include +#include +#include "helpwindow.h" +#include "utility.h" + +DBCNodeDuplicateEditor::DBCNodeDuplicateEditor(QWidget *parent) : + QDialog(parent), + ui(new Ui::DBCNodeDuplicateEditor) +{ + ui->setupUi(this); + + readSettings(); + + dbcHandler = DBCHandler::getReference(); + dbcNode = nullptr; + + connect(ui->btnDuplicate, &QPushButton::pressed, + [=]() + { + if (dbcNode == nullptr) return; + if (lowestMsgId > 0x1FFFFFFFul) return; + + uint newBase = Utility::ParseStringToNum(ui->lineNewBaseId->text()); + + if(newBase <= 0 || newBase > 0x1FFFFFFFul) + { + QMessageBox::question(this, "Invalid Address", "The new address is outside of the valid range.", + QMessageBox::Ok); + return; + } + + if(newBase == lowestMsgId) + { + QMessageBox::question(this, "Invalid Address", "The new address is the same as the original.", + QMessageBox::Ok); + return; + } + + int32_t rebaseDiff = newBase - lowestMsgId; + + QList messagesForNode = dbcFile->messageHandler->findMsgsByNode(dbcNode); + if(messagesForNode.count() == 0) + { + QMessageBox::question(this, "No Messages", "The node has no messages to duplicate.", + QMessageBox::Ok); + return; + } + + if(ui->lineNodeName->text().isEmpty()) + { + QMessageBox::question(this, "No Name", "The new node needs a name before it can be created.", + QMessageBox::Ok); + return; + } + + QString newNodeName = ui->lineNodeName->text(); + emit createNode(newNodeName); + + DBC_NODE *nodePtr = dbcFile->findNodeByName(newNodeName); + + if(nodePtr == nullptr) + { + QMessageBox::question(this, "Node Invalid", "There was an problem identifying the selected node.", + QMessageBox::Ok); + return; + } + + for (int i = 0; i < messagesForNode.count(); i++) + { + int32_t newMsgId = messagesForNode[i]->ID + rebaseDiff; + + if(newMsgId < 0 || newMsgId > 0x1FFFFFFFl) + { + QMessageBox::question(this, "Invalid Address Range", "The new starting address would cause a message to be outside of the valid address range.", + QMessageBox::Ok); + return; + } + } + + for (int i = 0; i < messagesForNode.count(); i++) + { + int32_t newMsgId = messagesForNode[i]->ID + rebaseDiff; + emit cloneMessageToNode(nodePtr, messagesForNode[i], newMsgId); + } + + dbcFile->setDirtyFlag(); + emit nodeAdded(); + + this->close(); + + }); + + connect(ui->btnCancel, &QPushButton::pressed, + [=]() + { + this->close(); + }); + + installEventFilter(this); +} + +DBCNodeDuplicateEditor::~DBCNodeDuplicateEditor() +{ + removeEventFilter(this); + delete ui; +} + +void DBCNodeDuplicateEditor::closeEvent(QCloseEvent *event) +{ + Q_UNUSED(event); + writeSettings(); +} + +bool DBCNodeDuplicateEditor::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::KeyRelease) { + QKeyEvent *keyEvent = static_cast(event); + switch (keyEvent->key()) + { + case Qt::Key_F1: + HelpWindow::getRef()->showHelp("nodeeditor.md"); + break; + } + return true; + } else { + // standard event processing + return QObject::eventFilter(obj, event); + } + return false; +} + +void DBCNodeDuplicateEditor::setFileIdx(int idx) +{ + if (idx < 0 || idx > dbcHandler->getFileCount() - 1) return; + dbcFile = dbcHandler->getFileByIdx(idx); +} + +void DBCNodeDuplicateEditor::readSettings() +{ + QSettings settings; + if (settings.value("Main/SaveRestorePositions", false).toBool()) + { + resize(settings.value("DBCNodeDuplicateEditor/WindowSize", QSize(312, 128)).toSize()); + move(Utility::constrainedWindowPos(settings.value("DBCNodeDuplicateEditor/WindowPos", QPoint(100, 100)).toPoint())); + } +} + +void DBCNodeDuplicateEditor::writeSettings() +{ + QSettings settings; + + if (settings.value("Main/SaveRestorePositions", false).toBool()) + { + settings.setValue("DBCNodeDuplicateEditor/WindowSize", size()); + settings.setValue("DBCNodeDuplicateEditor/WindowPos", pos()); + } +} + + +void DBCNodeDuplicateEditor::setNodeRef(DBC_NODE *node) +{ + dbcNode = node; +} + +void DBCNodeDuplicateEditor::showEvent(QShowEvent* event) +{ + QDialog::showEvent(event); + + refreshView(); +} + +bool DBCNodeDuplicateEditor::refreshView() +{ + ui->lineNewBaseId->setText(""); + + if(dbcNode) + { + QList messagesForNode = dbcFile->messageHandler->findMsgsByNode(dbcNode); + lowestMsgId = 0xFFFFFFFF; + + if(messagesForNode.count() == 0) + { + return false; + } + + for (int i=0; iID < lowestMsgId) + lowestMsgId = messagesForNode[i]->ID; + } + + ui->lineOriginalBaseId->setText(Utility::formatCANID(lowestMsgId & 0x1FFFFFFFul)); + ui->lineNodeName->setText(dbcNode->name + QString("_Copy")); + + return true; + } + + return false; +} diff --git a/dbc/dbcnodeduplicateeditor.h b/dbc/dbcnodeduplicateeditor.h new file mode 100644 index 0000000..57610bd --- /dev/null +++ b/dbc/dbcnodeduplicateeditor.h @@ -0,0 +1,45 @@ +#ifndef DBCNODEDUPLICATEEDITOR_H +#define DBCNODEDUPLICATEEDITOR_H + +#include +#include "dbc_classes.h" +#include "dbchandler.h" + +namespace Ui { +class DBCNodeDuplicateEditor; +} + +class DBCNodeDuplicateEditor : public QDialog +{ + Q_OBJECT + +public: + explicit DBCNodeDuplicateEditor(QWidget *parent = nullptr); + ~DBCNodeDuplicateEditor(); + void showEvent(QShowEvent*); + void setNodeRef(DBC_NODE *node); + void setFileIdx(int idx); + bool refreshView(); + +signals: + void updatedTreeInfo(DBC_MESSAGE *msg); + void createNode(QString nodeName); + void cloneMessageToNode(DBC_NODE *parentNode, DBC_MESSAGE *source, uint newMsgId); + void nodeAdded(); + +private: + Ui::DBCNodeDuplicateEditor *ui; + + DBCHandler *dbcHandler; + DBC_NODE *dbcNode; + DBCFile *dbcFile; + + void closeEvent(QCloseEvent *event); + bool eventFilter(QObject *obj, QEvent *event); + void readSettings(); + void writeSettings(); + + uint lowestMsgId; +}; + +#endif // DBCNODEDUPLICATEEDITOR_H diff --git a/dbc/dbcnodeeditor.cpp b/dbc/dbcnodeeditor.cpp index e8363a3..f553fb4 100644 --- a/dbc/dbcnodeeditor.cpp +++ b/dbc/dbcnodeeditor.cpp @@ -111,8 +111,11 @@ void DBCNodeEditor::showEvent(QShowEvent* event) void DBCNodeEditor::refreshView() { - ui->lineComment->setText(dbcNode->comment); - ui->lineMsgName->setText(dbcNode->name); + if(dbcNode) + { + ui->lineComment->setText(dbcNode->comment); + ui->lineMsgName->setText(dbcNode->name); + } //generateSampleText(); } diff --git a/dbc/dbcnoderebaseeditor.cpp b/dbc/dbcnoderebaseeditor.cpp new file mode 100644 index 0000000..f99e1df --- /dev/null +++ b/dbc/dbcnoderebaseeditor.cpp @@ -0,0 +1,195 @@ +#include "dbcnoderebaseeditor.h" +#include "ui_dbcnoderebaseeditor.h" + +#include +#include +#include +#include +#include "helpwindow.h" +#include "utility.h" + +DBCNodeRebaseEditor::DBCNodeRebaseEditor(QWidget *parent) : + QDialog(parent), + ui(new Ui::DBCNodeRebaseEditor) +{ + ui->setupUi(this); + + readSettings(); + + dbcHandler = DBCHandler::getReference(); + dbcNode = nullptr; + + connect(ui->btnDoRebase, &QPushButton::pressed, + [=]() + { + if (dbcNode == nullptr) + { + QMessageBox::question(this, "Node Invalid", "There was an problem identifying the selected node.", + QMessageBox::Ok); + return; + } + + if (lowestMsgId > 0x1FFFFFFFul) + { + QMessageBox::question(this, "No Valid Messages", "The node has no valid messages to change.", + QMessageBox::Ok); + return; + } + + uint newBase = Utility::ParseStringToNum(ui->lineNewBaseId->text()); + + if(newBase <= 0 || newBase > 0x1FFFFFFFul) + { + QMessageBox::question(this, "Invalid Address", "The new address is outside of the valid range.", + QMessageBox::Ok); + return; + } + + if(newBase == lowestMsgId) + { + QMessageBox::question(this, "Invalid Address", "The new address is the same as the original.", + QMessageBox::Ok); + return; + } + + uint rebaseDiff = newBase - lowestMsgId; + + QList messagesForNode = dbcFile->messageHandler->findMsgsByNode(dbcNode); + if(messagesForNode.count() == 0) + { + QMessageBox::question(this, "No Messages", "The node has no messages to change.", + QMessageBox::Ok); + return; + } + + for (int i = 0; i < messagesForNode.count(); i++) + { + int32_t newMsgId = messagesForNode[i]->ID + rebaseDiff; + + if(newMsgId < 0 || newMsgId > 0x1FFFFFFFl) + { + QMessageBox::question(this, "Invalid Address Range", "The new starting address would cause a message to be outside of the valid address range.", + QMessageBox::Ok); + return; + } + } + + for (int i = 0; i < messagesForNode.count(); i++) + { + messagesForNode[i]->ID += rebaseDiff; + emit updatedTreeInfo(messagesForNode[i]); + } + + dbcFile->setDirtyFlag(); + + this->close(); + + }); + + connect(ui->btnCancel, &QPushButton::pressed, + [=]() + { + this->close(); + }); + + installEventFilter(this); +} + +DBCNodeRebaseEditor::~DBCNodeRebaseEditor() +{ + removeEventFilter(this); + delete ui; +} + +void DBCNodeRebaseEditor::closeEvent(QCloseEvent *event) +{ + Q_UNUSED(event); + writeSettings(); +} + +bool DBCNodeRebaseEditor::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::KeyRelease) { + QKeyEvent *keyEvent = static_cast(event); + switch (keyEvent->key()) + { + case Qt::Key_F1: + HelpWindow::getRef()->showHelp("nodeeditor.md"); + break; + } + return true; + } else { + // standard event processing + return QObject::eventFilter(obj, event); + } + return false; +} + +void DBCNodeRebaseEditor::setFileIdx(int idx) +{ + if (idx < 0 || idx > dbcHandler->getFileCount() - 1) return; + dbcFile = dbcHandler->getFileByIdx(idx); +} + +void DBCNodeRebaseEditor::readSettings() +{ + QSettings settings; + if (settings.value("Main/SaveRestorePositions", false).toBool()) + { + resize(settings.value("DBCNodeRebaseEditor/WindowSize", QSize(312, 128)).toSize()); + move(Utility::constrainedWindowPos(settings.value("DBCNodeRebaseEditor/WindowPos", QPoint(100, 100)).toPoint())); + } +} + +void DBCNodeRebaseEditor::writeSettings() +{ + QSettings settings; + + if (settings.value("Main/SaveRestorePositions", false).toBool()) + { + settings.setValue("DBCNodeRebaseEditor/WindowSize", size()); + settings.setValue("DBCNodeRebaseEditor/WindowPos", pos()); + } +} + + +void DBCNodeRebaseEditor::setNodeRef(DBC_NODE *node) +{ + dbcNode = node; +} + +void DBCNodeRebaseEditor::showEvent(QShowEvent* event) +{ + QDialog::showEvent(event); + + refreshView(); +} + +bool DBCNodeRebaseEditor::refreshView() +{ + ui->lineNewBaseId->setText(""); + + if(dbcNode) + { + QList messagesForNode = dbcFile->messageHandler->findMsgsByNode(dbcNode); + lowestMsgId = 0xFFFFFFFF; + + if(messagesForNode.count() == 0) + { + return false; + } + + for (int i=0; iID < lowestMsgId) + lowestMsgId = messagesForNode[i]->ID; + } + + ui->lineOriginalBaseId->setText(Utility::formatCANID(lowestMsgId & 0x1FFFFFFFul)); + ui->lineNodeName->setText(dbcNode->name); + + return true; + } + + return false; +} diff --git a/dbc/dbcnoderebaseeditor.h b/dbc/dbcnoderebaseeditor.h new file mode 100644 index 0000000..26f1645 --- /dev/null +++ b/dbc/dbcnoderebaseeditor.h @@ -0,0 +1,42 @@ +#ifndef DBCNODEREBASEEDITOR_H +#define DBCNODEREBASEEDITOR_H + +#include +#include "dbc_classes.h" +#include "dbchandler.h" + +namespace Ui { +class DBCNodeRebaseEditor; +} + +class DBCNodeRebaseEditor : public QDialog +{ + Q_OBJECT + +public: + explicit DBCNodeRebaseEditor(QWidget *parent = nullptr); + ~DBCNodeRebaseEditor(); + void showEvent(QShowEvent*); + void setNodeRef(DBC_NODE *node); + void setFileIdx(int idx); + bool refreshView(); + +signals: + void updatedTreeInfo(DBC_MESSAGE *msg); + +private: + Ui::DBCNodeRebaseEditor *ui; + + DBCHandler *dbcHandler; + DBC_NODE *dbcNode; + DBCFile *dbcFile; + + void closeEvent(QCloseEvent *event); + bool eventFilter(QObject *obj, QEvent *event); + void readSettings(); + void writeSettings(); + + uint lowestMsgId; +}; + +#endif // DBCNODEREBASEEDITOR_H diff --git a/dbc/dbcsignaleditor.cpp b/dbc/dbcsignaleditor.cpp index b81c11c..20089fd 100644 --- a/dbc/dbcsignaleditor.cpp +++ b/dbc/dbcsignaleditor.cpp @@ -37,7 +37,9 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) : ui->bitfield->setMode(GridMode::SIGNAL_VIEW); - connect(ui->bitfield, SIGNAL(gridClicked(int,int)), this, SLOT(bitfieldClicked(int,int))); + connect(ui->bitfield, SIGNAL(gridClicked(int)), this, SLOT(bitfieldLeftClicked(int))); + connect(ui->bitfield, SIGNAL(gridRightClicked(int)), this, SLOT(bitfieldRightClicked(int))); + connect(ui->valuesTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onCustomMenuValues(QPoint))); ui->valuesTable->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->valuesTable, SIGNAL(cellChanged(int,int)), this, SLOT(onValuesCellChanged(int,int))); @@ -47,18 +49,29 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) : [=]() { if (currentSignal == nullptr) return; - if (currentSignal->intelByteOrder != ui->cbIntelFormat->isChecked()) dbcFile->setDirtyFlag(); - currentSignal->intelByteOrder = ui->cbIntelFormat->isChecked(); - fillSignalForm(currentSignal); + if (currentSignal->intelByteOrder != ui->cbIntelFormat->isChecked()) + { + dbcFile->setDirtyFlag(); + pushToUndoBuffer(); + currentSignal->intelByteOrder = ui->cbIntelFormat->isChecked(); + //fillSignalForm(currentSignal); + refreshBitGrid(); + } }); connect(ui->comboReceiver, &QComboBox::currentTextChanged, [=]() { if (currentSignal == nullptr) return; + if (inhibitMsgProc) return; + DBC_NODE *node = dbcFile->findNodeByName(ui->comboReceiver->currentText()); - if (currentSignal->receiver != node) dbcFile->setDirtyFlag(); - currentSignal->receiver = node; + if (currentSignal->receiver != node) + { + dbcFile->setDirtyFlag(); + pushToUndoBuffer(); + currentSignal->receiver = node; + } }); connect(ui->comboType, &QComboBox::currentTextChanged, [=]() @@ -67,27 +80,66 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) : switch (ui->comboType->currentIndex()) { case 0: - currentSignal->valType = UNSIGNED_INT; + if (currentSignal->valType != UNSIGNED_INT) + { + pushToUndoBuffer(); + currentSignal->valType = UNSIGNED_INT; + dbcFile->setDirtyFlag(); + fillSignalForm(currentSignal); + } break; case 1: - currentSignal->valType = SIGNED_INT; + if (currentSignal->valType != SIGNED_INT) + { + pushToUndoBuffer(); + currentSignal->valType = SIGNED_INT; + dbcFile->setDirtyFlag(); + fillSignalForm(currentSignal); + } break; case 2: - currentSignal->valType = SP_FLOAT; - if (currentSignal->startBit > 39) currentSignal->startBit = 39; - currentSignal->signalSize = 32; + if (currentSignal->valType != SP_FLOAT) + { + pushToUndoBuffer(); + currentSignal->valType = SP_FLOAT; + dbcFile->setDirtyFlag(); + if (dbcMessage) //if we have a good msg reference we can use it to get the # of bytes expected. + { + int maxBit = ((dbcMessage->len * 8) - 32 + 7); + if (maxBit < 0) maxBit = 0; + if (currentSignal->startBit > maxBit) currentSignal->startBit = maxBit; + } + else if (currentSignal->startBit > 39) currentSignal->startBit = 39; + currentSignal->signalSize = 32; + fillSignalForm(currentSignal); + } break; case 3: - currentSignal->valType = DP_FLOAT; - currentSignal->startBit = 7; //has to be! - currentSignal->signalSize = 64; + if (currentSignal->valType != DP_FLOAT) + { + pushToUndoBuffer(); + currentSignal->valType = DP_FLOAT; + dbcFile->setDirtyFlag(); + if (dbcMessage) + { + int maxBit = ((dbcMessage->len * 8) - 64 + 7); + if (currentSignal->startBit > maxBit) currentSignal->startBit = maxBit; + } + else currentSignal->startBit = 7; //has to be! + currentSignal->signalSize = 64; + fillSignalForm(currentSignal); + } break; case 4: - currentSignal->valType = STRING; - break; + if (currentSignal->valType != STRING) + { + pushToUndoBuffer(); + currentSignal->valType = STRING; + dbcFile->setDirtyFlag(); + fillSignalForm(currentSignal); + } + break; } - dbcFile->setDirtyFlag(); - fillSignalForm(currentSignal); }); connect(ui->txtBias, &QLineEdit::editingFinished, [=]() @@ -98,8 +150,12 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) : temp = ui->txtBias->text().toDouble(&result); if (result) { - if (currentSignal->bias != temp) dbcFile->setDirtyFlag(); - currentSignal->bias = temp; + if (currentSignal->bias != temp) + { + pushToUndoBuffer(); + dbcFile->setDirtyFlag(); + currentSignal->bias = temp; + } } }); @@ -112,8 +168,12 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) : temp = ui->txtMaxVal->text().toDouble(&result); if (result) { - if (currentSignal->max != temp) dbcFile->setDirtyFlag(); - currentSignal->max = temp; + if (currentSignal->max != temp) + { + pushToUndoBuffer(); + dbcFile->setDirtyFlag(); + currentSignal->max = temp; + } } }); @@ -126,10 +186,15 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) : temp = ui->txtMinVal->text().toDouble(&result); if (result) { - if (currentSignal->min != temp) dbcFile->setDirtyFlag(); - currentSignal->min = temp; + if (currentSignal->min != temp) + { + pushToUndoBuffer(); + dbcFile->setDirtyFlag(); + currentSignal->min = temp; + } } }); + connect(ui->txtScale, &QLineEdit::editingFinished, [=]() { @@ -139,26 +204,40 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) : temp = ui->txtScale->text().toDouble(&result); if (result) { - if (currentSignal->factor != temp) dbcFile->setDirtyFlag(); - currentSignal->factor = temp; + if (currentSignal->factor != temp) + { + pushToUndoBuffer(); + dbcFile->setDirtyFlag(); + currentSignal->factor = temp; + } } }); + connect(ui->txtComment, &QLineEdit::editingFinished, [=]() { if (currentSignal == nullptr) return; - if (currentSignal->comment != ui->txtComment->text().simplified().replace(' ','_')) dbcFile->setDirtyFlag(); - currentSignal->comment = ui->txtComment->text().simplified().replace(' ', '_'); - emit updatedTreeInfo(currentSignal); + if (currentSignal->comment != ui->txtComment->text().simplified().replace(' ','_')) + { + pushToUndoBuffer(); + dbcFile->setDirtyFlag(); + currentSignal->comment = ui->txtComment->text().simplified().replace(' ', '_'); + emit updatedTreeInfo(currentSignal); + } }); connect(ui->txtUnitName, &QLineEdit::editingFinished, [=]() { if (currentSignal == nullptr) return; - if (currentSignal->unitName != ui->txtUnitName->text().simplified().replace(' ','_')) dbcFile->setDirtyFlag(); - currentSignal->unitName = ui->txtUnitName->text().simplified().replace(' ', '_'); + if (currentSignal->unitName != ui->txtUnitName->text().simplified().replace(' ','_')) + { + pushToUndoBuffer(); + dbcFile->setDirtyFlag(); + currentSignal->unitName = ui->txtUnitName->text().simplified().replace(' ', '_'); + } }); + connect(ui->txtBitLength, &QLineEdit::textChanged, [=]() { @@ -166,21 +245,40 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) : int temp; temp = Utility::ParseStringToNum(ui->txtBitLength->text()); if (temp < 1) return; - if (temp > 64) return; - if (currentSignal->signalSize != temp) dbcFile->setDirtyFlag(); - if (currentSignal->valType != SP_FLOAT && currentSignal->valType != DP_FLOAT) + if (dbcMessage) + { + if (temp > (int)(dbcMessage->len * 8)) return; + } + else if (temp > 64) return; + + if (currentSignal->valType == SP_FLOAT) temp = 32; + if (currentSignal->valType == DP_FLOAT) temp = 64; + + if (currentSignal->signalSize != temp) + { + pushToUndoBuffer(); + dbcFile->setDirtyFlag(); currentSignal->signalSize = temp; - fillSignalForm(currentSignal); + //fillSignalForm(currentSignal); + refreshBitGrid(); + } }); + connect(ui->txtName, &QLineEdit::editingFinished, [=]() { if (currentSignal == nullptr) return; QString tempNameStr = ui->txtName->text().simplified().replace(' ', '_'); - if (currentSignal->name != tempNameStr) dbcFile->setDirtyFlag(); - if (tempNameStr.length() > 0) currentSignal->name = tempNameStr; - //need to update the tree too. - emit updatedTreeInfo(currentSignal); + if (tempNameStr.length() == 0) return; //can't do that! + if (currentSignal->name != tempNameStr) + { + pushToUndoBuffer(); + dbcFile->setDirtyFlag(); + currentSignal->name = tempNameStr; + refreshBitGrid(); + //need to update the tree too. + emit updatedTreeInfo(currentSignal); + } }); connect(ui->txtMultiplexLow, &QLineEdit::editingFinished, @@ -189,85 +287,113 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) : if (currentSignal == nullptr) return; int temp; temp = Utility::ParseStringToNum(ui->txtMultiplexLow->text()); - if (currentSignal->multiplexLowValue != temp) dbcFile->setDirtyFlag(); - //TODO: could look up the multiplexor and ensure that the value is within a range that the multiplexor could return - currentSignal->multiplexLowValue = temp; + if (currentSignal->multiplexLowValue != temp) + { + pushToUndoBuffer(); + dbcFile->setDirtyFlag(); + //TODO: could look up the multiplexor and ensure that the value is within a range that the multiplexor could return + currentSignal->multiplexLowValue = temp; + } }); connect(ui->txtMultiplexHigh, &QLineEdit::editingFinished, [=]() { - if (currentSignal == nullptr) return; + if (!currentSignal) return; int temp; temp = Utility::ParseStringToNum(ui->txtMultiplexHigh->text()); - if (currentSignal->multiplexHighValue != temp) dbcFile->setDirtyFlag(); - //TODO: could look up the multiplexor and ensure that the value is within a range that the multiplexor could return - currentSignal->multiplexHighValue = temp; + if (currentSignal->multiplexHighValue != temp) + { + pushToUndoBuffer(); + dbcFile->setDirtyFlag(); + //TODO: could look up the multiplexor and ensure that the value is within a range that the multiplexor could return + currentSignal->multiplexHighValue = temp; + } }); connect(ui->rbExtended, &QRadioButton::toggled, [=](bool state) { - if (state && currentSignal) //signal is now set as an extended multiplex/multiplexor + if (!currentSignal) return; + if (!state) return; //we only need to handle the case where it is true + + //only do anything if this is different from the current state. It should be because we're in a toggle event but let's be sure + if (!currentSignal->isMultiplexed || !currentSignal->isMultiplexor) { + pushToUndoBuffer(); + dbcFile->setDirtyFlag(); currentSignal->isMultiplexed = true; currentSignal->isMultiplexor = true; //an extended multi signal cannot be the root multiplexor for a message so make sure to remove it if it was. if (dbcMessage->multiplexorSignal == currentSignal) dbcMessage->multiplexorSignal = nullptr; + ui->txtMultiplexLow->setEnabled(currentSignal->isMultiplexed); + ui->txtMultiplexHigh->setEnabled(currentSignal->isMultiplexed); + ui->cbMultiplexParent->setEnabled(currentSignal->isMultiplexed); + fillSignalForm(currentSignal); } - ui->txtMultiplexLow->setEnabled(currentSignal->isMultiplexed); - ui->txtMultiplexHigh->setEnabled(currentSignal->isMultiplexed); - ui->cbMultiplexParent->setEnabled(currentSignal->isMultiplexed); - dbcFile->setDirtyFlag(); }); connect(ui->rbMultiplexed, &QRadioButton::toggled, [=](bool state) { - if (state && currentSignal) //signal is now set as a multiplexed signal + if (!currentSignal) return; + if (!state) return; //we only need to handle the case where it is true + + //only do anything if this is different from the current state. It should be because we're in a toggle event but let's be sure + if (!currentSignal->isMultiplexed || currentSignal->isMultiplexor) { + pushToUndoBuffer(); + dbcFile->setDirtyFlag(); currentSignal->isMultiplexed = true; currentSignal->isMultiplexor = false; //if the set multiplexor for the message was this signal then clear it if (dbcMessage->multiplexorSignal == currentSignal) dbcMessage->multiplexorSignal = nullptr; + ui->txtMultiplexLow->setEnabled(currentSignal->isMultiplexed); + ui->txtMultiplexHigh->setEnabled(currentSignal->isMultiplexed); + ui->cbMultiplexParent->setEnabled(currentSignal->isMultiplexed); + fillSignalForm(currentSignal); } - ui->txtMultiplexLow->setEnabled(currentSignal->isMultiplexed); - ui->txtMultiplexHigh->setEnabled(currentSignal->isMultiplexed); - ui->cbMultiplexParent->setEnabled(currentSignal->isMultiplexed); - dbcFile->setDirtyFlag(); }); connect(ui->rbMultiplexor, &QRadioButton::toggled, [=](bool state) { - if (state && currentSignal) //signal is now set as a multiplexed signal + if (!currentSignal) return; + if (!state) return; //we only need to handle the case where it is true + + if (currentSignal->isMultiplexed || !currentSignal->isMultiplexor) { - //don't allow this signal to be a multiplexor if there is already one for this message. - //if (dbcMessage->multiplexorSignal != currentSignal && dbcMessage->multiplexorSignal != nullptr) return; //I spoke too soon above... + pushToUndoBuffer(); + dbcFile->setDirtyFlag(); currentSignal->isMultiplexed = false; currentSignal->isMultiplexor = true; //we just set that this is the multiplexor so update the message to show that as well. dbcMessage->multiplexorSignal = currentSignal; + ui->txtMultiplexLow->setEnabled(currentSignal->isMultiplexed); + ui->txtMultiplexHigh->setEnabled(currentSignal->isMultiplexed); + ui->cbMultiplexParent->setEnabled(currentSignal->isMultiplexed); + fillSignalForm(currentSignal); } - ui->txtMultiplexLow->setEnabled(currentSignal->isMultiplexed); - ui->txtMultiplexHigh->setEnabled(currentSignal->isMultiplexed); - ui->cbMultiplexParent->setEnabled(currentSignal->isMultiplexed); - dbcFile->setDirtyFlag(); }); connect(ui->rbNotMulti, &QRadioButton::toggled, [=](bool state) { - if (state && currentSignal) //signal is now set as a multiplexed signal + if (!currentSignal) return; + if (!state) return; //we only need to handle the case where it is true + + if (currentSignal->isMultiplexed || currentSignal->isMultiplexor) { + pushToUndoBuffer(); + dbcFile->setDirtyFlag(); currentSignal->isMultiplexed = false; currentSignal->isMultiplexor = false; if (dbcMessage->multiplexorSignal == currentSignal) dbcMessage->multiplexorSignal = nullptr; + ui->txtMultiplexLow->setEnabled(currentSignal->isMultiplexed); + ui->txtMultiplexHigh->setEnabled(currentSignal->isMultiplexed); + ui->cbMultiplexParent->setEnabled(currentSignal->isMultiplexed); + fillSignalForm(currentSignal); } - ui->txtMultiplexLow->setEnabled(currentSignal->isMultiplexed); - ui->txtMultiplexHigh->setEnabled(currentSignal->isMultiplexed); - ui->cbMultiplexParent->setEnabled(currentSignal->isMultiplexed); - dbcFile->setDirtyFlag(); }); connect(ui->cbMultiplexParent, &QComboBox::textActivated, @@ -275,17 +401,20 @@ DBCSignalEditor::DBCSignalEditor(QWidget *parent) : { if (currentSignal == nullptr) return; if (inhibitMsgProc) return; + //qDebug() << "Curr text: :" << ui->cbMultiplexParent->currentText(); //try to look up the signal that we're set to now, remove this signal from existing children list //add it to this one, update this signal's parent multiplexor DBC_SIGNAL *newSig = dbcMessage->sigHandler->findSignalByName(ui->cbMultiplexParent->currentText()); DBC_SIGNAL *oldParent = currentSignal->multiplexParent; - if (newSig) + if (newSig && oldParent && (newSig != oldParent)) { + pushToUndoBuffer(); + dbcFile->setDirtyFlag(); oldParent->multiplexedChildren.removeOne(currentSignal); currentSignal->multiplexParent = newSig; newSig->multiplexedChildren.append(currentSignal); - dbcFile->setDirtyFlag(); + refreshBitGrid(); emit updatedTreeInfo(currentSignal); } }); @@ -314,6 +443,12 @@ bool DBCSignalEditor::eventFilter(QObject *obj, QEvent *event) case Qt::Key_F1: HelpWindow::getRef()->showHelp("signaleditor.md"); break; + case Qt::Key_Z: + if (keyEvent->modifiers() == Qt::ControlModifier) + { + popFromUndoBuffer(); + } + break; } return true; } else { @@ -373,18 +508,6 @@ void DBCSignalEditor::showEvent(QShowEvent* event) fillSignalForm(currentSignal); fillValueTable(currentSignal); - - ui->bitfield->clearSignalNames(); - for (int x = 0; x < dbcMessage->sigHandler->getCount(); x++) - { - DBC_SIGNAL *sig = dbcMessage->sigHandler->findSignalByIdx(x); - //only set a signal name for signals which match multiplexparent with our currentsignal - if (!sig->multiplexParent || ((sig->multiplexParent == currentSignal->multiplexParent) && (sig->multiplexHighValue == currentSignal->multiplexHighValue)) ) - { - ui->bitfield->setSignalNames(x, sig->name); - qDebug() << sig->name << sig->multiplexParent; - } - } } void DBCSignalEditor::refreshView() @@ -448,12 +571,14 @@ void DBCSignalEditor::deleteCurrentValue() /* WARNING: fillSignalForm can be called recursively since it is in the listener of cbIntelFormat */ void DBCSignalEditor::fillSignalForm(DBC_SIGNAL *sig) { - unsigned char bitpattern[8]; + //sanity checks + if (!dbcMessage) return; + if (!dbcMessage->sigHandler) return; inhibitMsgProc = true; if (sig == nullptr) { - ui->groupBox->setEnabled(false); + //ui->groupBox->setEnabled(false); ui->txtName->setText(""); ui->txtBias->setText(""); ui->txtBitLength->setText(""); @@ -467,9 +592,6 @@ void DBCSignalEditor::fillSignalForm(DBC_SIGNAL *sig) ui->rbMultiplexed->setChecked(false); ui->rbMultiplexor->setChecked(false); ui->rbNotMulti->setChecked(true); - memset(bitpattern, 0, 8); //clear it out - ui->bitfield->setReference(bitpattern, false); - ui->bitfield->updateData(bitpattern, true); ui->comboReceiver->setCurrentIndex(0); ui->comboType->setCurrentIndex(0); inhibitMsgProc = false; @@ -477,9 +599,8 @@ void DBCSignalEditor::fillSignalForm(DBC_SIGNAL *sig) } /* we have a signal */ - ui->groupBox->setEnabled(true); + //ui->groupBox->setEnabled(true); - generateUsedBits(); ui->txtName->setText(sig->name); ui->txtBias->setText(QString::number(sig->bias)); ui->txtBitLength->setText(QString::number(sig->signalSize)); @@ -513,7 +634,9 @@ void DBCSignalEditor::fillSignalForm(DBC_SIGNAL *sig) DBC_SIGNAL *sig_iter = dbcMessage->sigHandler->findSignalByIdx(i); if (sig_iter && sig_iter->isMultiplexor && (sig_iter != sig)) { - ui->cbMultiplexParent->addItem(sig_iter->name); + //only add this entry if there are no other entries with that name yet + if (ui->cbMultiplexParent->findText(sig_iter->name) == -1) + ui->cbMultiplexParent->addItem(sig_iter->name); if (sig->multiplexParent == sig_iter) ui->cbMultiplexParent->setCurrentIndex(ui->cbMultiplexParent->count() - 1); } } @@ -522,42 +645,6 @@ void DBCSignalEditor::fillSignalForm(DBC_SIGNAL *sig) ui->txtMultiplexHigh->setEnabled(sig->isMultiplexed); ui->cbMultiplexParent->setEnabled(sig->isMultiplexed); - memset(bitpattern, 0, 8); //clear it out first. - - int startBit, endBit; - - startBit = sig->startBit; - - bitpattern[startBit / 8] |= 1 << (startBit % 8); //make the start bit a different color to set it apart - ui->bitfield->setReference(bitpattern, false); - - if (sig->intelByteOrder) - { - endBit = startBit + sig->signalSize - 1; - if (startBit < 0) startBit = 0; - if (endBit > 63) endBit = 63; - for (int y = startBit; y <= endBit; y++) - { - int byt = y / 8; - bitpattern[byt] |= 1 << (y % 8); - } - } - else //big endian / motorola format - { - //much more irritating than the intel version... - int size = sig->signalSize; - while (size > 0) - { - int byt = startBit / 8; - bitpattern[byt] |= 1 << (startBit % 8); - size--; - if ((startBit % 8) == 0) startBit += 15; - else startBit--; - if (startBit > 63) startBit = 63; - } - } - - ui->bitfield->updateData(bitpattern, true); ui->cbIntelFormat->setChecked(sig->intelByteOrder); switch (sig->valType) @@ -588,9 +675,71 @@ void DBCSignalEditor::fillSignalForm(DBC_SIGNAL *sig) } } + refreshBitGrid(); + inhibitMsgProc = false; } +void DBCSignalEditor::refreshBitGrid() +{ + unsigned char bitpattern[64]; + + memset(bitpattern, 0, 64); //clear it out + ui->bitfield->setReference(bitpattern, false); + ui->bitfield->updateData(bitpattern, true); + + ui->bitfield->clearSignalNames(); + for (int x = 0; x < dbcMessage->sigHandler->getCount(); x++) + { + DBC_SIGNAL *sig = dbcMessage->sigHandler->findSignalByIdx(x); + //only set a signal name for signals which match multiplexparent with our currentsignal + if (!sig->multiplexParent || ((sig->multiplexParent == currentSignal->multiplexParent) && (sig->multiplexHighValue == currentSignal->multiplexHighValue)) ) + { + ui->bitfield->setSignalNames(x, sig->name); + //qDebug() << sig->name << sig->multiplexParent; + } + } + + generateUsedBits(); + memset(bitpattern, 0, 64); //clear it out first. + + int startBit, endBit; + + startBit = currentSignal->startBit; + + bitpattern[startBit / 8] |= 1 << (startBit % 8); //make the start bit a different color to set it apart + ui->bitfield->setReference(bitpattern, false); + + if (currentSignal->intelByteOrder) + { + endBit = startBit + currentSignal->signalSize - 1; + if (startBit < 0) startBit = 0; + if (endBit > 511) endBit = 511; + for (int y = startBit; y <= endBit; y++) + { + int byt = y / 8; + bitpattern[byt] |= 1 << (y % 8); + } + } + else //big endian / motorola format + { + //much more irritating than the intel version... + int size = currentSignal->signalSize; + while (size > 0) + { + int byt = startBit / 8; + bitpattern[byt] |= 1 << (startBit % 8); + size--; + if ((startBit % 8) == 0) startBit += 15; + else startBit--; + if (startBit > 511) startBit = 511; + } + } + + ui->bitfield->updateData(bitpattern, true); + +} + /* fillValueTable also handles "enabled" state */ void DBCSignalEditor::fillValueTable(DBC_SIGNAL *sig) { @@ -625,26 +774,68 @@ void DBCSignalEditor::fillValueTable(DBC_SIGNAL *sig) inhibitCellChanged = false; } -void DBCSignalEditor::bitfieldClicked(int x, int y) +//Left clicking the grid sets the "starting bit" for the current signal +void DBCSignalEditor::bitfieldLeftClicked(int bit) { - int bit = (7 - x) + (y * 8); if (currentSignal == nullptr) return; + + pushToUndoBuffer(); currentSignal->startBit = bit; if (currentSignal->valType == SP_FLOAT) { - if (currentSignal->startBit > 31) currentSignal->startBit = 39; + if (dbcMessage) + { + int maxBit = ((dbcMessage->len * 8) - 32 + 7); + if (maxBit < 0) maxBit = 0; + if (currentSignal->startBit > maxBit) currentSignal->startBit = maxBit; + } + else if (currentSignal->startBit > 31) currentSignal->startBit = 39; } if (currentSignal->valType == DP_FLOAT) - currentSignal->startBit = 7; + { + if (dbcMessage) + { + int maxBit = ((dbcMessage->len * 8) - 64 + 7); + if (maxBit < 0) maxBit = 0; + if (currentSignal->startBit > maxBit) currentSignal->startBit = maxBit; + } + else currentSignal->startBit = 7; + } fillSignalForm(currentSignal); } +//Right clicking the grid starts editing on whichever signal currently "owns" that bit. +//If there is no other signal then nothing happens (right now). +//Would be possible to create a new signal in that case +void DBCSignalEditor::bitfieldRightClicked(int bit) +{ + //will return -1 if there is no signal there. Otherwise, returns signal number + //which is quite luckily also the index into the signal handler table + int sigNum = ui->bitfield->getUsedSignalNum(bit); + if (sigNum < 0) return; + + pushToUndoBuffer(); // undo to resume editing the previous signal + + currentSignal = dbcMessage->sigHandler->findSignalByIdx(sigNum); + + if (currentSignal) + { + fillSignalForm(currentSignal); + fillValueTable(currentSignal); + } +} + + void DBCSignalEditor::generateUsedBits() { - uint8_t usedBits[8] = {0,0,0,0,0,0,0,0}; + uint8_t usedBits[64]; int startBit, endBit; + memset(usedBits, 0, 64); + + if (!dbcMessage || !dbcMessage->sigHandler) return; + for (int x = 0; x < dbcMessage->sigHandler->getCount(); x++) { DBC_SIGNAL *sig = dbcMessage->sigHandler->findSignalByIdx(x); @@ -663,7 +854,8 @@ void DBCSignalEditor::generateUsedBits() { endBit = startBit + sig->signalSize - 1; if (startBit < 0) startBit = 0; - if (endBit > 63) endBit = 63; + int maxBit = (dbcMessage->len * 8) - 1; + if (endBit > maxBit) endBit = maxBit; for (int y = startBit; y <= endBit; y++) { int byt = y / 8; @@ -683,9 +875,41 @@ void DBCSignalEditor::generateUsedBits() size--; if ((startBit % 8) == 0) startBit += 15; else startBit--; - if (startBit > 63) startBit = 63; + int maxBit = (dbcMessage->len * 8) - 1; + if (startBit > maxBit) startBit = maxBit; } } } ui->bitfield->setUsed(usedBits, false); + ui->bitfield->setBytesToDraw(dbcMessage->len); +} + +//Copy the current signal in its entirety to the undo buffer. Just for safe keeping +//Called before an edit is done to save the state so we can revert if necessary +void DBCSignalEditor::pushToUndoBuffer() +{ + if (!currentSignal) return; + //store a copy of the pointer so that if we need to pop we can pop to the proper place + currentSignal->self = currentSignal; + undoBuffer.append(*currentSignal); //save the whole thing + qDebug() << "Pushing to undo buffer"; +} + +//Pop the last copy of a signal from the stack and begin editing it +void DBCSignalEditor::popFromUndoBuffer() +{ + if (undoBuffer.empty()) + { + dbcFile->clearDirtyFlag(); //TODO: Don't do this. Implement per-item dirty flags. + qDebug() << "Undo buffer empty"; + return; //can't pop if there are no stored entries! + } + qDebug() << "Popping undo buffer"; + DBC_SIGNAL sig = undoBuffer.back(); + undoBuffer.pop_back(); + currentSignal = sig.self; //restore the pointer + *currentSignal = sig; //write the contents into the memory pointed to + + fillSignalForm(currentSignal); + fillValueTable(currentSignal); } diff --git a/dbc/dbcsignaleditor.h b/dbc/dbcsignaleditor.h index 37f5e6d..2f82a7d 100644 --- a/dbc/dbcsignaleditor.h +++ b/dbc/dbcsignaleditor.h @@ -26,7 +26,8 @@ signals: void updatedTreeInfo(DBC_SIGNAL *sig); private slots: - void bitfieldClicked(int x, int y); + void bitfieldLeftClicked(int bit); + void bitfieldRightClicked(int bit); void onValuesCellChanged(int row,int col); void onCustomMenuValues(QPoint); void deleteCurrentValue(); @@ -36,6 +37,7 @@ private: DBCHandler *dbcHandler; DBC_MESSAGE *dbcMessage; DBC_SIGNAL *currentSignal; + QList undoBuffer; DBCFile *dbcFile; bool inhibitCellChanged; bool inhibitMsgProc; @@ -43,11 +45,14 @@ private: void fillSignalForm(DBC_SIGNAL *sig); void fillValueTable(DBC_SIGNAL *sig); void generateUsedBits(); + void refreshBitGrid(); void closeEvent(QCloseEvent *event); bool eventFilter(QObject *obj, QEvent *event); void readSettings(); void writeSettings(); + void pushToUndoBuffer(); + void popFromUndoBuffer(); }; #endif // DBCSIGNALEDITOR_H diff --git a/firmwareuploaderwindow.cpp b/firmwareuploaderwindow.cpp index 05028be..bc234e2 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); + 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(); } 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); + bytes[3] = (char)0xC0; + bytes[2] = (char)0xDE; + bytes[1] = (char)0xFA; + bytes[0] = (char)0xDE; + output.setPayload(bytes); //sendCANFrame(output, bus); } @@ -182,23 +182,23 @@ 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; - 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; 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(); } diff --git a/framefileio.cpp b/framefileio.cpp index e64a767..ae94fd3 100644 --- a/framefileio.cpp +++ b/framefileio.cpp @@ -369,16 +369,6 @@ bool FrameFileIO::autoDetectLoadFile(QString filename, QVector* frames } } - qDebug() << "Attempting lawicel"; - if (isLawicelFile(filename)) - { - if (loadLawicelFile(filename, frames)) - { - qDebug() << "Loaded as lawicel successfully!"; - return true; - } - } - qDebug() << "Attempting 'CARBUS Analyzer'"; if (isCARBUSAnalyzerFile(filename)) { @@ -494,6 +484,16 @@ bool FrameFileIO::autoDetectLoadFile(QString filename, QVector* frames } } + qDebug() << "Attempting lawicel"; + if (isLawicelFile(filename)) + { + if (loadLawicelFile(filename, frames)) + { + qDebug() << "Loaded as lawicel successfully!"; + return true; + } + } + qDebug() << "Attempting generic CSV"; if (isGenericCSVFile(filename)) { @@ -867,7 +867,7 @@ bool FrameFileIO::loadCARBUSAnalyzerFile(QString filename, QVector* fr line = txt.readLine().simplified(); if (line.length() > 2) { - QList tokens = line.split(QRegExp("\\s+")); + QList tokens = line.split(QRegularExpression("\\s+")); if (tokens.length() > 3) { QString time = tokens[0].replace(",", ""); @@ -1054,7 +1054,7 @@ bool FrameFileIO::isCANHackerFile(QString filename) // CANHacker trace format // Time ID DLC Data Comment -// 00.000 00004000 8 36 47 19 43 01 00 00 80 +// 00[.|,]000 00004000 8 36 47 19 43 01 00 00 80 bool FrameFileIO::loadCANHackerFile(QString filename, QVector* frames) { QFile *inFile = new QFile(filename); @@ -1062,6 +1062,9 @@ bool FrameFileIO::loadCANHackerFile(QString filename, QVector* frames) QByteArray line; int lineCounter = 0; bool foundErrors = false; + int addendumTime = 0; + double previousTime = 0; + if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) { @@ -1086,6 +1089,11 @@ bool FrameFileIO::loadCANHackerFile(QString filename, QVector* frames) if (tokens.length() > 3) { int idxOfDecimal = tokens[0].indexOf('.'); + // If no dot is found, try comma + if(idxOfDecimal == -1) + { + idxOfDecimal = tokens[0].indexOf(','); + } if (idxOfDecimal > -1) { //int decimalPlaces = tokens[0].length() - tokens[0].indexOf('.') - 1; //the result of the above is the # of digits after the decimal. @@ -1097,7 +1105,11 @@ bool FrameFileIO::loadCANHackerFile(QString filename, QVector* frames) multiplier = 1; //special case. Assume no decimal means microseconds } //qDebug() << "decimal places " << decimalPlaces; - thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, static_cast(tokens[0].toDouble() * multiplier))); + if (previousTime > tokens[0].toDouble()) { + addendumTime += 60; + } + thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, static_cast((tokens[0].toDouble() + addendumTime) * multiplier))); + previousTime = tokens[0].toDouble(); thisFrame.setFrameId( static_cast(tokens[1].toInt(nullptr, 16)) ); thisFrame.setExtendedFrameFormat((thisFrame.frameId() > 0x7FF)); thisFrame.isReceived = true; @@ -1369,8 +1381,17 @@ bool FrameFileIO::isPCANFile(QString filename) ;---+-- ------+------ +- --+----- +- +- +- +- -- -- -- -- -- -- -- 0-6 8-20 22-23 25-32 34-35 37-38 40-? two chars each + space - Both versions are supported now but rather lazily. Very many aspects of the file are ignored. + Version 2.1 +; Message Time Type ID Rx/Tx +; Number Offset | Bus [hex] | Reserved +; | [ms] | | | | | Data Length Code +; | | | | | | | | Data [hex] ... +; | | | | | | | | | +;---+--- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- -- + 0 1 2 3 4 5 6 7 8 + + It seems as if Version 2 files might be able to store other protocols like ISO-TP or J1939 + */ bool FrameFileIO::loadPCANFile(QString filename, QVector* frames) { @@ -1395,10 +1416,14 @@ bool FrameFileIO::loadPCANFile(QString filename, QVector* frames) lineCounter = 0; } line = inFile->readLine(); - if (line.startsWith(";$FILEVERSION=2.0")) fileVersion = 20; - if (line.startsWith(";$FILEVERSION=1.3")) fileVersion = 13; - if (line.startsWith(";$FILEVERSION=1.1")) fileVersion = 11; - if (line.startsWith(';')) continue; + if (line.startsWith(';')) + { + if (line.contains("$FILEVERSION=2.1")) fileVersion = 21; + if (line.contains("$FILEVERSION=2.0")) fileVersion = 20; + if (line.contains("$FILEVERSION=1.3")) fileVersion = 13; + if (line.contains("$FILEVERSION=1.1")) fileVersion = 11; + continue; + } if (line.length() > 41) { line = line.simplified(); @@ -1459,7 +1484,7 @@ bool FrameFileIO::loadPCANFile(QString filename, QVector* frames) if (tokens.length() > 6) { thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, static_cast(tokens[1].toDouble() * 1000.0))); - thisFrame.setFrameId(tokens[3].toUInt(nullptr, 16)); + thisFrame.setFrameId(tokens[4].toUInt(nullptr, 16)); if (thisFrame.frameId() < 0x1FFFFFFF) { int numBytes = tokens[6].toInt(); @@ -1538,6 +1563,58 @@ bool FrameFileIO::loadPCANFile(QString filename, QVector* frames) } } } + /* + Version 2.1 +; Message Time Type ID Rx/Tx +; Number Offset | Bus [hex] | Reserved +; | [ms] | | | | | Data Length Code +; | | | | | | | | Data [hex] ... +; | | | | | | | | | +;---+--- ------+------ +- +- --+----- +- +- +--- +- -- -- -- -- -- -- -- + 0 1 2 3 4 5 6 7 8 + + */ + else if (fileVersion == 21) + { + if (tokens.length() > 7) + { + thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, static_cast(tokens[1].toDouble() * 1000.0))); + thisFrame.setFrameId(tokens[4].toUInt(nullptr, 16)); + if (thisFrame.frameId() < 0x1FFFFFFF) + { + int numBytes = tokens[7].toInt(); + QByteArray bytes(numBytes, 0); + //qDebug() << thisFrame.payload().length(); + thisFrame.isReceived = true; + thisFrame.bus = tokens[3].toInt(); + if (thisFrame.frameId() > 0x10000000) + { + thisFrame.setExtendedFrameFormat(true); + } + else + { + thisFrame.setExtendedFrameFormat(false); + } + if (tokens[2] == "R") + { + thisFrame.setFrameType(QCanBusFrame::RemoteRequestFrame); + } + else + { + thisFrame.setFrameType(QCanBusFrame::DataFrame); + for (int d = 0; d < numBytes; d++) + { + if (tokens[d + 8] != "") + { + bytes[d] = static_cast(tokens[d + 8].toInt(nullptr, 16)); + } + else bytes[d] = 0; + } + } + thisFrame.setPayload(bytes); + frames->append(thisFrame); + } + } + } } //else foundErrors = true; } @@ -1551,9 +1628,9 @@ bool FrameFileIO::isCanalyzerASC(QString filename) { QFile *inFile = new QFile(filename); QByteArray line; - int lineCounter = 0; + //int lineCounter = 0; + //bool inHeader = true; bool isMatch = true; - bool inHeader = true; QList tokens; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) @@ -1596,15 +1673,23 @@ bool FrameFileIO::isCanalyzerASC(QString filename) //There tends to be four lines of header first. The last of which starts with // so first burn off lines //until a line starting with // is seen, then the rest are formatted like this: //Version 8.0: -// 47.971842 2 248 Rx d 8 FF FF FF FF FF FF FF FF -//version 8.1 -//47244640.194244 CANFD 1 Rx 122 0 0 6 6 00 00 18 12 D2 00 0 0 200000 0 0 0 0 0 -//That can be simplified to look like this: //47.971842 2 248 Rx d 8 FF FF FF FF FF FF FF FF +//version 8.1 //47244640.194244 CANFD 1 Rx 122 0 0 6 6 00 00 18 12 D2 00 0 0 200000 0 0 0 0 0 -//Which is then easy to parse by splitting on the spaces -//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) +//Version 9.0 +//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 +//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); @@ -1638,7 +1723,6 @@ bool FrameFileIO::loadCanalyzerASC(QString filename, QVector* frames) { if (line.startsWith("//")) { - // version 8.1.0 line = line.right(line.length() - 11); QList versionTokens = line.split('.'); if (versionTokens.length() > 2) @@ -1646,6 +1730,7 @@ bool FrameFileIO::loadCanalyzerASC(QString filename, QVector* frames) verMajor = versionTokens[0].toInt(); verMinor = versionTokens[1].toInt(); verRevision = versionTokens[2].toInt(); + qDebug() << "Major: " << verMajor << " Minor:" << verMinor << " Rev:" << verRevision; } } if (line.startsWith("//") || lineCounter > 4) @@ -1655,115 +1740,135 @@ bool FrameFileIO::loadCanalyzerASC(QString filename, QVector* frames) } } if (inHeader) continue; - if (line.length() > 2) + if (line.length() > 2 && !line.startsWith("//")) { tokens = line.simplified().split(' '); - if (verMajor == 8 && verMinor == 0) + + if (tokens[0].contains("Begin")) continue; //probably begin triggerblock but we're ignoring that. + + //try to do some investigating to see if this line is a CAN frame or not. The file format has many other potential line types it seems... + if (tokens.length() > 5) { - if (tokens.length() > 5) + if (tokens[3].toUpper().startsWith("RX") || tokens[3].toUpper().startsWith("TX")) { thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, static_cast(tokens[0].toDouble() * 1000000.0))); - if (tokens[2].endsWith('x')) + if (tokens[1].contains("CAN")) //the different format I haven't seen a whole lot of, seems to support CANFD in this format { - QByteArray copied_id = tokens[2]; - copied_id.chop(1); - thisFrame.setFrameId(copied_id.toUInt(nullptr, 16)); - thisFrame.setExtendedFrameFormat(true); + if (tokens[4].endsWith('x')) + { + QByteArray copied_id = tokens[4]; + copied_id.chop(1); + thisFrame.setFrameId(copied_id.toUInt(nullptr, 16)); + thisFrame.setExtendedFrameFormat(true); + } + else + { + thisFrame.setFrameId(tokens[4].toUInt(nullptr, 16)); + thisFrame.setExtendedFrameFormat(thisFrame.frameId() > 0x7FF); //some .asc files have extended IDs without 'x' + } + + int payloadLen = tokens[8].toInt(); + qDebug() << "PayloadLen: " << payloadLen << " Tokens: " << tokens; + QByteArray bytes(payloadLen, 0); + if (payloadLen > 64) + { + qDebug() << "Payload length too long. Original line: " << line; + return false; + } + if (payloadLen < 0) + { + qDebug() << "Payload length negative! Original line: " << line; + return false; + } + thisFrame.isReceived = tokens[3].toUpper().contains("RX"); + thisFrame.bus = tokens[2].toInt(); + + if (tokens[5].at(0) >= '0' && tokens[5].at(0) <= '9') + { + for (int d = 9; d < (9 + payloadLen); d++) + { + if (tokens.count() > d) + { + bytes[d - 9] = static_cast(tokens[d].toInt(nullptr, 16)); + } + else //expected byte wasn't there to read. Set it zero and set error flag + { + bytes[d - 9] = 0; + foundErrors = true; + qDebug() << "D:" << d << " Count:" << tokens.count(); + qDebug() << "Expected byte missing! Original line: " << line; + } + } + thisFrame.setPayload(bytes); + } + else + { + for (int d = 10; d < (10 + payloadLen); d++) + { + if (tokens.count() > d) + { + bytes[d - 10] = static_cast(tokens[d].toInt(nullptr, 16)); + } + else //expected byte wasn't there to read. Set it zero and set error flag + { + bytes[d - 10] = 0; + foundErrors = true; + qDebug() << "D:" << d << " Count:" << tokens.count(); + qDebug() << "Expected byte missing! Original line: " << line; + } + } + thisFrame.setPayload(bytes); + } } else { - thisFrame.setFrameId(tokens[2].toUInt(nullptr, 16)); - thisFrame.setExtendedFrameFormat(thisFrame.frameId() > 0x7FF); //some .asc files have extended IDs without 'x' - } - - int payloadLen = tokens[5].toInt(); - QByteArray bytes(payloadLen, 0); - - if (payloadLen > 8) - { - qDebug() << "Payload length too long. Original line: " << line; - return false; - } - if (payloadLen < 0) - { - qDebug() << "Payload length negative! Original line: " << line; - return false; - } - thisFrame.isReceived = tokens[3].toUpper().contains("RX"); - thisFrame.bus = tokens[1].toInt(); - if (tokens[4] == "r") thisFrame.setFrameType(QCanBusFrame::RemoteRequestFrame); - for (int d = 6; d < (6 + payloadLen); d++) - { - if (tokens.count() > d) + int payloadLen = tokens[5].toInt(); + thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, static_cast(tokens[0].toDouble() * 1000000.0))); + if (tokens[2].endsWith('x')) { - bytes[d - 6] = static_cast(tokens[d].toInt(nullptr, 16)); + QByteArray copied_id = tokens[2]; + copied_id.chop(1); + thisFrame.setFrameId(copied_id.toUInt(nullptr, 16)); + thisFrame.setExtendedFrameFormat(true); } - else //expected byte wasn't there to read. Set it zero and set error flag + else { - bytes[d - 6] = 0; - foundErrors = true; - qDebug() << "D:" << d << " Count:" << tokens.count(); - qDebug() << "Expected byte missing! Original line: " << line; + thisFrame.setFrameId(tokens[2].toUInt(nullptr, 16)); + thisFrame.setExtendedFrameFormat(thisFrame.frameId() > 0x7FF); //some .asc files have extended IDs without 'x' } + QByteArray bytes(payloadLen, 0); + + if (payloadLen > 8) + { + qDebug() << "Payload length too long. Original line: " << line; + return false; + } + if (payloadLen < 0) + { + qDebug() << "Payload length negative! Original line: " << line; + return false; + } + thisFrame.isReceived = tokens[3].toUpper().contains("RX"); + thisFrame.bus = tokens[1].toInt(); + if (tokens[4] == "r") thisFrame.setFrameType(QCanBusFrame::RemoteRequestFrame); + for (int d = 6; d < (6 + payloadLen); d++) + { + if (tokens.count() > d) + { + bytes[d - 6] = static_cast(tokens[d].toInt(nullptr, 16)); + } + else //expected byte wasn't there to read. Set it zero and set error flag + { + bytes[d - 6] = 0; + foundErrors = true; + qDebug() << "D:" << d << " Count:" << tokens.count(); + qDebug() << "Expected byte missing! Original line: " << line; + } + } + thisFrame.setPayload(bytes); } - thisFrame.setPayload(bytes); + frames->append(thisFrame); } - frames->append(thisFrame); - } - if (verMajor == 8 && verMinor == 1) - { - if (tokens.length() > 5) - { - //47244640.194244 CANFD 1 Rx 122 0 0 6 6 00 00 18 12 D2 00 0 0 200000 0 0 0 0 0 - //Time type bus dir ID ? ? length length bytes then many values of unknown type (ver 8.1) - // 0 1 2 3 4 5 6 7 8 9- - thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, static_cast(tokens[0].toDouble() * 1000000.0))); - if (tokens[4].endsWith('x')) - { - QByteArray copied_id = tokens[4]; - copied_id.chop(1); - thisFrame.setFrameId(copied_id.toUInt(nullptr, 16)); - thisFrame.setExtendedFrameFormat(true); - } - else - { - thisFrame.setFrameId(tokens[4].toUInt(nullptr, 16)); - thisFrame.setExtendedFrameFormat(thisFrame.frameId() > 0x7FF); //some .asc files have extended IDs without 'x' - } - - int payloadLen = tokens[8].toInt(); - QByteArray bytes(payloadLen, 0); - - if (payloadLen > 8) - { - qDebug() << "Payload length too long. Original line: " << line; - return false; - } - if (payloadLen < 0) - { - qDebug() << "Payload length negative! Original line: " << line; - return false; - } - thisFrame.isReceived = tokens[3].toUpper().contains("RX"); - thisFrame.bus = tokens[2].toInt(); - if (tokens[5] == "r") thisFrame.setFrameType(QCanBusFrame::RemoteRequestFrame); - for (int d = 9; d < (9 + payloadLen); d++) - { - if (tokens.count() > d) - { - bytes[d - 9] = static_cast(tokens[d].toInt(nullptr, 16)); - } - else //expected byte wasn't there to read. Set it zero and set error flag - { - bytes[d - 9] = 0; - foundErrors = true; - qDebug() << "D:" << d << " Count:" << tokens.count(); - qDebug() << "Expected byte missing! Original line: " << line; - } - } - thisFrame.setPayload(bytes); - } - frames->append(thisFrame); } } //else foundErrors = true; @@ -2000,6 +2105,9 @@ bool FrameFileIO::loadNativeCSVFile(QString filename, QVector* frames) if (tokens[2].toUpper().contains("TRUE")) thisFrame.setExtendedFrameFormat(true); else thisFrame.setExtendedFrameFormat(false); + //fix for faulty files that fail to set the extended flag when they should + if (thisFrame.frameId() > 0x7FF) thisFrame.setExtendedFrameFormat(true); + thisFrame.setFrameType(QCanBusFrame::DataFrame); if (fileVersion == 1) @@ -2294,11 +2402,16 @@ bool FrameFileIO::loadGenericCSVFile(QString filename, QVector* frames else thisFrame.setExtendedFrameFormat(false); thisFrame.bus = 0; thisFrame.setFrameType(QCanBusFrame::DataFrame); - QList dataTok = tokens[1].split(' '); - QByteArray bytes(dataTok.length(), 0); - for (int d = 0; d < dataTok.length(); d++) bytes[d] = static_cast(dataTok[d].toInt(nullptr, 16)); - thisFrame.setPayload(bytes); - frames->append(thisFrame); + if (tokens.length() > 1) + { + QList dataTok = tokens[1].split(' '); + int dLen = dataTok.length(); + if (dLen > 8) dLen = 8; + QByteArray bytes(dLen, 0); + for (int d = 0; d < dLen; d++) bytes[d] = static_cast(dataTok[d].toInt(nullptr, 16)); + thisFrame.setPayload(bytes); + frames->append(thisFrame); + } } else foundErrors = true; } @@ -3468,9 +3581,9 @@ bool FrameFileIO::isCanDumpFile(QString filename) QFile *inFile = new QFile(filename); QByteArray line; QList tokens; - QRegExp timeExp("^\\((\\S+)\\)$"); - QRegExp IdValExp("^(\\S+)#(\\S+)$"); - QRegExp valExp("(\\S{2})"); + QRegularExpression timeExp(QRegularExpression::anchoredPattern("^\\((\\S+)\\)$")); //anchored pattern causes exact match + QRegularExpression IdValExp(QRegularExpression::anchoredPattern("^(\\S+)#(\\S+)$")); + QRegularExpression valExp("(\\S{2})"); int lineCounter = 0; int pos = 0; bool isMatch = true; @@ -3495,11 +3608,13 @@ bool FrameFileIO::isCanDumpFile(QString filename) tokens = line.simplified().split(' '); if(tokens.count() < 3) isMatch = false; - /* timestamp */ - ret = timeExp.exactMatch(tokens[0]); - if(!ret) isMatch = false; + /* timestamp */ + QRegularExpressionMatch timeExpMatched = timeExp.match(tokens[0]); + if(!timeExpMatched.hasMatch()) { + isMatch = false; + } - /*uint64_t timestamp = (uint64_t)*/(timeExp.cap(1).toDouble(&ret) /** (double)1000000.0*/); + /*uint64_t timestamp = (uint64_t)*/(timeExpMatched.captured(1).toDouble(&ret) /** (double)1000000.0*/); if(!ret) isMatch = false; if (line.contains('[')) //the expanded format @@ -3529,17 +3644,19 @@ bool FrameFileIO::isCanDumpFile(QString filename) isMatch = false; continue; } - ret = IdValExp.exactMatch(tokens[2]); - if(!ret) + + + QRegularExpressionMatch IdValExpMatched = IdValExp.match(tokens[2]); + if(!IdValExpMatched.hasMatch()) { isMatch = false; continue; } /* ID */ - /*int ID = */IdValExp.cap(1).toInt(&ret, 16); + /*int ID = */IdValExpMatched.captured(1).toInt(&ret, 16); - QString val= IdValExp.cap(2); + QString val= IdValExpMatched.captured(2); pos = 0; int len = 0; @@ -3553,22 +3670,23 @@ bool FrameFileIO::isCanDumpFile(QString filename) } else { /* val byte per byte */ int lng = 0; - while ((pos = valExp.indexIn(val, pos)) != -1) - { + QRegularExpressionMatch valExpMatch; + QRegularExpressionMatchIterator i = valExp.globalMatch(val); + while (i.hasNext()) { + valExpMatch = i.next(); lng++; if (lng > 8) { isMatch = false; break; } - /*int data = */valExp.cap(1).toInt(&ret, 16); + /*int data = */valExpMatch.captured(1).toInt(&ret, 16); if(!ret) { isMatch = false; break; } - pos += valExp.matchedLength(); } } } @@ -3595,11 +3713,10 @@ bool FrameFileIO::loadCanDumpFile(QString filename, QVector* frames) CANFrame thisFrame; QByteArray line; QList tokens; - QRegExp timeExp("^\\((\\S+)\\)$"); - QRegExp IdValExp("^(\\S+)#(\\S+)$"); - QRegExp valExp("(\\S{2})"); + QRegularExpression timeExp(QRegularExpression::anchoredPattern("^\\((\\S+)\\)$")); //anchored pattern causes exact match + QRegularExpression IdValExp(QRegularExpression::anchoredPattern("^(\\S+)#(\\S+)$")); + QRegularExpression valExp("(\\S{2})"); int lineCounter = 0; - int pos = 0; bool ret; if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) @@ -3625,8 +3742,8 @@ bool FrameFileIO::loadCanDumpFile(QString filename, QVector* frames) if(tokens.count()<3) continue; /* timestamp */ - ret = timeExp.exactMatch(tokens[0]); - if(!ret) continue; + QRegularExpressionMatch timeExpMatched = timeExp.match(tokens[0]); + if(!timeExpMatched.hasMatch()) continue; //Sort out the bus std::string busString = tokens[1].toStdString(); @@ -3647,7 +3764,7 @@ bool FrameFileIO::loadCanDumpFile(QString filename, QVector* frames) thisFrame.bus = busNum; - thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, (uint64_t)(timeExp.cap(1).toDouble(&ret) * (double)1000000.0))); + thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, (uint64_t)(timeExpMatched.captured(1).toDouble(&ret) * (double)1000000.0))); if(!ret) continue; if (line.contains('[')) //the expanded format (second one from the above list) @@ -3670,24 +3787,26 @@ bool FrameFileIO::loadCanDumpFile(QString filename, QVector* frames) { /* ID & value */ //qDebug() << tokens[2]; - ret = IdValExp.exactMatch(tokens[2]); - if(!ret) + QRegularExpressionMatch IdValExpMatched = IdValExp.match(tokens[2]); + if(!IdValExpMatched.hasMatch()) { qDebug() << "ID regex didn't match!"; continue; } /* ID */ - thisFrame.setFrameId(static_cast(IdValExp.cap(1).toInt(&ret, 16))); - if (IdValExp.cap(1).length() > 3) { + thisFrame.setFrameId(static_cast(IdValExpMatched.captured(1).toInt(&ret, 16))); + if (IdValExpMatched.captured(1).length() > 3) + { thisFrame.setExtendedFrameFormat(true); - } else { + } + else + { thisFrame.setExtendedFrameFormat(false); } - QString val= IdValExp.cap(2); + QString val = IdValExpMatched.captured(2); - pos = 0; QByteArray bytes; if (val.startsWith("R") && val.at(1).isDigit()) { thisFrame.payload().resize( val.at(1).toLatin1() - '0' ); @@ -3695,12 +3814,13 @@ bool FrameFileIO::loadCanDumpFile(QString filename, QVector* frames) } else { thisFrame.setFrameType(QCanBusFrame::DataFrame); /* val byte per byte */ - while ((pos = valExp.indexIn(val, pos)) != -1) + QRegularExpressionMatch valExpMatch; + QRegularExpressionMatchIterator i = valExp.globalMatch(val); + while (i.hasNext()) { - bytes.append((char)valExp.cap(1).toInt(&ret, 16)); + valExpMatch = i.next(); + bytes.append((char)valExpMatch.captured(1).toInt(&ret, 16)); if(!ret) continue; - - pos += valExp.matchedLength(); } } thisFrame.setPayload(bytes); @@ -4159,8 +4279,8 @@ bool FrameFileIO::loadTeslaAPFile(QString filename, QVector* frames) CANFrame thisFrame; int lineCounter = 0; QByteArray data; - int timeOffset = 0; - int64_t lastTimeStamp = 0; + //int timeOffset = 0; + //int64_t lastTimeStamp = 0; bool foundErrors = false; thisFrame.setFrameType(QCanBusFrame::DataFrame); TeslaAPCANRecord record; @@ -4217,7 +4337,7 @@ bool FrameFileIO::isCLX000File(QString filename) { } QTextStream fileStream(inFile.get()); - bool foundErrors = false; + //bool foundErrors = false; // Contains 16 lines of header prior to (potential) data. QString headerLine; @@ -4269,7 +4389,7 @@ bool FrameFileIO::isCLX000File(QString filename) { qDebug() << "Could not decode time format" << headerLine << "using pattern" << timeFormatPattern; return false; } - auto timeFormat = matchTimeFormat.captured("timeFormat").front().digitValue(); + //auto timeFormat = matchTimeFormat.captured("timeFormat").front().digitValue(); headerLine = fileStream.readLine(); QString const timeSeparatorPattern = "# Time separator: \"(?[" + validSeparators + "]?)\""; @@ -4279,7 +4399,7 @@ bool FrameFileIO::isCLX000File(QString filename) { qDebug() << "Could not decode time format" << headerLine << "using pattern" << timeSeparatorPattern; return false; } - QChar timeSeparator = matchTimeSeparator.captured("timeSeparator").front(); + //QChar timeSeparator = matchTimeSeparator.captured("timeSeparator").front(); headerLine = fileStream.readLine(); QString const timeSeparatorMsPattern = "# Time separator ms: \"(?[" + validSeparators + "]?)\""; @@ -4289,7 +4409,7 @@ bool FrameFileIO::isCLX000File(QString filename) { qDebug() << "Could not decode time format ms" << headerLine << "using pattern" << timeSeparatorMsPattern; return false; } - QChar timeSeparatorMs = matchTimeSeparatorMs.captured("timeSeparatorMs").front(); + //QChar timeSeparatorMs = matchTimeSeparatorMs.captured("timeSeparatorMs").front(); headerLine = fileStream.readLine(); QString const dateSeparatorPattern = "# Date separator: \"(?[" + validSeparators + "]?)\""; @@ -4299,7 +4419,7 @@ bool FrameFileIO::isCLX000File(QString filename) { qDebug() << "Could not decode time format ms" << headerLine << "using pattern" << dateSeparatorPattern; return false; } - QChar dateSeparator = matchDateSeparator.captured("dateSeparator").front(); + //QChar dateSeparator = matchDateSeparator.captured("dateSeparator").front(); headerLine = fileStream.readLine(); QString const timeDateSeparatorPattern = "# Time and date separator: \"(?[" + validSeparators + "]?)\""; @@ -4309,7 +4429,7 @@ bool FrameFileIO::isCLX000File(QString filename) { qDebug() << "Could not decode time format ms" << headerLine << "using pattern" << timeDateSeparatorPattern; return false; } - QChar timeDateSeparator = matchTimeDateSeparator.captured("timeDateSeparator").front(); + //QChar timeDateSeparator = matchTimeDateSeparator.captured("timeDateSeparator").front(); // Skip remaining header lines. fileStream.readLine(); @@ -4373,7 +4493,7 @@ bool FrameFileIO::loadCLX000File(QString filename, QVector* frames) { return false; } - QString const validSeparators(" -~"); + QString const validSeparators(" -~;"); // Decode separators and format for later decoding. headerLine = fileStream.readLine(); @@ -4480,6 +4600,7 @@ bool FrameFileIO::loadCLX000File(QString filename, QVector* frames) { } // Fallthrough + [[fallthrough]]; } case 5: { // Handle month. @@ -4490,6 +4611,7 @@ bool FrameFileIO::loadCLX000File(QString filename, QVector* frames) { } // Fallthrough + [[fallthrough]]; } case 4: { // Handle day. @@ -4500,6 +4622,7 @@ bool FrameFileIO::loadCLX000File(QString filename, QVector* frames) { } // Fallthrough + [[fallthrough]]; } case 3: { // Handle hours. @@ -4510,6 +4633,7 @@ bool FrameFileIO::loadCLX000File(QString filename, QVector* frames) { } // Fallthrough + [[fallthrough]]; } case 2: { // Handle minutes. @@ -4520,6 +4644,7 @@ bool FrameFileIO::loadCLX000File(QString filename, QVector* frames) { } // Fallthrough + [[fallthrough]]; } case 1: { // Handle seconds. @@ -4530,6 +4655,7 @@ bool FrameFileIO::loadCLX000File(QString filename, QVector* frames) { } // Fallthrough + [[fallthrough]]; } case 0: { // Handle milliseconds. @@ -4576,17 +4702,34 @@ bool FrameFileIO::loadCLX000File(QString filename, QVector* frames) { currentFrame.setFrameType(QCanBusFrame::DataFrame); currentFrame.bus = 0; - currentTime = currentTime.addYears(res.captured("year").toUInt()); - currentTime = currentTime.addMonths(res.captured("month").toUInt()); - currentTime = currentTime.addDays(res.captured("day").toUInt()); - currentTime = currentTime.addSecs( - res.captured("hours").toUInt() * 60 * 60 + - res.captured("minutes").toUInt() * 60 + - res.captured("seconds").toUInt() - ); - currentTime = currentTime.addMSecs(res.captured("ms").toUInt()); + //currentTime is already initialized with startDate so it already has year,month,etc filled out. + //For each timestamp on the line we need to figure out how it differs from the already set time + //and move the timestamp accordingly. + if (res.captured("year").length() > 0) + { + //for all of these, only add the difference not the whole value! + currentTime = currentTime.addYears(res.captured("year").toUInt() - startDate.date().year()); + } - currentFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, currentTime.toMSecsSinceEpoch())); + if (res.captured("month").length() > 0) + { + currentTime = currentTime.addMonths(res.captured("month").toUInt() - startDate.date().month()); + } + + if (res.captured("day").length() > 0) + { + currentTime = currentTime.addMonths(res.captured("day").toUInt() - startDate.date().day()); + } + + unsigned int seconds = res.captured("hours").toUInt() * 60 * 60 + res.captured("minutes").toUInt() * 60 + + res.captured("seconds").toUInt() - (startDate.time().msecsSinceStartOfDay() / 1000); + + currentTime = currentTime.addSecs(seconds); + currentTime = currentTime.addMSecs(res.captured("ms").toUInt() - (startDate.time().msecsSinceStartOfDay() % 1000)); + + //time is stored in microseconds so we need to get the timestamp as milliseconds since the epoch + //then multiplyby 1000 to get microseconds since the epoch. + currentFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, currentTime.toMSecsSinceEpoch() * 1000)); if(res.captured("type").length() != 0) { auto frameType = res.captured("type").toUInt(); @@ -4652,7 +4795,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; } @@ -4694,6 +4837,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) { @@ -4701,13 +4845,19 @@ 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; } } if (fileIsGood) { - uint64_t lastFrameTime = 0; + //uint64_t lastFrameTime = 0; uint8_t data[1]; while (!inFile->atEnd()) @@ -4725,7 +4875,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 { @@ -4734,10 +4890,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); @@ -4758,9 +4914,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); @@ -4769,39 +4925,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; + 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); + } - 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; + 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); @@ -4811,11 +4982,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); diff --git a/frameplaybackobject.cpp b/frameplaybackobject.cpp index b918b07..c79ecf6 100644 --- a/frameplaybackobject.cpp +++ b/frameplaybackobject.cpp @@ -32,6 +32,33 @@ quint64 FramePlaybackObject::updatePosition(bool forward) currentPosition = 0; return 0; } + + //only send frame out if its ID is checked in the list. Otherwise discard it. + CANFrame *thisFrame = ¤tSeqItem->data[currentPosition]; + uint32_t originalBus = thisFrame->bus; + if (currentSeqItem->idFilters.find(thisFrame->frameId()).value()) + { + if (whichBusSend > -1) + { + thisFrame->bus = whichBusSend; + sendingBuffer.append(*thisFrame); + } + else if (whichBusSend == -1) + { + for (int c = 0; c < numBuses; c++) + { + thisFrame->bus = c; + sendingBuffer.append(*thisFrame); + } + } + else //from file so retain original bus and send as-is + { + sendingBuffer.append(*thisFrame); + } + + thisFrame->bus = originalBus; + } + if (forward) { if (currentPosition < (currentSeqItem->data.count() - 1)) currentPosition++; //still in same file so keep going @@ -65,31 +92,6 @@ quint64 FramePlaybackObject::updatePosition(bool forward) } } - //only send frame out if its ID is checked in the list. Otherwise discard it. - CANFrame *thisFrame = ¤tSeqItem->data[currentPosition]; - uint32_t originalBus = thisFrame->bus; - if (currentSeqItem->idFilters.find(thisFrame->frameId()).value()) - { - if (whichBusSend > -1) - { - thisFrame->bus = whichBusSend; - sendingBuffer.append(*thisFrame); - } - else if (whichBusSend == -1) - { - for (int c = 0; c < numBuses; c++) - { - thisFrame->bus = c; - sendingBuffer.append(*thisFrame); - } - } - else //from file so retain original bus and send as-is - { - sendingBuffer.append(*thisFrame); - } - - thisFrame->bus = originalBus; - } return thisFrame->timeStamp().microSeconds(); } diff --git a/frameplaybackwindow.cpp b/frameplaybackwindow.cpp index 30167a5..10cce13 100644 --- a/frameplaybackwindow.cpp +++ b/frameplaybackwindow.cpp @@ -1,4 +1,5 @@ #include "frameplaybackwindow.h" +#include "mainwindow.h" #include "ui_frameplaybackwindow.h" #include #include @@ -48,6 +49,8 @@ FramePlaybackWindow::FramePlaybackWindow(const QVector *frames, QWidge currentPosition = 0; forward = true; isPlaying = false; + wantPlaying = false; + haveIncomingTraffic = false; updateFrameLabel(); @@ -72,6 +75,7 @@ FramePlaybackWindow::FramePlaybackWindow(const QVector *frames, QWidge connect(ui->btnLoadFilters, &QAbstractButton::clicked, this, &FramePlaybackWindow::loadFilters); connect(ui->btnSaveFilters, &QAbstractButton::clicked, this, &FramePlaybackWindow::saveFilters); connect(ui->cbOriginalTiming, &QCheckBox::toggled, this, &FramePlaybackWindow::useOrigTimingClicked); + connect(MainWindow::getReference(), SIGNAL(framesUpdated(int)), this, SLOT(updatedFrames(int))); connect(&playbackObject, &FramePlaybackObject::EndOfFrameCache, this, &FramePlaybackWindow::EndOfFrameCache); connect(&playbackObject, &FramePlaybackObject::statusUpdate, this, &FramePlaybackWindow::getStatusUpdate); @@ -101,6 +105,8 @@ void FramePlaybackWindow::showEvent(QShowEvent *) { readSettings(); installEventFilter(this); + + //any time the view is shown we'll go see how many buses are registered and redo the combobox to match int numBuses = CANConManager::getInstance()->getNumBuses(); ui->comboCANBus->clear(); for (int n = 0; n < numBuses; n++) ui->comboCANBus->addItem(QString::number(n)); @@ -279,6 +285,32 @@ void FramePlaybackWindow::loadFilters() } } +void FramePlaybackWindow::updatedFrames(int numFrames) +{ + CANFrame thisFrame; + + if (numFrames == -1) //all frames deleted. Don't care + { + } + else if (numFrames == -2) //all new set of frames. Don't care + { + } + else //just got some new frames. + { + if (numFrames > 0) + { + haveIncomingTraffic = true; + if (wantPlaying && !isPlaying) + { + isPlaying = true; + updateFrameLabel(); + if (forward) playbackObject.startPlaybackForward(); + else playbackObject.startPlaybackBackward(); + } + } + } +} + void FramePlaybackWindow::refreshIDList() { if (currentSeqNum < 0 || currentSeqItem == nullptr) @@ -338,6 +370,8 @@ void FramePlaybackWindow::EndOfFrameCache() else //not looping so stop playback entirely { isPlaying = false; + wantPlaying = false; + haveIncomingTraffic = false; playbackObject.stopPlayback(); } } @@ -361,6 +395,8 @@ void FramePlaybackWindow::EndOfFrameCache() else //not looping so stop playback entirely { isPlaying = false; + wantPlaying = false; + haveIncomingTraffic = false; playbackObject.stopPlayback(); } } @@ -395,7 +431,10 @@ void FramePlaybackWindow::updateFrameLabel() ui->lblCurrPlayback->setText(currentSeqItem->filename); - ui->lblPosition->setText(QString::number(currentPosition) + tr(" of ") + QString::number(seqItems[row].data.count())); + if (wantPlaying && !isPlaying) + ui->lblPosition->setText(QString::number(currentPosition) + tr(" of ") + QString::number(seqItems[row].data.count()) + " (WAITING)"); + else + ui->lblPosition->setText(QString::number(currentPosition) + tr(" of ") + QString::number(seqItems[row].data.count())); } void FramePlaybackWindow::seqTableCellClicked(int row, int col) @@ -537,28 +576,40 @@ void FramePlaybackWindow::btnLoadLive() void FramePlaybackWindow::btnBackOneClick() { + if (!checkNoSeqLoaded()) return; forward = false; isPlaying = false; + wantPlaying = false; playbackObject.stepPlaybackBackward(); + updateFrameLabel(); } void FramePlaybackWindow::btnPauseClick() { isPlaying = false; + wantPlaying = false; playbackObject.pausePlayback(); + updateFrameLabel(); } void FramePlaybackWindow::btnReverseClick() { + if (!checkNoSeqLoaded()) return; forward = false; - isPlaying = true; - playbackObject.startPlaybackBackward(); + wantPlaying = true; + if (!ui->ckWaitForTraffic->isChecked()) + { + playbackObject.startPlaybackBackward(); + isPlaying = true; + } updateFrameLabel(); } void FramePlaybackWindow::btnStopClick() { isPlaying = false; + wantPlaying = false; + haveIncomingTraffic = false; playbackObject.stopPlayback(); if (seqItems.count() > 0) { @@ -574,20 +625,40 @@ void FramePlaybackWindow::btnStopClick() ui->tblSequence->setCurrentCell(0, 0); refreshIDList(); } + updateFrameLabel(); } void FramePlaybackWindow::btnPlayClick() { + if (!checkNoSeqLoaded()) return; forward = true; - isPlaying = true; - playbackObject.startPlaybackForward(); + wantPlaying = true; + if (!ui->ckWaitForTraffic->isChecked()) + { + playbackObject.startPlaybackForward(); + isPlaying = true; + } + updateFrameLabel(); } void FramePlaybackWindow::btnFwdOneClick() { + if (!checkNoSeqLoaded()) return; forward = true; isPlaying = false; + wantPlaying = false; playbackObject.stepPlaybackForward(); + updateFrameLabel(); +} + +bool FramePlaybackWindow::checkNoSeqLoaded() +{ + if (seqItems.count() == 0) + { + QMessageBox::warning(this, "Warning", "Cannot begin playback until at\nleast one playback source is loaded."); + return false; + } + return true; } void FramePlaybackWindow::changePlaybackSpeed(int newSpeed) diff --git a/frameplaybackwindow.h b/frameplaybackwindow.h index 64bb149..a56bfc8 100644 --- a/frameplaybackwindow.h +++ b/frameplaybackwindow.h @@ -44,6 +44,7 @@ private slots: void useOrigTimingClicked(); void getStatusUpdate(int frameNum); void EndOfFrameCache(); + void updatedFrames(int); private: Ui::FramePlaybackWindow *ui; @@ -55,8 +56,10 @@ private: int currentSeqNum; FramePlaybackObject playbackObject; bool forward; + bool wantPlaying; bool isPlaying; int currentPosition; + bool haveIncomingTraffic = false; void refreshIDList(); void updateFrameLabel(); @@ -66,6 +69,7 @@ private: void readSettings(); void writeSettings(); void calculateWhichBus(); + bool checkNoSeqLoaded(); bool eventFilter(QObject *obj, QEvent *event); }; diff --git a/framesenderobject.cpp b/framesenderobject.cpp new file mode 100644 index 0000000..f9eb468 --- /dev/null +++ b/framesenderobject.cpp @@ -0,0 +1,457 @@ +#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()) + { + /* + * 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() << sendingLastTimeStamp; + //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) || (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 + //qDebug() << trigger->msCounter; + if (trigger->msCounter >= (trigger->milliseconds * 1000)) + { + trigger->msCounter -= (trigger->milliseconds * 1000); + 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 0000000..47a5800 --- /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 8e796ef..cd4b32f 100644 --- a/framesenderwindow.cpp +++ b/framesenderwindow.cpp @@ -7,6 +7,7 @@ #include "mainwindow.h" #include "helpwindow.h" #include "connections/canconmanager.h" +#include "triggerdialog.h" /* * notes: need to ensure that you grab pointers when modifying data structures and dont @@ -24,6 +25,8 @@ FrameSenderWindow::FrameSenderWindow(const QVector *frames, QWidget *p modelFrames = frames; + dbcHandler = DBCHandler::getReference(); + intervalTimer = new QTimer(); intervalTimer->setTimerType(Qt::PreciseTimer); intervalTimer->setInterval(1); @@ -32,6 +35,7 @@ FrameSenderWindow::FrameSenderWindow(const QVector *frames, QWidget *p createBlankRow(); connect(ui->tableSender, SIGNAL(cellChanged(int,int)), this, SLOT(onCellChanged(int,int))); + connect(ui->tableSender, SIGNAL(cellDoubleClicked(int, int)), SLOT(onCellDoubleTap(int, int))); connect(intervalTimer, SIGNAL(timeout()), this, SLOT(handleTick())); connect(ui->btnClearGrid, SIGNAL(clicked(bool)), this, SLOT(clearGrid())); connect(ui->btnDisableAll, SIGNAL(clicked(bool)), this, SLOT(disableAll())); @@ -48,19 +52,20 @@ FrameSenderWindow::FrameSenderWindow(const QVector *frames, QWidget *p void FrameSenderWindow::setupGrid() { QStringList headers; - headers << "En" << "Bus" << "ID" << "Len" << "Ext" << "Rem" << "Data" + headers << "En" << "Bus" << "ID" << "MsgName" << "Len" << "Ext" << "Rem" << "Data" << "Trigger" << "Modifications" << "Count"; - ui->tableSender->setColumnCount(10); - ui->tableSender->setColumnWidth(0, 50); - ui->tableSender->setColumnWidth(1, 50); - ui->tableSender->setColumnWidth(2, 50); - ui->tableSender->setColumnWidth(3, 50); - ui->tableSender->setColumnWidth(4, 50); - ui->tableSender->setColumnWidth(5, 50); - ui->tableSender->setColumnWidth(6, 220); - ui->tableSender->setColumnWidth(7, 220); - ui->tableSender->setColumnWidth(8, 220); - ui->tableSender->setColumnWidth(9, 80); + ui->tableSender->setColumnCount(11); + ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_EN, 50); + ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_BUS, 50); + ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_ID, 50); + ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_MSGNAME, 150); + ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_LEN, 50); + 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, 270); + ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_MODS, 270); + ui->tableSender->setColumnWidth(ST_COLS::SENDTAB_COL_COUNT, 80); ui->tableSender->setHorizontalHeaderLabels(headers); } @@ -100,22 +105,26 @@ void FrameSenderWindow::createBlankRow() item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(Qt::Unchecked); inhibitChanged = true; - ui->tableSender->setItem(row, 0, item); + ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_EN, item); item = new QTableWidgetItem(); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(Qt::Unchecked); - ui->tableSender->setItem(row, 4, item); + ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_EXT, item); item = new QTableWidgetItem(); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(Qt::Unchecked); - ui->tableSender->setItem(row, 5, item); + ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_REM, item); - for (int i = 1; i < 10; i++) + for (int i = 1; i <= ST_COLS::SENDTAB_COL_COUNT; i++) { - if (i != 4 && i != 5) { + if (i != ST_COLS::SENDTAB_COL_EXT && i != ST_COLS::SENDTAB_COL_REM) { item = new QTableWidgetItem(""); + //msgname is looked up via DBC interface and not able to be edited. + //Though, it might be perhaps interesting to allow renaming it to + //a shorter name in some cases? Jury is still out on this one... + if (i == ST_COLS::SENDTAB_COL_MSGNAME) item->setFlags(item->flags() & ~Qt::ItemIsEditable); ui->tableSender->setItem(row, i, item); } } @@ -178,30 +187,79 @@ void FrameSenderWindow::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]; - qDebug() << "Trigger ID: " << thisTrigger->ID; - qDebug() << "Frame ID: " << frame->frameId(); - if (thisTrigger->ID > 0 && (uint32_t)thisTrigger->ID == frame->frameId()) + //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) ) ) { - if (thisTrigger->bus == frame->bus || thisTrigger->bus == -1) + bool sigCheckPassed = false; + DBC_MESSAGE *msg = dbcHandler->findMessage(thisTrigger->ID); + if (msg) { - if (thisTrigger->currCount < thisTrigger->maxCount) + DBC_SIGNAL *sig = msg->sigHandler->findSignalByName(thisTrigger->sigName); + if (sig) { - if (thisTrigger->milliseconds == 0) //immediate reply + //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)) { - thisTrigger->currCount++; - sendingData[sd].count++; - doModifiers(sd); - updateGridRow(sd); - CANConManager::getInstance()->sendFrame(sendingData[sd]); - } - else //delayed sending frame - { - thisTrigger->readyCount = true; + 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; } } } @@ -212,7 +270,7 @@ void FrameSenderWindow::enableAll() { for (int i = 0; i < ui->tableSender->rowCount() - 1; i++) { - ui->tableSender->item(i, 0)->setCheckState(Qt::Checked); + ui->tableSender->item(i, ST_COLS::SENDTAB_COL_EN)->setCheckState(Qt::Checked); sendingData[i].enabled = true; } } @@ -221,7 +279,7 @@ void FrameSenderWindow::disableAll() { for (int i = 0; i < ui->tableSender->rowCount() - 1; i++) { - ui->tableSender->item(i, 0)->setCheckState(Qt::Unchecked); + ui->tableSender->item(i, ST_COLS::SENDTAB_COL_EN)->setCheckState(Qt::Unchecked); sendingData[i].enabled = false; } } @@ -290,6 +348,8 @@ void FrameSenderWindow::loadGrid() } } + createBlankRow(); + setupGrid(); } @@ -307,14 +367,14 @@ void FrameSenderWindow::saveSenderFile(QString filename) for (int c = 0; c < sendingData.count(); c++) { outString.clear(); - if (ui->tableSender->item(c, 0)->checkState() == Qt::Checked) + if (ui->tableSender->item(c, ST_COLS::SENDTAB_COL_EN)->checkState() == Qt::Checked) { outString = "T#"; } else outString = "F#"; - for (int i = 1; i < 9; i++) + for (int i = 1; i < ST_COLS::SENDTAB_COL_COUNT; i++) { - if (i == 4 || i == 5) { + if (i == ST_COLS::SENDTAB_COL_EXT || i == ST_COLS::SENDTAB_COL_REM) { if (ui->tableSender->item(c, i)->checkState() == Qt::Checked) { outString.append("T"); } else { @@ -357,28 +417,28 @@ void FrameSenderWindow::loadSenderFile(QString filename) int row = ui->tableSender->rowCount(); ui->tableSender->insertRow(row); QTableWidgetItem *item = new QTableWidgetItem(); - ui->tableSender->setItem(row, 0, item); - ui->tableSender->item(row, 0)->setFlags(item->flags() | Qt::ItemIsUserCheckable); + ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_EN, item); + ui->tableSender->item(row, ST_COLS::SENDTAB_COL_EN)->setFlags(item->flags() | Qt::ItemIsUserCheckable); item = new QTableWidgetItem(); - ui->tableSender->setItem(row, 4, item); - ui->tableSender->item(row, 4)->setFlags(item->flags() | Qt::ItemIsUserCheckable); + ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_EXT, item); + ui->tableSender->item(row, ST_COLS::SENDTAB_COL_EN)->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(Qt::Unchecked); item = new QTableWidgetItem(); - ui->tableSender->setItem(row, 5, item); - ui->tableSender->item(row, 5)->setFlags(item->flags() | Qt::ItemIsUserCheckable); + ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_REM, item); + ui->tableSender->item(row, ST_COLS::SENDTAB_COL_REM)->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(Qt::Unchecked); if (tokens[0] == "T") { - ui->tableSender->item(row, 0)->setCheckState(Qt::Checked); + ui->tableSender->item(row, ST_COLS::SENDTAB_COL_EN)->setCheckState(Qt::Checked); } - else ui->tableSender->item(row, 0)->setCheckState(Qt::Unchecked); + else ui->tableSender->item(row, ST_COLS::SENDTAB_COL_EN)->setCheckState(Qt::Unchecked); if (tokens.length() >= 9) { - for (int i = 1; i < 9; i++) + for (int i = 1; i < ST_COLS::SENDTAB_COL_COUNT; i++) { - if (i != 4 && i != 5) { + if (i != ST_COLS::SENDTAB_COL_EXT && i != ST_COLS::SENDTAB_COL_REM) { ui->tableSender->setItem(row, i, new QTableWidgetItem(QString(tokens[i]))); } else { if (tokens[i] == "T") { @@ -387,15 +447,15 @@ void FrameSenderWindow::loadSenderFile(QString filename) } } } else { - ui->tableSender->setItem(row, 1, new QTableWidgetItem(QString(tokens[1]))); - ui->tableSender->setItem(row, 2, new QTableWidgetItem(QString(tokens[2]))); - ui->tableSender->setItem(row, 3, new QTableWidgetItem(QString(tokens[3]))); - ui->tableSender->setItem(row, 6, new QTableWidgetItem(QString(tokens[4]))); - ui->tableSender->setItem(row, 7, new QTableWidgetItem(QString(tokens[5]))); - ui->tableSender->setItem(row, 8, new QTableWidgetItem(QString(tokens[6]))); + ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_BUS, new QTableWidgetItem(QString(tokens[1]))); + ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_ID, new QTableWidgetItem(QString(tokens[2]))); + ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_LEN, new QTableWidgetItem(QString(tokens[3]))); + ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_DATA, new QTableWidgetItem(QString(tokens[4]))); + ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_TRIGGER, new QTableWidgetItem(QString(tokens[5]))); + ui->tableSender->setItem(row, ST_COLS::SENDTAB_COL_MODS, new QTableWidgetItem(QString(tokens[6]))); } inhibitChanged = false; - for (int k = 0; k < 9; k++) processCellChange(row, k); + for (int k = 0; k < ST_COLS::SENDTAB_COL_COUNT; k++) processCellChange(row, k); } } @@ -403,6 +463,45 @@ void FrameSenderWindow::loadSenderFile(QString filename) delete inFile; } +void FrameSenderWindow::onCellDoubleTap(int row, int column) +{ + if (column == ST_COLS::SENDTAB_COL_TRIGGER) + { + if (row == ui->tableSender->rowCount() - 1) + { + createBlankRow(); + } + if (row >= sendingData.count()) + { + FrameSendData tempData; + tempData.enabled = false; + tempData.setFrameType(QCanBusFrame::DataFrame); + tempData.setExtendedFrameFormat(false); + sendingData.append(tempData); + } + sendData = nullptr; + sendData = &sendingData[row]; + td = new TriggerDialog(sendData->triggers); + if (td->exec() == QDialog::Accepted) + { + sendData->triggers.clear(); + sendData->triggers.append(td->getUpdatedTriggers()); + //now have to generate the actual trigger text + QString output; + foreach (Trigger trig, sendData->triggers) + { + output += td->buildEntry(trig) + ","; + } + output.chop(1); //don't want the trailing , + inhibitChanged = true; + ui->tableSender->item(row, ST_COLS::SENDTAB_COL_TRIGGER)->setText(output); + inhibitChanged = false; + } + delete td; + td = nullptr; + } +} + void FrameSenderWindow::onCellChanged(int row, int col) { if (inhibitChanged) return; @@ -424,44 +523,54 @@ void FrameSenderWindow::handleTick() { FrameSendData *sendData; Trigger *trigger; - int elapsed = elapsedTimer.restart(); - if (elapsed == 0) elapsed = 1; - //Modifier modifier; - for (int i = 0; i < sendingData.count(); i++) + if(mutex.tryLock()) { - sendData = &sendingData[i]; - if (!sendData->enabled) + int elapsed = elapsedTimer.restart(); + if (elapsed == 0) elapsed = 1; + //Modifier modifier; + for (int i = 0; i < sendingData.count(); i++) { - if (sendData->triggers.count() > 0) + sendData = &sendingData[i]; + if (!sendData->enabled) { - for (int j = 0; j < sendData->triggers.count(); j++) //resetting currCount when line is disabled + if (sendData->triggers.count() > 0) { - sendData->triggers[j].currCount = 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) 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 = 0; + sendData->count++; + trigger->currCount++; + doModifiers(i); + updateGridRow(i); + //qDebug() << "About to try to send a frame"; + CANConManager::getInstance()->sendFrame(sendingData[i]); + if (trigger->ID > 0) trigger->readyCount = false; //reset flag if this is a timed ID trigger } } - continue; //abort any processing on this if it is not enabled. - } - if (sendData->triggers.count() == 0) return; - 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 = 0; - sendData->count++; - trigger->currCount++; - doModifiers(i); - updateGridRow(i); - qDebug() << "About to try to send a frame"; - CANConManager::getInstance()->sendFrame(sendingData[i]); - if (trigger->ID > 0) trigger->readyCount = false; //reset flag if this is a timed ID trigger - } } + + mutex.unlock(); } + else + { + qDebug() << "framesenderwindow::handleTick() couldn't get mutex, elapsed is: " << elapsedTimer.elapsed(); + } + } /// @@ -479,7 +588,7 @@ void FrameSenderWindow::doModifiers(int idx) if (sendData->modifiers.count() == 0) return; //if no modifiers just leave right now - qDebug() << "Executing mods"; + //qDebug() << "Executing mods"; for (int i = 0; i < sendData->modifiers.count(); i++) { @@ -584,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 @@ -601,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]); @@ -648,10 +774,10 @@ void FrameSenderWindow::processModifierText(int line) { thisOp.operation = parseOperation(operation); QString secondOp = Utility::grabAlphaNumeric(mods[i]); - if (mods[i][0] == '~') + if (secondOp.length() > 0 && secondOp[0] == '~') { thisOp.second.notOper = true; - mods[i] = mods[i].remove(0, 1); //remove the ~ character + secondOp = secondOp.remove(0, 1); //remove the ~ character } else thisOp.second.notOper = false; thisOp.second.bus = sendingData[line].bus; @@ -676,11 +802,10 @@ void FrameSenderWindow::processTriggerText(int line) QString trigger; //Example line: - //id0x200 5ms 10x bus0,1000ms + //id0x200 5ms 10x bus0,1000ms,ID0x202 SIG[BMS_maxChargeCurrent;300] //trigger has two levels of syntactic parsing. First you split by comma to get each //actual trigger. Then you split by spaces to get the tokens within each trigger - //trigger = ui->tableSender->item(line, 5)->text().toUpper().trimmed().replace(" ", ""); - trigger = ui->tableSender->item(line, 7)->text().toUpper(); + trigger = ui->tableSender->item(line, ST_COLS::SENDTAB_COL_TRIGGER)->text().toUpper(); if (trigger != "") { QStringList triggers = trigger.split(','); @@ -696,6 +821,7 @@ void FrameSenderWindow::processTriggerText(int line) thisTrigger.milliseconds = -1; thisTrigger.currCount = 0; thisTrigger.msCounter = 0; + thisTrigger.triggerMask = 0; thisTrigger.readyCount = true; QStringList trigToks = triggers[k].split(' '); @@ -705,32 +831,59 @@ void FrameSenderWindow::processTriggerText(int line) if (tok.left(2) == "ID") { thisTrigger.ID = Utility::ParseStringToNum(tok.right(tok.length() - 2)); - if (thisTrigger.maxCount == -1) thisTrigger.maxCount = 10000000; + //if (thisTrigger.maxCount == -1) thisTrigger.maxCount = 0; if (thisTrigger.milliseconds == -1) thisTrigger.milliseconds = 0; //by default don't count, just send it upon trigger thisTrigger.readyCount = false; //won't try counting until trigger hits + thisTrigger.triggerMask |= TriggerMask::TRG_ID; } else if (tok.endsWith("MS")) { thisTrigger.milliseconds = Utility::ParseStringToNum(tok.left(tok.length()-2)); - if (thisTrigger.maxCount == -1) thisTrigger.maxCount = 10000000; - if (thisTrigger.ID == -1) thisTrigger.ID = 0; + thisTrigger.triggerMask |= TriggerMask::TRG_MS; + //if (thisTrigger.maxCount == -1) thisTrigger.maxCount = 0; + //if (thisTrigger.ID == -1) thisTrigger.ID = 0; } else if (tok.endsWith("X")) { thisTrigger.maxCount = Utility::ParseStringToNum(tok.left(tok.length() - 1)); - if (thisTrigger.ID == -1) thisTrigger.ID = 0; - if (thisTrigger.milliseconds == -1) thisTrigger.milliseconds = 10; + thisTrigger.triggerMask |= TriggerMask::TRG_COUNT; + //if (thisTrigger.ID == -1) thisTrigger.ID = 0; + if (thisTrigger.milliseconds == -1) + { + thisTrigger.milliseconds = 10; + thisTrigger.triggerMask |= TriggerMask::TRG_ID; + } } else if (tok.startsWith("BUS")) { thisTrigger.bus = Utility::ParseStringToNum(tok.right(tok.length() - 3)); + thisTrigger.triggerMask |= TriggerMask::TRG_BUS; + } + else if (tok.startsWith("SIG[")) + { + //remove the SIG[ header + tok = tok.right(tok.size() - 4); + int semi = tok.indexOf(';'); + if (semi == -1) semi = tok.indexOf(']'); + QString signame = tok.left(semi); + thisTrigger.sigName = signame; + thisTrigger.triggerMask |= TriggerMask::TRG_SIGNAL; + semi = tok.indexOf(';'); + if (semi > -1) //have a signal value to match too + { + thisTrigger.triggerMask |= TriggerMask::TRG_SIGVAL; + int end = tok.indexOf(']'); + thisTrigger.sigValueDbl = + tok.mid(semi + 1, end - semi - 1) + .toDouble(); + } } } //now, find anything that wasn't set and set it to defaults - if (thisTrigger.maxCount == -1) thisTrigger.maxCount = 10000000; - if (thisTrigger.milliseconds == -1) thisTrigger.milliseconds = 100; - if (thisTrigger.ID == -1) thisTrigger.ID = 0; + //if (thisTrigger.maxCount == -1) thisTrigger.maxCount = 0; + //if (thisTrigger.milliseconds == -1) thisTrigger.milliseconds = 100; + //if (thisTrigger.ID == -1) thisTrigger.ID = 0; sendingData[line].triggers.append(thisTrigger); } } @@ -741,6 +894,7 @@ void FrameSenderWindow::processTriggerText(int line) thisTrigger.ID = 0; thisTrigger.maxCount = 1; thisTrigger.milliseconds = 10; + thisTrigger.triggerMask = TriggerMask::TRG_MS; sendingData[line].triggers.append(thisTrigger); } } @@ -797,27 +951,39 @@ ModifierOperationType FrameSenderWindow::parseOperation(QString op) /// void FrameSenderWindow::updateGridRow(int idx) { - qDebug() << "updateGridRow"; + //qDebug() << "updateGridRow"; inhibitChanged = true; FrameSendData *temp = &sendingData[idx]; int gridLine = idx; QString dataString; - QTableWidgetItem *item = ui->tableSender->item(gridLine, 9); + QTableWidgetItem *item = ui->tableSender->item(gridLine, ST_COLS::SENDTAB_COL_COUNT); const unsigned char *data = reinterpret_cast(temp->payload().constData()); int dataLen = temp->payload().length(); - if (item == nullptr) item = new QTableWidgetItem(); - item->setText(QString::number(temp->count)); - if (temp->frameType() != QCanBusFrame::RemoteRequestFrame) { + if (item == nullptr) + { + item = new QTableWidgetItem(); + item->setText(QString::number(temp->count)); + ui->tableSender->setItem(gridLine, ST_COLS::SENDTAB_COL_COUNT, item); + } + else + { + item->setText(QString::number(temp->count)); + } + + if (temp->frameType() != QCanBusFrame::RemoteRequestFrame) + { for (int i = 0; i < dataLen; i++) { dataString.append(Utility::formatNumber(data[i])); dataString.append(" "); } - ui->tableSender->item(gridLine, 6)->setText(dataString); - } else { - ui->tableSender->item(gridLine, 6)->setText(""); + ui->tableSender->item(gridLine, ST_COLS::SENDTAB_COL_DATA)->setText(dataString); + } + else + { + ui->tableSender->item(gridLine, ST_COLS::SENDTAB_COL_DATA)->setText(""); } inhibitChanged = false; } @@ -828,6 +994,7 @@ void FrameSenderWindow::processCellChange(int line, int col) FrameSendData tempData; QStringList tokens; int tempVal; + DBC_MESSAGE *msg; //if this is a new line then create the base object for the line if (line >= sendingData.count()) @@ -846,7 +1013,7 @@ void FrameSenderWindow::processCellChange(int line, int col) switch (col) { - case 0: //Enable check box + case ST_COLS::SENDTAB_COL_EN: //Enable check box if (ui->tableSender->item(line, 0)->checkState() == Qt::Checked) { sendingData[line].enabled = true; @@ -854,51 +1021,61 @@ void FrameSenderWindow::processCellChange(int line, int col) else sendingData[line].enabled = false; qDebug() << "Setting enabled to " << sendingData[line].enabled; break; - case 1: //Bus designation - tempVal = Utility::ParseStringToNum(ui->tableSender->item(line, 1)->text()); + case ST_COLS::SENDTAB_COL_BUS: //Bus designation + tempVal = Utility::ParseStringToNum(ui->tableSender->item(line, ST_COLS::SENDTAB_COL_BUS)->text()); if (tempVal < -1) tempVal = -1; if (tempVal >= numBuses) tempVal = numBuses - 1; sendingData[line].bus = tempVal; qDebug() << "Setting bus to " << tempVal; break; - case 2: //ID field - tempVal = Utility::ParseStringToNum(ui->tableSender->item(line, 2)->text()); + case ST_COLS::SENDTAB_COL_ID: //ID field + tempVal = Utility::ParseStringToNum(ui->tableSender->item(line, ST_COLS::SENDTAB_COL_ID)->text()); if (tempVal < 0) tempVal = 0; if (tempVal > 0x7FFFFFFF) tempVal = 0x7FFFFFFF; sendingData[line].setFrameId(tempVal); if (sendingData[line].frameId() > 0x7FF) { sendingData[line].setExtendedFrameFormat(true); ui->tableSender->blockSignals(true); - ui->tableSender->item(line, 4)->setCheckState(Qt::Checked); + ui->tableSender->item(line, ST_COLS::SENDTAB_COL_EXT)->setCheckState(Qt::Checked); + ui->tableSender->blockSignals(false); + } + msg = dbcHandler->findMessage(sendingData[line].frameId()); + if (msg) + { + ui->tableSender->blockSignals(true); + ui->tableSender->item(line, ST_COLS::SENDTAB_COL_MSGNAME)->setText(msg->name); + ui->tableSender->item(line, ST_COLS::SENDTAB_COL_LEN)->setText(QString::number(msg->len)); ui->tableSender->blockSignals(false); } qDebug() << "setting ID to " << tempVal; break; - case 3: //length field + case ST_COLS::SENDTAB_COL_LEN: tempVal = Utility::ParseStringToNum(ui->tableSender->item(line, 3)->text()); if (tempVal < 0) tempVal = 0; if (tempVal > 8) tempVal = 8; arr.resize(tempVal); sendingData[line].setPayload(arr); break; - case 4: // Ext + case ST_COLS::SENDTAB_COL_EXT: if (ui->tableSender->item(line, 4)->checkState() == Qt::Checked) { sendingData[line].setExtendedFrameFormat(true); } else { sendingData[line].setExtendedFrameFormat(false); } break; - case 5: // Rem + case ST_COLS::SENDTAB_COL_REM: if (ui->tableSender->item(line, 5)->checkState() == Qt::Checked) { sendingData[line].setFrameType(QCanBusFrame::RemoteRequestFrame); } else { sendingData[line].setFrameType(QCanBusFrame::DataFrame); } break; - case 6: //Data bytes - for (int i = 0; i < 8; i++) sendingData[line].payload().data()[i] = 0; - - tokens = ui->tableSender->item(line, 6)->text().split(" "); + case ST_COLS::SENDTAB_COL_DATA: //Data bytes +#if QT_VERSION >= QT_VERSION_CHECK( 5, 14, 0 ) + tokens = ui->tableSender->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++) @@ -907,10 +1084,10 @@ void FrameSenderWindow::processCellChange(int line, int col) } sendingData[line].setPayload(arr); break; - case 7: //triggers + case ST_COLS::SENDTAB_COL_TRIGGER: //triggers processTriggerText(line); break; - case 8: //modifiers + case ST_COLS::SENDTAB_COL_MODS: //modifiers processModifierText(line); break; } diff --git a/framesenderwindow.h b/framesenderwindow.h index d4f399c..e8ed6e8 100644 --- a/framesenderwindow.h +++ b/framesenderwindow.h @@ -5,13 +5,31 @@ #include #include #include +#include #include "can_structs.h" #include "can_trigger_structs.h" +#include "dbc/dbchandler.h" +#include "triggerdialog.h" namespace Ui { class FrameSenderWindow; } +enum ST_COLS +{ + SENDTAB_COL_EN = 0, + SENDTAB_COL_BUS = 1, + SENDTAB_COL_ID = 2, + SENDTAB_COL_MSGNAME = 3, + SENDTAB_COL_LEN = 4, + SENDTAB_COL_EXT = 5, + SENDTAB_COL_REM = 6, + SENDTAB_COL_DATA = 7, + SENDTAB_COL_TRIGGER = 8, + SENDTAB_COL_MODS = 9, + SENDTAB_COL_COUNT = 10, +}; + class FrameSenderWindow : public QDialog { Q_OBJECT @@ -22,6 +40,7 @@ public: private slots: void onCellChanged(int, int); + void onCellDoubleTap(int, int); void handleTick(); void enableAll(); void disableAll(); @@ -38,6 +57,10 @@ private: QTimer *intervalTimer; QElapsedTimer elapsedTimer; bool inhibitChanged = false; + QMutex mutex; + DBCHandler *dbcHandler; + TriggerDialog *td; + FrameSendData *sendData; void createBlankRow(); void doModifiers(int); @@ -55,6 +78,7 @@ private: void processIncomingFrame(CANFrame *frame); bool eventFilter(QObject *obj, QEvent *event); void setupGrid(); + void finishedTriggerDialog(int result); }; #endif // FRAMESENDERWINDOW_H diff --git a/help/bisector.md b/help/bisector.md index ef8bf85..2e6994d 100644 --- a/help/bisector.md +++ b/help/bisector.md @@ -1,7 +1,7 @@ Bisector Window ================= -![Bisector Window](/images/Bisector.png) +![Bisector Window](images/Bisector.png) Using the Bisector Window ========================== @@ -14,8 +14,10 @@ Frame Number - You can split right at a given frame number. All frames up to tha Percentage - Pretty much just like the Frame Number option but in percentage instead in case that is more convenient. +Bus Number - You can also split the capture to include or exclude a given bus number. This can be helpful to allow breaking up the file into per-bus files. + In all cases, you have the option of which side of the split you want to save. Click "Calculate Split" to process the split. You will see above the buttons a reference of how many frames there were in total and how many you would be saving after the split. From here you *should* be able to do one of two things: "Save split frames to a new file" - Save the new list of frames (after the split) to a file. You can save to any file format that SavvyCAN supports elsewhere. -"Replace main list with split frames" - Were this to work it would replace the frames loaded in the rest of the application with the split list. I'm sure this would be a really nice thing to be able to do. Well, you can't right now. It's not implemented yet. So, just imagine you can do this. In the mean time you can save to a file then load that file. Sorry... +"Replace main list with split frames" - Erases all messages on the main window and replaces them with the results of the bisection. You will lose all discarded frames if you haven't saved them elsewhere. diff --git a/help/canbridge.md b/help/canbridge.md new file mode 100644 index 0000000..5055d02 --- /dev/null +++ b/help/canbridge.md @@ -0,0 +1,13 @@ +CAN Bridge Window +================= + +![Bridge Window](images/CANBridge.png) + +Using the Bridge Window +========================== + +The purpose of this window is simple, to allow one to forward traffic from one bus to another. Any two buses you have connected could be bridged in this way, even if they originate on different hardware adapters. By default neither side will forward to the other. You must select different buses for Side 1 and Side 2 then enable forwarding from one to the other. You can enable bi-directional forwarding. However, this can be problematic. If you are not careful you can create an infinite loop where traffic from one side gets forwarded to the other side which then forwards to the first side, and so on. + +If you'd like to only forward some traffic then uncheck the boxes next to IDs you do not want to forward. + +At the moment this window is in the preliminary stages and, as mentioned above, could be prone to allowing infinite loops in some circumstances. \ No newline at end of file diff --git a/help/dbc_editor.md b/help/dbc_editor.md index ff78f03..eb0a742 100644 --- a/help/dbc_editor.md +++ b/help/dbc_editor.md @@ -1,17 +1,31 @@ DBC Message Editor =================== -![DBC Editor](./images/DBCEditor.png) +![DBC Editor](images/DBCEditor.png) + +This new interface places all nodes, messages, and signals into a tree structure. Each node has a list of messages that are sent by that node. Each message, in turn, has a list of signals contained within. Multiplexed and multi-level multiplexed messages are supported. Double clicking a node, message, or signal will bring up the relevant editor. + +Quick Cheat Sheet +================== +* F3 = Go to the previous item while searching +* F4 = Go to the next item while searching +* F5 = Create a new node +* F6 = Create a new message +* F7 = Create a new signal +* DEL = Delete the currently selected item (node, message, or signal) +* You can right click on nodes to get some special operations +* If you have a message selected and you create a new message it will be a clone of the selected message. Likewise for signals. +* If you have a message selected and create a signal then that signal will have the message as its parent. +* All items have icons to help you quickly determine what they are. Nodes have triangle icons, messages have envelope icons, signals have a few different icons depending on whether they're normal signals, multiplexor, or multiplexed. Working with Nodes =================== -In DBC files a node is a device on the CAN bus. For instance, the engine control unit (ECU) would be a node as would a motor controller, a battery charger, or any other device that is connected to the CAN bus. DBC files let you define nodes that are -set as either the sender or receiver of a message. This allows messages to be organized for more easy retrieval. To add -a new node click on the empty row beneath the last defined node and type a new name and optionally a comment. The comment is not used by SavvyCAN but can be filled out for your own reference. When a node is selected in the top list you will then see -in the bottom a listing of every message it sends. +In DBC files a node is a device on the CAN bus. For instance, the engine control unit (ECU) would be a node as would a motor controller, a battery charger, or any other device that is connected to the CAN bus. DBC files let you define nodes that are set as either the sender or receiver of a message. This allows messages to be organized for more easy retrieval. To add a new node click on the "New Node" button at the top or press F5 and type a new name and optionally a comment. The comment is not used by SavvyCAN but can be filled out for your own reference. The arrow next to a node can be pressed to get a list of all messages contained within. Working with Messages ===================== -The bottom list is all of the messages that are sent by the selected node. A message is defined based on its message ID. The message ID is the CAN id used for this message. For normal DBC files this creates a one to one correspondence of ID to a given CAN ID. For J1939 messages special masking is done and so more than one actual CAN id will map to the given message ID but still only one specific J1939 PGN will come through. Once a message ID is entered in for a new message it will attempt to auto populate the Data Len column with the number of data bytes that message has. This is only for your information, it is not used by SavvyCAN. You can give the DBC message a meaningful name. The "Fg" and "Bg" columns can be clicked on to set a color. This will set the foreground and background color to use for this message. These colors will be used in the main frame view on the main screen when you click "Interpret Frames." Clicking on the "Signals" column will bring up the signals editor for that message so that you can edit, add, or remove signals from the message. The "Comments" column is once again not used by SavvyCAN and only for your viewing reference. +![DBC Editor](images/DBCMessageEditor.png) + +Within each node are zero or more messages. Messages are defined based on their frame ID. For normal DBC files this creates a one to one correspondence of ID to a given CAN ID. For J1939 and GMLAN messages special masking is done and so more than one actual CAN id will map to the given message ID but still only one specific J1939 PGN will come through. Once a message ID is entered in for a new message it will attempt to auto populate the Data Len column with the number of data bytes that message has. You won't be able to set signals into bits that are past the message length so care should be taken to make this value accurate. You can give the DBC message a meaningful name. You can set the text color and background color for each message. This information will be used on the main screen when you click "Interpret Frames" but can quickly back fire. If you set black on black you will have a bad time. And, making your window look like a circus might not be ideal either. However, the choice is yours! diff --git a/help/dbc_manager.md b/help/dbc_manager.md index 2ece572..602e020 100644 --- a/help/dbc_manager.md +++ b/help/dbc_manager.md @@ -6,7 +6,7 @@ DBC File Manager Working with DBC Files ======================= -This screen allows you to load and save DBC files. SavvyCAN supports loading more than one DBC file at a time. It can even use more than one DBC file per bus. But, "Associated Bus" can be used to associate a given DBC file to only one bus. If you don't need to associate to any specific bus then set this value to -1 which means "any bus." The J1939 button causes SavvyCAN to mask out J1939 message IDs to conform to J1939 signaling. You can create a brand new DBC file by clicking "Create new DBC" button. It will be automatically named a unique name for you. You probably don't want that name though. Any time you save a DBC file its name will automatically update in the list. The "Load", "Save", "Remove", "Edit" buttons are all straight forward. You can also edit a DBC file by double clicking it in the list. +This screen allows you to load and save DBC files. SavvyCAN supports loading more than one DBC file at a time. It can even use more than one DBC file per bus. But, "Associated Bus" can be used to associate a given DBC file to only one bus. If you don't need to associate to any specific bus then set this value to -1 which means "any bus." Matching criteria is used to select J1939 or GMLAN if necessary. These two systems have special ways of interpreting the frame ID. You can create a brand new DBC file by clicking "Create new DBC" button. It will be automatically named a unique name for you. You probably don't want that name though. Any time you save a DBC file its name will automatically update in the list. The "Load", "Save", "Remove", "Edit" buttons are all straight forward. You can edit a DBC file by double clicking it in the list. DBC File Ordering diff --git a/help/framedetails.md b/help/framedetails.md index 33c2593..f202c3c 100644 --- a/help/framedetails.md +++ b/help/framedetails.md @@ -16,6 +16,10 @@ If you have a DBC file loaded which matches the ID you've selected then you will The top right graph is a histogram of all the bits and the number of times each bit was set. This can be used to quickly visually see where data has changed. -The bottom right graph is of each individual byte as its value varies over time. All textual information can be saved to a text file for later analysis. +The bottom right has 8 graphs, one for each possible byte in a standard CAN frame (CAN-FD support is coming... eventually) Double clicking one of these graphs will size it up and remove the other 7 graphs. Double clicking again brings the 8 byte view back. -The interval histogram is in logarithmic scale and shows a listing of what intervals were seen between frames. This can be used to visually see the frame timing. Some frames get sent very regularly. They will show a very pronouced bell curve. Other frames might get sent on demand. These frames will have peaks at odd places and not conform to a nice distribution. For instance, in the picture you can see that the frame shows many frames around 15-20ms timing but some around 40, 60, and 80 as well. This might indicate the frame is not always sent or it might show that the frame is getting lost. After all, those timings are all around multiples of 20ms. +As in other windows, it is possible to use the mouse wheel to scale all the graphs on this form and it is also possible to pan around by clicking and dragging. Selecting one axis will also let you scale / pan just that one axis. + +In the middle there is a bitfield view. This is color coded based upon how often each bit changes. It is called the "heatmap" for this reason. Bits that don't change often are cold and colored blue. Bits that are hot get increasingly red. In the picture you can see distinct areas where the bits are blue, light blue, green, orange. This is highly indicative of a counter. When a counter is found you will find that the lower bit changes basically every frame, the next bit up every other frame, the next bit every fourth frame, etc. This produces a very distinct pattern in the heatmap. Bits that never change are black. This view thus allows one to see where changing data is found within a frame. Chances are you can ignore all the black parts and focus only on places where some change has happened. + +The interval histogram is in logarithmic scale and shows a listing of what intervals were seen between frames. This can be used to visually see the frame timing. Some frames get sent very regularly. They will show a very pronouced bell curve. Other frames might get sent on demand. These frames will have peaks at odd places and not conform to a nice distribution. For instance, in the picture you can see that the frame shows quite a few messages around 100ms but messages extend out to around 600ms as well. Still, the logrithmic scale means that the faster interval is, by far, the most common. diff --git a/help/graphsetup.md b/help/graphsetup.md index 9a8daaf..698a821 100644 --- a/help/graphsetup.md +++ b/help/graphsetup.md @@ -39,6 +39,8 @@ Once you've set both Data Len and Little Endian you can set the bits to use. The "Stride" is not often used but will cause only every "x" values to actually be graphed. This can be used to graph a very dense set of data with less points to speed things up. +"Associated Bus" defaults to -1 which means any bus. If you know you want to only graph messages that arrived on a particular bus you can enter the number here. It is rare for two buses to have the same frame ID but with different actual messages but not unheard of. If you find that your graphs appear to randomly jitter back and forth between two distinct sets of values this may be the cause. + "Only Points" will graph using disconnected points instead of lines. "Point Style" has a list of every style you can use for points. The default is to not show the points and instead draw a line through where the points are at. But, there are many other options. @@ -48,3 +50,5 @@ Once you've set both Data Len and Little Endian you can set the bits to use. The "Line Color" changes the color of the graphed line. It is automatically randomly set for new graphs but if you don't like the random color you can click the color and select a better one. "Fill Color" If you're feeling fancy you can also specify a fill color. The default fill color has an alpha value of 0 meaning it is completely transparent / no used. If you set a fill color you will need to set the alpha channel as well. An alpha of even 40-60 is usually plenty. + +DO NOTE -> all the options starting at "Point Style" will drastically affect performance. If you try to graph a million points with a special point style, large line thickness, and a fill color it WILL be slow. For maximum speed just graph colored lines with no points. diff --git a/help/images/Bisector.png b/help/images/Bisector.png index 79e0f94..cd8d2a2 100644 Binary files a/help/images/Bisector.png and b/help/images/Bisector.png differ diff --git a/help/images/CANBridge.png b/help/images/CANBridge.png new file mode 100644 index 0000000..91207e0 Binary files /dev/null and b/help/images/CANBridge.png differ diff --git a/help/images/DBCEditor.png b/help/images/DBCEditor.png index 28a7af9..3b0bef5 100644 Binary files a/help/images/DBCEditor.png and b/help/images/DBCEditor.png differ diff --git a/help/images/DBCManager.png b/help/images/DBCManager.png index 42ff749..2c8ec68 100644 Binary files a/help/images/DBCManager.png and b/help/images/DBCManager.png differ diff --git a/help/images/DBCMessageEditor.png b/help/images/DBCMessageEditor.png new file mode 100644 index 0000000..8edda47 Binary files /dev/null and b/help/images/DBCMessageEditor.png differ diff --git a/help/images/FlowView.png b/help/images/FlowView.png index 3f8389c..3ba6531 100644 Binary files a/help/images/FlowView.png and b/help/images/FlowView.png differ diff --git a/help/images/FrameInfoWindow.png b/help/images/FrameInfoWindow.png index 7ce64f1..4dd251c 100644 Binary files a/help/images/FrameInfoWindow.png and b/help/images/FrameInfoWindow.png differ diff --git a/help/images/GraphSetup.png b/help/images/GraphSetup.png index 91aae07..16a32c3 100644 Binary files a/help/images/GraphSetup.png and b/help/images/GraphSetup.png differ diff --git a/help/images/ISOTPDecoder.png b/help/images/ISOTPDecoder.png index 18f9cd8..40f9733 100644 Binary files a/help/images/ISOTPDecoder.png and b/help/images/ISOTPDecoder.png differ diff --git a/help/images/MainScreen.png b/help/images/MainScreen.png index 0916c60..d064e3f 100644 Binary files a/help/images/MainScreen.png and b/help/images/MainScreen.png differ diff --git a/help/images/Playback.png b/help/images/Playback.png index 2dbe9d7..82fd375 100644 Binary files a/help/images/Playback.png and b/help/images/Playback.png differ diff --git a/help/images/Preferences.png b/help/images/Preferences.png index bac038d..5ce7c7c 100644 Binary files a/help/images/Preferences.png and b/help/images/Preferences.png differ diff --git a/help/images/SignalEditor.png b/help/images/SignalEditor.png index f0cb215..1bb7865 100644 Binary files a/help/images/SignalEditor.png and b/help/images/SignalEditor.png differ diff --git a/help/images/UDS_Scanner.png b/help/images/UDS_Scanner.png index fa4e87d..c798321 100644 Binary files a/help/images/UDS_Scanner.png and b/help/images/UDS_Scanner.png differ diff --git a/help/mainscreen.md b/help/mainscreen.md index b36bed8..e41edaf 100644 --- a/help/mainscreen.md +++ b/help/mainscreen.md @@ -12,17 +12,18 @@ The Main Frame List The main frame list takes up the majority of the main screen. This list consists of the following sections: -- Timestamp: The timestamp is either in microseconds or seconds. This is a setting in preferences. Either way, the timestamp can have microsecond resolution. The difference is just whether there is a decimal point or not. GVRET has the ability to maintain full microsecond resolution for timestamping purposes. There is a third timing mode where the timestamp can be customized and is based upon the actual "clock" time. +- Timestamp: The timestamp is either in microseconds or seconds. This is a setting in preferences. Either way, the timestamp can have microsecond resolution. The difference is just whether there is a decimal point or not. Many of the CAN capture devices have the ability to maintain full microsecond resolution for timestamping purposes. There is a third timing mode where the timestamp can be customized and is based upon the actual "clock" time. - ID: The ID is specified either in hexadecimal or decimal (a preference you can set). This is the message identifier sent over the CAN bus. - RTR: 0 = Standard Message 1 = Remote transmit request. An RTR frame merely asks a node to send a message, it has no payload of its own. - Ext: 0 = Standard message (11 bit ID). 1 = Extended message (29 bit ID) - Dir: Either "Rx" or "Tx" to show whether SavvyCAN has received or sent this message. - Bus: SavvyCAN supports a variety of capture hardware. GVRET compatible devices can support more than one bus. The bus a frame came in on is specified here. Many file formats do not specify bus and thus all frames will be loaded as bus 0. -- Len: The number of data bytes that were sent with this frame. It can range from 0 to 8. +- Len: The number of data bytes that were sent with this frame. It can range from 0 to 8 for standard CAN and 0 to 64 for CAN-FD. - ASCII: A character based view of the CAN bytes in ASCII characters. Many systems that send serial numbers or VIN numbers will send them in ASCII and these will thus be visible here. - Data: All of the data bytes separated by spaces. Can be in either hexadecimal or decimal (preference). If "Interpret Frames" is checked you will also see extra data at the end of any frames that have DBC data. To see the rest of this data click upon the frame in the list. It will automatically expand to show all signals attached to that frame. + There is also a setting to limit the number of displayed bytes per line. This is especially useful for CAN-FD traffic. The Bottom Statusbar @@ -38,31 +39,33 @@ At the very bottom of the main screen is a status bar with three sections. The Rest of the Main Window =========================== -To the right of the main frames list is an area that shows the total number of captured frames and the frames per second. Total frames might not match the number of shown frames. If you've deselected any IDs in the filter list then fewer frames will be shown. Frames per second is calculated as an average and so will wind up or down when there is a sudden change. +*To the right of the main frames list is an area that shows the total number of captured frames and the frames per second. Total frames might not match the number of shown frames. If you've deselected any IDs in the filter list then fewer frames will be shown. Frames per second is calculated as an average and so will wind up or down when there is a sudden change. -Suspend Capturing / Resume Capturing is a button that will temporarily disable frame capture or re-enable it. This can be used to keep everything connected without capturing traffic for a short time. This can help to not capture traffic in between tests. +*Suspend Capturing / Resume Capturing is a button that will temporarily disable frame capture or re-enable it. This can be used to keep everything connected without capturing traffic for a short time. This can help to not capture traffic in between tests. -The "Normalize Frame Timing" button is used to reset the lowest timestamp to "0" and offset all other timestamps accordingly. This is useful to remove the starting offset when you start up a device long before actual traffic starts. SavvyCAN is designed such that this doesn't really matter most of the time but normalizing the timing might be useful to help correlate the timing between two different captures. +*The "Normalize Frame Timing" button is used to reset the lowest timestamp to "0" and offset all other timestamps accordingly. This is useful to remove the starting offset when you start up a device long before actual traffic starts. SavvyCAN is designed such that this doesn't really matter most of the time but normalizing the timing might be useful to help correlate the timing between two different captures. -The "Clear Frames" button will erase all captured messages. They will be irreversibly erased and all memory will be freed. +*The "Clear Frames" button will erase all captured messages. They will be irreversibly erased and all memory will be freed. -The "Auto Scroll Window" checkbox will cause the main frame list to hunt toward the bottom of the list as frames come in. It will normally not be quite +*"Keep Filters While Clearing" will keep all the filters intact if you push the "Clear Frames" button. Otherwise all filters will also be cleared. + +*The "Auto Scroll Window" checkbox will cause the main frame list to hunt toward the bottom of the list as frames come in. It will normally not be quite at the very bottom as, for performance reasons, the program runs at quarter second updates to things like the auto scroll. Thus, the main list will be scrolled to the bottom four times per second. -The "Interpret Frames" checkbox is used to specify whether the loaded DBC file should be used to interpret all available messages and signals. One might want +*The "Interpret Frames" checkbox is used to specify whether the loaded DBC file should be used to interpret all available messages and signals. One might want this off for performance reasons (interpreting takes some extra processor power and RAM) or to declutter the main frame list. -The "Overwrite Mode" checkbox is used to ensure that only the newest frame for each message ID is shown. That is, if 100 messages with ID 0x105 come in you +*The "Overwrite Mode" checkbox is used to ensure that only the newest frame for each message ID is shown. That is, if 100 messages with ID 0x105 come in you will see only the newest one. This is generally used alongside "Interpret Frames" to interpret frames and always see the up-to-date information. -"Expand All Rows" will expand all the rows to show every signal in every message. This will take a **VERY** long time if there are many messages loaded. Because of this, you may receive a warning if the program determines that this will take an excessive amount of time to complete. You can make it work faster by filtering away any unneeded messages. +*"Expand All Rows" will expand all the rows to show every signal in every message. This will take a **VERY** long time if there are many messages loaded. Because of this, you may receive a warning if the program determines that this will take an excessive amount of time to complete. You can make it work faster by filtering away any unneeded messages. -"Collapse All Rows" will drop all rows back to taking up only one line. This can also take a while to run and will also warn if the operation seems like it will take a very long time to complete. +*"Collapse All Rows" will drop all rows back to taking up only one line. This can also take a while to run and will also warn if the operation seems like it will take a very long time to complete. -"Bus Filtering" allows for messages to be shown or hidden based on which bus they came in on. +*"Bus Filtering" allows for messages to be shown or hidden based on which bus they came in on. -"Frame Filtering" provides a list of all the frame IDs seen so far. Any ID which is checked will be shown in the main list. Any ID which is unchecked will not. +*"Frame Filtering" provides a list of all the frame IDs seen so far. Any ID which is checked will be shown in the main list. Any ID which is unchecked will not. This can be used to hone in on frames of importance while hiding frames that are currently of no interest. The filtered list can be saved as well. diff --git a/help/playbackwindow.md b/help/playbackwindow.md index effd266..e8cd4a2 100644 --- a/help/playbackwindow.md +++ b/help/playbackwindow.md @@ -1,7 +1,7 @@ Playback Window =============== -![Playback Window](./images/PlayBack.png) +![Playback Window](./images/Playback.png) Preparing Frames for Playback @@ -18,7 +18,9 @@ The playback window can send frames on a specific bus, all buses (be careful wit The next order of business is frame timing. There are two approaches possible here. If you click "Use original frame timing from captured frames" then frames will be sent out in approximately the same timing as they came in with. The word approximately is used because it is difficult to get 1ms timing precision on a desktop OS. Frames that come in rapidly might have a 2-3ms jitter. In practice this is almost always irrelevant. This setting is suitable for nearly all uses. -Alternatively, it is also possible to send on a set schedule. With the "Use original" checkbox not checked you can set a playback speed in milliseconds and a burst rate. Burst means that it'll send that many frames every tick. So, if you have a burst of 5 and a timing of 10ms then every 10ms 5 frames will be sent. This mode can provide for a predictable number of frames per second and could be useful to test how quickly a device really requires traffic without faulting. But, it will potentially drastically alter the timing of frames compared to their timing when they were captured. +Alternatively, it is also possible to send on a set schedule. With the "Use original" checkbox not checked you can set a playback speed in milliseconds and a burst rate. Burst means that it'll send that many frames every tick. So, if you have a burst of 5 and a timing of 10ms then every 10ms 5 frames will be sent. This mode can provide for a predictable number of frames per second and could be useful to test how quickly a device really requires traffic without faulting. But, it will potentially drastically alter the timing of frames compared to their timing when they were captured. You can set a burst rate as well. Burst Rate is the number of frames sent every "tick." This can speed up how fast you can send traffic. + +There is also now a checkbox that allows for waiting for traffic before sending CAN frames when you click play forward or backward. If this checkbox is checked you will see (WAITING) to the left of the number of frames below "Current frame." Once any CAN traffic starts to come in your frames will begin to playback automatically. Why would you want to do this? Well, the most likely reason is that you want to play back a CAN capture but you only want to do so once the vehicle has been powered on. And, you want to wait until the CAN buses are active so that you don't fault by sending traffic into nowhere. Lastly, this allows your playback to happen very rapidly after start up which might otherwise be tougher to pull off accurately. The top of the window has a series of 6 icons all in a row: diff --git a/help/preferences.md b/help/preferences.md index 5583a9a..2161206 100644 --- a/help/preferences.md +++ b/help/preferences.md @@ -5,53 +5,71 @@ Preference Window There are a variety of preferences that can be set in the program and persisted for future sessions. +Main Form Settings +=================== +* Autoscroll main frame winow by default - When capturing frames should the main window track the newest incoming messages or stay where it was? This option defaults it to follow the incoming frames by default. The main window has a toggle for auto scroll. This setting just sets the default value of that toggle. +* Maximum data bytes per line - Really must helpful for CAN-FD traffic. Setting this to 8, 16, or 32 can help to be able to see all the bytes without the window having to be extremely wide. + General Settings ==================== -"Save/Restore window positions and sizes": If this is set then the size and placement of the various windows in this application will be saved when the program is closed and loaded when each window is brought back up in the future. This can be used to create your own preferred layout. If you'd rather things come up in their default state every time then you can uncheck this box. +* "Save/Restore window positions and sizes": If this is set then the size and placement of the various windows in this application will be saved when the program is closed and loaded when each window is brought back up in the future. This can be used to create your own preferred layout. If you'd rather things come up in their default state every time then you can uncheck this box. -"Save/Restore CAN bus connections": This will cause the program to remember the devices you were connected to last time you ran the program and attempt to reconnect to them upon start up. +* "Save/Restore CAN bus connections": This will cause the program to remember the devices you were connected to last time you ran the program and attempt to reconnect to them upon start up. -"Display values as hexadecimal": A lot of the time people who are doing CAN reverse engineering like to see values in hexadecimal (base 16) instead of the more familiar decimal (base 10) system. Checking this box will cause most of the values in the application to show up in hex. This applies to CAN ids and data bytes. Unchecking this causes values to default to decimal instead. The reason for using hex is that each hex digit is 4 bits. Integers on a computer tend to be in multiples of 8 - 8, 16, 32, 64. So, hex digits have a direct mapping to the underlying binary. Decimal does not have this correspondence AT ALL. But, the choice is yours. +* "Display values as hexadecimal": A lot of the time people who are doing CAN reverse engineering like to see values in hexadecimal (base 16) instead of the more familiar decimal (base 10) system. Checking this box will cause most of the values in the application to show up in hex. This applies to CAN ids and data bytes. Unchecking this causes values to default to decimal instead. The reason for using hex is that each hex digit is 4 bits. Integers on a computer tend to be in multiples of 8 - 8, 16, 32, 64. So, hex digits have a direct mapping to the underlying binary. Decimal does not have this correspondence AT ALL. But, the choice is yours. -"Require validation of GVRET connection": GVRET style devices run over a serial connection. Serial connections can be finicky sometimes and so the connection can be validated to prove that everything is really still operating and talking. There probably isn't any reason to turn this off except while debugging to see if it changes anything. Mostly just don't touch this. +* "Require validation of GVRET connection": GVRET style devices run over a serial connection. Serial connections can be finicky sometimes and so the connection can be validated to prove that everything is really still operating and talking. There probably isn't any reason to turn this off except while debugging to see if it changes anything. Mostly just don't touch this. -"Label filters using messages from DBC files": Part of a two part system. If this is selected and a given DBC file also has its checkbox checked then the IDs list on the main screen (lower right) used for filtering will have not only the IDs but also the message name from the DBC file. This will allow for more easily determining what a given message means. Sometimes IDs can be hard to remember. Do note that this checkbox must be checked or the checkboxes for each loaded DBC file won't do anything. This is the master on/off switch. +* "Use filtered frames in sub-windows": The main window has a filtering interface where you can uncheck IDs to hide them. Ordinarily when you bring up one of the other windows it will still use the main unfiltered list. Sometimes you really do want to deal with the filtered list of frames even in the other windows. If this is checked then the other windows will see the filtered list and not the unfiltered actual list of frames that have been captured. -"Time Keeping": There are a variety of ways one could timestamp CAN frames as they come into the program. Selecting "Seconds" will cause the timestamp to be expressed as seconds since the frame list was last cleared. This tends to be an easy choice to work with. "Microseconds" will express the timestamp as millionths of a second since the last time the frame list was cleared. This is exactly like "Seconds" mode but without any decimal point. You might find this to be a bit hard to conceptualize. The last option is "System Clock" this will timestamp frames with the current system time when the frame came in. This is still very precise but now you'll get an absolute time stamp with the full date and time. The display of this mode can be changed by editing the "Time Format String" value. It defaults to an output that looks like "JAN-10 12:34:53.234" But you can set it to other values. Look here to find a reference for how you can create new format strings: http://doc.qt.io/qt-4.8/qdatetime.html#toString +* "OpenGL Accelerated AntiAliased Graphing": Checking this will cause all of the graphs to use OpenGL 3D acceleration. Most modern machines have some form of 3D acceleration so this option should be OK to use. If you check this your graphs will look a lot better and on good hardware should also be faster. In the future other options are likely to be added to the graphing screen that will likely only be enabled if OpenGL mode is also enabled. Try enabling this and see if performance is still good. It's safe to leave it off if in doubt. -"Use filtered frames in sub-windows": The main window has a filtering interface where you can uncheck IDs to hide them. Ordinarily when you bring up one of the other windows it will still use the main unfiltered list. Sometimes you really do want to deal with the filtered list of frames even in the other windows. If this is checked then the other windows will see the filtered list and not the unfiltered actual list of frames that have been captured. +* "CAN Frame Pre-allocation Size" - This requires a bit of explanation and caution. When SavvyCAN starts it pre-allocates a giant buffer for incoming CAN traffic. Otherwise as traffic comes in the program would have a limited amount of space allocated to receive the traffic. If this reserved space runs out then the program would have to go ask the operating system for more and copy all existing frames to the newer, bigger buffer. This is a slow process. So, instead a giant buffer is allocated up front (by default 10 million frames worth!). You aren't likely to exceed this value and so it never has to ask for more memory and things run smoothly. 10M frames is about 1/2 of a gigabyte. This is a lot of memory but very doable for most modern PCs. But, if you are running on a Raspberry Pi it may be a good idea to turn this down to, say, 1M instead. You may be tempted to make this value really large so that, no matter what, it never has to reallocate. But, setting this 100x bigger would try to allocate 50GB of RAM. You probably don't have that much RAM to spare. So, be cautious if you raise this value. 10M should be enough for most anyone. Even if you did happen to exceed the value the program won't crash, it will just pause for a long time as it creates a larger buffer and moves everything over. -"OpenGL Accelerated AntiAliased Graphing": Checking this will cause all of the graphs to use OpenGL 3D acceleration. Most modern machines have some form of 3D acceleration so this option should be OK to use. If you check this your graphs will look a lot better and on good hardware should also be faster. In the future other options are likely to be added to the graphing screen that will likely only be enabled if OpenGL mode is also enabled. Try enabling this and see if performance is still good. It's safe to leave it off if in doubt. +* "Time Keeping": There are a variety of ways one could timestamp CAN frames as they come into the program. Selecting "Seconds" will cause the timestamp to be expressed as seconds since the frame list was last cleared. This tends to be an easy choice to work with. "Microseconds" will express the timestamp as millionths of a second since the last time the frame list was cleared. This is exactly like "Seconds" mode but without any decimal point. You might find this to be a bit hard to conceptualize. The last option is "System Clock" this will timestamp frames with the current system time when the frame came in. This is still very precise but now you'll get an absolute time stamp with the full date and time. The display of this mode can be changed by editing the "Time Format String" value. It defaults to an output that looks like "JAN-10 12:34:53.234" But you can set it to other values. Look here to find a reference for how you can create new format strings: http://doc.qt.io/qt-4.8/qdatetime.html#toString -"Autoscroll main frame window by default": If checked it will automatically check the relevant checkbox on the main screen when the program starts. This will cause the view to automatically scroll to the bottom by default. This is merely a convenience option if you'd prefer it to be the default. +Font Settings +============== +* "Use fixed-width font in tables" - The default is to use the normal system font for everything in the program. The problem is, most default fonts are not fixed width. Why would you want fixed width? This makes things line up better between lines. With fixed width fonts you know that the third byte of one line is directly over top of the third byte for the line one down. This can make it easier to scan through multiple lines. +* Size - You can set the default font size used in the application. Setting this up or down a bit may help things to look better to you. Getting to absurd is likely to make the program look really bad. This setting may be required if you use a 4k monitor and your operating system doesn't want to cooperate. Flow View Settings ================== -"Use timestamp mode by default": This is another convenience option. With this checked the Flow view will default to using time stamps on the graphing area instead of frame numbers. +* "Use timestamp mode by default": This is another convenience option. With this checked the Flow view will default to using time stamps on the graphing area instead of frame numbers. -"Set Auto Reference by default": The Flow view can either use static referencing or dynamic referencing (see the Flow view documentation for more info). If you'd like to use dynamic referencing and automatically set the reference by default then check this option. +* "Set Auto Reference by default": The Flow view can either use static referencing or dynamic referencing (see the Flow view documentation for more info). If you'd like to use dynamic referencing and automatically set the reference by default then check this option. + +* "Hexadecimal Graph Y Axis" - Use 00-FF instead of 0-255 for the Y axis. Playback Window Settings ======================== -"Loop by default": Set the playback window to default to looping infinitely by default. +* "Loop by default": Set the playback window to default to looping infinitely by default. -"Default playback speed (ms)": Set the default timing for playback +* "Default playback speed (ms)": Set the default timing for playback -"Default Sending Bus": Set a default for which bus to send frames on. +* "Default Sending Bus": Set a default for which bus to send frames on. +DBC Settings +============= +* "Label DBC Messages by default" - Normally the filter section of the main screen just shows the frame IDs. If you label DBC signals you will actually see the name of the DBC message next to the ID. + +* "Ignore DBC message colors" - It's possible to make DBC messages have rainbow colors. Each message can have a different background and foreground color. Sometimes they can get set to some really silly values. To save your sanity you have the option to completely ignore all colors set in the DBC file and just use the default colors for everything. + File Info And Comparator Window Settings ======================================== -"Auto expand all nodes": Both of the referenced windows have tree views that potentially have a large number of nodes. It's more neat not to expand them all by default but it also then requires more clicks if you have to expand them to view the information. So, you can set whether you'd like to expand them all by default or not. +* "Auto expand all nodes": Both of the referenced windows have tree views that potentially have a large number of nodes. It's more neat not to expand them all by default but it also then requires more clicks if you have to expand them to view the information. So, you can set whether you'd like to expand them all by default or not. + +* "Hexadecimal Graph Y Axis" - As with the Flowview, it is possible to change the Y axis to hexadecimal instead of decimal. MQTT Settings ============= -MQTT allows SavvyCAN to connec to a broker for CAN transmission over the internet. You will need to set up the host, port, username, and password. These things are determined by your broker. You can run your own on Linux with Mosquitto. +* MQTT allows SavvyCAN to connect to a broker for CAN transmission over the internet. You will need to set up the host, port, username, and password. These things are determined by your broker. You can run your own on Linux with Mosquitto. Yes, api.savvycan.com really does exist but, no, you can't actually connect as Anonymous and use it. Sorry... Final Word of Warning diff --git a/help/signaleditor.md b/help/signaleditor.md index cb05ed4..3429859 100644 --- a/help/signaleditor.md +++ b/help/signaleditor.md @@ -3,12 +3,25 @@ DBC Signal Editor ![Signal Editor](./images/SignalEditor.png) +Not So Secret Things +===================== +First of all, here are the things that might otherwise pass under the radar but are functions of this form: + +* Pretty much everything on this form now has secret undo functionality. If you make a mistake you can press CTRL-Z to revert it. This will revert practically anything you've done on this form. + +* The 8x8 data grid is not locked that way. CAN-FD signals are supported and so up to 64 bytes can be shown in the bitfield. The bitfield will automatically adjust for the number of bytes in a given message. However, you can also switch the views yourself by middle clicking in the bitfield. + +* If you right click a different signal in the bitfield you will then be editing that signal instead. This lets you quickly move between signals in the same message. However, this only works for other signals that are either not multiplexed (if you started with a non-multiplexed signal when editing) or otherwise only other signals that have the same multiplexor value. + + Defining and Editing Signals ============================ -On the right side you can rename the signal and you'll see that it is renamed in the DBC window as well. +At the top you can rename the signal and you'll see that it is renamed in the DBC window as well. -"Bit Length" - This sets how many bits the signal uses. Once you do this you'll see that that many bits are now highlighted in the 8x8 data grid above. The black bit is the "start" bit, green bits are the other bits in the signal. Gray bits are already used by another signal. You can still use them for the current signal too but doing so would be quite unusual unless you have multiplexed signals. +The majority of the view is taken up by the bitfield grid. This view shows all the signals contained within this message (that have the same multiplexor value as this one). They're all labeled as well. Bonus fun fact - SavvyCAN technically does support CAN-FD DBC files! That's right, you can load DBC files with CAN-FD signals and they will work. The bitfield will adjust to show enough bits to show the whole message. + +"Bit Length" - This sets how many bits the signal uses. Once you do this you'll see that that many bits are now highlighted in the data grid. The black bit with diagonal stripes is the "start" bit, green bits (with stripes the other way) are the other bits in the signal. Bits used by other signals are colored according to a secret list of colors. They also have the signal name. You should not overlap onto other signals. This could be possible with multiplexed signals but you will NOT see signals on this grid that are not part of the same multiplexor value as this signal so you should NOT overlap here. "Byte Order" - The way that the green bits are filled out is changed by this checkbox. Checking it selects little endian mode whereas deselecting chooses big endian mode. This will have an effect on any signal that crosses byte boundaries. The simplest explanation is that little endian signals start at the start bit and then go "down" in bit numbers while big endian mode goes "up." diff --git a/help/uds_scanner.md b/help/uds_scanner.md index 1254e30..6b57d68 100644 --- a/help/uds_scanner.md +++ b/help/uds_scanner.md @@ -12,13 +12,15 @@ This is essentially another CAN fuzzing window but a very special one. This wind Using the UDS Scan Window ========================== +This window allows one to set a list of tests to perform. They will be done in order. You are free to change the parameters of each separately. For instance, one test can scan 0x7E0 through 0x7E7 and the next test can can only 0x600. + UDS queries are sent out on the bus from "Starting ID" to "Ending ID". Usually UDS compliant ECUs will respond to 0x7E0 through 0x7E7 which is why those are the defaults. Some vehicles use UDS "like" protocols on other IDs. Usually UDS nodes reply with an ID 8 higher than the request ID. This is thus the default in the program. However, some nodes cheat and do not do this. It is quite common for responses to come from an address 16 higher instead. Sometimes the reply address has no resemblance to the listening address. To deal with this situation there is a checkbox "Allow adaptive reply offset." If this is checked then replies will be accepted no matter what address they come from. Deselecting this will cause only replies of the proper offset to be accepted. The offset defaults to 8 but can be changed with the "Reply Offset" selector. Additionally, you can select which bus to scan and set how long you want to wait for replies. -"Show tests with no Replies" - This checkbox does what it says. It is a personal preference whether you'd like to see an entry in the list for scans that returned no results. Sometimes an ECU will just plain ignore messages it doesn't like. In that case you have the option to see an entry in the list telling you that the message was ignored or whether you'd prefer to reduce clutter and just skip anything that had no reply. +"Show 'No Reply'" - This checkbox does what it says. It is a personal preference whether you'd like to see an entry in the list for scans that returned no results. Sometimes an ECU will just plain ignore messages it doesn't like. In that case you have the option to see an entry in the list telling you that the message was ignored or whether you'd prefer to reduce clutter and just skip anything that had no reply. -You need to also set a type of scan to do. You can select more than one type but if you don't set any then you aren't going to see any results. Keep that in mind as none are checked by default. +You need to also set a type of scan to do. Each test you register will do one type of scan but you are free to create as many tests as you like. -"Read By ID" - UDS allows one to read data from the ECU by an ID number. These are not defined anywhere and are custom to each ECU. But, you can use this to scan a range of IDs to see if you get a response to any of them. There is no real standard for how many bytes the ID will be. It could be 1, it could be 2, 3, 4. It's likely to be around 2. You can set the number of subfunction bytes which will set the size of the ID. Then you can set the upper and lower bound to scan. +"Read By ID" - UDS allows one to read data from the ECU by an ID number. Many of these are not defined anywhere and are custom to each ECU. But, you can use this to scan a range of IDs to see if you get a response to any of them. There is usually no real standard for how many bytes the ID will be. It could be 1, it could be 2, 3, 4. It's likely to be around 2. You can set the number of subfunction bytes which will set the size of the ID. Then you can set the upper and lower bound to scan. "Read By Addr" - You can also read data by address. Like IDs the address could be different sizes depending on the hardware you are querying. This works the same as reading by ID but by address instead. @@ -26,8 +28,10 @@ You need to also set a type of scan to do. You can select more than one type but "ECU Reset" - The ECU might also support being reset by a UDS message. This scan type will try the various reset types and see which are supported. -"Security Access" - When a device is first started and normally operating it will generally not allow you to do potentially dangerous or sensitive operations such as downloading firmware or changing parameters. To do these things you need to enter a different security level. The security access mechanism is used for this. There are a few different security levels that are likely to be supported. This scan type will attempt to find security levels and see if they are protected or not. That is, unfortunately for the casual cracker, most of the time the elevate security levels will be protected by a challenge/response system. The ECU will send you a challenge in the form of one or more random looking bytes. You are tasked with returning the proper response for those challenge bytes. You generally don't have very many guesses before your hands are slapped. Sometimes the C/R is actually fixed and you can just capture valid traffic once and then use the same response forever. Sometimes the C/R is stupidly easy or there are only a couple of different possible answers. Sometimes the ECU developer actually took more than 2 seconds to implement these features and your work will be cut out for you. This scan type will find which levels the ECU seems to support but is unlikely to actually unlock them. +"Security Access" - When a device is first started and normally operating it will generally not allow you to do potentially dangerous or sensitive operations such as downloading firmware or changing parameters. To do these things you need to enter a different security level. The security access mechanism is used for this. There are a few different security levels that are likely to be supported. This scan type will attempt to find security levels and see if they are protected or not. That is, unfortunately for the casual cracker, most of the time the elevated security levels will be protected by a challenge/response system. The ECU will send you a challenge in the form of one or more random looking bytes. You are tasked with returning the proper response for those challenge bytes. You generally don't have very many guesses before your hands are slapped. Sometimes the C/R is actually fixed and you can just capture valid traffic once and then use the same response forever. Sometimes the C/R is stupidly easy or there are only a couple of different possible answers. Sometimes the ECU developer actually took more than 2 seconds to implement these features and your work will be cut out for you. This scan type will find which levels the ECU seems to support but is unlikely to actually unlock them. "Tester Present" - A scan that tries to see if tester present is supported. It almost certainly is. This scan can be used to sweep a wide range of addresses just to narrow down the list of addresses to scan more thoroughly. Normally tester present is sent periodically by a connected device just to let the ECU know that someone is still there. "Wildcard" - Allows for you to set a lower and upper range for the service byte as well as the number of subfunction bytes and the range there as well. This allows for UDS fuzzing by shooting the moon and trying a huge range of traffic just to see what is supported and what isn't. This test can take a VERY long time if you aren't careful but will thoroughly determine what the ECU will support and what it won't. + +"Run test in session type" is supported for some of the above scan types. This will cause the program to attempt to put the ECU into the chosen session type before doing the test. This can be useful as some things will only be supported in diagnostics mode or programming mode. Unfortunately, entering programming mode is likely to require one to elevate the security level which this program is not set up to do (that being a proprietary process.) diff --git a/install.sh b/install.sh index 1b72104..1ffe6f9 100755 --- a/install.sh +++ b/install.sh @@ -1,16 +1,12 @@ #!/bin/sh CURDIR=`pwd` -APPDIR="$(dirname -- "$(readlink -f -- "${0}")" )" -cd "$APPDIR" - -# Set absolute path work around -sed -e "s,FULLPATH,$PWD,g" SavvyCAN.desktop > SavvyCAN.desktop.temp +# Set absolute path +sed -e "s,,$CURDIR,g" SavvyCAN.desktop > SavvyCAN.desktop.temp cp SavvyCAN.desktop.temp ~/.local/share/applications/SavvyCAN.desktop cp SavvyCAN.desktop.temp ~/Desktop/SavvyCAN.desktop rm SavvyCAN.desktop.temp echo "Installed SavvyCAN icons on menu and desktop !" - diff --git a/jsedit.cpp b/jsedit.cpp index 5a994d0..8893443 100644 --- a/jsedit.cpp +++ b/jsedit.cpp @@ -481,12 +481,12 @@ void JSHighlighter::mark(const QString &str, Qt::CaseSensitivity caseSensitivity QStringList JSHighlighter::keywords() const { - return m_keywords.toList(); + return m_keywords.values(); } void JSHighlighter::setKeywords(const QStringList &keywords) { - m_keywords = QSet::fromList(keywords); + m_keywords = QSet(keywords.begin(), keywords.end()); rehighlight(); } @@ -931,7 +931,8 @@ void JSEdit::resizeEvent(QResizeEvent *e) void JSEdit::wheelEvent(QWheelEvent *e) { if (e->modifiers() == Qt::ControlModifier) { - int steps = e->delta() / 20; + QPoint numDegrees = e->angleDelta(); + int steps = numDegrees.y() / 20; steps = qBound(-3, steps, 3); QFont textFont = font(); int pointSize = textFont.pointSize() + steps; diff --git a/main.cpp b/main.cpp index c9466dc..df2f4a4 100644 --- a/main.cpp +++ b/main.cpp @@ -24,6 +24,12 @@ public: int main(int argc, char *argv[]) { +#ifdef QT_DEBUG + //uncomment for verbose debug data in application output + //qputenv("QT_FATAL_WARNINGS", "1"); + //qSetMessagePattern("Type: %{type}\nProduct Name: %{appname}\nFile: %{file}\nLine: %{line}\nMethod: %{function}\nThreadID: %{threadid}\nThreadPtr: %{qthreadptr}\nMessage: %{message}"); +#endif + SavvyCANApplication a(argc, argv); //Add a local path for Qt extensions, to allow for per-application extensions. diff --git a/mainsettingsdialog.cpp b/mainsettingsdialog.cpp index 77bf257..eae6272 100644 --- a/mainsettingsdialog.cpp +++ b/mainsettingsdialog.cpp @@ -21,7 +21,7 @@ MainSettingsDialog::MainSettingsDialog(QWidget *parent) : ui->comboSendingBus->addItem(tr("None")); ui->comboSendingBus->addItem(tr("0")); ui->comboSendingBus->addItem(tr("1")); - ui->comboSendingBus->addItem(tr("Both")); + ui->comboSendingBus->addItem(tr("All")); ui->comboSendingBus->addItem(tr("From File")); //update the GUI with all the settings we have stored giving things @@ -48,6 +48,7 @@ MainSettingsDialog::MainSettingsDialog(QWidget *parent) : ui->cbLoadConnections->setChecked(settings.value("Main/SaveRestoreConnections", false).toBool()); ui->spinFontSize->setValue(settings.value("Main/FontSize", ui->cbDisplayHex->font().pointSize()).toUInt()); + ui->cbFontFixedWidth->setChecked(settings.value("Main/FontFixedWidth", false).toBool()); bool secondsMode = settings.value("Main/TimeSeconds", false).toBool(); bool clockMode = settings.value("Main/TimeClock", false).toBool(); @@ -84,12 +85,28 @@ MainSettingsDialog::MainSettingsDialog(QWidget *parent) : } } + ui->cbCSVAbsTime->setChecked(settings.value("Main/CSVAbsTime", false).toBool()); ui->comboSendingBus->setCurrentIndex(settings.value("Playback/SendingBus", 4).toInt()); ui->cbUseFiltered->setChecked(settings.value("Main/UseFiltered", false).toBool()); ui->cbUseOpenGL->setChecked(settings.value("Main/UseOpenGL", false).toBool()); ui->cbFilterLabeling->setChecked(settings.value("Main/FilterLabeling", true).toBool()); ui->cbIgnoreDBCColors->setChecked(settings.value("Main/IgnoreDBCColors", false).toBool()); + int maxFramesDefault; + if (QSysInfo::WordSize > 32) + { + qDebug() << "64 bit OS detected. Requesting a large preallocation"; + maxFramesDefault = 10000000; + } + else //if compiling for 32 bit you can't ask for gigabytes of preallocation so tone it down. + { + qDebug() << "32 bit OS detected. Requesting a much restricted prealloc"; + maxFramesDefault = 2000000; + } + + ui->spinMaximumFrames->setValue(settings.value("Main/MaximumFrames", maxFramesDefault).toInt()); + ui->spinBytesPerLine->setValue(settings.value("Main/BytesPerLine", 8).toInt()); + //just for simplicity they all call the same function and that function updates all settings at once connect(ui->cbDisplayHex, SIGNAL(toggled(bool)), this, SLOT(updateSettings())); connect(ui->cbFlowAutoRef, SIGNAL(toggled(bool)), this, SLOT(updateSettings())); @@ -104,6 +121,7 @@ MainSettingsDialog::MainSettingsDialog(QWidget *parent) : connect(ui->rbMicros, SIGNAL(toggled(bool)), this, SLOT(updateSettings())); connect(ui->rbSysClock, SIGNAL(toggled(bool)), this, SLOT(updateSettings())); connect(ui->rbMillis, SIGNAL(toggled(bool)), this, SLOT(updateSettings())); + connect(ui->cbCSVAbsTime, SIGNAL(toggled(bool)), this, SLOT(updateSettings())); connect(ui->comboSendingBus, SIGNAL(currentIndexChanged(int)), this, SLOT(updateSettings())); connect(ui->cbUseFiltered, SIGNAL(toggled(bool)), this, SLOT(updateSettings())); connect(ui->lineClockFormat, SIGNAL(editingFinished()), this, SLOT(updateSettings())); @@ -117,6 +135,9 @@ MainSettingsDialog::MainSettingsDialog(QWidget *parent) : connect(ui->cbHexGraphFlow, SIGNAL(toggled(bool)), this, SLOT(updateSettings())); connect(ui->cbHexGraphInfo, SIGNAL(toggled(bool)), this, SLOT(updateSettings())); connect(ui->cbIgnoreDBCColors, SIGNAL(toggled(bool)), this, SLOT(updateSettings())); + connect(ui->spinMaximumFrames, SIGNAL(valueChanged(int)), this, SLOT(updateSettings())); + connect(ui->cbFontFixedWidth, SIGNAL(toggled(bool)), this, SLOT(updateSettings())); + connect(ui->spinBytesPerLine, SIGNAL(valueChanged(int)), this, SLOT(updateSettings())); installEventFilter(this); } @@ -171,6 +192,7 @@ void MainSettingsDialog::updateSettings() settings.setValue("Main/TimeSeconds", ui->rbSeconds->isChecked()); settings.setValue("Main/TimeMillis", ui->rbMillis->isChecked()); settings.setValue("Main/TimeClock", ui->rbSysClock->isChecked()); + settings.setValue("Main/CSVAbsTime", ui->cbCSVAbsTime->isChecked()); settings.setValue("Playback/SendingBus", ui->comboSendingBus->currentIndex()); settings.setValue("Main/UseFiltered", ui->cbUseFiltered->isChecked()); settings.setValue("Main/UseOpenGL", ui->cbUseOpenGL->isChecked()); @@ -183,6 +205,9 @@ void MainSettingsDialog::updateSettings() settings.setValue("Remote/Pass", encPass); settings.setValue("Main/FilterLabeling", ui->cbFilterLabeling->isChecked()); settings.setValue("Main/IgnoreDBCColors", ui->cbIgnoreDBCColors->isChecked()); + settings.setValue("Main/MaximumFrames", ui->spinMaximumFrames->value()); + settings.setValue("Main/BytesPerLine", ui->spinBytesPerLine->value()); + settings.setValue("Main/FontFixedWidth", ui->cbFontFixedWidth->isChecked()); settings.sync(); emit updatedSettings(); diff --git a/mainwindow.cpp b/mainwindow.cpp index f32125a..9e6fea3 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -30,8 +30,10 @@ MainWindow::MainWindow(QWidget *parent) : ui(new Ui::MainWindow) { ui->setupUi(this); +#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) qRegisterMetaTypeStreamOperators>(); qRegisterMetaTypeStreamOperators>(); +#endif useHex = true; @@ -55,12 +57,18 @@ MainWindow::MainWindow(QWidget *parent) : verticalHeader->setSectionResizeMode(QHeaderView::Fixed); QSettings settings; int fontSize = settings.value("Main/FontSize", 9).toUInt(); - QFont sysFont = QFont(); //get default font + QFont sysFont; + if(settings.value("Main/FontFixedWidth", false).toBool()) + sysFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); //get default fixed width font + else + sysFont = QFont(); //get default font sysFont.setPointSize(fontSize); verticalHeader->setDefaultSectionSize(sysFont.pixelSize()); + verticalHeader->setFont(QFont()); ui->canFramesView->setFont(sysFont); QHeaderView *HorzHdr = ui->canFramesView->horizontalHeader(); + HorzHdr->setFont(QFont()); HorzHdr->setStretchLastSection(true); //causes the data column to automatically fill the tableview connect(HorzHdr, SIGNAL(sectionClicked(int)), this, SLOT(headerClicked(int))); @@ -87,6 +95,7 @@ MainWindow::MainWindow(QWidget *parent) : signalViewerWindow = nullptr; temporalGraphWindow = nullptr; dbcComparatorWindow = nullptr; + canBridgeWindow = nullptr; dbcHandler = DBCHandler::getReference(); bDirty = false; inhibitFilterUpdate = false; @@ -111,6 +120,7 @@ MainWindow::MainWindow(QWidget *parent) : connect(ui->actionFuzzy_Scope, &QAction::triggered, this, &MainWindow::showFuzzyScopeWindow); connect(ui->actionRange_State_2, &QAction::triggered, this, &MainWindow::showRangeWindow); connect(ui->actionSave_Decoded_Frames, &QAction::triggered, this, &MainWindow::handleSaveDecoded); + connect(ui->actionSave_Decoded_Frames_CSV, &QAction::triggered, this, &MainWindow::handleSaveDecodedCsv); connect(ui->actionSingle_Multi_State_2, &QAction::triggered, this, &MainWindow::showSingleMultiWindow); connect(ui->actionFile_Comparison, &QAction::triggered, this, &MainWindow::showComparisonWindow); connect(ui->actionDBC_Comparison, &QAction::triggered, this, &MainWindow::showDBCComparisonWindow); @@ -127,6 +137,7 @@ MainWindow::MainWindow(QWidget *parent) : connect(ui->actionSignal_Viewer, &QAction::triggered, this, &MainWindow::showSignalViewer); connect(ui->actionSave_Continuous_Logfile, &QAction::triggered, this, &MainWindow::handleContinousLogging); connect(ui->actionTemporal_Graph, &QAction::triggered, this, &MainWindow::showTemporalGraphWindow); + connect(ui->actionCAN_Bridge, &QAction::triggered, this, &MainWindow::showCANBridgeWindow); //handlers fror interactions with the main can frame view table connect(ui->canFramesView, &QAbstractItemView::clicked, this, &MainWindow::gridClicked); @@ -141,6 +152,7 @@ MainWindow::MainWindow(QWidget *parent) : connect(ui->cbInterpret, &QAbstractButton::toggled, this, &MainWindow::interpretToggled); connect(ui->cbOverwrite, &QAbstractButton::toggled, this, &MainWindow::overwriteToggled); + connect(ui->cbPersistentFilters, &QAbstractButton::toggled, this, &MainWindow::presistentFiltersToggled); connect(ui->listFilters, &QListWidget::itemChanged, this, &MainWindow::filterListItemChanged); connect(ui->listBusFilters, &QListWidget::itemChanged, this, &MainWindow::busFilterListItemChanged); @@ -152,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); @@ -177,7 +191,7 @@ MainWindow::MainWindow(QWidget *parent) : updateTimer.setInterval(250); updateTimer.start(); - elapsedTime = new QTime; + elapsedTime = new QElapsedTimer; elapsedTime->start(); isConnected = false; @@ -192,6 +206,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); @@ -199,6 +214,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))); @@ -212,12 +229,34 @@ MainWindow::MainWindow(QWidget *parent) : ui->actionMotorControlConfig->setVisible(false); ui->actionSingle_Multi_State_2->setVisible(false); + QStringList headers; + headers << "En" << "Bus" << "ID" << "Ext" << "Rem" << "Data" + << "Interval" << "Count"; + 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_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; @@ -252,8 +291,11 @@ void MainWindow::killEmAll() killWindow(firmwareUploaderWindow); killWindow(motorctrlConfigWindow); killWindow(signalViewerWindow); - killWindow(connectionWindow); killWindow(temporalGraphWindow); + killWindow(canBridgeWindow); + + //trying to kill this window can cause a fault to happen. It's closed last just in case. + killWindow(connectionWindow); } //forcefully close the window, kill it, and salt the earth @@ -372,16 +414,25 @@ void MainWindow::readUpdateableSettings() useHex = settings.value("Main/UseHex", true).toBool(); model->setHexMode(useHex); Utility::decimalMode = !useHex; - secondsMode = settings.value("Main/TimeSeconds", false).toBool(); - model->setSecondsMode(secondsMode); - useSystemClock = settings.value("Main/TimeClock", false).toBool(); - model->setSysTimeMode(useSystemClock); - millisMode = settings.value("Main/TimeMillis", false).toBool(); - model->setMillisMode(millisMode); + + bool tempBool; + TimeStyle ts = TS_MICROS; + tempBool = settings.value("Main/TimeSeconds", false).toBool(); + if (tempBool) ts = TS_SECONDS; + tempBool = settings.value("Main/TimeClock", false).toBool(); + if (tempBool) ts = TS_CLOCK; + tempBool = settings.value("Main/TimeMillis", false).toBool(); + if (tempBool) ts = TS_MILLIS; + model->setTimeStyle(ts); + useFiltered = settings.value("Main/UseFiltered", false).toBool(); model->setTimeFormat(settings.value("Main/TimeFormat", "MMM-dd HH:mm:ss.zzz").toString()); ignoreDBCColors = settings.value("Main/IgnoreDBCColors", false).toBool(); model->setIgnoreDBCColors(ignoreDBCColors); + int bpl = settings.value("Main/BytesPerLine", 8).toInt(); + model->setBytesPerLine(bpl); + + CSVAbsTime = settings.value("Main/CSVAbsTime", false).toBool(); if (settings.value("Main/FilterLabeling", false).toBool()) ui->listFilters->setMaximumWidth(250); @@ -411,6 +462,171 @@ 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) + { + 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); + } + + 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_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, SIMP_COL::SC_COL_DATA)->text().split(" ", Qt::SkipEmptyParts); +#else + tokens = ui->tableSimpleSender->item(line, SIMP_COL::SC_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, SIMP_COL::SC_COL_INTERVAL)->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); @@ -432,6 +648,8 @@ void MainWindow::headerClicked(int logicalIndex) { //ui->canFramesView->sortByColumn(logicalIndex); model->sortByColumn(logicalIndex); + + manageRowExpansion(); } void MainWindow::expandAllRows() @@ -452,9 +670,30 @@ void MainWindow::expandAllRows() if (goAhead) { ui->canFramesView->resizeRowsToContents(); + + rowExpansionActive = true; } } +void MainWindow::manageRowExpansion() +{ + int numRows = ui->canFramesView->model()->rowCount(); + if(numRows < 20000) + { + if(rowExpansionActive && model->getInterpretMode()) + ui->canFramesView->resizeRowsToContents(); + } + else + { + disableAutoRowExpansion(); + } +} + +void MainWindow::disableAutoRowExpansion() +{ + rowExpansionActive = false; +} + void MainWindow::collapseAllRows() { bool goAhead = false; @@ -473,6 +712,8 @@ void MainWindow::collapseAllRows() if (goAhead) { for (int i = 0; i < numRows; i++) ui->canFramesView->setRowHeight(i, normalRowHeight); + + rowExpansionActive = false; } } @@ -551,7 +792,7 @@ void MainWindow::setupSendToLatestGraphWindow() GraphParams param; QString signalName = getSignalNameFromPosition(contextMenuPosition); param.ID = getMessageIDFromPosition(contextMenuPosition); - DBC_MESSAGE *msg = dbcHandler->findMessageForFilter(param.ID, nullptr); + DBC_MESSAGE *msg = dbcHandler->findMessage(param.ID); if(msg) { DBC_SIGNAL *sig = msg->sigHandler->findSignalByName(signalName); @@ -612,16 +853,29 @@ void MainWindow::overwriteToggled(bool state) } else { + rowExpansionActive = false; model->setOverwriteMode(false); } } +void MainWindow::presistentFiltersToggled(bool state) +{ + if (state) + { + model->setClearMode(true); + } + else + { + model->setClearMode(false); + } +} + void MainWindow::updateFilterList() { if (model == nullptr) return; const QMap *filters = model->getFiltersReference(); const QMap *busFilters = model->getBusFiltersReference(); - if (filters == nullptr && busFilters == nullptr) return; + if (filters == nullptr || busFilters == nullptr) return; qDebug() << "updateFilterList called on MainWindow"; @@ -642,7 +896,7 @@ void MainWindow::updateFilterList() for (filterIter = busFilters->begin(); filterIter != busFilters->end(); ++filterIter) { - QListWidgetItem *thisItem = FilterUtility::createCheckableBusFilterItem(filterIter.key(), filterIter.value(), ui->listBusFilters); + /*QListWidgetItem *thisItem = */ FilterUtility::createCheckableBusFilterItem(filterIter.key(), filterIter.value(), ui->listBusFilters); } inhibitFilterUpdate = false; } @@ -658,6 +912,8 @@ void MainWindow::filterListItemChanged(QListWidgetItem *item) if (item->checkState() == Qt::Checked) isSet = true; model->setFilterState(ID, isSet); + + manageRowExpansion(); } void MainWindow::busFilterListItemChanged(QListWidgetItem *item) @@ -671,6 +927,8 @@ void MainWindow::busFilterListItemChanged(QListWidgetItem *item) if (item->checkState() == Qt::Checked) isSet = true; model->setBusFilterState(ID, isSet); + + manageRowExpansion(); } void MainWindow::filterSetAll() @@ -682,6 +940,8 @@ void MainWindow::filterSetAll() } inhibitFilterUpdate = false; model->setAllFilters(true); + + manageRowExpansion(); } void MainWindow::filterClearAll() @@ -697,6 +957,7 @@ void MainWindow::filterClearAll() void MainWindow::logReceivedFrame(CANConnection* conn, QVector frames) { + Q_UNUSED(conn); if (continuousLogging) { FrameFileIO::writeContinuousNative(&frames, 0); @@ -717,12 +978,14 @@ void MainWindow::tickGUIUpdate() framesPerSec = 0; ui->lbNumFrames->setText(QString::number(model->rowCount())); - if (rxFrames > 0 && /*allowCapture && */ ui->cbAutoScroll->isChecked()) ui->canFramesView->scrollToBottom(); + if (rxFrames > 0 && /*allowCapture && */ ui->cbAutoScroll->isChecked()) + ui->canFramesView->scrollToBottom(); ui->lbFPS->setText(QString::number(framesPerSec)); if (rxFrames > 0) { bDirty = true; emit framesUpdated(rxFrames); //anyone care that frames were updated? + manageRowExpansion(); } if (model->needsFilterRefresh()) updateFilterList(); @@ -751,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; //} } @@ -773,7 +1048,7 @@ void MainWindow::addFrameToDisplay(CANFrame &frame, bool autoRefresh = false) //A sub-window is sending us a center on timestamp and ID signal //try to find the relevant frame in the list and focus on it. -void MainWindow::gotCenterTimeID(int32_t ID, double timestamp) +void MainWindow::gotCenterTimeID(uint32_t ID, double timestamp) { int idx = model->getIndexFromTimeID(ID, timestamp); if (idx > -1) @@ -823,6 +1098,7 @@ void MainWindow::handleLoadFile() if (loadResult) { + disableAutoRowExpansion(); ui->canFramesView->scrollToTop(); model->clearFrames(); model->insertFrames(tempFrames); @@ -866,6 +1142,7 @@ void MainWindow::handleDroppedFile(const QString &filename) if (loadResult) { + disableAutoRowExpansion(); ui->canFramesView->scrollToTop(); model->clearFrames(); model->insertFrames(loadedFrames); @@ -967,13 +1244,24 @@ void MainWindow::handleLoadFilters() } void MainWindow::handleSaveDecoded() +{ + handleSaveDecodedMethod(false); +} + +void MainWindow::handleSaveDecodedCsv() +{ + handleSaveDecodedMethod(true); +} + +void MainWindow::handleSaveDecodedMethod(bool csv) { QString filename; QFileDialog dialog(this); QSettings settings; QStringList filters; - filters.append(QString(tr("Text File (*.txt)"))); + if (!csv) filters.append(QString(tr("Text File (*.txt *.TXT)"))); + else filters.append(QString(tr("CSV File (*.csv *.CSV)"))); dialog.setDirectory(settings.value("FileIO/LoadSaveDirectory", dialog.directory().path()).toString()); dialog.setFileMode(QFileDialog::AnyFile); @@ -984,12 +1272,196 @@ void MainWindow::handleSaveDecoded() if (dialog.exec() == QDialog::Accepted) { filename = dialog.selectedFiles()[0]; - if (!filename.contains('.')) filename += ".txt"; - saveDecodedTextFile(filename); + if (!filename.contains('.')) + { + if (!csv) filename += ".txt"; + else filename += ".csv"; + } + + if(csv) + saveDecodedTextFileAsColumns(filename); + else + saveDecodedTextFile(filename); + settings.setValue("FileIO/LoadSaveDirectory", dialog.directory().path()); } } +void MainWindow::saveDecodedTextFileAsColumns(QString filename) +{ + QFile *outFile = new QFile(filename); + const QVector *frames = model->getFilteredListReference(); + + //const unsigned char *data; + int dataLen; + const CANFrame *frame; + + if (!outFile->open(QIODevice::WriteOnly | QIODevice::Text)) + return; +/* +Time: 205.173000 ID: 0x20E Std Bus: 0 Len: 8 +Data Bytes: 88 10 00 13 BB 00 06 00 + SignalName Value +*/ + QList> msgsAndColumns; + int columnsAdded = 0; + int dataStartCol = 0; + + QString builderString; + if (CSVAbsTime) + { + builderString += tr("Year") + "," + tr("Month") + "," + tr("Day") + "," + tr("Hour") + "," + tr("Minute") + "," + tr("Second") + "," + tr("Ms") + ","; + dataStartCol += 7; + } + else + { + //time + builderString += tr("Time") + ","; + dataStartCol++; + } + //id + builderString += tr("ID") + ","; + dataStartCol++; + //if (frame->hasExtendedFrameFormat()) builderString += tr(" Ext "); + //else builderString += tr(" Std "); + //bus + builderString += tr("Bus") + ","; + dataStartCol++; + //len + builderString += tr("DataLen") + ","; + dataStartCol++; + + columnsAdded = dataStartCol; + + //loop through all the frames and the message data therein + for (int c = 0; c < frames->count(); c++) + { + frame = &frames->at(c); + //data = reinterpret_cast(frame->payload().constData()); + dataLen = frame->payload().count(); + + //add all column names + if (dbcHandler != nullptr) + { + DBC_MESSAGE *msg = dbcHandler->findMessage(*frame); + if (msg != nullptr) + { + bool found = false; + for (int j = 0; j < msg->sigHandler->getCount(); j++) + { + if(j==0) + { + for(int m=0; mID) + found = true; + } + if(found == false) + msgsAndColumns.append(QPair(msg->ID, columnsAdded)); + } + + if(found == false) + { + QString temp; + if (msg->sigHandler->findSignalByIdx(j)->processAsText(*frame, temp)) + { + builderString.append(msg->sigHandler->findSignalByIdx(j)->name); + builderString.append(","); + columnsAdded++; + } + } + } + } + } + } + + //add EOL + builderString += "\n"; + //write out the header row + outFile->write(builderString.toUtf8()); + + //builderString = tr("Data Bytes: "); + //for (int temp = 0; temp < dataLen; temp++) + //{ + // builderString += Utility::formatNumber(data[temp]) + " "; + //} + //builderString += "\n"; + //outFile->write(builderString.toUtf8()); + + int dataColumnsAdded = 0; + builderString = ""; + for (int c = 0; c < frames->count(); c++) + { + dataColumnsAdded = 0; + frame = &frames->at(c); + //data = reinterpret_cast(frame->payload().constData()); + dataLen = frame->payload().count(); + + QString builderString; + if (CSVAbsTime) + { + QDateTime dt = QDateTime::fromMSecsSinceEpoch(frame->timeStamp().microSeconds() / 1000); + builderString += QString::number(dt.date().year()) + "," + QString::number(dt.date().month()) + ","; + builderString += QString::number(dt.date().day()) + "," + QString::number(dt.time().hour()) + ","; + builderString += QString::number(dt.time().minute()) + "," + QString::number(dt.time().second()) + ","; + builderString += QString::number(dt.time().msec()) + ","; + dataColumnsAdded += 7; + } + else { + builderString += QString::number((frame->timeStamp().microSeconds() / 1000000.0), 'f', 6) + ","; + dataColumnsAdded++; + } + //id + builderString += Utility::formatCANID(frame->frameId(), frame->hasExtendedFrameFormat()) + ","; + dataColumnsAdded++; + //if (frame->hasExtendedFrameFormat()) builderString += tr(" Ext "); + //else builderString += tr(" Std "); + //bus + builderString += QString::number(frame->bus) + ","; + dataColumnsAdded++; + //len + builderString += QString::number(dataLen) + ","; + dataColumnsAdded++; + + if (dbcHandler != nullptr) + { + DBC_MESSAGE *msg = dbcHandler->findMessage(*frame); + if (msg != nullptr) + { + for (int j = 0; j < msg->sigHandler->getCount(); j++) + { + if(j==0) + { + for(int i = 0; iID) + { + int startCol = msgsAndColumns[i].second; + while(dataColumnsAdded < startCol) + { + builderString += ","; + dataColumnsAdded++; + } + } + } + } + + QString temp; + if (msg->sigHandler->findSignalByIdx(j)->processAsText(*frame, temp, false, false)) + { + builderString.append(temp); + builderString.append(","); + dataColumnsAdded++; + } + } + } + builderString.append("\n"); + outFile->write(builderString.toUtf8()); + } + } + outFile->close(); +} + void MainWindow::saveDecodedTextFile(QString filename) { QFile *outFile = new QFile(filename); @@ -1100,6 +1572,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) @@ -1129,13 +1606,13 @@ void MainWindow::showGraphingWindow() lastGraphingWindow = new GraphingWindow(model->getListReference()); graphWindows.append(lastGraphingWindow); - connect(lastGraphingWindow, SIGNAL(sendCenterTimeID(uint32_t,double)), this, SLOT(gotCenterTimeID(int32_t,double))); - connect(this, SIGNAL(sendCenterTimeID(uint32_t,double)), lastGraphingWindow, SLOT(gotCenterTimeID(int32_t,double))); + connect(lastGraphingWindow, SIGNAL(sendCenterTimeID(uint32_t,double)), this, SLOT(gotCenterTimeID(uint32_t,double))); + connect(this, SIGNAL(sendCenterTimeID(uint32_t,double)), lastGraphingWindow, SLOT(gotCenterTimeID(uint32_t,double))); if (flowViewWindow) //connect the two external windows together { - connect(lastGraphingWindow, SIGNAL(sendCenterTimeID(uint32_t,double)), flowViewWindow, SLOT(gotCenterTimeID(int32_t,double))); - connect(flowViewWindow, SIGNAL(sendCenterTimeID(uint32_t,double)), lastGraphingWindow, SLOT(gotCenterTimeID(int32_t,double))); + connect(lastGraphingWindow, SIGNAL(sendCenterTimeID(uint32_t,double)), flowViewWindow, SLOT(gotCenterTimeID(uint32_t,double))); + connect(flowViewWindow, SIGNAL(sendCenterTimeID(uint32_t,double)), lastGraphingWindow, SLOT(gotCenterTimeID(uint32_t,double))); } lastGraphingWindow->show(); @@ -1146,11 +1623,26 @@ void MainWindow::showTemporalGraphWindow() //only create an instance of the object if we dont have one. Otherwise just display the existing one. if (!temporalGraphWindow) { + const QVector *frames; if (!useFiltered) - temporalGraphWindow = new TemporalGraphWindow(model->getListReference()); + frames = model->getListReference(); else - temporalGraphWindow = new TemporalGraphWindow(model->getFilteredListReference()); + frames = model->getFilteredListReference(); + + if(frames->count() > 2000) + { + QMessageBox::StandardButton confirmDialog; + confirmDialog = QMessageBox::question(this, "Danger Will Robinson", "There are a lot of frames (>2000) to plot, this may take a while or crash the app. Crash likely with more than 10k frames. Continue?", + QMessageBox::Yes|QMessageBox::No); + if (confirmDialog == QMessageBox::No) + { + return; + } + } + + temporalGraphWindow = new TemporalGraphWindow(frames); } + temporalGraphWindow->show(); } @@ -1195,6 +1687,15 @@ void MainWindow::showBisectWindow() bisectWindow->show(); } +void MainWindow::showCANBridgeWindow() +{ + if (!canBridgeWindow) + { + canBridgeWindow = new CANBridgeWindow(model->getListReference()); + } + canBridgeWindow->show(); +} + void MainWindow::showFrameSenderWindow() { if (!frameSenderWindow) diff --git a/mainwindow.h b/mainwindow.h index 0b0fd90..31df02c 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" @@ -33,12 +33,25 @@ #include "signalviewerwindow.h" #include "re/temporalgraphwindow.h" #include "re/dbccomparatorwindow.h" +#include "canbridgewindow.h" class CANConnection; class ConnectionWindow; class ISOTP_InterpreterWindow; class ScriptingWindow; +enum SIMP_COL +{ + SC_COL_EN = 0, + SC_COL_BUS = 1, + SC_COL_ID = 2, + SC_COL_EXT = 3, + SC_COL_REM = 4, + SC_COL_DATA = 5, + SC_COL_INTERVAL = 6, + SC_COL_COUNT = 7, +}; + namespace Ui { class MainWindow; } @@ -89,8 +102,10 @@ private slots: void showSignalViewer(); void showTemporalGraphWindow(); void showDBCComparisonWindow(); + void showCANBridgeWindow(); void exitApp(); void handleSaveDecoded(); + void handleSaveDecodedCsv(); void connectionStatusUpdated(int conns); void gridClicked(QModelIndex); void gridDoubleClicked(QModelIndex); @@ -99,6 +114,7 @@ private slots: void setupSendToLatestGraphWindow(); void interpretToggled(bool); void overwriteToggled(bool); + void presistentFiltersToggled(bool state); void logReceivedFrame(CANConnection*, QVector); void tickGUIUpdate(); void toggleCapture(); @@ -110,12 +126,13 @@ private slots: void filterClearAll(); void headerClicked (int logicalIndex); void DBCSettingsUpdated(); + void onSenderCellChanged(int, int); public slots: void gotFrames(int); void updateSettings(); void readUpdateableSettings(); - void gotCenterTimeID(int32_t ID, double timestamp); + void gotCenterTimeID(uint32_t ID, double timestamp); void updateConnectionSettings(QString connectionType, QString port, int speed0, int speed1); signals: @@ -137,18 +154,18 @@ private: DBCHandler *dbcHandler; QByteArray inputBuffer; QTimer updateTimer; - QTime *elapsedTime; + QElapsedTimer *elapsedTime; + FrameSenderObject *frameSender; int framesPerSec; int rxFrames; bool inhibitFilterUpdate; bool useHex; bool allowCapture; - bool secondsMode; - bool millisMode; - bool useSystemClock; bool ignoreDBCColors; + 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; @@ -181,6 +198,7 @@ private: SignalViewerWindow *signalViewerWindow; TemporalGraphWindow *temporalGraphWindow; DBCComparatorWindow *dbcComparatorWindow; + CANBridgeWindow *canBridgeWindow; //various private storage QLabel lbStatusConnected; @@ -190,11 +208,14 @@ private: int normalRowHeight; bool isConnected; QPoint contextMenuPosition; + bool rowExpansionActive = false; //private methods QString getSignalNameFromPosition(QPoint pos); uint32_t getMessageIDFromPosition(QPoint pos); + void handleSaveDecodedMethod(bool csv); void saveDecodedTextFile(QString); + void saveDecodedTextFileAsColumns(QString); void addFrameToDisplay(CANFrame &, bool); void updateFileStatus(); void closeEvent(QCloseEvent *event); @@ -203,6 +224,10 @@ private: void readSettings(); void writeSettings(); bool eventFilter(QObject *obj, QEvent *event); + void manageRowExpansion(); + void disableAutoRowExpansion(); + void createSenderRow(); + void processSenderCellChange(int line, int col); }; #endif // MAINWINDOW_H diff --git a/mqtt/qmqtt_client.h b/mqtt/qmqtt_client.h index da89f01..690319b 100644 --- a/mqtt/qmqtt_client.h +++ b/mqtt/qmqtt_client.h @@ -129,7 +129,7 @@ class Q_MQTT_EXPORT Client : public QObject Q_PROPERTY(quint8 _willQos READ willQos WRITE setWillQos) Q_PROPERTY(bool _willRetain READ willRetain WRITE setWillRetain) Q_PROPERTY(QByteArray _willMessage READ willMessage WRITE setWillMessage) - Q_PROPERTY(QString _connectionState READ connectionState) + Q_PROPERTY(ConnectionState _connectionState READ connectionState) #ifndef QT_NO_SSL Q_PROPERTY(QSslConfiguration _sslConfiguration READ sslConfiguration WRITE setSslConfiguration) #endif // QT_NO_SSL diff --git a/mqtt/qmqtt_socket.cpp b/mqtt/qmqtt_socket.cpp index f0628f0..5805f94 100644 --- a/mqtt/qmqtt_socket.cpp +++ b/mqtt/qmqtt_socket.cpp @@ -41,9 +41,9 @@ QMQTT::Socket::Socket(QObject* parent) connect(_socket.data(), &QTcpSocket::connected, this, &SocketInterface::connected); connect(_socket.data(), &QTcpSocket::disconnected, this, &SocketInterface::disconnected); connect(_socket.data(), - static_cast(&QTcpSocket::error), + SIGNAL(error(QTcpSocket::error)), this, - static_cast(&SocketInterface::error)); + SLOT(errorHandler(SocketInterface::error))); } QMQTT::Socket::~Socket() diff --git a/mqtt/qmqtt_ssl_socket.cpp b/mqtt/qmqtt_ssl_socket.cpp index 2d3a808..9f70434 100644 --- a/mqtt/qmqtt_ssl_socket.cpp +++ b/mqtt/qmqtt_ssl_socket.cpp @@ -46,9 +46,9 @@ QMQTT::SslSocket::SslSocket(const QSslConfiguration& config, QObject* parent) connect(_socket.data(), &QSslSocket::encrypted, this, &SocketInterface::connected); connect(_socket.data(), &QSslSocket::disconnected, this, &SocketInterface::disconnected); connect(_socket.data(), - static_cast(&QSslSocket::error), + SIGNAL(error(QSslSocket::error)), this, - static_cast(&SocketInterface::error)); + SLOT(errorHandler(SocketInterface::error))); connect(_socket.data(), static_cast&)>(&QSslSocket::sslErrors), this, diff --git a/pcaplite.cpp b/pcaplite.cpp index 27c72d4..189b2ce 100644 --- a/pcaplite.cpp +++ b/pcaplite.cpp @@ -113,7 +113,7 @@ const unsigned char *pcap_next_ng(pcap_t *p, struct pcap_pkthdr *h) { return (NULL); } - // calcualte option length aligned on 4 byte boundary + // calculate option length aligned on 4 byte boundary unsigned int option_file_length = 4*(oh.option_length/4) + ((oh.option_length%4) ? 4:0); diff --git a/pcaplite.h b/pcaplite.h index 315fa5a..8a48153 100644 --- a/pcaplite.h +++ b/pcaplite.h @@ -2,7 +2,7 @@ #define PCAPLITE_H #include -#if defined(unix) +#if defined(unix) || defined __APPLE__ #include #else #include diff --git a/qcpaxistickerhex.cpp b/qcpaxistickerhex.cpp index 45ca1ac..cc67171 100644 --- a/qcpaxistickerhex.cpp +++ b/qcpaxistickerhex.cpp @@ -3,6 +3,9 @@ QString QCPAxisTickerHex::getTickLabel (double tick, const QLocale &locale, QChar formatChar, int precision) { + Q_UNUSED(locale); + Q_UNUSED(formatChar); + Q_UNUSED(precision); int64_t hexVal = static_cast(tick); return QString::number(hexVal, 16); } diff --git a/qcustomplot.cpp b/qcustomplot.cpp index c1bad52..72b5bfb 100644 --- a/qcustomplot.cpp +++ b/qcustomplot.cpp @@ -1,7 +1,7 @@ /*************************************************************************** ** ** ** QCustomPlot, an easy to use, modern plotting widget for Qt ** -** Copyright (C) 2011-2021 Emanuel Eichhammer ** +** Copyright (C) 2011-2022 Emanuel Eichhammer ** ** ** ** This program is free software: you can redistribute it and/or modify ** ** it under the terms of the GNU General Public License as published by ** @@ -18,16 +18,16 @@ ** ** **************************************************************************** ** Author: Emanuel Eichhammer ** -** Website/Contact: http://www.qcustomplot.com/ ** -** Date: 29.03.21 ** -** Version: 2.1.0 ** +** Website/Contact: https://www.qcustomplot.com/ ** +** Date: 06.11.22 ** +** Version: 2.1.1 ** ****************************************************************************/ #include "qcustomplot.h" /* including file 'src/vector2d.cpp' */ -/* modified 2021-03-29T02:30:44, size 7973 */ +/* modified 2022-11-06T12:45:56, size 7973 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPVector2D @@ -272,7 +272,7 @@ QCPVector2D &QCPVector2D::operator-=(const QCPVector2D &vector) /* including file 'src/painter.cpp' */ -/* modified 2021-03-29T02:30:44, size 8656 */ +/* modified 2022-11-06T12:45:56, size 8656 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPPainter @@ -489,7 +489,7 @@ void QCPPainter::makeNonCosmetic() /* including file 'src/paintbuffer.cpp' */ -/* modified 2021-03-29T02:30:44, size 18915 */ +/* modified 2022-11-06T12:45:56, size 18915 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPAbstractPaintBuffer @@ -908,11 +908,6 @@ void QCPPaintBufferGlFbo::draw(QCPPainter *painter) const qDebug() << Q_FUNC_INFO << "OpenGL frame buffer object doesn't exist, reallocateBuffer was not called?"; return; } - - QOpenGLContext *cont = mGlContext.toStrongRef().data(); - if (cont != QOpenGLContext::currentContext()) - cont->makeCurrent(cont->surface()); - painter->drawImage(0, 0, mGlFrameBuffer->toImage()); } @@ -981,7 +976,7 @@ void QCPPaintBufferGlFbo::reallocateBuffer() /* including file 'src/layer.cpp' */ -/* modified 2021-03-29T02:30:44, size 37615 */ +/* modified 2022-11-06T12:45:56, size 37615 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPLayer @@ -1841,7 +1836,7 @@ void QCPLayerable::wheelEvent(QWheelEvent *event) /* including file 'src/axis/range.cpp' */ -/* modified 2021-03-29T02:30:44, size 12221 */ +/* modified 2022-11-06T12:45:56, size 12221 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPRange @@ -2163,7 +2158,7 @@ bool QCPRange::validRange(const QCPRange &range) /* including file 'src/selection.cpp' */ -/* modified 2021-03-29T02:30:44, size 21837 */ +/* modified 2022-11-06T12:45:56, size 21837 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPDataRange @@ -2764,7 +2759,7 @@ QCPDataSelection QCPDataSelection::inverse(const QCPDataRange &outerRange) const /* including file 'src/selectionrect.cpp' */ -/* modified 2021-03-29T02:30:44, size 9215 */ +/* modified 2022-11-06T12:45:56, size 9215 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPSelectionRect @@ -2993,7 +2988,7 @@ void QCPSelectionRect::draw(QCPPainter *painter) /* including file 'src/layout.cpp' */ -/* modified 2021-03-29T02:30:44, size 78863 */ +/* modified 2022-11-06T12:45:56, size 78863 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPMarginGroup @@ -5166,7 +5161,7 @@ void QCPLayoutInset::addElement(QCPLayoutElement *element, const QRectF &rect) /* including file 'src/lineending.cpp' */ -/* modified 2021-03-29T02:30:44, size 11189 */ +/* modified 2022-11-06T12:45:56, size 11189 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPLineEnding @@ -5460,7 +5455,7 @@ void QCPLineEnding::draw(QCPPainter *painter, const QCPVector2D &pos, double ang /* including file 'src/axis/labelpainter.cpp' */ -/* modified 2021-03-29T02:30:44, size 27296 */ +/* modified 2022-11-06T12:45:56, size 27519 */ //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -5658,8 +5653,8 @@ QByteArray QCPLabelPainterPrivate::generateLabelParameterHash() const QByteArray result; result.append(QByteArray::number(mParentPlot->bufferDevicePixelRatio())); result.append(QByteArray::number(mRotation)); - //result.append(QByteArray::number((int)tickLabelSide)); TODO: check whether this is really a cache-invalidating property - result.append(QByteArray::number((int)mSubstituteExponent)); + //result.append(QByteArray::number(int(tickLabelSide))); TODO: check whether this is really a cache-invalidating property + result.append(QByteArray::number(int(mSubstituteExponent))); result.append(QString(mMultiplicationSymbol).toUtf8()); result.append(mColor.name().toLatin1()+QByteArray::number(mColor.alpha(), 16)); result.append(mFont.toString().toLatin1()); @@ -5762,9 +5757,12 @@ QPointF QCPLabelPainterPrivate::getAnchorPos(const QPointF &tickPos) case asTopRight: return tickPos+QPointF(-mPadding*M_SQRT1_2, mPadding*M_SQRT1_2); case asBottomRight: return tickPos+QPointF(-mPadding*M_SQRT1_2, -mPadding*M_SQRT1_2); case asBottomLeft: return tickPos+QPointF(mPadding*M_SQRT1_2, -mPadding*M_SQRT1_2); + default: qDebug() << Q_FUNC_INFO << "invalid mode for anchor side: " << mAnchorSide; break; } + break; } case amSkewedUpright: + // fall through case amSkewedRotated: { QCPVector2D anchorNormal(tickPos-mAnchorReference); @@ -5773,6 +5771,7 @@ QPointF QCPLabelPainterPrivate::getAnchorPos(const QPointF &tickPos) anchorNormal.normalize(); return tickPos+(anchorNormal*mPadding).toPointF(); } + default: qDebug() << Q_FUNC_INFO << "invalid mode for anchor mode: " << mAnchorMode; break; } return tickPos; } @@ -5990,8 +5989,8 @@ QByteArray QCPLabelPainterPrivate::cacheKey(const QString &text, const QColor &c { return text.toUtf8()+ QByteArray::number(color.red()+256*color.green()+65536*color.blue(), 36)+ - QByteArray::number(color.alpha()+256*(int)side, 36)+ - QByteArray::number((int)(rotation*100)%36000, 36); + QByteArray::number(color.alpha()+256*int(side), 36)+ + QByteArray::number(int(rotation*100), 36); } QCPLabelPainterPrivate::AnchorSide QCPLabelPainterPrivate::skewedAnchorSide(const QPointF &tickPos, double sideExpandHorz, double sideExpandVert) const @@ -6059,7 +6058,7 @@ void QCPLabelPainterPrivate::analyzeFontMetrics() /* including file 'src/axis/axisticker.cpp' */ -/* modified 2021-03-29T02:30:44, size 18688 */ +/* modified 2022-11-06T12:45:56, size 18693 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPAxisTicker @@ -6443,7 +6442,7 @@ double QCPAxisTicker::pickClosest(double target, const QVector &candidat */ double QCPAxisTicker::getMantissa(double input, double *magnitude) const { - const double mag = qPow(10.0, qFloor(qLn(input)/qLn(10.0))); + const double mag = std::pow(10.0, std::floor(std::log10(input))); if (magnitude) *magnitude = mag; return input/mag; } @@ -6479,7 +6478,7 @@ double QCPAxisTicker::cleanMantissa(double input) const /* including file 'src/axis/axistickerdatetime.cpp' */ -/* modified 2021-03-29T02:30:44, size 18829 */ +/* modified 2022-11-06T12:45:56, size 18829 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPAxisTickerDateTime @@ -6836,7 +6835,7 @@ double QCPAxisTickerDateTime::dateTimeToKey(const QDate &date, Qt::TimeSpec time /* including file 'src/axis/axistickertime.cpp' */ -/* modified 2021-03-29T02:30:44, size 11745 */ +/* modified 2022-11-06T12:45:56, size 11745 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPAxisTickerTime @@ -7085,7 +7084,7 @@ void QCPAxisTickerTime::replaceUnit(QString &text, QCPAxisTickerTime::TimeUnit u /* including file 'src/axis/axistickerfixed.cpp' */ -/* modified 2021-03-29T02:30:44, size 5575 */ +/* modified 2022-11-06T12:45:56, size 5575 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPAxisTickerFixed @@ -7187,7 +7186,7 @@ double QCPAxisTickerFixed::getTickStep(const QCPRange &range) /* including file 'src/axis/axistickertext.cpp' */ -/* modified 2021-03-29T02:30:44, size 8742 */ +/* modified 2022-11-06T12:45:56, size 8742 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPAxisTickerText @@ -7404,7 +7403,7 @@ QVector QCPAxisTickerText::createTickVector(double tickStep, const QCPRa /* including file 'src/axis/axistickerpi.cpp' */ -/* modified 2021-03-29T02:30:44, size 11177 */ +/* modified 2022-11-06T12:45:56, size 11177 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPAxisTickerPi @@ -7691,7 +7690,7 @@ QString QCPAxisTickerPi::unicodeSubscript(int number) const /* including file 'src/axis/axistickerlog.cpp' */ -/* modified 2021-03-29T02:30:44, size 7890 */ +/* modified 2022-11-06T12:45:56, size 7890 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPAxisTickerLog @@ -7832,7 +7831,7 @@ QVector QCPAxisTickerLog::createTickVector(double tickStep, const QCPRan /* including file 'src/axis/axis.cpp' */ -/* modified 2021-03-29T02:30:44, size 99883 */ +/* modified 2022-11-06T12:45:56, size 99911 */ //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -10353,7 +10352,7 @@ QCPAxisPainterPrivate::TickLabelData QCPAxisPainterPrivate::getTickLabelData(con int eLast = -1; // last index of exponent part, rest of text after this will be suffixPart if (substituteExponent) { - ePos = text.indexOf(QLatin1Char('e')); + ePos = text.indexOf(QString(mParentPlot->locale().exponential())); if (ePos > 0 && text.at(ePos-1).isDigit()) { eLast = ePos; @@ -10550,7 +10549,7 @@ void QCPAxisPainterPrivate::getMaxTickLabelSize(const QFont &font, const QString /* including file 'src/scatterstyle.cpp' */ -/* modified 2021-03-29T02:30:44, size 17466 */ +/* modified 2022-11-06T12:45:56, size 17466 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPScatterStyle @@ -11023,7 +11022,7 @@ void QCPScatterStyle::drawShape(QCPPainter *painter, double x, double y) const /* including file 'src/plottable.cpp' */ -/* modified 2021-03-29T02:30:44, size 38818 */ +/* modified 2022-11-06T12:45:56, size 38818 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPSelectionDecorator @@ -11994,7 +11993,7 @@ void QCPAbstractPlottable::deselectEvent(bool *selectionStateChanged) /* including file 'src/item.cpp' */ -/* modified 2021-03-29T02:30:44, size 49486 */ +/* modified 2022-11-06T12:45:56, size 49486 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPItemAnchor @@ -13266,7 +13265,7 @@ QCP::Interaction QCPAbstractItem::selectionCategory() const /* including file 'src/core.cpp' */ -/* modified 2021-03-29T02:30:44, size 127198 */ +/* modified 2022-11-06T12:45:56, size 127625 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCustomPlot @@ -13278,7 +13277,7 @@ QCP::Interaction QCPAbstractItem::selectionCategory() const interacts with the user. For tutorials on how to use QCustomPlot, see the website\n - http://www.qcustomplot.com/ + https://www.qcustomplot.com/ */ /* start of documentation of inline functions */ @@ -13602,7 +13601,7 @@ QCustomPlot::QCustomPlot(QWidget *parent) : xAxis2(nullptr), yAxis2(nullptr), legend(nullptr), - mBufferDevicePixelRatio(1.0), // will be adapted to primary screen below + mBufferDevicePixelRatio(1.0), // will be adapted to true value below mPlotLayout(nullptr), mAutoAddPlottableToLegend(true), mAntialiasedElements(QCP::aeNone), @@ -13631,7 +13630,6 @@ QCustomPlot::QCustomPlot(QWidget *parent) : mOpenGlCacheLabelsBackup(true) { setAttribute(Qt::WA_NoMousePropagation); - setAttribute(Qt::WA_OpaquePaintEvent); setFocusPolicy(Qt::ClickFocus); setMouseTracking(true); QLocale currentLocale = locale(); @@ -15471,6 +15469,22 @@ QSize QCustomPlot::sizeHint() const void QCustomPlot::paintEvent(QPaintEvent *event) { Q_UNUSED(event) + + // detect if the device pixel ratio has changed (e.g. moving window between different DPI screens), and adapt buffers if necessary: +#ifdef QCP_DEVICEPIXELRATIO_SUPPORTED +# ifdef QCP_DEVICEPIXELRATIO_FLOAT + double newDpr = devicePixelRatioF(); +# else + double newDpr = devicePixelRatio(); +# endif + if (!qFuzzyCompare(mBufferDevicePixelRatio, newDpr)) + { + setBufferDevicePixelRatio(newDpr); + replot(QCustomPlot::rpQueuedRefresh); + return; + } +#endif + QCPPainter painter(this); if (painter.isActive()) { @@ -16485,7 +16499,7 @@ void QCustomPlot::toPainter(QCPPainter *painter, int width, int height) /* including file 'src/colorgradient.cpp' */ -/* modified 2021-03-29T02:30:44, size 25278 */ +/* modified 2022-11-06T12:45:56, size 25408 */ //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -16715,10 +16729,10 @@ void QCPColorGradient::colorize(const double *data, const QCPRange &range, QRgb const double value = data[dataIndexFactor*i]; if (skipNanCheck || !std::isnan(value)) { - int index = int((!logarithmic ? value-range.lower : qLn(value/range.lower)) * posToIndexFactor); + qint64 index = qint64((!logarithmic ? value-range.lower : qLn(value/range.lower)) * posToIndexFactor); if (!mPeriodic) { - index = qBound(0, index, mLevelCount-1); + index = qBound(qint64(0), index, qint64(mLevelCount-1)); } else { index %= mLevelCount; @@ -16776,10 +16790,10 @@ void QCPColorGradient::colorize(const double *data, const unsigned char *alpha, const double value = data[dataIndexFactor*i]; if (skipNanCheck || !std::isnan(value)) { - int index = int((!logarithmic ? value-range.lower : qLn(value/range.lower)) * posToIndexFactor); + qint64 index = qint64((!logarithmic ? value-range.lower : qLn(value/range.lower)) * posToIndexFactor); if (!mPeriodic) { - index = qBound(0, index, mLevelCount-1); + index = qBound(qint64(0), index, qint64(mLevelCount-1)); } else { index %= mLevelCount; @@ -17022,7 +17036,7 @@ void QCPColorGradient::updateColorBuffer() for (int i=0; i::const_iterator it = mColorStops.lowerBound(position); + QMap::const_iterator it = const_cast*>(&mColorStops)->lowerBound(position); // force using the const lowerBound method if (it == mColorStops.constEnd()) // position is on or after last stop, use color of last stop { if (useAlpha) @@ -17120,7 +17134,7 @@ void QCPColorGradient::updateColorBuffer() /* including file 'src/selectiondecorator-bracket.cpp' */ -/* modified 2021-03-29T02:30:44, size 12308 */ +/* modified 2022-11-06T12:45:56, size 12308 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPSelectionDecoratorBracket @@ -17406,7 +17420,7 @@ QPointF QCPSelectionDecoratorBracket::getPixelCoordinates(const QCPPlottableInte /* including file 'src/layoutelements/layoutelement-axisrect.cpp' */ -/* modified 2021-03-29T02:30:44, size 47193 */ +/* modified 2022-11-06T12:45:56, size 47193 */ //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -18691,7 +18705,7 @@ void QCPAxisRect::wheelEvent(QWheelEvent *event) /* including file 'src/layoutelements/layoutelement-legend.cpp' */ -/* modified 2021-03-29T02:30:44, size 31762 */ +/* modified 2022-11-06T12:45:56, size 31762 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPAbstractLegendItem @@ -19615,7 +19629,7 @@ void QCPLegend::parentPlotInitialized(QCustomPlot *parentPlot) /* including file 'src/layoutelements/layoutelement-textelement.cpp' */ -/* modified 2021-03-29T02:30:44, size 12925 */ +/* modified 2022-11-06T12:45:56, size 12925 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPTextElement @@ -20021,7 +20035,7 @@ QColor QCPTextElement::mainTextColor() const /* including file 'src/layoutelements/layoutelement-colorscale.cpp' */ -/* modified 2021-03-29T02:30:44, size 26531 */ +/* modified 2022-11-06T12:45:56, size 26531 */ //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -20705,7 +20719,7 @@ void QCPColorScaleAxisRectPrivate::axisSelectableChanged(QCPAxis::SelectablePart /* including file 'src/plottables/plottable-graph.cpp' */ -/* modified 2021-03-29T02:30:44, size 74518 */ +/* modified 2022-11-06T12:45:57, size 74926 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPGraphData @@ -21535,21 +21549,37 @@ QVector QCPGraph::dataToImpulseLines(const QVector &data) { for (int i=0; icoordToPixel(data.at(i).key); - result[i*2+0].setX(valueAxis->coordToPixel(0)); - result[i*2+0].setY(key); - result[i*2+1].setX(valueAxis->coordToPixel(data.at(i).value)); - result[i*2+1].setY(key); + const QCPGraphData ¤t = data.at(i); + if (!qIsNaN(current.value)) + { + const double key = keyAxis->coordToPixel(current.key); + result[i*2+0].setX(valueAxis->coordToPixel(0)); + result[i*2+0].setY(key); + result[i*2+1].setX(valueAxis->coordToPixel(current.value)); + result[i*2+1].setY(key); + } else + { + result[i*2+0] = QPointF(0, 0); + result[i*2+1] = QPointF(0, 0); + } } } else // key axis is horizontal { for (int i=0; icoordToPixel(data.at(i).key); - result[i*2+0].setX(key); - result[i*2+0].setY(valueAxis->coordToPixel(0)); - result[i*2+1].setX(key); - result[i*2+1].setY(valueAxis->coordToPixel(data.at(i).value)); + const QCPGraphData ¤t = data.at(i); + if (!qIsNaN(current.value)) + { + const double key = keyAxis->coordToPixel(data.at(i).key); + result[i*2+0].setX(key); + result[i*2+0].setY(valueAxis->coordToPixel(0)); + result[i*2+1].setX(key); + result[i*2+1].setY(valueAxis->coordToPixel(data.at(i).value)); + } else + { + result[i*2+0] = QPointF(0, 0); + result[i*2+1] = QPointF(0, 0); + } } } return result; @@ -22463,7 +22493,7 @@ int QCPGraph::findIndexBelowY(const QVector *data, double y) const /* including file 'src/plottables/plottable-curve.cpp' */ -/* modified 2021-03-29T02:30:44, size 63851 */ +/* modified 2022-11-06T12:45:56, size 63851 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPCurveData @@ -23921,7 +23951,7 @@ double QCPCurve::pointDistance(const QPointF &pixelPoint, QCPCurveDataContainer: /* including file 'src/plottables/plottable-bars.cpp' */ -/* modified 2021-03-29T02:30:44, size 43907 */ +/* modified 2022-11-06T12:45:56, size 43907 */ //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -25097,7 +25127,7 @@ void QCPBars::connectBars(QCPBars *lower, QCPBars *upper) /* including file 'src/plottables/plottable-statisticalbox.cpp' */ -/* modified 2021-03-29T02:30:44, size 28951 */ +/* modified 2022-11-06T12:45:57, size 28951 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPStatisticalBoxData @@ -25759,7 +25789,7 @@ QVector QCPStatisticalBox::getWhiskerBarLines(QCPStatisticalBoxDataConta /* including file 'src/plottables/plottable-colormap.cpp' */ -/* modified 2021-03-29T02:30:44, size 48149 */ +/* modified 2022-11-06T12:45:56, size 48189 */ //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// QCPColorMapData @@ -26129,8 +26159,8 @@ void QCPColorMapData::recalculateDataBounds() { if (mKeySize > 0 && mValueSize > 0) { - double minHeight = mData[0]; - double maxHeight = mData[0]; + double minHeight = std::numeric_limits::max(); + double maxHeight = -std::numeric_limits::max(); const int dataCount = mValueSize*mKeySize; for (int i=0; i= 0x060200 // don't use QT_VERSION_CHECK here, some moc versions don't understand it namespace QCP { -#else -class QCP { // when in moc-run, make it look like a class, so we get Q_GADGET, Q_ENUMS/Q_FLAGS features in namespace + Q_NAMESPACE // this is how to add the staticMetaObject to namespaces in newer Qt versions +#else // Qt version older than 6.2.0 +# ifndef Q_MOC_RUN +namespace QCP { +# else // not in moc run +class QCP { Q_GADGET Q_ENUMS(ExportPen) Q_ENUMS(ResolutionUnit) Q_ENUMS(SignDomain) Q_ENUMS(MarginSide) - Q_FLAGS(MarginSides) Q_ENUMS(AntialiasedElement) - Q_FLAGS(AntialiasedElements) Q_ENUMS(PlottingHint) - Q_FLAGS(PlottingHints) Q_ENUMS(Interaction) - Q_FLAGS(Interactions) Q_ENUMS(SelectionRectMode) Q_ENUMS(SelectionType) + + Q_FLAGS(AntialiasedElements) + Q_FLAGS(PlottingHints) + Q_FLAGS(MarginSides) + Q_FLAGS(Interactions) public: +# endif #endif + /*! Defines the different units in which the image resolution can be specified in the export functions. @@ -378,14 +387,39 @@ inline int getMarginValue(const QMargins &margins, QCP::MarginSide side) return 0; } +// for newer Qt versions we have to declare the enums/flags as metatypes inside the namespace using Q_ENUM_NS/Q_FLAG_NS: +// if you change anything here, don't forget to change it for older Qt versions below, too, +// and at the start of the namespace in the fake moc-run class +#if QT_VERSION >= 0x060200 +Q_ENUM_NS(ExportPen) +Q_ENUM_NS(ResolutionUnit) +Q_ENUM_NS(SignDomain) +Q_ENUM_NS(MarginSide) +Q_ENUM_NS(AntialiasedElement) +Q_ENUM_NS(PlottingHint) +Q_ENUM_NS(Interaction) +Q_ENUM_NS(SelectionRectMode) +Q_ENUM_NS(SelectionType) -extern const QMetaObject staticMetaObject; // in moc-run we create a static meta object for QCP "fake" object. This line is the link to it via QCP::staticMetaObject in normal operation as namespace +Q_FLAG_NS(AntialiasedElements) +Q_FLAG_NS(PlottingHints) +Q_FLAG_NS(MarginSides) +Q_FLAG_NS(Interactions) +#else +extern const QMetaObject staticMetaObject; +#endif } // end of namespace QCP + Q_DECLARE_OPERATORS_FOR_FLAGS(QCP::AntialiasedElements) Q_DECLARE_OPERATORS_FOR_FLAGS(QCP::PlottingHints) Q_DECLARE_OPERATORS_FOR_FLAGS(QCP::MarginSides) Q_DECLARE_OPERATORS_FOR_FLAGS(QCP::Interactions) + +// for older Qt versions we have to declare the enums/flags as metatypes outside the namespace using Q_DECLARE_METATYPE: +// if you change anything here, don't forget to change it for newer Qt versions above, too, +// and at the start of the namespace in the fake moc-run class +#if QT_VERSION < QT_VERSION_CHECK(6, 2, 0) Q_DECLARE_METATYPE(QCP::ExportPen) Q_DECLARE_METATYPE(QCP::ResolutionUnit) Q_DECLARE_METATYPE(QCP::SignDomain) @@ -395,12 +429,13 @@ Q_DECLARE_METATYPE(QCP::PlottingHint) Q_DECLARE_METATYPE(QCP::Interaction) Q_DECLARE_METATYPE(QCP::SelectionRectMode) Q_DECLARE_METATYPE(QCP::SelectionType) +#endif /* end of 'src/global.h' */ /* including file 'src/vector2d.h' */ -/* modified 2021-03-29T02:30:44, size 4988 */ +/* modified 2022-11-06T12:45:56, size 4988 */ class QCP_LIB_DECL QCPVector2D { @@ -475,7 +510,7 @@ inline QDebug operator<< (QDebug d, const QCPVector2D &vec) /* including file 'src/painter.h' */ -/* modified 2021-03-29T02:30:44, size 4035 */ +/* modified 2022-11-06T12:45:56, size 4035 */ class QCP_LIB_DECL QCPPainter : public QPainter { @@ -534,7 +569,7 @@ Q_DECLARE_METATYPE(QCPPainter::PainterMode) /* including file 'src/paintbuffer.h' */ -/* modified 2021-03-29T02:30:44, size 5006 */ +/* modified 2022-11-06T12:45:56, size 5006 */ class QCP_LIB_DECL QCPAbstractPaintBuffer { @@ -642,7 +677,7 @@ protected: /* including file 'src/layer.h' */ -/* modified 2021-03-29T02:30:44, size 7038 */ +/* modified 2022-11-06T12:45:56, size 7038 */ class QCP_LIB_DECL QCPLayer : public QObject { @@ -791,7 +826,7 @@ private: /* including file 'src/axis/range.h' */ -/* modified 2021-03-29T02:30:44, size 5280 */ +/* modified 2022-11-06T12:45:56, size 5280 */ class QCP_LIB_DECL QCPRange { @@ -909,7 +944,7 @@ inline const QCPRange operator/(const QCPRange& range, double value) /* including file 'src/selection.h' */ -/* modified 2021-03-29T02:30:44, size 8569 */ +/* modified 2022-11-06T12:45:56, size 8569 */ class QCP_LIB_DECL QCPDataRange { @@ -1113,7 +1148,7 @@ inline QDebug operator<< (QDebug d, const QCPDataSelection &selection) /* including file 'src/selectionrect.h' */ -/* modified 2021-03-29T02:30:44, size 3354 */ +/* modified 2022-11-06T12:45:56, size 3354 */ class QCP_LIB_DECL QCPSelectionRect : public QCPLayerable { @@ -1167,7 +1202,7 @@ protected: /* including file 'src/layout.h' */ -/* modified 2021-03-29T02:30:44, size 14279 */ +/* modified 2022-11-06T12:45:56, size 14279 */ class QCP_LIB_DECL QCPMarginGroup : public QObject { @@ -1488,7 +1523,7 @@ Q_DECLARE_METATYPE(QCPLayoutInset::InsetPlacement) /* including file 'src/lineending.h' */ -/* modified 2021-03-29T02:30:44, size 4426 */ +/* modified 2022-11-06T12:45:56, size 4426 */ class QCP_LIB_DECL QCPLineEnding { @@ -1552,7 +1587,7 @@ Q_DECLARE_METATYPE(QCPLineEnding::EndingStyle) /* including file 'src/axis/labelpainter.h' */ -/* modified 2021-03-29T02:30:44, size 7086 */ +/* modified 2022-11-06T12:45:56, size 7086 */ class QCPLabelPainterPrivate { @@ -1692,7 +1727,7 @@ Q_DECLARE_METATYPE(QCPLabelPainterPrivate::AnchorSide) /* including file 'src/axis/axisticker.h' */ -/* modified 2021-03-29T02:30:44, size 4230 */ +/* modified 2022-11-06T12:45:56, size 4230 */ class QCP_LIB_DECL QCPAxisTicker { @@ -1757,7 +1792,7 @@ Q_DECLARE_METATYPE(QSharedPointer) /* including file 'src/axis/axistickerdatetime.h' */ -/* modified 2021-03-29T02:30:44, size 3600 */ +/* modified 2022-11-06T12:45:56, size 3600 */ class QCP_LIB_DECL QCPAxisTickerDateTime : public QCPAxisTicker { @@ -1806,7 +1841,7 @@ protected: /* including file 'src/axis/axistickertime.h' */ -/* modified 2021-03-29T02:30:44, size 3542 */ +/* modified 2022-11-06T12:45:56, size 3542 */ class QCP_LIB_DECL QCPAxisTickerTime : public QCPAxisTicker { @@ -1858,7 +1893,7 @@ Q_DECLARE_METATYPE(QCPAxisTickerTime::TimeUnit) /* including file 'src/axis/axistickerfixed.h' */ -/* modified 2021-03-29T02:30:44, size 3308 */ +/* modified 2022-11-06T12:45:56, size 3308 */ class QCP_LIB_DECL QCPAxisTickerFixed : public QCPAxisTicker { @@ -1900,7 +1935,7 @@ Q_DECLARE_METATYPE(QCPAxisTickerFixed::ScaleStrategy) /* including file 'src/axis/axistickertext.h' */ -/* modified 2021-03-29T02:30:44, size 3090 */ +/* modified 2022-11-06T12:45:56, size 3090 */ class QCP_LIB_DECL QCPAxisTickerText : public QCPAxisTicker { @@ -1938,7 +1973,7 @@ protected: /* including file 'src/axis/axistickerpi.h' */ -/* modified 2021-03-29T02:30:44, size 3911 */ +/* modified 2022-11-06T12:45:56, size 3911 */ class QCP_LIB_DECL QCPAxisTickerPi : public QCPAxisTicker { @@ -1997,7 +2032,7 @@ Q_DECLARE_METATYPE(QCPAxisTickerPi::FractionStyle) /* including file 'src/axis/axistickerlog.h' */ -/* modified 2021-03-29T02:30:44, size 2594 */ +/* modified 2022-11-06T12:45:56, size 2594 */ class QCP_LIB_DECL QCPAxisTickerLog : public QCPAxisTicker { @@ -2029,7 +2064,7 @@ protected: /* including file 'src/axis/axis.h' */ -/* modified 2021-03-29T02:30:44, size 20913 */ +/* modified 2022-11-06T12:45:56, size 20913 */ class QCP_LIB_DECL QCPGrid :public QCPLayerable { @@ -2457,7 +2492,7 @@ protected: /* including file 'src/scatterstyle.h' */ -/* modified 2021-03-29T02:30:44, size 7275 */ +/* modified 2022-11-06T12:45:56, size 7275 */ class QCP_LIB_DECL QCPScatterStyle { @@ -2564,7 +2599,7 @@ Q_DECLARE_METATYPE(QCPScatterStyle::ScatterShape) /* including file 'src/datacontainer.h' */ -/* modified 2021-03-29T02:30:44, size 34070 */ +/* modified 2022-11-06T12:45:56, size 34305 */ /*! \relates QCPDataContainer Returns whether the sort key of \a a is less than the sort key of \a b. @@ -3233,6 +3268,8 @@ QCPRange QCPDataContainer::keyRange(bool &foundRange, QCP::SignDomain output parameter \a foundRange indicates whether a sensible range was found. If this is false, you should not use the returned QCPRange (e.g. the data container is empty or all points have the same value). + + Inf and -Inf data values are ignored. If \a inKeyRange has both lower and upper bound set to zero (is equal to QCPRange()), all data points are considered, without any restriction on the keys. @@ -3270,12 +3307,12 @@ QCPRange QCPDataContainer::valueRange(bool &foundRange, QCP::SignDomai if (restrictKeyRange && (it->mainKey() < inKeyRange.lower || it->mainKey() > inKeyRange.upper)) continue; current = it->valueRange(); - if ((current.lower < range.lower || !haveLower) && !qIsNaN(current.lower)) + if ((current.lower < range.lower || !haveLower) && !qIsNaN(current.lower) && std::isfinite(current.lower)) { range.lower = current.lower; haveLower = true; } - if ((current.upper > range.upper || !haveUpper) && !qIsNaN(current.upper)) + if ((current.upper > range.upper || !haveUpper) && !qIsNaN(current.upper) && std::isfinite(current.upper)) { range.upper = current.upper; haveUpper = true; @@ -3288,12 +3325,12 @@ QCPRange QCPDataContainer::valueRange(bool &foundRange, QCP::SignDomai if (restrictKeyRange && (it->mainKey() < inKeyRange.lower || it->mainKey() > inKeyRange.upper)) continue; current = it->valueRange(); - if ((current.lower < range.lower || !haveLower) && current.lower < 0 && !qIsNaN(current.lower)) + if ((current.lower < range.lower || !haveLower) && current.lower < 0 && !qIsNaN(current.lower) && std::isfinite(current.lower)) { range.lower = current.lower; haveLower = true; } - if ((current.upper > range.upper || !haveUpper) && current.upper < 0 && !qIsNaN(current.upper)) + if ((current.upper > range.upper || !haveUpper) && current.upper < 0 && !qIsNaN(current.upper) && std::isfinite(current.upper)) { range.upper = current.upper; haveUpper = true; @@ -3306,12 +3343,12 @@ QCPRange QCPDataContainer::valueRange(bool &foundRange, QCP::SignDomai if (restrictKeyRange && (it->mainKey() < inKeyRange.lower || it->mainKey() > inKeyRange.upper)) continue; current = it->valueRange(); - if ((current.lower < range.lower || !haveLower) && current.lower > 0 && !qIsNaN(current.lower)) + if ((current.lower < range.lower || !haveLower) && current.lower > 0 && !qIsNaN(current.lower) && std::isfinite(current.lower)) { range.lower = current.lower; haveLower = true; } - if ((current.upper > range.upper || !haveUpper) && current.upper > 0 && !qIsNaN(current.upper)) + if ((current.upper > range.upper || !haveUpper) && current.upper > 0 && !qIsNaN(current.upper) && std::isfinite(current.upper)) { range.upper = current.upper; haveUpper = true; @@ -3406,7 +3443,7 @@ void QCPDataContainer::performAutoSqueeze() /* including file 'src/plottable.h' */ -/* modified 2021-03-29T02:30:44, size 8461 */ +/* modified 2022-11-06T12:45:56, size 8461 */ class QCP_LIB_DECL QCPSelectionDecorator { @@ -3563,7 +3600,7 @@ private: /* including file 'src/item.h' */ -/* modified 2021-03-29T02:30:44, size 9425 */ +/* modified 2022-11-06T12:45:56, size 9425 */ class QCP_LIB_DECL QCPItemAnchor { @@ -3748,7 +3785,7 @@ private: /* including file 'src/core.h' */ -/* modified 2021-03-29T02:30:44, size 19304 */ +/* modified 2022-11-06T12:45:56, size 19304 */ class QCP_LIB_DECL QCustomPlot : public QWidget { @@ -4118,7 +4155,7 @@ ItemType *QCustomPlot::itemAt(const QPointF &pos, bool onlySelectable) const /* including file 'src/plottable1d.h' */ -/* modified 2021-03-29T02:30:44, size 25638 */ +/* modified 2022-11-06T12:45:56, size 25638 */ class QCPPlottableInterface1D { @@ -4710,7 +4747,7 @@ void QCPAbstractPlottable1D::drawPolyline(QCPPainter *painter, const Q /* including file 'src/colorgradient.h' */ -/* modified 2021-03-29T02:30:44, size 7262 */ +/* modified 2022-11-06T12:45:56, size 7262 */ class QCP_LIB_DECL QCPColorGradient { @@ -4813,7 +4850,7 @@ Q_DECLARE_METATYPE(QCPColorGradient::GradientPreset) /* including file 'src/selectiondecorator-bracket.h' */ -/* modified 2021-03-29T02:30:44, size 4458 */ +/* modified 2022-11-06T12:45:56, size 4458 */ class QCP_LIB_DECL QCPSelectionDecoratorBracket : public QCPSelectionDecorator { @@ -4882,7 +4919,7 @@ Q_DECLARE_METATYPE(QCPSelectionDecoratorBracket::BracketStyle) /* including file 'src/layoutelements/layoutelement-axisrect.h' */ -/* modified 2021-03-29T02:30:44, size 7529 */ +/* modified 2022-11-06T12:45:56, size 7529 */ class QCP_LIB_DECL QCPAxisRect : public QCPLayoutElement { @@ -5008,7 +5045,7 @@ private: /* including file 'src/layoutelements/layoutelement-legend.h' */ -/* modified 2021-03-29T02:30:44, size 10425 */ +/* modified 2022-11-06T12:45:56, size 10425 */ class QCP_LIB_DECL QCPAbstractLegendItem : public QCPLayoutElement { @@ -5226,7 +5263,7 @@ Q_DECLARE_METATYPE(QCPLegend::SelectablePart) /* including file 'src/layoutelements/layoutelement-textelement.h' */ -/* modified 2021-03-29T02:30:44, size 5359 */ +/* modified 2022-11-06T12:45:56, size 5359 */ class QCP_LIB_DECL QCPTextElement : public QCPLayoutElement { @@ -5313,7 +5350,7 @@ private: /* including file 'src/layoutelements/layoutelement-colorscale.h' */ -/* modified 2021-03-29T02:30:44, size 5939 */ +/* modified 2022-11-06T12:45:56, size 5939 */ class QCPColorScaleAxisRectPrivate : public QCPAxisRect @@ -5421,7 +5458,7 @@ private: /* including file 'src/plottables/plottable-graph.h' */ -/* modified 2021-03-29T02:30:44, size 9316 */ +/* modified 2022-11-06T12:45:56, size 9316 */ class QCP_LIB_DECL QCPGraphData { @@ -5560,7 +5597,7 @@ Q_DECLARE_METATYPE(QCPGraph::LineStyle) /* including file 'src/plottables/plottable-curve.h' */ -/* modified 2021-03-29T02:30:44, size 7434 */ +/* modified 2022-11-06T12:45:56, size 7434 */ class QCP_LIB_DECL QCPCurveData { @@ -5675,7 +5712,7 @@ Q_DECLARE_METATYPE(QCPCurve::LineStyle) /* including file 'src/plottables/plottable-bars.h' */ -/* modified 2021-03-29T02:30:44, size 8955 */ +/* modified 2022-11-06T12:45:56, size 8955 */ class QCP_LIB_DECL QCPBarsGroup : public QObject { @@ -5863,7 +5900,7 @@ Q_DECLARE_METATYPE(QCPBars::WidthType) /* including file 'src/plottables/plottable-statisticalbox.h' */ -/* modified 2021-03-29T02:30:44, size 7522 */ +/* modified 2022-11-06T12:45:56, size 7522 */ class QCP_LIB_DECL QCPStatisticalBoxData { @@ -5980,7 +6017,7 @@ protected: /* including file 'src/plottables/plottable-colormap.h' */ -/* modified 2021-03-29T02:30:44, size 7092 */ +/* modified 2022-11-06T12:45:56, size 7092 */ class QCP_LIB_DECL QCPColorMapData { @@ -6116,7 +6153,7 @@ protected: /* including file 'src/plottables/plottable-financial.h' */ -/* modified 2021-03-29T02:30:44, size 8644 */ +/* modified 2022-11-06T12:45:56, size 8644 */ class QCP_LIB_DECL QCPFinancialData { @@ -6255,7 +6292,7 @@ Q_DECLARE_METATYPE(QCPFinancial::ChartStyle) /* including file 'src/plottables/plottable-errorbar.h' */ -/* modified 2021-03-29T02:30:44, size 7749 */ +/* modified 2022-11-06T12:45:56, size 7749 */ class QCP_LIB_DECL QCPErrorBarsData { @@ -6380,7 +6417,7 @@ protected: /* including file 'src/items/item-straightline.h' */ -/* modified 2021-03-29T02:30:44, size 3137 */ +/* modified 2022-11-06T12:45:56, size 3137 */ class QCP_LIB_DECL QCPItemStraightLine : public QCPAbstractItem { @@ -6423,7 +6460,7 @@ protected: /* including file 'src/items/item-line.h' */ -/* modified 2021-03-29T02:30:44, size 3429 */ +/* modified 2022-11-06T12:45:56, size 3429 */ class QCP_LIB_DECL QCPItemLine : public QCPAbstractItem { @@ -6473,7 +6510,7 @@ protected: /* including file 'src/items/item-curve.h' */ -/* modified 2021-03-29T02:30:44, size 3401 */ +/* modified 2022-11-06T12:45:56, size 3401 */ class QCP_LIB_DECL QCPItemCurve : public QCPAbstractItem { @@ -6524,7 +6561,7 @@ protected: /* including file 'src/items/item-rect.h' */ -/* modified 2021-03-29T02:30:44, size 3710 */ +/* modified 2022-11-06T12:45:56, size 3710 */ class QCP_LIB_DECL QCPItemRect : public QCPAbstractItem { @@ -6583,7 +6620,7 @@ protected: /* including file 'src/items/item-text.h' */ -/* modified 2021-03-29T02:30:44, size 5576 */ +/* modified 2022-11-06T12:45:56, size 5576 */ class QCP_LIB_DECL QCPItemText : public QCPAbstractItem { @@ -6680,7 +6717,7 @@ protected: /* including file 'src/items/item-ellipse.h' */ -/* modified 2021-03-29T02:30:44, size 3890 */ +/* modified 2022-11-06T12:45:56, size 3890 */ class QCP_LIB_DECL QCPItemEllipse : public QCPAbstractItem { @@ -6742,7 +6779,7 @@ protected: /* including file 'src/items/item-pixmap.h' */ -/* modified 2021-03-29T02:30:44, size 4407 */ +/* modified 2022-11-06T12:45:56, size 4407 */ class QCP_LIB_DECL QCPItemPixmap : public QCPAbstractItem { @@ -6811,7 +6848,7 @@ protected: /* including file 'src/items/item-tracer.h' */ -/* modified 2021-03-29T02:30:44, size 4811 */ +/* modified 2022-11-06T12:45:56, size 4811 */ class QCP_LIB_DECL QCPItemTracer : public QCPAbstractItem { @@ -6897,7 +6934,7 @@ Q_DECLARE_METATYPE(QCPItemTracer::TracerStyle) /* including file 'src/items/item-bracket.h' */ -/* modified 2021-03-29T02:30:44, size 3991 */ +/* modified 2022-11-06T12:45:56, size 3991 */ class QCP_LIB_DECL QCPItemBracket : public QCPAbstractItem { @@ -6964,7 +7001,7 @@ Q_DECLARE_METATYPE(QCPItemBracket::BracketStyle) /* including file 'src/polar/radialaxis.h' */ -/* modified 2021-03-29T02:30:44, size 12227 */ +/* modified 2022-11-06T12:45:56, size 12227 */ class QCP_LIB_DECL QCPPolarAxisRadial : public QCPLayerable @@ -7216,7 +7253,7 @@ Q_DECLARE_METATYPE(QCPPolarAxisRadial::SelectablePart) /* including file 'src/polar/layoutelement-angularaxis.h' */ -/* modified 2021-03-29T02:30:44, size 13461 */ +/* modified 2022-11-06T12:45:56, size 13461 */ class QCP_LIB_DECL QCPPolarAxisAngular : public QCPLayoutElement { @@ -7489,7 +7526,7 @@ Q_DECLARE_METATYPE(QCPPolarAxisAngular::SelectablePart) /* including file 'src/polar/polargrid.h' */ -/* modified 2021-03-29T02:30:44, size 4506 */ +/* modified 2022-11-06T12:45:56, size 4506 */ class QCP_LIB_DECL QCPPolarGrid :public QCPLayerable { @@ -7569,7 +7606,7 @@ Q_DECLARE_METATYPE(QCPPolarGrid::GridType) /* including file 'src/polar/polargraph.h' */ -/* modified 2021-03-29T02:30:44, size 9606 */ +/* modified 2022-11-06T12:45:56, size 9606 */ class QCP_LIB_DECL QCPPolarLegendItem : public QCPAbstractLegendItem diff --git a/re/dbccomparatorwindow.cpp b/re/dbccomparatorwindow.cpp index e9b87b2..f8eb0bd 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); } } } diff --git a/re/flowviewwindow.cpp b/re/flowviewwindow.cpp index 37236dd..77c088f 100644 --- a/re/flowviewwindow.cpp +++ b/re/flowviewwindow.cpp @@ -6,7 +6,7 @@ #include "qcpaxistickerhex.h" const QColor FlowViewWindow::graphColors[8] = {Qt::blue, Qt::green, Qt::black, Qt::red, //0 1 2 3 - Qt::gray, Qt::yellow, Qt::cyan, Qt::darkMagenta}; //4 5 6 7 + Qt::gray, Qt::darkYellow, Qt::cyan, Qt::darkMagenta}; //4 5 6 7 FlowViewWindow::FlowViewWindow(const QVector *frames, QWidget *parent) : @@ -26,10 +26,10 @@ FlowViewWindow::FlowViewWindow(const QVector *frames, QWidget *parent) playbackActive = false; playbackForward = true; - memset(refBytes, 0, 8); - memset(currBytes, 0, 8); + memset(refBytes, 0, 64); + memset(currBytes, 0, 64); memset(triggerValues, -1, sizeof(int) * 8); - triggerBits = 0; + for (int i = 0; i < 8; i++) triggerBits[i] = 0; //ui->graphView->setInteractions(); @@ -86,6 +86,8 @@ FlowViewWindow::FlowViewWindow(const QVector *frames, QWidget *parent) ui->graphView->setAntialiasedElements(QCP::aeNone); } + ui->graphView->setBufferDevicePixelRatio(1); + connect(ui->btnBackOne, SIGNAL(clicked(bool)), this, SLOT(btnBackOneClick())); connect(ui->btnPause, SIGNAL(clicked(bool)), this, SLOT(btnPauseClick())); connect(ui->btnReverse, SIGNAL(clicked(bool)), this, SLOT(btnReverseClick())); @@ -131,7 +133,7 @@ FlowViewWindow::FlowViewWindow(const QVector *frames, QWidget *parent) connect(ui->graphView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenuRequestGraph(QPoint))); ui->flowView->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->flowView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenuRequestFlow(QPoint))); - connect(ui->flowView, SIGNAL(gridClicked(int,int)), this, SLOT(gotCellClick(int,int))); + connect(ui->flowView, SIGNAL(gridClicked(int)), this, SLOT(gotCellClick(int))); // Prevent annoying accidental horizontal scrolling when filter list is populated with long interpreted message names ui->listFrameID->horizontalScrollBar()->setEnabled(false); @@ -262,13 +264,14 @@ void FlowViewWindow::changeGraphVisibility(int state){ ui->graphView->replot(); } } -void FlowViewWindow::gotCellClick(int x, int y) +void FlowViewWindow::gotCellClick(int bitPosition) { - int bitnum = (7-x) + (8 * y); - triggerBits = triggerBits ^ (1ull << bitnum); - if (triggerBits & (1ull << bitnum)) ui->flowView->setCellTextState(x, y, GridTextState::BOLD_BLUE); - else ui->flowView->setCellTextState(x, y, GridTextState::NORMAL); - qDebug() << "Bit Num: " << bitnum << " Hex of trigger bits: " << QString::number(triggerBits, 16); + int chunk = bitPosition / 64; + int idx = bitPosition & 63; + triggerBits[chunk] = triggerBits[chunk] ^ (1ull << idx); + if (triggerBits[chunk] & (1ull << idx)) ui->flowView->setCellTextState(bitPosition, GridTextState::BOLD_BLUE); + else ui->flowView->setCellTextState(bitPosition, GridTextState::NORMAL); + qDebug() << "Bit Num: " << bitPosition << " Hex of trigger bits: " << QString::number(triggerBits[chunk], 16); } void FlowViewWindow::graphRangeChanged(int range) { @@ -298,7 +301,7 @@ void FlowViewWindow::plottableDoubleClick(QCPAbstractPlottable* plottable, QMous else emit sendCenterTimeID(id, coord / 1000000.0); } -void FlowViewWindow::gotCenterTimeID(int32_t ID, double timestamp) +void FlowViewWindow::gotCenterTimeID(uint32_t ID, double timestamp) { int64_t t_stamp; @@ -310,7 +313,7 @@ void FlowViewWindow::gotCenterTimeID(int32_t ID, double timestamp) for (int j = 0; j < ui->listFrameID->count(); j++) { - int thisNum = FilterUtility::getIdAsInt(ui->listFrameID->item(j)); + uint32_t thisNum = FilterUtility::getIdAsInt(ui->listFrameID->item(j)); if (thisNum == ID) { ui->listFrameID->setCurrentRow(j); @@ -454,8 +457,8 @@ void FlowViewWindow::updatedFrames(int numFrames) refreshIDList(); updateFrameLabel(); removeAllGraphs(); - memset(refBytes, 0, 8); - memset(currBytes, 0, 8); + memset(refBytes, 0, 64); + memset(currBytes, 0, 64); updateDataView(); } else if (numFrames == -2) //all new set of frames. Reset @@ -518,9 +521,9 @@ void FlowViewWindow::updatedFrames(int numFrames) if (ui->cbLiveMode->checkState() == Qt::Checked) { currentPosition = frameCache.count() - 1; - memset(currBytes, 0, 8); + memset(currBytes, 0, 64); memcpy(currBytes, frameCache.at(currentPosition).payload().data(), frameCache.at(currentPosition).payload().length()); - memcpy(refBytes, currBytes, 8); + memcpy(refBytes, currBytes, 64); } if (needRefresh) @@ -635,6 +638,7 @@ void FlowViewWindow::changeID(QString newID) playbackTimer->stop(); playbackActive = false; + int maxBytes = 0; for (int x = 0; x < modelFrames->count(); x++) { CANFrame thisFrame = modelFrames->at(x); @@ -642,8 +646,10 @@ void FlowViewWindow::changeID(QString newID) { thisFrame.payload().clear(); frameCache.append(thisFrame); + if (thisFrame.payload().length() > maxBytes) maxBytes = thisFrame.payload().length(); } } + ui->flowView->setBytesToDraw(maxBytes); currentPosition = 0; if (frameCache.count() == 0) return; @@ -657,9 +663,9 @@ void FlowViewWindow::changeID(QString newID) updateGraphLocation(); - memset(currBytes, 0, 8); + memset(currBytes, 0, 64); memcpy(currBytes, frameCache.at(currentPosition).payload().constData(), frameCache.at(currentPosition).payload().length()); - memcpy(refBytes, currBytes, 8); + memcpy(refBytes, currBytes, 64); updateDataView(); ui->check_0->setChecked(true); @@ -705,9 +711,9 @@ void FlowViewWindow::btnStopClick() playbackActive = false; currentPosition = 0; - memset(currBytes, 0, 8); + memset(currBytes, 0, 64); memcpy(currBytes, frameCache.at(currentPosition).payload().constData(), frameCache.at(currentPosition).payload().length()); - memcpy(refBytes, currBytes, 8); + memcpy(refBytes, currBytes, 64); updateFrameLabel(); updateDataView(); @@ -831,31 +837,36 @@ void FlowViewWindow::updatePosition(bool forward) if (ui->cbAutoRef->isChecked()) { - memcpy(refBytes, currBytes, 8); + memcpy(refBytes, currBytes, 64); } //figure out which bits changed since the previous frame and then AND that with the trigger bits. If any bits //get through that then they're changed and a trigger so we stop playback at this frame. - uint64_t changedBits = 0; - uint8_t cngByte; - for (int i = 0; i < frameCache.at(currentPosition).payload().length(); i++) + //This is complicated by the fact that CAN-FD frames might have far more than 64 bits. It is necessary + //to thus process them 64 bits at a time and just move chunk to chunk until done. + for (int chunk = 0; chunk < frameCache.at(currentPosition).payload().length(); chunk += 8) { - unsigned char thisByte = static_cast(frameCache.at(currentPosition).payload().data()[i]); - cngByte = currBytes[i] ^ thisByte; - changedBits |= (uint64_t)cngByte << (8ull * i); - } + uint64_t changedBits = 0; + uint8_t cngByte; + 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()[i]); + cngByte = currBytes[i] ^ thisByte; + changedBits |= (uint64_t)cngByte << (8ull * (i & 7)); + } - qDebug() << "ChangedBits: " << QString::number(changedBits, 16); - qDebug() << "TriggerBits: " << QString::number(triggerBits, 16); - changedBits &= triggerBits; - qDebug() << "Final ChangedBits: " << QString::number(changedBits, 16); - if (changedBits) - { - playbackActive = false; - playbackTimer->stop(); + qDebug() << "ChangedBits: " << QString::number(changedBits, 16); + qDebug() << "TriggerBits: " << QString::number(triggerBits[chunk / 8], 16); + changedBits &= triggerBits[chunk / 8]; + qDebug() << "Final ChangedBits: " << QString::number(changedBits, 16); + if (changedBits) + { + playbackActive = false; + playbackTimer->stop(); + } } - - memset(currBytes, 0, 8); + memset(currBytes, 0, 64); memcpy(currBytes, frameCache.at(currentPosition).payload().constData(), frameCache.at(currentPosition).payload().length()); if (ui->cbSync->checkState() == Qt::Checked) emit sendCenterTimeID(frameCache[currentPosition].frameId(), frameCache[currentPosition].timeStamp().microSeconds() / 1000000.0); diff --git a/re/flowviewwindow.h b/re/flowviewwindow.h index e2705c1..9d13330 100644 --- a/re/flowviewwindow.h +++ b/re/flowviewwindow.h @@ -37,9 +37,9 @@ private slots: void saveFileFlow(); void saveFileGraph(); void plottableDoubleClick(QCPAbstractPlottable* plottable, QMouseEvent* event); - void gotCenterTimeID(int32_t ID, double timestamp); + void gotCenterTimeID(uint32_t ID, double timestamp); void updateTriggerValues(); - void gotCellClick(int x, int y); + void gotCellClick(int bitPosition); void graphRangeChanged(int range); void changeGraphVisibility(int state); @@ -51,10 +51,10 @@ private: QList foundID; QList frameCache; const QVector *modelFrames; - unsigned char refBytes[8]; - unsigned char currBytes[8]; + unsigned char refBytes[64]; + unsigned char currBytes[64]; int triggerValues[8]; - uint64_t triggerBits; + uint64_t triggerBits[8]; int currentPosition; QTimer *playbackTimer; bool playbackActive; diff --git a/re/frameinfowindow.cpp b/re/frameinfowindow.cpp index b545b50..d47faf9 100644 --- a/re/frameinfowindow.cpp +++ b/re/frameinfowindow.cpp @@ -8,7 +8,7 @@ #include "qcpaxistickerhex.h" const QColor FrameInfoWindow::byteGraphColors[8] = {Qt::blue, Qt::green, Qt::black, Qt::red, //0 1 2 3 - Qt::gray, Qt::yellow, Qt::cyan, Qt::darkMagenta}; //4 5 6 7 + Qt::gray, Qt::darkYellow, Qt::cyan, Qt::darkMagenta}; //4 5 6 7 QPen FrameInfoWindow::bytePens[8]; const int numIntervalHistBars = 20; @@ -34,85 +34,78 @@ FrameInfoWindow::FrameInfoWindow(const QVector *frames, QWidget *paren connect(MainWindow::getReference(), &MainWindow::framesUpdated, this, &FrameInfoWindow::updatedFrames); connect(ui->btnSave, &QAbstractButton::clicked, this, &FrameInfoWindow::saveDetails); - connect(ui->check_0, &QCheckBox::stateChanged, this, &FrameInfoWindow::changeGraphVisibility); - connect(ui->check_1, &QCheckBox::stateChanged, this, &FrameInfoWindow::changeGraphVisibility); - connect(ui->check_2, &QCheckBox::stateChanged, this, &FrameInfoWindow::changeGraphVisibility); - connect(ui->check_3, &QCheckBox::stateChanged, this, &FrameInfoWindow::changeGraphVisibility); - connect(ui->check_4, &QCheckBox::stateChanged, this, &FrameInfoWindow::changeGraphVisibility); - connect(ui->check_5, &QCheckBox::stateChanged, this, &FrameInfoWindow::changeGraphVisibility); - connect(ui->check_6, &QCheckBox::stateChanged, this, &FrameInfoWindow::changeGraphVisibility); - connect(ui->check_7, &QCheckBox::stateChanged, this, &FrameInfoWindow::changeGraphVisibility); + ui->splitter->setStretchFactor(0, 1); //idx, stretch factor + ui->splitter->setStretchFactor(1, 4); //goal is to make right hand side larger by default - - ui->graphHistogram->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectAxes | - QCP::iSelectLegend | QCP::iSelectPlottables); - - ui->graphHistogram->xAxis->setRange(0, 63); - ui->graphHistogram->yAxis->setRange(0, 100); - QSharedPointer graphHistoLogTicker(new QCPAxisTickerLog); - ui->graphHistogram->yAxis->setTicker(graphHistoLogTicker); - ui->graphHistogram->yAxis2->setTicker(graphHistoLogTicker); - ui->graphHistogram->yAxis->setNumberFormat("eb"); // e = exponential, b = beautiful decimal powers - ui->graphHistogram->yAxis->setNumberPrecision(0); //log ticker always picks powers of 10 so no need or use for precision - - ui->graphHistogram->axisRect()->setupFullAxesBox(); - - ui->graphHistogram->xAxis->setLabel("Bits"); - ui->graphHistogram->yAxis->setLabel("Instances"); - - ui->graphHistogram->legend->setVisible(false); - - ui->graphBytes->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectAxes | - QCP::iSelectLegend | QCP::iSelectPlottables); - - ui->graphBytes->xAxis->setRange(0, 63); - ui->graphBytes->yAxis->setRange(0, 265); - if (useHexTicker) + for (int i = 0; i < 8; i++) { - QSharedPointer hexTicker(new QCPAxisTickerHex); - ui->graphBytes->yAxis->setTicker(hexTicker); + graphByte[i] = new QCustomPlot(); + setupByteGraph(graphByte[i], i); + ui->gridLower->addWidget(graphByte[i], i / 4, i & 3); } - ui->graphBytes->axisRect()->setupFullAxesBox(); - ui->graphBytes->xAxis->setLabel("Time"); - if (useHexTicker) ui->graphBytes->yAxis->setLabel("Value (HEX)"); - else ui->graphBytes->yAxis->setLabel("Value (Dec)"); + ui->gridUpper->addWidget(new QLabel("Heatmap"), 0, 0); + heatmap = new CANDataGrid(); + heatmap->setMode(GridMode::HEAT_VIEW); + ui->gridUpper->addWidget(heatmap, 1, 0); - ui->graphBytes->legend->setVisible(false); + ui->gridUpper->addWidget(new QLabel("Bit Histogram"), 0, 1); + graphHistogram = new QCustomPlot(); + ui->gridUpper->addWidget(graphHistogram, 1, 1); + + ui->gridUpper->setRowMinimumHeight(0, 20); + ui->gridUpper->setRowStretch(0, 1); + ui->gridUpper->setRowStretch(1, 10); + + graphHistogram->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectAxes | + QCP::iSelectLegend); + + graphHistogram->xAxis->setRange(0, 63); + graphHistogram->yAxis->setRange(0, 100); + QSharedPointer graphHistoLogTicker(new QCPAxisTickerLog); + graphHistogram->yAxis->setTicker(graphHistoLogTicker); + //graphHistogram->yAxis2->setTicker(graphHistoLogTicker); + graphHistogram->yAxis->setNumberFormat("eb"); // e = exponential, b = beautiful decimal powers + graphHistogram->yAxis->setNumberPrecision(0); //log ticker always picks powers of 10 so no need or use for precision + + //graphHistogram->axisRect()->setupFullAxesBox(); + graphHistogram->setBufferDevicePixelRatio(1); + + graphHistogram->xAxis->setLabel("Bits"); + graphHistogram->yAxis->setLabel("Instances"); + + graphHistogram->legend->setVisible(false); ui->timeHistogram->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectAxes | - QCP::iSelectLegend | QCP::iSelectPlottables); + QCP::iSelectLegend); ui->timeHistogram->xAxis->setRange(0, numIntervalHistBars); ui->timeHistogram->yAxis->setRange(0, 100); QSharedPointer logTicker(new QCPAxisTickerLog); ui->timeHistogram->yAxis->setTicker(logTicker); - ui->timeHistogram->yAxis2->setTicker(logTicker); + //ui->timeHistogram->yAxis2->setTicker(logTicker); ui->timeHistogram->yAxis->setScaleType(QCPAxis::stLogarithmic); ui->timeHistogram->yAxis->setNumberFormat("eb"); // e = exponential, b = beautiful decimal powers ui->timeHistogram->yAxis->setNumberPrecision(0); //log ticker always picks powers of 10 so no need or use for precision - ui->timeHistogram->axisRect()->setupFullAxesBox(); + //ui->timeHistogram->axisRect()->setupFullAxesBox(); ui->timeHistogram->xAxis->setLabel("Interval (ms)"); ui->timeHistogram->yAxis->setLabel("Occurrences"); ui->timeHistogram->legend->setVisible(false); + ui->timeHistogram->setBufferDevicePixelRatio(1); if (useOpenGL) { - ui->graphHistogram->setAntialiasedElements(QCP::aeAll); - ui->graphHistogram->setOpenGl(true); - ui->graphBytes->setAntialiasedElements(QCP::aeAll); - ui->graphBytes->setOpenGl(true); + graphHistogram->setAntialiasedElements(QCP::aeAll); + graphHistogram->setOpenGl(true); ui->timeHistogram->setAntialiasedElements(QCP::aeAll); ui->timeHistogram->setOpenGl(true); } else { - ui->graphHistogram->setOpenGl(false); - ui->graphHistogram->setAntialiasedElements(QCP::aeNone); - ui->graphBytes->setOpenGl(false); - ui->graphBytes->setAntialiasedElements(QCP::aeNone); + graphHistogram->setOpenGl(false); + graphHistogram->setAntialiasedElements(QCP::aeNone); ui->timeHistogram->setOpenGl(false); ui->timeHistogram->setAntialiasedElements(QCP::aeNone); } @@ -128,9 +121,113 @@ FrameInfoWindow::FrameInfoWindow(const QVector *frames, QWidget *paren bytePens[i].setWidth(1); } + connect(graphHistogram, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(mousePress())); + connect(graphHistogram, SIGNAL(mouseWheel(QWheelEvent*)), this, SLOT(mouseWheel())); + connect(ui->timeHistogram, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(mousePress())); + connect(ui->timeHistogram, SIGNAL(mouseWheel(QWheelEvent*)), this, SLOT(mouseWheel())); + dbcHandler = DBCHandler::getReference(); } +void FrameInfoWindow::setupByteGraph(QCustomPlot *plot, int num) +{ + plot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectAxes | + QCP::iSelectLegend); + + plot->xAxis->setRange(0, 63); + plot->yAxis->setRange(0, 265); + if (useHexTicker) + { + QSharedPointer hexTicker(new QCPAxisTickerHex); + plot->yAxis->setTicker(hexTicker); + } + //plot->axisRect()->setupFullAxesBox(); + + plot->xAxis->setLabel("Time [" + QString::number(num) + "]"); + //if (useHexTicker) plot->yAxis->setLabel("Value (HEX)"); + //else plot->yAxis->setLabel("Value (Dec)"); + plot->yAxis->setLabel(""); + + plot->legend->setVisible(false); + plot->setBufferDevicePixelRatio(1); + + if (useOpenGL) + { + plot->setAntialiasedElements(QCP::aeAll); + plot->setOpenGl(true); + } + else + { + plot->setOpenGl(false); + plot->setAntialiasedElements(QCP::aeNone); + } + + connect(plot, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(mousePress())); + connect(plot, SIGNAL(mouseWheel(QWheelEvent*)), this, SLOT(mouseWheel())); + connect(plot, &QCustomPlot::mouseDoubleClick, this, &FrameInfoWindow::mouseDoubleClick); +} + +void FrameInfoWindow::mousePress() +{ + QCustomPlot *plot = qobject_cast(sender()); + if (!plot) return; + // if an axis is selected, only allow the direction of that axis to be dragged + // if no axis is selected, both directions may be dragged + + if (plot->xAxis->selectedParts().testFlag(QCPAxis::spAxis)) + plot->axisRect()->setRangeDrag(plot->xAxis->orientation()); + else if (plot->yAxis->selectedParts().testFlag(QCPAxis::spAxis)) + plot->axisRect()->setRangeDrag(plot->yAxis->orientation()); + else + plot->axisRect()->setRangeDrag(Qt::Horizontal|Qt::Vertical); +} + +void FrameInfoWindow::mouseWheel() +{ + QCustomPlot *plot = qobject_cast(sender()); + if (!plot) return; + + // if an axis is selected, only allow the direction of that axis to be zoomed + // if no axis is selected, both directions may be zoomed + + if (plot->xAxis->selectedParts().testFlag(QCPAxis::spAxis)) + plot->axisRect()->setRangeZoom(plot->xAxis->orientation()); + else if (plot->yAxis->selectedParts().testFlag(QCPAxis::spAxis)) + plot->axisRect()->setRangeZoom(plot->yAxis->orientation()); + else + plot->axisRect()->setRangeZoom(Qt::Horizontal|Qt::Vertical); +} + + +//two modes here. If none of the 8 sub graphs are hidden then hide all except the one the user +//just double clicked on. Otherwise unhide the 7 hidden ones +void FrameInfoWindow::mouseDoubleClick() +{ + QCustomPlot *plot = qobject_cast(sender()); + bool hideMode = true; + + for (int i = 0; i < 8; i++) + { + if (ui->gridLower->itemAt(i)->widget()->isHidden()) hideMode = false; + } + + if (hideMode) + { + for (int i = 0; i < 8; i++) + { + if (ui->gridLower->itemAt(i)->widget() == (plot)) qDebug() << "Idx " << i << " matched!"; + else ui->gridLower->itemAt(i)->widget()->setHidden(true); + } + } + else + { + for (int i = 0; i < 8; i++) + { + ui->gridLower->itemAt(i)->widget()->setHidden(false); + } + } +} + void FrameInfoWindow::showEvent(QShowEvent* event) { QDialog::showEvent(event); @@ -267,7 +364,7 @@ void FrameInfoWindow::updatedFrames(int numFrames) } //default is to sort in ascending order ui->listFrameID->sortItems(); - ui->lblFrameID->setText(tr("Frame IDs: (") + QString::number(ui->listFrameID->count()) + tr(" unique ids)")); + ui->lblUniqueID->setText(")" + QString::number(ui->listFrameID->count()) + tr(" unique ids)")); } } @@ -290,6 +387,12 @@ void FrameInfoWindow::updateDetailsWindow(QString newID) double maxY = -1000.0; uint8_t changedBits[8]; uint8_t referenceBits[8]; + uint8_t heatVals[512]; + + //these two used by bitflip heatmap functionality + uint8_t refByte[8]; + double bitFlipHeat[64]; + QTreeWidgetItem *baseNode, *dataBase, *histBase, *tempItem; if (modelFrames->count() == 0) return; @@ -310,6 +413,8 @@ void FrameInfoWindow::updateDetailsWindow(QString newID) if (thisFrame.frameId() == static_cast(targettedID)) frameCache.append(thisFrame); } + if (frameCache.count() == 0) return; //nothing to do if there are no frames! + const unsigned char *data = reinterpret_cast(frameCache.at(0).payload().constData()); int dataLen = frameCache.at(0).payload().length(); @@ -397,7 +502,11 @@ void FrameInfoWindow::updateDetailsWindow(QString newID) maxData[i] = -1; for (int k = 0; k < 256; k++) dataHistogram[k][i] = 0; } - for (int j = 0; j < 64; j++) bitfieldHistogram[j] = 0; + for (int j = 0; j < 64; j++) + { + bitfieldHistogram[j] = 0; + bitFlipHeat[j] = 0; + } signalInstances.clear(); data = reinterpret_cast(frameCache.at(0).payload().constData()); @@ -407,6 +516,7 @@ void FrameInfoWindow::updateDetailsWindow(QString newID) { changedBits[c] = 0; referenceBits[c] = data[c]; + refByte[c] = data[c]; //qDebug() << referenceBits[c]; } @@ -460,6 +570,16 @@ void FrameInfoWindow::updateDetailsWindow(QString newID) } } changedBits[c] |= referenceBits[c] ^ dat; + + if (refByte[c] != dat) //if this byte doesn't match the value it last had + { + uint8_t newBits = refByte[c] ^ dat; //get changed bits since last ref + for (int l = 0; l < 8; l++) + { + if (newBits & (1 << l)) bitFlipHeat[(c * 8) + l]++; + } + refByte[c] = dat; //set the reference to this current byte now that processing is done + } } //Search every signal in the selected message and give output of the range the signal took and @@ -485,6 +605,9 @@ void FrameInfoWindow::updateDetailsWindow(QString newID) } } + //Divide all the bit flip heat values by the number of frames to get a ratio + for (int j = 0; j < 64; j++) bitFlipHeat[j] /= (double)frameCache.count(); + std::sort(sortedIntervals.begin(), sortedIntervals.end()); int64_t intervalStdDiv = 0, intervalPctl5 = 0, intervalPctl95 = 0, intervalMean = 0, intervalVariance = 0; @@ -530,6 +653,8 @@ void FrameInfoWindow::updateDetailsWindow(QString newID) avgInterval = avgInterval / (frameCache.count() - 1); else avgInterval = 0; + //now that data processing is done, create all of our output + tempItem = new QTreeWidgetItem(); if (minLen < maxLen) @@ -596,7 +721,7 @@ void FrameInfoWindow::updateDetailsWindow(QString newID) { tempItem = new QTreeWidgetItem(); tempItem->setText(0, QString::number(c) + " (Byte " + QString::number(c / 8) + " Bit " - + QString::number(c % 8) + ") :" + QString::number(bitfieldHistogram[c])); + + QString::number(c % 8) + ") : " + QString::number(bitfieldHistogram[c])); dataBase->addChild(tempItem); histGraphX.append(c); @@ -605,6 +730,28 @@ void FrameInfoWindow::updateDetailsWindow(QString newID) } baseNode->addChild(dataBase); + //heat map output + dataBase = new QTreeWidgetItem(); + dataBase->setText(0, tr("Bitchange Heatmap")); + memset(heatVals, 0, 512); //always clear the array before populating it. + for (int c = 0; c < 8 * maxLen; c++) + { + tempItem = new QTreeWidgetItem(); + tempItem->setText(0, QString::number(c) + " (Byte " + QString::number(c / 8) + " Bit " + + QString::number(c % 8) + ") : " + QString::number(bitFlipHeat[c] * 100.0, 'f', 2)); + + dataBase->addChild(tempItem); + histGraphX.append(c); + histGraphY.append(bitfieldHistogram[c]); + if (bitfieldHistogram[c] > maxY) maxY = bitfieldHistogram[c]; + uint8_t heat = bitFlipHeat[c] * 255; + if ((heat < 1) && (bitFlipHeat[c] > 0.0001)) heat = 1; //make sure any little bit of heat causes at least some output + //qDebug() << "Heat for bit " << c << " is " << heat; + heatVals[c] = heat; + } + baseNode->addChild(dataBase); + heatmap->setHeat(heatVals); + QHash>::const_iterator it = signalInstances.constBegin(); while (it != signalInstances.constEnd()) { dataBase = new QTreeWidgetItem(); @@ -623,29 +770,29 @@ void FrameInfoWindow::updateDetailsWindow(QString newID) ui->treeDetails->insertTopLevelItem(0, baseNode); - ui->graphHistogram->clearGraphs(); - ui->graphHistogram->addGraph(); - ui->graphHistogram->graph()->setData(histGraphX, histGraphY); - ui->graphHistogram->graph()->setLineStyle(QCPGraph::lsStepLeft); //connect points with lines + graphHistogram->clearGraphs(); + graphHistogram->addGraph(); + graphHistogram->graph()->setData(histGraphX, histGraphY); + graphHistogram->graph()->setLineStyle(QCPGraph::lsStepLeft); //connect points with lines QBrush graphBrush; graphBrush.setColor(Qt::red); graphBrush.setStyle(Qt::SolidPattern); - ui->graphHistogram->graph()->setPen(Qt::NoPen); - ui->graphHistogram->graph()->setBrush(graphBrush); - ui->graphHistogram->yAxis->setRange(0.8, maxY * 1.2); - ui->graphHistogram->yAxis->setScaleType(QCPAxis::stLogarithmic); - ui->graphHistogram->axisRect()->setupFullAxesBox(); - ui->graphHistogram->replot(); + graphHistogram->graph()->setPen(Qt::NoPen); + graphHistogram->graph()->setBrush(graphBrush); + graphHistogram->yAxis->setRange(0.8, maxY * 1.2); + graphHistogram->yAxis->setScaleType(QCPAxis::stLogarithmic); + graphHistogram->axisRect()->setupFullAxesBox(); + graphHistogram->replot(); - ui->graphBytes->clearGraphs(); for (int graphs = 0; graphs < 8; graphs++) { - graphRef[graphs] = ui->graphBytes->addGraph(); - ui->graphBytes->graph()->setData(byteGraphX, byteGraphY[graphs]); - ui->graphBytes->graph()->setPen(bytePens[graphs]); + graphByte[graphs]->clearGraphs(); + graphRef[graphs] = graphByte[graphs]->addGraph(); + graphByte[graphs]->graph()->setData(byteGraphX, byteGraphY[graphs]); + graphByte[graphs]->graph()->setPen(bytePens[graphs]); + graphByte[graphs]->xAxis->setRange(0, byteGraphX.count()); + graphByte[graphs]->replot(); } - ui->graphBytes->xAxis->setRange(0, byteGraphX.count()); - ui->graphBytes->replot(); ui->timeHistogram->clearGraphs(); ui->timeHistogram->addGraph(); @@ -661,15 +808,6 @@ void FrameInfoWindow::updateDetailsWindow(QString newID) ui->timeHistogram->axisRect()->setupFullAxesBox(); ui->timeHistogram->rescaleAxes(); ui->timeHistogram->replot(); - - ui->check_0->setChecked(true); - ui->check_1->setChecked(true); - ui->check_2->setChecked(true); - ui->check_3->setChecked(true); - ui->check_4->setChecked(true); - ui->check_5->setChecked(true); - ui->check_6->setChecked(true); - ui->check_7->setChecked(true); } else { @@ -697,7 +835,7 @@ void FrameInfoWindow::refreshIDList() } //default is to sort in ascending order ui->listFrameID->sortItems(); - ui->lblFrameID->setText(tr("Frame IDs: (") + QString::number(ui->listFrameID->count()) + tr(" unique ids)")); + ui->lblUniqueID->setText("(" + QString::number(ui->listFrameID->count()) + tr(" unique ids)")); } void FrameInfoWindow::saveDetails() @@ -744,24 +882,6 @@ void FrameInfoWindow::saveDetails() } } -void FrameInfoWindow::changeGraphVisibility(int state){ - QCheckBox *sender = qobject_cast(QObject::sender()); - if(sender){ - sender->objectName(); - int graphId = sender->objectName().right(1).toInt(); - for (int k = 0; k < 8; k++) - { - if (k == graphId && graphRef[k] && graphRef[k]->data()){ - graphRef[k]->setVisible(state); - qDebug() << graphId << state << k; - } - } - qDebug() << graphId << state; - - ui->graphBytes->replot(); - } -} - void FrameInfoWindow::dumpNode(QTreeWidgetItem* item, QFile *file, int indent) { for (int i = 0; i < indent; i++) file->write("\t"); diff --git a/re/frameinfowindow.h b/re/frameinfowindow.h index 5196d93..c67c2a7 100644 --- a/re/frameinfowindow.h +++ b/re/frameinfowindow.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "can_structs.h" #include "bus_protocols/j1939_handler.h" #include "dbc/dbchandler.h" @@ -28,10 +29,15 @@ private slots: void updateDetailsWindow(QString); void updatedFrames(int); void saveDetails(); - void changeGraphVisibility(int state); + void mousePress(); + void mouseWheel(); + void mouseDoubleClick(); private: Ui::FrameInfoWindow *ui; + QCustomPlot *graphByte[8]; + QCustomPlot *graphHistogram; + CANDataGrid *heatmap; QList foundID; QList frameCache; @@ -47,6 +53,7 @@ private: void refreshIDList(); void closeEvent(QCloseEvent *event); bool eventFilter(QObject *obj, QEvent *event); + void setupByteGraph(QCustomPlot *plot, int num); void readSettings(); void writeSettings(); void dumpNode(QTreeWidgetItem* item, QFile *file, int indent); diff --git a/re/fuzzingwindow.cpp b/re/fuzzingwindow.cpp index c320248..d7c84a4 100644 --- a/re/fuzzingwindow.cpp +++ b/re/fuzzingwindow.cpp @@ -26,7 +26,7 @@ FuzzingWindow::FuzzingWindow(const QVector *frames, QWidget *parent) : connect(ui->spinTiming, SIGNAL(valueChanged(int)), this, SLOT(changePlaybackSpeed(int))); connect(ui->listID, &QListWidget::itemChanged, this, &FuzzingWindow::idListChanged); connect(ui->spinBytes, SIGNAL(valueChanged(int)), this, SLOT(changedNumDataBytes(int))); - connect(ui->bitfield, SIGNAL(gridClicked(int,int)), this, SLOT(bitfieldClicked(int,int))); + connect(ui->bitfield, SIGNAL(gridClicked(int)), this, SLOT(bitfieldClicked(int))); connect(ui->txtByte0, &QLineEdit::returnPressed, this, [=](){changedDataByteText(0, ui->txtByte0->text());}); connect(ui->txtByte1, &QLineEdit::returnPressed, this, [=](){changedDataByteText(1, ui->txtByte1->text());}); connect(ui->txtByte2, &QLineEdit::returnPressed, this, [=](){changedDataByteText(2, ui->txtByte2->text());}); @@ -43,7 +43,7 @@ FuzzingWindow::FuzzingWindow(const QVector *frames, QWidget *parent) : currentlyFuzzing = false; - for (int j = 0; j < 64; j++) bitGrid[j] = 1; + for (int j = 0; j < 512; j++) bitGrid[j] = 1; numBits = 64; bitAccum = 0; redrawGrid(); @@ -146,8 +146,10 @@ void FuzzingWindow::changedNumDataBytes(int newVal) ui->txtByte6->setEnabled((newVal > 6) ? true : false); ui->txtByte7->setEnabled((newVal > 7) ? true : false); + ui->bitfield->setBytesToDraw(newVal); + int byt; - for (int i = 0; i < 64; i++) + for (int i = 0; i < 511; i++) { byt = i / 8; if (byt >= newVal) @@ -448,20 +450,18 @@ void FuzzingWindow::idListChanged(QListWidgetItem *item) } /* -bitGrid stores the state of all 64 bits. +bitGrid stores the state of all bits. The grid is capable of showing the following colors: White = not used (left as 0) Gray = past the end of the valid bits (because of # of data bytes requested) Green = fuzz it black = always keep it set to 1 */ -void FuzzingWindow::bitfieldClicked(int x, int y) +void FuzzingWindow::bitfieldClicked(int bitPos) { - qDebug() << "X: " << x << " Y: " << y; - int bit = (7 - x) + (y * 8); - if (bitGrid[bit] == 3) return; //naughty! - bitGrid[bit]++; - if (bitGrid[bit] > 2) bitGrid[bit] = 0; + if (bitGrid[bitPos] == 3) return; //naughty! + bitGrid[bitPos]++; + if (bitGrid[bitPos] > 2) bitGrid[bitPos] = 0; redrawGrid(); } @@ -469,11 +469,11 @@ void FuzzingWindow::bitfieldClicked(int x, int y) void FuzzingWindow::redrawGrid() { //now update the bits in the bitfield control - uint8_t refBytes[8]; - uint8_t dataBytes[8]; - uint8_t usedBytes[8]; + uint8_t refBytes[64]; + uint8_t dataBytes[64]; + uint8_t usedBytes[64]; - for (int j = 0; j < 8; j++) + for (int j = 0; j < 64; j++) { refBytes[j] = 0; dataBytes[j] = 0; @@ -482,7 +482,7 @@ void FuzzingWindow::redrawGrid() numBits = 0; - for (int i = 0; i < 64; i++) + for (int i = 0; i < 512; i++) { int byt = i / 8; int bit = i % 8; diff --git a/re/fuzzingwindow.h b/re/fuzzingwindow.h index 23a7ece..6b52da4 100644 --- a/re/fuzzingwindow.h +++ b/re/fuzzingwindow.h @@ -39,7 +39,7 @@ private slots: void setAllFilters(); void toggleFuzzing(); void idListChanged(QListWidgetItem *item); - void bitfieldClicked(int, int); + void bitfieldClicked(int); void changedNumDataBytes(int newVal); void updatedFrames(int numFrames); @@ -54,8 +54,8 @@ private: bool seqIDScan, rangeIDSelect; int bitSequenceType; bool currentlyFuzzing; - uint8_t currentBytes[8]; - uint8_t bitGrid[64]; + uint8_t currentBytes[64]; + uint8_t bitGrid[512]; uint8_t numBits; uint64_t bitAccum; int numSentFrames; diff --git a/re/graphingwindow.cpp b/re/graphingwindow.cpp index 44b10bc..80b32d5 100644 --- a/re/graphingwindow.cpp +++ b/re/graphingwindow.cpp @@ -3,6 +3,7 @@ #include "newgraphdialog.h" #include "mainwindow.h" #include "helpwindow.h" +#include "utility.h" #include #include @@ -33,9 +34,16 @@ GraphingWindow::GraphingWindow(const QVector *frames, QWidget *parent) ui->graphingView->xAxis->setLabel("Time Axis"); ui->graphingView->yAxis->setLabel("Value Axis"); ui->graphingView->xAxis->setNumberFormat("f"); - if (secondsMode) ui->graphingView->xAxis->setNumberPrecision(6); + if (Utility::timeStyle == TS_SECONDS) ui->graphingView->xAxis->setNumberPrecision(6); else ui->graphingView->xAxis->setNumberPrecision(0); + if (Utility::timeStyle == TS_CLOCK) + { + QSharedPointer timeTicker = QSharedPointer::create(); + timeTicker->setTimeFormat("%h:%m:%s.%z"); + ui->graphingView->xAxis->setTicker(timeTicker); + } + ui->graphingView->legend->setVisible(true); QFont legendFont = font(); legendFont.setPointSize(10); @@ -61,9 +69,9 @@ GraphingWindow::GraphingWindow(const QVector *frames, QWidget *parent) // connect slot that ties some axis selections together (especially opposite axes): connect(ui->graphingView, SIGNAL(selectionChangedByUser()), this, SLOT(selectionChanged())); //connect up the mouse controls - connect(ui->graphingView, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(mousePress())); connect(ui->graphingView, SIGNAL(plottableDoubleClick(QCPAbstractPlottable*,int,QMouseEvent*)), this, SLOT(plottableDoubleClick(QCPAbstractPlottable*,int,QMouseEvent*))); connect(ui->graphingView, SIGNAL(plottableClick(QCPAbstractPlottable*,int,QMouseEvent*)), this, SLOT(plottableClick(QCPAbstractPlottable*,int,QMouseEvent*))); + connect(ui->graphingView, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(mousePress())); connect(ui->graphingView, SIGNAL(mouseWheel(QWheelEvent*)), this, SLOT(mouseWheel())); // make bottom and left axes transfer their ranges to top and right axes: @@ -89,6 +97,9 @@ GraphingWindow::GraphingWindow(const QVector *frames, QWidget *parent) if (useOpenGL) { + //Fix the device pixel ratio for openGL so the graph doesn't render double sized + ui->graphingView->setBufferDevicePixelRatio(1); + ui->graphingView->setAntialiasedElements(QCP::aeAll); //ui->graphingView->setNoAntialiasingOnDrag(true); ui->graphingView->setOpenGl(true); @@ -136,7 +147,7 @@ void GraphingWindow::changeEvent(QEvent *event) } else { - setWindowOpacity(0.25); + //setWindowOpacity(0.25); // widget is now inactive qDebug() << "Hide"; } @@ -151,7 +162,7 @@ void GraphingWindow::readSettings() resize(settings.value("Graphing/WindowSize", QSize(800, 600)).toSize()); move(Utility::constrainedWindowPos(settings.value("Graphing/WindowPos", QPoint(50, 50)).toPoint())); } - secondsMode = settings.value("Main/TimeSeconds", false).toBool(); + useOpenGL = settings.value("Main/UseOpenGL", false).toBool(); } @@ -199,7 +210,7 @@ void GraphingWindow::updatedFrames(int numFrames) ui->graphingView->replot(); //now, redisplay them all } else //just got some new frames. See if they are relevant. - { + { if (numFrames > modelFrames->count()) return; for (int j = 0; j < graphParams.count(); j++) @@ -210,7 +221,7 @@ void GraphingWindow::updatedFrames(int numFrames) for (int i = modelFrames->count() - numFrames; i < modelFrames->count(); i++) { thisFrame = modelFrames->at(i); - if (graphParams[j].ID == thisFrame.frameId()) + if ( graphParams[j].ID == thisFrame.frameId() && ( (graphParams[j].bus == -1) || (graphParams[j].bus == thisFrame.bus) ) ) { appendToGraph(graphParams[j], thisFrame, x, y); appendedToGraph = true; @@ -264,7 +275,7 @@ void GraphingWindow::plottableDoubleClick(QCPAbstractPlottable* plottable, int d //apply transforms to get the X axis value where we double clicked double coord = plottable->keyAxis()->pixelToCoord(event->localPos().x()); id = plottable->property("id").toInt(); - if (secondsMode) emit sendCenterTimeID(id, coord); + if (Utility::timeStyle == TS_SECONDS) emit sendCenterTimeID(id, coord); else emit sendCenterTimeID(id, coord / 1000000.0); double x, y; @@ -281,7 +292,7 @@ void GraphingWindow::plottableDoubleClick(QCPAbstractPlottable* plottable, int d locationText->setText("X: " + QString::number(x) + " Y: " + QString::number(itemTracer->position->value())); } -void GraphingWindow::gotCenterTimeID(int32_t ID, double timestamp) +void GraphingWindow::gotCenterTimeID(uint32_t ID, double timestamp) { Q_UNUSED(ID) //its problematic to try to highlight a graph since we get the ID @@ -295,7 +306,7 @@ void GraphingWindow::gotCenterTimeID(int32_t ID, double timestamp) QCPRange range = ui->graphingView->xAxis->range(); double offset = range.size() / 2.0; - if (!secondsMode) timestamp *= 1000000.0; //timestamp is always in seconds when being passed so convert if necessary + if (Utility::timeStyle != TS_SECONDS) timestamp *= 1000000.0; //timestamp is always in seconds when being passed so convert if necessary ui->graphingView->xAxis->setRange(timestamp - offset, timestamp + offset); ui->graphingView->replot(); } @@ -560,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; @@ -579,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; @@ -594,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; @@ -614,7 +625,8 @@ void GraphingWindow::removeAllGraphs() QMessageBox::StandardButton confirmDialog; confirmDialog = QMessageBox::question(this, "Really?", "Remove all graphs?", QMessageBox::Yes|QMessageBox::No); - if (confirmDialog == QMessageBox::Yes) { + if (confirmDialog == QMessageBox::Yes) + { ui->graphingView->clearGraphs(); ui->graphingView->clearItems(); graphParams.clear(); @@ -689,7 +701,6 @@ void GraphingWindow::contextMenuRequest(QPoint pos) void GraphingWindow::saveGraphs() { - QString filename; QFileDialog dialog(this); QSettings settings; @@ -706,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]) @@ -729,7 +740,6 @@ void GraphingWindow::saveGraphs() void GraphingWindow::saveSpreadsheet() { - QString filename; QFileDialog dialog(this); QSettings settings; @@ -744,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; /* @@ -767,12 +777,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; @@ -783,27 +793,25 @@ 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); - 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 (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++) + outFile.write(QString::number(currentX, 'f').toUtf8()); + 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) @@ -837,27 +845,26 @@ 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); 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; @@ -872,77 +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("X,"); - 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->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; @@ -959,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; @@ -978,7 +986,8 @@ void GraphingWindow::loadDefinitions() gp.associatedSignal = nullptr; //might not be saved in the graph definition so default it to nothing - if (tokens[0] == "X") //newest format based around signals + //should probably do better at merging all the code that is the same between all these formats instead of duplication... + if (tokens[0] == "Z") //very newest format, adds ability to set bus number { gp.ID = tokens[1].toUInt(nullptr, 16); gp.mask = tokens[2].toULongLong(nullptr, 16); @@ -994,6 +1003,55 @@ void GraphingWindow::loadDefinitions() gp.bias = tokens[6].toFloat(); gp.scale = tokens[7].toFloat(); gp.stride = tokens[8].toInt(); + gp.bus = tokens[9].toInt(); + + gp.lineColor.setRed( tokens[10].toInt() ); + gp.lineColor.setGreen( tokens[11].toInt() ); + gp.lineColor.setBlue( tokens[12].toInt() ); + if (tokens.length() > 13) + gp.graphName = tokens[13]; + else + gp.graphName = QString(); + if (tokens.length() > 20) //even newer format with extra graph formatting options + { + gp.fillColor.setRed( tokens[14].toInt() ); + gp.fillColor.setGreen( tokens[15].toInt() ); + gp.fillColor.setBlue( tokens[16].toInt() ); + gp.fillColor.setAlpha( tokens[17].toInt() ); + if (tokens[18] == "Y") gp.drawOnlyPoints = true; + else gp.drawOnlyPoints = false; + gp.pointType = tokens[19].toInt(); + gp.lineWidth = tokens[20].toInt(); + } + if (tokens.length() > 22) + { + DBC_MESSAGE *msg = dbcHandler->findMessage(gp.ID); + if (msg) + { + gp.associatedSignal = msg->sigHandler->findSignalByName(tokens[22]); + } + else qDebug() << "Couldn't find the message by name! " << tokens[21] << " " << tokens[22]; + } + + createGraph(gp, true); + } + else if (tokens[0] == "X") //second newest format based around signals + { + gp.ID = tokens[1].toUInt(nullptr, 16); + gp.mask = tokens[2].toULongLong(nullptr, 16); + gp.startBit = tokens[3].toInt(); + if (gp.startBit < 0) { + gp.intelFormat = false; + gp.startBit *= -1; + } + else gp.intelFormat = true; + gp.numBits = tokens[4].toInt(); + if (tokens[5] == "Y") gp.isSigned = true; + else gp.isSigned = false; + gp.bias = tokens[6].toFloat(); + gp.scale = tokens[7].toFloat(); + gp.stride = tokens[8].toInt(); + gp.bus = -1; gp.lineColor.setRed( tokens[9].toInt() ); gp.lineColor.setGreen( tokens[10].toInt() ); @@ -1015,7 +1073,7 @@ void GraphingWindow::loadDefinitions() } if (tokens.length() > 21) { - DBC_MESSAGE *msg = dbcHandler->findMessage(QString(tokens[20])); + DBC_MESSAGE *msg = dbcHandler->findMessage(gp.ID); if (msg) { gp.associatedSignal = msg->sigHandler->findSignalByName(tokens[21]); @@ -1028,6 +1086,7 @@ void GraphingWindow::loadDefinitions() else //one of the two older formats then { gp.ID = tokens[0].toUInt(nullptr, 16); + gp.bus = -1; if (tokens[1] == "S") //old signal based graph definition { //tokens[2] is the signal name. Need to use the message ID and this name to look it up @@ -1140,7 +1199,7 @@ void GraphingWindow::loadDefinitions() } } } - inFile->close(); + inFile.close(); } } @@ -1185,10 +1244,15 @@ void GraphingWindow::appendToGraph(GraphParams ¶ms, CANFrame &frame, QVector int64_t tempVal; //64 bit temp value. tempVal = Utility::processIntegerSignal(frame.payload(), params.startBit, params.numBits, params.intelFormat, params.isSigned); //& params.mask; double xVal, yVal; - if (secondsMode) + if (Utility::timeStyle == TS_SECONDS) { xVal = ((double)(frame.timeStamp().microSeconds()) / 1000000.0 - params.xbias); } + else if (Utility::timeStyle == TS_CLOCK) + { + QDateTime dt = QDateTime::fromMSecsSinceEpoch((frame.timeStamp().microSeconds() / 1000) - params.xbias); + xVal = (dt.time().second() + dt.time().minute() * 60 + dt.time().hour() * 3600); + } else { xVal = (frame.timeStamp().microSeconds() - params.xbias); @@ -1255,10 +1319,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; @@ -1271,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) 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 @@ -1290,15 +1353,17 @@ 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); //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++) { @@ -1318,10 +1383,15 @@ void GraphingWindow::createGraph(GraphParams ¶ms, bool createGraphParam) y = (tempVal * params.scale) + params.bias; params.y.append( y ); - if (secondsMode) + if (Utility::timeStyle == TS_SECONDS) { x = (frameCache[k].timeStamp().microSeconds()) / 1000000.0; } + else if (Utility::timeStyle == TS_CLOCK) + { + QDateTime dt = QDateTime::fromMSecsSinceEpoch((frameCache[k].timeStamp().microSeconds() / 1000) - params.xbias); + x = (dt.time().msecsSinceStartOfDay() / 1000.0); + } else { x = frameCache[k].timeStamp().microSeconds(); @@ -1380,7 +1450,6 @@ void GraphingWindow::createGraph(GraphParams ¶ms, bool createGraphParam) if (params.prevValLocation != QPointF(0,0)) { - int j = numEntries - 1; QCPItemBracket *bracket = new QCPItemBracket(ui->graphingView); bracket->left->setCoords(params.prevValLocation); bracket->right->setCoords(x, params.prevValLocation.y()); @@ -1484,16 +1553,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() @@ -1508,6 +1577,7 @@ GraphParams::GraphParams() scale = 1; stride = 1; strideSoFar = 1; + bus = -1; lineColor = QColor(0,0,0); fillColor = QColor(255,255,255,0); lineWidth = 1; diff --git a/re/graphingwindow.h b/re/graphingwindow.h index 830384e..0735153 100644 --- a/re/graphingwindow.h +++ b/re/graphingwindow.h @@ -25,6 +25,7 @@ public: double scale; int stride; int strideSoFar; + int bus; QColor lineColor; QColor fillColor; int lineWidth; @@ -33,6 +34,7 @@ public: QCPGraph *ref; QString graphName; DBC_SIGNAL *associatedSignal; + //the below stuff is used for internal purposes only - code should be refactored so these can be private QVector x, y; double xbias; @@ -81,7 +83,7 @@ private slots: void appendToGraph(GraphParams ¶ms, CANFrame &frame, QVector &x, QVector &y); void editSelectedGraph(); void updatedFrames(int); - void gotCenterTimeID(int32_t ID, double timestamp); + void gotCenterTimeID(uint32_t ID, double timestamp); void resetView(); void zoomIn(); void zoomOut(); @@ -100,7 +102,6 @@ private: QCPItemText *locationText; QCPItemTracer *itemTracer; bool needScaleSetup; //do we need to set x,y graphing extents? - bool secondsMode; bool useOpenGL; bool followGraphEnd; diff --git a/re/newgraphdialog.cpp b/re/newgraphdialog.cpp index 6c14a80..62f73c6 100644 --- a/re/newgraphdialog.cpp +++ b/re/newgraphdialog.cpp @@ -43,8 +43,9 @@ NewGraphDialog::NewGraphDialog(DBCHandler *handler, QWidget *parent) : ui->coPointStyle->addItem("Plus Inside Circle"); ui->coPointStyle->addItem("Peace Sign"); + connect(ui->cbNodes, SIGNAL(currentIndexChanged(int)), this, SLOT(loadMessages(int))); connect(ui->cbMessages, SIGNAL(currentIndexChanged(int)), this, SLOT(loadSignals(int))); - connect(ui->gridData, SIGNAL(gridClicked(int,int)), this, SLOT(bitfieldClicked(int,int))); + connect(ui->gridData, SIGNAL(gridClicked(int)), this, SLOT(bitfieldClicked(int))); connect(ui->txtDataLen, SIGNAL(textChanged(QString)), this, SLOT(handleDataLenUpdate())); connect(ui->cbIntel, SIGNAL(toggled(bool)), this, SLOT(drawBitfield())); connect(ui->btnCopySignal, SIGNAL(clicked(bool)), this, SLOT(copySignalToParamsUI())); @@ -54,7 +55,7 @@ NewGraphDialog::NewGraphDialog(DBCHandler *handler, QWidget *parent) : dataLen = 1; assocSignal = nullptr; - loadMessages(); + loadNodes(); installEventFilter(this); } @@ -68,7 +69,8 @@ NewGraphDialog::~NewGraphDialog() void NewGraphDialog::showEvent(QShowEvent* event) { QDialog::showEvent(event); - loadMessages(); + if(shownFromPlotEdit == false) + loadNodes(); drawBitfield(); qDebug() << "S" << ui->gridData->geometry(); } @@ -124,9 +126,11 @@ void NewGraphDialog::checkSignalAgreement() DBC_MESSAGE *msg = nullptr; if (dbcHandler == nullptr) return; - if (dbcHandler->getFileCount() == 0) return; + if (dbcHandler->getFileCount() == 0) return; - msg = dbcHandler->findMessage(ui->cbMessages->currentText()); + QString fullyQualifiedNodeName = ui->cbNodes->itemText(ui->cbNodes->currentIndex()); + QString msgName = ui->cbMessages->currentText(); + msg = dbcHandler->findMessage(msgName, fullyQualifiedNodeName); if (msg) { sig = msg->sigHandler->findSignalByName(ui->cbSignals->currentText()); @@ -149,13 +153,20 @@ void NewGraphDialog::checkSignalAgreement() testingParams.startBit = startBit; testingParams.numBits = dataLen; - if (testingParams.ID != msg->ID) bAgree = false; - if (fabs(testingParams.bias - sig->bias) > 0.01) bAgree = false; - if (testingParams.isSigned != sigSigned) bAgree = false; - if (testingParams.intelFormat != sig->intelByteOrder) bAgree = false; - if (fabs(testingParams.scale - sig->factor) > 0.01) bAgree = false; - if (testingParams.startBit != sig->startBit) bAgree = false; - if (testingParams.numBits != sig->signalSize) bAgree = false; + if (testingParams.ID != msg->ID) + bAgree = false; + if (fabs(testingParams.bias - sig->bias) > 0.01) + bAgree = false; + if (testingParams.isSigned != sigSigned) + bAgree = false; + if (testingParams.intelFormat != sig->intelByteOrder) + bAgree = false; + if (fabs(testingParams.scale - sig->factor) > 0.01) + bAgree = false; + if (testingParams.startBit != sig->startBit) + bAgree = false; + if (testingParams.numBits != sig->signalSize) + bAgree = false; } else { @@ -187,6 +198,8 @@ void NewGraphDialog::clearParams() void NewGraphDialog::setParams(GraphParams ¶ms) { + shownFromPlotEdit = true; + ui->txtBias->setText(QString::number(params.bias)); ui->txtMask->setText(Utility::formatNumber(params.mask)); ui->txtScale->setText(QString::number(params.scale)); @@ -199,6 +212,7 @@ void NewGraphDialog::setParams(GraphParams ¶ms) ui->txtDataLen->setText(QString::number(dataLen)); ui->txtID->setText(Utility::formatCANID(params.ID)); ui->txtName->setText(params.graphName); + ui->editBus->setText(QString::number(params.bus)); QPalette p = ui->colorSwatch->palette(); p.setColor(QPalette::Button, params.lineColor); ui->colorSwatch->setPalette(p); @@ -211,8 +225,62 @@ void NewGraphDialog::setParams(GraphParams ¶ms) assocSignal = params.associatedSignal; - loadMessages(); - loadSignals(0); + loadNodes(); + + if (assocSignal) + { + auto msg = assocSignal->parentMessage; + auto node = msg->sender; + + bool nodeFound = false; + for(int i=0; icbNodes->count(); i++) + { + if(ui->cbNodes->itemText(i) == node->sourceFileName + Utility::fullyQualifiedNameSeperator + node->name) + { + ui->cbNodes->setCurrentIndex(i); + nodeFound = true; + break; + } + } + + qDebug() << "Matching plot params to Node: " << nodeFound; + + if(nodeFound) + { + bool msgFound = false; + for(int i=0; icbMessages->count(); i++) + { + if(ui->cbMessages->itemText(i) == msg->name) + { + ui->cbMessages->setCurrentIndex(i); + msgFound = true; + break; + } + } + + qDebug() << "Matching plot params to Msg: " << msgFound; + + if(msgFound) + { + bool sigFound = false; + for(int i=0; icbSignals->count(); i++) + { + if(ui->cbSignals->itemText(i) == assocSignal->name) + { + ui->cbSignals->setCurrentIndex(i); + sigFound = true; + break; + } + } + + qDebug() << "Matching plot params to Signal: " << sigFound; + } + } + } + //ui->cbNodes->model()-> + + + //loadSignals(0); drawBitfield(); checkSignalAgreement(); } @@ -234,6 +302,7 @@ void NewGraphDialog::getParams(GraphParams ¶ms) params.mask = Utility::ParseStringToNum(ui->txtMask->text()); params.scale = ui->txtScale->text().toFloat(); params.stride = Utility::ParseStringToNum(ui->txtStride->text()); + params.bus = Utility::ParseStringToNum(ui->editBus->text()); params.startBit = startBit; params.numBits = dataLen; @@ -246,41 +315,84 @@ void NewGraphDialog::getParams(GraphParams ¶ms) if (params.stride < 1) params.stride = 1; } -void NewGraphDialog::loadMessages() +void NewGraphDialog::loadNodes() { - DBC_MESSAGE *msg; - ui->cbMessages->clear(); + int numFiles; + ui->cbNodes->clear(); if (dbcHandler == nullptr) return; - if (dbcHandler->getFileCount() == 0) return; - for (int y = 0; y < dbcHandler->getFileCount(); y++) + if ((numFiles = dbcHandler->getFileCount()) == 0) return; + qDebug() << numFiles; + for (int f = 0; f < numFiles; f++) { - for (int x = 0; x < dbcHandler->getFileByIdx(y)->messageHandler->getCount(); x++) + DBCFile* thisFile = dbcHandler->getFileByIdx(f); + qDebug() << thisFile->messageHandler->getCount(); + + QList names; + + for (int x = 0; x < thisFile->dbc_nodes.count(); x++) { - msg = dbcHandler->getFileByIdx(y)->messageHandler->findMsgByIdx(x); - if (msg) + bool messagesInNode = false; + for (int m = 0; m < thisFile->messageHandler->getCount(); m++) { - ui->cbMessages->addItem(msg->name); - if (assocSignal && msg->name == assocSignal->parentMessage->name) + if(thisFile->messageHandler->findMsgByIdx(m)->sender->name == thisFile->dbc_nodes[x].name) { - ui->cbMessages->setCurrentIndex(ui->cbMessages->count() -1); - //qDebug() << "Found my parent"; + messagesInNode = true; + break; } } + if(messagesInNode) + { + QString fullyQualifiedNodeName = thisFile->getFilenameNoExt() + Utility::fullyQualifiedNameSeperator + thisFile->dbc_nodes[x].name; + names.append(fullyQualifiedNodeName); + } + } + + if(names.count() > 0) + { + names.sort(); + ui->cbNodes->addItem("----" + thisFile->getFilename()); + Utility::SetComboBoxItemEnabled(ui->cbNodes, ui->cbNodes->count() -1, false); + for(int i=0; icbNodes->addItem(names[i]); + } + } +} + +void NewGraphDialog::loadMessages(int idx) +{ + int numFiles = 0; + ui->cbMessages->clear(); + if (dbcHandler == nullptr) return; + if ((numFiles = dbcHandler->getFileCount()) == 0) return; + qDebug() << numFiles; + + QString displayedNodeName = ui->cbNodes->itemText(idx); + + for (int f = 0; f < numFiles; f++) + { + qDebug() << dbcHandler->getFileByIdx(f)->messageHandler->getCount(); + + for (int x = 0; x < dbcHandler->getFileByIdx(f)->messageHandler->getCount(); x++) + { + QString fullyQualifiedNodeName = dbcHandler->getFileByIdx(f)->getFilenameNoExt() + Utility::fullyQualifiedNameSeperator + dbcHandler->getFileByIdx(f)->messageHandler->findMsgByIdx(x)->sender->name; + if(fullyQualifiedNodeName == displayedNodeName) + ui->cbMessages->addItem(dbcHandler->getFileByIdx(f)->messageHandler->findMsgByIdx(x)->name); } } - ui->cbMessages->model()->sort(0); } void NewGraphDialog::loadSignals(int idx) { Q_UNUSED(idx); - //search through all DBC files in order to try to find a message with the given name - DBC_MESSAGE *msg = dbcHandler->findMessage(ui->cbMessages->currentText()); - DBC_SIGNAL *sig; - - if (msg == nullptr) return; ui->cbSignals->clear(); + + //search through all DBC files in order to try to find a message with the given name + QString fullyQualifiedNodeName = ui->cbNodes->itemText(ui->cbNodes->currentIndex()); + DBC_MESSAGE *msg = dbcHandler->findMessage(ui->cbMessages->currentText(), fullyQualifiedNodeName); + if (msg == nullptr) return; + + DBC_SIGNAL *sig; for (int x = 0; x < msg->sigHandler->getCount(); x++) { sig = msg->sigHandler->findSignalByIdx(x); @@ -298,10 +410,8 @@ void NewGraphDialog::loadSignals(int idx) checkSignalAgreement(); } -void NewGraphDialog::bitfieldClicked(int x,int y) +void NewGraphDialog::bitfieldClicked(int bit) { - int bit = (y * 8 + (7-x)); - qDebug() << "Clicked bit: " << bit; startBit = bit; drawBitfield(); @@ -310,20 +420,25 @@ void NewGraphDialog::bitfieldClicked(int x,int y) void NewGraphDialog::drawBitfield() { qDebug() << "Draw Bitfield"; - int64_t bitField = 0; + uint8_t bitField[64]; int endBit, sBit; - bitField |= 1ull << (startBit); //make the start bit a different color to set it apart + memset(bitField, 0, 64); + + //make the start bit a different color to set it apart + bitField[Utility::getByteFromBitPosition(startBit)] |= 1 << Utility::getBitFromBitPosition(startBit); + ui->gridData->setReference((unsigned char *)&bitField, false); if (ui->cbIntel->isChecked()) { endBit = startBit + dataLen - 1; if (startBit < 0) startBit = 0; - if (endBit > 63) endBit = 63; + if (endBit > 511) endBit = 511; for (int y = startBit; y <= endBit; y++) { - bitField |= 1ull << y; + //bitField |= 1ull << y; + bitField[Utility::getByteFromBitPosition(y)] |= 1 << Utility::getBitFromBitPosition(y); } } else //big endian / motorola format @@ -333,11 +448,12 @@ void NewGraphDialog::drawBitfield() sBit = startBit; while (size > 0) { - bitField |= 1ull << sBit; + //bitField |= 1ull << sBit; + bitField[Utility::getByteFromBitPosition(sBit)] |= 1 << Utility::getBitFromBitPosition(sBit); size--; if ((sBit % 8) == 0) sBit += 15; else sBit--; - if (sBit > 63) sBit = 63; + if (sBit > 511) sBit = 511; } } @@ -350,7 +466,7 @@ void NewGraphDialog::handleDataLenUpdate() { dataLen = ui->txtDataLen->text().toInt(); if (dataLen < 1) dataLen = 1; - if (dataLen > 63) dataLen = 63; + if (dataLen > 64) dataLen = 64; drawBitfield(); checkSignalAgreement(); } @@ -360,11 +476,10 @@ void NewGraphDialog::copySignalToParamsUI() assocSignal = nullptr; DBC_MESSAGE *msg = nullptr; - for(int i = 0; i < dbcHandler->getFileCount(); i++) - { - msg = dbcHandler->getFileByIdx(i)->messageHandler->findMsgByName(ui->cbMessages->currentText()); - if (msg) break; - } + QString fullyQualifiedNodeName = ui->cbNodes->itemText(ui->cbNodes->currentIndex()); + QString msgName = ui->cbMessages->itemText(ui->cbMessages->currentIndex()); + + msg = dbcHandler->findMessage(msgName, fullyQualifiedNodeName); if (!msg) return; DBC_SIGNAL *sig = msg->sigHandler->findSignalByName(ui->cbSignals->currentText()); diff --git a/re/newgraphdialog.h b/re/newgraphdialog.h index 26283c3..1cfab38 100644 --- a/re/newgraphdialog.h +++ b/re/newgraphdialog.h @@ -25,9 +25,10 @@ private slots: void addButtonClicked(); void colorSwatchClick(); void fillSwatchClick(); - void loadMessages(); + void loadNodes(); + void loadMessages(int idx); void loadSignals(int idx); - void bitfieldClicked(int,int); + void bitfieldClicked(int); void handleDataLenUpdate(); void drawBitfield(); void copySignalToParamsUI(); @@ -40,6 +41,7 @@ private: DBCHandler *dbcHandler; DBC_SIGNAL *assocSignal; int startBit, dataLen; + bool shownFromPlotEdit; }; #endif // NEWGRAPHDIALOG_H diff --git a/re/sniffer/SnifferDelegate.cpp b/re/sniffer/SnifferDelegate.cpp index 34cdd5e..9f11431 100644 --- a/re/sniffer/SnifferDelegate.cpp +++ b/re/sniffer/SnifferDelegate.cpp @@ -43,17 +43,17 @@ void SnifferDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio { //qDebug() << "SnifferDelegate Paint Event"; - if (index.column() < 2) //allow default handling of the first two columns + if (index.column() < 3) //allow default handling of the first two columns { QItemDelegate::paint(painter, option, index); return; } - if (index.column() > 9) return; + if (index.column() > 10) return; int x; SnifferItem *item = static_cast(index.internalPointer()); - int idx = index.column() - 2; + int idx = index.column() - 3; int val = item->getData(idx); int prevVal = item->getLastData(idx); int notchPattern = item->getNotchPattern(idx); @@ -70,7 +70,7 @@ void SnifferDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio //qDebug() << "XSpan" << xSpan << " YSpan " << ySpan; int xSector = xSpan / 8; - int v = item->getSeqInterval(index.column() - 2) * 10; + int v = item->getSeqInterval(index.column() - 3) * 10; if (v > 225) v = 225; if (v < 0) v = 0; diff --git a/re/sniffer/snifferitem.h b/re/sniffer/snifferitem.h index 0995a10..ea0a086 100644 --- a/re/sniffer/snifferitem.h +++ b/re/sniffer/snifferitem.h @@ -2,7 +2,7 @@ #define SNIFFERITEM_H #include -#include +#include #include "can_structs.h" struct fstCan @@ -50,7 +50,7 @@ private: quint64 mCurrentTime; quint64 mCurrSeqVal; - QTime mTime; + QElapsedTimer mTime; }; #endif // SNIFFERITEM_H diff --git a/re/sniffer/sniffermodel.cpp b/re/sniffer/sniffermodel.cpp index 01d1353..25240ef 100644 --- a/re/sniffer/sniffermodel.cpp +++ b/re/sniffer/sniffermodel.cpp @@ -66,9 +66,10 @@ QVariant SnifferModel::data(const QModelIndex &index, int role) const case tc::DELTA: return QString::number(item->getDelta(), 'f'); case tc::FREQUENCY: + if (item->getDelta() == 0) return QString("0 hz"); return QString("%1 hz").arg(qRound(1.00 / item->getDelta())); case tc::ID: - return "0x" + QString("%1").arg(item->getId(), 5, 16, QLatin1Char(' ')).toUpper(); + return "0x" + QString("%1").arg(item->getId(), 5, 16, QLatin1Char('0')).toUpper(); default: break; } @@ -136,7 +137,7 @@ QVariant SnifferModel::data(const QModelIndex &index, int role) const Qt::ItemFlags SnifferModel::flags(const QModelIndex &index) const { if (!index.isValid()) - return 0; + return Qt::NoItemFlags; return QAbstractItemModel::flags(index); } @@ -252,7 +253,7 @@ void SnifferModel::refresh() for (i = mMap.begin(); i != mMap.end(); ++i) { //i.value()->updateMarker(); - if(i.value()->elapsed() > mExpireInterval && !mNeverExpire) + if(i.value()->elapsed() > (int)mExpireInterval && !mNeverExpire) toRemove.append(i.key()); } @@ -272,7 +273,7 @@ void SnifferModel::refresh() } else /* refresh data */ - dataChanged(createIndex(0, 0), + emit dataChanged(createIndex(0, 0), createIndex(rowCount()-1, columnCount()-1), QVector(Qt::DisplayRole)); } diff --git a/re/sniffer/snifferwindow.cpp b/re/sniffer/snifferwindow.cpp index e0ecfb8..f8145b1 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"; } diff --git a/re/udsscanwindow.cpp b/re/udsscanwindow.cpp index 630ccab..80dc87e 100644 --- a/re/udsscanwindow.cpp +++ b/re/udsscanwindow.cpp @@ -6,6 +6,23 @@ #include "utility.h" #include "helpwindow.h" + +static QVector SCANTYPE_NAMES = { + QString("Tester Present"), + QString("Session Control"), + QString("Communication Control"), + QString("ECU Reset"), + QString("Clear DTCs"), + QString("Read DTCs"), + QString("Security Access"), + QString("Read By ID"), + QString("Read By Address"), + QString("Read Scaling Data By ID"), + QString("IO Control"), + QString("Routine Control"), + QString("Custom UDS"), +}; + UDSScanWindow::UDSScanWindow(const QVector *frames, QWidget *parent) : QDialog(parent), ui(new Ui::UDSScanWindow) @@ -21,15 +38,23 @@ UDSScanWindow::UDSScanWindow(const QVector *frames, QWidget *parent) : waitTimer->setInterval(100); udsHandler = new UDS_HANDLER; + inhibitUpdates = false; + + for (int i = 0; i < 13; i++) ui->cbScanType->addItem(SCANTYPE_NAMES[i]); + + ui->cbSessType->addItem("No Change"); + ui->cbSessType->addItem("Default"); + ui->cbSessType->addItem("Programming"); + ui->cbSessType->addItem("Extended Diag"); + ui->cbSessType->addItem("Safety Sys Diag"); connect(MainWindow::getReference(), SIGNAL(framesUpdated(int)), this, SLOT(updatedFrames(int))); connect(udsHandler, &UDS_HANDLER::newUDSMessage, this, &UDSScanWindow::gotUDSReply); - connect(ui->btnScan, &QPushButton::clicked, this, &UDSScanWindow::scanUDS); + connect(ui->btnScanAll, &QPushButton::clicked, this, &UDSScanWindow::scanAll); + connect(ui->btnScanSelected, &QPushButton::clicked, this, &UDSScanWindow::scanSelected); connect(waitTimer, &QTimer::timeout, this, &UDSScanWindow::timeOut); connect(ui->btnSaveResults, &QPushButton::clicked, this, &UDSScanWindow::saveResults); - connect(ui->ckWildcard, &QCheckBox::toggled, this, &UDSScanWindow::wildcardToggled); - connect(ui->ckReadByAddr, &QCheckBox::toggled, this, &UDSScanWindow::readByToggled); - connect(ui->ckReadByID, &QCheckBox::toggled, this, &UDSScanWindow::readByToggled); + connect(ui->cbScanType, &QComboBox::currentTextChanged, this, &UDSScanWindow::changedScanType); connect(ui->cbAllowAdaptiveOffset, &QCheckBox::toggled, this, &UDSScanWindow::adaptiveToggled); connect(ui->spinNumBytes, SIGNAL(valueChanged(int)), this, SLOT(numBytesChanged())); connect(ui->spinLowerService, SIGNAL(valueChanged(int)), this, SLOT(checkServiceRange())); @@ -38,10 +63,30 @@ UDSScanWindow::UDSScanWindow(const QVector *frames, QWidget *parent) : connect(ui->spinUpperSubfunc, SIGNAL(valueChanged(int)), this, SLOT(checkSubFuncRange())); connect(ui->spinStartID, SIGNAL(valueChanged(int)), this, SLOT(checkIDRange())); connect(ui->spinEndID, SIGNAL(valueChanged(int)), this, SLOT(checkIDRange())); + connect(ui->btnAdd, &QPushButton::clicked, this, &UDSScanWindow::addNewScan); + connect(ui->btnDelete, &QPushButton::clicked, this, &UDSScanWindow::deleteSelectedScan); + connect(ui->btnSave, &QPushButton::clicked, this, &UDSScanWindow::saveScans); + connect(ui->btnLoad, &QPushButton::clicked, this, &UDSScanWindow::loadScans); + connect(ui->listScansToRun, &QListWidget::currentRowChanged, this, &UDSScanWindow::displayScanEntry); + connect(ui->ckShowNoReply, &QCheckBox::toggled, this, &UDSScanWindow::setNoReplyVal); + connect(ui->spinDelay, SIGNAL(valueChanged(int)), this, SLOT(setMaxDelayVal())); + connect(ui->spinIncrement, SIGNAL(valueChanged(int)), this, SLOT(setIncrementVal())); + connect(ui->spinReplyOffset, SIGNAL(valueChanged(int)), this, SLOT(setReplyOffset())); + connect(ui->cbSessType, &QComboBox::currentTextChanged, this, &UDSScanWindow::setSessType); +//not handling show no reply, max reply delay, reply offset, increment int numBuses = CANConManager::getInstance()->getNumBuses(); for (int n = 0; n < numBuses; n++) ui->cbBuses->addItem(QString::number(n)); + + connect(ui->cbBuses, &QComboBox::currentTextChanged, this, &UDSScanWindow::setBusToScan); + installEventFilter(this); + + addNewScan(); + + checkIDRange(); + checkServiceRange(); + checkSubFuncRange(); } UDSScanWindow::~UDSScanWindow() @@ -71,74 +116,401 @@ bool UDSScanWindow::eventFilter(QObject *obj, QEvent *event) return false; } +void UDSScanWindow::setControlState(QWidget & widget, bool valid) +{ + QPalette pal = widget.palette(); + pal.setColor(QPalette::ColorRole::Base , valid ? Qt::white : Qt::red); + widget.setPalette(pal); +} + +void UDSScanWindow::displayScanEntry(int idx) +{ + if (inhibitUpdates) return; + if (idx == -1) return; + inhibitUpdates = true; //need to do this so it doesn't mess things up as we fill in the fields + currEditEntry = &scanEntries.data()[idx]; + ui->spinStartID->setValue(currEditEntry->startID); + ui->spinEndID->setValue(currEditEntry->endID); + ui->spinReplyOffset->setValue(currEditEntry->idOffset); + ui->cbAllowAdaptiveOffset->setChecked(currEditEntry->bAdaptiveOffset); + ui->ckShowNoReply->setChecked(currEditEntry->bShowNoReplies); + ui->cbBuses->setCurrentIndex(currEditEntry->busToScan); + ui->spinDelay->setValue(currEditEntry->maxWaitTime); + ui->cbScanType->setCurrentIndex((int)currEditEntry->scanType); + ui->cbSessType->setCurrentIndex(currEditEntry->sessType); + ui->spinNumBytes->setValue(currEditEntry->subfunctLen); + ui->spinLowerService->setValue(currEditEntry->serviceLower); + ui->spinUpperService->setValue(currEditEntry->serviceUpper); + ui->spinLowerSubfunc->setValue(currEditEntry->subfunctLower); + ui->spinUpperSubfunc->setValue(currEditEntry->subfunctUpper); + ui->spinIncrement->setValue(currEditEntry->subfunctIncrement); + inhibitUpdates = false; +} + +void UDSScanWindow::deleteSelectedScan() +{ + int idx = ui->listScansToRun->currentRow(); + if (idx == -1) return; + scanEntries.removeAt(idx); + + QListWidgetItem *item = ui->listScansToRun->takeItem(idx); + delete item; + int rows = ui->listScansToRun->count(); + ui->listScansToRun->setCurrentRow(rows - 1); + + for (int i = 0; i < rows; i++) + { + QListWidgetItem *item = ui->listScansToRun->item(i); + item->setText(generateListDesc(i)); + } +} + +void UDSScanWindow::addNewScan() +{ + ScanEntry newEntry; + newEntry.busToScan = 0; + newEntry.scanType = SCAN_TYPE::ST_TESTER_PRESENT; + newEntry.sessType = 0; + newEntry.bAdaptiveOffset = false; + newEntry.bShowNoReplies = false; + newEntry.idOffset = 8; + newEntry.startID = 0x7E0; + newEntry.endID = 0x7E7; + newEntry.maxWaitTime = 100; + newEntry.subfunctLen = 1; + newEntry.subfunctIncrement = 1; + newEntry.subfunctLower = 0; + newEntry.subfunctUpper = 0; + newEntry.serviceLower = 1; + newEntry.serviceUpper = 1; + scanEntries.append(newEntry); + displayScanEntry(scanEntries.length() - 1); + ui->listScansToRun->addItem(generateListDesc(scanEntries.length() - 1)); + ui->listScansToRun->setCurrentRow(ui->listScansToRun->count() - 1); +} + +void UDSScanWindow::loadScans() +{ + QString filename; + QFileDialog dialog(qApp->activeWindow()); + QSettings settings; + + QStringList filters; + filters.append(QString(tr("UDS Test Specification (*.uds *.UDS)"))); + + dialog.setDirectory(settings.value("FileIO/LoadSaveDirectory", dialog.directory().path()).toString()); + dialog.setFileMode(QFileDialog::AnyFile); + dialog.setNameFilters(filters); + dialog.setViewMode(QFileDialog::Detail); + dialog.setAcceptMode(QFileDialog::AcceptOpen); + + if (dialog.exec() == QDialog::Accepted) + { + filename = dialog.selectedFiles()[0]; + QFile inFile(filename); + inFile.open(QIODevice::ReadOnly); + QDataStream load(&inFile); + int fileVersion; + load >> fileVersion; + if (fileVersion != 1) + { + QMessageBox::warning(this, "Cannot Load File", "File is not a supported version.\nCannot load it!"); + return; + } + inhibitUpdates = true; + scanEntries.clear(); + ui->listScansToRun->clear(); + int numEntries; + load >> numEntries; + for (int i = 0; i < numEntries; i++) + { + ScanEntry entry; + load >> entry.startID; + load >> entry.endID; + load >> entry.idOffset; + load >> entry.bAdaptiveOffset; + load >> entry.bShowNoReplies; + load >> entry.busToScan; + load >> entry.maxWaitTime; + load >> entry.scanType; + load >> entry.sessType; + load >> entry.subfunctLen; + load >> entry.subfunctLower; + load >> entry.subfunctUpper; + load >> entry.subfunctIncrement; + load >> entry.serviceLower; + load >> entry.serviceUpper; + + scanEntries.append(entry); + ui->listScansToRun->addItem(generateListDesc(scanEntries.length() - 1)); + } + inFile.close(); + inhibitUpdates = false; + ui->listScansToRun->setCurrentRow(ui->listScansToRun->count() - 1); + displayScanEntry(scanEntries.length() - 1); + } +} + +void UDSScanWindow::saveScans() +{ + QString filename; + QFileDialog dialog(qApp->activeWindow()); + QSettings settings; + + QStringList filters; + filters.append(QString(tr("UDS Test Specification (*.uds *.UDS)"))); + + dialog.setDirectory(settings.value("FileIO/LoadSaveDirectory", dialog.directory().path()).toString()); + dialog.setFileMode(QFileDialog::AnyFile); + dialog.setNameFilters(filters); + dialog.setViewMode(QFileDialog::Detail); + dialog.setAcceptMode(QFileDialog::AcceptSave); + dialog.setDefaultSuffix(".uds"); + + if (dialog.exec() == QDialog::Accepted) + { + filename = dialog.selectedFiles()[0]; + QFile outFile(filename); + outFile.open(QIODevice::WriteOnly); + QDataStream save(&outFile); + save << 1; //file version + save << scanEntries.count(); + for (int i = 0; i < scanEntries.count(); i++) + { + save << scanEntries[i].startID; + save << scanEntries[i].endID; + save << scanEntries[i].idOffset; + save << scanEntries[i].bAdaptiveOffset; + save << scanEntries[i].bShowNoReplies; + save << scanEntries[i].busToScan; + save << scanEntries[i].maxWaitTime; + save << scanEntries[i].scanType; + save << scanEntries[i].sessType; + save << scanEntries[i].subfunctLen; + save << scanEntries[i].subfunctLower; + save << scanEntries[i].subfunctUpper; + save << scanEntries[i].subfunctIncrement; + save << scanEntries[i].serviceLower; + save << scanEntries[i].serviceUpper; + } + outFile.close(); + } +} + +QString UDSScanWindow::generateListDesc(int idx) +{ + QString builder; + ScanEntry entry = scanEntries[idx]; + builder = "(0x" + QString::number(entry.startID, 16) + " - 0x" + QString::number(entry.endID, 16) + ") "; + switch (entry.scanType) + { + case ST_TESTER_PRESENT: + builder += "TP"; + break; + case ST_SESS_CTRL: + builder += "SESS"; + break; + case ST_COMM_CTRL: + builder += "COMM"; + break; + case ST_ECU_RESET: + builder += "ECU"; + break; + case ST_CLEAR_DTC: + builder += "CDTC"; + break; + case ST_READ_DTC: + builder += "RDTC"; + break; + case ST_SEC_ACCESS: + builder += "SECU"; + break; + case ST_READ_ID: + builder += "RID"; + break; + case ST_READ_ADDR: + builder += "RADD"; + break; + case ST_READ_SCALING: + builder += "RSCAL"; + break; + case ST_IO_CTRL: + builder += "IO"; + break; + case ST_ROUTINE_CTRL: + builder += "ROUT"; + break; + case ST_CUSTOM: + builder += "CUST"; + break; + } + return builder; +} + void UDSScanWindow::adaptiveToggled() { + if (inhibitUpdates) return; if (ui->cbAllowAdaptiveOffset->isChecked()) ui->spinReplyOffset->setEnabled(false); else ui->spinReplyOffset->setEnabled(true); + if (currEditEntry) currEditEntry->bAdaptiveOffset = ui->cbAllowAdaptiveOffset->isChecked(); } -void UDSScanWindow::wildcardToggled() +void UDSScanWindow::setSessType() { - bool state = false; - if (ui->ckWildcard->isChecked()) state = false; - else state = true; - - ui->ckReset->setEnabled(state); - ui->ckSecurity->setEnabled(state); - ui->ckSession->setEnabled(state); - ui->ckTester->setEnabled(state); - ui->ckReadByAddr->setEnabled(state); - ui->ckReadByID->setEnabled(state); - ui->ckReset->setChecked(false); - ui->ckSecurity->setChecked(false); - ui->ckSession->setChecked(false); - ui->ckTester->setChecked(false); - ui->ckReadByAddr->setChecked(false); - ui->ckReadByID->setChecked(false); - - ui->spinLowerService->setEnabled(!state); - ui->spinLowerSubfunc->setEnabled(!state); - ui->spinNumBytes->setEnabled(!state); - ui->spinUpperService->setEnabled(!state); - ui->spinUpperSubfunc->setEnabled(!state); + if (inhibitUpdates) return; + if (currEditEntry) currEditEntry->sessType = ui->cbSessType->currentIndex(); } -void UDSScanWindow::readByToggled() +void UDSScanWindow::setBusToScan() { - bool state = false; - if (ui->ckReadByAddr->isChecked() || ui->ckReadByID->isChecked()) state = true; - else state = false; + if (inhibitUpdates) return; + if (currEditEntry) currEditEntry->busToScan = ui->cbBuses->currentIndex(); +} - ui->spinLowerService->setEnabled(false); - ui->spinLowerSubfunc->setEnabled(state); - ui->spinNumBytes->setEnabled(state); - ui->spinUpperService->setEnabled(false); - ui->spinUpperSubfunc->setEnabled(state); +void UDSScanWindow::setNoReplyVal() +{ + if (inhibitUpdates) return; + if (currEditEntry) currEditEntry->bShowNoReplies = ui->ckShowNoReply->isChecked(); +} + +void UDSScanWindow::setMaxDelayVal() +{ + if (inhibitUpdates) return; + if (currEditEntry) currEditEntry->maxWaitTime = ui->spinDelay->value(); +} + +void UDSScanWindow::setIncrementVal() +{ + if (inhibitUpdates) return; + if (currEditEntry) currEditEntry->subfunctIncrement = ui->spinIncrement->value(); +} + +void UDSScanWindow::setReplyOffset() +{ + if (inhibitUpdates) return; + if (currEditEntry) currEditEntry->idOffset = ui->spinReplyOffset->value(); +} + +void UDSScanWindow::changedScanType() +{ + if (inhibitUpdates) return; + int idx = ui->cbScanType->currentIndex(); + if (idx == -1) + { + ui->cbScanType->setCurrentIndex(0); + idx = 0; + } + + if (currEditEntry) currEditEntry->scanType = (SCAN_TYPE)idx; + + QListWidgetItem* item = ui->listScansToRun->currentItem(); + item->setText(generateListDesc(ui->listScansToRun->currentRow())); + + switch (idx) + { + case ST_TESTER_PRESENT: + case ST_SESS_CTRL: + ui->cbSessType->setEnabled(false); + ui->spinLowerService->setEnabled(false); + ui->spinLowerSubfunc->setEnabled(false); + ui->spinUpperService->setEnabled(false); + ui->spinUpperSubfunc->setEnabled(false); + ui->spinNumBytes->setEnabled(false); + ui->spinIncrement->setEnabled(false); + ui->cbSessType->setCurrentIndex(0); + break; + case ST_COMM_CTRL: + case ST_ECU_RESET: + case ST_CLEAR_DTC: + case ST_SEC_ACCESS: + case ST_READ_DTC: + ui->cbSessType->setEnabled(true); + ui->spinLowerService->setEnabled(false); + ui->spinUpperService->setEnabled(false); + ui->spinLowerSubfunc->setEnabled(false); + ui->spinUpperSubfunc->setEnabled(false); + ui->spinNumBytes->setEnabled(false); + ui->spinIncrement->setEnabled(false); + break; + case ST_IO_CTRL: + case ST_ROUTINE_CTRL: + case ST_READ_SCALING: + ui->cbSessType->setEnabled(true); + ui->spinLowerService->setEnabled(false); + ui->spinUpperService->setEnabled(false); + ui->spinLowerSubfunc->setEnabled(true); + ui->spinUpperSubfunc->setEnabled(true); + ui->spinNumBytes->setEnabled(false); + ui->spinNumBytes->setValue(2); + ui->spinIncrement->setEnabled(true); + break; + case ST_READ_ID: + case ST_READ_ADDR: + ui->cbSessType->setEnabled(true); + ui->spinLowerService->setEnabled(false); + ui->spinUpperService->setEnabled(false); + ui->spinLowerSubfunc->setEnabled(true); + ui->spinUpperSubfunc->setEnabled(true); + ui->spinNumBytes->setEnabled(true); + ui->spinIncrement->setEnabled(true); + break; + case ST_CUSTOM: + ui->cbSessType->setEnabled(true); + ui->spinLowerService->setEnabled(true); + ui->spinUpperService->setEnabled(true); + ui->spinLowerSubfunc->setEnabled(true); + ui->spinUpperSubfunc->setEnabled(true); + ui->spinNumBytes->setEnabled(true); + ui->spinIncrement->setEnabled(true); + break; + } } void UDSScanWindow::numBytesChanged() { + if (inhibitUpdates) return; uint64_t upperBound = (1ull << (8ull * ui->spinNumBytes->value())) - 1; if (upperBound > 0x7FFFFFFF) upperBound = 0x7FFFFFFF; ui->spinUpperSubfunc->setMaximum(upperBound); + ui->spinLowerSubfunc->setMaximum(upperBound); + if (currEditEntry) currEditEntry->subfunctLen = ui->spinNumBytes->value(); } void UDSScanWindow::checkIDRange() { - ui->spinStartID->setMaximum(ui->spinEndID->value()); - ui->spinEndID->setMinimum(ui->spinStartID->value()); + if (inhibitUpdates) return; + + const bool isValid = ui->spinStartID->value() <= ui->spinEndID->value(); + setControlState(*ui->spinStartID, isValid); + setControlState(*ui->spinEndID, isValid); + + if (currEditEntry) currEditEntry->startID = ui->spinStartID->value(); + if (currEditEntry) currEditEntry->endID = ui->spinEndID->value(); + if (QListWidgetItem* item = ui->listScansToRun->currentItem()) + { + item->setText(generateListDesc(ui->listScansToRun->currentRow())); + } } void UDSScanWindow::checkServiceRange() { - ui->spinLowerService->setMaximum(ui->spinUpperService->value()); - ui->spinUpperService->setMinimum(ui->spinLowerService->value()); + if (inhibitUpdates) return; + + const bool isValid = ui->spinLowerService->value() <= ui->spinUpperService->value(); + setControlState(*ui->spinLowerService, isValid); + setControlState(*ui->spinUpperService, isValid); + if (currEditEntry) currEditEntry->serviceLower = ui->spinLowerService->value(); + if (currEditEntry) currEditEntry->serviceUpper = ui->spinUpperService->value(); } void UDSScanWindow::checkSubFuncRange() { - ui->spinLowerSubfunc->setMaximum(ui->spinUpperSubfunc->value()); - ui->spinUpperSubfunc->setMinimum(ui->spinLowerSubfunc->value()); + if (inhibitUpdates) return; + const bool isValid = ui->spinLowerSubfunc->value() <= ui->spinUpperSubfunc->value(); + setControlState(*ui->spinLowerSubfunc, isValid); + setControlState(*ui->spinUpperSubfunc, isValid); + if (currEditEntry) currEditEntry->subfunctLower = ui->spinLowerSubfunc->value(); + if (currEditEntry) currEditEntry->subfunctUpper = ui->spinUpperSubfunc->value(); } void UDSScanWindow::saveResults() @@ -196,19 +568,28 @@ void UDSScanWindow::sendOnBuses(UDS_MESSAGE test, int buses) sendingFrames.append(test); } -void UDSScanWindow::scanUDS() +void UDSScanWindow::scanAll() { - if (currentlyRunning) + sendingFrames.clear(); + for (int i = 0; i < scanEntries.count(); i++) { - waitTimer->stop(); - sendingFrames.clear(); - udsHandler->setReception(false); - udsHandler->setProcessAllIDs(false); - udsHandler->setFlowCtrl(false); - currentlyRunning = false; - ui->btnScan->setText("Start Scan"); - return; + setupScan(i); } + startScan(); +} + +void UDSScanWindow::scanSelected() +{ + sendingFrames.clear(); + int idx = ui->listScansToRun->currentRow(); + if (idx < 0) return; + setupScan(idx); + startScan(); +} + +void UDSScanWindow::startScan() +{ + if (sendingFrames.isEmpty()) return; udsHandler->setReception(true); udsHandler->setProcessAllIDs(true); @@ -217,121 +598,173 @@ void UDSScanWindow::scanUDS() waitTimer->setInterval(ui->spinDelay->value()); ui->treeResults->clear(); - sendingFrames.clear(); nodeService = nullptr; nodeID = nullptr; nodeSubFunc = nullptr; - UDS_MESSAGE test; - int typ, id; - int startID, endID; - startID = ui->spinStartID->value(); - endID = ui->spinEndID->value(); - if (endID < startID) { - int temp = startID; - startID = endID; - endID = temp; - } - - int buses = ui->cbBuses->currentIndex(); - - for (id = startID; id <= endID; id++) - { - test.setFrameId( id ); - test.payload().clear(); - - if (ui->ckTester->isChecked()) - { - test.service = UDS_SERVICES::TESTER_PRESENT; - test.subFunc = 0; - sendOnBuses(test, buses); - } - - if (ui->ckSession->isChecked()) - { - for (typ = 1; typ < 4; typ++) //try each type of session access - { - test.service = UDS_SERVICES::DIAG_CONTROL; - test.subFunc = typ; - sendOnBuses(test, buses); - } - } - - if (ui->ckReset->isChecked()) //try to command a reset of the ECU. You're likely to know if it works. ;) - { - for (typ = 1; typ < 4; typ++) //try each type of session access - { - test.service = UDS_SERVICES::ECU_RESET; - test.subFunc = typ; - sendOnBuses(test, buses); - } - } - - if (ui->ckSecurity->isChecked()) //try to enter security mode - very likely to get a response if an ECU exists. - { - for (typ = 1; typ < 0x42; typ = typ + 2) //try each type of session access. In practice only the first 1-3 are likely to work - { - test.service = UDS_SERVICES::SECURITY_ACCESS; - test.subFunc = typ; - sendOnBuses(test, buses); - } - } - - if (ui->ckReadByAddr->isChecked()) - { - test.subFuncLen = ui->spinNumBytes->value(); - test.service = UDS_SERVICES::READ_BY_ADDR; - for (int subf = ui->spinLowerSubfunc->value(); subf <= ui->spinUpperSubfunc->value(); subf++) - { - test.subFunc = subf; - sendOnBuses(test, buses); - } - } - - if (ui->ckReadByID->isChecked()) - { - test.subFuncLen = ui->spinNumBytes->value(); - test.service = UDS_SERVICES::READ_BY_ID; - for (int subf = ui->spinLowerSubfunc->value(); subf <= ui->spinUpperSubfunc->value(); subf++) - { - test.subFunc = subf; - sendOnBuses(test, buses); - } - } - - if (ui->ckWildcard->isChecked()) - { - //preallocate the whole buffer so we don't have to keep updating the size and moving as we go. - //TODO: this shows a downside to the current method - it might potentially need to create a huge - //number of frames here. Of course, the rest of the system will do the same so I guess it's a bad idea - //any way you go to generate a 1 billion frame test. - int size = (endID - startID) * (ui->spinUpperService->value() - ui->spinLowerService->value()); - size *= (ui->spinUpperSubfunc->value() - ui->spinLowerSubfunc->value()); - sendingFrames.reserve(size); - - test.subFuncLen = ui->spinNumBytes->value(); - - for (typ = ui->spinLowerService->value(); typ <= ui->spinUpperService->value(); typ++) - { - test.service = typ; - for (int subTyp = ui->spinLowerSubfunc->value(); subTyp <= ui->spinUpperSubfunc->value(); subTyp++) - { - test.subFunc = subTyp; - sendOnBuses(test, buses); - } - } - } - } - waitTimer->start(); currIdx = -1; currentlyRunning = true; - ui->btnScan->setText("Abort Scan"); + //ui->btnScan->setText("Abort Scan"); ui->progressBar->setValue(0); ui->progressBar->setMaximum(sendingFrames.length()); + qDebug() << "Number of operations: " << sendingFrames.length(); sendNextMsg(); } +void UDSScanWindow::stopScan() +{ + waitTimer->stop(); + sendingFrames.clear(); + udsHandler->setReception(false); + udsHandler->setProcessAllIDs(false); + udsHandler->setFlowCtrl(false); + currentlyRunning = false; + //ui->btnScan->setText("Start Scan"); +} + +void UDSScanWindow::setupScan(int idx) +{ + UDS_MESSAGE test; + + qDebug() << "Generating scan id: " << idx; + + for (uint32_t id = scanEntries[idx].startID; id <= scanEntries[idx].endID; id++) + { + test.setFrameId( id ); + + if (scanEntries[idx].sessType > 0) + { + test.payload().clear(); + test.service = UDS_SERVICES::DIAG_CONTROL; + test.subFuncLen = 1; + test.subFunc = scanEntries[ idx].sessType; + sendOnBuses(test, scanEntries[idx].busToScan); + } + + test.payload().clear(); + + qDebug() << "Generating scan on ID " << QString::number(id, 16) << " of type " << scanEntries[idx].scanType; + + switch (scanEntries[idx].scanType) + { + case ST_TESTER_PRESENT: + test.service = UDS_SERVICES::TESTER_PRESENT; + test.subFuncLen = 1; + test.subFunc = 0; + sendOnBuses(test, scanEntries[idx].busToScan); + break; + case ST_SESS_CTRL: + for (int typ = 1; typ < 4; typ++) //try each type of session access + { + test.service = UDS_SERVICES::DIAG_CONTROL; + test.subFuncLen = 1; + test.subFunc = typ; + sendOnBuses(test, scanEntries[idx].busToScan); + } + break; + case ST_COMM_CTRL: + test.service = UDS_SERVICES::COMM_CTRL; + test.subFuncLen = 2; //need two bytes for this one + test.subFunc = 0x100; //00 01 on the bus = enable Rx/Tx + sendOnBuses(test, scanEntries[idx].busToScan); + break; + case ST_ECU_RESET: + for (int typ = 1; typ < 4; typ++) //try each type of session access + { + test.service = UDS_SERVICES::ECU_RESET; + test.subFuncLen = 1; + test.subFunc = typ; + sendOnBuses(test, scanEntries[idx].busToScan); + } + break; + case ST_CLEAR_DTC: + test.service = UDS_SERVICES::CLEAR_DIAG; + test.subFuncLen = 3; //DTC groups are sent as 3 bytes + test.subFunc = 0xFFFFFF; //clear everything! + sendOnBuses(test, scanEntries[idx].busToScan); + break; + case ST_READ_DTC: + test.service = UDS_SERVICES::READ_DTC; + test.subFuncLen = 2; + test.subFunc = 0x02FF; //get DTCs by mask (FF is the mask) + sendOnBuses(test, scanEntries[idx].busToScan); + break; + case ST_SEC_ACCESS: + for (int typ = 1; typ < 0x42; typ = typ + 2) //try each type of session access. In practice only the first 1-3 are likely to work + { + test.service = UDS_SERVICES::SECURITY_ACCESS; + test.subFuncLen = 1; + test.subFunc = typ; + sendOnBuses(test, scanEntries[idx].busToScan); + } + break; + case ST_READ_ID: + test.subFuncLen = scanEntries[idx].subfunctLen; + test.service = UDS_SERVICES::READ_BY_ID; + for (int subf = scanEntries[idx].subfunctLower; subf <= scanEntries[idx].subfunctUpper; subf += scanEntries[idx].subfunctIncrement) + { + test.subFunc = subf; + sendOnBuses(test, scanEntries[idx].busToScan); + } + break; + case ST_READ_ADDR: + test.subFuncLen = scanEntries[idx].subfunctLen; + test.service = UDS_SERVICES::READ_BY_ADDR; + for (int subf = scanEntries[idx].subfunctLower; subf <= scanEntries[idx].subfunctUpper; subf += scanEntries[idx].subfunctIncrement) + { + test.subFunc = subf; + sendOnBuses(test, scanEntries[idx].busToScan); + } + break; + case ST_READ_SCALING: + //sub function should always be a length of 2 though. Should conform to this in the GUI + test.subFuncLen = 2; + test.service = UDS_SERVICES::READ_SCALING_ID; + for (int subf = scanEntries[idx].subfunctLower; subf <= scanEntries[idx].subfunctUpper; subf += scanEntries[idx].subfunctIncrement) + { + test.subFunc = subf; + sendOnBuses(test, scanEntries[idx].busToScan); + } + break; + case ST_IO_CTRL: + test.subFuncLen = 3; + test.service = UDS_SERVICES::IO_CTRL; + for (int subf = scanEntries[idx].subfunctLower; subf <= scanEntries[idx].subfunctUpper; subf += scanEntries[idx].subfunctIncrement) + { + test.subFunc = subf; //the upper byte will be 0 which is what we want. 0 = Return control to ECU + sendOnBuses(test, scanEntries[idx].busToScan); + } + break; + case ST_ROUTINE_CTRL: + test.subFuncLen = 3; + test.service = UDS_SERVICES::ROUTINE_CTRL; + for (int subf = scanEntries[idx].subfunctLower; subf <= scanEntries[idx].subfunctUpper; subf += scanEntries[idx].subfunctIncrement) + { + //request results of routine for all the addresses we're testing. This is the safest thing to do. + //starting or stopping arbitrary routines is super dangerous. Don't do that unless you really know + //what the hell you're doing or something really crazy might happen. + test.subFunc = (subf << 8) + 3; + sendOnBuses(test, scanEntries[idx].busToScan); + } + break; + case ST_CUSTOM: + test.subFuncLen = scanEntries[idx].subfunctLen; + + for (uint32_t typ = scanEntries[idx].serviceLower; typ <= scanEntries[idx].serviceUpper; typ++) + { + test.service = typ; + for (int subTyp = scanEntries[idx].subfunctLower; subTyp <= scanEntries[idx].subfunctUpper; subTyp += scanEntries[idx].subfunctIncrement) + { + test.subFunc = subTyp; + sendOnBuses(test, scanEntries[idx].busToScan); + } + } + break; + } + } +} + //Updates here are sent about every 1/4 second. That's fine for most windows but not this one. void UDSScanWindow::updatedFrames(int numFrames) { @@ -491,7 +924,7 @@ void UDSScanWindow::sendNextMsg() udsHandler->setReception(false); udsHandler->setProcessAllIDs(false); udsHandler->setFlowCtrl(false); - ui->btnScan->setText("Start Scan"); + //ui->btnScan->setText("Start Scan"); currentlyRunning = false; } ui->progressBar->setValue(currIdx); diff --git a/re/udsscanwindow.h b/re/udsscanwindow.h index 9ec8745..fe203a7 100644 --- a/re/udsscanwindow.h +++ b/re/udsscanwindow.h @@ -9,6 +9,44 @@ #include #include + +enum SCAN_TYPE +{ + ST_TESTER_PRESENT, + ST_SESS_CTRL, + ST_COMM_CTRL, + ST_ECU_RESET, + ST_CLEAR_DTC, + ST_READ_DTC, + ST_SEC_ACCESS, + ST_READ_ID, + ST_READ_ADDR, + ST_READ_SCALING, + ST_IO_CTRL, + ST_ROUTINE_CTRL, + ST_CUSTOM, +}; + +//stores the parameters for one scan +class ScanEntry +{ +public: + uint32_t startID, endID; + int32_t idOffset; + bool bAdaptiveOffset; + bool bShowNoReplies; + uint32_t busToScan; + uint32_t maxWaitTime; //in milliseconds + SCAN_TYPE scanType; + uint32_t sessType; + uint32_t subfunctLen; + qint64 subfunctLower; + qint64 subfunctUpper; + uint32_t subfunctIncrement; + uint32_t serviceLower; + uint32_t serviceUpper; +}; + namespace Ui { class UDSScanWindow; } @@ -24,16 +62,26 @@ public: private slots: void updatedFrames(int numFrames); void gotUDSReply(UDS_MESSAGE msg); - void scanUDS(); + void scanAll(); + void scanSelected(); void saveResults(); void timeOut(); void adaptiveToggled(); - void wildcardToggled(); - void readByToggled(); + void changedScanType(); void numBytesChanged(); void checkIDRange(); void checkServiceRange(); void checkSubFuncRange(); + void deleteSelectedScan(); + void addNewScan(); + void loadScans(); + void saveScans(); + void setNoReplyVal(); + void setMaxDelayVal(); + void setIncrementVal(); + void setReplyOffset(); + void setSessType(); + void setBusToScan(); private: Ui::UDSScanWindow *ui; @@ -44,13 +92,23 @@ private: QTreeWidgetItem *nodeID; QTreeWidgetItem *nodeService; QTreeWidgetItem *nodeSubFunc; + QVector scanEntries; + ScanEntry *currEditEntry; int currIdx = 0; bool currentlyRunning; + bool inhibitUpdates; + void displayScanEntry(int idx); + QString generateListDesc(int idx); + void setupScan(int idx); + void startScan(); + void stopScan(); void sendNextMsg(); void sendOnBuses(UDS_MESSAGE frame, int buses); void setupNodes(uint32_t replyID); void dumpNode(QTreeWidgetItem* item, QFile *file, int indent); bool eventFilter(QObject *obj, QEvent *event); + + static void setControlState(QWidget & widget, bool valid); }; #endif // UDSSCANWINDOW_H diff --git a/scriptcontainer.cpp b/scriptcontainer.cpp index 5cb63da..8720235 100644 --- a/scriptcontainer.cpp +++ b/scriptcontainer.cpp @@ -263,7 +263,7 @@ void CANScriptHelper::gotTargettedFrame(const CANFrame &frame) if (filters[i].checkFilter(frame.frameId(), frame.bus)) { QJSValueList args; - args << frame.bus << frame.frameId() << frame.payload().length(); + args << frame.bus << frame.frameId() << static_cast(frame.payload().length()); QJSValue dataBytes = scriptEngine->newArray(dataLen); for (int j = 0; j < dataLen; j++) dataBytes.setProperty(j, QJSValue(data[j])); @@ -341,7 +341,7 @@ void ISOTPScriptHelper::newISOMessage(ISOTP_MESSAGE msg) //qDebug() << "Got frame in script interface"; QJSValueList args; - args << msg.bus << msg.frameId() << msg.payload().length(); + args << msg.bus << msg.frameId() << static_cast(msg.payload().length()); QJSValue dataBytes = scriptEngine->newArray(static_cast(msg.payload().length())); for (int j = 0; j < msg.payload().length(); j++) dataBytes.setProperty(static_cast(j), QJSValue((unsigned char)msg.payload()[j])); @@ -420,7 +420,7 @@ void UDSScriptHelper::newUDSMessage(UDS_MESSAGE msg) qDebug() << "Got frame in script interface"; QJSValueList args; - args << msg.bus << msg.frameId() << msg.service << msg.subFunc << msg.payload().length(); + args << msg.bus << msg.frameId() << msg.service << msg.subFunc << static_cast(msg.payload().length()); QJSValue dataBytes = scriptEngine->newArray(static_cast(msg.payload().length())); for (int j = 0; j < msg.payload().length(); j++) dataBytes.setProperty(static_cast(j), QJSValue((unsigned char)msg.payload()[j])); diff --git a/scriptingwindow.cpp b/scriptingwindow.cpp index 1d5f378..ea254c3 100644 --- a/scriptingwindow.cpp +++ b/scriptingwindow.cpp @@ -4,6 +4,9 @@ #include #include #include +#if QT_VERSION >= QT_VERSION_CHECK( 5, 10, 0 ) +#include +#endif #include "connections/canconmanager.h" #include "helpwindow.h" @@ -38,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))); @@ -234,7 +240,13 @@ void ScriptingWindow::createNewScript() container = new ScriptContainer(); - container->fileName = "UNNAMED_" + QString::number((qrand() % 10000)) + ".js"; + QString randomPart; +#if QT_VERSION < QT_VERSION_CHECK( 5, 10, 0 ) + randomPart = QString::number((qrand() % 10000)); +#else + randomPart = QString::number((QRandomGenerator::global()->bounded(10000))); +#endif + container->fileName = "UNNAMED_" + randomPart + ".js"; container->filePath = QString(); container->scriptText = QString(); container->setScriptWindow(this); @@ -292,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); @@ -349,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) @@ -364,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 156a72e..5577099 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: void closeEvent(QCloseEvent *event); void readSettings(); void writeSettings(); + void saveLog(); bool eventFilter(QObject *obj, QEvent *event); Ui::ScriptingWindow *ui; diff --git a/signalviewerwindow.cpp b/signalviewerwindow.cpp index c401b49..c31f0fb 100644 --- a/signalviewerwindow.cpp +++ b/signalviewerwindow.cpp @@ -2,8 +2,12 @@ #include "ui_signalviewerwindow.h" #include "helpwindow.h" #include "mainwindow.h" +#include "utility.h" #include +#define MSG_COL 1 +#define VALUE_COL 2 + SignalViewerWindow::SignalViewerWindow(const QVector *frames, QWidget *parent) : QDialog(parent), ui(new Ui::SignalViewerWindow) @@ -14,21 +18,42 @@ SignalViewerWindow::SignalViewerWindow(const QVector *frames, QWidget modelFrames = frames; QStringList headers; - headers << "Signal" << "Value"; + headers << "Node" << "Signal" << "Value"; ui->tableViewer->setHorizontalHeaderLabels(headers); - ui->tableViewer->setColumnWidth(0, 150); - ui->tableViewer->setColumnWidth(1, 300); + ui->tableViewer->setColumnWidth(0, 100); + ui->tableViewer->setColumnWidth(1, 150); + + QSettings settings; + QFont sysFont; + int fontSize = settings.value("Main/FontSize", 9).toUInt(); + if(settings.value("Main/FontFixedWidth", false).toBool()) + sysFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); //get default fixed width font + else + sysFont = QFont(); //get default font + sysFont.setPointSize(fontSize); + ui->tableViewer->setFont(sysFont); + QHeaderView *HorzHdr = ui->tableViewer->horizontalHeader(); HorzHdr->setStretchLastSection(true); //causes the data column to automatically fill the tableview + HorzHdr->setFont(QFont()); + + QHeaderView *verticalHeader = ui->tableViewer->verticalHeader(); + verticalHeader->setFont(QFont()); dbcHandler = DBCHandler::getReference(); + currentlySelectedMsg = nullptr; + connect(ui->cbNodes, SIGNAL(currentIndexChanged(int)), this, SLOT(loadMessages(int))); connect(ui->cbMessages, SIGNAL(currentIndexChanged(int)), this, SLOT(loadSignals(int))); connect(ui->btnAdd, SIGNAL(clicked(bool)), this, SLOT(addSignal())); connect(MainWindow::getReference(), SIGNAL(framesUpdated(int)), this, SLOT(updatedFrames(int))); connect(ui->btnRemove, SIGNAL(clicked(bool)), this, SLOT(removeSelectedSignal())); + connect(ui->btnSave, SIGNAL(clicked(bool)), this, SLOT(saveSignalsFile())); + connect(ui->btnLoad, SIGNAL(clicked(bool)), this, SLOT(loadSignalsFile())); + connect(ui->btnAppend, SIGNAL(clicked(bool)), this, SLOT(appendSignalsFile())); + connect(ui->btnClear, SIGNAL(clicked(bool)), this, SLOT(clearSignalsTable())); - loadMessages(); + loadNodes(); } SignalViewerWindow::~SignalViewerWindow() @@ -73,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, 1); - 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, 1, 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); } } } @@ -95,19 +123,68 @@ void SignalViewerWindow::removeSelectedSignal() ui->tableViewer->removeRow(selRow); } -void SignalViewerWindow::loadMessages() +void SignalViewerWindow::loadNodes() { int numFiles; - ui->cbMessages->clear(); + ui->cbNodes->clear(); if (dbcHandler == nullptr) return; if ((numFiles = dbcHandler->getFileCount()) == 0) return; qDebug() << numFiles; + for (int f = 0; f < numFiles; f++) + { + DBCFile* thisFile = dbcHandler->getFileByIdx(f); + qDebug() << thisFile->messageHandler->getCount(); + + QList names; + + for (int x = 0; x < thisFile->dbc_nodes.count(); x++) + { + bool messagesInNode = false; + for (int m = 0; m < thisFile->messageHandler->getCount(); m++) + { + if(thisFile->messageHandler->findMsgByIdx(m)->sender->name == thisFile->dbc_nodes[x].name) + { + messagesInNode = true; + break; + } + } + if(messagesInNode) + { + QString fullyQualifiedNodeName = thisFile->getFilenameNoExt() + Utility::fullyQualifiedNameSeperator + thisFile->dbc_nodes[x].name; + names.append(fullyQualifiedNodeName); + } + } + + if(names.count() > 0) + { + names.sort(); + ui->cbNodes->addItem("----" + thisFile->getFilename()); + Utility::SetComboBoxItemEnabled(ui->cbNodes, ui->cbNodes->count() -1, false); + for(int i=0; icbNodes->addItem(names[i]); + } + } +} + +void SignalViewerWindow::loadMessages(int idx) +{ + int numFiles = 0; + ui->cbMessages->clear(); + if (dbcHandler == nullptr) return; + if ((numFiles = dbcHandler->getFileCount()) == 0) return; + qDebug() << numFiles; + + QString displayedNodeName = ui->cbNodes->itemText(idx); + for (int f = 0; f < numFiles; f++) { qDebug() << dbcHandler->getFileByIdx(f)->messageHandler->getCount(); + for (int x = 0; x < dbcHandler->getFileByIdx(f)->messageHandler->getCount(); x++) { - ui->cbMessages->addItem(dbcHandler->getFileByIdx(f)->messageHandler->findMsgByIdx(x)->name); + QString fullyQualifiedNodeName = dbcHandler->getFileByIdx(f)->getFilenameNoExt() + Utility::fullyQualifiedNameSeperator + dbcHandler->getFileByIdx(f)->messageHandler->findMsgByIdx(x)->sender->name; + if(fullyQualifiedNodeName == displayedNodeName) + ui->cbMessages->addItem(dbcHandler->getFileByIdx(f)->messageHandler->findMsgByIdx(x)->name); } } } @@ -115,14 +192,14 @@ void SignalViewerWindow::loadMessages() void SignalViewerWindow::loadSignals(int idx) { Q_UNUSED(idx); - //messages were placed into the list in the same order as they exist - //in the data structure so it should have been possible to just - //look it up based on index but by name is probably safer and this operation - //is not time critical at all. - DBC_MESSAGE *msg = dbcHandler->findMessage(ui->cbMessages->currentText()); - if (msg == nullptr) return; ui->cbSignals->clear(); + + QString fullyQualifiedNodeName = ui->cbNodes->itemText(ui->cbNodes->currentIndex()); + QString msgName = ui->cbMessages->itemText(ui->cbMessages->currentIndex()); + + DBC_MESSAGE *msg = dbcHandler->findMessage(msgName, fullyQualifiedNodeName); + if (msg == nullptr) return; for (int x = 0; x < msg->sigHandler->getCount(); x++) { ui->cbSignals->addItem(msg->sigHandler->findSignalByIdx(x)->name); @@ -131,16 +208,194 @@ void SignalViewerWindow::loadSignals(int idx) void SignalViewerWindow::addSignal() { - DBC_MESSAGE *msg = dbcHandler->findMessage(ui->cbMessages->currentText()); + DBC_MESSAGE *msg = nullptr; + + QString fullyQualifiedNodeName = ui->cbNodes->itemText(ui->cbNodes->currentIndex()); + QString msgName = ui->cbMessages->itemText(ui->cbMessages->currentIndex()); + + msg = dbcHandler->findMessage(msgName, fullyQualifiedNodeName); + if (!msg) return; DBC_SIGNAL *sig = msg->sigHandler->findSignalByName(ui->cbSignals->currentText()); if (!sig) return; + addSignal(sig); +} + +void SignalViewerWindow::addSignal(DBC_SIGNAL *sig) +{ signalList.append(sig); int rowIdx = ui->tableViewer->rowCount(); ui->tableViewer->insertRow(rowIdx); - QTableWidgetItem *item = new QTableWidgetItem(sig->name); - ui->tableViewer->setItem(rowIdx, 0, item); + QTableWidgetItem *nodeitem = new QTableWidgetItem(sig->parentMessage->sender->name); + ui->tableViewer->setItem(rowIdx, 0, nodeitem); + QTableWidgetItem *msgitem = new QTableWidgetItem(sig->name); + ui->tableViewer->setItem(rowIdx, 1, msgitem); +} +void SignalViewerWindow::saveSignalsFile() +{ + saveDefinitions(); +} + +void SignalViewerWindow::loadSignalsFile() +{ + loadDefinitions(false); +} + +void SignalViewerWindow::appendSignalsFile() +{ + loadDefinitions(true); +} + +void SignalViewerWindow::clearSignalsTable() +{ + clearSignalsTable(true); +} + +void SignalViewerWindow::clearSignalsTable(bool askForConfirmation) +{ + if(askForConfirmation) + { + QMessageBox::StandardButton confirmDialog; + confirmDialog = QMessageBox::question(this, "Danger Will Robinson", "Are you sure you want to clear all of your signals?", + QMessageBox::Yes|QMessageBox::No); + if (confirmDialog == QMessageBox::No) + { + return; + } + } + + signalList.clear(); + ui->tableViewer->setRowCount(0); +} + +void SignalViewerWindow::saveDefinitions() +{ + QString filename; + QFileDialog dialog(this); + QSettings settings; + + QStringList filters; + filters.append(QString(tr("SignalViewer definition (*.sdf)"))); + + dialog.setFileMode(QFileDialog::AnyFile); + dialog.setNameFilters(filters); + dialog.setViewMode(QFileDialog::Detail); + dialog.setAcceptMode(QFileDialog::AcceptSave); + dialog.setDirectory(settings.value("SignalViewer/LoadSaveDirectory", dialog.directory().path()).toString()); + + if (dialog.exec() == QDialog::Accepted) + { + filename = dialog.selectedFiles()[0]; + settings.setValue("SignalViewer/LoadSaveDirectory", dialog.directory().path()); + + if (!filename.contains('.')) filename += ".sdf"; + + QFile *outFile = new QFile(filename); + + if (!outFile->open(QIODevice::WriteOnly | QIODevice::Text)) + return; + + DBC_SIGNAL *sig; + for (int i = 0; i < signalList.count(); i++) + { + sig = signalList.at(i); + + outFile->write("SV1"); + outFile->putChar(','); + outFile->write(QString::number(sig->parentMessage->ID, 16).toUtf8()); + outFile->putChar(','); + outFile->write(sig->parentMessage->name.toUtf8()); + outFile->putChar(','); + outFile->write(sig->name.toUtf8()); + + outFile->write("\n"); + } + outFile->close(); + } +} + +void SignalViewerWindow::loadDefinitions(bool append) +{ + QString filename; + QFileDialog dialog; + QSettings settings; + + QStringList filters; + filters.append(QString(tr("SignalViewer definition (*.sdf)"))); + + QList loadedSignals; + + if (dbcHandler == nullptr) return; + if (dbcHandler->getFileCount() == 0) dbcHandler->createBlankFile(); + + dialog.setFileMode(QFileDialog::ExistingFile); + dialog.setNameFilters(filters); + dialog.setViewMode(QFileDialog::Detail); + dialog.setDirectory(settings.value("SignalViewer/LoadSaveDirectory", dialog.directory().path()).toString()); + + if (dialog.exec() == QDialog::Accepted) + { + filename = dialog.selectedFiles()[0]; + settings.setValue("SignalViewer/LoadSaveDirectory", dialog.directory().path()); + + QFile *inFile = new QFile(filename); + QByteArray line; + + if (!inFile->open(QIODevice::ReadOnly | QIODevice::Text)) + return; + + while (!inFile->atEnd()) { + line = inFile->readLine().simplified(); + if (line.length() > 2) + { + QList tokens = line.split(','); + + DBC_SIGNAL *sig; + + if (tokens[0] == "SV1") //signal viewer save format v1 + { + // = tokens[1].toUInt(nullptr, 16); + + int msgId = tokens[1].toUInt(nullptr, 16); + QString msgName = QString(tokens[2]); + QString sigName = QString(tokens[3]); + DBC_MESSAGE *msg;; + if( (msg = dbcHandler->findMessage(msgId)) ) + { + sig = msg->sigHandler->findSignalByName(sigName); + if(sig) + loadedSignals.append(sig); + } + else if ( (msg = dbcHandler->findMessage(msgName)) ) + { + //this is not a very safe way to match since messages names can be duplicated + sig = msg->sigHandler->findSignalByName(sigName); + if(sig) + loadedSignals.append(sig); + } + else + { + qDebug() << "Couldn't find the message by ID or name! " << msgName << " " << sigName; + } + } + } + } + inFile->close(); + + if(loadedSignals.count() > 0) + { + if(append == false) + { + clearSignalsTable(false); + } + + for (int i=0; i signalList; const QVector *modelFrames; diff --git a/simplecrypt.cpp b/simplecrypt.cpp index 79a16ba..51f4271 100644 --- a/simplecrypt.cpp +++ b/simplecrypt.cpp @@ -31,6 +31,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include +#if QT_VERSION >= QT_VERSION_CHECK( 5, 10, 0 ) +#include +#endif SimpleCrypt::SimpleCrypt(): m_key(0), @@ -38,7 +42,9 @@ SimpleCrypt::SimpleCrypt(): m_protectionMode(ProtectionChecksum), m_lastError(ErrorNoError) { +#if QT_VERSION < QT_VERSION_CHECK( 5, 10, 0 ) qsrand(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF)); +#endif } SimpleCrypt::SimpleCrypt(quint64 key): @@ -47,7 +53,9 @@ SimpleCrypt::SimpleCrypt(quint64 key): m_protectionMode(ProtectionChecksum), m_lastError(ErrorNoError) { +#if QT_VERSION < QT_VERSION_CHECK( 5, 10, 0 ) qsrand(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF)); +#endif splitKey(); } @@ -113,7 +121,11 @@ QByteArray SimpleCrypt::encryptToByteArray(QByteArray plaintext) } //prepend a random char to the string +#if QT_VERSION < QT_VERSION_CHECK( 5, 10, 0 ) char randomChar = char(qrand() & 0xFF); +#else + char randomChar = char(QRandomGenerator::global()->bounded(256)); +#endif ba = randomChar + integrityProtection + ba; int pos(0); diff --git a/triggerdialog.cpp b/triggerdialog.cpp new file mode 100644 index 0000000..bb09c3a --- /dev/null +++ b/triggerdialog.cpp @@ -0,0 +1,348 @@ +#include "triggerdialog.h" +#include "ui_triggerdialog.h" +#include + +TriggerDialog::TriggerDialog(QList trigs, QWidget *parent) : + QDialog(parent), + ui(new Ui::TriggerDialog) +{ + ui->setupUi(this); + triggers.clear(); + triggers.append(trigs); //make sure it's just a clone of the existing triggers + + //if there are no triggers then create a default one + if (triggers.count() == 0) + { + Trigger trig; + trig.ID = 0; + trig.bus = 0; + trig.maxCount = 0; + trig.milliseconds = 100; + trig.sigName = ""; + trig.sigValueDbl = 0.0; + trig.sigValueInt = 0; + trig.triggerMask = TriggerMask::TRG_MS; + triggers.append(trig); + } + lastMsgID = 0xFFFFFFFF; + inhibitUpdates = false; + + dbcHandler = DBCHandler::getReference(); + /* + getting the messages possible is complicated by the fact that multiple DBC files + can be loaded and each one can be associated with a different bus. So, this is + tough. At first I suppose we need to list all possible messages but if the user + sets a bus then we should recalculate and only show messages possible on that bus. + */ + ui->cmMsgID->clear(); + QStringList entries; + int numFiles = dbcHandler->getFileCount(); + for (int i = 0; i < numFiles; i++) + { + DBCFile *file = dbcHandler->getFileByIdx(i); + int numMsgs = file->messageHandler->getCount(); + for (int j = 0; j < numMsgs; j++) + { + DBC_MESSAGE *msg = file->messageHandler->findMsgByIdx(j); + entries.append(("0x" + QString::number(msg->ID, 16) + " (" + msg->name + ")")); + } + } + entries.sort(); + ui->cmMsgID->addItems(entries); + + connect(ui->listTriggers, &QListWidget::currentRowChanged, this, &TriggerDialog::FillDetails); + connect(ui->btnDelete, &QPushButton::clicked, this, &TriggerDialog::deleteSelectedTrigger); + connect(ui->btnNew, &QPushButton::clicked, this, &TriggerDialog::addNewTrigger); + connect(ui->btnSave, &QPushButton::clicked, this, &TriggerDialog::saveAndExit); + connect(ui->cbBus, &QCheckBox::stateChanged, this, &TriggerDialog::handleCheckboxes); + connect(ui->cbMillis, &QCheckBox::stateChanged, this, &TriggerDialog::handleCheckboxes); + connect(ui->cbMaxCount, &QCheckBox::stateChanged, this, &TriggerDialog::handleCheckboxes); + connect(ui->cbMsgID, &QCheckBox::stateChanged, this, &TriggerDialog::handleCheckboxes); + connect(ui->cbSigValue, &QCheckBox::stateChanged, this, &TriggerDialog::handleCheckboxes); + connect(ui->cbSignal, &QCheckBox::stateChanged, this, &TriggerDialog::handleCheckboxes); + connect(ui->txtMaxCount, &QLineEdit::textEdited, this, &TriggerDialog::regenerateCurrentListItem); + connect(ui->cmMsgID, &QComboBox::currentTextChanged, this, &TriggerDialog::regenerateCurrentListItem); + connect(ui->cmSignal, &QComboBox::currentTextChanged, this, &TriggerDialog::regenerateCurrentListItem); + connect(ui->txtBus, &QLineEdit::textEdited, this, &TriggerDialog::regenerateCurrentListItem); + connect(ui->txtDelay, &QLineEdit::textEdited, this, &TriggerDialog::regenerateCurrentListItem); + connect(ui->txtSigValue, &QLineEdit::textEdited, this, &TriggerDialog::regenerateCurrentListItem); +} + +TriggerDialog::~TriggerDialog() +{ + delete ui; +} + +QList TriggerDialog::getUpdatedTriggers() +{ + return triggers; +} + +void TriggerDialog::showEvent(QShowEvent* event) +{ + QDialog::showEvent(event); + regenerateList(); +} + +void TriggerDialog::regenerateList() +{ + ui->listTriggers->clear(); + //iterate over the QList of triggers, show them in the list, + //load the first item in the list, and display it broken down on the right. + foreach(Trigger trig, triggers) + { + ui->listTriggers->addItem(buildEntry(trig)); + } + ui->listTriggers->setCurrentRow(0); + FillDetails(); +} + +QString TriggerDialog::buildEntry(Trigger trig) +{ + QString build; + if (trig.triggerMask & TriggerMask::TRG_BUS) + { + build = build + "BUS" + QString::number(trig.bus) + " "; + } + + if (trig.triggerMask & TriggerMask::TRG_ID) + { + build = build + "ID0x" + QString::number(trig.ID, 16) + " "; + } + + if (trig.triggerMask & TriggerMask::TRG_SIGNAL) + { + build = build + "SIG[" + trig.sigName; + if (trig.triggerMask & TriggerMask::TRG_SIGVAL) + { + build = build + ";" + QString::number(trig.sigValueDbl); + } + build = build + "] "; + } + + if (trig.triggerMask & TriggerMask::TRG_COUNT) + { + build = build + QString::number(trig.maxCount) + "X "; + } + + if (trig.triggerMask & TriggerMask::TRG_MS) + { + build = build + QString::number(trig.milliseconds) + "MS "; + } + + return build; +} + +void TriggerDialog::handleCheckboxes() +{ + ui->txtBus->setEnabled(ui->cbBus->checkState()); + ui->txtDelay->setEnabled(ui->cbMillis->checkState()); + ui->txtMaxCount->setEnabled(ui->cbMaxCount->checkState()); + ui->cmMsgID->setEnabled(ui->cbMsgID->checkState()); + ui->cmSignal->setEnabled(ui->cbSignal->checkState()); + ui->txtSigValue->setEnabled(ui->cbSigValue->checkState()); + + if (ui->cbBus->checkState() == Qt::Unchecked) ui->txtBus->setText(""); + if (ui->cbMillis->checkState() == Qt::Unchecked) ui->txtDelay->setText(""); + if (ui->cbMaxCount->checkState() == Qt::Unchecked) ui->txtMaxCount->setText(""); + if (ui->cbMsgID->checkState() == Qt::Unchecked) ui->cmMsgID->setCurrentText(""); + if (ui->cbSignal->checkState() == Qt::Unchecked) ui->cmSignal->setCurrentText(""); + if (ui->cbSigValue->checkState() == Qt::Unchecked) ui->txtSigValue->setText(""); + + regenerateCurrentListItem(); +} + +//load all the values from the editing screen and place them into the structure +//also, redo the list entry so it shows the proper text +void TriggerDialog::regenerateCurrentListItem() +{ + if (inhibitUpdates) return; + int row = ui->listTriggers->currentRow(); + Trigger trig; + trig.ID = 0; + trig.bus = 0; + trig.maxCount = 0; + trig.milliseconds = 0; + trig.sigName = ""; + trig.sigValueDbl = 0.0; + trig.triggerMask = 0; + + if (ui->cbMsgID->isChecked() && ui->cmMsgID->currentText().length() > 0) + { + trig.triggerMask |= TriggerMask::TRG_ID; + trig.ID = ui->cmMsgID->currentText() + .replace("0x", "") + .split("(")[0] + .toInt(nullptr, 16); + if (trig.ID != lastMsgID) + { + lastMsgID = trig.ID; + //now, reload signals combobox + ui->cmSignal->clear(); + QStringList entries; + //danger - this isn't doing a great job of making sure we're using + //the right message. Should at least search by message name instead + //if possible. + DBC_MESSAGE *msg = dbcHandler->findMessage(trig.ID); + int numSigs = msg->sigHandler->getCount(); + for (int s = 0; s < numSigs; s++) + { + DBC_SIGNAL *sig = msg->sigHandler->findSignalByIdx(s); + + entries.append(sig->name); + } + entries.sort(); + ui->cmSignal->addItems(entries); + } + } + + if (ui->cbBus->isChecked() && ui->txtBus ->text().length() > 0) + { + trig.triggerMask |= TriggerMask::TRG_BUS; + trig.bus = ui->txtBus->text().toInt(); + } + + if (ui->cbMillis->isChecked() && ui->txtDelay->text().length() > 0) + { + trig.triggerMask |= TriggerMask::TRG_MS; + trig.milliseconds = ui->txtDelay->text().toInt(); + } + + if (ui->cbMaxCount->isChecked() && ui->txtMaxCount->text().length() > 0) + { + trig.triggerMask |= TriggerMask::TRG_COUNT; + trig.maxCount = ui->txtMaxCount->text().toInt(); + } + + if (ui->cbSigValue->isChecked() && ui->txtSigValue->text().length() > 0) + { + trig.triggerMask |= TriggerMask::TRG_SIGVAL; + trig.sigValueDbl = ui->txtSigValue->text().toDouble(); + } + + if (ui->cbSignal->isChecked() && ui->cmSignal->currentText().length() > 0) + { + trig.triggerMask |= TriggerMask::TRG_SIGNAL; + trig.sigName = ui->cmSignal->currentText().trimmed(); + } + + triggers[row] = trig; + ui->listTriggers->item(row)->setText(buildEntry(trig)); +} + +void TriggerDialog::FillDetails() +{ + int selectedRow = ui->listTriggers->currentRow(); + if (selectedRow < 0) return; + inhibitUpdates = true; //this function makes a lot of changes and they should not trigger the normal updates + + if (triggers[selectedRow].triggerMask & TriggerMask::TRG_BUS) + { + ui->txtBus->setEnabled(true); + ui->txtBus->setText(QString::number(triggers[selectedRow].bus)); + ui->cbBus->setCheckState(Qt::Checked); + } + else + { + ui->txtBus->setEnabled(false); + ui->cbBus->setCheckState(Qt::Unchecked); + } + + if (triggers[selectedRow].triggerMask & TriggerMask::TRG_ID) + { + ui->cmMsgID->setEnabled(true); + ui->cbMsgID->setCheckState(Qt::Checked); + QString build = "0x" + QString::number(triggers[selectedRow].ID, 16); + + DBC_MESSAGE *msg = dbcHandler->findMessage(triggers[selectedRow].ID); + if (msg) + { + build = build + " (" + msg->name + ")"; + } + ui->cmMsgID->setCurrentText(build); + } + else + { + ui->cmMsgID->setEnabled(false); + ui->cbMsgID->setCheckState(Qt::Unchecked); + } + + if (triggers[selectedRow].triggerMask & TriggerMask::TRG_COUNT) + { + ui->txtMaxCount->setEnabled(true); + ui->txtMaxCount->setText(QString::number(triggers[selectedRow].maxCount)); + ui->cbMaxCount->setCheckState(Qt::Checked); + } + else + { + ui->txtMaxCount->setEnabled(false); + ui->cbMaxCount->setCheckState(Qt::Unchecked); + } + + if (triggers[selectedRow].triggerMask & TriggerMask::TRG_MS) + { + ui->txtDelay->setEnabled(true); + ui->txtDelay->setText(QString::number(triggers[selectedRow].milliseconds)); + ui->cbMillis->setCheckState(Qt::Checked); + } + else + { + ui->txtDelay->setEnabled(false); + ui->cbMillis->setCheckState(Qt::Unchecked); + } + + if (triggers[selectedRow].triggerMask & TriggerMask::TRG_SIGNAL) + { + ui->cmSignal->setEnabled(true); + ui->cbSignal->setCheckState(Qt::Checked); + ui->cmSignal->setCurrentText(triggers[selectedRow].sigName); + } + else + { + ui->cmSignal->setEnabled(false); + ui->cbSignal->setCheckState(Qt::Unchecked); + } + + if (triggers[selectedRow].triggerMask & TriggerMask::TRG_SIGVAL) + { + ui->txtSigValue->setEnabled(true); + ui->txtSigValue->setText(QString::number(triggers[selectedRow].sigValueDbl)); + ui->cbSigValue->setCheckState(Qt::Checked); + } + else + { + ui->txtSigValue->setEnabled(false); + ui->cbSigValue->setCheckState(Qt::Unchecked); + } + + inhibitUpdates = false; +} + +void TriggerDialog::addNewTrigger() +{ + Trigger newTrig; + newTrig.ID = 0; + newTrig.bus = 0; + newTrig.maxCount = 0; + newTrig.milliseconds = 100; + newTrig.sigName = ""; + newTrig.sigValueDbl = 0.0; + newTrig.sigValueInt = 0; + newTrig.triggerMask = TriggerMask::TRG_MS; + triggers.append(newTrig); + ui->listTriggers->addItem(buildEntry(newTrig)); +} + +void TriggerDialog::deleteSelectedTrigger() +{ + if (triggers.count() == 1) + { + QMessageBox::information(this, "Error", "Cannot delete the last trigger"); + return; + } +} + +void TriggerDialog::saveAndExit() +{ + accept(); +} diff --git a/triggerdialog.h b/triggerdialog.h new file mode 100644 index 0000000..09f6a1c --- /dev/null +++ b/triggerdialog.h @@ -0,0 +1,40 @@ +#ifndef TRIGGERDIALOG_H +#define TRIGGERDIALOG_H + +#include +#include "can_trigger_structs.h" +#include "dbc/dbchandler.h" + +namespace Ui { +class TriggerDialog; +} + +class TriggerDialog : public QDialog +{ + Q_OBJECT + +public: + explicit TriggerDialog(QList triggers, QWidget *parent = nullptr); + QList getUpdatedTriggers(); + void showEvent(QShowEvent*); + ~TriggerDialog(); + QString buildEntry(Trigger trig); + +private: + Ui::TriggerDialog *ui; + DBCHandler *dbcHandler; + int lastMsgID; + bool inhibitUpdates; + + QList triggers; + + void FillDetails(); + void addNewTrigger(); + void deleteSelectedTrigger(); + void saveAndExit(); + void regenerateList(); + void handleCheckboxes(); + void regenerateCurrentListItem(); +}; + +#endif // TRIGGERDIALOG_H diff --git a/triggerdialog.ui b/triggerdialog.ui new file mode 100644 index 0000000..95477e0 --- /dev/null +++ b/triggerdialog.ui @@ -0,0 +1,191 @@ + + + TriggerDialog + + + + 0 + 0 + 771 + 303 + + + + Trigger Builder + + + + + + + + + + + + + Add New + + + + + + + Delete + + + + + + + + + QFrame::Raised + + + 7 + + + 3 + + + Qt::Vertical + + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + Bus + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + false + + + + + + + Msg ID + + + + + + + Signal + + + + + + + false + + + + 0 + 0 + + + + true + + + + + + + MS Delay + + + + + + + false + + + + + + + Max Count + + + + + + + false + + + + + + + false + + + + 0 + 0 + + + + true + + + + + + + Signal Value + + + + + + + false + + + + + + + + + + + Save And Update + + + + + + + + diff --git a/ui/bisectwindow.ui b/ui/bisectwindow.ui index 1a5f8b3..61ae5be 100644 --- a/ui/bisectwindow.ui +++ b/ui/bisectwindow.ui @@ -107,6 +107,20 @@ + + + + + + Bus Number + + + + + + + + diff --git a/ui/canbridgewindow.ui b/ui/canbridgewindow.ui new file mode 100644 index 0000000..3818efa --- /dev/null +++ b/ui/canbridgewindow.ui @@ -0,0 +1,89 @@ + + + CANBridgeWindow + + + + 0 + 0 + 640 + 480 + + + + CAN Bridger + + + false + + + + + + + + Side 1 + + + + + + + + + + Enable forwarding + + + false + + + + + + + IDs to forward to side 2 + + + + + + + + + + + + + + Side 2 + + + + + + + + + + Enable forwarding + + + + + + + IDs to forward to side 1 + + + + + + + + + + + + + diff --git a/ui/connectionwindow.ui b/ui/connectionwindow.ui index 0cf7ea4..4f94395 100644 --- a/ui/connectionwindow.ui +++ b/ui/connectionwindow.ui @@ -64,9 +64,6 @@ Qt::AlignCenter - - - @@ -107,15 +104,38 @@ - + Save Bus Settings + + + + + + + Enable CAN FD: + + + + + + + + + + CAN FD Data Rate + + + + + + diff --git a/ui/dbcnodeduplicateeditor.ui b/ui/dbcnodeduplicateeditor.ui new file mode 100644 index 0000000..b236251 --- /dev/null +++ b/ui/dbcnodeduplicateeditor.ui @@ -0,0 +1,88 @@ + + + DBCNodeDuplicateEditor + + + + 0 + 0 + 314 + 144 + + + + + 0 + 0 + + + + Duplicate Node + + + + + 10 + 10 + 290 + 116 + + + + + + + New Node Name: + + + + + + + + + + Current Base ID: + + + + + + + true + + + true + + + + + + + New Base ID: + + + + + + + + + + Duplicate Node + + + + + + + Cancel + + + + + + + + + diff --git a/ui/dbcnoderebaseeditor.ui b/ui/dbcnoderebaseeditor.ui new file mode 100644 index 0000000..3c043cc --- /dev/null +++ b/ui/dbcnoderebaseeditor.ui @@ -0,0 +1,95 @@ + + + DBCNodeRebaseEditor + + + + 0 + 0 + 303 + 148 + + + + + 0 + 0 + + + + Rebase All Node Messages + + + + + 10 + 20 + 283 + 116 + + + + + + + Node Name: + + + + + + + true + + + true + + + + + + + Current Base ID: + + + + + + + true + + + true + + + + + + + New Base ID: + + + + + + + + + + Rebase All Messages in Node + + + + + + + Cancel + + + + + + + + + diff --git a/ui/dbcsignaleditor.ui b/ui/dbcsignaleditor.ui index 24361a6..df055dd 100644 --- a/ui/dbcsignaleditor.ui +++ b/ui/dbcsignaleditor.ui @@ -6,7 +6,7 @@ 0 0 - 967 + 995 644 @@ -17,46 +17,38 @@ - - - Signal Parameters - - - - - - Name: - - - - - - - - - - Start Bit: - - - - - - - - 0 - 0 - - - - - 300 - 300 - - - - - - + + + + + + + Name: + + + + + + + + + + + + + 0 + 0 + + + + + 300 + 300 + + + + + @@ -258,10 +250,23 @@ - txtName + txtBitLength + cbIntelFormat + comboType + txtScale + txtBias + txtMinVal + txtMaxVal + txtUnitName + comboReceiver rbNotMulti rbMultiplexed rbMultiplexor + rbExtended + txtMultiplexLow + txtMultiplexHigh + cbMultiplexParent + txtComment valuesTable diff --git a/ui/flowviewwindow.ui b/ui/flowviewwindow.ui index 71424c6..419b50b 100644 --- a/ui/flowviewwindow.ui +++ b/ui/flowviewwindow.ui @@ -7,7 +7,7 @@ 0 0 906 - 568 + 706 @@ -1037,9 +1037,9 @@ - 252 - 233 - 79 + 167 + 154 + 52 @@ -1048,9 +1048,9 @@ - 252 - 233 - 79 + 35 + 38 + 41 @@ -1071,7 +1071,6 @@ 13 - 75 true @@ -1131,7 +1130,6 @@ 13 - 75 true @@ -1191,7 +1189,6 @@ 13 - 75 true @@ -1251,7 +1248,6 @@ 13 - 75 true @@ -1311,7 +1307,6 @@ 13 - 75 true @@ -1371,7 +1366,6 @@ 13 - 75 true @@ -1431,7 +1425,6 @@ 13 - 75 true @@ -1491,7 +1484,6 @@ 13 - 75 true @@ -1553,7 +1545,6 @@ - 75 true diff --git a/ui/frameinfowindow.ui b/ui/frameinfowindow.ui index 406b02a..375c63e 100644 --- a/ui/frameinfowindow.ui +++ b/ui/frameinfowindow.ui @@ -6,567 +6,141 @@ 0 0 - 794 - 494 + 1142 + 651 Detailed Frame Information - + - - - + + + Qt::Horizontal + + + 8 + + + - + - - - Frame IDs: - - + + + + + Frame IDs: + + + + + + + Unique IDs + + + + + + + + 0 + 0 + + + + + 80 + 0 + + + + + - - - - 0 - 0 - - - - - 120 - 0 - - - - - - - - - - - - Details: - - - - - - - - 1 - - - - - - - - Save details to file - - - - - - - Interval Histogram - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - + + + + + Details: + + + + + + + Qt::ScrollBarAlwaysOn + + + + 1 + + + + + + + + Save details to file + + + + + + + Interval Histogram + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + - - - - - - - - - Bit Histograph - - - Qt::AlignCenter - - - - - - - - - - Bytes Graph - - - Qt::AlignCenter - - - - - - - - + + + - - - - - - - - 52 - 101 - 164 - - - - - - - - - 52 - 101 - 164 - - - - - - - - - 120 - 120 - 120 - - - - - - - - - 13 - 75 - true - - + + + + - 0 + Bytes Graph - - true + + Qt::AlignCenter - - - - - - - - 78 - 154 - 6 - - - - - - - - - 78 - 154 - 6 - - - - - - - - - 120 - 120 - 120 - - - - - - - - - 13 - 75 - true - - - - 1 - - - true - - - - - - - - - - - - 0 - 0 - 0 - - - - - - - - - 0 - 0 - 0 - - - - - - - - - 120 - 120 - 120 - - - - - - - - - 13 - 75 - true - - - - 2 - - - true - - - - - - - - - - - - 204 - 0 - 0 - - - - - - - - - 204 - 0 - 0 - - - - - - - - - 120 - 120 - 120 - - - - - - - - - 13 - 75 - true - - - - 3 - - - true - - - - - - - - - - - - 136 - 138 - 133 - - - - - - - - - 136 - 138 - 133 - - - - - - - - - 120 - 120 - 120 - - - - - - - - - 13 - 75 - true - - - - 4 - - - true - - - - - - - - - - - - 252 - 233 - 79 - - - - - - - - - 252 - 233 - 79 - - - - - - - - - 120 - 120 - 120 - - - - - - - - - 13 - 75 - true - - - - 5 - - - true - - - - - - - - - - - - 114 - 159 - 207 - - - - - - - - - 114 - 159 - 207 - - - - - - - - - 120 - 120 - 120 - - - - - - - - - 13 - 75 - true - - - - 6 - - - true - - - - - - - - - - - - 173 - 127 - 168 - - - - - - - - - 173 - 127 - 168 - - - - - - - - - 120 - 120 - 120 - - - - - - - - - 13 - 75 - true - - - - 7 - - - true - - + - - + + diff --git a/ui/frameplaybackwindow.ui b/ui/frameplaybackwindow.ui index b49c350..a8c3d8a 100644 --- a/ui/frameplaybackwindow.ui +++ b/ui/frameplaybackwindow.ui @@ -107,6 +107,13 @@ + + + + Wait for traffic before starting playback + + + diff --git a/ui/framesenderwindow.ui b/ui/framesenderwindow.ui index 4e027bd..0f0bebf 100644 --- a/ui/framesenderwindow.ui +++ b/ui/framesenderwindow.ui @@ -6,7 +6,7 @@ 0 0 - 1008 + 1350 611 diff --git a/ui/fuzzingwindow.ui b/ui/fuzzingwindow.ui index ba6e22f..1600b97 100644 --- a/ui/fuzzingwindow.ui +++ b/ui/fuzzingwindow.ui @@ -6,8 +6,8 @@ 0 0 - 760 - 671 + 1188 + 701 @@ -45,7 +45,7 @@ - 8 + 64 8 @@ -264,7 +264,6 @@ - 75 true @@ -283,7 +282,6 @@ - 75 true @@ -299,7 +297,6 @@ - 75 true @@ -315,7 +312,6 @@ - 75 true @@ -331,7 +327,6 @@ - 75 true @@ -347,7 +342,6 @@ - 75 true @@ -363,7 +357,6 @@ - 75 true @@ -379,7 +372,6 @@ - 75 true @@ -391,6 +383,86 @@ + + + + 8 + + + + + + + + + + 9 + + + + + + + + + + 10 + + + + + + + + + + 11 + + + + + + + + + + 12 + + + + + + + + + + 13 + + + + + + + + + + 14 + + + + + + + + + + 15 + + + + + + @@ -427,7 +499,6 @@ 11 - 75 true diff --git a/helpwindow.ui b/ui/helpwindow.ui similarity index 100% rename from helpwindow.ui rename to ui/helpwindow.ui diff --git a/ui/mainsettingsdialog.ui b/ui/mainsettingsdialog.ui index 586e4e0..e65e91c 100644 --- a/ui/mainsettingsdialog.ui +++ b/ui/mainsettingsdialog.ui @@ -7,7 +7,7 @@ 0 0 965 - 678 + 852 @@ -29,6 +29,30 @@ + + + + + + Maximum data bytes per line + + + + + + + 8 + + + 64 + + + 8 + + + + + @@ -80,6 +104,39 @@ + + + + 0 + + + 0 + + + + + CAN Frame Pre-allocation Size + + + + + + + 100000 + + + 1000000000 + + + 100000 + + + 10000000 + + + + + @@ -152,15 +209,36 @@ + + + + Use absolute time in decoded CSV output + + + - Font Size + Font + + + + Use fixed-width font in tables + + + + + + + Size + + + @@ -284,14 +362,14 @@ - Label DBC Signals by default + Label DBC messages by default - Ignore DBC signal colors + Ignore DBC message colors diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index 4fb01d8..72d9cec 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -6,8 +6,8 @@ 0 0 - 800 - 682 + 1177 + 717 @@ -26,329 +26,350 @@ SavvyCAN - + - - - - 11 - 50 - false - - - - true - - - QAbstractItemView::ScrollPerPixel - - - false - - - - - + - - - true - - - Remote Connection Active: - - - Qt::AlignCenter - - - - - - - true - - - - 1000 - 50 - - - - - 150 - 50 - - - - - 50 - false - - - - 8 - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - true - - - - - - - - - - - - 255 - 0 - 0 - - - - - - - - - 255 - 0 - 0 - - - - - - - - - 120 - 120 - 120 - - - - - - - - - 11 - 75 - true - true - - - - - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - Total Frames Captured: - - - - - - - - 0 - 0 - - - - - - - - - - - - 0 - 0 - - - - Frames Per Second: - - - - - - - - 0 - 0 - - - - - - - - - - - Suspend Capturing - - - false - - - - - - - Normalize Frame Timing - - - - - - - Clear Frames - - - - - - - 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 - - - - - - + - + + + + 11 + false + + + + true + + + QAbstractItemView::ScrollPerPixel + + + false + + + false + + + + + + + + + + + + + + true + - All + Remote Connection Active: + + + Qt::AlignCenter - - - None + + + 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 + + + + 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 + + + + + + + Frame Filtering: + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 300 + 160 + + + + + 1000 + 16777215 + + + + false + + + + + + + + + All + + + + + + + None + + + + + @@ -360,8 +381,8 @@ 0 0 - 800 - 22 + 1177 + 26 @@ -392,6 +413,7 @@ + @@ -408,6 +430,7 @@ + @@ -608,6 +631,16 @@ DBC Comparison + + + Save Decoded Frames CSV + + + + + CAN Bridge + + diff --git a/ui/newconnectiondialog.ui b/ui/newconnectiondialog.ui index faa847a..e459583 100644 --- a/ui/newconnectiondialog.ui +++ b/ui/newconnectiondialog.ui @@ -6,8 +6,8 @@ 0 0 - 356 - 282 + 426 + 540 @@ -23,6 +23,30 @@ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + socketcand (SocketCAN over Ethernet) + + + + + + + false + + + QT SerialBus Devices (SocketCAN, PeakCAN, etc) + + + + + + + MQTT Connection + + + @@ -40,27 +64,24 @@ - - - - false - + + - QT SerialBus Devices (SocketCAN, PeakCAN, etc) + LAWICEL / SLCAN Serial - - + + - socketcand (SocketCAN over Ethernet) + CANserver - - + + - MQTT Connection + CANlogserver @@ -109,6 +130,56 @@ + + + + Serial Port Speed: + + + + + + + + + + CAN Bus Speed: + + + + + + + + + + CAN-FD + + + + + + + CAN-FD data rate + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + diff --git a/ui/newgraphdialog.ui b/ui/newgraphdialog.ui index 28687a6..9b2f036 100644 --- a/ui/newgraphdialog.ui +++ b/ui/newgraphdialog.ui @@ -7,7 +7,7 @@ 0 0 681 - 806 + 828 @@ -142,38 +142,38 @@ - + Only Points - + - + Point Style - + - + Line Thickness - + 15 @@ -183,14 +183,14 @@ - + Line Color - + true @@ -203,14 +203,14 @@ - + Fill Color - + true @@ -223,7 +223,7 @@ - + Add this graph @@ -243,6 +243,20 @@ + + + + Associated Bus + + + + + + + -1 + + + @@ -263,6 +277,19 @@ + + + + Node: + + + Qt::AlignCenter + + + + + + @@ -313,7 +340,6 @@ 12 - 75 true diff --git a/ui/scriptingwindow.ui b/ui/scriptingwindow.ui index 35c0b9e..5623804 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 diff --git a/ui/signalviewerwindow.ui b/ui/signalviewerwindow.ui index b5b5d5d..8f1c75d 100644 --- a/ui/signalviewerwindow.ui +++ b/ui/signalviewerwindow.ui @@ -18,8 +18,11 @@ + + true + - 2 + 3 300 @@ -32,6 +35,7 @@ + @@ -45,6 +49,16 @@ + + + + Node + + + + + + @@ -85,6 +99,34 @@ + + + + Save View + + + + + + + Load View + + + + + + + Append to View + + + + + + + Clear View + + + diff --git a/ui/udsscanwindow.ui b/ui/udsscanwindow.ui index cae150e..dd8cf66 100644 --- a/ui/udsscanwindow.ui +++ b/ui/udsscanwindow.ui @@ -6,61 +6,346 @@ 0 0 - 742 - 628 + 1053 + 624 UDS Scanner - + - - - Results - - - - - - - false - - - - 1 - - - - - - - - Save Scan Results - - - - - + - + + + Scans To Run + + + + + + + + - + - Starting ID + Add - + + + Delete + + + + + + + + + + + Load + + + + + + + Save + + + + + + + + + + + + + + + + + Starting ID + + + + + + + 0x + + + 536870911 + + + 2016 + + + 16 + + + + + + + + + + + Ending ID + + + + + + + 0x + + + 536870911 + + + 2023 + + + 16 + + + + + + + + + + + Show "No Reply" + + + true + + + + + + + Allow adaptive reply offset + + + true + + + + + + + + + + + + + Bus to scan + + + + + + + + + + Maximum reply delay (ms) + + + + + + + 50 + + + 10000 + + + 10 + + + 100 + + + + + + + Reply Offset: + + + + + + + false + + + -128 + + + 128 + + + 8 + + + + + + + + + + + + 0 + 0 + + + + Scan Type: + + + + + + + + 0 + 0 + + + + + + + + Run test in session type: + + + + + + + false + + + + + + + + + + + + + + Subfunct Len: + + + + + + + false + + + 1 + + + 4 + + + 1 + + + + + + + Lower Bound: + + + + + + + false + 0x - 536870911 + 255 + + + 16 + + + + + + + Upper Bound: + + + + + + + false + + + 0x + + + 255 - 2016 + 255 + + + 16 + + + + + + + Increment + + + + + + + false + + + 0x + + + 1024 + + + 1 16 @@ -70,24 +355,50 @@ - + - + - Ending ID + Lower Service: - + + + false + 0x - 536870911 + 255 + + + 16 + + + + + + + Upper Service: + + + + + + + false + + + 0x + + + 255 - 2023 + 255 16 @@ -97,323 +408,101 @@ - - - Show Tests with no Replies + + + + 0 + 20 + - - true + + QFrame::Plain - - - - - - - - - - Reply Offset: - - - - - - - false - - - -128 - - - 128 - - - 8 - - - - - - - Allow adaptive reply offset - - - true - - - - - - - - - - - Bus to scan - - - - - - - - - - Maximum reply delay (ms) - - - - - - - 50 - - - 10000 - - - 10 - - - 100 - - - - - - - - - - - Scan Types: - - - - - - - Tester Present - - - - - - - Session Ctrl - - - - - - - ECU Reset - - - - - - - Security Access - - - - - - - - - - - Read By ID - - - - - - - Read By Addr - - - - - - - Wildcard - - - - - - - - - - - Lower Service: - - - - - - - false - - - 0x - - - 255 - - - 16 - - - - - - - Upper Service: - - - - - - - false - - - 0x - - - 255 - - - 255 - - - 16 - - - - - - - - - - - Subfunction Bytes: - - - - - - - false - - - 1 - - + 4 - - 1 + + Qt::Horizontal - + - Lower Bound: + Results + + + Qt::AlignCenter - + + + false + + + + 1 + + + + + + false - - 0x + + 0 - - 255 + + true - - 16 - - - - - - - Upper Bound: - - - - - - + false - - 0x - - - 255 - - - 255 - - - 16 + + + + + + + + Run All Scans + + + + + + + Run Selected Scan + + + + + + + + + Save Scan Results - - - - 0 - - - - - - - Start Scan - - - spinStartID spinEndID - ckShowNoReply - spinReplyOffset - cbAllowAdaptiveOffset cbBuses spinDelay - ckTester - ckSession - ckReset - ckSecurity - ckReadByID - ckReadByAddr - ckWildcard spinLowerService spinUpperService spinNumBytes spinLowerSubfunc spinUpperSubfunc - btnScan - treeResults - btnSaveResults diff --git a/utility.cpp b/utility.cpp index a677427..996ece1 100644 --- a/utility.cpp +++ b/utility.cpp @@ -1,7 +1,6 @@ #include "utility.h" bool Utility::decimalMode = false; -bool Utility::secondsMode = true; -bool Utility::sysTimeMode = false; -bool Utility::millisMode = false; QString Utility::timeFormat = "MMM-dd HH:mm:ss.zzz"; +TimeStyle Utility::timeStyle = TS_MICROS; +QString Utility::fullyQualifiedNameSeperator = "::"; diff --git a/utility.h b/utility.h index 0f31ca3..88bbb31 100644 --- a/utility.h +++ b/utility.h @@ -8,18 +8,40 @@ #include #include #include +#include +#include //#include +enum TimeStyle +{ + TS_SECONDS, + TS_MICROS, + TS_MILLIS, + TS_CLOCK +}; + class Utility { public: static bool decimalMode; - static bool secondsMode; - static bool millisMode; - static bool sysTimeMode; + static TimeStyle timeStyle; static QString timeFormat; + static QString fullyQualifiedNameSeperator; + + static void SetComboBoxItemEnabled(QComboBox * comboBox, int index, bool enabled) + { + auto * model = qobject_cast(comboBox->model()); + assert(model); + if(!model) return; + + auto * item = model->item(index); + assert(item); + if(!item) return; + item->setEnabled(enabled); + } + //determines whether the window position is within any available screens. If it is not we default //back to 0,0 which is going to be on screen. This fixes a problem where some operating systems would //otherwise let you put windows on a second monitor, disconnect that monitor, and still put windows on it. @@ -146,17 +168,29 @@ public: return output; } + static QString formatByteAsHex(uint8_t value) + { + return QString::number(value, 16).toUpper().rightJustified(2,'0'); + } + static QVariant formatTimestamp(uint64_t timestamp) { - if (!sysTimeMode) { - if (millisMode) return (double)timestamp / 1000.0; - if (!secondsMode) return (unsigned long long)(timestamp); - else - { - return (double)timestamp / 1000000.0; - } + switch (timeStyle) + { + case TS_CLOCK: + return QDateTime::fromMSecsSinceEpoch(timestamp / 1000); + break; + case TS_MILLIS: + return (double)timestamp / 1000.0; + break; + case TS_MICROS: + return (unsigned long long)(timestamp); + break; + case TS_SECONDS: + return (double)timestamp / 1000000.0; + break; } - else return QDateTime::fromMSecsSinceEpoch(timestamp / 1000); + return QVariant(); // should never happen } //parses the input string to grab as much of it as possible while staying alpha numeric @@ -194,27 +228,64 @@ public: return builder; } + static int getByteFromBitPosition(int bitPos) + { + return bitPos / 8; + } + + static int getBitFromBitPosition(int bitPos) + { + return bitPos & 7; + } + //simple linear interpolation between value1 and value2. sample point is 0.0 to 1.0 static double Lerp(double value1, double value2, double samplePoint) { return (value1 * (1.0 - samplePoint)) + (value2 * samplePoint); } + /* A unified function that can extract a signal from the (up to) 64 bits of data bytes in a CAN frame + * handles both little and big endian signals (and floats too). + */ static int64_t processIntegerSignal(const QByteArray data, int startBit, int sigSize, bool littleEndian, bool isSigned) { - int64_t result = 0; - int bit; + uint64_t result = 0; + int bit = 0; int maxBytes = (startBit + sigSize) / 8; - if (data.size() < maxBytes) return 0; + if (data.size() < maxBytes) return 0; //if signal extends past the end of data then abort if (littleEndian) { +/* + int currByte = (startBit) / 8; + int currOffset = startBit - (currByte * 8); + int remainingBits = qMax(0, (sigSize - (8 - currOffset)) ); + int prevBits = qMin((8 - currOffset), sigSize); + result = data[currByte] >> currOffset; + result &= ( (1 << sigSize) - 1); //doesn't hurt to do this even if sigSize is way larger than the # of bits we've got so far + + while (remainingBits > 0) + { + currByte++; + if (remainingBits >= 8) //use this entire byte, its easy + { + result += data[currByte] << prevBits; + remainingBits -= 8; + prevBits += 8; + } + else //use only part of this byte. We're going to need to mask it + { + result += ((data[currByte] & ((1 << remainingBits) - 1) ) << prevBits); + remainingBits = 0; + } + }*/ + bit = startBit; for (int bitpos = 0; bitpos < sigSize; bitpos++) { - if (bit < 64) { + if (bit < 512) { int bytePos = bit / 8; if (bytePos >= data.count()) return 0; //error! if (data[bit / 8] & (1 << (bit % 8))) @@ -222,13 +293,14 @@ public: } bit++; } + } else //motorola / big endian mode { bit = startBit; for (int bitpos = 0; bitpos < sigSize; bitpos++) { - if (bit < 64) { + if (bit < 512) { int bytePos = bit / 8; if (bytePos >= data.count()) return 0; //error! if (data[bit / 8] & (1 << (bit % 8))) @@ -244,7 +316,7 @@ public: if (isSigned) { - int64_t mask = (1ULL << (sigSize - 1)); + uint64_t mask = (1ULL << (sigSize - 1)); if ((result & mask) == mask) //is the highest bit possible for this signal size set? { /* @@ -266,6 +338,7 @@ public: */ uint64_t signedMask = ~((1ULL << sigSize) - 1); result = (-1LL & signedMask) | result; + return (int64_t)(result); } }